diff options
Diffstat (limited to 'module/interaction')
-rw-r--r-- | module/interaction/InteractionManager.py | 143 | ||||
-rw-r--r-- | module/interaction/InteractionTask.py | 37 |
2 files changed, 103 insertions, 77 deletions
diff --git a/module/interaction/InteractionManager.py b/module/interaction/InteractionManager.py index 1d26b1665..e4ae05501 100644 --- a/module/interaction/InteractionManager.py +++ b/module/interaction/InteractionManager.py @@ -17,11 +17,13 @@ """ from threading import Lock from time import time +from base64 import standard_b64encode from new_collections import OrderedDict -from module.utils import lock, bits_set, to_list -from module.Api import Input, Output +from module.utils import lock, bits_set +from module.Api import Interaction as IA +from module.Api import InputType, Input from InteractionTask import InteractionTask @@ -29,51 +31,38 @@ class InteractionManager: """ Class that gives ability to interact with the user. 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 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 outgoing tasks only - self.notifications = [] #list of notifications + self.tasks = OrderedDict() #task store, for all outgoing tasks - self.last_clients = { - Output.Notification : 0, - Output.Captcha : 0, - Output.Query : 0, - } + self.last_clients = {} + self.ids = 0 #uniue interaction ids - 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 + 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 [x for x in self.notifications if x.timedOut()]: - self.notifications.remove(n) - - # store at most 100 notifications - del self.notifications[50:] + 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=""): + def createNotification(self, title, content, desc="", plugin="", owner=None): """ Creates and queues a new Notification :param title: short title @@ -82,67 +71,86 @@ class InteractionManager: :param plugin: plugin name :return: :class:`InteractionTask` """ - task = InteractionTask(self.ids, Input.Text, [content], Output.Notification, "", title, desc, plugin) + task = InteractionTask(self.ids, IA.Notification, Input(InputType.Text, content), "", title, desc, plugin, + owner=owner) self.ids += 1 - self.notifications.insert(0, task) - self.handleTask(task) + self.queueTask(task) return task @lock - def newQueryTask(self, input, data, desc, default="", plugin=""): - task = InteractionTask(self.ids, input, to_list(data), Output.Query, default, _("Query"), desc, plugin) + 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 newCaptchaTask(self, img, format, filename, plugin="", input=Input.Text): + 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, input, [img, format, filename],Output.Captcha, - "", _("Captcha request"), _("Please solve the captcha."), 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 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 + def getTaskByID(self, iid): + return self.tasks.get(iid, None) @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 + def getTasks(self, user, mode=IA.All): + # update last active clients + self.last_clients[user] = time() - return self.notifications + # 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] - def isTaskWaiting(self, mode=Output.All): - return self.getTask(mode) is not None + return tasks - @lock - def getTaskByID(self, iid): - if iid in self.tasks: - task = self.tasks[iid] - del self.tasks[iid] - return task + 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 handleTask(self, task): - cli = self.isClientConnected(task.output) + def queueTask(self, task): + cli = self.isClientConnected(task.owner) - if cli: #client connected -> should handle the task - task.setWaiting(self.CLIENT_THRESHOLD) # wait for response + # 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.output == Output.Notification: - task.setWaiting(60 * 60 * 30) # notifications are valid for 30h + if task.type == IA.Notification: + task.setWaiting(self.NOTIFICATION_TIMEOUT) # notifications are valid for 30h for plugin in self.core.addonManager.activePlugins(): try: @@ -150,10 +158,9 @@ class InteractionManager: except: self.core.print_exc() - if task.output != Output.Notification: - self.tasks[task.iid] = task + 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/module/interaction/InteractionTask.py b/module/interaction/InteractionTask.py index b372321b0..d2877b2b0 100644 --- a/module/interaction/InteractionTask.py +++ b/module/interaction/InteractionTask.py @@ -19,12 +19,12 @@ from time import time from module.Api import InteractionTask as BaseInteractionTask -from module.Api import Input, Output +from module.Api import Interaction, InputType, Input #noinspection PyUnresolvedReferences class InteractionTask(BaseInteractionTask): """ - General Interaction Task extends ITask defined by thrift with additional fields and methods. + General Interaction Task extends ITask defined by api with additional fields and methods. """ #: Plugins can put needed data here storage = None @@ -38,8 +38,21 @@ class InteractionTask(BaseInteractionTask): 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 @@ -54,28 +67,34 @@ class InteractionTask(BaseInteractionTask): 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: - self.wait_until = max(time() + sec, self.wait_until) + 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 time() > self.waitUntil: + if self.result or self.error or self.timedOut(): return False return True def timedOut(self): - return time() > self.wait_until > 0 + 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] - - def __str__(self): - return "<InteractionTask '%s'>" % self.id
\ No newline at end of file + [x.taskInvalid(self) for x in self.handler]
\ No newline at end of file |