Example #1
0
class CoreCommand:
    "Base command"

    _events = {}
    _entries = {}
    _native_pool = None
    _progress_counter = itertools.count(1)
    _progresses = LRUCache(1000 * 1000)

    def __new__(cls, *args, **kwargs):
        obj = super(CoreCommand, cls).__new__(cls)
        obj._get_commands()
        return obj

    def __init__(self, priority=constants.Priority.Normal):
        self._created_time = arrow.now()
        self.command_id = None
        self._started_time = None
        self._finished_time = None
        self._priority = priority
        self._futures = []
        self._progress_max = None
        self._progress_current = None
        self._progress_text = None
        self._progress_count = None
        self._progress_type = None
        self._progress_tree = None
        self._progress_time = None
        self._progress_timestamp = 0
        self._progress_title = self.__class__.__name__

    def _run(self, *args, **kwargs):
        """
        Run the command with *args and **kwargs.
        """
        log.d("Running command:", self.__class__.__name__)
        r = self.main(*args, **kwargs)
        log.d("Finished running command:", self.__class__.__name__)
        return r

    @classmethod
    def get_all_progress(cls):
        ps = []
        for c, t in cls._progresses.items():
            x = t.get_node(t.root).data()
            if x:
                ps.insert(0, x.get_progress())
        return ps

    def _add_progress(self, add=True):
        if not self._progress_count:
            self._progress_count = next(self._progress_counter)

        if self._progress_tree is None and self._progress_count not in self._progresses:
            self._progress_tree = Tree()
            if add:
                self._progresses[self._progress_count] = self._progress_tree
            self._progress_tree.create_node(self._progress_count,
                                            self._progress_count,
                                            data=weakref.ref(self))
            self._progress_timestamp = arrow.now().timestamp

        self._progress_time = arrow.now()

    def merge(self, cmd):
        """
        Merge this command into given command
        """
        assert cmd is None or isinstance(cmd, CoreCommand)
        if cmd:
            self.merge_progress_into(cmd)
        return self

    def merge_progress_into(self, cmd):
        assert isinstance(cmd, CoreCommand)
        cmd._add_progress()
        self._add_progress(False)
        cmd._progress_tree.paste(cmd._progress_count, self._progress_tree)

        self._progress_tree = cmd._progress_tree

        if self._progress_count in self._progresses:
            del self._progresses[self._progress_count]

    def _str_progress_tree(self):
        self._tree_reader = ""

        def w(l):
            self._tree_reader = l.decode('utf-8') + '\n'

        try:
            self._progress_tree._Tree__print_backend(func=w)
        except tree_exceptions.NodeIDAbsentError:
            self._tree_reader = "Tree is empty"
        return self._tree_reader

    def get_progress(self):

        if self._progress_tree:
            log.d("Command", self,
                  "progress tree:\n{}".format(self._str_progress_tree()))
            p = {
                'title': self._progress_title,
                'subtitle': '',
                'subtype': None,
                'text': '',
                'value': .0,
                'percent': .0,
                'max': .0,
                'type': self._progress_type,
                'state': self.state.value if hasattr(self, "state") else None,
                'timestamp': self._progress_timestamp
            }

            t = self._progress_tree.subtree(self._progress_count)
            prog_time = self._progress_time
            prog_text = self._progress_text if self._progress_text else ''
            prog_subtitle = ''
            prog_subtype = None
            for _, n in t.nodes.items():
                cmd = n.data()
                if cmd:
                    if cmd._progress_max:
                        p['max'] += cmd._progress_max
                    if cmd._progress_current:
                        p['value'] += cmd._progress_current

                    if not prog_time or (cmd._progress_time
                                         and cmd._progress_time > prog_time):
                        prog_text = cmd._progress_text
                        prog_subtitle = cmd._progress_title
                        prog_subtype = cmd._progress_type
            if p['max']:
                p['percent'] = (100 / p['max']) * p['value']
            else:
                p['percent'] = -1.0
            p['text'] = prog_text
            p['subtitle'] = prog_subtitle
            p['subtype'] = prog_subtype
            return p
        return None

    def set_progress(self, value=None, text=None, title=None, type_=None):
        assert value is None or isinstance(value, (int, float))
        assert text is None or isinstance(text, str)
        assert title is None or isinstance(text, str)
        self._add_progress()
        if title is not None:
            self._progress_title = title
        if value is not None:
            self._progress_current = value
        if text is not None:
            self._progress_text = text
        if type_ is not None:
            self._progress_type = type_

    def set_max_progress(self, value, add=False):
        assert isinstance(value, (int, float))
        self._add_progress()
        if add:
            if self._progress_max is None:
                self._progress_max = 0
            self._progress_max += value
        else:
            self._progress_max = value

    def next_progress(self, add=1, text=None, _from=0):
        assert isinstance(add, (int, float))
        if self._progress_current is None:
            self._progress_current = _from
        if text is not None:
            self._progress_text = text
        self._progress_current += add
        utils.switch(self._priority)

    @contextmanager
    def progress(self, max_progress=None, text=None):
        if max_progress is not None:
            self.set_max_progress(max_progress)
        yield
        if max_progress is not None:
            self.set_progress(max_progress, text)

    def run_native(self, f, *args, **kwargs):
        f = async_utils.AsyncFuture(
            self, self._native_pool.apply_async(_native_runner(f), args,
                                                kwargs))
        self._futures.append(f)
        return f

    def push(self, msg, scope=None):
        if constants.notification:
            return constants.notification.push(msg, scope=scope)
        # TODO: raise error perhaps?

    def kill(self):
        [f.kill() for f in self._futures]

    def _log_stats(self, d=None):
        create_delta = self._finished_time - self._created_time
        run_delta = self._finished_time - self._started_time
        log_delta = (d - self._finished_time) if d else None
        log.i(
            "Command - '{}' -".format(self.__class__.__name__),
            "ID({})".format(self.command_id) if self.command_id else '',
            "running time:\n",
            "\t\tCreation delta: {} (time between creation and finish)\n".
            format(create_delta),
            "\t\tRunning delta: {} (time between start and finish)\n".format(
                run_delta),
            "\t\tLog delta: {} (time between finish and this log)\n".format(
                log_delta),
        )

    def __del__(self):
        if hasattr(self, '_progress_count') and hasattr(self, '_progresses'):
            if self._progress_count and self._progress_count in self._progresses:
                del self._progresses[self._progress_count]

    @classmethod
    def _get_commands(cls, self=None):
        ""
        if self is not None:
            cls = self
        events = {}
        entries = {}
        for a in cls.__dict__.values():
            if isinstance(a, CommandEvent):
                a.command_cls = cls
                events[a.name] = a
                a._init()

            if isinstance(a, CommandEntry):
                a.command_cls = cls
                entries[a.name] = a
                a._init()
        cls._entries = entries
        cls._events = events
        return entries, events