| @@ -32,7 +32,24 @@ from earwigbot.tasks import BaseTask | |||||
| __all__ = ["CommandManager", "TaskManager"] | __all__ = ["CommandManager", "TaskManager"] | ||||
| class _BaseManager(object): | |||||
| class _ResourceManager(object): | |||||
| """ | |||||
| EarwigBot's Base Resource Manager | |||||
| Resources are essentially objects dynamically loaded by the bot, both | |||||
| packaged with it (built-in resources) and created by users (plugins, aka | |||||
| custom resources). Currently, the only two types of resources are IRC | |||||
| commands and bot tasks. These are both loaded from two locations: the | |||||
| earwigbot.commands and earwigbot.tasks packages, and the commands/ and | |||||
| tasks/ directories within the bot's working directory. | |||||
| This class handles the low-level tasks of (re)loading resources via load(), | |||||
| retrieving specific resources via get(), and iterating over all resources | |||||
| via __iter__(). If iterating over resources, it is recommended to acquire | |||||
| self.lock beforehand and release it afterwards (alternatively, wrap your | |||||
| code in a `with` statement) so an attempt at reloading resources in another | |||||
| thread won't disrupt your iteration. | |||||
| """ | |||||
| def __init__(self, bot, name, attribute, base): | def __init__(self, bot, name, attribute, base): | ||||
| self.bot = bot | self.bot = bot | ||||
| self.logger = bot.logger.getChild(name) | self.logger = bot.logger.getChild(name) | ||||
| @@ -43,6 +60,10 @@ class _BaseManager(object): | |||||
| self._resource_base = base # e.g. BaseCommand or BaseTask | self._resource_base = base # e.g. BaseCommand or BaseTask | ||||
| self._resource_access_lock = Lock() | self._resource_access_lock = Lock() | ||||
| @property | |||||
| def lock(self): | |||||
| return self._resource_access_lock | |||||
| def __iter__(self): | def __iter__(self): | ||||
| for name in self._resources: | for name in self._resources: | ||||
| yield name | yield name | ||||
| @@ -97,7 +118,7 @@ class _BaseManager(object): | |||||
| def load(self): | def load(self): | ||||
| """Load (or reload) all valid resources into self._resources.""" | """Load (or reload) all valid resources into self._resources.""" | ||||
| name = self._resource_name # e.g. "commands" or "tasks" | name = self._resource_name # e.g. "commands" or "tasks" | ||||
| with self._resource_access_lock: | |||||
| with self.lock: | |||||
| self._resources.clear() | self._resources.clear() | ||||
| builtin_dir = path.join(path.dirname(__file__), name) | builtin_dir = path.join(path.dirname(__file__), name) | ||||
| plugins_dir = path.join(self.bot.config.root_dir, name) | plugins_dir = path.join(self.bot.config.root_dir, name) | ||||
| @@ -116,15 +137,20 @@ class _BaseManager(object): | |||||
| return self._resources[key] | return self._resources[key] | ||||
| class CommandManager(_BaseManager): | |||||
| class CommandManager(_ResourceManager): | |||||
| """ | |||||
| EarwigBot's IRC Command Manager | |||||
| Manages (i.e., loads, reloads, and calls) IRC commands. | |||||
| """ | |||||
| def __init__(self, bot): | def __init__(self, bot): | ||||
| super(CommandManager, self).__init__(bot, "commands", "Command", | |||||
| BaseCommand) | |||||
| base = super(CommandManager, self) | |||||
| base.__init__(bot, "commands", "Command", BaseCommand) | |||||
| def check(self, hook, data): | def check(self, hook, data): | ||||
| """Given an IRC event, check if there's anything we can respond to.""" | """Given an IRC event, check if there's anything we can respond to.""" | ||||
| with self._resource_access_lock: | |||||
| for command in self._resources.values(): | |||||
| with self.lock: | |||||
| for command in self._resources.itervalues(): | |||||
| if hook in command.hooks: | if hook in command.hooks: | ||||
| if command.check(data): | if command.check(data): | ||||
| try: | try: | ||||
| @@ -135,7 +161,12 @@ class CommandManager(_BaseManager): | |||||
| break | break | ||||
| class TaskManager(_BaseManager): | |||||
| class TaskManager(_ResourceManager): | |||||
| """ | |||||
| EarwigBot's Bot Task Manager | |||||
| Manages (i.e., loads, reloads, schedules, and runs) wiki bot tasks. | |||||
| """ | |||||
| def __init__(self, bot): | def __init__(self, bot): | ||||
| super(TaskManager, self).__init__(bot, "tasks", "Task", BaseTask) | super(TaskManager, self).__init__(bot, "tasks", "Task", BaseTask) | ||||
| @@ -155,13 +186,12 @@ class TaskManager(_BaseManager): | |||||
| msg = "Starting task '{0}' in a new thread" | msg = "Starting task '{0}' in a new thread" | ||||
| self.logger.info(msg.format(task_name)) | self.logger.info(msg.format(task_name)) | ||||
| with self._resource_access_lock: | |||||
| try: | |||||
| task = self._resources[task_name] | |||||
| except KeyError: | |||||
| e = "Couldn't find task '{0}':" | |||||
| self.logger.error(e.format(task_name)) | |||||
| return | |||||
| try: | |||||
| task = self.get(task_name) | |||||
| except KeyError: | |||||
| e = "Couldn't find task '{0}':" | |||||
| self.logger.error(e.format(task_name)) | |||||
| return | |||||
| task_thread = Thread(target=self._wrapper, args=(task,), kwargs=kwargs) | task_thread = Thread(target=self._wrapper, args=(task,), kwargs=kwargs) | ||||
| start_time = strftime("%b %d %H:%M:%S") | start_time = strftime("%b %d %H:%M:%S") | ||||