summaryrefslogtreecommitdiffstats
path: root/module/interaction
diff options
context:
space:
mode:
Diffstat (limited to 'module/interaction')
-rw-r--r--module/interaction/InteractionManager.py143
-rw-r--r--module/interaction/InteractionTask.py37
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