|
|
@@ -33,22 +33,22 @@ from earwigbot.tasks import BaseTask |
|
|
|
__all__ = ["CommandManager", "TaskManager"] |
|
|
|
|
|
|
|
class _BaseManager(object): |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
class CommandManager(_BaseManager): |
|
|
|
def __init__(self, bot): |
|
|
|
def __init__(self, bot, name, attribute, base): |
|
|
|
self.bot = bot |
|
|
|
self.logger = bot.logger.getChild("commands") |
|
|
|
self._commands = {} |
|
|
|
self._command_access_lock = Lock() |
|
|
|
self.logger = bot.logger.getChild(name) |
|
|
|
|
|
|
|
self._resources = {} |
|
|
|
self._resource_name = name # e.g. "commands" or "tasks" |
|
|
|
self._resource_attribute = attribute # e.g. "Command" or "Task" |
|
|
|
self._resource_base = base # e.g. BaseCommand or BaseTask |
|
|
|
self._resource_access_lock = Lock() |
|
|
|
|
|
|
|
def __iter__(self): |
|
|
|
for name in self._commands: |
|
|
|
for name in self._resources: |
|
|
|
yield name |
|
|
|
|
|
|
|
def _load_command(self, name, path): |
|
|
|
"""Load a specific command from a module, identified by name and path. |
|
|
|
def _load_resource(self, name, path): |
|
|
|
"""Load a specific resource from a module, identified by name and path. |
|
|
|
|
|
|
|
We'll first try to import it using imp magic, and if that works, make |
|
|
|
an instance of the 'Command' class inside (assuming it is an instance |
|
|
@@ -59,30 +59,30 @@ class CommandManager(_BaseManager): |
|
|
|
try: |
|
|
|
module = imp.load_module(name, f, path, desc) |
|
|
|
except Exception: |
|
|
|
e = "Couldn't load module {0} from {1}" |
|
|
|
e = "Couldn't load module {0} (from {1})" |
|
|
|
self.logger.exception(e.format(name, path)) |
|
|
|
return |
|
|
|
finally: |
|
|
|
f.close() |
|
|
|
|
|
|
|
attr = self._resource_attribute |
|
|
|
if not hasattr(module, attr): |
|
|
|
return # No resources in this module |
|
|
|
resource_class = getattr(module, attr) |
|
|
|
try: |
|
|
|
command_class = module.Command |
|
|
|
except AttributeError: |
|
|
|
return # No command in this module |
|
|
|
try: |
|
|
|
command = command_class(self.bot) |
|
|
|
resource = resource_class(self.bot) # Create instance of resource |
|
|
|
except Exception: |
|
|
|
e = "Error initializing Command() class in {0} (from {1})" |
|
|
|
self.logger.exception(e.format(name, path)) |
|
|
|
e = "Error instantiating {0} class in {1} (from {2})" |
|
|
|
self.logger.exception(e.format(attr, name, path)) |
|
|
|
return |
|
|
|
if not isinstance(command, BaseCommand): |
|
|
|
if not isinstance(resource, self._resource_base): |
|
|
|
return |
|
|
|
|
|
|
|
self._commands[command.name] = command |
|
|
|
self.logger.debug("Loaded command {0}".format(command.name)) |
|
|
|
self._resources[resource.name] = resource |
|
|
|
self.logger.debug("Loaded {0} {1}".format(attr.lower(), resource.name)) |
|
|
|
|
|
|
|
def _load_directory(self, dir): |
|
|
|
"""Load all valid commands in a given directory.""" |
|
|
|
"""Load all valid resources in a given directory.""" |
|
|
|
processed = [] |
|
|
|
for name in listdir(dir): |
|
|
|
if not name.endswith(".py") and not name.endswith(".pyc"): |
|
|
@@ -91,26 +91,40 @@ class CommandManager(_BaseManager): |
|
|
|
continue |
|
|
|
modname = sub("\.pyc?$", "", name) # Remove extension |
|
|
|
if modname not in processed: |
|
|
|
self._load_command(modname, dir) |
|
|
|
self._load_resource(modname, dir) |
|
|
|
processed.append(modname) |
|
|
|
|
|
|
|
def load(self): |
|
|
|
"""Load (or reload) all valid commands into self._commands.""" |
|
|
|
with self._command_access_lock: |
|
|
|
self._commands.clear() |
|
|
|
builtin_dir = path.join(path.dirname(__file__), "commands") |
|
|
|
plugins_dir = path.join(self.bot.config.root_dir, "commands") |
|
|
|
self._load_directory(builtin_dir) # Built-in commands |
|
|
|
self._load_directory(plugins_dir) # Custom commands, aka plugins |
|
|
|
|
|
|
|
msg = "Loaded {0} commands: {1}" |
|
|
|
commands = ", ".join(self._commands.keys()) |
|
|
|
self.logger.info(msg.format(len(self._commands), commands)) |
|
|
|
"""Load (or reload) all valid resources into self._resources.""" |
|
|
|
name = self._resource_name # e.g. "commands" or "tasks" |
|
|
|
with self._resource_access_lock: |
|
|
|
self._resources.clear() |
|
|
|
builtin_dir = path.join(path.dirname(__file__), name) |
|
|
|
plugins_dir = path.join(self.bot.config.root_dir, name) |
|
|
|
self._load_directory(builtin_dir) # Built-in resources |
|
|
|
self._load_directory(plugins_dir) # Custom resources, aka plugins |
|
|
|
|
|
|
|
msg = "Loaded {0} {1}: {2}" |
|
|
|
resources = ", ".join(self._resources.keys()) |
|
|
|
self.logger.info(msg.format(len(self._resources), name, resources)) |
|
|
|
|
|
|
|
def get(self, key): |
|
|
|
"""Return the class instance associated with a certain resource. |
|
|
|
|
|
|
|
Will raise KeyError if the resource (command or task) is not found. |
|
|
|
""" |
|
|
|
return self._resources[key] |
|
|
|
|
|
|
|
|
|
|
|
class CommandManager(_BaseManager): |
|
|
|
def __init__(self, bot): |
|
|
|
super(CommandManager, self).__init__(bot, "commands", "Command", |
|
|
|
BaseCommand) |
|
|
|
|
|
|
|
def check(self, hook, data): |
|
|
|
"""Given an IRC event, check if there's anything we can respond to.""" |
|
|
|
with self._command_access_lock: |
|
|
|
for command in self._commands.values(): |
|
|
|
with self._resource_access_lock: |
|
|
|
for command in self._resources.values(): |
|
|
|
if hook in command.hooks: |
|
|
|
if command.check(data): |
|
|
|
try: |
|
|
@@ -120,24 +134,10 @@ class CommandManager(_BaseManager): |
|
|
|
self.logger.exception(e.format(data.command)) |
|
|
|
break |
|
|
|
|
|
|
|
def get(self, command_name): |
|
|
|
"""Return the class instance associated with a certain command name. |
|
|
|
|
|
|
|
Will raise KeyError if the command is not found. |
|
|
|
""" |
|
|
|
return self._command[command_name] |
|
|
|
|
|
|
|
|
|
|
|
class TaskManager(_BaseManager): |
|
|
|
def __init__(self, bot): |
|
|
|
self.bot = bot |
|
|
|
self.logger = bot.logger.getChild("tasks") |
|
|
|
self._tasks = {} |
|
|
|
self._task_access_lock = Lock() |
|
|
|
|
|
|
|
def __iter__(self): |
|
|
|
for name in self._tasks: |
|
|
|
yield name |
|
|
|
super(TaskManager, self).__init__(bot, "tasks", "Task", BaseTask) |
|
|
|
|
|
|
|
def _wrapper(self, task, **kwargs): |
|
|
|
"""Wrapper for task classes: run the task and catch any errors.""" |
|
|
@@ -150,74 +150,14 @@ class TaskManager(_BaseManager): |
|
|
|
msg = "Task '{0}' finished without error" |
|
|
|
self.logger.info(msg.format(task.name)) |
|
|
|
|
|
|
|
def _load_task(self, name, path): |
|
|
|
"""Load a specific task from a module, identified by name and path. |
|
|
|
|
|
|
|
We'll first try to import it using imp magic, and if that works, make |
|
|
|
an instance of the 'Task' class inside (assuming it is an instance of |
|
|
|
BaseTask), add it to self._tasks, and log the addition. Any problems |
|
|
|
along the way will either be ignored or logged. |
|
|
|
""" |
|
|
|
f, path, desc = imp.find_module(name, [path]) |
|
|
|
try: |
|
|
|
module = imp.load_module(name, f, path, desc) |
|
|
|
except Exception: |
|
|
|
e = "Couldn't load module {0} from {1}" |
|
|
|
self.logger.exception(e.format(name, path)) |
|
|
|
return |
|
|
|
finally: |
|
|
|
f.close() |
|
|
|
|
|
|
|
try: |
|
|
|
task_class = module.Task |
|
|
|
except AttributeError: |
|
|
|
return # No task in this module |
|
|
|
try: |
|
|
|
task = task_class(self.bot) |
|
|
|
except Exception: |
|
|
|
e = "Error initializing Task() class in {0} (from {1})" |
|
|
|
self.logger.exception(e.format(name, path)) |
|
|
|
return |
|
|
|
if not isinstance(task, BaseTask): |
|
|
|
return |
|
|
|
|
|
|
|
self._tasks[task.name] = task |
|
|
|
self.logger.debug("Loaded task {0}".format(task.name)) |
|
|
|
|
|
|
|
def _load_directory(self, dir): |
|
|
|
"""Load all valid tasks in a given directory.""" |
|
|
|
processed = [] |
|
|
|
for name in listdir(dir): |
|
|
|
if not name.endswith(".py") and not name.endswith(".pyc"): |
|
|
|
continue |
|
|
|
if name.startswith("_") or name.startswith("."): |
|
|
|
continue |
|
|
|
modname = sub("\.pyc?$", "", name) # Remove extension |
|
|
|
if modname not in processed: |
|
|
|
self._load_task(modname, dir) |
|
|
|
processed.append(modname) |
|
|
|
|
|
|
|
def load(self): |
|
|
|
"""Load (or reload) all valid tasks into self._tasks.""" |
|
|
|
with self._task_access_lock: |
|
|
|
self._tasks.clear() |
|
|
|
builtin_dir = path.join(path.dirname(__file__), "tasks") |
|
|
|
plugins_dir = path.join(self.bot.config.root_dir, "tasks") |
|
|
|
self._load_directory(builtin_dir) # Built-in tasks |
|
|
|
self._load_directory(plugins_dir) # Custom tasks, aka plugins |
|
|
|
|
|
|
|
msg = "Loaded {0} tasks: {1}" |
|
|
|
tasks = ', '.join(self._tasks.keys()) |
|
|
|
self.logger.info(msg.format(len(self._tasks), tasks)) |
|
|
|
|
|
|
|
def start(self, task_name, **kwargs): |
|
|
|
"""Start a given task in a new thread. kwargs are passed to task.run""" |
|
|
|
msg = "Starting task '{0}' in a new thread" |
|
|
|
self.logger.info(msg.format(task_name)) |
|
|
|
|
|
|
|
with self._task_access_lock: |
|
|
|
with self._resource_access_lock: |
|
|
|
try: |
|
|
|
task = self._tasks[task_name] |
|
|
|
task = self._resources[task_name] |
|
|
|
except KeyError: |
|
|
|
e = "Couldn't find task '{0}':" |
|
|
|
self.logger.error(e.format(task_name)) |
|
|
@@ -241,10 +181,3 @@ class TaskManager(_BaseManager): |
|
|
|
self.start(task[0], **task[1]) # so pass those to start |
|
|
|
else: # Otherwise, just pass task_name |
|
|
|
self.start(task) |
|
|
|
|
|
|
|
def get(self, task_name): |
|
|
|
"""Return the class instance associated with a certain task name. |
|
|
|
|
|
|
|
Will raise KeyError if the task is not found. |
|
|
|
""" |
|
|
|
return self._tasks[task_name] |