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, None, content), title, desc, plugin, owner=owner) self.ids += 1 self.queueTask(task) return task @lock def createQueryTask(self, input, desc, 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, _("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, data=[standard_b64encode(img), format, filename]) #todo: title desc plugin task = InteractionTask(self.ids, IA.Captcha, input, _("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)
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, None, content), title, desc, plugin, owner=owner) self.ids += 1 self.queueTask(task) return task @lock def createQueryTask(self, input, desc, 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, _("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, data=[standard_b64encode(img), format, filename]) #todo: title desc plugin task = InteractionTask(self.ids, IA.Captcha, input, _("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)
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 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.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): # 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 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, 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, 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.iid in self.tasks: del self.tasks[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 @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): if iid in self.tasks: task = self.tasks[iid] del self.tasks[iid] return task def handleTask(self, task): cli = self.isClientConnected(task.output) 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.newInteractionTask(task) except: self.core.print_exc() if task.output != Output.Notification: self.tasks[task.iid] = task
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 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.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): # 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 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, 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, 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.iid in self.tasks: del self.tasks[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 @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): if iid in self.tasks: task = self.tasks[iid] del self.tasks[iid] return task def handleTask(self, task): cli = self.isClientConnected(task.output) 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.newInteractionTask(task) except: self.core.print_exc() if task.output != Output.Notification: self.tasks[task.iid] = task