| @@ -166,6 +166,13 @@ for and what they should be overridden with, but these are the basics: | |||
| - Class attribute ``name`` is the name of the command. This must be specified. | |||
| - Class attribute ``commands`` is a list of names that will trigger this | |||
| command. It defaults to the command's ``name``, but you can override it with | |||
| multiple names to serve as aliases. This is handled by the default | |||
| ``check()`` implementation (see below), so if ``check()`` is overridden, this | |||
| is ignored by everything except the help_ command (so ``!help alias`` will | |||
| trigger help for the actual command). | |||
| - Class attribute ``hooks`` is a list of the "IRC events" that this command | |||
| might respond to. It defaults to ``["msg"]``, but options include | |||
| ``"msg_private"`` (for private messages only), ``"msg_public"`` (for channel | |||
| @@ -181,10 +188,12 @@ for and what they should be overridden with, but these are the basics: | |||
| - Method ``check()`` is passed a ``Data`` [2]_ object, and should return | |||
| ``True`` if you want to respond to this message, or ``False`` otherwise. The | |||
| default behavior is to return ``True`` only if ``data.is_command`` is | |||
| ``True`` and ``data.command == self.name``, which is suitable for most cases. | |||
| A common, straightforward reason for overriding is if a command has aliases | |||
| (see chanops_ for an example). Note that by returning ``True``, you prevent | |||
| any other commands from responding to this message. | |||
| ``True`` and ``data.command`` ``==`` ``self.name`` (or ``data.command`` is in | |||
| ``self.commands`` if that list is overriden; see above), which is suitable | |||
| for most cases. A possible reason for overriding is if you want to do | |||
| something in response to events from a specific channel only. Note that by | |||
| returning ``True``, you prevent any other commands from responding to this | |||
| message. | |||
| - Method ``process()`` is passed the same ``Data`` object as ``check()``, but | |||
| only if ``check()`` returned ``True``. This is where the bulk of your command | |||
| @@ -230,7 +239,7 @@ and what they should be overridden with, but these are the basics: | |||
| ``"([[WP:BOT|Bot]]; [[User:EarwigBot#Task $1|Task $1]]): $2"``, which the | |||
| task class's ``make_summary(comment)`` method will take and replace ``$1`` | |||
| with the task number and ``$2`` with the details of the edit. | |||
| Additionally, ``shutoff_enabled()`` (which checks whether the bot has been | |||
| told to stop on-wiki by checking the content of a particular page) can check | |||
| a different page for each task using similar variables. EarwigBot's | |||
| @@ -510,6 +519,7 @@ Footnotes | |||
| .. _earwigbot.bot.Bot: https://github.com/earwig/earwigbot/blob/develop/earwigbot/bot.py | |||
| .. _earwigbot.config.BotConfig: https://github.com/earwig/earwigbot/blob/develop/earwigbot/config.py | |||
| .. _earwigbot.commands.BaseCommand: https://github.com/earwig/earwigbot/blob/develop/earwigbot/commands/__init__.py | |||
| .. _help: https://github.com/earwig/earwigbot/blob/develop/earwigbot/commands/help.py | |||
| .. _afc_status: https://github.com/earwig/earwigbot-plugins/blob/develop/commands/afc_status.py | |||
| .. _chanops: https://github.com/earwig/earwigbot/blob/develop/earwigbot/commands/chanops.py | |||
| .. _test: https://github.com/earwig/earwigbot/blob/develop/earwigbot/commands/test.py | |||
| @@ -96,6 +96,15 @@ these are the basics: | |||
| - Class attribute :py:attr:`~earwigbot.commands.BaseCommand.name` is the name | |||
| of the command. This must be specified. | |||
| - Class attribute :py:attr:`~earwigbot.commands.BaseCommand.commands` is a list | |||
| of names that will trigger this command. It defaults to the command's | |||
| :py:attr:`~earwigbot.commands.BaseCommand.name`, but you can override it with | |||
| multiple names to serve as aliases. This is handled by the default | |||
| :py:meth:`~earwigbot.commands.BaseCommand.check` implementation (see below), | |||
| so if :py:meth:`~earwigbot.commands.BaseCommand.check` is overridden, this is | |||
| ignored by everything except the help_ command (so ``!help alias`` will | |||
| trigger help for the actual command). | |||
| - Class attribute :py:attr:`~earwigbot.commands.BaseCommand.hooks` is a list of | |||
| the "IRC events" that this command might respond to. It defaults to | |||
| ``["msg"]``, but options include ``"msg_private"`` (for private messages | |||
| @@ -113,12 +122,15 @@ these are the basics: | |||
| - Method :py:meth:`~earwigbot.commands.BaseCommand.check` is passed a | |||
| :py:class:`~earwigbot.irc.data.Data` [1]_ object, and should return ``True`` | |||
| if you want to respond to this message, or ``False`` otherwise. The default | |||
| behavior is to return ``True`` only if | |||
| :py:attr:`data.is_command` is ``True`` and :py:attr:`data.command` == | |||
| :py:attr:`~earwigbot.commands.BaseCommand.name`, which is suitable for most | |||
| cases. A common, straightforward reason for overriding is if a command has | |||
| aliases (see chanops_ for an example). Note that by returning ``True``, you | |||
| prevent any other commands from responding to this message. | |||
| behavior is to return ``True`` only if :py:attr:`data.is_command` is ``True`` | |||
| and :py:attr:`data.command` ``==`` | |||
| :py:attr:`~earwigbot.commands.BaseCommand.name` (or :py:attr:`data.command | |||
| <earwigbot.irc.data.Data.command>` is in | |||
| :py:attr:`~earwigbot.commands.BaseCommand.commands` if that list is | |||
| overriden; see above), which is suitable for most cases. A possible reason | |||
| for overriding is if you want to do something in response to events from a | |||
| specific channel only. Note that by returning ``True``, you prevent any other | |||
| commands from responding to this message. | |||
| - Method :py:meth:`~earwigbot.commands.BaseCommand.process` is passed the same | |||
| :py:class:`~earwigbot.irc.data.Data` object as | |||
| @@ -179,7 +191,7 @@ are the basics: | |||
| task class's :py:meth:`make_summary(comment) | |||
| <earwigbot.tasks.BaseTask.make_summary>` method will take and replace | |||
| ``$1`` with the task number and ``$2`` with the details of the edit. | |||
| Additionally, :py:meth:`~earwigbot.tasks.BaseTask.shutoff_enabled` (which | |||
| checks whether the bot has been told to stop on-wiki by checking the content | |||
| of a particular page) can check a different page for each task using similar | |||
| @@ -249,6 +261,7 @@ task, or the afc_statistics_ plugin for a more complicated one. | |||
| :py:attr:`~earwigbot.irc.data.Data.ident`, | |||
| and :py:attr:`~earwigbot.irc.data.Data.host`. | |||
| .. _help: https://github.com/earwig/earwigbot/blob/develop/earwigbot/commands/help.py | |||
| .. _afc_status: https://github.com/earwig/earwigbot-plugins/blob/develop/commands/afc_status.py | |||
| .. _chanops: https://github.com/earwig/earwigbot/blob/develop/earwigbot/commands/chanops.py | |||
| .. _test: https://github.com/earwig/earwigbot/blob/develop/earwigbot/commands/test.py | |||
| @@ -36,9 +36,13 @@ class BaseCommand(object): | |||
| This docstring is reported to the user when they type ``"!help | |||
| <command>"``. | |||
| """ | |||
| # This is the command's name, as reported to the user when they use !help: | |||
| # The command's name, as reported to the user when they use !help: | |||
| name = None | |||
| # A list of names that will trigger this command. If left empty, it will | |||
| # be triggered by the command's name and its name only: | |||
| commands = [] | |||
| # Hooks are "msg", "msg_private", "msg_public", and "join". "msg" is the | |||
| # default behavior; if you wish to override that, change the value in your | |||
| # command subclass: | |||
| @@ -86,11 +90,15 @@ class BaseCommand(object): | |||
| sent on IRC, it should be cheap to execute and unlikely to throw | |||
| exceptions. | |||
| Most commands return ``True`` if :py:attr:`data.command | |||
| Most commands return ``True`` only if :py:attr:`data.command | |||
| <earwigbot.irc.data.Data.command>` ``==`` :py:attr:`self.name <name>`, | |||
| otherwise they return ``False``. This is the default behavior of | |||
| :py:meth:`check`; you need only override it if you wish to change that. | |||
| or :py:attr:`data.command <earwigbot.irc.data.Data.command>` is in | |||
| :py:attr:`self.commands <commands>` if that list is overriden. This is | |||
| the default behavior; you should only override it if you wish to change | |||
| that. | |||
| """ | |||
| if self.commands: | |||
| return data.is_command and data.command in self.commands | |||
| return data.is_command and data.command == self.name | |||
| def process(self, data): | |||
| @@ -21,11 +21,6 @@ def parse(command, line, line2, nick, chan, host, auth, notice, say, reply, s): | |||
| u.close() | |||
| say('"' + info['Date'] + '" - tycho.usno.navy.mil', chan) | |||
| return | |||
| if command == "beats": | |||
| beats = ((time.time() + 3600) % 86400) / 86.4 | |||
| beats = int(math.floor(beats)) | |||
| say('@%03i' % beats, chan) | |||
| return | |||
| if command == "dict" or command == "dictionary": | |||
| def trim(thing): | |||
| if thing.endswith(' '): | |||
| @@ -25,10 +25,7 @@ from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| """Link the user to the pending AFC submissions page and category.""" | |||
| name = "pending" | |||
| def check(self, data): | |||
| commands = ["pending", "pend"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["pending", "pend"] | |||
| def process(self, data): | |||
| msg1 = "pending submissions status page: http://enwp.org/WP:AFC/ST" | |||
| @@ -28,13 +28,12 @@ class Command(BaseCommand): | |||
| """Get the number of pending AfC submissions, open redirect requests, and | |||
| open file upload requests.""" | |||
| name = "status" | |||
| commands = ["status", "count", "num", "number"] | |||
| hooks = ["join", "msg"] | |||
| def check(self, data): | |||
| commands = ["status", "count", "num", "number"] | |||
| if data.is_command and data.command in commands: | |||
| if data.is_command and data.command in self.commands: | |||
| return True | |||
| try: | |||
| if data.line[1] == "JOIN" and data.chan == "#wikipedia-en-afc": | |||
| if data.nick != self.config.irc["frontend"]["nick"]: | |||
| @@ -26,10 +26,7 @@ class Command(BaseCommand): | |||
| """Voice, devoice, op, or deop users in the channel, or join or part from | |||
| other channels.""" | |||
| name = "chanops" | |||
| def check(self, data): | |||
| cmnds = ["chanops", "voice", "devoice", "op", "deop", "join", "part"] | |||
| return data.is_command and data.command in cmnds | |||
| commands = ["chanops", "voice", "devoice", "op", "deop", "join", "part"] | |||
| def process(self, data): | |||
| if data.command == "chanops": | |||
| @@ -30,10 +30,7 @@ class Command(BaseCommand): | |||
| """Provides hash functions with !hash (!hash list for supported algorithms) | |||
| and blowfish encryption with !encrypt and !decrypt.""" | |||
| name = "crypt" | |||
| def check(self, data): | |||
| commands = ["crypt", "hash", "encrypt", "decrypt"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["crypt", "hash", "encrypt", "decrypt"] | |||
| def process(self, data): | |||
| if data.command == "crypt": | |||
| @@ -28,10 +28,7 @@ from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| """Return a user's edit count.""" | |||
| name = "editcount" | |||
| def check(self, data): | |||
| commands = ["ec", "editcount"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["ec", "editcount"] | |||
| def process(self, data): | |||
| if not data.args: | |||
| @@ -28,6 +28,7 @@ from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| """Geolocate an IP address (via http://ipinfodb.com/).""" | |||
| name = "geolocate" | |||
| commands = ["geolocate", "locate", "geo", "ip"] | |||
| def setup(self): | |||
| self.config.decrypt(self.config.commands, (self.name, "apiKey")) | |||
| @@ -38,10 +39,6 @@ class Command(BaseCommand): | |||
| log = 'Cannot use without an API key for http://ipinfodb.com/ stored as config.commands["{0}"]["apiKey"]' | |||
| self.logger.warn(log.format(self.name)) | |||
| def check(self, data): | |||
| commands = ["geolocate", "locate", "geo", "ip"] | |||
| return data.is_command and data.command in commands | |||
| def process(self, data): | |||
| if not data.args: | |||
| self.reply(data, "please specify an IP to lookup.") | |||
| @@ -23,7 +23,6 @@ | |||
| import re | |||
| from earwigbot.commands import BaseCommand | |||
| from earwigbot.irc import Data | |||
| class Command(BaseCommand): | |||
| """Displays help information.""" | |||
| @@ -48,34 +47,24 @@ class Command(BaseCommand): | |||
| def do_main_help(self, data): | |||
| """Give the user a general help message with a list of all commands.""" | |||
| msg = "Hi, I'm a bot! I have {0} commands loaded: {1}. You can get help for any command with '!help <command>'." | |||
| cmnds = sorted(self.bot.commands) | |||
| cmnds = sorted([cmnd.name for cmnd in self.bot.commands]) | |||
| msg = msg.format(len(cmnds), ', '.join(cmnds)) | |||
| self.reply(data, msg) | |||
| def do_command_help(self, data): | |||
| """Give the user help for a specific command.""" | |||
| command = data.args[0] | |||
| target = data.args[0] | |||
| # Create a dummy message to test which commands pick up the user's | |||
| # input: | |||
| msg = ":foo!bar@example.com PRIVMSG #channel :msg".split() | |||
| dummy = Data(self.bot, msg) | |||
| dummy.command = command.lower() | |||
| dummy.is_command = True | |||
| for command in self.bot.commands: | |||
| if command.name == target or target in command.commands: | |||
| if command.__doc__: | |||
| doc = command.__doc__.replace("\n", "") | |||
| doc = re.sub("\s\s+", " ", doc) | |||
| msg = "help for command \x0303{0}\x0301: \"{1}\"" | |||
| self.reply(data, msg.format(target, doc)) | |||
| return | |||
| for cmnd_name in self.bot.commands: | |||
| cmnd = self.bot.commands.get(cmnd_name) | |||
| if not cmnd.check(dummy): | |||
| continue | |||
| if cmnd.__doc__: | |||
| doc = cmnd.__doc__.replace("\n", "") | |||
| doc = re.sub("\s\s+", " ", doc) | |||
| msg = "help for command \x0303{0}\x0301: \"{1}\"" | |||
| self.reply(data, msg.format(command, doc)) | |||
| return | |||
| break | |||
| msg = "sorry, no help for \x0303{0}\x0301.".format(command) | |||
| msg = "sorry, no help for \x0303{0}\x0301.".format(target) | |||
| self.reply(data, msg) | |||
| def do_hello(self, data): | |||
| @@ -26,10 +26,7 @@ class Command(BaseCommand): | |||
| """Convert a language code into its name and a list of WMF sites in that | |||
| language.""" | |||
| name = "langcode" | |||
| def check(self, data): | |||
| commands = ["langcode", "lang", "language"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["langcode", "lang", "language"] | |||
| def process(self, data): | |||
| if not data.args: | |||
| @@ -25,11 +25,8 @@ from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| """Praise people!""" | |||
| name = "praise" | |||
| def check(self, data): | |||
| commands = ["praise", "earwig", "leonard", "leonard^bloom", "groove", | |||
| "groovedog"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["praise", "earwig", "leonard", "leonard^bloom", "groove", | |||
| "groovedog"] | |||
| def process(self, data): | |||
| if data.command == "earwig": | |||
| @@ -26,10 +26,7 @@ class Command(BaseCommand): | |||
| """Quit, restart, or reload components from the bot. Only the owners can | |||
| run this command.""" | |||
| name = "quit" | |||
| def check(self, data): | |||
| commands = ["quit", "restart", "reload"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["quit", "restart", "reload"] | |||
| def process(self, data): | |||
| if data.host not in self.config.irc["permissions"]["owners"]: | |||
| @@ -28,10 +28,7 @@ from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| """Return when a user registered.""" | |||
| name = "registration" | |||
| def check(self, data): | |||
| commands = ["registration", "reg", "age"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["registration", "reg", "age"] | |||
| def process(self, data): | |||
| if not data.args: | |||
| @@ -20,7 +20,7 @@ | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| import threading | |||
| from threading import Timer | |||
| import time | |||
| from earwigbot.commands import BaseCommand | |||
| @@ -28,9 +28,7 @@ from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| """Set a message to be repeated to you in a certain amount of time.""" | |||
| name = "remind" | |||
| def check(self, data): | |||
| return data.is_command and data.command in ["remind", "reminder"] | |||
| commands = ["remind", "reminder"] | |||
| def process(self, data): | |||
| if not data.args: | |||
| @@ -58,12 +56,7 @@ class Command(BaseCommand): | |||
| msg = msg.format(message, wait, end_time_with_timezone) | |||
| self.reply(data, msg) | |||
| t_reminder = threading.Thread(target=self.reminder, | |||
| args=(data, message, wait)) | |||
| t_reminder = Timer(wait, self.reply, args=(data, message)) | |||
| t_reminder.name = "reminder " + end_time | |||
| t_reminder.daemon = True | |||
| t_reminder.start() | |||
| def reminder(self, data, message, wait): | |||
| time.sleep(wait) | |||
| self.reply(data, message) | |||
| @@ -26,10 +26,7 @@ from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| """Retrieve a list of rights for a given username.""" | |||
| name = "rights" | |||
| def check(self, data): | |||
| commands = ["rights", "groups", "permissions", "privileges"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["rights", "groups", "permissions", "privileges"] | |||
| def process(self, data): | |||
| if not data.args: | |||
| @@ -29,10 +29,7 @@ from earwigbot.exceptions import KwargParseError | |||
| class Command(BaseCommand): | |||
| """Manage wiki tasks from IRC, and check on thread status.""" | |||
| name = "threads" | |||
| def check(self, data): | |||
| commands = ["tasks", "task", "threads", "tasklist"] | |||
| return data.is_command and data.command in commands | |||
| commands = ["tasks", "task", "threads", "tasklist"] | |||
| def process(self, data): | |||
| self.data = data | |||
| @@ -103,7 +100,7 @@ class Command(BaseCommand): | |||
| whether they are currently running or idle.""" | |||
| threads = threading.enumerate() | |||
| tasklist = [] | |||
| for task in sorted(self.bot.tasks): | |||
| for task in sorted([task.name for task in self.bot.tasks]): | |||
| threadlist = [t for t in threads if t.name.startswith(task)] | |||
| ids = [str(t.ident) for t in threadlist] | |||
| if not ids: | |||
| @@ -138,7 +135,7 @@ class Command(BaseCommand): | |||
| self.reply(data, msg) | |||
| return | |||
| if task_name not in self.bot.tasks: | |||
| if task_name not in [task.name for task in self.bot.tasks]: | |||
| # This task does not exist or hasn't been loaded: | |||
| msg = "task could not be found; either it doesn't exist, or it wasn't loaded correctly." | |||
| self.reply(data, msg.format(task_name)) | |||
| @@ -0,0 +1,70 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2009-2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| # in the Software without restriction, including without limitation the rights | |||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| # copies of the Software, and to permit persons to whom the Software is | |||
| # furnished to do so, subject to the following conditions: | |||
| # | |||
| # The above copyright notice and this permission notice shall be included in | |||
| # all copies or substantial portions of the Software. | |||
| # | |||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| from datetime import datetime, timedelta | |||
| from math import floor | |||
| from time import time | |||
| from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| """Report the current time in any timezone (UTC default), or in beats.""" | |||
| name = "time" | |||
| commands = ["time", "beats", "swatch"] | |||
| timezones = [ | |||
| "UTC": 0, | |||
| "EST": -5, | |||
| "EDT": -4, | |||
| "CST": -6, | |||
| "CDT": -5, | |||
| "MST": -7, | |||
| "MDT": -6, | |||
| "PST": -8, | |||
| "PDT": -7, | |||
| ] | |||
| def process(self, data): | |||
| if data.command in ["beats", "swatch"]: | |||
| self.do_beats(data) | |||
| return | |||
| if data.args: | |||
| timezone = data.args[0] | |||
| else: | |||
| timezone = "UTC" | |||
| if timezone in ["beats", "swatch"]: | |||
| self.do_beats(data) | |||
| else: | |||
| self.do_time(data, timezone) | |||
| def do_beats(self, data): | |||
| beats = ((time() + 3600) % 86400) / 86.4 | |||
| beats = int(floor(beats)) | |||
| self.reply(data, "@{0:0>3}".format(beats)) | |||
| def do_time(self, data, timezone): | |||
| now = datetime.utcnow() | |||
| try: | |||
| now += timedelta(hours=self.timezones[timezone]) # Timezone offset | |||
| except KeyError: | |||
| self.reply(data, "unknown timezone: {0}.".format(timezone)) | |||
| return | |||
| self.reply(data, now.strftime("%Y-%m-%d %H:%M:%S") + " " + timezone) | |||
| @@ -24,7 +24,7 @@ | |||
| import imp | |||
| from os import listdir, path | |||
| from re import sub | |||
| from threading import Lock, Thread | |||
| from threading import RLock, Thread | |||
| from time import gmtime, strftime | |||
| from earwigbot.commands import BaseCommand | |||
| @@ -46,11 +46,7 @@ class _ResourceManager(object): | |||
| This class handles the low-level tasks of (re)loading resources via | |||
| :py:meth:`load`, retrieving specific resources via :py:meth:`get`, and | |||
| iterating over all resources via :py:meth:`__iter__`. If iterating over | |||
| resources, it is recommended to acquire :py:attr:`self.lock <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. | |||
| iterating over all resources via :py:meth:`__iter__`. | |||
| """ | |||
| def __init__(self, bot, name, attribute, base): | |||
| self.bot = bot | |||
| @@ -60,16 +56,12 @@ class _ResourceManager(object): | |||
| 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() | |||
| @property | |||
| def lock(self): | |||
| """The resource access/modify lock.""" | |||
| return self._resource_access_lock | |||
| self._resource_access_lock = RLock() | |||
| def __iter__(self): | |||
| for name in self._resources: | |||
| yield name | |||
| with self.lock: | |||
| for resource in self._resources.itervalues(): | |||
| yield resource | |||
| def _load_resource(self, name, path): | |||
| """Load a specific resource from a module, identified by name and path. | |||
| @@ -118,6 +110,11 @@ class _ResourceManager(object): | |||
| self._load_resource(modname, dir) | |||
| processed.append(modname) | |||
| @property | |||
| def lock(self): | |||
| """The resource access/modify lock.""" | |||
| return self._resource_access_lock | |||
| def load(self): | |||
| """Load (or reload) all valid resources into :py:attr:`_resources`.""" | |||
| name = self._resource_name # e.g. "commands" or "tasks" | |||
| @@ -138,7 +135,8 @@ class _ResourceManager(object): | |||
| Will raise :py:exc:`KeyError` if the resource (a command or task) is | |||
| not found. | |||
| """ | |||
| return self._resources[key] | |||
| with self.lock: | |||
| return self._resources[key] | |||
| class CommandManager(_ResourceManager): | |||
| @@ -167,13 +165,10 @@ class CommandManager(_ResourceManager): | |||
| def call(self, hook, data): | |||
| """Respond to a hook type and a :py:class:`Data` object.""" | |||
| self.lock.acquire() | |||
| for command in self._resources.itervalues(): | |||
| for command in self: | |||
| if hook in command.hooks and self._wrap_check(command, data): | |||
| self.lock.release() | |||
| self._wrap_process(command, data) | |||
| return | |||
| self.lock.release() | |||
| class TaskManager(_ResourceManager): | |||