From 5499be89203a18ca61a21cfc7266cf0f4ebe6547 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Thu, 15 Dec 2011 23:18:21 +0100 Subject: refractoring --- module/interaction/CaptchaManager.py | 158 +++++++++++++++++++++++++++++++ module/interaction/InteractionManager.py | 89 +++++++++++++++++ module/interaction/InteractionTask.py | 129 +++++++++++++++++++++++++ module/interaction/PullEvents.py | 120 +++++++++++++++++++++++ module/interaction/__init__.py | 2 + 5 files changed, 498 insertions(+) create mode 100644 module/interaction/CaptchaManager.py create mode 100644 module/interaction/InteractionManager.py create mode 100644 module/interaction/InteractionTask.py create mode 100644 module/interaction/PullEvents.py create mode 100644 module/interaction/__init__.py (limited to 'module/interaction') diff --git a/module/interaction/CaptchaManager.py b/module/interaction/CaptchaManager.py new file mode 100644 index 000000000..02cd10a11 --- /dev/null +++ b/module/interaction/CaptchaManager.py @@ -0,0 +1,158 @@ +# -*- 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 . + + @author: mkaay, RaNaN +""" + +from time import time +from traceback import print_exc +from threading import Lock + +class CaptchaManager(): + def __init__(self, core): + self.lock = Lock() + self.core = core + self.tasks = [] #task store, for outgoing tasks only + + self.ids = 0 #only for internal purpose + + def newTask(self, img, format, file, result_type): + task = CaptchaTask(self.ids, img, format, file, result_type) + self.ids += 1 + return task + + def removeTask(self, task): + self.lock.acquire() + if task in self.tasks: + self.tasks.remove(task) + self.lock.release() + + def getTask(self): + self.lock.acquire() + for task in self.tasks: + if task.status in ("waiting", "shared-user"): + self.lock.release() + return task + self.lock.release() + return None + + def getTaskByID(self, tid): + self.lock.acquire() + for task in self.tasks: + if task.id == str(tid): #task ids are strings + self.lock.release() + return task + self.lock.release() + return None + + def handleCaptcha(self, task): + cli = self.core.isClientConnected() + + if cli: #client connected -> should solve the captcha + task.setWaiting(50) #wait 50 sec for response + + for plugin in self.core.hookManager.activePlugins(): + try: + plugin.newCaptchaTask(task) + except: + if self.core.debug: + print_exc() + + if task.handler or cli: #the captcha was handled + self.tasks.append(task) + return True + + task.error = _("No Client connected for captcha decrypting") + + return False + + +class CaptchaTask(): + def __init__(self, id, img, format, file, result_type='textual'): + self.id = str(id) + self.captchaImg = img + self.captchaFormat = format + self.captchaFile = file + self.captchaResultType = result_type + self.handler = [] #the hook plugins that will take care of the solution + self.result = None + self.waitUntil = None + self.error = None #error message + + self.status = "init" + self.data = {} #handler can store data here + + def getCaptcha(self): + return self.captchaImg, self.captchaFormat, self.captchaResultType + + def setResult(self, text): + if self.isTextual(): + self.result = text + if self.isPositional(): + try: + parts = text.split(',') + self.result = (int(parts[0]), int(parts[1])) + except: + self.result = None + + def getResult(self): + try: + res = self.result.encode("utf8", "replace") + except: + res = self.result + + return res + + def getStatus(self): + return self.status + + def setWaiting(self, sec): + """ let the captcha wait secs for the solution """ + self.waitUntil = max(time() + sec, self.waitUntil) + self.status = "waiting" + + def isWaiting(self): + if self.result or self.error or time() > self.waitUntil: + return False + + return True + + def isTextual(self): + """ returns if text is written on the captcha """ + return self.captchaResultType == 'textual' + + def isPositional(self): + """ returns if user have to click a specific region on the captcha """ + return self.captchaResultType == 'positional' + + def setWatingForUser(self, exclusive): + if exclusive: + self.status = "user" + else: + self.status = "shared-user" + + def timedOut(self): + return time() > self.waitUntil + + def invalid(self): + """ indicates the captcha was not correct """ + [x.captchaInvalid(self) for x in self.handler] + + def correct(self): + [x.captchaCorrect(self) for x in self.handler] + + def __str__(self): + return "" % self.id diff --git a/module/interaction/InteractionManager.py b/module/interaction/InteractionManager.py new file mode 100644 index 000000000..8bb500f3b --- /dev/null +++ b/module/interaction/InteractionManager.py @@ -0,0 +1,89 @@ +# -*- 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 . + + @author: RaNaN +""" +from time import time +from utils import lock +from traceback import print_exc +from threading import Lock + + + + +class InteractionManager: + """ + Class that gives ability to interact with the user. + Arbitary task with predefined output and input type can be set off. + Asyncronous callbacks and default values keeps the ability to fallback if no user is present. + """ + def __init__(self, core): + self.lock = Lock() + self.core = core + self.tasks = [] #task store, for outgoing tasks only + + self.ids = 0 #only for internal purpose + + def work(self): + """Mainloop that gets the work done""" + + def newTask(self, img, format, file, result_type): + task = CaptchaTask(self.ids, img, format, file, result_type) + self.ids += 1 + return task + + @lock + def removeTask(self, task): + if task in self.tasks: + self.tasks.remove(task) + + @lock + def getTask(self): + for task in self.tasks: + if task.status in ("waiting", "shared-user"): + return task + + @lock + def getTaskByID(self, tid): + for task in self.tasks: + if task.id == str(tid): #task ids are strings + self.lock.release() + return task + + def handleCaptcha(self, task): + cli = self.core.isClientConnected() + + if cli: #client connected -> should solve the captcha + task.setWaiting(50) #wait 50 sec for response + + for plugin in self.core.hookManager.activePlugins(): + try: + plugin.newCaptchaTask(task) + except: + if self.core.debug: + print_exc() + + if task.handler or cli: #the captcha was handled + self.tasks.append(task) + return True + + task.error = _("No Client connected for captcha decrypting") + + return False + + +if __name__ == "__main__": + + it = InteractionTask() \ No newline at end of file diff --git a/module/interaction/InteractionTask.py b/module/interaction/InteractionTask.py new file mode 100644 index 000000000..97cb16794 --- /dev/null +++ b/module/interaction/InteractionTask.py @@ -0,0 +1,129 @@ +# -*- 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 . + + @author: RaNaN +""" + +from module.Api import InteractionTask as BaseInteractionTask +from module.Api import Input, Output + +#noinspection PyUnresolvedReferences +class InteractionTask(BaseInteractionTask): + """ + General Interaction Task extends ITask defined by thrift with additional fields and methods. + """ + #: Plugins can put needed data here + storage = None + #: Timestamp when task expires + waitUntil = 0 + #: Default data to be used, or True if preset should be used + default = None + #: The received result as string representation + result = None + #: List of registered handles + handler = None + #: Callback functions + callbacks = None + #: Error Message + error = None + #: Status string + status = None + + def __init__(self, *args, **kwargs): + BaseInteractionTask.__init__(self, *args, **kwargs) + + # additional internal attributes + self.storage = {} + self.default = [] + self.handler = [] + self.callbacks = [] + + +class CaptchaTask: + def __init__(self, id, img, format, file, result_type='textual'): + self.id = str(id) + self.captchaImg = img + self.captchaFormat = format + self.captchaFile = file + self.captchaResultType = result_type + self.handler = [] #the hook plugins that will take care of the solution + self.result = None + self.waitUntil = None + self.error = None #error message + + self.status = "init" + self.data = {} #handler can store data here + + def getCaptcha(self): + return self.captchaImg, self.captchaFormat, self.captchaResultType + + def setResult(self, text): + if self.isTextual(): + self.result = text + if self.isPositional(): + try: + parts = text.split(',') + self.result = (int(parts[0]), int(parts[1])) + except: + self.result = None + + def getResult(self): + try: + res = self.result.encode("utf8", "replace") + except: + res = self.result + + return res + + def getStatus(self): + return self.status + + def setWaiting(self, sec): + """ let the captcha wait secs for the solution """ + self.waitUntil = max(time() + sec, self.waitUntil) + self.status = "waiting" + + def isWaiting(self): + if self.result or self.error or time() > self.waitUntil: + return False + + return True + + def isTextual(self): + """ returns if text is written on the captcha """ + return self.captchaResultType == 'textual' + + def isPositional(self): + """ returns if user have to click a specific region on the captcha """ + return self.captchaResultType == 'positional' + + def setWatingForUser(self, exclusive): + if exclusive: + self.status = "user" + else: + self.status = "shared-user" + + def timedOut(self): + return time() > self.waitUntil + + def invalid(self): + """ indicates the captcha was not correct """ + [x.captchaInvalid(self) for x in self.handler] + + def correct(self): + [x.captchaCorrect(self) for x in self.handler] + + def __str__(self): + return "" % self.id diff --git a/module/interaction/PullEvents.py b/module/interaction/PullEvents.py new file mode 100644 index 000000000..5ec76765e --- /dev/null +++ b/module/interaction/PullEvents.py @@ -0,0 +1,120 @@ +# -*- 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 . + + @author: mkaay +""" + +from time import time +from module.utils import uniqify + +class PullManager(): + def __init__(self, core): + self.core = core + self.clients = [] + + def newClient(self, uuid): + self.clients.append(Client(uuid)) + + def clean(self): + for n, client in enumerate(self.clients): + if client.lastActive + 30 < time(): + del self.clients[n] + + def getEvents(self, uuid): + events = [] + validUuid = False + for client in self.clients: + if client.uuid == uuid: + client.lastActive = time() + validUuid = True + while client.newEvents(): + events.append(client.popEvent().toList()) + break + if not validUuid: + self.newClient(uuid) + events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()] + return uniqify(events, repr) + + def addEvent(self, event): + for client in self.clients: + client.addEvent(event) + +class Client(): + def __init__(self, uuid): + self.uuid = uuid + self.lastActive = time() + self.events = [] + + def newEvents(self): + return len(self.events) > 0 + + def popEvent(self): + if not len(self.events): + return None + return self.events.pop(0) + + def addEvent(self, event): + self.events.append(event) + +class UpdateEvent(): + def __init__(self, itype, iid, destination): + assert itype == "pack" or itype == "file" + assert destination == "queue" or destination == "collector" + self.type = itype + self.id = iid + self.destination = destination + + def toList(self): + return ["update", self.destination, self.type, self.id] + +class RemoveEvent(): + def __init__(self, itype, iid, destination): + assert itype == "pack" or itype == "file" + assert destination == "queue" or destination == "collector" + self.type = itype + self.id = iid + self.destination = destination + + def toList(self): + return ["remove", self.destination, self.type, self.id] + +class InsertEvent(): + def __init__(self, itype, iid, after, destination): + assert itype == "pack" or itype == "file" + assert destination == "queue" or destination == "collector" + self.type = itype + self.id = iid + self.after = after + self.destination = destination + + def toList(self): + return ["insert", self.destination, self.type, self.id, self.after] + +class ReloadAllEvent(): + def __init__(self, destination): + assert destination == "queue" or destination == "collector" + self.destination = destination + + def toList(self): + return ["reload", self.destination] + +class AccountUpdateEvent(): + def toList(self): + return ["account"] + +class ConfigUpdateEvent(): + def toList(self): + return ["config"] diff --git a/module/interaction/__init__.py b/module/interaction/__init__.py new file mode 100644 index 000000000..de6d13128 --- /dev/null +++ b/module/interaction/__init__.py @@ -0,0 +1,2 @@ +__author__ = 'christian' + \ No newline at end of file -- cgit v1.2.3 From 958bf611f5d9d117f19f824990ec6fd6b537e967 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Thu, 22 Dec 2011 23:45:38 +0100 Subject: accountmanager v2, delete your accounts.conf and re-enter them in pyload, new nice debug functions, try core.shell() and core.breakpoint() --- module/interaction/EventManager.py | 55 ++++++++++++++++++++++++++++++++++++++ module/interaction/PullEvents.py | 52 ----------------------------------- 2 files changed, 55 insertions(+), 52 deletions(-) create mode 100644 module/interaction/EventManager.py (limited to 'module/interaction') diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py new file mode 100644 index 000000000..c45c388f3 --- /dev/null +++ b/module/interaction/EventManager.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +from time import time + +from PullEvents import ReloadAllEvent +from module.utils import uniqify + +class EventManager: + def __init__(self, core): + self.core = core + self.clients = [] + + def newClient(self, uuid): + self.clients.append(Client(uuid)) + + def clean(self): + for n, client in enumerate(self.clients): + if client.lastActive + 30 < time(): + del self.clients[n] + + def getEvents(self, uuid): + events = [] + validUuid = False + for client in self.clients: + if client.uuid == uuid: + client.lastActive = time() + validUuid = True + while client.newEvents(): + events.append(client.popEvent().toList()) + break + if not validUuid: + self.newClient(uuid) + events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()] + return uniqify(events, repr) + + def addEvent(self, event): + for client in self.clients: + client.addEvent(event) + + +class Client: + def __init__(self, uuid): + self.uuid = uuid + self.lastActive = time() + self.events = [] + + def newEvents(self): + return len(self.events) > 0 + + def popEvent(self): + if not len(self.events): + return None + return self.events.pop(0) + + def addEvent(self, event): + self.events.append(event) \ No newline at end of file diff --git a/module/interaction/PullEvents.py b/module/interaction/PullEvents.py index 5ec76765e..f34b01d48 100644 --- a/module/interaction/PullEvents.py +++ b/module/interaction/PullEvents.py @@ -17,58 +17,6 @@ @author: mkaay """ -from time import time -from module.utils import uniqify - -class PullManager(): - def __init__(self, core): - self.core = core - self.clients = [] - - def newClient(self, uuid): - self.clients.append(Client(uuid)) - - def clean(self): - for n, client in enumerate(self.clients): - if client.lastActive + 30 < time(): - del self.clients[n] - - def getEvents(self, uuid): - events = [] - validUuid = False - for client in self.clients: - if client.uuid == uuid: - client.lastActive = time() - validUuid = True - while client.newEvents(): - events.append(client.popEvent().toList()) - break - if not validUuid: - self.newClient(uuid) - events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()] - return uniqify(events, repr) - - def addEvent(self, event): - for client in self.clients: - client.addEvent(event) - -class Client(): - def __init__(self, uuid): - self.uuid = uuid - self.lastActive = time() - self.events = [] - - def newEvents(self): - return len(self.events) > 0 - - def popEvent(self): - if not len(self.events): - return None - return self.events.pop(0) - - def addEvent(self, event): - self.events.append(event) - class UpdateEvent(): def __init__(self, itype, iid, destination): assert itype == "pack" or itype == "file" -- cgit v1.2.3 From d35c003cc53d4723d1dfe0d81eeb9bea78cee594 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sat, 31 Dec 2011 16:01:24 +0100 Subject: new crypter plugin API, now decrypting possible for now. --- module/interaction/EventManager.py | 3 ++ module/interaction/PullEvents.py | 68 -------------------------------------- 2 files changed, 3 insertions(+), 68 deletions(-) delete mode 100644 module/interaction/PullEvents.py (limited to 'module/interaction') diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py index c45c388f3..0c4fc80c9 100644 --- a/module/interaction/EventManager.py +++ b/module/interaction/EventManager.py @@ -36,6 +36,9 @@ class EventManager: for client in self.clients: client.addEvent(event) + def dispatchEvent(self, *args): + pass + class Client: def __init__(self, uuid): diff --git a/module/interaction/PullEvents.py b/module/interaction/PullEvents.py deleted file mode 100644 index f34b01d48..000000000 --- a/module/interaction/PullEvents.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- 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 . - - @author: mkaay -""" - -class UpdateEvent(): - def __init__(self, itype, iid, destination): - assert itype == "pack" or itype == "file" - assert destination == "queue" or destination == "collector" - self.type = itype - self.id = iid - self.destination = destination - - def toList(self): - return ["update", self.destination, self.type, self.id] - -class RemoveEvent(): - def __init__(self, itype, iid, destination): - assert itype == "pack" or itype == "file" - assert destination == "queue" or destination == "collector" - self.type = itype - self.id = iid - self.destination = destination - - def toList(self): - return ["remove", self.destination, self.type, self.id] - -class InsertEvent(): - def __init__(self, itype, iid, after, destination): - assert itype == "pack" or itype == "file" - assert destination == "queue" or destination == "collector" - self.type = itype - self.id = iid - self.after = after - self.destination = destination - - def toList(self): - return ["insert", self.destination, self.type, self.id, self.after] - -class ReloadAllEvent(): - def __init__(self, destination): - assert destination == "queue" or destination == "collector" - self.destination = destination - - def toList(self): - return ["reload", self.destination] - -class AccountUpdateEvent(): - def toList(self): - return ["account"] - -class ConfigUpdateEvent(): - def toList(self): - return ["config"] -- cgit v1.2.3 From 5a3e5a8228e4c5421b44d18c9c9ae2f1fe616400 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 1 Jan 2012 18:01:25 +0100 Subject: fixed imports --- module/interaction/EventManager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'module/interaction') diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py index 0c4fc80c9..8a80553cf 100644 --- a/module/interaction/EventManager.py +++ b/module/interaction/EventManager.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from time import time -from PullEvents import ReloadAllEvent from module.utils import uniqify class EventManager: @@ -55,4 +54,4 @@ class Client: return self.events.pop(0) def addEvent(self, event): - self.events.append(event) \ No newline at end of file + self.events.append(event) -- cgit v1.2.3 From 4a3a81b63cd85cc3dcd9669868a2079da65838a2 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Tue, 3 Jan 2012 20:41:23 +0100 Subject: fixes for old style decrypter --- module/interaction/InteractionManager.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'module/interaction') diff --git a/module/interaction/InteractionManager.py b/module/interaction/InteractionManager.py index 8bb500f3b..5ebcd1fcd 100644 --- a/module/interaction/InteractionManager.py +++ b/module/interaction/InteractionManager.py @@ -15,14 +15,10 @@ @author: RaNaN """ -from time import time from utils import lock from traceback import print_exc from threading import Lock - - - class InteractionManager: """ Class that gives ability to interact with the user. -- cgit v1.2.3 From 18466eb7f8f3cd4ca9a0824074d2ff454939fce6 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Wed, 4 Jan 2012 17:23:13 +0100 Subject: some fixes --- module/interaction/EventManager.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'module/interaction') diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py index 8a80553cf..931f50446 100644 --- a/module/interaction/EventManager.py +++ b/module/interaction/EventManager.py @@ -4,6 +4,28 @@ from time import time from module.utils import uniqify class EventManager: + """ + Handles all Event related task, also stores an Event queue for clients, so they can retrieve them later. + + **Known Events:** + Most hook methods exists as events. These are some additional known events. + + ===================== ============== ================================== + Name Arguments Description + ===================== ============== ================================== + downloadPreparing fid A download was just queued and will be prepared now. + downloadStarts fid A plugin will immediately starts the download afterwards. + linksAdded links, pid Someone just added links, you are able to modify the links. + allDownloadsProcessed Every link was handled, pyload would idle afterwards. + allDownloadsFinished Every download in queue is finished. + unrarFinished folder, fname An Unrar job finished + configChanged sec,opt,value The config was changed. + ===================== ============== ================================== + + | Notes: + | allDownloadsProcessed is *always* called before allDownloadsFinished. + | configChanged is *always* called before pluginConfigChanged. + """ def __init__(self, core): self.core = core self.clients = [] -- cgit v1.2.3 From b877847094b0ba03a098dff0fd769eb456b48dd1 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Fri, 6 Jan 2012 17:54:53 +0100 Subject: several improvements, also closes #486, #487 --- module/interaction/EventManager.py | 151 ++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 51 deletions(-) (limited to 'module/interaction') diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py index 931f50446..38faa3c46 100644 --- a/module/interaction/EventManager.py +++ b/module/interaction/EventManager.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from time import time -from module.utils import uniqify +from traceback import print_exc +from time import time class EventManager: """ @@ -10,70 +10,119 @@ class EventManager: **Known Events:** Most hook methods exists as events. These are some additional known events. - ===================== ============== ================================== - Name Arguments Description - ===================== ============== ================================== - downloadPreparing fid A download was just queued and will be prepared now. - downloadStarts fid A plugin will immediately starts the download afterwards. - linksAdded links, pid Someone just added links, you are able to modify the links. - allDownloadsProcessed Every link was handled, pyload would idle afterwards. - allDownloadsFinished Every download in queue is finished. - unrarFinished folder, fname An Unrar job finished - configChanged sec,opt,value The config was changed. - ===================== ============== ================================== + ===================== ================ =========================================================== + Name Arguments Description + ===================== ================ =========================================================== + metaEvent eventName, *args Called for every event, with eventName and orginal args + downloadPreparing fid A download was just queued and will be prepared now. + downloadStarts fid A plugin will immediately starts the download afterwards. + linksAdded links, pid Someone just added links, you are able to modify the links. + allDownloadsProcessed Every link was handled, pyload would idle afterwards. + allDownloadsFinished Every download in queue is finished. + unrarFinished folder, fname An Unrar job finished + configChanged sec, opt, value The config was changed. + ===================== ================ =========================================================== | Notes: | allDownloadsProcessed is *always* called before allDownloadsFinished. | configChanged is *always* called before pluginConfigChanged. """ + + CLIENT_EVENTS = ("packageUpdated", "packageInserted", "linkUpdated", "packageDeleted") + def __init__(self, core): self.core = core - self.clients = [] - - def newClient(self, uuid): - self.clients.append(Client(uuid)) + self.log = core.log - def clean(self): - for n, client in enumerate(self.clients): - if client.lastActive + 30 < time(): - del self.clients[n] + # uuid : list of events + self.clients = {} + self.events = {"metaEvent": []} def getEvents(self, uuid): - events = [] - validUuid = False - for client in self.clients: - if client.uuid == uuid: - client.lastActive = time() - validUuid = True - while client.newEvents(): - events.append(client.popEvent().toList()) - break - if not validUuid: - self.newClient(uuid) - events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()] - return uniqify(events, repr) - - def addEvent(self, event): - for client in self.clients: - client.addEvent(event) - - def dispatchEvent(self, *args): - pass + """ Get accumulated events for uuid since last call, this also registeres 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 dispatchEvent(self, event, *args): + """dispatches event with args""" + for f in self.events["metaEvent"]: + try: + f(event, *args) + except Exception, e: + self.log.warning("Error calling event handler %s: %s, %s, %s" + % ("metaEvent", 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() + + # append to client event queue + if event in self.CLIENT_EVENTS: + for uuid, client in self.clients.items(): + if client.delete(): + del self.clients[uuid] + else: + client.append(event, args) + + + def removeFromEvents(self, func): + """ Removes func from all known events """ + for name, events in self.events.iteritems(): + if func in events: + events.remove(func) + class Client: - def __init__(self, uuid): - self.uuid = uuid + + # delete clients after this time + TIMEOUT = 60 * 60 + # max events, if this value is reached you should assume that older events were dropped + MAX = 30 + + def __init__(self): self.lastActive = time() self.events = [] - def newEvents(self): - return len(self.events) > 0 + def delete(self): + return self.lastActive + self.TIMEOUT < time() + + def append(self, event, args): + ev = (event, args) + if ev not in self.events: + self.events.insert(0, ev) - def popEvent(self): - if not len(self.events): - return None - return self.events.pop(0) + del self.events[self.MAX:] + + + def get(self): + self.lastActive = time() + + events = self.events + self.events = [] - def addEvent(self, event): - self.events.append(event) + return [(ev, [str(x) for x in args]) for ev, args in events] \ No newline at end of file -- cgit v1.2.3 From 4df2b77fdf42046fe19bd371be7c7255986b5980 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Tue, 6 Mar 2012 13:36:39 +0100 Subject: renamed hooks to addons, new filemanager and database, many new api methods you will loose ALL your LINKS, webinterface will NOT work --- module/interaction/CaptchaManager.py | 158 ------------------------------- module/interaction/EventManager.py | 12 ++- module/interaction/InteractionManager.py | 33 +++++-- module/interaction/InteractionTask.py | 87 ++++------------- 4 files changed, 50 insertions(+), 240 deletions(-) delete mode 100644 module/interaction/CaptchaManager.py (limited to 'module/interaction') diff --git a/module/interaction/CaptchaManager.py b/module/interaction/CaptchaManager.py deleted file mode 100644 index 02cd10a11..000000000 --- a/module/interaction/CaptchaManager.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- 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 . - - @author: mkaay, RaNaN -""" - -from time import time -from traceback import print_exc -from threading import Lock - -class CaptchaManager(): - def __init__(self, core): - self.lock = Lock() - self.core = core - self.tasks = [] #task store, for outgoing tasks only - - self.ids = 0 #only for internal purpose - - def newTask(self, img, format, file, result_type): - task = CaptchaTask(self.ids, img, format, file, result_type) - self.ids += 1 - return task - - def removeTask(self, task): - self.lock.acquire() - if task in self.tasks: - self.tasks.remove(task) - self.lock.release() - - def getTask(self): - self.lock.acquire() - for task in self.tasks: - if task.status in ("waiting", "shared-user"): - self.lock.release() - return task - self.lock.release() - return None - - def getTaskByID(self, tid): - self.lock.acquire() - for task in self.tasks: - if task.id == str(tid): #task ids are strings - self.lock.release() - return task - self.lock.release() - return None - - def handleCaptcha(self, task): - cli = self.core.isClientConnected() - - if cli: #client connected -> should solve the captcha - task.setWaiting(50) #wait 50 sec for response - - for plugin in self.core.hookManager.activePlugins(): - try: - plugin.newCaptchaTask(task) - except: - if self.core.debug: - print_exc() - - if task.handler or cli: #the captcha was handled - self.tasks.append(task) - return True - - task.error = _("No Client connected for captcha decrypting") - - return False - - -class CaptchaTask(): - def __init__(self, id, img, format, file, result_type='textual'): - self.id = str(id) - self.captchaImg = img - self.captchaFormat = format - self.captchaFile = file - self.captchaResultType = result_type - self.handler = [] #the hook plugins that will take care of the solution - self.result = None - self.waitUntil = None - self.error = None #error message - - self.status = "init" - self.data = {} #handler can store data here - - def getCaptcha(self): - return self.captchaImg, self.captchaFormat, self.captchaResultType - - def setResult(self, text): - if self.isTextual(): - self.result = text - if self.isPositional(): - try: - parts = text.split(',') - self.result = (int(parts[0]), int(parts[1])) - except: - self.result = None - - def getResult(self): - try: - res = self.result.encode("utf8", "replace") - except: - res = self.result - - return res - - def getStatus(self): - return self.status - - def setWaiting(self, sec): - """ let the captcha wait secs for the solution """ - self.waitUntil = max(time() + sec, self.waitUntil) - self.status = "waiting" - - def isWaiting(self): - if self.result or self.error or time() > self.waitUntil: - return False - - return True - - def isTextual(self): - """ returns if text is written on the captcha """ - return self.captchaResultType == 'textual' - - def isPositional(self): - """ returns if user have to click a specific region on the captcha """ - return self.captchaResultType == 'positional' - - def setWatingForUser(self, exclusive): - if exclusive: - self.status = "user" - else: - self.status = "shared-user" - - def timedOut(self): - return time() > self.waitUntil - - def invalid(self): - """ indicates the captcha was not correct """ - [x.captchaInvalid(self) for x in self.handler] - - def correct(self): - [x.captchaCorrect(self) for x in self.handler] - - def __str__(self): - return "" % self.id diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py index 38faa3c46..02ecb82fb 100644 --- a/module/interaction/EventManager.py +++ b/module/interaction/EventManager.py @@ -1,14 +1,17 @@ # -*- coding: utf-8 -*- +from threading import Lock from traceback import print_exc from time import time +from module.utils import lock + class EventManager: """ Handles all Event related task, also stores an Event queue for clients, so they can retrieve them later. **Known Events:** - Most hook methods exists as events. These are some additional known events. + Most addon methods exists as events. These are some additional known events. ===================== ================ =========================================================== Name Arguments Description @@ -38,6 +41,8 @@ class EventManager: self.clients = {} self.events = {"metaEvent": []} + self.lock = Lock() + def getEvents(self, uuid): """ Get accumulated events for uuid since last call, this also registeres new client """ if uuid not in self.clients: @@ -80,6 +85,10 @@ class EventManager: if self.core.debug: print_exc() + self.updateClients(event, args) + + @lock + def updateClients(self, event, args): # append to client event queue if event in self.CLIENT_EVENTS: for uuid, client in self.clients.items(): @@ -88,7 +97,6 @@ class EventManager: else: client.append(event, args) - def removeFromEvents(self, func): """ Removes func from all known events """ for name, events in self.events.iteritems(): diff --git a/module/interaction/InteractionManager.py b/module/interaction/InteractionManager.py index 5ebcd1fcd..c547e1c97 100644 --- a/module/interaction/InteractionManager.py +++ b/module/interaction/InteractionManager.py @@ -15,10 +15,13 @@ @author: RaNaN """ -from utils import lock from traceback import print_exc from threading import Lock +from module.utils import lock, bits_set + +from InteractionTask import InteractionTask + class InteractionManager: """ Class that gives ability to interact with the user. @@ -30,13 +33,25 @@ class InteractionManager: self.core = core self.tasks = [] #task store, for outgoing tasks only + self.last_clients = {} + self.ids = 0 #only for internal purpose + def work(self): - """Mainloop that gets the work done""" + pass + + @lock + def newNotification(self): + pass + + @lock + def newQueryTask(self): + pass - def newTask(self, img, format, file, result_type): - task = CaptchaTask(self.ids, img, format, file, result_type) + @lock + def newCaptchaTask(self, img, format, file, result_type): + task = InteractionTask(self.ids, img, format, file, result_type) self.ids += 1 return task @@ -48,14 +63,12 @@ class InteractionManager: @lock def getTask(self): for task in self.tasks: - if task.status in ("waiting", "shared-user"): - return task + return task @lock - def getTaskByID(self, tid): + def getTaskByID(self, iid): for task in self.tasks: - if task.id == str(tid): #task ids are strings - self.lock.release() + if task.id == iid: return task def handleCaptcha(self, task): @@ -64,7 +77,7 @@ class InteractionManager: if cli: #client connected -> should solve the captcha task.setWaiting(50) #wait 50 sec for response - for plugin in self.core.hookManager.activePlugins(): + for plugin in self.core.addonManager.activePlugins(): try: plugin.newCaptchaTask(task) except: diff --git a/module/interaction/InteractionTask.py b/module/interaction/InteractionTask.py index 97cb16794..7963a5c72 100644 --- a/module/interaction/InteractionTask.py +++ b/module/interaction/InteractionTask.py @@ -16,6 +16,8 @@ @author: RaNaN """ +from time import time + from module.Api import InteractionTask as BaseInteractionTask from module.Api import Input, Output @@ -27,103 +29,48 @@ class InteractionTask(BaseInteractionTask): #: Plugins can put needed data here storage = None #: Timestamp when task expires - waitUntil = 0 - #: Default data to be used, or True if preset should be used - default = None + wait_until = 0 #: The received result as string representation result = None #: List of registered handles handler = None - #: Callback functions - callbacks = None #: Error Message error = None - #: Status string - status = None def __init__(self, *args, **kwargs): BaseInteractionTask.__init__(self, *args, **kwargs) # additional internal attributes self.storage = {} - self.default = [] self.handler = [] - self.callbacks = [] - - -class CaptchaTask: - def __init__(self, id, img, format, file, result_type='textual'): - self.id = str(id) - self.captchaImg = img - self.captchaFormat = format - self.captchaFile = file - self.captchaResultType = result_type - self.handler = [] #the hook plugins that will take care of the solution - self.result = None - self.waitUntil = None - self.error = None #error message - - self.status = "init" - self.data = {} #handler can store data here - - def getCaptcha(self): - return self.captchaImg, self.captchaFormat, self.captchaResultType - - def setResult(self, text): - if self.isTextual(): - self.result = text - if self.isPositional(): - try: - parts = text.split(',') - self.result = (int(parts[0]), int(parts[1])) - except: - self.result = None + self.wait_until = 0 - def getResult(self): - try: - res = self.result.encode("utf8", "replace") - except: - res = self.result + def convertResult(self, value): + return value - return res + def getResult(self): + return self.result - def getStatus(self): - return self.status + def setResult(self, value): + pass def setWaiting(self, sec): - """ let the captcha wait secs for the solution """ - self.waitUntil = max(time() + sec, self.waitUntil) - self.status = "waiting" + self.wait_until = max(time() + sec, self.wait_until) - def isWaiting(self): + def isWaiting(self, sec): if self.result or self.error or time() > self.waitUntil: return False return True - def isTextual(self): - """ returns if text is written on the captcha """ - return self.captchaResultType == 'textual' - - def isPositional(self): - """ returns if user have to click a specific region on the captcha """ - return self.captchaResultType == 'positional' - - def setWatingForUser(self, exclusive): - if exclusive: - self.status = "user" - else: - self.status = "shared-user" - def timedOut(self): return time() > self.waitUntil - def invalid(self): - """ indicates the captcha was not correct """ - [x.captchaInvalid(self) for x in self.handler] - def correct(self): - [x.captchaCorrect(self) for x in self.handler] + [x.taskCorrect(self) for x in self.handler] + + def invalid(self): + [x.taskInvalid(self) for x in self.handler] def __str__(self): - return "" % self.id + return "" % self.id \ No newline at end of file -- cgit v1.2.3 From 50d4df8b4d48b855bd18e9922355b7f3f2b4da4e Mon Sep 17 00:00:00 2001 From: RaNaN Date: Tue, 20 Mar 2012 14:57:45 +0100 Subject: captcha decrypting for all plugin types, new interaction manager --- module/interaction/InteractionManager.py | 127 +++++++++++++++++++++++-------- module/interaction/InteractionTask.py | 17 +++-- 2 files changed, 105 insertions(+), 39 deletions(-) (limited to 'module/interaction') diff --git a/module/interaction/InteractionManager.py b/module/interaction/InteractionManager.py index c547e1c97..0c125bdd4 100644 --- a/module/interaction/InteractionManager.py +++ b/module/interaction/InteractionManager.py @@ -15,10 +15,13 @@ @author: RaNaN """ -from traceback import print_exc from threading import Lock +from time import time -from module.utils import lock, bits_set +from new_collections import OrderedDict + +from module.utils import lock, bits_set, to_list +from module.Api import Input, Output from InteractionTask import InteractionTask @@ -28,69 +31,127 @@ class InteractionManager: Arbitary task with predefined output and input type can be set off. Asyncronous callbacks and default values keeps the ability to fallback if no user is present. """ + + # number of seconds a client is classified as active + CLIENT_THRESHOLD = 60 + def __init__(self, core): self.lock = Lock() self.core = core - self.tasks = [] #task store, for outgoing tasks only + self.tasks = OrderedDict() #task store, for outgoing tasks only + self.notifications = [] #list of notifications - self.last_clients = {} + self.last_clients = { + Output.Notification : 0, + Output.Captcha : 0, + Output.Query : 0, + } self.ids = 0 #only for internal purpose + def isClientConnected(self, mode=Output.All): + if mode == Output.All: + return max(self.last_clients.values()) + self.CLIENT_THRESHOLD <= time() + else: + self.last_clients.get(mode, 0) + self.CLIENT_THRESHOLD <= time() + + def updateClient(self, mode): + t = time() + for output in self.last_clients: + if bits_set(output, mode): + self.last_clients[output] = t + + @lock def work(self): - pass + # old notifications will be removed + for n in [x for x in self.notifications if x.timedOut()]: + self.notifications.remove(n) + + # store at most 100 notifications + del self.notifications[50:] + @lock - def newNotification(self): - pass + def createNotification(self, title, content, desc="", plugin=""): + """ 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, Input.Text, [content], Output.Notification, "", title, desc, plugin) + self.ids += 1 + self.notifications.insert(0, task) + self.handleTask(task) + return task @lock - def newQueryTask(self): - pass + def newQueryTask(self, input, data, desc, default="", plugin=""): + task = InteractionTask(self.ids, input, to_list(data), Output.Query, default, _("Query"), desc, plugin) + self.ids += 1 + return task @lock - def newCaptchaTask(self, img, format, file, result_type): - task = InteractionTask(self.ids, img, format, file, result_type) + def newCaptchaTask(self, img, format, filename, plugin="", input=Input.Text): + #todo: title desc plugin + task = InteractionTask(self.ids, input, [img, format, filename],Output.Captcha, + "", _("Captcha request"), _("Please solve the captcha."), plugin) self.ids += 1 return task @lock def removeTask(self, task): - if task in self.tasks: - self.tasks.remove(task) + if task.iid in self.tasks: + del self.tasks[task.iid] @lock - def getTask(self): - for task in self.tasks: - return task + def getTask(self, mode=Output.All): + self.updateClient(mode) + + for task in self.tasks.itervalues(): + if mode == Output.All or bits_set(task.output, mode): + return task + + @lock + def getNotifications(self): + """retrieves notifications, old ones are only deleted after a while\ + client has to make sure itself to dont display it twice""" + for n in self.notifications: + n.setWaiting(self.CLIENT_THRESHOLD * 5, True) + #store notification for shorter period, lock the timeout + + return self.notifications + + def isTaskWaiting(self, mode=Output.All): + return self.getTask(mode) is not None @lock def getTaskByID(self, iid): - for task in self.tasks: - if task.id == iid: - return task + if iid in self.tasks: + task = self.tasks[iid] + del self.tasks[iid] + return task - def handleCaptcha(self, task): - cli = self.core.isClientConnected() + def handleTask(self, task): + cli = self.isClientConnected(task.output) - if cli: #client connected -> should solve the captcha - task.setWaiting(50) #wait 50 sec for response + if cli: #client connected -> should handle the task + task.setWaiting(self.CLIENT_THRESHOLD) # wait for response + + if task.output == Output.Notification: + task.setWaiting(60 * 60 * 30) # notifications are valid for 30h for plugin in self.core.addonManager.activePlugins(): try: - plugin.newCaptchaTask(task) + plugin.newInteractionTask(task) except: - if self.core.debug: - print_exc() - - if task.handler or cli: #the captcha was handled - self.tasks.append(task) - return True - - task.error = _("No Client connected for captcha decrypting") + self.core.print_exc() - return False + if task.output != Output.Notification: + self.tasks[task.iid] = task if __name__ == "__main__": diff --git a/module/interaction/InteractionTask.py b/module/interaction/InteractionTask.py index 7963a5c72..b372321b0 100644 --- a/module/interaction/InteractionTask.py +++ b/module/interaction/InteractionTask.py @@ -30,12 +30,14 @@ class InteractionTask(BaseInteractionTask): storage = None #: Timestamp when task expires wait_until = 0 - #: The received result as string representation + #: The received result result = None #: List of registered handles handler = None #: Error Message error = None + #: Timeout locked + locked = False def __init__(self, *args, **kwargs): BaseInteractionTask.__init__(self, *args, **kwargs) @@ -46,25 +48,28 @@ class InteractionTask(BaseInteractionTask): self.wait_until = 0 def convertResult(self, value): + #TODO: convert based on input/output return value def getResult(self): return self.result def setResult(self, value): - pass + self.result = self.convertResult(value) - def setWaiting(self, sec): - self.wait_until = max(time() + sec, self.wait_until) + def setWaiting(self, sec, lock=False): + if not self.locked: + self.wait_until = max(time() + sec, self.wait_until) + if lock: self.locked = True - def isWaiting(self, sec): + def isWaiting(self): if self.result or self.error or time() > self.waitUntil: return False return True def timedOut(self): - return time() > self.waitUntil + return time() > self.wait_until > 0 def correct(self): [x.taskCorrect(self) for x in self.handler] -- cgit v1.2.3 From b40b32ee05f611323a7827fad2a25fa0a28dcb24 Mon Sep 17 00:00:00 2001 From: X3n0m0rph59 Date: Sun, 22 Apr 2012 19:56:17 +0200 Subject: a huge pile of spelling fixes --- module/interaction/EventManager.py | 16 ++++++++-------- module/interaction/InteractionManager.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'module/interaction') diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py index 02ecb82fb..976a92413 100644 --- a/module/interaction/EventManager.py +++ b/module/interaction/EventManager.py @@ -8,20 +8,20 @@ from module.utils import lock class EventManager: """ - Handles all Event related task, also stores an Event queue for clients, so they can retrieve them later. + Handles all event-related tasks, also stores an event queue for clients, so they can retrieve them later. **Known Events:** - Most addon methods exists as events. These are some additional known events. + Most addon methods exist as events. These are some additional known events. ===================== ================ =========================================================== Name Arguments Description ===================== ================ =========================================================== - metaEvent eventName, *args Called for every event, with eventName and orginal args + metaEvent eventName, *args Called for every event, with eventName and original args downloadPreparing fid A download was just queued and will be prepared now. - downloadStarts fid A plugin will immediately starts the download afterwards. - linksAdded links, pid Someone just added links, you are able to modify the links. - allDownloadsProcessed Every link was handled, pyload would idle afterwards. - allDownloadsFinished Every download in queue is finished. + downloadStarts fid A plugin will immediately start the download afterwards. + linksAdded links, pid Someone just added links, you are able to modify these links. + allDownloadsProcessed All links were handled, pyLoad would idle afterwards. + allDownloadsFinished All downloads in the queue are finished. unrarFinished folder, fname An Unrar job finished configChanged sec, opt, value The config was changed. ===================== ================ =========================================================== @@ -44,7 +44,7 @@ class EventManager: self.lock = Lock() def getEvents(self, uuid): - """ Get accumulated events for uuid since last call, this also registeres new client """ + """ 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() diff --git a/module/interaction/InteractionManager.py b/module/interaction/InteractionManager.py index 0c125bdd4..1d26b1665 100644 --- a/module/interaction/InteractionManager.py +++ b/module/interaction/InteractionManager.py @@ -28,8 +28,8 @@ from InteractionTask import InteractionTask class InteractionManager: """ Class that gives ability to interact with the user. - Arbitary task with predefined output and input type can be set off. - Asyncronous callbacks and default values keeps the ability to fallback if no user is present. + Arbitrary tasks with predefined output and input types can be set off. + Asynchronous callbacks and default values keep the ability to fallback if no user is present. """ # number of seconds a client is classified as active -- cgit v1.2.3