diff options
| author | 2013-06-09 18:10:22 +0200 | |
|---|---|---|
| committer | 2013-06-09 18:10:23 +0200 | |
| commit | 16af85004c84d0d6c626b4f8424ce9647669a0c1 (patch) | |
| tree | 025d479862d376dbc17e934f4ed20031c8cd97d1 /pyload/interaction | |
| parent | adapted to jshint config (diff) | |
| download | pyload-16af85004c84d0d6c626b4f8424ce9647669a0c1.tar.xz | |
moved everything from module to pyload
Diffstat (limited to 'pyload/interaction')
| -rw-r--r-- | pyload/interaction/EventManager.py | 84 | ||||
| -rw-r--r-- | pyload/interaction/InteractionManager.py | 166 | ||||
| -rw-r--r-- | pyload/interaction/InteractionTask.py | 100 | ||||
| -rw-r--r-- | pyload/interaction/__init__.py | 2 | 
4 files changed, 352 insertions, 0 deletions
diff --git a/pyload/interaction/EventManager.py b/pyload/interaction/EventManager.py new file mode 100644 index 000000000..7d37ca6b9 --- /dev/null +++ b/pyload/interaction/EventManager.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +from threading import Lock +from traceback import print_exc + +class EventManager: +    """ +    Handles all event-related tasks, also stores an event queue for clients, so they can retrieve them later. + +    **Known Events:** +    Most addon methods exist as events. These are some additional known events. + +    ===================== ================ =========================================================== +    Name                      Arguments      Description +    ===================== ================ =========================================================== +    event                 eventName, *args Called for every event, with eventName and original args +    download:preparing    fid              A download was just queued and will be prepared now. +    download:start        fid              A plugin will immediately start the download afterwards. +    download:allProcessed                  All links were handled, pyLoad would idle afterwards. +    download:allFinished                   All downloads in the queue are finished. +    config:changed        sec, opt, value  The config was changed. +    ===================== ================ =========================================================== + +    | Notes: +    |    download:allProcessed is *always* called before download:allFinished. +    """ + +    def __init__(self, core): +        self.core = core +        self.log = core.log + +        # uuid : list of events +        self.clients = {} +        self.events = {"event": []} + +        self.lock = Lock() + +    def getEvents(self, uuid): +        """ Get accumulated events for uuid since last call, this also registers a new client """ +        if uuid not in self.clients: +            self.clients[uuid] = Client() +        return self.clients[uuid].get() + +    def addEvent(self, event, func): +        """Adds an event listener for event name""" +        if event in self.events: +            if func in self.events[event]: +                self.log.debug("Function already registered %s" % func) +            else: +                self.events[event].append(func) +        else: +            self.events[event] = [func] + +    def removeEvent(self, event, func): +        """removes previously added event listener""" +        if event in self.events: +            self.events[event].remove(func) + +    def removeFromEvents(self, func): +        """ Removes func from all known events """ +        for name, events in self.events.iteritems(): +            if func in events: +                events.remove(func) + +    def dispatchEvent(self, event, *args): +        """dispatches event with args""" +        for f in self.events["event"]: +            try: +                f(event, *args) +            except Exception, e: +                self.log.warning("Error calling event handler %s: %s, %s, %s" +                % ("event", f, args, str(e))) +                if self.core.debug: +                    print_exc() + +        if event in self.events: +            for f in self.events[event]: +                try: +                    f(*args) +                except Exception, e: +                    self.log.warning("Error calling event handler %s: %s, %s, %s" +                    % (event, f, args, str(e))) +                    if self.core.debug: +                        print_exc()
\ No newline at end of file diff --git a/pyload/interaction/InteractionManager.py b/pyload/interaction/InteractionManager.py new file mode 100644 index 000000000..9c5449b31 --- /dev/null +++ b/pyload/interaction/InteractionManager.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +""" +    This program is free software; you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation; either version 3 of the License, +    or (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +    See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program; if not, see <http://www.gnu.org/licenses/>. + +    @author: RaNaN +""" +from threading import Lock +from time import time +from base64 import standard_b64encode + +from new_collections import OrderedDict + +from pyload.utils import lock, bits_set +from pyload.Api import Interaction as IA +from pyload.Api import InputType, Input + +from InteractionTask import InteractionTask + +class InteractionManager: +    """ +    Class that gives ability to interact with the user. +    Arbitrary tasks with predefined output and input types can be set off. +    """ + +    # number of seconds a client is classified as active +    CLIENT_THRESHOLD = 60 +    NOTIFICATION_TIMEOUT = 60 * 60 * 30 +    MAX_NOTIFICATIONS = 50 + +    def __init__(self, core): +        self.lock = Lock() +        self.core = core +        self.tasks = OrderedDict() #task store, for all outgoing tasks + +        self.last_clients = {} +        self.ids = 0 #uniue interaction ids + +    def isClientConnected(self, user): +        return self.last_clients.get(user, 0) + self.CLIENT_THRESHOLD > time() + +    @lock +    def work(self): +        # old notifications will be removed +        for n in [k for k, v in self.tasks.iteritems() if v.timedOut()]: +            del self.tasks[n] + +        # keep notifications count limited +        n = [k for k,v in self.tasks.iteritems() if v.type == IA.Notification] +        n.reverse() +        for v in n[:self.MAX_NOTIFICATIONS]: +            del self.tasks[v] + +    @lock +    def createNotification(self, title, content, desc="", plugin="", owner=None): +        """ Creates and queues a new Notification + +        :param title: short title +        :param content: text content +        :param desc: short form of the notification +        :param plugin: plugin name +        :return: :class:`InteractionTask` +        """ +        task = InteractionTask(self.ids, IA.Notification, Input(InputType.Text, content), "", title, desc, plugin, +                               owner=owner) +        self.ids += 1 +        self.queueTask(task) +        return task + +    @lock +    def createQueryTask(self, input, desc, default="", plugin="", owner=None): +        # input type was given, create a input widget +        if type(input) == int: +            input = Input(input) +        if not isinstance(input, Input): +            raise TypeError("'Input' class expected not '%s'" % type(input)) + +        task = InteractionTask(self.ids, IA.Query, input, default, _("Query"), desc, plugin, owner=owner) +        self.ids += 1 +        self.queueTask(task) +        return task + +    @lock +    def createCaptchaTask(self, img, format, filename, plugin="", type=InputType.Text, owner=None): +        """ Createss a new captcha task. + +        :param img: image content (not base encoded) +        :param format: img format +        :param type: :class:`InputType` +        :return: +        """ +        if type == 'textual': +            type = InputType.Text +        elif type == 'positional': +            type = InputType.Click + +        input = Input(type, [standard_b64encode(img), format, filename]) + +        #todo: title desc plugin +        task = InteractionTask(self.ids, IA.Captcha, input, +                               None, _("Captcha request"), _("Please solve the captcha."), plugin, owner=owner) + +        self.ids += 1 +        self.queueTask(task) +        return task + +    @lock +    def removeTask(self, task): +        if task.iid in self.tasks: +            del self.tasks[task.iid] +            self.core.evm.dispatchEvent("interaction:deleted", task.iid) + +    @lock +    def getTaskByID(self, iid): +        return self.tasks.get(iid, None) + +    @lock +    def getTasks(self, user, mode=IA.All): +        # update last active clients +        self.last_clients[user] = time() + +        # filter current mode +        tasks = [t for t in self.tasks.itervalues() if mode == IA.All or bits_set(t.type, mode)] +        # filter correct user / or shared +        tasks = [t for t in tasks if user is None or user == t.owner or t.shared] + +        return tasks + +    def isTaskWaiting(self, user, mode=IA.All): +        tasks = [t for t in self.getTasks(user, mode) if not t.type == IA.Notification or not t.seen] +        return len(tasks) > 0 + +    def queueTask(self, task): +        cli = self.isClientConnected(task.owner) + +        # set waiting times based on threshold +        if cli: +            task.setWaiting(self.CLIENT_THRESHOLD) +        else: # TODO: higher threshold after client connects? +            task.setWaiting(self.CLIENT_THRESHOLD / 3) + +        if task.type == IA.Notification: +            task.setWaiting(self.NOTIFICATION_TIMEOUT) # notifications are valid for 30h + +        for plugin in self.core.addonManager.activePlugins(): +            try: +                plugin.newInteractionTask(task) +            except: +                self.core.print_exc() + +        self.tasks[task.iid] = task +        self.core.evm.dispatchEvent("interaction:added", task) + + +if __name__ == "__main__": +    it = InteractionTask()
\ No newline at end of file diff --git a/pyload/interaction/InteractionTask.py b/pyload/interaction/InteractionTask.py new file mode 100644 index 000000000..b404aa6ce --- /dev/null +++ b/pyload/interaction/InteractionTask.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +""" +    This program is free software; you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation; either version 3 of the License, +    or (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +    See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program; if not, see <http://www.gnu.org/licenses/>. + +    @author: RaNaN +""" + +from time import time + +from pyload.Api import InteractionTask as BaseInteractionTask +from pyload.Api import Interaction, InputType, Input + +#noinspection PyUnresolvedReferences +class InteractionTask(BaseInteractionTask): +    """ +    General Interaction Task extends ITask defined by api with additional fields and methods. +    """ +    #: Plugins can put needed data here +    storage = None +    #: Timestamp when task expires +    wait_until = 0 +    #: The received result +    result = None +    #: List of registered handles +    handler = None +    #: Error Message +    error = None +    #: Timeout locked +    locked = False +    #: A task that was retrieved counts as seen +    seen = False +    #: A task that is relevant to every user +    shared = False +    #: primary uid of the owner +    owner = None + +    def __init__(self, *args, **kwargs): +        if 'owner' in kwargs: +            self.owner = kwargs['owner'] +            del kwargs['owner'] +        if 'shared' in kwargs: +            self.shared = kwargs['shared'] +            del kwargs['shared'] + +        BaseInteractionTask.__init__(self, *args, **kwargs) + +        # additional internal attributes +        self.storage = {} +        self.handler = [] +        self.wait_until = 0 + +    def convertResult(self, value): +        #TODO: convert based on input/output +        return value + +    def getResult(self): +        return self.result + +    def setShared(self): +        """ enable shared mode, should not be reversed""" +        self.shared = True + +    def setResult(self, value): +        self.result = self.convertResult(value) + +    def setWaiting(self, sec, lock=False): +        """ sets waiting in seconds from now, < 0 can be used as infinitive  """ +        if not self.locked: +            if sec < 0: +                self.wait_until = -1 +            else: +                self.wait_until = max(time() + sec, self.wait_until) + +            if lock: self.locked = True + +    def isWaiting(self): +        if self.result or self.error or self.timedOut(): +            return False + +        return True + +    def timedOut(self): +        return time() > self.wait_until > -1 + +    def correct(self): +        [x.taskCorrect(self) for x in self.handler] + +    def invalid(self): +        [x.taskInvalid(self) for x in self.handler]
\ No newline at end of file diff --git a/pyload/interaction/__init__.py b/pyload/interaction/__init__.py new file mode 100644 index 000000000..de6d13128 --- /dev/null +++ b/pyload/interaction/__init__.py @@ -0,0 +1,2 @@ +__author__ = 'christian' +  
\ No newline at end of file  | 
