Esempio n. 1
0
class ExchangeManager(BaseManager):
    """
    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):
        BaseManager.__init__(self, core)
        self.tasks = OrderedDict()  # task store, for all outgoing tasks
        self.last_clients = {}
        self.ids = 0  # uniue interaction ids

    def is_client_connected(self, user):
        return self.last_clients.get(user,
                                     0) + self.CLIENT_THRESHOLD > time.time()

    @lock
    def work(self):
        # old notifications will be removed
        for n in (k for k, v in self.tasks.items() if v.timed_out()):
            del self.tasks[n]

        # keep notifications count limited
        n = [
            k for k, v in self.tasks.items()
            if v.type == Interaction.Notification
        ][::-1]
        for v in n[:self.MAX_NOTIFICATIONS]:
            del self.tasks[v]

    @lock
    def create_notification(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,
                               Interaction.Notification,
                               Input(InputType.Str, None, content),
                               title,
                               desc,
                               plugin,
                               owner=owner)
        self.ids += 1
        self.queue_task(task)
        return task

    @lock
    def create_query_task(self, input, desc, plugin="", owner=None):
        # input type was given, create a input widget
        if isinstance(input, int):
            input = Input(input)
        if not isinstance(input, Input):
            raise TypeError("'Input' class expected not '{0}'".format(
                type(input)))

        task = InteractionTask(self.ids,
                               Interaction.Query,
                               input,
                               self._("Query"),
                               desc,
                               plugin,
                               owner=owner)
        self.ids += 1
        self.queue_task(task)
        return task

    @lock
    def create_captcha_task(self,
                            img,
                            format,
                            filename,
                            plugin="",
                            type_=InputType.Str,
                            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.Str
        elif type_ == 'positional':
            type_ = InputType.Click

        input = Input(type_, data=[standard_b64encode(img), format, filename])

        # TODO: title desc plugin
        task = InteractionTask(self.ids,
                               Interaction.Captcha,
                               input,
                               self._("Captcha request"),
                               self._("Please solve the captcha"),
                               plugin,
                               owner=owner)

        self.ids += 1
        self.queue_task(task)
        return task

    @lock
    def remove_task(self, task):
        if task.iid in self.tasks:
            del self.tasks[task.iid]
            self.__pyload.evm.fire("interaction:deleted", task.iid)

    @lock
    def get_task_by_id(self, iid):
        return self.tasks.get(iid, None)

    @lock
    def get_tasks(self, user, mode=Interaction.All):
        # update last active clients
        self.last_clients[user] = time.time()

        # filter current mode
        tasks = [
            tsk for tsk in self.tasks.values()
            if mode == Interaction.All or bitset(tsk.type, mode)
        ]
        # filter correct user / or shared
        tasks = [
            tsk for tsk in tasks
            if user is None or user == tsk.owner or tsk.shared
        ]

        return tasks

    def is_task_waiting(self, user, mode=Interaction.All):
        tasks = [
            tsk for tsk in self.get_tasks(user, mode)
            if not tsk.type == Interaction.Notification or not tsk.seen
        ]
        return len(tasks) > 0

    def queue_task(self, task):
        cli = self.is_client_connected(task.owner)

        # set waiting times based on threshold
        if cli:
            task.set_waiting(self.CLIENT_THRESHOLD)
        else:  # TODO: higher threshold after client connects?
            task.set_waiting(self.CLIENT_THRESHOLD // 3)

        if task.type == Interaction.Notification:
            # notifications are valid for 30h
            task.set_waiting(self.NOTIFICATION_TIMEOUT)

        for plugin in self.__pyload.adm.active_plugins():
            try:
                plugin.new_interaction_task(task)
            except Exception:
                # self.__pyload.print_exc()
                pass

        self.tasks[task.iid] = task
        self.__pyload.evm.fire("interaction:added", task)
Esempio n. 2
0
class InteractionManager(object):
    """
    Class that gives ability to interact with the user.
    Arbitrary tasks with predefined output and input types can be set off
    """
    __slots__ = [
        'CLIENT_THRESHOLD',
        'MAX_NOTIFICATIONS',
        'NOTIFICATION_TIMEOUT',
        'ids',
        'last_clients',
        'lock',
        'pyload',
        'tasks']

    # 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.pyload = core
        self.tasks = OrderedDict()  #: task store, for all outgoing tasks
        self.last_clients = {}
        self.ids = 0  #: uniue interaction ids

    def is_client_connected(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.items() if v.timed_out()]:
            del self.tasks[n]

        # keep notifications count limited
        n = [k for k, v in self.tasks.items() if v.type == Interaction.Notification][
            ::-1]
        for v in n[:self.MAX_NOTIFICATIONS]:
            del self.tasks[v]

    @lock
    def create_notification(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, Interaction.Notification, Input(InputType.Text, None, content), title, desc, plugin,
                               owner=owner)
        self.ids += 1
        self.queue_task(task)
        return task

    @lock
    def create_query_task(self, input, desc, plugin="", owner=None):
        # input type was given, create a input widget
        if isinstance(input, int):
            input = Input(input)
        if not isinstance(input, Input):
            raise TypeError(
                "'Input' class expected not '{0}'".format(type(input)))

        task = InteractionTask(self.ids, Interaction.Query, input, _(
            "Query"), desc, plugin, owner=owner)
        self.ids += 1
        self.queue_task(task)
        return task

    @lock
    def create_captcha_task(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, Interaction.Captcha, input,
                               _("Captcha request"), _("Please solve the captcha"), plugin, owner=owner)

        self.ids += 1
        self.queue_task(task)
        return task

    @lock
    def remove_task(self, task):
        if task.iid in self.tasks:
            del self.tasks[task.iid]
            self.pyload.evm.fire("interaction:deleted", task.iid)

    @lock
    def get_task_by_id(self, iid):
        return self.tasks.get(iid, None)

    @lock
    def get_tasks(self, user, mode=Interaction.All):
        # update last active clients
        self.last_clients[user] = time()

        # filter current mode
        tasks = [t for t in self.tasks.values() if mode ==
                 Interaction.All or bitset(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 is_task_waiting(self, user, mode=Interaction.All):
        tasks = [t for t in self.get_tasks(
            user, mode) if not t.type == Interaction.Notification or not t.seen]
        return len(tasks) > 0

    def queue_task(self, task):
        cli = self.is_client_connected(task.owner)

        # set waiting times based on threshold
        if cli:
            task.set_waiting(self.CLIENT_THRESHOLD)
        else:  # TODO: higher threshold after client connects?
            task.set_waiting(self.CLIENT_THRESHOLD // 3)

        if task.type == Interaction.Notification:
            # notifications are valid for 30h
            task.set_waiting(self.NOTIFICATION_TIMEOUT)

        for plugin in self.pyload.adm.active_plugins():
            try:
                plugin.new_interaction_task(task)
            except Exception:
                # self.pyload.print_exc()
                pass

        self.tasks[task.iid] = task
        self.pyload.evm.fire("interaction:added", task)
Esempio n. 3
0
class ConfigManager(ConfigParser):
    """
    Manages the core config and configs for addons and single user.
    Has similar interface to ConfigParser.
    """
    __slots__ = ['config', 'parser', 'pyload', 'values']

    def __init__(self, core, parser):
        # No __init__ call to super class is needed!

        self.pyload = core

        # The config parser, holding the core config
        self.parser = parser

        # similar to parser, separated meta data and values
        self.config = OrderedDict()

        # Value cache for multiple user configs
        # Values are populated from db on first access
        # Entries are saved as (user, section) keys
        self.values = {}
        # TODO: similar to a cache, could be deleted periodically

    def save(self):
        self.parser.save()

    @convertkeyerror
    def get(self, section, option, user=None):
        """
        Get config value, core config only available for admins.
        if user is not valid default value will be returned.
        """
        # Core config loaded from parser, when no user is given or he is admin
        if section in self.parser and user is None:
            return self.parser.get(section, option)
        else:
            # We need the id and not the instance
            # Will be None for admin user and so the same as internal access
            try:
                # Check if this config exists
                # Configs without meta data can not be loaded!
                assert self.config[section].config[option]
                return self.load_values(user, section)[option]
            except KeyError:
                pass  #: Returns default value later

        return self.config[section].config[option].input.default_value

    def load_values(self, user, section):
        if (user, section) not in self.values:
            conf = self.pyload.db.load_config(section, user)
            try:
                self.values[user, section] = json.loads(conf) if conf else {}
            except ValueError:  #: Something did go wrong when parsing
                self.values[user, section] = {}
                # self.pyload.print_exc()

        return self.values[user, section]

    @convertkeyerror
    def set(self, section, option, value, sync=True, user=None):
        """
        Set config value.
        """
        changed = False
        if section in self.parser and user is None:
            changed = self.parser.set(section, option, value, sync)
        else:
            data = self.config[section].config[option]
            value = from_string(value, data.input.type)
            old_value = self.get(section, option)

            # Values will always be saved to db, sync is ignored
            if value != old_value:
                changed = True
                self.values[user, section][option] = value
                if sync:
                    self.save_values(user, section)

        if changed:
            self.pyload.evm.fire("config:changed", section, option, value)
        return changed

    def save_values(self, user, section):
        if section in self.parser and user is None:
            self.save()
        elif (user, section) in self.values:
            self.pyload.db.save_config(section,
                                       json.dumps(self.values[user, section]),
                                       user)

    def delete(self, section, user=None):
        """
        Deletes values saved in db and cached values for given user, NOT meta data.
        Does not trigger an error when nothing was deleted.
        """
        if (user, section) in self.values:
            del self.values[user, section]

        self.pyload.db.delete_config(section, user)
        self.pyload.evm.fire("config:deleted", section, user)

    def iter_core_sections(self):
        return self.parser.iter_sections()

    def iter_sections(self, user=None):
        """
        Yields: section, metadata, values.
        """
        values = self.pyload.db.load_configs_for_user(user)

        # Every section needs to be json decoded
        for section, data in values.items():
            try:
                values[section] = json.loads(data) if data else {}
            except ValueError:
                values[section] = {}
                # self.pyload.print_exc()

        for name, config in self.config.items():
            yield name, config, values[name] if name in values else {}

    def get_section(self, section, user=None):
        if section in self.parser and user is None:
            return self.parser.get_section(section)

        values = self.load_values(user, section)
        return self.config.get(section), values
Esempio n. 4
0
class ConfigManager(ConfigParser):
    """
    Manages the core config and configs for addons and single user.
    Has similar interface to ConfigParser.
    """
    __slots__ = ['config', 'parser', 'pyload', 'values']

    def __init__(self, core, parser):
        # No __init__ call to super class is needed!

        self.pyload = core

        # The config parser, holding the core config
        self.parser = parser

        # similar to parser, separated meta data and values
        self.config = OrderedDict()

        # Value cache for multiple user configs
        # Values are populated from db on first access
        # Entries are saved as (user, section) keys
        self.values = {}
        # TODO: similar to a cache, could be deleted periodically

    def save(self):
        self.parser.save()

    @convertkeyerror
    def get(self, section, option, user=None):
        """
        Get config value, core config only available for admins.
        if user is not valid default value will be returned.
        """
        # Core config loaded from parser, when no user is given or he is admin
        if section in self.parser and user is None:
            return self.parser.get(section, option)
        else:
            # We need the id and not the instance
            # Will be None for admin user and so the same as internal access
            try:
                # Check if this config exists
                # Configs without meta data can not be loaded!
                assert self.config[section].config[option]
                return self.load_values(user, section)[option]
            except KeyError:
                pass  #: Returns default value later

        return self.config[section].config[option].input.default_value

    def load_values(self, user, section):
        if (user, section) not in self.values:
            conf = self.pyload.db.load_config(section, user)
            try:
                self.values[user, section] = json.loads(conf) if conf else {}
            except ValueError:  #: Something did go wrong when parsing
                self.values[user, section] = {}
                # self.pyload.print_exc()

        return self.values[user, section]

    @convertkeyerror
    def set(self, section, option, value, sync=True, user=None):
        """
        Set config value.
        """
        changed = False
        if section in self.parser and user is None:
            changed = self.parser.set(section, option, value, sync)
        else:
            data = self.config[section].config[option]
            value = from_string(value, data.input.type)
            old_value = self.get(section, option)

            # Values will always be saved to db, sync is ignored
            if value != old_value:
                changed = True
                self.values[user, section][option] = value
                if sync:
                    self.save_values(user, section)

        if changed:
            self.pyload.evm.fire("config:changed", section, option, value)
        return changed

    def save_values(self, user, section):
        if section in self.parser and user is None:
            self.save()
        elif (user, section) in self.values:
            self.pyload.db.save_config(section, json.dumps(
                self.values[user, section]), user)

    def delete(self, section, user=None):
        """
        Deletes values saved in db and cached values for given user, NOT meta data.
        Does not trigger an error when nothing was deleted.
        """
        if (user, section) in self.values:
            del self.values[user, section]

        self.pyload.db.delete_config(section, user)
        self.pyload.evm.fire("config:deleted", section, user)

    def iter_core_sections(self):
        return self.parser.iter_sections()

    def iter_sections(self, user=None):
        """
        Yields: section, metadata, values.
        """
        values = self.pyload.db.load_configs_for_user(user)

        # Every section needs to be json decoded
        for section, data in values.items():
            try:
                values[section] = json.loads(data) if data else {}
            except ValueError:
                values[section] = {}
                # self.pyload.print_exc()

        for name, config in self.config.items():
            yield name, config, values[name] if name in values else {}

    def get_section(self, section, user=None):
        if section in self.parser and user is None:
            return self.parser.get_section(section)

        values = self.load_values(user, section)
        return self.config.get(section), values