Beispiel #1
0
    def _create_engine(self, account):
        self._engine = Engine(account, self._message_handler)

        self._main_view.load_engine_account(self._engine, account)
        self._set_actions()
        self._set_mediatypes_menu()
        self._update_widgets(account)
Beispiel #2
0
    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """
        print('Initializing engine...')
        self.engine = Engine(self.account, self.messagehandler)
        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.connect_signal('prompt_for_add', self._ask_add)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

        print()
        print("Ready. Type 'help' for a list of commands.")
        print("Press tab for autocompletion and up/down for command history.")
        self.do_filter(None) # Show available filters
        print()
Beispiel #3
0
    def __init__(self, accountnum=1):
        """
		Defaults to the first account.
		"""
        self.watch = Watcher()
        self.accs = dict(AccountManager().get_accounts())
        self.engine = Engine(self.accs.get(accountnum))
        self.engine.start()
        self.tList = list(self.engine.get_list())
        with open(self.watch.WATCH_FILE, 'r') as watch_file:
            self.adList = list(json.load(watch_file))
            watch_file.close()
        self._sort_lists()
Beispiel #4
0
    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """
        print('Initializing engine...')
        self.engine = Engine(self.account, self.messagehandler)
        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

        print()
        print("Ready. Type 'help' for a list of commands.")
        print("Press tab for autocompletion and up/down for command history.")
        self.do_filter(None) # Show available filters
        print()
Beispiel #5
0
    def start(self):
        """
        Initializes the engine
        
        Creates an Engine object and starts it.
        """
        print 'Initializing engine...'
        self.engine = Engine(self.account, self.messagehandler)
        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()
Beispiel #6
0
    def _start(self, account):
        self.engine = Engine(account, self._messagehandler)

        self.engine.connect_signal('episode_changed', self._changed_show)
        self.engine.connect_signal('score_changed', self._changed_show)
        self.engine.connect_signal('tags_changed', self._changed_show)
        self.engine.connect_signal('status_changed', self._changed_show_status)
        self.engine.connect_signal('playing', self._playing_show)
        self.engine.connect_signal('show_added', self._changed_list)
        self.engine.connect_signal('show_deleted', self._changed_list)
        self.engine.connect_signal('show_synced', self._changed_show)
        self.engine.connect_signal('queue_changed', self._changed_queue)
        self.engine.connect_signal('prompt_for_update',
                                   self._prompt_for_update)
        self.engine.connect_signal('prompt_for_add', self._prompt_for_add)
        self.engine.connect_signal('tracker_state', self._tracker_state)

        self.engine.start()
Beispiel #7
0
    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """

        if self.interactive:
            print('Initializing engine...')
            self.engine = Engine(self.account, self.messagehandler)
        else:
            self.engine = Engine(self.account)
            self.engine.set_config("tracker_enabled", False)
            self.engine.set_config("library_autoscan", False)
            self.engine.set_config("use_hooks", False)

        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.connect_signal('prompt_for_add', self._ask_add)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()

        if self.interactive:
            self._update_prompt()

            print()
            print("Ready. Type 'help' for a list of commands.")
            print(
                "Press tab for autocompletion and up/down for command history."
            )
            self.do_filter(None)  # Show available filters
            print()
        else:
            # We set the message handler only after initializing
            # so we still receive the important messages but avoid
            # the initial spam.
            self.engine.set_message_handler(self.messagehandler)
Beispiel #8
0
    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """

        if self.interactive:
            print('Initializing engine...')
            self.engine = Engine(self.account, self.messagehandler)
        else:
            self.engine = Engine(self.account)
            self.engine.set_config("tracker_enabled", False)
            self.engine.set_config("library_autoscan", False)
            self.engine.set_config("use_hooks", False)

        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.connect_signal('prompt_for_add', self._ask_add)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()

        if self.interactive:
            self._update_prompt()

            print()
            print("Ready. Type 'help' for a list of commands.")
            print("Press tab for autocompletion and up/down for command history.")
            self.do_filter(None) # Show available filters
            print()
        else:
            # We set the message handler only after initializing
            # so we still receive the important messages but avoid
            # the initial spam.
            self.engine.set_message_handler(self.messagehandler)
Beispiel #9
0
    def start(self, account):
        """Starts the engine"""
        # Engine configuration
        self.started = False

        self.status("Starting engine...")
        self.engine = Engine(account, self.message_handler)
        self.engine.connect_signal('episode_changed', self.changed_show)
        self.engine.connect_signal('score_changed', self.changed_show)
        self.engine.connect_signal('status_changed', self.changed_show_status)
        self.engine.connect_signal('playing', self.playing_show)
        self.engine.connect_signal('show_added', self.changed_list)
        self.engine.connect_signal('show_deleted', self.changed_list)
        self.engine.connect_signal('show_synced', self.changed_show)
        self.engine.connect_signal('queue_changed', self.changed_queue)
        self.engine.connect_signal('prompt_for_update', self.prompt_update)
        self.engine.connect_signal('tracker_state', self.tracker_state)

        # Engine start and list rebuildi
        self.status("Building lists...")
        self.engine.start()
        self._rebuild()
Beispiel #10
0
    def _start(self, account):
        self.engine = Engine(account, self._messagehandler)

        self.engine.connect_signal('episode_changed', self._changed_show)
        self.engine.connect_signal('score_changed', self._changed_show)
        self.engine.connect_signal('tags_changed', self._changed_show)
        self.engine.connect_signal('status_changed', self._changed_show_status)
        self.engine.connect_signal('playing', self._playing_show)
        self.engine.connect_signal('show_added', self._changed_list)
        self.engine.connect_signal('show_deleted', self._changed_list)
        self.engine.connect_signal('show_synced', self._changed_show)
        self.engine.connect_signal('queue_changed', self._changed_queue)
        self.engine.connect_signal('prompt_for_update', self._prompt_for_update)
        self.engine.connect_signal('prompt_for_add', self._prompt_for_add)
        self.engine.connect_signal('tracker_state', self._tracker_state)

        self.engine.start()
Beispiel #11
0
 def start(self):
     """
     Initializes the engine
     
     Creates an Engine object and starts it.
     """
     print 'Initializing engine...'
     self.engine = Engine(self.account, self.messagehandler)
     self.engine.connect_signal('show_added', self._load_list)
     self.engine.connect_signal('show_deleted', self._load_list)
     self.engine.connect_signal('status_changed', self._load_list)
     self.engine.connect_signal('episode_changed', self._load_list)
     self.engine.start()
     
     # Start with default filter selected
     self.filter_num = self.engine.mediainfo['statuses'][0]
     self._load_list()
     self._update_prompt()
Beispiel #12
0
    def start(self, account):
        """Starts the engine"""
        # Engine configuration
        self.started = False

        self.status("Starting engine...")
        self.engine = Engine(account, self.message_handler)
        self.engine.connect_signal("episode_changed", self.changed_show)
        self.engine.connect_signal("score_changed", self.changed_show)
        self.engine.connect_signal("status_changed", self.changed_show_status)
        self.engine.connect_signal("playing", self.playing_show)
        self.engine.connect_signal("show_added", self.changed_list)
        self.engine.connect_signal("show_deleted", self.changed_list)
        self.engine.connect_signal("show_synced", self.changed_show)
        self.engine.connect_signal("prompt_for_update", self.prompt_update)

        # Engine start and list rebuildi
        self.status("Building lists...")
        self.engine.start()
        self._rebuild()
Beispiel #13
0
class EngineWorker(QtCore.QThread):
    """
    Worker thread

    Contains the engine and manages every process in a separate thread.

    """
    engine = None
    function = None
    finished = QtCore.pyqtSignal(dict)

    # Message handler signals
    changed_status = QtCore.pyqtSignal(str, int, str)
    raised_error = QtCore.pyqtSignal(str)
    raised_fatal = QtCore.pyqtSignal(str)

    # Event handler signals
    changed_show = QtCore.pyqtSignal(dict)
    changed_show_status = QtCore.pyqtSignal(dict, object)
    changed_list = QtCore.pyqtSignal(dict)
    changed_queue = QtCore.pyqtSignal(int)
    tracker_state = QtCore.pyqtSignal(dict)
    playing_show = QtCore.pyqtSignal(dict, bool, int)
    prompt_for_update = QtCore.pyqtSignal(dict, int)
    prompt_for_add = QtCore.pyqtSignal(dict, int)

    def __init__(self):
        super(EngineWorker, self).__init__()

        self.overrides = {'start': self._start}

    def _messagehandler(self, classname, msgtype, msg):
        self.changed_status.emit(classname, msgtype, msg)

    def _error(self, msg):
        self.raised_error.emit(str(msg))

    def _fatal(self, msg):
        self.raised_fatal.emit(str(msg))

    def _changed_show(self, show, changes=None):
        self.changed_show.emit(show)

    def _changed_show_status(self, show, old_status=None):
        self.changed_show_status.emit(show, old_status)

    def _changed_list(self, show):
        self.changed_list.emit(show)

    def _changed_queue(self, queue):
        self.changed_queue.emit(len(queue))

    def _tracker_state(self, status):
        self.tracker_state.emit(status)

    def _playing_show(self, show, is_playing, episode):
        self.playing_show.emit(show, is_playing, episode)

    def _prompt_for_update(self, show, episode):
        self.prompt_for_update.emit(show, episode)

    def _prompt_for_add(self, show, episode):
        self.prompt_for_add.emit(show, episode)

    def _start(self, account):
        self.engine = Engine(account, self._messagehandler)

        self.engine.connect_signal('episode_changed', self._changed_show)
        self.engine.connect_signal('score_changed', self._changed_show)
        self.engine.connect_signal('tags_changed', self._changed_show)
        self.engine.connect_signal('status_changed', self._changed_show_status)
        self.engine.connect_signal('playing', self._playing_show)
        self.engine.connect_signal('show_added', self._changed_list)
        self.engine.connect_signal('show_deleted', self._changed_list)
        self.engine.connect_signal('show_synced', self._changed_show)
        self.engine.connect_signal('queue_changed', self._changed_queue)
        self.engine.connect_signal('prompt_for_update',
                                   self._prompt_for_update)
        self.engine.connect_signal('prompt_for_add', self._prompt_for_add)
        self.engine.connect_signal('tracker_state', self._tracker_state)

        self.engine.start()

    def set_function(self, function, ret_function, *args, **kwargs):
        if function in self.overrides:
            self.function = self.overrides[function]
        else:
            self.function = getattr(self.engine, function)

        try:
            self.finished.disconnect()
        except Exception:
            pass

        if ret_function:
            self.finished.connect(ret_function)

        self.args = args
        self.kwargs = kwargs

    def __del__(self):
        self.wait()

    def run(self):
        try:
            ret = self.function(*self.args, **self.kwargs)
            self.finished.emit({'success': True, 'result': ret})
        except utils.TrackmaError as e:
            self._error(e)
            self.finished.emit({'success': False})
        except utils.TrackmaFatal as e:
            self._fatal(e)
Beispiel #14
0
class Trackma_cmd(cmd.Cmd):
    """
    Main program, inherits from the useful Cmd class
    for interactive console
    """
    engine = None
    filter_num = 1
    sort = 'title'
    completekey = 'Tab'
    cmdqueue = []
    stdout = sys.stdout
    in_prompt = False
    sortedlist = []
    needed_args = {
        'altname':      (1, 2),
        'filter':       (0, 1),
        'sort':         1,
        'mediatype':    (0, 1),
        'info':         1,
        'search':       1,
        'add':          1,
        'del':          1,
        'delete':       1,
        'play':         (1, 2),
        'openfolder':   1,
        'update':       (1, 2),
        'score':        2,
        'status':       2,
    }

    def __init__(self, account_num=None, debug=False, interactive=True):
        super().__init__()

        if interactive:
            print('Trackma v'+utils.VERSION+'  Copyright (C) 2012-2017  z411')
            print('This program comes with ABSOLUTELY NO WARRANTY; for details type `about\'')
            print('This is free software, and you are welcome to redistribute it')
            print('under certain conditions; see the COPYING file for details.')
            print()

        self.interactive = interactive
        self.debug = debug

        self.accountman = Trackma_accounts()
        if account_num:
            try:
                self.account = self.accountman.get_account(account_num)
            except KeyError:
                print("Account {} doesn't exist.".format(account_num))
                self.account = self.accountman.select_account(True)
            except ValueError:
                print("Account {} must be numeric.".format(account_num))
                self.account = self.accountman.select_account(True)
        else:
            self.account = self.accountman.select_account(False)

    def forget_account(self):
        self.accountman.set_default(None)

    def _update_prompt(self):
        self.prompt = "{c_u}{u}{c_r} [{c_a}{a}{c_r}] ({c_mt}{mt}{c_r}) {c_s}{s}{c_r} >> ".format(
            u  = self.engine.get_userconfig('username'),
            a  = self.engine.api_info['shortname'],
            mt = self.engine.api_info['mediatype'],
            s  = self.engine.mediainfo['statuses_dict'][self.filter_num].lower().replace(' ', ''),
            c_r  = _PCOLOR_RESET,
            c_u  = _PCOLOR_USER,
            c_a  = _PCOLOR_API,
            c_mt = _PCOLOR_MEDIATYPE,
            c_s  = _COLOR_RESET
        )

    def _load_list(self, *args):
        showlist = self.engine.filter_list(self.filter_num)
        sortedlist = sorted(showlist, key=itemgetter(self.sort))
        self.sortedlist = list(enumerate(sortedlist, 1))

    def _get_show(self, title):
        # Attempt parsing list index
        # otherwise use title
        try:
            index = int(title)-1
            return self.sortedlist[index][1]
        except (ValueError, AttributeError, IndexError):
            return self.engine.get_show_info(title=title)

    def _ask_update(self, show, episode):
        do = input("Should I update {} to episode {}? [y/N] ".format(show['title'], episode))
        if do.lower() == 'y':
            self.engine.set_episode(show['id'], episode)

    def _ask_add(self, show, episode):
        do = input("Should I search for the show {}? [y/N] ".format(show['title']))
        if do.lower() == 'y':
            self.do_add([show['title']])

    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """

        if self.interactive:
            print('Initializing engine...')
            self.engine = Engine(self.account, self.messagehandler)
        else:
            self.engine = Engine(self.account)
            self.engine.set_config("tracker_enabled", False)
            self.engine.set_config("library_autoscan", False)
            self.engine.set_config("use_hooks", False)

        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.connect_signal('prompt_for_add', self._ask_add)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()

        if self.interactive:
            self._update_prompt()

            print()
            print("Ready. Type 'help' for a list of commands.")
            print("Press tab for autocompletion and up/down for command history.")
            self.do_filter(None) # Show available filters
            print()
        else:
            # We set the message handler only after initializing
            # so we still receive the important messages but avoid
            # the initial spam.
            self.engine.set_message_handler(self.messagehandler)

    def do_about(self, args):
        print("Trackma {}  by z411 ([email protected])".format(utils.VERSION))
        print("Trackma is an open source client for media tracking websites.")
        print("https://github.com/z411/trackma")
        print()
        print("This program is licensed under the GPLv3 and it comes with ASOLUTELY NO WARRANTY.")
        print("Many contributors have helped to run this project; for more information see the AUTHORS file.")
        print("For more information about the license, see the COPYING file.")
        print()
        print("If you encounter any problems please report them in https://github.com/z411/trackma/issues")
        print()
        print("This is the CLI version of Trackma. To see available commands type `help'.")
        print("For other available interfaces please see the README file.")
        print()

    def do_help(self, arg):
        if arg:
            try:
                doc = getattr(self, 'do_' + arg).__doc__
                if doc:
                    (name, args, expl, usage, examples) = self._parse_doc(arg, doc)

                    print()
                    print(name)
                    for line in expl:
                        print("  {}".format(line))
                    if args:
                        print("\n  Arguments:")
                        for arg in args:
                            if arg[2]:
                                print("    {}: {}".format(arg[0], arg[1]))
                            else:
                                print("    {} (optional): {}".format(arg[0], arg[1]))
                    if usage:
                        print("\n  Usage: " + usage)
                    for example in examples:
                        print("  Example: " + example)
                    print()
                    return
            except AttributeError:
                pass

            print("No help available.")
            return
        else:
            CMD_LENGTH = 11
            ARG_LENGTH = 13

            (height, width) = utils.get_terminal_size()
            prev_width = CMD_LENGTH + ARG_LENGTH + 3

            tw = textwrap.TextWrapper()
            tw.width = width - 2
            tw.subsequent_indent = ' ' * prev_width

            print()
            print(" {0:>{1}} {2:{3}} {4}".format(
                    'command', CMD_LENGTH,
                    'args', ARG_LENGTH,
                    'description'))
            print(" " + "-"*(min(prev_width+81, width-3)))

            names = self.get_names()
            names.sort()
            for name in names:
                if name[:3] == 'do_':
                    doc = getattr(self, name).__doc__
                    if not doc:
                        continue

                    cmd = name[3:]
                    (name, args, expl, usage, examples) = self._parse_doc(cmd, doc)

                    line = " {0:>{1}} {2:{3}} {4}".format(
                        name, CMD_LENGTH,
                        '<' + ','.join( a[0] for a in args) + '>', ARG_LENGTH,
                        expl[0])
                    print(tw.fill(line))

            print()
            print("Use `help <command>` for detailed information.")
            print()


    def do_account(self, args):
        """
        Switch to a different account.
        """

        self.account = self.accountman.select_account(True)
        self.engine.reload(account=self.account)

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_filter(self, args):
        """
        Changes the filtering of list by status (shows current if empty).

        :optparam status Name of status to filter
        :usage filter [filter type]
        """
        # Query the engine for the available statuses
        # that the user can choose
        if args:
            try:
                self.filter_num = self._guess_status(args[0].lower())
                self._load_list()
                self._update_prompt()
            except KeyError:
                print("Invalid filter.")
        else:
            print("Available statuses: %s" % ', '.join( v.lower().replace(' ', '') for v in self.engine.mediainfo['statuses_dict'].values() ))

    def do_sort(self, args):
        """
        Change of the lists

        :param type Sort type; available types: id, title, my_progress, total, my_score
        :usage sort <sort type>
        """
        sorts = ('id', 'title', 'my_progress', 'total', 'my_score')
        if args[0] in sorts:
            self.sort = args[0]
            self._load_list()
        else:
            print("Invalid sort.")

    def do_mediatype(self, args):
        """
        Reloads engine with different mediatype (shows current if empty).
        Call with no arguments to see supported mediatypes.

        :optparam mediatype Mediatype name
        :usage mediatype [mediatype]
        """
        if args:
            if args[0] in self.engine.api_info['supported_mediatypes']:
                self.engine.reload(mediatype=args[0])

                # Start with default filter selected
                self.filter_num = self.engine.mediainfo['statuses'][0]
                self._load_list()
                self._update_prompt()
            else:
                print("Invalid mediatype.")
        else:
            print("Supported mediatypes: %s" % ', '.join(self.engine.api_info['supported_mediatypes']))

    def do_ls(self,args):
        self.do_list(args)

    def do_list(self, args):
        """
        Lists all shows available in the local list.

        :name list|ls
        """
        # Show the list in memory
        self._make_list(self.sortedlist)

    def do_info(self, args):
        """
        Gets detailed information about a local show.

        :param show Show index or title.
        :usage info <show index or title>
        """
        try:
            show = self._get_show(args[0])
            details = self.engine.get_show_details(show)
        except utils.TrackmaError as e:
            self.display_error(e)
            return

        print(show['title'])
        print("-" * len(show['title']))
        print(show['url'])
        print()

        for line in details['extra']:
            print("%s: %s" % line)

    def do_search(self, args):
        """
        Does a regex search on shows in the local lists.

        :param pattern Regex pattern to search for.
        :usage search <pattern>
        """
        sortedlist = list(v for v in self.sortedlist if re.search(args[0], v[1]['title'], re.I))
        self._make_list(sortedlist)

    def do_add(self, args):
        """
        Search for a show in the remote service and add it.

        :param pattern Show criteria to search.
        :usage add <pattern>
        """
        try:
            entries = self.engine.search(args[0])
        except utils.TrackmaError as e:
            self.display_error(e)
            return

        for i, entry in enumerate(entries, start=1):
            print("%d: (%s) %s" % (i, entry['type'], entry['title']))
        do_update = input("Choose show to add (blank to cancel): ")
        if do_update != '':
            try:
                show = entries[int(do_update)-1]
            except ValueError:
                print("Choice must be numeric.")
                return
            except IndexError:
                print("Invalid show.")
                return

            # Tell the engine to add the show
            try:
                self.engine.add_show(show, self.filter_num)
            except utils.TrackmaError as e:
                self.display_error(e)

    def do_del(self, args):
        self.do_delete(args)

    def do_delete(self, args):
        """
        Deletes a show from the local list.

        :name delete|del
        :param show Show index or title.
        :usage delete <show index or title>
        """
        try:
            show = self._get_show(args[0])

            do_delete = input("Delete %s? [y/N] " % show['title'])
            if do_delete.lower() == 'y':
                self.engine.delete_show(show)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_rescan(self, args):
        """
        Re-scans the local library.
        """
        self.engine.scan_library(rescan=True)

    def do_random(self, args):
        """
        Starts the media player with a random new episode.
        """
        try:
            self.engine.play_random()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_tracker(self, args):
        """
        Shows information about the tracker, if it's running.

        :usage trackmer
        """
        try:
            info = self.engine.tracker_status()
            print("- Tracker status -")

            if info:
                if info['state'] == utils.TRACKER_NOVIDEO:
                    state = 'No video'
                elif info['state'] == utils.TRACKER_PLAYING:
                    state = 'Playing'
                elif info['state'] == utils.TRACKER_UNRECOGNIZED:
                    state = 'Unrecognized'
                elif info['state'] == utils.TRACKER_NOT_FOUND:
                    state = 'Not found'
                elif info['state'] == utils.TRACKER_IGNORED:
                    state = 'Ignored'
                else:
                    state = 'N/A'

                print("State: {}".format(state))
                print("Filename: {}".format(info['filename'] or 'N/A'))
                print("Timer: {}{}".format(info['timer'] or 'N/A', ' [P]' if info['paused'] else ''))
                if info['show']:
                    (show, ep) = info['show']
                    print("Show: {}\nEpisode: {}".format(show['title'], ep))
                else:
                    print("Show: N/A")
            else:
                print("Not started")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_play(self, args):
        """
        Starts the media player with the specified episode number (next if unspecified).

        :param show Episode index or title.
        :optparam ep Episode number. Assume next if not specified.
        :usage play <show index or title> [episode number]
        """
        try:
            episode = 0
            show = self._get_show(args[0])

            # If the user specified an episode, play it
            # otherwise play the next episode not watched yet
            if len(args) > 1:
                episode = args[1]

            self.engine.play_episode(show, episode)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_openfolder(self, args):
        """
        Opens the folder containing the show

        :param show Show index or name.
        :usage openfolder <show index or name>
        """

        try:
            show = self._get_show(args[0])
            filename = self.engine.get_episode_path(show, 1)
            with open(os.devnull, 'wb') as DEVNULL:
                subprocess.Popen(["/usr/bin/xdg-open",
                os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL)
        except OSError:
            # xdg-open failed.
            self.display_error("Could not open folder.")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_update(self, args):
        """
        Updates the progress of a show to the specified episode (next if unspecified).

        :param show Show index, title or filename (prepend with file:).
        :optparam ep Episode number (numeric).
        :usage update <show index or name> [episode number]
        :example update Toradora! 5
        :example update 6
        :example update file:filename.mkv
        """
        try:
            if args[0][:5] == "file:":
                (show, ep) = self.engine.get_show_info(filename=args[0][5:])
            else:
                (show, ep) = (self._get_show(args[0]), None)

            if len(args) > 1:
                self.engine.set_episode(show['id'], args[1])
            else:
                self.engine.set_episode(show['id'], ep or show['my_progress']+1)
        except IndexError:
            print("Missing arguments.")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_score(self, args):
        """
        Changes the score of a show.

        :param show Show index or name.
        :param score Score to set (numeric/decimal).
        :usage score <show index or name> <score>
        """
        try:
            show = self._get_show(args[0])
            self.engine.set_score(show['id'], args[1])
        except IndexError:
            print("Missing arguments.")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_status(self, args):
        """
        Changes the status of a show.
        Use the command `filter` without arguments to see the available statuses.

        :param show Show index or name.
        :param status Status name. Use `filter` without args to list them.
        :usage status <show index or name> <status name>
        """
        try:
            _showtitle = args[0]
            _filter = args[1]
        except IndexError:
            print("Missing arguments.")
            return

        try:
            _filter_num = self._guess_status(_filter)
        except KeyError:
            print("Invalid filter.")
            return

        try:
            show = self._get_show(_showtitle)
            self.engine.set_status(show['id'], _filter_num)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_altname(self, args):
        """
        Changes the alternative name of a show (removes if unspecified).
        Use the command 'altname' without arguments to clear the alternative
        name.

        :param show Show index or name
        :param alt  The alternative name. Use `altname` without alt to clear it
        :usage altname <show index or name> <alternative name>
        """
        try:
            show = self._get_show(args[0])
            altname = args[1] if len(args) > 1 else ''
            self.engine.altname(show['id'],altname)
        except IndexError:
            print("Missing arguments")
            return
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_send(self, args):
        """
        Sends queued changes to the remote service.
        """
        try:
            self.engine.list_upload()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_retrieve(self, args):
        """
        Retrieves the remote list overwrites the local one.
        """
        try:
            if self.engine.get_queue():
                answer = input("There are unqueued changes. Overwrite local list? [y/N] ")
                if answer.lower() == 'y':
                    self.engine.list_download()
            else:
                self.engine.list_download()
            self._load_list()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_undoall(self, args):
        """
        Undo all changes in queue.
        """
        try:
            self.engine.undoall()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_viewqueue(self, args):
        """
        List the queued changes.
        """
        queue = self.engine.get_queue()
        if queue:
            print("Queue:")
            for show in queue:
                print("- %s" % show['title'])
        else:
            print("Queue is empty.")

    def do_exit(self, args):
        self.do_quit(args)

    def do_quit(self, args):
        """
        Quits the program.

        :name quit|exit
        """
        try:
            self.engine.unload()
        except utils.TrackmaError as e:
            self.display_error(e)

        print('Bye!')
        sys.exit(0)

    def do_EOF(self, args):
        print()
        self.do_quit(args)

    def complete_update(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_play(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_score(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_status(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_delete(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_filter(self, text, line, begidx, endidx):
        return [v.lower().replace(' ', '') for v in self.engine.mediainfo['statuses_dict'].values()]

    def parse_args(self, arg):
        if arg:
            return shlex.split(arg)

        return []

    def emptyline(self):
        return

    def preloop(self):
        """ Override. """
        self.in_prompt = True

    def precmd(self, line):
        """ Override. """
        self.in_prompt = False
        return line

    def postcmd(self, stop, line):
        """ Override. """
        self.in_prompt = True
        return stop

    def onecmd(self, line):
        """ Override. """
        cmd, arg, line = self.parseline(line)
        if not line:
            return self.emptyline()
        if cmd is None:
            return self.default(line)
        self.lastcmd = line
        if line == 'EOF' :
            self.lastcmd = ''
        if cmd == '':
            return self.default(line)
        elif cmd == 'help':
            return self.do_help(arg)
        else:
            try:
                args = self.parse_args(arg)
            except ValueError:
                return self.default(line)
            else:
                return self.execute(cmd, args, line)

    def execute(self, cmd, args, line):
        try:
            func = getattr(self, 'do_' + cmd)
        except AttributeError:
            return self.default(line)

        try:
            needed = self.needed_args[cmd]
        except KeyError:
            needed = 0

        if isinstance(needed, int):
            needed = (needed, needed)

        if needed[0] <= len(args) <= needed[1]:
            return func(args)
        else:
            print("Incorrent number of arguments. See `help %s`" % cmd)

    def display_error(self, e):
        print("%s%s: %s%s" % (_COLOR_ERROR, type(e).__name__, e, _COLOR_RESET))

    def messagehandler(self, classname, msgtype, msg):
        """
        Handles and shows messages coming from
        the engine messenger to provide feedback.
        """
        color_escape = ''
        color_reset = _COLOR_RESET

        if classname == 'Engine':
            color_escape = _COLOR_ENGINE
        elif classname == 'Data':
            color_escape = _COLOR_DATA
        elif classname.startswith('lib'):
            color_escape = _COLOR_API
        elif classname.startswith('Tracker'):
            color_escape = _COLOR_TRACKER
        else:
            color_reset = ''

        if msgtype == messenger.TYPE_INFO:
            out = "%s%s: %s%s" % (color_escape, classname, msg, color_reset)
        elif msgtype == messenger.TYPE_WARN:
            out = "%s%s warning: %s%s" % (color_escape, classname, msg, color_reset)
        elif self.debug and msgtype == messenger.TYPE_DEBUG:
            out = "[D] %s%s: %s%s" % (color_escape, classname, msg, color_reset)
        else:
            return # Unrecognized message, don't show anything

        if has_readline and self.in_prompt:
            # If we're in a prompt and receive a message
            # (often from the tracker) we need to clear the line
            # first, show the message, then re-show the prompt.
            buf = readline.get_line_buffer()
            self.stdout.write('\r' + ' '*(len(self.prompt)+len(buf)) + '\r')

            print(out)

            self.stdout.write(self.prompt + buf)
            self.stdout.flush()
        else:
            print(out)

    def _guess_status(self, string):
        for k, v in self.engine.mediainfo['statuses_dict'].items():
            if string.lower() == v.lower().replace(' ', ''):
                return k
        raise KeyError

    def _parse_doc(self, cmd, doc):
        lines = doc.split('\n')
        name = cmd
        args = []
        expl = []
        usage = None
        examples = []

        for line in lines:
            line = line.strip()
            if line[:6] == ":param":
                args.append( line[7:].split(' ', 1) + [True] )
            elif line[:9] == ":optparam":
                args.append( line[10:].split(' ', 1) + [False] )
            elif line[:6] == ':usage':
                usage = line[7:]
            elif line[:5] == ':name':
                name = line[6:]
            elif line[:8] == ':example':
                examples.append(line[9:])
            elif line:
                expl.append(line)

        return (name, args, expl, usage, examples)

    def _make_list(self, showlist):
        """
        Helper function for printing a formatted show list
        """
        # Fixed column widths
        col_id_length = 7
        col_index_length = 6
        col_title_length = 5
        col_episodes_length = 9
        col_score_length = 6
        altnames = self.engine.altnames()

        # Calculate maximum width for the title column
        # based on the width of the terminal
        (height, width) = utils.get_terminal_size()
        max_title_length = width - col_id_length - col_episodes_length - col_score_length - col_index_length - 5

        # Find the widest title so we can adjust the title column
        for index, show in showlist:
            if len(show['title']) > col_title_length:
                if len(show['title']) > max_title_length:
                    # Stop if we exceeded the maximum column width
                    col_title_length = max_title_length
                    break
                else:
                    col_title_length = len(show['title'])

        # Print header
        print("| {0:{1}} {2:{3}} {4:{5}} {6:{7}} |".format(
                'Index',    col_index_length,
                'Title',    max_title_length,
                'Progress', col_episodes_length,
                'Score',    col_score_length))

        # List shows
        for index, show in showlist:
            if self.engine.mediainfo['has_progress']:
                episodes_str = "{0:3} / {1}".format(show['my_progress'], show['total'] or '?')
            else:
                episodes_str = "-"

            #Get title (and alt. title) and if need be, truncate it
            title_str = show['title']
            if altnames.get(show['id']):
                title_str += " [{}]".format(altnames.get(show['id']))
            title_str = title_str[:max_title_length] if len(title_str) > max_title_length else title_str

            # Color title according to status
            if show['status'] == utils.STATUS_AIRING:
                colored_title = _COLOR_AIRING + title_str + _COLOR_RESET
            else:
                colored_title = title_str

            print("| {0:^{1}} {2}{3} {4:{5}} {6:^{7}} |".format(
                index, col_index_length,
                colored_title,
                '.' * (max_title_length-len(title_str)),
                episodes_str, col_episodes_length,
                show['my_score'], col_score_length))

        # Print result count
        print('%d results' % len(showlist))
        print()
Beispiel #15
0
class TrackmaWindow(Gtk.ApplicationWindow):
    __gtype_name__ = 'TrackmaWindow'

    btn_appmenu = Gtk.Template.Child()
    btn_mediatype = Gtk.Template.Child()
    header_bar = Gtk.Template.Child()

    def __init__(self, app, debug=False):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.init_template()

        self._debug = debug
        self._configfile = utils.to_config_path('ui-Gtk.json')
        self._config = utils.parse_config(self._configfile, utils.gtk_defaults)

        self.statusicon = None
        self._main_view = None
        self._modals = []

        self._account = None
        self._engine = None
        self.close_thread = None
        self.hidden = False

        self._init_widgets()

    def init_account_selection(self):
        manager = AccountManager()

        # Use the remembered account if there's one
        if manager.get_default():
            self._create_engine(manager.get_default())
        else:
            self._show_accounts(switch=False)

    def _init_widgets(self):
        Gtk.Window.set_default_icon_from_file(utils.DATADIR + '/icon.png')
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_title('Trackma')

        if self._config['remember_geometry']:
            self.resize(self._config['last_width'],
                        self._config['last_height'])

        if not self._main_view:
            self._main_view = MainView(self._config)
            self._main_view.connect('error', self._on_main_view_error)
            self._main_view.connect(
                'success', lambda x: self._set_buttons_sensitive(True))
            self._main_view.connect('error-fatal',
                                    self._on_main_view_error_fatal)
            self._main_view.connect('show-action', self._on_show_action)
            self.add(self._main_view)

        self.connect('delete_event', self._on_delete_event)

        builder = Gtk.Builder.new_from_file(
            os.path.join(gtk_dir, 'data/shortcuts.ui'))
        help_overlay = builder.get_object('shortcuts-window')
        self.set_help_overlay(help_overlay)

        # Status icon
        if TrackmaStatusIcon.is_tray_available():
            self.statusicon = TrackmaStatusIcon()
            self.statusicon.connect('hide-clicked', self._on_tray_hide_clicked)
            self.statusicon.connect('about-clicked',
                                    self._on_tray_about_clicked)
            self.statusicon.connect('quit-clicked', self._on_tray_quit_clicked)

            if self._config['show_tray']:
                self.statusicon.set_visible(True)
            else:
                self.statusicon.set_visible(False)

        # Don't show the main window if start in tray option is set
        if self.statusicon and self._config['show_tray'] and self._config[
                'start_in_tray']:
            self.hidden = True
        else:
            self.present()

    def _on_tray_hide_clicked(self, status_icon):
        self._destroy_modals()

        if self.hidden:
            self.deiconify()
            self.present()

            if not self._engine:
                self._show_accounts(switch=False)
        else:
            self.hide()

        self.hidden = not self.hidden

    def _destroy_modals(self):
        self.get_help_overlay().hide()

        for modal_window in self._modals:
            modal_window.destroy()

        self._modals = []

    def _on_tray_about_clicked(self, status_icon):
        self._on_about(None, None)

    def _on_tray_quit_clicked(self, status_icon):
        self._quit()

    def _on_delete_event(self, widget, event, data=None):
        if self.statusicon and self.statusicon.get_visible(
        ) and self._config['close_to_tray']:
            self.hidden = True
            self.hide()
        else:
            self._quit()
        return True

    def _create_engine(self, account):
        self._engine = Engine(account, self._message_handler)

        self._main_view.load_engine_account(self._engine, account)
        self._set_actions()
        self._set_mediatypes_menu()
        self._update_widgets(account)
        self._set_buttons_sensitive(True)

    def _set_actions(self):
        builder = Gtk.Builder.new_from_file(
            os.path.join(gtk_dir, 'data/app-menu.ui'))
        settings = Gtk.Settings.get_default()
        if not settings.get_property("gtk-shell-shows-menubar"):
            self.btn_appmenu.set_menu_model(builder.get_object('app-menu'))
        else:
            self.get_application().set_menubar(builder.get_object('menu-bar'))
            self.btn_appmenu.set_property('visible', False)

        def add_action(name, callback):
            action = Gio.SimpleAction.new(name, None)
            action.connect('activate', callback)
            self.add_action(action)

        add_action('search', self._on_search)
        add_action('syncronize', self._on_synchronize)
        add_action('upload', self._on_upload)
        add_action('download', self._on_download)
        add_action('scanfiles', self._on_scanfiles)
        add_action('accounts', self._on_accounts)
        add_action('preferences', self._on_preferences)
        add_action('about', self._on_about)

        add_action('play_next', self._on_action_play_next)
        add_action('play_random', self._on_action_play_random)
        add_action('episode_add', self._on_action_episode_add)
        add_action('episode_remove', self._on_action_episode_remove)
        add_action('delete', self._on_action_delete)
        add_action('copy', self._on_action_copy)

    def _set_mediatypes_action(self):
        action_name = 'change-mediatype'
        if self.has_action(action_name):
            self.remove_action(action_name)

        state = GLib.Variant.new_string(self._engine.api_info['mediatype'])
        action = Gio.SimpleAction.new_stateful(action_name, state.get_type(),
                                               state)
        action.connect('change-state', self._on_change_mediatype)
        self.add_action(action)

    def _set_mediatypes_menu(self):
        self._set_mediatypes_action()
        menu = Gio.Menu()

        for mediatype in self._engine.api_info['supported_mediatypes']:
            variant = GLib.Variant.new_string(mediatype)
            menu_item = Gio.MenuItem()
            menu_item.set_label(mediatype)
            menu_item.set_action_and_target_value('win.change-mediatype',
                                                  variant)
            menu.append_item(menu_item)

        self.btn_mediatype.set_menu_model(menu)

        if len(self._engine.api_info['supported_mediatypes']) <= 1:
            self.btn_mediatype.hide()

    def _update_widgets(self, account):
        current_api = utils.available_libs[account['api']]
        api_iconpath = 1
        api_iconfile = current_api[api_iconpath]

        self.header_bar.set_subtitle(self._engine.api_info['name'] + " (" +
                                     self._engine.api_info['mediatype'] + ")")

        if self.statusicon and self._config['tray_api_icon']:
            self.statusicon.set_from_file(api_iconfile)

    def _on_change_mediatype(self, action, value):
        action.set_state(value)
        mediatype = value.get_string()
        self._set_buttons_sensitive(False)
        self._main_view.load_account_mediatype(None, mediatype,
                                               self.header_bar)

    def _on_search(self, action, param):
        current_status = self._main_view.get_current_status()
        win = SearchWindow(self._engine,
                           self._config['colors'],
                           current_status,
                           transient_for=self)
        win.connect('search-error', self._on_search_error)
        win.connect('destroy', self._on_modal_destroy)
        win.present()
        self._modals.append(win)

    def _on_search_error(self, search_window, error_msg):
        print(error_msg)

    def _on_synchronize(self, action, param):
        threading.Thread(target=self._synchronization_task,
                         args=(True, True)).start()

    def _on_upload(self, action, param):
        threading.Thread(target=self._synchronization_task,
                         args=(True, False)).start()

    def _on_download(self, action, param):
        def _download_lists():
            threading.Thread(target=self._synchronization_task,
                             args=(False, True)).start()

        def _on_download_response(_dialog, response):
            _dialog.destroy()

            if response == Gtk.ResponseType.YES:
                _download_lists()

        queue = self._engine.get_queue()
        if queue:
            dialog = Gtk.MessageDialog(
                self, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION,
                Gtk.ButtonsType.YES_NO,
                "There are %d queued changes in your list. If you retrieve the remote list now you will lose your queued changes. Are you sure you want to continue?"
                % len(queue))
            dialog.show_all()
            dialog.connect("response", _on_download_response)
        else:
            # If the user doesn't have any queued changes
            # just go ahead
            _download_lists()

    def _synchronization_task(self, send, retrieve):
        self._set_buttons_sensitive_idle(False)

        try:
            if send:
                self._engine.list_upload()
            if retrieve:
                self._engine.list_download()

            # GLib.idle_add(self._set_score_ranges)
            GLib.idle_add(self._main_view.populate_all_pages)
        except utils.TrackmaError as e:
            self._error_dialog_idle(e)
        except utils.TrackmaFatal as e:
            self._show_accounts_idle(switch=False, forget=True)
            self._error_dialog_idle("Fatal engine error: %s" % e)
            return

        self._main_view.set_status_idle("Ready.")
        self._set_buttons_sensitive_idle(True)

    def _on_scanfiles(self, action, param):
        threading.Thread(target=self._scanfiles_task).start()

    def _scanfiles_task(self):
        self._set_buttons_sensitive_idle(False)
        try:
            self._engine.scan_library(rescan=True)
        except utils.TrackmaError as e:
            self._error_dialog_idle(e)

        GLib.idle_add(self._main_view.populate_all_pages)

        self._main_view.set_status_idle("Ready.")
        self._set_buttons_sensitive_idle(True)

    def _on_accounts(self, action, param):
        self._show_accounts()

    def _show_accounts_idle(self, switch=True, forget=False):
        GLib.idle_add(self._show_accounts, switch, forget)

    def _show_accounts(self, switch=True, forget=False):
        manager = AccountManager()

        if forget:
            manager.set_default(None)

        accountsel = AccountsWindow(manager, transient_for=self)
        accountsel.connect('account-open', self._on_account_open)
        accountsel.connect('account-cancel', self._on_account_cancel, switch)
        accountsel.connect('destroy', self._on_modal_destroy)
        accountsel.present()
        self._modals.append(accountsel)

    def _on_account_open(self, accounts_window, account_num, remember):
        manager = AccountManager()
        account = manager.get_account(account_num)

        if remember:
            manager.set_default(account_num)
        else:
            manager.set_default(None)

        # Reload the engine if already started,
        # start it otherwise
        self._set_buttons_sensitive(False)
        if self._engine and self._engine.loaded:
            self._main_view.load_account_mediatype(account, None, None)
        else:
            self._create_engine(account)

    def _on_account_cancel(self, _accounts_window, switch):
        manager = AccountManager()

        if not switch or not manager.get_accounts():
            self._quit()

    def _on_preferences(self, _action, _param):
        win = SettingsWindow(self._engine,
                             self._config,
                             self._configfile,
                             transient_for=self)
        win.connect('destroy', self._on_modal_destroy)
        win.present()
        self._modals.append(win)

    def _on_about(self, _action, _param):
        about = Gtk.AboutDialog(parent=self)
        about.set_modal(True)
        about.set_transient_for(self)
        about.set_program_name("Trackma GTK")
        about.set_version(utils.VERSION)
        about.set_license_type(Gtk.License.GPL_3_0_ONLY)
        about.set_comments(
            "Trackma is an open source client for media tracking websites.\nThanks to all contributors."
        )
        about.set_website("http://github.com/z411/trackma")
        about.set_copyright("© z411, et al.")
        about.set_authors(["See AUTHORS file"])
        about.set_artists(["shuuichi"])
        about.connect('destroy', self._on_modal_destroy)
        about.connect('response', lambda dialog, response: dialog.destroy())
        about.present()
        self._modals.append(about)

    def _on_modal_destroy(self, modal_window):
        self._modals.remove(modal_window)

    def _quit(self):
        if self._config['remember_geometry']:
            self._store_geometry()

        if not self._engine:
            self.get_application().quit()
            return

        if self.close_thread is None:
            self._set_buttons_sensitive_idle(False)
            self.close_thread = threading.Thread(target=self._unload_task)
            self.close_thread.start()

    def _unload_task(self):
        self._engine.unload()
        GLib.idle_add(self.get_application().quit)

    def _store_geometry(self):
        (width, height) = self.get_size()
        self._config['last_width'] = width
        self._config['last_height'] = height
        utils.save_config(self._config, self._configfile)

    def _message_handler(self, classname, msgtype, msg):
        # Thread safe
        # print("%s: %s" % (classname, msg))
        if msgtype == messenger.TYPE_WARN:
            self._main_view.set_status_idle("%s warning: %s" %
                                            (classname, msg))
        elif msgtype != messenger.TYPE_DEBUG:
            self._main_view.set_status_idle("%s: %s" % (classname, msg))
        elif self._debug:
            print('[D] {}: {}'.format(classname, msg))

    def _on_main_view_error(self, main_view, error_msg):
        self._error_dialog_idle(error_msg)

    def _on_main_view_error_fatal(self, main_view, error_msg):
        self._show_accounts_idle(switch=False, forget=True)
        self._error_dialog_idle(error_msg)

    def _error_dialog_idle(self, msg, icon=Gtk.MessageType.ERROR):
        # Thread safe
        GLib.idle_add(self._error_dialog, msg, icon)

    def _error_dialog(self, msg, icon=Gtk.MessageType.ERROR):
        def error_dialog_response(widget, response_id):
            widget.destroy()

        dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL, icon,
                                   Gtk.ButtonsType.OK, str(msg))
        dialog.show_all()
        dialog.connect("response", error_dialog_response)
        print('Error: {}'.format(msg))

    def _on_action_play_next(self, action, param):
        selected_show = self._main_view.get_selected_show()

        if selected_show:
            self._play_next(selected_show)

    def _on_action_play_random(self, action, param):
        self._play_random()

    def _on_action_episode_add(self, action, param):
        selected_show = self._main_view.get_selected_show()

        if selected_show:
            self._episode_add(selected_show)

    def _on_action_episode_remove(self, action, param):
        selected_show = self._main_view.get_selected_show()

        if selected_show:
            self._episode_remove(selected_show)

    def _on_action_delete(self, action, param):
        selected_show = self._main_view.get_selected_show()

        if selected_show:
            self._remove_show(selected_show)

    def _on_action_copy(self, action, param):
        selected_show = self._main_view.get_selected_show()

        if selected_show:
            self._copy_title(selected_show)

    def _on_show_action(self, main_view, event_type, data):
        if event_type == ShowEventType.PLAY_NEXT:
            self._play_next(*data)
        elif event_type == ShowEventType.PLAY_EPISODE:
            self._play_episode(*data)
        elif event_type == ShowEventType.EPISODE_REMOVE:
            self._episode_remove(*data)
        elif event_type == ShowEventType.EPISODE_SET:
            self._episode_set(*data)
        elif event_type == ShowEventType.EPISODE_ADD:
            self._episode_add(*data)
        elif event_type == ShowEventType.SET_SCORE:
            self._set_score(*data)
        elif event_type == ShowEventType.SET_STATUS:
            self._set_status(*data)
        elif event_type == ShowEventType.DETAILS:
            self._open_details(*data)
        elif event_type == ShowEventType.OPEN_WEBSITE:
            self._open_website(*data)
        elif event_type == ShowEventType.OPEN_FOLDER:
            self._open_folder(*data)
        elif event_type == ShowEventType.COPY_TITLE:
            self._copy_title(*data)
        elif event_type == ShowEventType.CHANGE_ALTERNATIVE_TITLE:
            self._change_alternative_title(*data)
        elif event_type == ShowEventType.REMOVE:
            self._remove_show(*data)

    def _play_next(self, show_id):
        show = self._engine.get_show_info(show_id)
        try:
            args = self._engine.play_episode(show)
            utils.spawn_process(args)
        except utils.TrackmaError as e:
            self._error_dialog(e)

    def _play_episode(self, show_id, episode):
        show = self._engine.get_show_info(show_id)
        try:
            if not episode:
                episode = self.show_ep_num.get_value_as_int()
            args = self._engine.play_episode(show, episode)
            utils.spawn_process(args)
        except utils.TrackmaError as e:
            self._error_dialog(e)

    def _play_random(self):
        try:
            args = self._engine.play_random()
            utils.spawn_process(args)
        except utils.TrackmaError as e:
            self._error_dialog(e)

    def _episode_add(self, show_id):
        show = self._engine.get_show_info(show_id)
        self._episode_set(show_id, show['my_progress'] + 1)

    def _episode_remove(self, show_id):
        show = self._engine.get_show_info(show_id)
        self._episode_set(show_id, show['my_progress'] - 1)

    def _episode_set(self, show_id, episode):
        try:
            self._engine.set_episode(show_id, episode)
        except utils.TrackmaError as e:
            self._error_dialog(e)

    def _set_score(self, show_id, score):
        try:
            self._engine.set_score(show_id, score)
        except utils.TrackmaError as e:
            self._error_dialog(e)

    def _set_status(self, show_id, status):
        try:
            self._engine.set_status(show_id, status)
        except utils.TrackmaError as e:
            self._error_dialog(e)

    def _open_details(self, show_id):
        show = self._engine.get_show_info(show_id)
        win = ShowInfoWindow(self._engine, show, transient_for=self)
        win.connect('destroy', self._on_modal_destroy)
        win.present()
        self._modals.append(win)

    def _open_website(self, show_id):
        show = self._engine.get_show_info(show_id)
        if show['url']:
            Gtk.show_uri(None, show['url'], Gdk.CURRENT_TIME)

    def _open_folder(self, show_id):
        show = self._engine.get_show_info(show_id)
        try:
            filename = self._engine.get_episode_path(show, 1)
            with open(os.devnull, 'wb') as DEVNULL:
                if sys.platform == 'darwin':
                    subprocess.Popen(
                        ["open", os.path.dirname(filename)],
                        stdout=DEVNULL,
                        stderr=DEVNULL)
                elif sys.platform == 'win32':
                    subprocess.Popen(
                        ["explorer", os.path.dirname(filename)],
                        stdout=DEVNULL,
                        stderr=DEVNULL)
                else:
                    subprocess.Popen(
                        ["/usr/bin/xdg-open",
                         os.path.dirname(filename)],
                        stdout=DEVNULL,
                        stderr=DEVNULL)
        except OSError:
            # xdg-open failed.
            raise utils.EngineError("Could not open folder.")

        except utils.EngineError:
            # Show not in library.
            self._error_dialog_idle("No folder found.")

    def _copy_title(self, show_id):
        show = self._engine.get_show_info(show_id)
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(show['title'], -1)

        self._main_view.set_status_idle('Title copied to clipboard.')

    def _change_alternative_title(self, show_id):
        show = self._engine.get_show_info(show_id)
        current_altname = self._engine.altname(show_id)

        def altname_response(entry, dialog, response):
            dialog.response(response)

        dialog = Gtk.MessageDialog(
            self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
            Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, None)
        dialog.set_markup('Set the <b>alternate title</b> for the show.')
        entry = Gtk.Entry()
        entry.set_text(current_altname)
        entry.connect("activate", altname_response, dialog,
                      Gtk.ResponseType.OK)
        hbox = Gtk.HBox()
        hbox.pack_start(Gtk.Label("Alternate Title:"), False, 5, 5)
        hbox.pack_end(entry, True, True, 0)
        dialog.format_secondary_markup(
            "Use this if the tracker is unable to find this show. Leave blank to disable."
        )
        dialog.vbox.pack_end(hbox, True, True, 0)
        dialog.show_all()
        retval = dialog.run()

        if retval == Gtk.ResponseType.OK:
            text = entry.get_text()
            self._engine.altname(show_id, text)
            self._main_view.change_show_title_idle(show, text)

        dialog.destroy()

    def _remove_show(self, show_id):
        try:
            show = self._engine.get_show_info(show_id)
            self._engine.delete_show(show)
        except utils.TrackmaError as e:
            self._error_dialog_idle(e)

    def _set_buttons_sensitive_idle(self, sensitive):
        GLib.idle_add(self._set_buttons_sensitive, sensitive)
        self._main_view.set_buttons_sensitive_idle(sensitive)

    def _set_buttons_sensitive(self, sensitive):
        actions_names = [
            'search', 'syncronize', 'upload', 'download', 'scanfiles',
            'accounts', 'play_next', 'play_random', 'episode_add',
            'episode_remove', 'delete', 'copy'
        ]

        for action_name in actions_names:
            action = self.lookup_action(action_name)

            if action is not None:
                action.set_enabled(sensitive)
Beispiel #16
0
class Trackma_cmd(cmd.Cmd):
    """
    Main program, inherits from the useful Cmd class
    for interactive console
    """
    engine = None
    filter_num = 1
    sort = 'title'
    completekey = 'Tab'
    cmdqueue = []
    stdout = sys.stdout
    sortedlist = []
    needed_args = {
        'filter':       (0, 1),
        'sort':         1,
        'mediatype':    (0, 1),
        'info':         1,
        'search':       1,
        'add':          1,
        'delete':       1,
        'play':         (1, 2),
        'update':       2,
        'score':        2,
        'status':       2,
    }

    def __init__(self):
        print 'Trackma v'+utils.VERSION+'  Copyright (C) 2012  z411'
        print 'This program comes with ABSOLUTELY NO WARRANTY; for details type `info\''
        print 'This is free software, and you are welcome to redistribute it'
        print 'under certain conditions; see the file COPYING for details.'
        print

        self.accountman = Trackma_accounts()
        self.account = self.accountman.select_account(False)

    def _update_prompt(self):
        self.prompt = "{c_u}{u}{c_r} [{c_a}{a}{c_r}] ({c_mt}{mt}{c_r}) {c_s}{s}{c_r} >> ".format(
                u  = self.engine.get_userconfig('username'),
                a  = self.engine.api_info['shortname'],
                mt = self.engine.api_info['mediatype'],
                s  = self.engine.mediainfo['statuses_dict'][self.filter_num].lower().replace(' ', ''),
                c_r  = _PCOLOR_RESET,
                c_u  = _PCOLOR_USER,
                c_a  = _PCOLOR_API,
                c_mt = _PCOLOR_MEDIATYPE,
                c_s  = _COLOR_RESET
        )

    def _load_list(self, *args):
        showlist = self.engine.filter_list(self.filter_num)
        self.sortedlist = sorted(showlist, key=itemgetter(self.sort))

    def _get_show(self, title):
        # Attempt parsing list index
        # otherwise use title
        try:
            index = int(title)-1
            return self.sortedlist[index]
        except (ValueError, AttributeError, IndexError):
            return self.engine.get_show_info_title(title)

    def _ask_update(self, show, episode):
        do_update = raw_input("Should I update %s to episode %d? [y/N] " % (show['title'].encode('utf-8'), episode))
        if do_update.lower() == 'y':
            self.engine.set_episode(show['id'], episode)

    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """
        print 'Initializing engine...'
        self.engine = Engine(self.account, self.messagehandler)
        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

        print
        print "Ready. Type 'help' for a list of commands."
        print "Press tab for autocompletion and up/down for command history."
        self.do_filter(None) # Show available filters
        print

    def do_help(self, arg):
        if arg:
            try:
                doc = getattr(self, 'do_' + arg).__doc__
                if doc:
                    (name, args, expl, usage) = self._parse_doc(arg, doc)

                    print
                    print name
                    for line in expl:
                        print "  {}".format(line)
                    if args:
                        print "\n  Arguments:"
                        for arg in args:
                            if arg[2]:
                                print "    {}: {}".format(arg[0], arg[1])
                            else:
                                print "    {} (optional): {}".format(arg[0], arg[1])
                    if usage:
                        print "\n  Usage: " + usage
                    print
                    return
            except AttributeError:
                pass

            print "No help available."
            return
        else:
            CMD_LENGTH = 11
            ARG_LENGTH = 13

            (height, width) = utils.get_terminal_size()
            prev_width = CMD_LENGTH + ARG_LENGTH + 3

            tw = textwrap.TextWrapper()
            tw.width = width - 2
            tw.subsequent_indent = ' ' * prev_width

            print
            print " {0:>{1}} {2:{3}} {4}".format(
                    'command', CMD_LENGTH,
                    'args', ARG_LENGTH,
                    'description')
            print " " + "-"*(min(prev_width+81, width-3))

            names = self.get_names()
            names.sort()
            cmds = []
            for name in names:
                if name[:3] == 'do_':
                    doc = getattr(self, name).__doc__
                    if not doc:
                        continue

                    cmd = name[3:]
                    (name, args, expl, usage) = self._parse_doc(cmd, doc)

                    line = " {0:>{1}} {2:{3}} {4}".format(
                           name, CMD_LENGTH,
                           '<' + ','.join( a[0] for a in args) + '>', ARG_LENGTH,
                           expl[0])
                    print tw.fill(line)

            print
            print "Use `help <command>` for detailed information."
            print


    def do_account(self, args):
        """
        Switch to a different account.
        """

        self.account = self.accountman.select_account(True)
        self.engine.reload(account=self.account)

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_filter(self, args):
        """
        Changes the filtering of list by status.s

        :optparam status Name of status to filter
        :usage filter [filter type]
        """
        # Query the engine for the available statuses
        # that the user can choose
        if args:
            try:
                self.filter_num = self._guess_status(args[0].lower())
                self._load_list()
                self._update_prompt()
            except KeyError:
                print "Invalid filter."
        else:
            print "Available statuses: %s" % ', '.join( v.lower().replace(' ', '') for v in self.engine.mediainfo['statuses_dict'].values() )

    def do_sort(self, args):
        """
        Change of the lists

        :param type Sort type; available types: id, title, my_progress, total, my_score
        :usage sort <sort type>
        """
        sorts = ('id', 'title', 'my_progress', 'total', 'my_score')
        if args[0] in sorts:
            self.sort = args[0]
            self._load_list()
        else:
            print "Invalid sort."

    def do_mediatype(self, args):
        """
        Reloads engine with different mediatype.
        Call with no arguments to see supported mediatypes.

        :optparam mediatype Mediatype name
        :usage mediatype [mediatype]
        """
        if args:
            if args[0] in self.engine.api_info['supported_mediatypes']:
                self.engine.reload(mediatype=args[0])

                # Start with default filter selected
                self.filter_num = self.engine.mediainfo['statuses'][0]
                self._load_list()
                self._update_prompt()
            else:
                print "Invalid mediatype."
        else:
            print "Supported mediatypes: %s" % ', '.join(self.engine.api_info['supported_mediatypes'])

    def do_ls(self,args):
        self.do_list(args)

    def do_list(self, args):
        """
        Lists all shows available in the local list.

        :name list|ls
        """
        # Show the list in memory
        self._make_list(self.sortedlist)

    def do_info(self, args):
        """
        Gets detailed information about a local show.

        :param show Show index or title.
        :usage info <show index or title>
        """
        try:
            show = self._get_show(args[0])
            details = self.engine.get_show_details(show)
        except utils.TrackmaError, e:
            self.display_error(e)
            return

        print "Title: %s" % details['title']
        for line in details['extra']:
            print "%s: %s" % line
Beispiel #17
0
class Trackma_urwid():
    """
    Main class for the urwid version of Trackma
    """

    """Main objects"""
    engine = None
    mainloop = None
    cur_sort = 'title'
    sorts_iter = cycle(('my_progress', 'total', 'my_score', 'id', 'title'))
    cur_order = False
    orders_iter = cycle((True, False))
    keymapping = dict()
    positions = list()
    last_search = None
    last_update_prompt = ()

    """Widgets"""
    header = None
    listbox = None
    view = None

    def __init__(self):
        """Creates main widgets and creates mainloop"""
        self.config = utils.parse_config(utils.to_config_path('ui-curses.json'), utils.curses_defaults)
        keymap = utils.curses_defaults['keymap']
        keymap.update(self.config['keymap'])
        self.keymap_str = self.get_keymap_str(keymap)
        self.keymapping = self.map_key_to_func(keymap)

        palette = []
        for k, color in self.config['palette'].items():
            palette.append( (k, color[0], color[1]) )

        # Prepare header
        sys.stdout.write("\x1b]0;Trackma-curses "+utils.VERSION+"\x07");
        self.header_title = urwid.Text('Trackma-curses ' + utils.VERSION)
        self.header_api = urwid.Text('API:')
        self.header_filter = urwid.Text('Filter:')
        self.header_sort = urwid.Text('Sort:title')
        self.header_order = urwid.Text('Order:d')
        self.header = urwid.AttrMap(urwid.Columns([
            self.header_title,
            ('fixed', 30, self.header_filter),
            ('fixed', 17, self.header_sort),
            ('fixed', 16, self.header_api)]), 'status')

        top_pile = [self.header]

        if self.config['show_help']:
            top_text = "{help}:Help  {sort}:Sort  " + \
                       "{update}:Update  {play}:Play  " + \
                       "{status}:Status  {score}:Score  " + \
                       "{quit}:Quit"
            top_text = top_text.format(**self.keymap_str)
            top_pile.append(urwid.AttrMap(urwid.Text(top_text), 'status'))

        self.top_pile = urwid.Pile(top_pile)

        # Prepare status bar
        self.status_text = urwid.Text('Trackma-curses '+utils.VERSION)
        self.status_queue = urwid.Text('Q:N/A')
        self.status_tracker = urwid.Text('T:N/A')
        self.statusbar = urwid.AttrMap(urwid.Columns([
            self.status_text,
            ('fixed', 10, self.status_tracker),
            ('fixed', 6, self.status_queue),
            ]), 'status')

        self.listheader = urwid.AttrMap(
            urwid.Columns([
                ('weight', 1, urwid.Text('Title')),
                ('fixed', 10, urwid.Text('Progress')),
                ('fixed', 7, urwid.Text('Score')),
            ]), 'header')

        self.listwalker = ShowWalker([])
        self.listbox = urwid.ListBox(self.listwalker)
        self.listframe = urwid.Frame(self.listbox, header=self.listheader)

        self.viewing_info = False

        self.view = urwid.Frame(self.listframe, header=self.top_pile, footer=self.statusbar)
        self.mainloop = urwid.MainLoop(self.view, palette, unhandled_input=self.keystroke, screen=urwid.raw_display.Screen())

    def run(self):
        self.mainloop.set_alarm_in(0, self.do_switch_account)
        self.mainloop.run()

    def map_key_to_func(self, keymap):
        keymapping = dict()
        funcmap = { 'help': self.do_help,
                    'prev_filter': self.do_prev_filter,
                    'next_filter': self.do_next_filter,
                    'sort': self.do_sort,
                    'sort_order': self.change_sort_order,
                    'update': self.do_update,
                    'play': self.do_play,
                    'openfolder': self.do_openfolder,
                    'play_random': self.do_play_random,
                    'status': self.do_status,
                    'score': self.do_score,
                    'send': self.do_send,
                    'retrieve': self.do_retrieve,
                    'addsearch': self.do_addsearch,
                    'reload': self.do_reload,
                    'switch_account': self.do_switch_account,
                    'delete': self.do_delete,
                    'quit': self.do_quit,
                    'altname': self.do_altname,
                    'search': self.do_search,
                    'neweps': self.do_neweps,
                    'details': self.do_info,
                    'details_exit': self.do_info_exit,
                    'open_web': self.do_open_web,
                    'left': self.key_left,
                    'down': self.key_down,
                    'up': self.key_up,
                    'right': self.key_right,
                    'page_down': self.key_page_down,
                    'page_up': self.key_page_up,
                    }

        for func, keybind in keymap.items():
            try:
                if isinstance(keybind, list):
                    for keybindm in keybind:
                        keymapping[keybindm] = funcmap[func]
                else:
                    keymapping[keybind] = funcmap[func]
            except KeyError:
                # keymap.json requested an action not available in funcmap
                pass
        return keymapping

    def get_keymap_str(self, keymap):
        stringed = {}
        for k, keybind in keymap.items():
            if isinstance(keybind, list):
                stringed[k] = ','.join(keybind)
            else:
                stringed[k] = keybind
        return stringed

    def _rebuild(self):
        self.header_api.set_text('API:%s' % self.engine.api_info['name'])
        self.lists = dict()
        self.filters = self.engine.mediainfo['statuses_dict']
        self.filters_nums = self.engine.mediainfo['statuses']
        self.filters_sizes = []

        track_info = self.engine.tracker_status()
        if track_info:
            self.tracker_state(track_info)

        for status in self.filters_nums:
            self.lists[status] = urwid.ListBox(ShowWalker([]))

        self._rebuild_lists()

        # Put the number of shows in every status in a list
        for status in self.filters_nums:
            self.filters_sizes.append(len(self.lists[status].body))

        self.set_filter(0)
        self.status('Ready.')
        self.started = True

    def _rebuild_lists(self, status=None):
        if status:
            self.lists[status].body[:] = []
            showlist = self.engine.filter_list(status)
        else:
            for _status in self.lists.keys():
                self.lists[_status].body[:] = []
            showlist = self.engine.get_list()

        library = self.engine.library()
        sortedlist = sorted(showlist, key=itemgetter(self.cur_sort), reverse=self.cur_order)

        for show in sortedlist:
            item = ShowItem(show, self.engine.mediainfo['has_progress'], self.engine.altname(show['id']), library.get(show['id']))

            self.lists[show['my_status']].body.append(item)

    def start(self, account):
        """Starts the engine"""
        # Engine configuration
        self.started = False

        self.status("Starting engine...")
        self.engine = Engine(account, self.message_handler)
        self.engine.connect_signal('episode_changed', self.changed_show)
        self.engine.connect_signal('score_changed', self.changed_show)
        self.engine.connect_signal('status_changed', self.changed_show_status)
        self.engine.connect_signal('playing', self.playing_show)
        self.engine.connect_signal('show_added', self.changed_list)
        self.engine.connect_signal('show_deleted', self.changed_list)
        self.engine.connect_signal('show_synced', self.changed_show)
        self.engine.connect_signal('queue_changed', self.changed_queue)
        self.engine.connect_signal('prompt_for_update', self.prompt_update)
        self.engine.connect_signal('tracker_state', self.tracker_state)

        # Engine start and list rebuildi
        self.status("Building lists...")
        self.engine.start()
        self._rebuild()

    def set_filter(self, filter_num):
        self.cur_filter = filter_num
        _filter = self.filters_nums[self.cur_filter]
        self.header_filter.set_text("Filter:%s (%d)" % (self.filters[_filter], self.filters_sizes[self.cur_filter]))

        self.listframe.body = self.lists[_filter]

    def _get_cur_list(self):
        _filter = self.filters_nums[self.cur_filter]
        return self.lists[_filter].body

    def _get_selected_item(self):
        return self._get_cur_list().get_focus()[0]

    def status(self, msg):
        self.status_text.set_text(msg)

    def error(self, msg):
        self.status_text.set_text([('error', "Error: %s" % msg)])

    def message_handler(self, classname, msgtype, msg):
        if msgtype != messenger.TYPE_DEBUG:
            try:
                self.status(msg)
                self.mainloop.draw_screen()
            except AssertionError:
                print(msg)

    def keystroke(self, input):
        try:
            self.keymapping[input]()
        except KeyError:
            # Unbinded key pressed; do nothing
            pass

    def key_left(self):
        self.mainloop.process_input(['left'])

    def key_down(self):
        self.mainloop.process_input(['down'])

    def key_up(self):
        self.mainloop.process_input(['up'])

    def key_right(self):
        self.mainloop.process_input(['right'])

    def key_page_down(self):
        self.mainloop.process_input(['page down'])

    def key_page_up(self):
        self.mainloop.process_input(['page up'])

    def forget_account(self):
        manager = AccountManager()
        manager.set_default(None)

    def do_switch_account(self, loop=None, data=None):
        manager = AccountManager()

        if self.engine is None:
            if manager.get_default():
                self.start(manager.get_default())
            else:
                self.dialog = AccountDialog(self.mainloop, manager, False)
                urwid.connect_signal(self.dialog, 'done', self.start)
        else:
            self.dialog = AccountDialog(self.mainloop, manager, True)
            urwid.connect_signal(self.dialog, 'done', self.do_reload_engine)

    def do_addsearch(self):
        self.ask('Search on remote: ', self.addsearch_request)

    def do_delete(self):
        if self._get_selected_item():
            self.question('Delete selected show? [y/n] ', self.delete_request)

    def do_prev_filter(self):
        if self.cur_filter > 0:
            self.set_filter(self.cur_filter - 1)

    def do_next_filter(self):
        if self.cur_filter < len(self.filters)-1:
            self.set_filter(self.cur_filter + 1)

    def do_sort(self):
        self.status("Sorting...")
        _sort = next(self.sorts_iter)
        self.cur_sort = _sort
        self.header_sort.set_text("Sort:%s" % _sort)
        self._rebuild_lists()
        self.status("Ready.")

    def change_sort_order(self):
        self.status("Sorting...")
        _order = next(self.orders_iter)
        self.cur_order = _order
        self._rebuild_lists()
        self.status("Ready.")

    def do_update(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            self.ask('[Update] Episode # to update to: ', self.update_request, show['my_progress']+1)

    def do_play(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            self.ask('[Play] Episode # to play: ', self.play_request, show['my_progress']+1)

    def do_openfolder(self):
        item = self._get_selected_item()

        try:
            show = self.engine.get_show_info(item.showid)
            filename = self.engine.get_episode_path(show, 1)
            with open(os.devnull, 'wb') as DEVNULL:
                if sys.platform == 'darwin':
                    subprocess.Popen(["open",
                    os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL)
                elif sys.platform == 'win32':
                    subprocess.Popen(["explorer",
                    os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL)
                else:
                    subprocess.Popen(["/usr/bin/xdg-open",
                    os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL)
        except OSError:
            # xdg-open failed.
            raise utils.EngineError("Could not open folder.")

        except utils.EngineError:
            # Show not in library.
             self.error("No folder found.")


    def do_play_random(self):
        try:
            self.engine.play_random()
        except utils.TrackmaError as e:
            self.error(e)
            return

    def do_send(self):
        self.engine.list_upload()
        self.status("Ready.")

    def do_retrieve(self):
        try:
            self.engine.list_download()
            self._rebuild_lists()
            self.status("Ready.")
        except utils.TrackmaError as e:
            self.error(e)

    def do_help(self):
        helptext = "Trackma-curses "+utils.VERSION+"  by z411 ([email protected])\n\n"
        helptext += "Trackma is an open source client for media tracking websites.\n"
        helptext += "http://github.com/z411/trackma\n\n"
        helptext += "This program is licensed under the GPLv3,\nfor more information read COPYING file.\n\n"
        helptext += "More controls:\n  {prev_filter}/{next_filter}:Change Filter\n  {search}:Search\n  {addsearch}:Add\n  {reload}:Change API/Mediatype\n"
        helptext += "  {delete}:Delete\n  {send}:Send changes\n  {sort_order}:Change sort order\n  {retrieve}:Retrieve list\n  {details}: View details\n  {open_web}: Open website\n  {openfolder}: Open folder containing show\n  {altname}:Set alternative title\n  {neweps}:Search for new episodes\n  {play_random}:Play Random\n  {switch_account}: Change account"
        helptext = helptext.format(**self.keymap_str)
        ok_button = urwid.Button('OK', self.help_close)
        ok_button_wrap = urwid.Padding(urwid.AttrMap(ok_button, 'button', 'button hilight'), 'center', 6)
        pile = urwid.Pile([urwid.Text(helptext), ok_button_wrap])
        self.dialog = Dialog(pile, self.mainloop, width=62, title='About/Help')
        self.dialog.show()

    def help_close(self, widget):
        self.dialog.close()

    def do_altname(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            self.status(show['title'])
            self.ask('[Altname] New alternative name: ', self.altname_request, self.engine.altname(item.showid))

    def do_score(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            self.ask('[Score] Score to change to: ', self.score_request, show['my_score'])

    def do_status(self):
        item = self._get_selected_item()
        if not item:
            return

        show = self.engine.get_show_info(item.showid)

        buttons = list()
        num = 1
        selected = 1
        title = urwid.Text('Choose status:')
        title.align = 'center'
        buttons.append(title)
        for status in self.filters_nums:
            name = self.filters[status]
            button = urwid.Button(name, self.status_request, status)
            button._label.align = 'center'
            buttons.append(urwid.AttrMap(button, 'button', 'button hilight'))
            if status == show['my_status']:
                selected = num
            num += 1
        pile = urwid.Pile(buttons)
        pile.set_focus(selected)
        self.dialog = Dialog(pile, self.mainloop, width=22)
        self.dialog.show()

    def do_reload(self):
        # Create a list of buttons to select the mediatype
        rb_mt = []
        mediatypes = []
        for mediatype in self.engine.api_info['supported_mediatypes']:
            but = urwid.RadioButton(rb_mt, mediatype)
            # Make it selected if it's the current mediatype
            if self.engine.api_info['mediatype'] == mediatype:
                but.set_state(True)
            urwid.connect_signal(but, 'change', self.reload_request, [None, mediatype])
            mediatypes.append(urwid.AttrMap(but, 'button', 'button hilight'))
        mediatype = urwid.Columns([urwid.Text('Mediatype:'), urwid.Pile(mediatypes)])

        #main_pile = urwid.Pile([mediatype, urwid.Divider(), api])
        self.dialog = Dialog(mediatype, self.mainloop, width=30, title='Change media type')
        self.dialog.show()

    def do_reload_engine(self, account=None, mediatype=None):
        self.started = False
        self.engine.reload(account, mediatype)
        self._rebuild()

    def do_open_web(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            if show['url']:
                webbrowser.open(show['url'], 2, True)

    def do_info(self):
        if self.viewing_info:
            return

        item = self._get_selected_item()
        if not item:
            return

        show = self.engine.get_show_info(item.showid)

        self.status("Getting show details...")

        try:
            details = self.engine.get_show_details(show)
        except utils.TrackmaError as e:
            self.error(e)
            return

        title = urwid.Text( ('info_title', show['title']), 'center', 'any')
        widgets = []
        for line in details['extra']:
            if line[0] and line[1]:
                widgets.append( urwid.Text( ('info_section', "%s: " % line[0] ) ) )
                if isinstance(line[1], dict):
                    linestr = repr(line[1])
                elif isinstance(line[1], int) or isinstance(line[1], list):
                    linestr = str(line[1])
                else:
                    linestr = line[1]

                widgets.append( urwid.Padding(urwid.Text( linestr + "\n" ), left=3) )

        self.view.body = urwid.Frame(urwid.ListBox(widgets), header=title)
        self.viewing_info = True
        self.status("Detail View | ESC:Return  Up/Down:Scroll  O:View website")

    def do_info_exit(self):
        if self.viewing_info:
            self.view.body = self.listframe
            self.viewing_info = False
            self.status("Ready.")

    def do_neweps(self):
        try:
            shows = self.engine.scan_library(rescan=True)
            self._rebuild_lists()

            self.status("Ready.")
        except utils.TrackmaError as e:
            self.error(e)

    def do_quit(self):
        if self.viewing_info:
            self.do_info_exit()
        else:
            self.engine.unload()
            raise urwid.ExitMainLoop()

    def addsearch_request(self, data):
        self.ask_finish(self.addsearch_request)
        if data:
            try:
                shows = self.engine.search(data)
            except utils.TrackmaError as e:
                self.error(e)
                return

            if len(shows) > 0:
                self.status("Ready.")
                self.dialog = AddDialog(self.mainloop, self.engine, showlist=shows, width=('relative', 80))
                urwid.connect_signal(self.dialog, 'done', self.addsearch_do)
                self.dialog.show()
            else:
                self.status("No results.")

    def addsearch_do(self, show):
        self.dialog.close()
        # Add show as current status
        _filter = self.filters_nums[self.cur_filter]
        try:
            self.engine.add_show(show, _filter)
        except utils.TrackmaError as e:
            self.error(e)

    def delete_request(self, data):
        self.ask_finish(self.delete_request)
        if data == 'y':
            showid = self._get_selected_item().showid
            show = self.engine.get_show_info(showid)

            try:
                self.engine.delete_show(show)
            except utils.TrackmaError as e:
                self.error(e)

    def status_request(self, widget, data=None):
        self.dialog.close()
        if data is not None:
            item = self._get_selected_item()

            try:
                show = self.engine.set_status(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def reload_request(self, widget, selected, data):
        if selected:
            self.dialog.close()
            self.do_reload_engine(data[0], data[1])

    def update_request(self, data):
        self.ask_finish(self.update_request)
        if data:
            item = self._get_selected_item()

            try:
                show = self.engine.set_episode(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def score_request(self, data):
        self.ask_finish(self.score_request)
        if data:
            item = self._get_selected_item()

            try:
                show = self.engine.set_score(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def altname_request(self, data):
        self.ask_finish(self.altname_request)
        if data:
            item = self._get_selected_item()

            try:
                self.engine.altname(item.showid, data)
                item.update_altname(self.engine.altname(item.showid))
            except utils.TrackmaError as e:
                self.error(e)
                return

    def play_request(self, data):
        self.ask_finish(self.play_request)
        if data:
            item = self._get_selected_item()
            show = self.engine.get_show_info(item.showid)

            try:
                self.engine.play_episode(show, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def prompt_update_request(self, data):
        (show, episode) = self.last_update_prompt
        self.ask_finish(self.prompt_update_request)
        if data == 'y':
            try:
                show = self.engine.set_episode(show['id'], episode)
            except utils.TrackmaError as e:
                self.error(e)
                return
        else:
            self.status('Ready.')

    def prompt_update(self, show, episode):
        self.last_update_prompt = (show, episode)
        self.question("Update %s to episode %d? [y/N] " % (show['title'], episode), self.prompt_update_request)

    def changed_show(self, show, changes=None):
        if self.started and show:
            status = show['my_status']
            self.lists[status].body.update_show(show)
            self.mainloop.draw_screen()

    def changed_show_status(self, show, old_status=None):
        self._rebuild_lists(show['my_status'])
        if old_status is not None:
            self._rebuild_lists(old_status)

        go_filter = 0
        for _filter in self.filters_nums:
            if _filter == show['my_status']:
                break
            go_filter += 1

        self.set_filter(go_filter)
        self._get_cur_list().select_show(show)

    def changed_queue(self, queue):
        self.status_queue.set_text("Q:{}".format(len(queue)))

    def tracker_state(self, status):
        state = status['state']
        timer = status['timer']
        paused = status['paused']

        if state == utils.TRACKER_NOVIDEO:
            st = 'LISTEN'
        elif state == utils.TRACKER_PLAYING:
            st = '{}{}'.format('#' if paused else '+', timer)
        elif state == utils.TRACKER_UNRECOGNIZED:
            st = 'UNRECOG'
        elif state == utils.TRACKER_NOT_FOUND:
            st = 'NOTFOUN'
        elif state == utils.TRACKER_IGNORED:
            st = 'IGNORE'
        else:
            st = '???'

        self.status_tracker.set_text("T:{}".format(st))
        self.mainloop.draw_screen()

    def playing_show(self, show, is_playing, episode=None):
        status = show['my_status']
        self.lists[status].body.playing_show(show, is_playing)
        self.mainloop.draw_screen()

    def changed_list(self, show):
        self._rebuild_lists(show['my_status'])

    def ask(self, msg, callback, data=u''):
        self.asker = Asker(msg, str(data))
        self.view.set_footer(urwid.AttrMap(self.asker, 'status'))
        self.view.set_focus('footer')
        urwid.connect_signal(self.asker, 'done', callback)

    def question(self, msg, callback, data=u''):
        self.asker = QuestionAsker(msg, str(data))
        self.view.set_footer(urwid.AttrMap(self.asker, 'status'))
        self.view.set_focus('footer')
        urwid.connect_signal(self.asker, 'done', callback)

    def ask_finish(self, callback):
        self.view.set_focus('body')
        urwid.disconnect_signal(self, self.asker, 'done', callback)
        self.view.set_footer(self.statusbar)

    def do_search(self, key=''):
        if self.last_search:
            text = "Search forward [%s]: " % self.last_search
        else:
            text = "Search forward: "

        self.ask(text, self.search_request, key)
        #urwid.connect_signal(self.asker, 'change', self.search_live)

    #def search_live(self, widget, data):
    #    if data:
    #        self.listwalker.select_match(data)

    def search_request(self, data):
        self.ask_finish(self.search_request)
        if data:
            self.last_search = data
            self._get_cur_list().select_match(data)
        elif self.last_search:
            self._get_cur_list().select_match(self.last_search)
Beispiel #18
0
class Trackma_cmd(cmd.Cmd):
    """
    Main program, inherits from the useful Cmd class
    for interactive console
    """
    engine = None
    filter_num = 1
    sort = 'title'
    completekey = 'Tab'
    cmdqueue = []
    stdout = sys.stdout
    in_prompt = False
    sortedlist = []
    needed_args = {
        'altname':      (1, 2),
        'filter':       (0, 1),
        'sort':         1,
        'mediatype':    (0, 1),
        'info':         1,
        'search':       1,
        'add':          1,
        'del':          1,
        'delete':       1,
        'play':         (1, 2),
        'openfolder':   1,
        'update':       (1, 2),
        'score':        2,
        'status':       2,
    }

    def __init__(self, account_num=None, debug=False, interactive=True):
        super().__init__()

        if interactive:
            print('Trackma v'+utils.VERSION+'  Copyright (C) 2012-2017  z411')
            print('This program comes with ABSOLUTELY NO WARRANTY; for details type `about\'')
            print('This is free software, and you are welcome to redistribute it')
            print('under certain conditions; see the COPYING file for details.')
            print()

        self.interactive = interactive
        self.debug = debug

        self.accountman = Trackma_accounts()
        if account_num:
            try:
                self.account = self.accountman.get_account(account_num)
            except KeyError:
                print("Account {} doesn't exist.".format(account_num))
                self.account = self.accountman.select_account(True)
            except ValueError:
                print("Account {} must be numeric.".format(account_num))
                self.account = self.accountman.select_account(True)
        else:
            self.account = self.accountman.select_account(False)

    def forget_account(self):
        self.accountman.set_default(None)

    def _update_prompt(self):
        self.prompt = "{c_u}{u}{c_r} [{c_a}{a}{c_r}] ({c_mt}{mt}{c_r}) {c_s}{s}{c_r} >> ".format(
            u  = self.engine.get_userconfig('username'),
            a  = self.engine.api_info['shortname'],
            mt = self.engine.api_info['mediatype'],
            s  = self.engine.mediainfo['statuses_dict'][self.filter_num].lower().replace(' ', ''),
            c_r  = _PCOLOR_RESET,
            c_u  = _PCOLOR_USER,
            c_a  = _PCOLOR_API,
            c_mt = _PCOLOR_MEDIATYPE,
            c_s  = _COLOR_RESET
        )

    def _load_list(self, *args):
        showlist = self.engine.filter_list(self.filter_num)
        sortedlist = sorted(showlist, key=itemgetter(self.sort))
        self.sortedlist = list(enumerate(sortedlist, 1))

    def _get_show(self, title):
        # Attempt parsing list index
        # otherwise use title
        try:
            index = int(title)-1
            return self.sortedlist[index][1]
        except (ValueError, AttributeError, IndexError):
            return self.engine.get_show_info(title=title)

    def _ask_update(self, show, episode):
        do = input("Should I update {} to episode {}? [y/N] ".format(show['title'], episode))
        if do.lower() == 'y':
            self.engine.set_episode(show['id'], episode)

    def _ask_add(self, show_title, episode):
        do = input("Should I search for the show {}? [y/N] ".format(show_title))
        if do.lower() == 'y':
            self.do_add([show_title])

    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """

        if self.interactive:
            print('Initializing engine...')
            self.engine = Engine(self.account, self.messagehandler)
        else:
            self.engine = Engine(self.account)
            self.engine.set_config("tracker_enabled", False)
            self.engine.set_config("library_autoscan", False)
            self.engine.set_config("use_hooks", False)

        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.connect_signal('prompt_for_add', self._ask_add)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()

        if self.interactive:
            self._update_prompt()

            print()
            print("Ready. Type 'help' for a list of commands.")
            print("Press tab for autocompletion and up/down for command history.")
            self.do_filter(None) # Show available filters
            print()
        else:
            # We set the message handler only after initializing
            # so we still receive the important messages but avoid
            # the initial spam.
            self.engine.set_message_handler(self.messagehandler)

    def do_about(self, args):
        print("Trackma {}  by z411 ([email protected])".format(utils.VERSION))
        print("Trackma is an open source client for media tracking websites.")
        print("https://github.com/z411/trackma")
        print()
        print("This program is licensed under the GPLv3 and it comes with ASOLUTELY NO WARRANTY.")
        print("Many contributors have helped to run this project; for more information see the AUTHORS file.")
        print("For more information about the license, see the COPYING file.")
        print()
        print("If you encounter any problems please report them in https://github.com/z411/trackma/issues")
        print()
        print("This is the CLI version of Trackma. To see available commands type `help'.")
        print("For other available interfaces please see the README file.")
        print()

    def do_help(self, arg):
        if arg:
            try:
                doc = getattr(self, 'do_' + arg).__doc__
                if doc:
                    (name, args, expl, usage, examples) = self._parse_doc(arg, doc)

                    print()
                    print(name)
                    for line in expl:
                        print("  {}".format(line))
                    if args:
                        print("\n  Arguments:")
                        for arg in args:
                            if arg[2]:
                                print("    {}: {}".format(arg[0], arg[1]))
                            else:
                                print("    {} (optional): {}".format(arg[0], arg[1]))
                    if usage:
                        print("\n  Usage: " + usage)
                    for example in examples:
                        print("  Example: " + example)
                    print()
                    return
            except AttributeError:
                pass

            print("No help available.")
            return
        else:
            CMD_LENGTH = 11
            ARG_LENGTH = 13

            (height, width) = utils.get_terminal_size()
            prev_width = CMD_LENGTH + ARG_LENGTH + 3

            tw = textwrap.TextWrapper()
            tw.width = width - 2
            tw.subsequent_indent = ' ' * prev_width

            print()
            print(" {0:>{1}} {2:{3}} {4}".format(
                    'command', CMD_LENGTH,
                    'args', ARG_LENGTH,
                    'description'))
            print(" " + "-"*(min(prev_width+81, width-3)))

            names = self.get_names()
            names.sort()
            for name in names:
                if name[:3] == 'do_':
                    doc = getattr(self, name).__doc__
                    if not doc:
                        continue

                    cmd = name[3:]
                    (name, args, expl, usage, examples) = self._parse_doc(cmd, doc)

                    line = " {0:>{1}} {2:{3}} {4}".format(
                        name, CMD_LENGTH,
                        '<' + ','.join( a[0] for a in args) + '>', ARG_LENGTH,
                        expl[0])
                    print(tw.fill(line))

            print()
            print("Use `help <command>` for detailed information.")
            print()


    def do_account(self, args):
        """
        Switch to a different account.
        """

        self.account = self.accountman.select_account(True)
        self.engine.reload(account=self.account)

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_filter(self, args):
        """
        Changes the filtering of list by status (shows current if empty).

        :optparam status Name of status to filter
        :usage filter [filter type]
        """
        # Query the engine for the available statuses
        # that the user can choose
        if args:
            try:
                self.filter_num = self._guess_status(args[0].lower())
                self._load_list()
                self._update_prompt()
            except KeyError:
                print("Invalid filter.")
        else:
            print("Available statuses: %s" % ', '.join( v.lower().replace(' ', '') for v in self.engine.mediainfo['statuses_dict'].values() ))

    def do_sort(self, args):
        """
        Change of the lists

        :param type Sort type; available types: id, title, my_progress, total, my_score
        :usage sort <sort type>
        """
        sorts = ('id', 'title', 'my_progress', 'total', 'my_score')
        if args[0] in sorts:
            self.sort = args[0]
            self._load_list()
        else:
            print("Invalid sort.")

    def do_mediatype(self, args):
        """
        Reloads engine with different mediatype (shows current if empty).
        Call with no arguments to see supported mediatypes.

        :optparam mediatype Mediatype name
        :usage mediatype [mediatype]
        """
        if args:
            if args[0] in self.engine.api_info['supported_mediatypes']:
                self.engine.reload(mediatype=args[0])

                # Start with default filter selected
                self.filter_num = self.engine.mediainfo['statuses'][0]
                self._load_list()
                self._update_prompt()
            else:
                print("Invalid mediatype.")
        else:
            print("Supported mediatypes: %s" % ', '.join(self.engine.api_info['supported_mediatypes']))

    def do_ls(self,args):
        self.do_list(args)

    def do_list(self, args):
        """
        Lists all shows available in the local list.

        :name list|ls
        """
        # Show the list in memory
        self._make_list(self.sortedlist)

    def do_info(self, args):
        """
        Gets detailed information about a local show.

        :param show Show index or title.
        :usage info <show index or title>
        """
        try:
            show = self._get_show(args[0])
            details = self.engine.get_show_details(show)
        except utils.TrackmaError as e:
            self.display_error(e)
            return

        print(show['title'])
        print("-" * len(show['title']))
        print(show['url'])
        print()

        for line in details['extra']:
            print("%s: %s" % line)

    def do_search(self, args):
        """
        Does a regex search on shows in the local lists.

        :param pattern Regex pattern to search for.
        :usage search <pattern>
        """
        sortedlist = list(v for v in self.sortedlist if re.search(args[0], v[1]['title'], re.I))
        self._make_list(sortedlist)

    def do_add(self, args):
        """
        Search for a show in the remote service and add it.

        :param pattern Show criteria to search.
        :usage add <pattern>
        """
        try:
            entries = self.engine.search(args[0])
        except utils.TrackmaError as e:
            self.display_error(e)
            return

        for i, entry in enumerate(entries, start=1):
            print("%d: (%s) %s" % (i, entry['type'], entry['title']))
        do_update = input("Choose show to add (blank to cancel): ")
        if do_update != '':
            try:
                show = entries[int(do_update)-1]
            except ValueError:
                print("Choice must be numeric.")
                return
            except IndexError:
                print("Invalid show.")
                return

            # Tell the engine to add the show
            try:
                self.engine.add_show(show, self.filter_num)
            except utils.TrackmaError as e:
                self.display_error(e)

    def do_del(self, args):
        self.do_delete(args)

    def do_delete(self, args):
        """
        Deletes a show from the local list.

        :name delete|del
        :param show Show index or title.
        :usage delete <show index or title>
        """
        try:
            show = self._get_show(args[0])

            do_delete = input("Delete %s? [y/N] " % show['title'])
            if do_delete.lower() == 'y':
                self.engine.delete_show(show)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_rescan(self, args):
        """
        Re-scans the local library.
        """
        self.engine.scan_library(rescan=True)

    def do_random(self, args):
        """
        Starts the media player with a random new episode.
        """
        try:
            self.engine.play_random()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_tracker(self, args):
        """
        Shows information about the tracker, if it's running.

        :usage trackmer
        """
        try:
            info = self.engine.tracker_status()
            print("- Tracker status -")

            if info:
                if info['state'] == utils.TRACKER_NOVIDEO:
                    state = 'No video'
                elif info['state'] == utils.TRACKER_PLAYING:
                    state = 'Playing'
                elif info['state'] == utils.TRACKER_UNRECOGNIZED:
                    state = 'Unrecognized'
                elif info['state'] == utils.TRACKER_NOT_FOUND:
                    state = 'Not found'
                elif info['state'] == utils.TRACKER_IGNORED:
                    state = 'Ignored'
                else:
                    state = 'N/A'

                print("State: {}".format(state))
                print("Filename: {}".format(info['filename'] or 'N/A'))
                print("Timer: {}{}".format(info['timer'] or 'N/A', ' [P]' if info['paused'] else ''))
                if info['show']:
                    (show, ep) = info['show']
                    print("Show: {}\nEpisode: {}".format(show['title'], ep))
                else:
                    print("Show: N/A")
            else:
                print("Not started")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_play(self, args):
        """
        Starts the media player with the specified episode number (next if unspecified).

        :param show Episode index or title.
        :optparam ep Episode number. Assume next if not specified.
        :usage play <show index or title> [episode number]
        """
        try:
            episode = 0
            show = self._get_show(args[0])

            # If the user specified an episode, play it
            # otherwise play the next episode not watched yet
            if len(args) > 1:
                episode = args[1]

            self.engine.play_episode(show, episode)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_openfolder(self, args):
        """
        Opens the folder containing the show

        :param show Show index or name.
        :usage openfolder <show index or name>
        """

        try:
            show = self._get_show(args[0])
            filename = self.engine.get_episode_path(show, 1)
            with open(os.devnull, 'wb') as DEVNULL:
                subprocess.Popen(["/usr/bin/xdg-open",
                os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL)
        except OSError:
            # xdg-open failed.
            self.display_error("Could not open folder.")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_update(self, args):
        """
        Updates the progress of a show to the specified episode (next if unspecified).

        :param show Show index, title or filename (prepend with file:).
        :optparam ep Episode number (numeric).
        :usage update <show index or name> [episode number]
        :example update Toradora! 5
        :example update 6
        :example update file:filename.mkv
        """
        try:
            if args[0][:5] == "file:":
                (show, ep) = self.engine.get_show_info(filename=args[0][5:])
            else:
                (show, ep) = (self._get_show(args[0]), None)

            if len(args) > 1:
                self.engine.set_episode(show['id'], args[1])
            else:
                self.engine.set_episode(show['id'], ep or show['my_progress']+1)
        except IndexError:
            print("Missing arguments.")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_score(self, args):
        """
        Changes the score of a show.

        :param show Show index or name.
        :param score Score to set (numeric/decimal).
        :usage score <show index or name> <score>
        """
        try:
            show = self._get_show(args[0])
            self.engine.set_score(show['id'], args[1])
        except IndexError:
            print("Missing arguments.")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_status(self, args):
        """
        Changes the status of a show.
        Use the command `filter` without arguments to see the available statuses.

        :param show Show index or name.
        :param status Status name. Use `filter` without args to list them.
        :usage status <show index or name> <status name>
        """
        try:
            _showtitle = args[0]
            _filter = args[1]
        except IndexError:
            print("Missing arguments.")
            return

        try:
            _filter_num = self._guess_status(_filter)
        except KeyError:
            print("Invalid filter.")
            return

        try:
            show = self._get_show(_showtitle)
            self.engine.set_status(show['id'], _filter_num)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_altname(self, args):
        """
        Changes the alternative name of a show (removes if unspecified).
        Use the command 'altname' without arguments to clear the alternative
        name.

        :param show Show index or name
        :param alt  The alternative name. Use `altname` without alt to clear it
        :usage altname <show index or name> <alternative name>
        """
        try:
            show = self._get_show(args[0])
            altname = args[1] if len(args) > 1 else ''
            self.engine.altname(show['id'],altname)
        except IndexError:
            print("Missing arguments")
            return
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_send(self, args):
        """
        Sends queued changes to the remote service.
        """
        try:
            self.engine.list_upload()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_retrieve(self, args):
        """
        Retrieves the remote list overwrites the local one.
        """
        try:
            if self.engine.get_queue():
                answer = input("There are unqueued changes. Overwrite local list? [y/N] ")
                if answer.lower() == 'y':
                    self.engine.list_download()
            else:
                self.engine.list_download()
            self._load_list()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_undoall(self, args):
        """
        Undo all changes in queue.
        """
        try:
            self.engine.undoall()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_viewqueue(self, args):
        """
        List the queued changes.
        """
        queue = self.engine.get_queue()
        if queue:
            print("Queue:")
            for show in queue:
                print("- %s" % show['title'])
        else:
            print("Queue is empty.")

    def do_exit(self, args):
        self.do_quit(args)

    def do_quit(self, args):
        """
        Quits the program.

        :name quit|exit
        """
        try:
            self.engine.unload()
        except utils.TrackmaError as e:
            self.display_error(e)

        print('Bye!')
        sys.exit(0)

    def do_EOF(self, args):
        print()
        self.do_quit(args)

    def complete_update(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_play(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_score(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_status(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_delete(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_filter(self, text, line, begidx, endidx):
        return [v.lower().replace(' ', '') for v in self.engine.mediainfo['statuses_dict'].values()]

    def parse_args(self, arg):
        if arg:
            return shlex.split(arg)

        return []

    def emptyline(self):
        return

    def preloop(self):
        """ Override. """
        self.in_prompt = True

    def precmd(self, line):
        """ Override. """
        self.in_prompt = False
        return line

    def postcmd(self, stop, line):
        """ Override. """
        self.in_prompt = True
        return stop

    def onecmd(self, line):
        """ Override. """
        cmd, arg, line = self.parseline(line)
        if not line:
            return self.emptyline()
        if cmd is None:
            return self.default(line)
        self.lastcmd = line
        if line == 'EOF' :
            self.lastcmd = ''
        if cmd == '':
            return self.default(line)
        elif cmd == 'help':
            return self.do_help(arg)
        else:
            return self.execute(cmd, self.parse_args(arg), line)

    def execute(self, cmd, args, line):
        try:
            func = getattr(self, 'do_' + cmd)
        except AttributeError:
            return self.default(line)

        try:
            needed = self.needed_args[cmd]
        except KeyError:
            needed = 0

        if isinstance(needed, int):
            needed = (needed, needed)

        if needed[0] <= len(args) <= needed[1]:
            return func(args)
        else:
            print("Incorrent number of arguments. See `help %s`" % cmd)

    def display_error(self, e):
        print("%s%s: %s%s" % (_COLOR_ERROR, type(e).__name__, e, _COLOR_RESET))

    def messagehandler(self, classname, msgtype, msg):
        """
        Handles and shows messages coming from
        the engine messenger to provide feedback.
        """
        color_escape = ''
        color_reset = _COLOR_RESET

        if classname == 'Engine':
            color_escape = _COLOR_ENGINE
        elif classname == 'Data':
            color_escape = _COLOR_DATA
        elif classname.startswith('lib'):
            color_escape = _COLOR_API
        elif classname.startswith('Tracker'):
            color_escape = _COLOR_TRACKER
        else:
            color_reset = ''

        if msgtype == messenger.TYPE_INFO:
            out = "%s%s: %s%s" % (color_escape, classname, msg, color_reset)
        elif msgtype == messenger.TYPE_WARN:
            out = "%s%s warning: %s%s" % (color_escape, classname, msg, color_reset)
        elif self.debug and msgtype == messenger.TYPE_DEBUG:
            out = "[D] %s%s: %s%s" % (color_escape, classname, msg, color_reset)
        else:
            return # Unrecognized message, don't show anything

        if has_readline and self.in_prompt:
            # If we're in a prompt and receive a message
            # (often from the tracker) we need to clear the line
            # first, show the message, then re-show the prompt.
            buf = readline.get_line_buffer()
            self.stdout.write('\r' + ' '*(len(self.prompt)+len(buf)) + '\r')

            print(out)

            self.stdout.write(self.prompt + buf)
            self.stdout.flush()
        else:
            print(out)

    def _guess_status(self, string):
        for k, v in self.engine.mediainfo['statuses_dict'].items():
            if string.lower() == v.lower().replace(' ', ''):
                return k
        raise KeyError

    def _parse_doc(self, cmd, doc):
        lines = doc.split('\n')
        name = cmd
        args = []
        expl = []
        usage = None
        examples = []

        for line in lines:
            line = line.strip()
            if line[:6] == ":param":
                args.append( line[7:].split(' ', 1) + [True] )
            elif line[:9] == ":optparam":
                args.append( line[10:].split(' ', 1) + [False] )
            elif line[:6] == ':usage':
                usage = line[7:]
            elif line[:5] == ':name':
                name = line[6:]
            elif line[:8] == ':example':
                examples.append(line[9:])
            elif line:
                expl.append(line)

        return (name, args, expl, usage, examples)

    def _make_list(self, showlist):
        """
        Helper function for printing a formatted show list
        """
        # Fixed column widths
        col_id_length = 7
        col_index_length = 6
        col_title_length = 5
        col_episodes_length = 9
        col_score_length = 6
        altnames = self.engine.altnames()

        # Calculate maximum width for the title column
        # based on the width of the terminal
        (height, width) = utils.get_terminal_size()
        max_title_length = width - col_id_length - col_episodes_length - col_score_length - col_index_length - 5

        # Find the widest title so we can adjust the title column
        for index, show in showlist:
            if len(show['title']) > col_title_length:
                if len(show['title']) > max_title_length:
                    # Stop if we exceeded the maximum column width
                    col_title_length = max_title_length
                    break
                else:
                    col_title_length = len(show['title'])

        # Print header
        print("| {0:{1}} {2:{3}} {4:{5}} {6:{7}} |".format(
                'Index',    col_index_length,
                'Title',    max_title_length,
                'Progress', col_episodes_length,
                'Score',    col_score_length))

        # List shows
        for index, show in showlist:
            if self.engine.mediainfo['has_progress']:
                episodes_str = "{0:3} / {1}".format(show['my_progress'], show['total'] or '?')
            else:
                episodes_str = "-"

            #Get title (and alt. title) and if need be, truncate it
            title_str = show['title']
            if altnames.get(show['id']):
                title_str += " [{}]".format(altnames.get(show['id']))
            title_str = title_str[:max_title_length] if len(title_str) > max_title_length else title_str

            # Color title according to status
            if show['status'] == utils.STATUS_AIRING:
                colored_title = _COLOR_AIRING + title_str + _COLOR_RESET
            else:
                colored_title = title_str

            print("| {0:^{1}} {2}{3} {4:{5}} {6:^{7}} |".format(
                index, col_index_length,
                colored_title,
                '.' * (max_title_length-len(title_str)),
                episodes_str, col_episodes_length,
                show['my_score'], col_score_length))

        # Print result count
        print('%d results' % len(showlist))
        print()
Beispiel #19
0
class Trackma_cmd(cmd.Cmd):
    """
    Main program, inherits from the useful Cmd class
    for interactive console
    """
    engine = None
    filter_num = 1
    sort = 'title'
    completekey = 'Tab'
    cmdqueue = []
    stdout = sys.stdout
    sortedlist = []
    needed_args = {
        'filter': (0, 1),
        'sort': 1,
        'mediatype': (0, 1),
        'info': 1,
        'search': 1,
        'add': 1,
        'delete': 1,
        'play': (1, 2),
        'update': 2,
        'score': 2,
        'status': 2,
    }

    def __init__(self):
        print 'Trackma v' + utils.VERSION + '  Copyright (C) 2012  z411'
        print 'This program comes with ABSOLUTELY NO WARRANTY; for details type `info\''
        print 'This is free software, and you are welcome to redistribute it'
        print 'under certain conditions; see the file COPYING for details.'
        print

        self.accountman = Trackma_accounts()
        self.account = self.accountman.select_account(False)

    def _update_prompt(self):
        self.prompt = "{0}@{1}({2}) {3}> ".format(
            self.engine.get_userconfig('username'),
            self.engine.api_info['name'], self.engine.api_info['mediatype'],
            self.engine.mediainfo['statuses_dict'][self.filter_num])

    def _load_list(self, *args):
        showlist = self.engine.filter_list(self.filter_num)
        self.sortedlist = sorted(showlist, key=itemgetter(self.sort))

    def _get_show(self, title):
        # Attempt parsing list index
        # otherwise use title
        try:
            index = int(title) - 1
            return self.sortedlist[index]
        except (ValueError, AttributeError, IndexError):
            return self.engine.get_show_info_title(title)

    def start(self):
        """
        Initializes the engine
        
        Creates an Engine object and starts it.
        """
        print 'Initializing engine...'
        self.engine = Engine(self.account, self.messagehandler)
        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_account(self, args):
        """
        account - Switch to a different account

        Usage: account
        """

        self.account = self.accountman.select_account(True)
        self.engine.reload(account=self.account)

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_filter(self, args):
        """
        filter - Changes the filtering of list by status; call with no arguments to see available filters
        
        Usage: filter [filter type]
        """
        # Query the engine for the available statuses
        # that the user can choose
        if args:
            try:
                self.filter_num = self._guess_status(args[0].lower())
                self._load_list()
                self._update_prompt()
            except KeyError:
                print "Invalid filter."
        else:
            print "Available filters: %s" % ', '.join(
                v.lower().replace(' ', '')
                for v in self.engine.mediainfo['statuses_dict'].values())

    def do_sort(self, args):
        """
        sort - Change sort
        
        Usage: sort <sort type>
        Available types: id, title, my_progress, total, my_score
        """
        sorts = ('id', 'title', 'my_progress', 'total', 'my_score')
        if arg[0] in sorts:
            self.sort = arg[0]
            self._load_list()
        else:
            print "Invalid sort."

    def do_mediatype(self, args):
        """
        mediatype - Reloads engine with different mediatype; call with no arguments to see supported mediatypes
        
        Usage: mediatype [mediatype]
        """
        if args:
            if args[0] in self.engine.api_info['supported_mediatypes']:
                self.engine.reload(mediatype=args[0])

                # Start with default filter selected
                self.filter_num = self.engine.mediainfo['statuses'][0]
                self._load_list()
                self._update_prompt()
            else:
                print "Invalid mediatype."
        else:
            print "Supported mediatypes: %s" % ', '.join(
                self.engine.api_info['supported_mediatypes'])

    def do_list(self, args):
        """
        list - Lists all shows available in the local list as a nice formatted list.
        """
        # Show the list in memory
        self._make_list(self.sortedlist)

    def do_info(self, args):
        """
        info - Gets detailed information about a show in the local list.

        Usage: info <show index or title>

        """
        try:
            show = self._get_show(args[0])
            details = self.engine.get_show_details(show)
        except utils.TrackmaError, e:
            self.display_error(e)
            return

        print "Title: %s" % details['title']
        for line in details['extra']:
            print "%s: %s" % line
Beispiel #20
0
class Trackma_cmd(cmd.Cmd):
    """
    Main program, inherits from the useful Cmd class
    for interactive console
    """
    engine = None
    filter_num = 1
    sort = 'title'
    completekey = 'Tab'
    cmdqueue = []
    stdout = sys.stdout
    sortedlist = []
    needed_args = {
        'filter': (0, 1),
        'sort': 1,
        'mediatype': (0, 1),
        'info': 1,
        'search': 1,
        'add': 1,
        'delete': 1,
        'play': (1, 2),
        'update': 2,
        'score': 2,
        'status': 2,
    }

    def __init__(self):
        print 'Trackma v' + utils.VERSION + '  Copyright (C) 2012  z411'
        print 'This program comes with ABSOLUTELY NO WARRANTY; for details type `info\''
        print 'This is free software, and you are welcome to redistribute it'
        print 'under certain conditions; see the file COPYING for details.'
        print

        self.accountman = Trackma_accounts()
        self.account = self.accountman.select_account(False)

    def _update_prompt(self):
        self.prompt = "{c_u}{u}{c_r} [{c_a}{a}{c_r}] ({c_mt}{mt}{c_r}) {c_s}{s}{c_r} >> ".format(
            u=self.engine.get_userconfig('username'),
            a=self.engine.api_info['shortname'],
            mt=self.engine.api_info['mediatype'],
            s=self.engine.mediainfo['statuses_dict'][
                self.filter_num].lower().replace(' ', ''),
            c_r=_PCOLOR_RESET,
            c_u=_PCOLOR_USER,
            c_a=_PCOLOR_API,
            c_mt=_PCOLOR_MEDIATYPE,
            c_s=_COLOR_RESET)

    def _load_list(self, *args):
        showlist = self.engine.filter_list(self.filter_num)
        self.sortedlist = sorted(showlist, key=itemgetter(self.sort))

    def _get_show(self, title):
        # Attempt parsing list index
        # otherwise use title
        try:
            index = int(title) - 1
            return self.sortedlist[index]
        except (ValueError, AttributeError, IndexError):
            return self.engine.get_show_info_title(title)

    def _ask_update(self, show, episode):
        do_update = raw_input("Should I update %s to episode %d? [y/N] " %
                              (show['title'].encode('utf-8'), episode))
        if do_update.lower() == 'y':
            self.engine.set_episode(show['id'], episode)

    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """
        print 'Initializing engine...'
        self.engine = Engine(self.account, self.messagehandler)
        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

        print
        print "Ready. Type 'help' for a list of commands."
        print "Press tab for autocompletion and up/down for command history."
        self.do_filter(None)  # Show available filters
        print

    def do_help(self, arg):
        if arg:
            try:
                doc = getattr(self, 'do_' + arg).__doc__
                if doc:
                    (name, args, expl, usage) = self._parse_doc(arg, doc)

                    print
                    print name
                    for line in expl:
                        print "  {}".format(line)
                    if args:
                        print "\n  Arguments:"
                        for arg in args:
                            if arg[2]:
                                print "    {}: {}".format(arg[0], arg[1])
                            else:
                                print "    {} (optional): {}".format(
                                    arg[0], arg[1])
                    if usage:
                        print "\n  Usage: " + usage
                    print
                    return
            except AttributeError:
                pass

            print "No help available."
            return
        else:
            CMD_LENGTH = 11
            ARG_LENGTH = 13

            (height, width) = utils.get_terminal_size()
            prev_width = CMD_LENGTH + ARG_LENGTH + 3

            tw = textwrap.TextWrapper()
            tw.width = width - 2
            tw.subsequent_indent = ' ' * prev_width

            print
            print " {0:>{1}} {2:{3}} {4}".format('command', CMD_LENGTH, 'args',
                                                 ARG_LENGTH, 'description')
            print " " + "-" * (min(prev_width + 81, width - 3))

            names = self.get_names()
            names.sort()
            cmds = []
            for name in names:
                if name[:3] == 'do_':
                    doc = getattr(self, name).__doc__
                    if not doc:
                        continue

                    cmd = name[3:]
                    (name, args, expl, usage) = self._parse_doc(cmd, doc)

                    line = " {0:>{1}} {2:{3}} {4}".format(
                        name, CMD_LENGTH,
                        '<' + ','.join(a[0] for a in args) + '>', ARG_LENGTH,
                        expl[0])
                    print tw.fill(line)

            print
            print "Use `help <command>` for detailed information."
            print

    def do_account(self, args):
        """
        Switch to a different account.
        """

        self.account = self.accountman.select_account(True)
        self.engine.reload(account=self.account)

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_filter(self, args):
        """
        Changes the filtering of list by status.s

        :optparam status Name of status to filter
        :usage filter [filter type]
        """
        # Query the engine for the available statuses
        # that the user can choose
        if args:
            try:
                self.filter_num = self._guess_status(args[0].lower())
                self._load_list()
                self._update_prompt()
            except KeyError:
                print "Invalid filter."
        else:
            print "Available statuses: %s" % ', '.join(
                v.lower().replace(' ', '')
                for v in self.engine.mediainfo['statuses_dict'].values())

    def do_sort(self, args):
        """
        Change of the lists

        :param type Sort type; available types: id, title, my_progress, total, my_score
        :usage sort <sort type>
        """
        sorts = ('id', 'title', 'my_progress', 'total', 'my_score')
        if args[0] in sorts:
            self.sort = args[0]
            self._load_list()
        else:
            print "Invalid sort."

    def do_mediatype(self, args):
        """
        Reloads engine with different mediatype.
        Call with no arguments to see supported mediatypes.

        :optparam mediatype Mediatype name
        :usage mediatype [mediatype]
        """
        if args:
            if args[0] in self.engine.api_info['supported_mediatypes']:
                self.engine.reload(mediatype=args[0])

                # Start with default filter selected
                self.filter_num = self.engine.mediainfo['statuses'][0]
                self._load_list()
                self._update_prompt()
            else:
                print "Invalid mediatype."
        else:
            print "Supported mediatypes: %s" % ', '.join(
                self.engine.api_info['supported_mediatypes'])

    def do_ls(self, args):
        self.do_list(args)

    def do_list(self, args):
        """
        Lists all shows available in the local list.

        :name list|ls
        """
        # Show the list in memory
        self._make_list(self.sortedlist)

    def do_info(self, args):
        """
        Gets detailed information about a local show.

        :param show Show index or title.
        :usage info <show index or title>
        """
        try:
            show = self._get_show(args[0])
            details = self.engine.get_show_details(show)
        except utils.TrackmaError, e:
            self.display_error(e)
            return

        print "Title: %s" % details['title']
        for line in details['extra']:
            print "%s: %s" % line
Beispiel #21
0
class Trackma_cmd(cmd.Cmd):
    """
    Main program, inherits from the useful Cmd class
    for interactive console
    """
    engine = None
    filter_num = 1
    sort = 'title'
    completekey = 'Tab'
    cmdqueue = []
    stdout = sys.stdout
    sortedlist = []
    needed_args = {
        'filter':       (0, 1),
        'sort':         1,
        'mediatype':    (0, 1),
        'info':         1,
        'search':       1,
        'add':          1,
        'delete':       1,
        'play':         (1, 2),
        'update':       2,
        'score':        2,
        'status':       2,
    }

    def __init__(self):
        print 'Trackma v'+utils.VERSION+'  Copyright (C) 2012  z411'
        print 'This program comes with ABSOLUTELY NO WARRANTY; for details type `info\''
        print 'This is free software, and you are welcome to redistribute it'
        print 'under certain conditions; see the file COPYING for details.'
        print

        self.accountman = Trackma_accounts()
        self.account = self.accountman.select_account(False)

    def _update_prompt(self):
        self.prompt = "{0}@{1}({2}) {3}> ".format(
                self.engine.get_userconfig('username'),
                self.engine.api_info['name'],
                self.engine.api_info['mediatype'],
                self.engine.mediainfo['statuses_dict'][self.filter_num]
        )

    def _load_list(self, *args):
        showlist = self.engine.filter_list(self.filter_num)
        self.sortedlist = sorted(showlist, key=itemgetter(self.sort))

    def _get_show(self, title):
        # Attempt parsing list index
        # otherwise use title
        try:
            index = int(title)-1
            return self.sortedlist[index]
        except (ValueError, AttributeError, IndexError):
            return self.engine.get_show_info_title(title)
    
    def _ask_update(self, show, episode):
        do_update = raw_input("Should I update %s to episode %d? [y/N] " % (show['title'].encode('utf-8'), episode))
        if do_update.lower() == 'y':
            self.engine.set_episode(show['id'], episode)

    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """
        print 'Initializing engine...'
        self.engine = Engine(self.account, self.messagehandler)
        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_account(self, args):
        """
        account - Switch to a different account

        Usage: account
        """

        self.account = self.accountman.select_account(True)
        self.engine.reload(account=self.account)

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_filter(self, args):
        """
        filter - Changes the filtering of list by status; call with no arguments to see available filters

        Usage: filter [filter type]
        """
        # Query the engine for the available statuses
        # that the user can choose
        if args:
            try:
                self.filter_num = self._guess_status(args[0].lower())
                self._load_list()
                self._update_prompt()
            except KeyError:
                print "Invalid filter."
        else:
            print "Available filters: %s" % ', '.join( v.lower().replace(' ', '') for v in self.engine.mediainfo['statuses_dict'].values() )

    def do_sort(self, args):
        """
        sort - Change sort

        Usage: sort <sort type>
        Available types: id, title, my_progress, total, my_score
        """
        sorts = ('id', 'title', 'my_progress', 'total', 'my_score')
        if args[0] in sorts:
            self.sort = args[0]
            self._load_list()
        else:
            print "Invalid sort."

    def do_mediatype(self, args):
        """
        mediatype - Reloads engine with different mediatype; call with no arguments to see supported mediatypes

        Usage: mediatype [mediatype]
        """
        if args:
            if args[0] in self.engine.api_info['supported_mediatypes']:
                self.engine.reload(mediatype=args[0])

                # Start with default filter selected
                self.filter_num = self.engine.mediainfo['statuses'][0]
                self._load_list()
                self._update_prompt()
            else:
                print "Invalid mediatype."
        else:
            print "Supported mediatypes: %s" % ', '.join(self.engine.api_info['supported_mediatypes'])

    def do_list(self, args):
        """
        list - Lists all shows available in the local list as a nice formatted list.
        """
        # Show the list in memory
        self._make_list(self.sortedlist)

    def do_info(self, args):
        """
        info - Gets detailed information about a show in the local list.

        Usage: info <show index or title>

        """
        try:
            show = self._get_show(args[0])
            details = self.engine.get_show_details(show)
        except utils.TrackmaError, e:
            self.display_error(e)
            return

        print "Title: %s" % details['title']
        for line in details['extra']:
            print "%s: %s" % line
Beispiel #22
0
class Trackma_urwid():
    """
    Main class for the urwid version of Trackma
    """

    """Main objects"""
    engine = None
    mainloop = None
    cur_sort = 'title'
    sorts_iter = cycle(('my_progress', 'total', 'my_score', 'id', 'title'))
    cur_order = False
    orders_iter = cycle((True, False))
    keymapping = dict()
    positions = list()
    last_search = None
    last_update_prompt = ()

    """Widgets"""
    header = None
    listbox = None
    view = None

    def __init__(self):
        """Creates main widgets and creates mainloop"""
        self.config = utils.parse_config(utils.get_root_filename('ui-curses.json'), utils.curses_defaults)
        keymap = utils.curses_defaults['keymap']
        keymap.update(self.config['keymap'])
        self.keymap_str = self.get_keymap_str(keymap)
        self.keymapping = self.map_key_to_func(keymap)

        palette = []
        for k, color in self.config['palette'].items():
            palette.append( (k, color[0], color[1]) )

        # Prepare header
        sys.stdout.write("\x1b]0;Trackma-curses "+utils.VERSION+"\x07");
        self.header_title = urwid.Text('Trackma-curses ' + utils.VERSION)
        self.header_api = urwid.Text('API:')
        self.header_filter = urwid.Text('Filter:')
        self.header_sort = urwid.Text('Sort:title')
        self.header_order = urwid.Text('Order:d')
        self.header = urwid.AttrMap(urwid.Columns([
            self.header_title,
            ('fixed', 30, self.header_filter),
            ('fixed', 17, self.header_sort),
            ('fixed', 16, self.header_api)]), 'status')

        top_pile = [self.header]

        if self.config['show_help']:
            top_text = "{help}:Help  {sort}:Sort  " + \
                       "{update}:Update  {play}:Play  " + \
                       "{status}:Status  {score}:Score  " + \
                       "{quit}:Quit"
            top_text = top_text.format(**self.keymap_str)
            top_pile.append(urwid.AttrMap(urwid.Text(top_text), 'status'))

        self.top_pile = urwid.Pile(top_pile)

        # Prepare status bar
        self.status_text = urwid.Text('Trackma-curses '+utils.VERSION)
        self.status_queue = urwid.Text('Q:N/A')
        self.status_tracker = urwid.Text('T:N/A')
        self.statusbar = urwid.AttrMap(urwid.Columns([
            self.status_text,
            ('fixed', 10, self.status_tracker),
            ('fixed', 6, self.status_queue),
            ]), 'status')

        self.listheader = urwid.AttrMap(
            urwid.Columns([
                ('weight', 1, urwid.Text('Title')),
                ('fixed', 10, urwid.Text('Progress')),
                ('fixed', 7, urwid.Text('Score')),
            ]), 'header')

        self.listwalker = ShowWalker([])
        self.listbox = urwid.ListBox(self.listwalker)
        self.listframe = urwid.Frame(self.listbox, header=self.listheader)

        self.viewing_info = False

        self.view = urwid.Frame(self.listframe, header=self.top_pile, footer=self.statusbar)
        self.mainloop = urwid.MainLoop(self.view, palette, unhandled_input=self.keystroke, screen=urwid.raw_display.Screen())

    def run(self):
        self.mainloop.set_alarm_in(0, self.do_switch_account)
        self.mainloop.run()

    def map_key_to_func(self, keymap):
        keymapping = dict()
        funcmap = { 'help': self.do_help,
                    'prev_filter': self.do_prev_filter,
                    'next_filter': self.do_next_filter,
                    'sort': self.do_sort,
                    'sort_order': self.change_sort_order,
                    'update': self.do_update,
                    'play': self.do_play,
                    'openfolder': self.do_openfolder,
                    'play_random': self.do_play_random,
                    'status': self.do_status,
                    'score': self.do_score,
                    'send': self.do_send,
                    'retrieve': self.do_retrieve,
                    'addsearch': self.do_addsearch,
                    'reload': self.do_reload,
                    'switch_account': self.do_switch_account,
                    'delete': self.do_delete,
                    'quit': self.do_quit,
                    'altname': self.do_altname,
                    'search': self.do_search,
                    'neweps': self.do_neweps,
                    'details': self.do_info,
                    'details_exit': self.do_info_exit,
                    'open_web': self.do_open_web,
                    }

        for func, keybind in keymap.items():
            try:
                if isinstance(keybind, list):
                    for keybindm in keybind:
                        keymapping[keybindm] = funcmap[func]
                else:
                    keymapping[keybind] = funcmap[func]
            except KeyError:
                # keymap.json requested an action not available in funcmap
                pass
        return keymapping

    def get_keymap_str(self, keymap):
        stringed = {}
        for k, keybind in keymap.items():
            if isinstance(keybind, list):
                stringed[k] = ','.join(keybind)
            else:
                stringed[k] = keybind
        return stringed

    def _rebuild(self):
        self.header_api.set_text('API:%s' % self.engine.api_info['name'])
        self.lists = dict()
        self.filters = self.engine.mediainfo['statuses_dict']
        self.filters_nums = self.engine.mediainfo['statuses']
        self.filters_sizes = []

        track_info = self.engine.tracker_status()
        if track_info:
            self.tracker_state(track_info['state'], None)

        for status in self.filters_nums:
            self.lists[status] = urwid.ListBox(ShowWalker([]))

        self._rebuild_lists()
        
        # Put the number of shows in every status in a list
        for status in self.filters_nums:
            self.filters_sizes.append(len(self.lists[status].body))
        
        self.set_filter(0)
        self.status('Ready.')
        self.started = True

    def _rebuild_lists(self, status=None):
        if status:
            self.lists[status].body[:] = []
            showlist = self.engine.filter_list(status)
        else:
            for _status in self.lists.keys():
                self.lists[_status].body[:] = []
            showlist = self.engine.get_list()

        library = self.engine.library()
        sortedlist = sorted(showlist, key=itemgetter(self.cur_sort), reverse=self.cur_order)

        for show in sortedlist:
            if show['my_status'] == self.engine.mediainfo['status_start']:
                item = ShowItem(show, self.engine.mediainfo['has_progress'], self.engine.altname(show['id']), library.get(show['id']))
            else:
                item = ShowItem(show, self.engine.mediainfo['has_progress'], self.engine.altname(show['id']))

            self.lists[show['my_status']].body.append(item)

    def start(self, account):
        """Starts the engine"""
        # Engine configuration
        self.started = False

        self.status("Starting engine...")
        self.engine = Engine(account, self.message_handler)
        self.engine.connect_signal('episode_changed', self.changed_show)
        self.engine.connect_signal('score_changed', self.changed_show)
        self.engine.connect_signal('status_changed', self.changed_show_status)
        self.engine.connect_signal('playing', self.playing_show)
        self.engine.connect_signal('show_added', self.changed_list)
        self.engine.connect_signal('show_deleted', self.changed_list)
        self.engine.connect_signal('show_synced', self.changed_show)
        self.engine.connect_signal('queue_changed', self.changed_queue)
        self.engine.connect_signal('prompt_for_update', self.prompt_update)
        self.engine.connect_signal('tracker_state', self.tracker_state)

        # Engine start and list rebuildi
        self.status("Building lists...")
        self.engine.start()
        self._rebuild()

    def set_filter(self, filter_num):
        self.cur_filter = filter_num
        _filter = self.filters_nums[self.cur_filter]
        self.header_filter.set_text("Filter:%s (%d)" % (self.filters[_filter], self.filters_sizes[self.cur_filter]))

        self.listframe.body = self.lists[_filter]

    def _get_cur_list(self):
        _filter = self.filters_nums[self.cur_filter]
        return self.lists[_filter].body

    def _get_selected_item(self):
        return self._get_cur_list().get_focus()[0]

    def status(self, msg):
        self.status_text.set_text(msg)

    def error(self, msg):
        self.status_text.set_text([('error', "Error: %s" % msg)])

    def message_handler(self, classname, msgtype, msg):
        if msgtype != messenger.TYPE_DEBUG:
            try:
                self.status(msg)
                self.mainloop.draw_screen()
            except AssertionError:
                print(msg)

    def keystroke(self, input):
        try:
            self.keymapping[input]()
        except KeyError:
            # Unbinded key pressed; do nothing
            pass

    def forget_account(self):
        manager = AccountManager()
        manager.set_default(None)

    def do_switch_account(self, loop=None, data=None):
        manager = AccountManager()

        if self.engine is None:
            if manager.get_default():
                self.start(manager.get_default())
            else:
                self.dialog = AccountDialog(self.mainloop, manager, False)
                urwid.connect_signal(self.dialog, 'done', self.start)
        else:
            self.dialog = AccountDialog(self.mainloop, manager, True)
            urwid.connect_signal(self.dialog, 'done', self.do_reload_engine)

    def do_addsearch(self):
        self.ask('Search on remote: ', self.addsearch_request)

    def do_delete(self):
        if self._get_selected_item():
            self.question('Delete selected show? [y/n] ', self.delete_request)

    def do_prev_filter(self):
        if self.cur_filter > 0:
            self.set_filter(self.cur_filter - 1)

    def do_next_filter(self):
        if self.cur_filter < len(self.filters)-1:
            self.set_filter(self.cur_filter + 1)

    def do_sort(self):
        self.status("Sorting...")
        _sort = next(self.sorts_iter)
        self.cur_sort = _sort
        self.header_sort.set_text("Sort:%s" % _sort)
        self._rebuild_lists()
        self.status("Ready.")

    def change_sort_order(self):
        self.status("Sorting...")
        _order = next(self.orders_iter)
        self.cur_order = _order
        self._rebuild_lists()
        self.status("Ready.")

    def do_update(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            self.ask('[Update] Episode # to update to: ', self.update_request, show['my_progress']+1)

    def do_play(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            self.ask('[Play] Episode # to play: ', self.play_request, show['my_progress']+1)

    def do_openfolder(self):
        item = self._get_selected_item()

        try:
            show = self.engine.get_show_info(item.showid)
            filename = self.engine.get_episode_path(show, 1)
            with open(os.devnull, 'wb') as DEVNULL:
                subprocess.Popen(["/usr/bin/xdg-open",
                os.path.dirname(filename)], stdout=DEVNULL, stderr=DEVNULL)
        except OSError:
            # xdg-open failed.
            raise utils.EngineError("Could not open folder.")

        except utils.EngineError:
            # Show not in library.
             self.error("No folder found.")


    def do_play_random(self):
        try:
            self.engine.play_random()
        except utils.TrackmaError as e:
            self.error(e)
            return

    def do_send(self):
        self.engine.list_upload()
        self.status("Ready.")

    def do_retrieve(self):
        try:
            self.engine.list_download()
            self._rebuild_lists()
            self.status("Ready.")
        except utils.TrackmaError as e:
            self.error(e)

    def do_help(self):
        helptext = "Trackma-curses "+utils.VERSION+"  by z411 ([email protected])\n\n"
        helptext += "Trackma is an open source client for media tracking websites.\n"
        helptext += "http://github.com/z411/trackma\n\n"
        helptext += "This program is licensed under the GPLv3,\nfor more information read COPYING file.\n\n"
        helptext += "More controls:\n  {prev_filter}/{next_filter}:Change Filter\n  {search}:Search\n  {addsearch}:Add\n  {reload}:Change API/Mediatype\n"
        helptext += "  {delete}:Delete\n  {send}:Send changes\n  {sort_order}:Change sort order\n  {retrieve}:Retrieve list\n  {details}: View details\n  {open_web}: Open website\n  {openfolder}: Open folder containing show\n  {altname}:Set alternative title\n  {neweps}:Search for new episodes\n  {play_random}:Play Random\n  {switch_account}: Change account"
        helptext = helptext.format(**self.keymap_str)
        ok_button = urwid.Button('OK', self.help_close)
        ok_button_wrap = urwid.Padding(urwid.AttrMap(ok_button, 'button', 'button hilight'), 'center', 6)
        pile = urwid.Pile([urwid.Text(helptext), ok_button_wrap])
        self.dialog = Dialog(pile, self.mainloop, width=62, title='About/Help')
        self.dialog.show()

    def help_close(self, widget):
        self.dialog.close()

    def do_altname(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            self.status(show['title'])
            self.ask('[Altname] New alternative name: ', self.altname_request, self.engine.altname(item.showid))

    def do_score(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            self.ask('[Score] Score to change to: ', self.score_request, show['my_score'])

    def do_status(self):
        item = self._get_selected_item()
        if not item:
            return

        show = self.engine.get_show_info(item.showid)

        buttons = list()
        num = 1
        selected = 1
        title = urwid.Text('Choose status:')
        title.align = 'center'
        buttons.append(title)
        for status in self.filters_nums:
            name = self.filters[status]
            button = urwid.Button(name, self.status_request, status)
            button._label.align = 'center'
            buttons.append(urwid.AttrMap(button, 'button', 'button hilight'))
            if status == show['my_status']:
                selected = num
            num += 1
        pile = urwid.Pile(buttons)
        pile.set_focus(selected)
        self.dialog = Dialog(pile, self.mainloop, width=22)
        self.dialog.show()

    def do_reload(self):
        # Create a list of buttons to select the mediatype
        rb_mt = []
        mediatypes = []
        for mediatype in self.engine.api_info['supported_mediatypes']:
            but = urwid.RadioButton(rb_mt, mediatype)
            # Make it selected if it's the current mediatype
            if self.engine.api_info['mediatype'] == mediatype:
                but.set_state(True)
            urwid.connect_signal(but, 'change', self.reload_request, [None, mediatype])
            mediatypes.append(urwid.AttrMap(but, 'button', 'button hilight'))
        mediatype = urwid.Columns([urwid.Text('Mediatype:'), urwid.Pile(mediatypes)])

        #main_pile = urwid.Pile([mediatype, urwid.Divider(), api])
        self.dialog = Dialog(mediatype, self.mainloop, width=30, title='Change media type')
        self.dialog.show()

    def do_reload_engine(self, account=None, mediatype=None):
        self.started = False
        self.engine.reload(account, mediatype)
        self._rebuild()

    def do_open_web(self):
        item = self._get_selected_item()
        if item:
            show = self.engine.get_show_info(item.showid)
            if show['url']:
                webbrowser.open(show['url'], 2, True)

    def do_info(self):
        if self.viewing_info:
            return

        item = self._get_selected_item()
        if not item:
            return

        show = self.engine.get_show_info(item.showid)

        self.status("Getting show details...")

        try:
            details = self.engine.get_show_details(show)
        except utils.TrackmaError as e:
            self.error(e)
            return

        title = urwid.Text( ('info_title', show['title']), 'center', 'any')
        widgets = []
        for line in details['extra']:
            if line[0] and line[1]:
                widgets.append( urwid.Text( ('info_section', "%s: " % line[0] ) ) )
                if isinstance(line[1], dict):
                    linestr = repr(line[1])
                elif isinstance(line[1], int) or isinstance(line[1], list):
                    linestr = str(line[1])
                else:
                    linestr = line[1]

                widgets.append( urwid.Padding(urwid.Text( linestr + "\n" ), left=3) )

        self.view.body = urwid.Frame(urwid.ListBox(widgets), header=title)
        self.viewing_info = True
        self.status("Detail View | ESC:Return  Up/Down:Scroll  O:View website")

    def do_info_exit(self):
        if self.viewing_info:
            self.view.body = self.listframe
            self.viewing_info = False
            self.status("Ready.")

    def do_neweps(self):
        try:
            shows = self.engine.scan_library(rescan=True)
            self._rebuild_lists(self.engine.mediainfo['status_start'])

            self.status("Ready.")
        except utils.TrackmaError as e:
            self.error(e)

    def do_quit(self):
        self.engine.unload()
        raise urwid.ExitMainLoop()

    def addsearch_request(self, data):
        self.ask_finish(self.addsearch_request)
        if data:
            try:
                shows = self.engine.search(data)
            except utils.TrackmaError as e:
                self.error(e)
                return

            if len(shows) > 0:
                self.status("Ready.")
                self.dialog = AddDialog(self.mainloop, self.engine, showlist=shows, width=('relative', 80))
                urwid.connect_signal(self.dialog, 'done', self.addsearch_do)
                self.dialog.show()
            else:
                self.status("No results.")

    def addsearch_do(self, show):
        self.dialog.close()
        # Add show as current status
        _filter = self.filters_nums[self.cur_filter]
        try:
            self.engine.add_show(show, _filter)
        except utils.TrackmaError as e:
            self.error(e)

    def delete_request(self, data):
        self.ask_finish(self.delete_request)
        if data == 'y':
            showid = self._get_selected_item().showid
            show = self.engine.get_show_info(showid)

            try:
                show = self.engine.delete_show(show)
            except utils.TrackmaError as e:
                self.error(e)

    def status_request(self, widget, data=None):
        self.dialog.close()
        if data is not None:
            item = self._get_selected_item()

            try:
                show = self.engine.set_status(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def reload_request(self, widget, selected, data):
        if selected:
            self.dialog.close()
            self.do_reload_engine(data[0], data[1])

    def update_request(self, data):
        self.ask_finish(self.update_request)
        if data:
            item = self._get_selected_item()

            try:
                show = self.engine.set_episode(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def score_request(self, data):
        self.ask_finish(self.score_request)
        if data:
            item = self._get_selected_item()

            try:
                show = self.engine.set_score(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def altname_request(self, data):
        self.ask_finish(self.altname_request)
        if data:
            item = self._get_selected_item()

            try:
                self.engine.altname(item.showid, data)
                item.update_altname(self.engine.altname(item.showid))
            except utils.TrackmaError as e:
                self.error(e)
                return

    def play_request(self, data):
        self.ask_finish(self.play_request)
        if data:
            item = self._get_selected_item()
            show = self.engine.get_show_info(item.showid)

            try:
                self.engine.play_episode(show, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def prompt_update_request(self, data):
        (show, episode) = self.last_update_prompt
        self.ask_finish(self.prompt_update_request)
        if data == 'y':
            try:
                show = self.engine.set_episode(show['id'], episode)
            except utils.TrackmaError as e:
                self.error(e)
                return
        else:
            self.status('Ready.')

    def prompt_update(self, show, episode):
        self.last_update_prompt = (show, episode)
        self.question("Update %s to episode %d? [y/N] " % (show['title'], episode), self.prompt_update_request)

    def changed_show(self, show, changes=None):
        if self.started and show:
            status = show['my_status']
            self.lists[status].body.update_show(show)
            self.mainloop.draw_screen()

    def changed_show_status(self, show, old_status=None):
        self._rebuild_lists(show['my_status'])
        if old_status is not None:
            self._rebuild_lists(old_status)

        go_filter = 0
        for _filter in self.filters_nums:
            if _filter == show['my_status']:
                break
            go_filter += 1

        self.set_filter(go_filter)
        self._get_cur_list().select_show(show)

    def changed_queue(self, queue):
        self.status_queue.set_text("Q:{}".format(len(queue)))

    def tracker_state(self, state, timer):
        if state == utils.TRACKER_NOVIDEO:
            st = 'LISTEN'
        elif state == utils.TRACKER_PLAYING:
            st = '+{}'.format(timer)
        elif state == utils.TRACKER_UNRECOGNIZED:
            st = 'UNRECOG'
        elif state == utils.TRACKER_NOT_FOUND:
            st = 'NOTFOUN'
        elif state == utils.TRACKER_IGNORED:
            st = 'IGNORE'
        else:
            st = '???'

        self.status_tracker.set_text("T:{}".format(st))
        self.mainloop.draw_screen()

    def tracker_timer(self, timer):
        if timer is not None:
            self.status_tracker.set_text("T:+{}".format(timer))
            self.mainloop.draw_screen()

    def playing_show(self, show, is_playing, episode=None):
        status = show['my_status']
        self.lists[status].body.playing_show(show, is_playing)
        self.mainloop.draw_screen()

    def changed_list(self, show):
        self._rebuild_lists(show['my_status'])

    def ask(self, msg, callback, data=u''):
        self.asker = Asker(msg, str(data))
        self.view.set_footer(urwid.AttrMap(self.asker, 'status'))
        self.view.set_focus('footer')
        urwid.connect_signal(self.asker, 'done', callback)

    def question(self, msg, callback, data=u''):
        self.asker = QuestionAsker(msg, str(data))
        self.view.set_footer(urwid.AttrMap(self.asker, 'status'))
        self.view.set_focus('footer')
        urwid.connect_signal(self.asker, 'done', callback)

    def ask_finish(self, callback):
        self.view.set_focus('body')
        urwid.disconnect_signal(self, self.asker, 'done', callback)
        self.view.set_footer(self.statusbar)

    def do_search(self, key=''):
        if self.last_search:
            text = "Search forward [%s]: " % self.last_search
        else:
            text = "Search forward: "

        self.ask(text, self.search_request, key)
        #urwid.connect_signal(self.asker, 'change', self.search_live)

    #def search_live(self, widget, data):
    #    if data:
    #        self.listwalker.select_match(data)

    def search_request(self, data):
        self.ask_finish(self.search_request)
        if data:
            self.last_search = data
            self._get_cur_list().select_match(data)
        elif self.last_search:
            self._get_cur_list().select_match(self.last_search)
Beispiel #23
0
class APIHandler:
    """
	The class used to communicate with Trackma.
	"""
    comparision = {
        "mal_ID": "id",
        "title": "title",
        "episodes_done": "my_progress",
        "watch_status": "my_status",
        "score": "my_score",
        "_len": "total"
    }
    watch = None
    accs = None
    engine = None
    adList = []
    tList = []

    def __init__(self, accountnum=1):
        """
		Defaults to the first account.
		"""
        self.watch = Watcher()
        self.accs = dict(AccountManager().get_accounts())
        self.engine = Engine(self.accs.get(accountnum))
        self.engine.start()
        self.tList = list(self.engine.get_list())
        with open(self.watch.WATCH_FILE, 'r') as watch_file:
            self.adList = list(json.load(watch_file))
            watch_file.close()
        self._sort_lists()

    def _sort_lists(self, key="mal_ID"):
        """
		Sorts lists for easier comparision.\n
		Called on initializing the class.
		Mutilates the lists.
		"""
        self.adList.sort(key=lambda val: val[key])
        self.tList.sort(key=lambda val: val[self.comparision[key]])

    def _equalize_lists(self, format=False):
        """
		Strips both the lists to the common categories and returns it
		as two sublists with the animedl list being first.
		"""
        tempList = [[], []]

        for i in range(len(self.tList)):
            entry = [{}, {}]
            for cat in self.comparision:

                entry[0][self.comparision[cat]
                         if format else cat] = self.adList[i][cat]

                entry[1][self.comparision[cat] if format else
                         cat] = "planned" if self.tList[i][self.comparision[
                             cat]] == "plan_to_watch" else self.tList[i][
                                 self.comparision[cat]]

            tempList[0].append(entry[0])
            tempList[1].append(entry[1])
        return tempList

    def _stage_changes(self, preference=True):
        """
		Returns the modified items in the animedl list (compares to Trackma).\n
		Replaces it with the Trackma entry, to reverse this
		set preference to False.
		"""
        (sadList, stList) = self._equalize_lists()
        tempList = []
        for i in range(len(stList)):
            if (sadList[i] != stList[i]):
                tempList.append((sadList[i] if preference else stList[i]))
        return tempList

    def add_staged_to_trackma(self):
        """
		Updates the Trackma queue.
		"""
        qList = self._stage_changes()
        for item in qList:
            self.engine.get_show_info(item["mal_ID"])

    """
Beispiel #24
0
class Trackma_urwid():
    """
    Main class for the urwid version of Trackma
    """

    """Main objects"""
    engine = None
    mainloop = None
    cur_sort = 'title'
    sorts_iter = cycle(('my_progress', 'total', 'my_score', 'id', 'title'))
    cur_order = False
    orders_iter = cycle((True, False))
    keymapping = dict()
    positions = list()
    last_search = None
    last_update_prompt = ()

    """Widgets"""
    header = None
    listbox = None
    view = None

    def __init__(self):
        """Creates main widgets and creates mainloop"""

        palette = [
        ('body','', ''),
        ('focus','standout', ''),
        ('head','light red', 'black'),
        ('header','bold', ''),
        ('status', 'white', 'dark blue'),
        ('error', 'light red', 'dark blue'),
        ('window', 'white', 'dark blue'),
        ('button', 'black', 'light gray'),
        ('button hilight', 'white', 'dark red'),
        ('item_airing', 'dark blue', ''),
        ('item_notaired', 'yellow', ''),
        ('item_neweps', 'white', 'brown'),
        ('item_updated', 'white', 'dark green'),
        ('item_playing', 'white', 'dark blue'),
        ('info_title', 'light red', ''),
        ('info_section', 'dark blue', ''),
        ]

        keymap = utils.parse_config(utils.get_root_filename('keymap.json'), utils.keymap_defaults)
        self.keymapping = self.map_key_to_func(keymap)

        sys.stdout.write("\x1b]0;Trackma-curses "+utils.VERSION+"\x07");
        self.header_title = urwid.Text('Trackma-curses ' + utils.VERSION)
        self.header_api = urwid.Text('API:')
        self.header_filter = urwid.Text('Filter:')
        self.header_sort = urwid.Text('Sort:title')
        self.header_order = urwid.Text('Order:d')
        self.header = urwid.AttrMap(urwid.Columns([
            self.header_title,
            ('fixed', 23, self.header_filter),
            ('fixed', 17, self.header_sort),
            ('fixed', 16, self.header_api)]), 'status')


        top_text = keymap['help'] + ':Help  ' + keymap['sort'] +':Sort  ' + \
                   keymap['update'] + ':Update  ' + keymap['play'] + ':Play  ' + \
                   keymap['status'] + ':Status  ' + keymap['score'] + ':Score  ' + \
                   keymap['quit'] + ':Quit'
        self.top_pile = urwid.Pile([self.header,
            urwid.AttrMap(urwid.Text(top_text), 'status')
        ])

        self.statusbar = urwid.AttrMap(urwid.Text('Trackma-curses '+utils.VERSION), 'status')

        self.listheader = urwid.AttrMap(
            urwid.Columns([
                ('weight', 1, urwid.Text('Title')),
                ('fixed', 10, urwid.Text('Progress')),
                ('fixed', 7, urwid.Text('Score')),
            ]), 'header')

        self.listwalker = ShowWalker([])
        self.listbox = urwid.ListBox(self.listwalker)
        self.listframe = urwid.Frame(self.listbox, header=self.listheader)

        self.viewing_info = False

        self.view = urwid.Frame(self.listframe, header=self.top_pile, footer=self.statusbar)
        self.mainloop = urwid.MainLoop(self.view, palette, unhandled_input=self.keystroke, screen=urwid.raw_display.Screen())

        self.mainloop.set_alarm_in(0, self.do_switch_account)
        self.mainloop.run()

    def map_key_to_func(self, keymap):
        keymapping = dict()
        funcmap = { 'help': self.do_help,
                    'prev_filter': self.do_prev_filter,
                    'next_filter': self.do_next_filter,
                    'sort': self.do_sort,
                    'sort_order': self.change_sort_order,
                    'update': self.do_update,
                    'play': self.do_play,
                    'status': self.do_status,
                    'score': self.do_score,
                    'send': self.do_send,
                    'retrieve': self.do_retrieve,
                    'addsearch': self.do_addsearch,
                    'reload': self.do_reload,
                    'switch_account': self.do_switch_account,
                    'delete': self.do_delete,
                    'quit': self.do_quit,
                    'altname': self.do_altname,
                    'search': self.do_search,
                    'neweps': self.do_neweps,
                    'details': self.do_info,
                    'details_exit': self.do_info_exit,
                    'open_web': self.do_open_web,
                    }

        for key, value in keymap.items():
            try:
                keymapping.update({value: funcmap[key]})
            except KeyError:
                # keymap.json requested an action not available in funcmap
                pass
        return keymapping

    def _rebuild(self):
        self.header_api.set_text('API:%s' % self.engine.api_info['name'])
        self.lists = dict()
        self.filters = self.engine.mediainfo['statuses_dict']
        self.filters_nums = self.engine.mediainfo['statuses']

        for status in self.filters_nums:
            self.lists[status] = urwid.ListBox(ShowWalker([]))

        self._rebuild_lists()
        self.set_filter(0)

        self.status('Ready.')
        self.started = True

    def _rebuild_lists(self, status=None):
        if status:
            self.lists[status].body[:] = []
            showlist = self.engine.filter_list(status)
        else:
            for _status in self.lists.keys():
                self.lists[_status].body[:] = []
            showlist = self.engine.get_list()

        library = self.engine.library()
        sortedlist = sorted(showlist, key=itemgetter(self.cur_sort), reverse=self.cur_order)

        for show in sortedlist:
            if show['my_status'] == self.engine.mediainfo['status_start']:
                item = ShowItem(show, self.engine.mediainfo['has_progress'], self.engine.altname(show['id']), library.get(show['id']))
            else:
                item = ShowItem(show, self.engine.mediainfo['has_progress'], self.engine.altname(show['id']))

            self.lists[show['my_status']].body.append(item)

    def start(self, account):
        """Starts the engine"""
        # Engine configuration
        self.started = False

        self.status("Starting engine...")
        self.engine = Engine(account, self.message_handler)
        self.engine.connect_signal('episode_changed', self.changed_show)
        self.engine.connect_signal('score_changed', self.changed_show)
        self.engine.connect_signal('status_changed', self.changed_show_status)
        self.engine.connect_signal('playing', self.playing_show)
        self.engine.connect_signal('show_added', self.changed_list)
        self.engine.connect_signal('show_deleted', self.changed_list)
        self.engine.connect_signal('show_synced', self.changed_show)
        self.engine.connect_signal('prompt_for_update', self.prompt_update)

        # Engine start and list rebuildi
        self.status("Building lists...")
        self.engine.start()
        self._rebuild()

    def set_filter(self, filter_num):
        self.cur_filter = filter_num
        _filter = self.filters_nums[self.cur_filter]
        self.header_filter.set_text("Filter:%s" % self.filters[_filter])

        self.listframe.body = self.lists[_filter]

    def _get_cur_list(self):
        _filter = self.filters_nums[self.cur_filter]
        return self.lists[_filter].body

    def _get_selected_item(self):
        return self._get_cur_list().get_focus()[0]

    def status(self, msg):
        self.statusbar.base_widget.set_text(msg)

    def error(self, msg):
        self.statusbar.base_widget.set_text([('error', "Error: %s" % msg)])

    def message_handler(self, classname, msgtype, msg):
        if msgtype != messenger.TYPE_DEBUG:
            try:
                self.status(msg)
                self.mainloop.draw_screen()
            except AssertionError:
                print(msg)

    def keystroke(self, input):
        try:
            self.keymapping[input]()
        except KeyError:
            # Unbinded key pressed; do nothing
            pass

    def do_switch_account(self, loop=None, data=None):
        manager = AccountManager()

        if self.engine is None:
            if manager.get_default():
                self.start(manager.get_default())
            else:
                self.dialog = AccountDialog(self.mainloop, manager, False)
                urwid.connect_signal(self.dialog, 'done', self.start)
        else:
            self.dialog = AccountDialog(self.mainloop, manager, True)
            urwid.connect_signal(self.dialog, 'done', self.do_reload_engine)

    def do_addsearch(self):
        self.ask('Search on remote: ', self.addsearch_request)

    def do_delete(self):
        self.question('Delete selected show? [y/n] ', self.delete_request)

    def do_prev_filter(self):
        if self.cur_filter > 0:
            self.set_filter(self.cur_filter - 1)

    def do_next_filter(self):
        if self.cur_filter < len(self.filters)-1:
            self.set_filter(self.cur_filter + 1)

    def do_sort(self):
        self.status("Sorting...")
        _sort = next(self.sorts_iter)
        self.cur_sort = _sort
        self.header_sort.set_text("Sort:%s" % _sort)
        self._rebuild_lists()
        self.status("Ready.")

    def change_sort_order(self):
        self.status("Sorting...")
        _order = next(self.orders_iter)
        self.cur_order = _order
        self._rebuild_lists()
        self.status("Ready.")

    def do_update(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        self.ask('[Update] Episode # to update to: ', self.update_request, show['my_progress'])

    def do_play(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        self.ask('[Play] Episode # to play: ', self.play_request, show['my_progress']+1)

    def do_send(self):
        self.engine.list_upload()
        self.status("Ready.")

    def do_retrieve(self):
        try:
            self.engine.list_download()
            self._rebuild_lists()
            self.status("Ready.")
        except utils.TrackmaError as e:
            self.error(e)

    def do_help(self):
        helptext = "Trackma-curses "+utils.VERSION+"  by z411 ([email protected])\n\n"
        helptext += "Trackma is an open source client for media tracking websites.\n"
        helptext += "http://github.com/z411/trackma\n\n"
        helptext += "This program is licensed under the GPLv3,\nfor more information read COPYING file.\n\n"
        helptext += "More controls:\n  Left/Right:Change Filter\n  /:Search\n  a:Add\n  c:Change API/Mediatype\n"
        helptext += "  d:Delete\n  s:Send changes\n  r:Change sort order\n  R:Retrieve list\n  Enter: View details\n  O: Open website\n  A:Set alternative title\n  N:Search for new episodes\n  F9: Change account"
        ok_button = urwid.Button('OK', self.help_close)
        ok_button_wrap = urwid.Padding(urwid.AttrMap(ok_button, 'button', 'button hilight'), 'center', 6)
        pile = urwid.Pile([urwid.Text(helptext), ok_button_wrap])
        self.dialog = Dialog(pile, self.mainloop, width=62, title='About/Help')
        self.dialog.show()

    def help_close(self, widget):
        self.dialog.close()

    def do_altname(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        self.status(show['title'])
        self.ask('[Altname] New alternative name: ', self.altname_request, self.engine.altname(showid))

    def do_score(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        self.ask('[Score] Score to change to: ', self.score_request, show['my_score'])

    def do_status(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)

        buttons = list()
        num = 1
        selected = 1
        title = urwid.Text('Choose status:')
        title.align = 'center'
        buttons.append(title)
        for status in self.filters_nums:
            name = self.filters[status]
            button = urwid.Button(name, self.status_request, status)
            button._label.align = 'center'
            buttons.append(urwid.AttrMap(button, 'button', 'button hilight'))
            if status == show['my_status']:
                selected = num
            num += 1
        pile = urwid.Pile(buttons)
        pile.set_focus(selected)
        self.dialog = Dialog(pile, self.mainloop, width=22)
        self.dialog.show()

    def do_reload(self):
        # Create a list of buttons to select the mediatype
        rb_mt = []
        mediatypes = []
        for mediatype in self.engine.api_info['supported_mediatypes']:
            but = urwid.RadioButton(rb_mt, mediatype)
            # Make it selected if it's the current mediatype
            if self.engine.api_info['mediatype'] == mediatype:
                but.set_state(True)
            urwid.connect_signal(but, 'change', self.reload_request, [None, mediatype])
            mediatypes.append(urwid.AttrMap(but, 'button', 'button hilight'))
        mediatype = urwid.Columns([urwid.Text('Mediatype:'), urwid.Pile(mediatypes)])

        #main_pile = urwid.Pile([mediatype, urwid.Divider(), api])
        self.dialog = Dialog(mediatype, self.mainloop, width=30, title='Change media type')
        self.dialog.show()

    def do_reload_engine(self, account=None, mediatype=None):
        self.started = False
        self.engine.reload(account, mediatype)
        self._rebuild()

    def do_open_web(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        if show['url']:
            webbrowser.open(show['url'], 2, True)

    def do_info(self):
        if self.viewing_info:
            return

        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)

        self.status("Getting show details...")

        try:
            details = self.engine.get_show_details(show)
        except utils.TrackmaError as e:
            self.error(e)
            return

        title = urwid.Text( ('info_title', show['title']), 'center', 'any')
        widgets = []
        for line in details['extra']:
            if line[0] and line[1]:
                widgets.append( urwid.Text( ('info_section', "%s: " % line[0] ) ) )
                if isinstance(line[1], dict):
                    linestr = repr(line[1])
                elif isinstance(line[1], int):
                    linestr = str(line[1])
                else:
                    linestr = line[1]

                widgets.append( urwid.Padding(urwid.Text( linestr + "\n" ), left=3) )

        self.view.body = urwid.Frame(urwid.ListBox(widgets), header=title)
        self.viewing_info = True
        self.status("Detail View | ESC:Return  Up/Down:Scroll  O:View website")

    def do_info_exit(self):
        if self.viewing_info:
            self.view.body = self.listframe
            self.viewing_info = False
            self.status("Ready.")

    def do_neweps(self):
        try:
            shows = self.engine.scan_library()
            self._rebuild_lists(self.engine.mediainfo['status_start'])

            self.status("Ready.")
        except utils.TrackmaError as e:
            self.error(e)

    def do_quit(self):
        self.engine.unload()
        raise urwid.ExitMainLoop()

    def addsearch_request(self, data):
        self.ask_finish(self.addsearch_request)
        if data:
            try:
                shows = self.engine.search(data)
            except utils.TrackmaError as e:
                self.error(e)
                return

            if len(shows) > 0:
                self.status("Ready.")
                self.dialog = AddDialog(self.mainloop, self.engine, showlist=shows, width=('relative', 80))
                urwid.connect_signal(self.dialog, 'done', self.addsearch_do)
                self.dialog.show()
            else:
                self.status("No results.")

    def addsearch_do(self, show):
        self.dialog.close()
        # Add show as current status
        _filter = self.filters_nums[self.cur_filter]
        try:
            self.engine.add_show(show, _filter)
        except utils.TrackmaError as e:
            self.error(e)

    def delete_request(self, data):
        self.ask_finish(self.delete_request)
        if data == 'y':
            showid = self._get_selected_item().showid
            show = self.engine.get_show_info(showid)

            try:
                show = self.engine.delete_show(show)
            except utils.TrackmaError as e:
                self.error(e)

    def status_request(self, widget, data=None):
        self.dialog.close()
        if data is not None:
            item = self._get_selected_item()

            try:
                show = self.engine.set_status(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def reload_request(self, widget, selected, data):
        if selected:
            self.dialog.close()
            self.do_reload_engine(data[0], data[1])

    def update_request(self, data):
        self.ask_finish(self.update_request)
        if data:
            item = self._get_selected_item()

            try:
                show = self.engine.set_episode(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def score_request(self, data):
        self.ask_finish(self.score_request)
        if data:
            item = self._get_selected_item()

            try:
                show = self.engine.set_score(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def altname_request(self, data):
        self.ask_finish(self.altname_request)
        if data:
            item = self._get_selected_item()

            try:
                self.engine.altname(item.showid, data)
                item.update_altname(self.engine.altname(item.showid))
            except utils.TrackmaError as e:
                self.error(e)
                return

    def play_request(self, data):
        self.ask_finish(self.play_request)
        if data:
            item = self._get_selected_item()
            show = self.engine.get_show_info(item.showid)

            try:
                self.engine.play_episode(show, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

            self.status('Ready.')

    def prompt_update_request(self, data):
        (show, episode) = self.last_update_prompt
        self.ask_finish(self.prompt_update_request)
        if data == 'y':
            try:
                show = self.engine.set_episode(show['id'], episode)
            except utils.TrackmaError as e:
                self.error(e)
                return
        else:
            self.status('Ready.')

    def prompt_update(self, show, episode):
        self.last_update_prompt = (show, episode)
        self.question("Update %s to episode %d? [y/N] " % (show['title'], episode), self.prompt_update_request)

    def changed_show(self, show, changes=None):
        if self.started and show:
            status = show['my_status']
            self.lists[status].body.update_show(show)
            self.mainloop.draw_screen()

    def changed_show_status(self, show, old_status=None):
        self._rebuild_lists(show['my_status'])
        if old_status is not None:
            self._rebuild_lists(old_status)

        go_filter = 0
        for _filter in self.filters_nums:
            if _filter == show['my_status']:
                break
            go_filter += 1

        self.set_filter(go_filter)
        self._get_cur_list().select_show(show)

    def playing_show(self, show, is_playing, episode=None):
        status = show['my_status']
        self.lists[status].body.playing_show(show, is_playing)
        self.mainloop.draw_screen()

    def changed_list(self, show):
        self._rebuild_lists(show['my_status'])

    def ask(self, msg, callback, data=u''):
        self.asker = Asker(msg, str(data))
        self.view.set_footer(urwid.AttrMap(self.asker, 'status'))
        self.view.set_focus('footer')
        urwid.connect_signal(self.asker, 'done', callback)

    def question(self, msg, callback, data=u''):
        self.asker = QuestionAsker(msg, str(data))
        self.view.set_footer(urwid.AttrMap(self.asker, 'status'))
        self.view.set_focus('footer')
        urwid.connect_signal(self.asker, 'done', callback)

    def ask_finish(self, callback):
        self.view.set_focus('body')
        urwid.disconnect_signal(self, self.asker, 'done', callback)
        self.view.set_footer(self.statusbar)

    def do_search(self, key=''):
        if self.last_search:
            text = "Search forward [%s]: " % self.last_search
        else:
            text = "Search forward: "

        self.ask(text, self.search_request, key)
        #urwid.connect_signal(self.asker, 'change', self.search_live)

    #def search_live(self, widget, data):
    #    if data:
    #        self.listwalker.select_match(data)

    def search_request(self, data):
        self.ask_finish(self.search_request)
        if data:
            self.last_search = data
            self._get_cur_list().select_match(data)
        elif self.last_search:
            self._get_cur_list().select_match(self.last_search)
Beispiel #25
0
class Trackma_urwid:
    """
    Main class for the urwid version of Trackma
    """

    """Main objects"""
    engine = None
    mainloop = None
    cur_sort = "title"
    sorts_iter = cycle(("my_progress", "total", "my_score", "id", "title"))
    cur_order = False
    orders_iter = cycle((True, False))
    keymapping = dict()
    positions = list()
    last_search = None
    last_update_prompt = ()

    """Widgets"""
    header = None
    listbox = None
    view = None

    def __init__(self):
        """Creates main widgets and creates mainloop"""
        self.config = utils.parse_config(utils.get_root_filename("ui-curses.json"), utils.curses_defaults)
        keymap = self.config["keymap"]
        self.keymap_str = self.get_keymap_str(keymap)
        self.keymapping = self.map_key_to_func(keymap)

        palette = []
        for k, color in self.config["palette"].items():
            palette.append((k, color[0], color[1]))

        sys.stdout.write("\x1b]0;Trackma-curses " + utils.VERSION + "\x07")
        self.header_title = urwid.Text("Trackma-curses " + utils.VERSION)
        self.header_api = urwid.Text("API:")
        self.header_filter = urwid.Text("Filter:")
        self.header_sort = urwid.Text("Sort:title")
        self.header_order = urwid.Text("Order:d")
        self.header = urwid.AttrMap(
            urwid.Columns(
                [
                    self.header_title,
                    ("fixed", 23, self.header_filter),
                    ("fixed", 17, self.header_sort),
                    ("fixed", 16, self.header_api),
                ]
            ),
            "status",
        )

        top_pile = [self.header]

        if self.config["show_help"]:
            top_text = (
                "{help}:Help  {sort}:Sort  "
                + "{update}:Update  {play}:Play  "
                + "{status}:Status  {score}:Score  "
                + "{quit}:Quit"
            )
            top_text = top_text.format(**self.keymap_str)
            top_pile.append(urwid.AttrMap(urwid.Text(top_text), "status"))

        self.top_pile = urwid.Pile(top_pile)
        self.statusbar = urwid.AttrMap(urwid.Text("Trackma-curses " + utils.VERSION), "status")

        self.listheader = urwid.AttrMap(
            urwid.Columns(
                [
                    ("weight", 1, urwid.Text("Title")),
                    ("fixed", 10, urwid.Text("Progress")),
                    ("fixed", 7, urwid.Text("Score")),
                ]
            ),
            "header",
        )

        self.listwalker = ShowWalker([])
        self.listbox = urwid.ListBox(self.listwalker)
        self.listframe = urwid.Frame(self.listbox, header=self.listheader)

        self.viewing_info = False

        self.view = urwid.Frame(self.listframe, header=self.top_pile, footer=self.statusbar)
        self.mainloop = urwid.MainLoop(
            self.view, palette, unhandled_input=self.keystroke, screen=urwid.raw_display.Screen()
        )

        self.mainloop.set_alarm_in(0, self.do_switch_account)
        self.mainloop.run()

    def map_key_to_func(self, keymap):
        keymapping = dict()
        funcmap = {
            "help": self.do_help,
            "prev_filter": self.do_prev_filter,
            "next_filter": self.do_next_filter,
            "sort": self.do_sort,
            "sort_order": self.change_sort_order,
            "update": self.do_update,
            "play": self.do_play,
            "status": self.do_status,
            "score": self.do_score,
            "send": self.do_send,
            "retrieve": self.do_retrieve,
            "addsearch": self.do_addsearch,
            "reload": self.do_reload,
            "switch_account": self.do_switch_account,
            "delete": self.do_delete,
            "quit": self.do_quit,
            "altname": self.do_altname,
            "search": self.do_search,
            "neweps": self.do_neweps,
            "details": self.do_info,
            "details_exit": self.do_info_exit,
            "open_web": self.do_open_web,
        }

        for func, keybind in keymap.items():
            try:
                if isinstance(keybind, list):
                    for keybindm in keybind:
                        keymapping[keybindm] = funcmap[func]
                else:
                    keymapping[keybind] = funcmap[func]
            except KeyError:
                # keymap.json requested an action not available in funcmap
                pass
        return keymapping

    def get_keymap_str(self, keymap):
        stringed = {}
        for k, keybind in keymap.items():
            if isinstance(keybind, list):
                stringed[k] = ",".join(keybind)
            else:
                stringed[k] = keybind
        return stringed

    def _rebuild(self):
        self.header_api.set_text("API:%s" % self.engine.api_info["name"])
        self.lists = dict()
        self.filters = self.engine.mediainfo["statuses_dict"]
        self.filters_nums = self.engine.mediainfo["statuses"]

        for status in self.filters_nums:
            self.lists[status] = urwid.ListBox(ShowWalker([]))

        self._rebuild_lists()
        self.set_filter(0)

        self.status("Ready.")
        self.started = True

    def _rebuild_lists(self, status=None):
        if status:
            self.lists[status].body[:] = []
            showlist = self.engine.filter_list(status)
        else:
            for _status in self.lists.keys():
                self.lists[_status].body[:] = []
            showlist = self.engine.get_list()

        library = self.engine.library()
        sortedlist = sorted(showlist, key=itemgetter(self.cur_sort), reverse=self.cur_order)

        for show in sortedlist:
            if show["my_status"] == self.engine.mediainfo["status_start"]:
                item = ShowItem(
                    show,
                    self.engine.mediainfo["has_progress"],
                    self.engine.altname(show["id"]),
                    library.get(show["id"]),
                )
            else:
                item = ShowItem(show, self.engine.mediainfo["has_progress"], self.engine.altname(show["id"]))

            self.lists[show["my_status"]].body.append(item)

    def start(self, account):
        """Starts the engine"""
        # Engine configuration
        self.started = False

        self.status("Starting engine...")
        self.engine = Engine(account, self.message_handler)
        self.engine.connect_signal("episode_changed", self.changed_show)
        self.engine.connect_signal("score_changed", self.changed_show)
        self.engine.connect_signal("status_changed", self.changed_show_status)
        self.engine.connect_signal("playing", self.playing_show)
        self.engine.connect_signal("show_added", self.changed_list)
        self.engine.connect_signal("show_deleted", self.changed_list)
        self.engine.connect_signal("show_synced", self.changed_show)
        self.engine.connect_signal("prompt_for_update", self.prompt_update)

        # Engine start and list rebuildi
        self.status("Building lists...")
        self.engine.start()
        self._rebuild()

    def set_filter(self, filter_num):
        self.cur_filter = filter_num
        _filter = self.filters_nums[self.cur_filter]
        self.header_filter.set_text("Filter:%s" % self.filters[_filter])

        self.listframe.body = self.lists[_filter]

    def _get_cur_list(self):
        _filter = self.filters_nums[self.cur_filter]
        return self.lists[_filter].body

    def _get_selected_item(self):
        return self._get_cur_list().get_focus()[0]

    def status(self, msg):
        self.statusbar.base_widget.set_text(msg)

    def error(self, msg):
        self.statusbar.base_widget.set_text([("error", "Error: %s" % msg)])

    def message_handler(self, classname, msgtype, msg):
        if msgtype != messenger.TYPE_DEBUG:
            try:
                self.status(msg)
                self.mainloop.draw_screen()
            except AssertionError:
                print(msg)

    def keystroke(self, input):
        try:
            self.keymapping[input]()
        except KeyError:
            # Unbinded key pressed; do nothing
            pass

    def do_switch_account(self, loop=None, data=None):
        manager = AccountManager()

        if self.engine is None:
            if manager.get_default():
                self.start(manager.get_default())
            else:
                self.dialog = AccountDialog(self.mainloop, manager, False)
                urwid.connect_signal(self.dialog, "done", self.start)
        else:
            self.dialog = AccountDialog(self.mainloop, manager, True)
            urwid.connect_signal(self.dialog, "done", self.do_reload_engine)

    def do_addsearch(self):
        self.ask("Search on remote: ", self.addsearch_request)

    def do_delete(self):
        self.question("Delete selected show? [y/n] ", self.delete_request)

    def do_prev_filter(self):
        if self.cur_filter > 0:
            self.set_filter(self.cur_filter - 1)

    def do_next_filter(self):
        if self.cur_filter < len(self.filters) - 1:
            self.set_filter(self.cur_filter + 1)

    def do_sort(self):
        self.status("Sorting...")
        _sort = next(self.sorts_iter)
        self.cur_sort = _sort
        self.header_sort.set_text("Sort:%s" % _sort)
        self._rebuild_lists()
        self.status("Ready.")

    def change_sort_order(self):
        self.status("Sorting...")
        _order = next(self.orders_iter)
        self.cur_order = _order
        self._rebuild_lists()
        self.status("Ready.")

    def do_update(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        self.ask("[Update] Episode # to update to: ", self.update_request, show["my_progress"])

    def do_play(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        self.ask("[Play] Episode # to play: ", self.play_request, show["my_progress"] + 1)

    def do_send(self):
        self.engine.list_upload()
        self.status("Ready.")

    def do_retrieve(self):
        try:
            self.engine.list_download()
            self._rebuild_lists()
            self.status("Ready.")
        except utils.TrackmaError as e:
            self.error(e)

    def do_help(self):
        helptext = "Trackma-curses " + utils.VERSION + "  by z411 ([email protected])\n\n"
        helptext += "Trackma is an open source client for media tracking websites.\n"
        helptext += "http://github.com/z411/trackma\n\n"
        helptext += "This program is licensed under the GPLv3,\nfor more information read COPYING file.\n\n"
        helptext += "More controls:\n  {prev_filter}/{next_filter}:Change Filter\n  {search}:Search\n  {addsearch}:Add\n  {reload}:Change API/Mediatype\n"
        helptext += "  {delete}:Delete\n  {send}:Send changes\n  {sort_order}:Change sort order\n  {retrieve}:Retrieve list\n  {details}: View details\n  {open_web}: Open website\n  {altname}:Set alternative title\n  {neweps}:Search for new episodes\n  {switch_account}: Change account"
        helptext = helptext.format(**self.keymap_str)
        ok_button = urwid.Button("OK", self.help_close)
        ok_button_wrap = urwid.Padding(urwid.AttrMap(ok_button, "button", "button hilight"), "center", 6)
        pile = urwid.Pile([urwid.Text(helptext), ok_button_wrap])
        self.dialog = Dialog(pile, self.mainloop, width=62, title="About/Help")
        self.dialog.show()

    def help_close(self, widget):
        self.dialog.close()

    def do_altname(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        self.status(show["title"])
        self.ask("[Altname] New alternative name: ", self.altname_request, self.engine.altname(showid))

    def do_score(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        self.ask("[Score] Score to change to: ", self.score_request, show["my_score"])

    def do_status(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)

        buttons = list()
        num = 1
        selected = 1
        title = urwid.Text("Choose status:")
        title.align = "center"
        buttons.append(title)
        for status in self.filters_nums:
            name = self.filters[status]
            button = urwid.Button(name, self.status_request, status)
            button._label.align = "center"
            buttons.append(urwid.AttrMap(button, "button", "button hilight"))
            if status == show["my_status"]:
                selected = num
            num += 1
        pile = urwid.Pile(buttons)
        pile.set_focus(selected)
        self.dialog = Dialog(pile, self.mainloop, width=22)
        self.dialog.show()

    def do_reload(self):
        # Create a list of buttons to select the mediatype
        rb_mt = []
        mediatypes = []
        for mediatype in self.engine.api_info["supported_mediatypes"]:
            but = urwid.RadioButton(rb_mt, mediatype)
            # Make it selected if it's the current mediatype
            if self.engine.api_info["mediatype"] == mediatype:
                but.set_state(True)
            urwid.connect_signal(but, "change", self.reload_request, [None, mediatype])
            mediatypes.append(urwid.AttrMap(but, "button", "button hilight"))
        mediatype = urwid.Columns([urwid.Text("Mediatype:"), urwid.Pile(mediatypes)])

        # main_pile = urwid.Pile([mediatype, urwid.Divider(), api])
        self.dialog = Dialog(mediatype, self.mainloop, width=30, title="Change media type")
        self.dialog.show()

    def do_reload_engine(self, account=None, mediatype=None):
        self.started = False
        self.engine.reload(account, mediatype)
        self._rebuild()

    def do_open_web(self):
        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)
        if show["url"]:
            webbrowser.open(show["url"], 2, True)

    def do_info(self):
        if self.viewing_info:
            return

        showid = self._get_selected_item().showid
        show = self.engine.get_show_info(showid)

        self.status("Getting show details...")

        try:
            details = self.engine.get_show_details(show)
        except utils.TrackmaError as e:
            self.error(e)
            return

        title = urwid.Text(("info_title", show["title"]), "center", "any")
        widgets = []
        for line in details["extra"]:
            if line[0] and line[1]:
                widgets.append(urwid.Text(("info_section", "%s: " % line[0])))
                if isinstance(line[1], dict):
                    linestr = repr(line[1])
                elif isinstance(line[1], int) or isinstance(line[1], list):
                    linestr = str(line[1])
                else:
                    linestr = line[1]

                widgets.append(urwid.Padding(urwid.Text(linestr + "\n"), left=3))

        self.view.body = urwid.Frame(urwid.ListBox(widgets), header=title)
        self.viewing_info = True
        self.status("Detail View | ESC:Return  Up/Down:Scroll  O:View website")

    def do_info_exit(self):
        if self.viewing_info:
            self.view.body = self.listframe
            self.viewing_info = False
            self.status("Ready.")

    def do_neweps(self):
        try:
            shows = self.engine.scan_library(rescan=True)
            self._rebuild_lists(self.engine.mediainfo["status_start"])

            self.status("Ready.")
        except utils.TrackmaError as e:
            self.error(e)

    def do_quit(self):
        self.engine.unload()
        raise urwid.ExitMainLoop()

    def addsearch_request(self, data):
        self.ask_finish(self.addsearch_request)
        if data:
            try:
                shows = self.engine.search(data)
            except utils.TrackmaError as e:
                self.error(e)
                return

            if len(shows) > 0:
                self.status("Ready.")
                self.dialog = AddDialog(self.mainloop, self.engine, showlist=shows, width=("relative", 80))
                urwid.connect_signal(self.dialog, "done", self.addsearch_do)
                self.dialog.show()
            else:
                self.status("No results.")

    def addsearch_do(self, show):
        self.dialog.close()
        # Add show as current status
        _filter = self.filters_nums[self.cur_filter]
        try:
            self.engine.add_show(show, _filter)
        except utils.TrackmaError as e:
            self.error(e)

    def delete_request(self, data):
        self.ask_finish(self.delete_request)
        if data == "y":
            showid = self._get_selected_item().showid
            show = self.engine.get_show_info(showid)

            try:
                show = self.engine.delete_show(show)
            except utils.TrackmaError as e:
                self.error(e)

    def status_request(self, widget, data=None):
        self.dialog.close()
        if data is not None:
            item = self._get_selected_item()

            try:
                show = self.engine.set_status(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def reload_request(self, widget, selected, data):
        if selected:
            self.dialog.close()
            self.do_reload_engine(data[0], data[1])

    def update_request(self, data):
        self.ask_finish(self.update_request)
        if data:
            item = self._get_selected_item()

            try:
                show = self.engine.set_episode(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def score_request(self, data):
        self.ask_finish(self.score_request)
        if data:
            item = self._get_selected_item()

            try:
                show = self.engine.set_score(item.showid, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

    def altname_request(self, data):
        self.ask_finish(self.altname_request)
        if data:
            item = self._get_selected_item()

            try:
                self.engine.altname(item.showid, data)
                item.update_altname(self.engine.altname(item.showid))
            except utils.TrackmaError as e:
                self.error(e)
                return

    def play_request(self, data):
        self.ask_finish(self.play_request)
        if data:
            item = self._get_selected_item()
            show = self.engine.get_show_info(item.showid)

            try:
                self.engine.play_episode(show, data)
            except utils.TrackmaError as e:
                self.error(e)
                return

            self.status("Ready.")

    def prompt_update_request(self, data):
        (show, episode) = self.last_update_prompt
        self.ask_finish(self.prompt_update_request)
        if data == "y":
            try:
                show = self.engine.set_episode(show["id"], episode)
            except utils.TrackmaError as e:
                self.error(e)
                return
        else:
            self.status("Ready.")

    def prompt_update(self, show, episode):
        self.last_update_prompt = (show, episode)
        self.question("Update %s to episode %d? [y/N] " % (show["title"], episode), self.prompt_update_request)

    def changed_show(self, show, changes=None):
        if self.started and show:
            status = show["my_status"]
            self.lists[status].body.update_show(show)
            self.mainloop.draw_screen()

    def changed_show_status(self, show, old_status=None):
        self._rebuild_lists(show["my_status"])
        if old_status is not None:
            self._rebuild_lists(old_status)

        go_filter = 0
        for _filter in self.filters_nums:
            if _filter == show["my_status"]:
                break
            go_filter += 1

        self.set_filter(go_filter)
        self._get_cur_list().select_show(show)

    def playing_show(self, show, is_playing, episode=None):
        status = show["my_status"]
        self.lists[status].body.playing_show(show, is_playing)
        self.mainloop.draw_screen()

    def changed_list(self, show):
        self._rebuild_lists(show["my_status"])

    def ask(self, msg, callback, data=u""):
        self.asker = Asker(msg, str(data))
        self.view.set_footer(urwid.AttrMap(self.asker, "status"))
        self.view.set_focus("footer")
        urwid.connect_signal(self.asker, "done", callback)

    def question(self, msg, callback, data=u""):
        self.asker = QuestionAsker(msg, str(data))
        self.view.set_footer(urwid.AttrMap(self.asker, "status"))
        self.view.set_focus("footer")
        urwid.connect_signal(self.asker, "done", callback)

    def ask_finish(self, callback):
        self.view.set_focus("body")
        urwid.disconnect_signal(self, self.asker, "done", callback)
        self.view.set_footer(self.statusbar)

    def do_search(self, key=""):
        if self.last_search:
            text = "Search forward [%s]: " % self.last_search
        else:
            text = "Search forward: "

        self.ask(text, self.search_request, key)
        # urwid.connect_signal(self.asker, 'change', self.search_live)

    # def search_live(self, widget, data):
    #    if data:
    #        self.listwalker.select_match(data)

    def search_request(self, data):
        self.ask_finish(self.search_request)
        if data:
            self.last_search = data
            self._get_cur_list().select_match(data)
        elif self.last_search:
            self._get_cur_list().select_match(self.last_search)
Beispiel #26
0
class TrackmaWindow(Gtk.ApplicationWindow):
    __gtype_name__ = 'TrackmaWindow'

    btn_appmenu = GtkTemplate.Child()
    btn_mediatype = GtkTemplate.Child()

    _config = None
    show_lists = dict()
    image_thread = None
    close_thread = None
    hidden = False
    quit = False

    statusicon = None

    def __init__(self, debug=False):
        Gtk.ApplicationWindow.__init__(self)
        self.init_template()

        self._debug = debug
        self._configfile = utils.to_config_path('ui-Gtk.json')
        self._config = utils.parse_config(self._configfile, utils.gtk_defaults)

        self._main_view = None
        self._account = None
        self._engine = None

        self._init_widgets()
        self.present()

    def main(self):
        """Start the Account Selector"""
        manager = AccountManager()

        # Use the remembered account if there's one
        if manager.get_default():
            self._create_engine(manager.get_default())
        else:
            self._show_accounts(switch=False)

    def _init_widgets(self):
        Gtk.Window.set_default_icon_from_file(utils.DATADIR + '/icon.png')
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_title('Trackma-gtk ' + utils.VERSION)

        if self._config['remember_geometry']:
            self.resize(self._config['last_width'],
                        self._config['last_height'])

        if not self._main_view:
            self._main_view = MainView(self._config)
            self._main_view.connect('error', self._on_main_view_error)
            self._main_view.connect('error-fatal',
                                    self._on_main_view_error_fatal)
            self._main_view.connect('show-action', self._on_show_action)
            self.add(self._main_view)

        self.connect('delete_event', self._on_delete_event)
        self.connect('destroy', self._on_destroy)

        # Status icon
        if tray_available:
            self.statusicon = Gtk.StatusIcon()
            self.statusicon.set_from_file(utils.DATADIR + '/icon.png')
            self.statusicon.set_tooltip_text('Trackma-gtk ' + utils.VERSION)
            self.statusicon.connect('activate', self._tray_status_event)
            self.statusicon.connect('popup-menu', self._tray_status_menu_event)
            if self._config['show_tray']:
                self.statusicon.set_visible(True)
            else:
                self.statusicon.set_visible(False)

    def _on_delete_event(self, widget, event, data=None):
        if self.statusicon and self.statusicon.get_visible(
        ) and self._config['close_to_tray']:
            self.hidden = True
            self.hide()
        else:
            self._quit()
        return True

    def _on_destroy(self, widget):
        if self.quit:
            Gtk.main_quit()

    def _create_engine(self, account):
        self._engine = Engine(account, self._message_handler)

        self._main_view.load_engine_account(self._engine, account)
        self._set_actions()
        self._set_mediatypes_menu()
        self._update_widgets(account)

    def _set_actions(self):
        builder = Gtk.Builder.new_from_file(
            os.path.join(gtk_dir, 'data/app-menu.ui'))
        self.btn_appmenu.set_menu_model(builder.get_object('app-menu'))

        def add_action(name, callback):
            action = Gio.SimpleAction.new(name, None)
            action.connect('activate', callback)
            self.add_action(action)

        add_action('search', self._on_search)
        add_action('syncronize', self._on_synchronize)
        add_action('upload', self._on_upload)
        add_action('download', self._on_download)
        add_action('scanfiles', self._on_scanfiles)
        add_action('accounts', self._on_accounts)
        add_action('preferences', self._on_preferences)
        add_action('about', self._on_about)
        add_action('quit', self._on_quit)

    def _set_mediatypes_action(self):
        action_name = 'change-mediatype'
        if self.has_action(action_name):
            self.remove_action(action_name)

        state = GLib.Variant.new_string(self._engine.api_info['mediatype'])
        action = Gio.SimpleAction.new_stateful(action_name, state.get_type(),
                                               state)
        action.connect('change-state', self._on_change_mediatype)
        self.add_action(action)

    def _set_mediatypes_menu(self):
        self._set_mediatypes_action()
        menu = Gio.Menu()

        for mediatype in self._engine.api_info['supported_mediatypes']:
            variant = GLib.Variant.new_string(mediatype)
            menu_item = Gio.MenuItem()
            menu_item.set_label(mediatype)
            menu_item.set_action_and_target_value('win.change-mediatype',
                                                  variant)
            menu.append_item(menu_item)

        self.btn_mediatype.set_menu_model(menu)

        if len(self._engine.api_info['supported_mediatypes']) <= 1:
            self.btn_mediatype.hide()

    def _update_widgets(self, account):
        current_api = utils.available_libs[account['api']]
        api_iconpath = 1
        api_iconfile = current_api[api_iconpath]

        self.set_title('Trackma-gtk %s [%s (%s)]' %
                       (utils.VERSION, self._engine.api_info['name'],
                        self._engine.api_info['mediatype']))

        if self.statusicon and self._config['tray_api_icon']:
            self.statusicon.set_from_file(api_iconfile)

        # Don't show the main dialog if start in tray option is set
        if self.statusicon and self._config['show_tray'] and self._config[
                'start_in_tray']:
            self.hidden = True
        else:
            self.show()

    def _on_change_mediatype(self, action, value):
        action.set_state(value)
        mediatype = value.get_string()
        self._main_view.load_account_mediatype(None, mediatype)

    def _on_search(self, action, param):
        current_status = self._main_view.get_current_status()
        win = SearchWindow(self._engine,
                           self._config['colors'],
                           current_status,
                           transient_for=self)
        win.connect('search-error', self._on_search_error)
        win.show_all()

    def _on_search_error(self, search_window, error_msg):
        print(error_msg)

    def _on_synchronize(self, action, param):
        threading.Thread(target=self._synchronization_task,
                         args=(True, True)).start()

    def _on_upload(self, action, param):
        threading.Thread(target=self._synchronization_task,
                         args=(True, False)).start()

    def _on_download(self, action, param):
        def _download_lists():
            threading.Thread(target=self._synchronization_task,
                             args=(False, True)).start()

        def _on_download_response(_dialog, response):
            _dialog.destroy()

            if response == Gtk.ResponseType.YES:
                _download_lists()

        queue = self._engine.get_queue()
        if not queue:
            dialog = Gtk.MessageDialog(
                self, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION,
                Gtk.ButtonsType.YES_NO,
                "There are %d queued changes in your list. If you retrieve the remote list now you will lose your queued changes. Are you sure you want to continue?"
                % len(queue))
            dialog.show_all()
            dialog.connect("response", _on_download_response)
        else:
            # If the user doesn't have any queued changes
            # just go ahead
            _download_lists()

    def _synchronization_task(self, send, retrieve):
        self._main_view.set_buttons_sensitive_idle(False)

        try:
            if send:
                self._engine.list_upload()
            if retrieve:
                self._engine.list_download()

            # GLib.idle_add(self._set_score_ranges)
            GLib.idle_add(self._main_view.populate_all_pages)
        except utils.TrackmaError as e:
            self._error_dialog_idle(e)
        except utils.TrackmaFatal as e:
            self._show_accounts_idle(switch=False, forget=True)
            self._error_dialog_idle("Fatal engine error: %s" % e)
            return

        self._main_view.set_status_idle("Ready.")
        self._main_view.set_buttons_sensitive_idle(True)

    def _on_scanfiles(self, action, param):
        threading.Thread(target=self._scanfiles_task).start()

    def _scanfiles_task(self):
        try:
            self._engine.scan_library(rescan=True)
        except utils.TrackmaError as e:
            self._error_dialog_idle(e)

        GLib.idle_add(self._main_view.populate_page,
                      self._engine.mediainfo['status_start'])

        self._main_view.set_status_idle("Ready.")
        self._main_view.set_buttons_sensitive_idle(True)

    def _on_accounts(self, action, param):
        self._show_accounts()

    def _show_accounts_idle(self, switch=True, forget=False):
        GLib.idle_add(self._show_accounts, switch, forget)

    def _show_accounts(self, switch=True, forget=False):
        manager = AccountManager()

        if forget:
            manager.set_default(None)

        def _on_accountsel_cancel(accounts_window):
            Gtk.main_quit()

        accountsel = AccountsWindow(manager, transient_for=self)
        accountsel.connect('account-open', self._on_account_open)

        if not switch:
            accountsel.connect('account-cancel', _on_accountsel_cancel)

    def _on_account_open(self, accounts_window, account_num, remember):
        manager = AccountManager()
        account = manager.get_account(account_num)

        if remember:
            manager.set_default(account_num)
        else:
            manager.set_default(None)

        # Reload the engine if already started,
        # start it otherwise
        if self._engine and self._engine.loaded:
            self._main_view.load_account_mediatype(account, None)
        else:
            self._create_engine(account)

    def _on_preferences(self, action, param):
        win = SettingsWindow(self._engine,
                             self._config,
                             self._configfile,
                             transient_for=self)
        win.show_all()

    def _on_about(self, action, param):
        about = Gtk.AboutDialog(parent=self)
        about.set_program_name("Trackma-gtk")
        about.set_version(utils.VERSION)
        about.set_license_type(Gtk.License.GPL_3_0_ONLY)
        about.set_comments(
            "Trackma is an open source client for media tracking websites.\nThanks to all contributors."
        )
        about.set_website("http://github.com/z411/trackma")
        about.set_copyright("© z411, et al.")
        about.set_authors(["See AUTHORS file"])
        about.set_artists(["shuuichi"])
        about.run()
        about.destroy()

    def _on_quit(self, action, param):
        self._quit()

    def _quit(self):
        if self._config['remember_geometry']:
            self._store_geometry()
        if self.close_thread is None:
            self._main_view.set_buttons_sensitive_idle(False)
            self.close_thread = threading.Thread(target=self._unload_task)
            self.close_thread.start()

    def _unload_task(self):
        self._engine.unload()
        self._destroy_idle()

    def _destroy_idle(self):
        GLib.idle_add(self._destroy_push)

    def _destroy_push(self):
        self.quit = True
        self.destroy()

    def _store_geometry(self):
        (width, height) = self.get_size()
        self._config['last_width'] = width
        self._config['last_height'] = height
        utils.save_config(self._config, self._configfile)

    def _message_handler(self, classname, msgtype, msg):
        # Thread safe
        # print("%s: %s" % (classname, msg))
        if msgtype == messenger.TYPE_WARN:
            self._main_view.set_status_idle("%s warning: %s" %
                                            (classname, msg))
        elif msgtype != messenger.TYPE_DEBUG:
            self._main_view.set_status_idle("%s: %s" % (classname, msg))
        elif self._debug:
            print('[D] {}: {}'.format(classname, msg))

    def _on_main_view_error(self, main_view, error_msg):
        self._error_dialog_idle(error_msg)

    def _on_main_view_error_fatal(self, main_view, error_msg):
        self._show_accounts_idle(switch=False, forget=True)
        self._error_dialog_idle(error_msg)

    def _column_toggled(self, w, column_name, visible):
        if visible:
            # Make column visible
            self._config['visible_columns'].append(column_name)

            for view in self.show_lists.values():
                view.cols[column_name].set_visible(True)
        else:
            # Make column invisible
            if len(self._config['visible_columns']) <= 1:
                return  # There should be at least 1 column visible

            self._config['visible_columns'].remove(column_name)
            for view in self.show_lists.values():
                view.cols[column_name].set_visible(False)

        utils.save_config(self._config, self._configfile)

    def _tray_status_event(self, widget):
        # Called when the tray icon is left-clicked
        if self.hidden:
            self.show()
            self.hidden = False
        else:
            self.hide()
            self.hidden = True

    def _tray_status_menu_event(self, icon, button, time):
        # Called when the tray icon is right-clicked
        menu = Gtk.Menu()
        mb_show = Gtk.MenuItem("Show/Hide")
        mb_about = Gtk.ImageMenuItem(
            'About', Gtk.Image.new_from_icon_name(Gtk.STOCK_ABOUT, 0))
        mb_quit = Gtk.ImageMenuItem(
            'Quit', Gtk.Image.new_from_icon_name(Gtk.STOCK_QUIT, 0))

        def on_mb_about():
            self._on_about(None, None)

        def on_mb_quit():
            self._quit()

        mb_show.connect("activate", self._tray_status_event)
        mb_about.connect("activate", on_mb_about)
        mb_quit.connect("activate", on_mb_quit)

        menu.append(mb_show)
        menu.append(mb_about)
        menu.append(Gtk.SeparatorMenuItem())
        menu.append(mb_quit)
        menu.show_all()

        def pos(menu, icon):
            return Gtk.StatusIcon.position_menu(menu, icon)

        menu.popup(None, None, None, pos, button, time)

    def _error_dialog_idle(self, msg, icon=Gtk.MessageType.ERROR):
        # Thread safe
        GLib.idle_add(self._error_dialog, msg, icon)

    def _error_dialog(self, msg, icon=Gtk.MessageType.ERROR):
        def modal_close(widget, response_id):
            widget.destroy()

        dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL, icon,
                                   Gtk.ButtonsType.OK, str(msg))
        dialog.show_all()
        dialog.connect("response", modal_close)
        print('Error: {}'.format(msg))

    def _on_show_action(self, main_view, event_type, selected_show, data):
        if event_type == ShowEventType.PLAY_NEXT:
            self._play_next(selected_show)
        elif event_type == ShowEventType.PLAY_EPISODE:
            self._play_episode(selected_show, data)
        elif event_type == ShowEventType.DETAILS:
            self._open_details(selected_show)
        elif event_type == ShowEventType.OPEN_WEBSITE:
            self._open_website(selected_show)
        elif event_type == ShowEventType.OPEN_FOLDER:
            self._open_folder(selected_show)
        elif event_type == ShowEventType.COPY_TITLE:
            self._copy_title(selected_show)
        elif event_type == ShowEventType.CHANGE_ALTERNATIVE_TITLE:
            self._change_alternative_title(selected_show)
        elif event_type == ShowEventType.REMOVE:
            self._remove_show(selected_show)

    def _play_next(self, show_id):
        threading.Thread(target=self._play_task, args=[show_id, True,
                                                       None]).start()

    def _play_episode(self, show_id, episode):
        threading.Thread(target=self._play_task,
                         args=[show_id, False, episode]).start()

    def _play_task(self, show_id, playnext, episode):
        self._main_view.set_buttons_sensitive_idle(False)

        show = self._engine.get_show_info(show_id)
        try:
            if playnext:
                self._engine.play_episode(show)
            else:
                if not episode:
                    episode = self.show_ep_num.get_value_as_int()
                self._engine.play_episode(show, episode)
        except utils.TrackmaError as e:
            self._error_dialog_idle(e)

        self._main_view.set_status_idle("Ready.")
        self._main_view.set_buttons_sensitive_idle(True)

    def _play_random(self):
        # TODO: Reimplement functionality in GUI
        threading.Thread(target=self._play_random_task).start()

    def _play_random_task(self):
        self._main_view.set_buttons_sensitive_idle(False)

        try:
            self._engine.play_random()
        except utils.TrackmaError as e:
            self._error_dialog_idle(e)

        self._main_view.set_status_idle("Ready.")
        self._main_view.set_buttons_sensitive_idle(True)

    def _open_details(self, show_id):
        show = self._engine.get_show_info(show_id)
        ShowInfoWindow(self._engine, show, transient_for=self)

    def _open_website(self, show_id):
        show = self._engine.get_show_info(show_id)
        if show['url']:
            Gtk.show_uri(None, show['url'], Gdk.CURRENT_TIME)

    def _open_folder(self, show_id):
        show = self._engine.get_show_info(show_id)
        try:
            filename = self._engine.get_episode_path(show, 1)
            with open(os.devnull, 'wb') as DEVNULL:
                subprocess.Popen(
                    ["/usr/bin/xdg-open",
                     os.path.dirname(filename)],
                    stdout=DEVNULL,
                    stderr=DEVNULL)
        except OSError:
            # xdg-open failed.
            raise utils.EngineError("Could not open folder.")

        except utils.EngineError:
            # Show not in library.
            self._error_dialog_idle("No folder found.")

    def _copy_title(self, show_id):
        show = self._engine.get_show_info(show_id)
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(show['title'], -1)

        self._main_view.set_status_idle('Title copied to clipboard.')

    def _change_alternative_title(self, show_id):
        show = self._engine.get_show_info(show_id)
        current_altname = self._engine.altname(show_id)

        def altname_response(entry, dialog, response):
            dialog.response(response)

        dialog = Gtk.MessageDialog(
            self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
            Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, None)
        dialog.set_markup('Set the <b>alternate title</b> for the show.')
        entry = Gtk.Entry()
        entry.set_text(current_altname)
        entry.connect("activate", altname_response, dialog,
                      Gtk.ResponseType.OK)
        hbox = Gtk.HBox()
        hbox.pack_start(Gtk.Label("Alternate Title:"), False, 5, 5)
        hbox.pack_end(entry, True, True, 0)
        dialog.format_secondary_markup(
            "Use this if the tracker is unable to find this show. Leave blank to disable."
        )
        dialog.vbox.pack_end(hbox, True, True, 0)
        dialog.show_all()
        retval = dialog.run()

        if retval == Gtk.ResponseType.OK:
            text = entry.get_text()
            self._engine.altname(show_id, text)
            self._main_view.change_show_title_idle(show, text)

        dialog.destroy()

    def _remove_show(self, show_id):
        print('Window__remove_show: ', show_id)
        try:
            show = self._engine.get_show_info(show_id)
            self._engine.delete_show(show)
        except utils.TrackmaError as e:
            self._error_dialog_idle(e)
Beispiel #27
0
class Trackma_cmd(cmd.Cmd):
    """
    Main program, inherits from the useful Cmd class
    for interactive console
    """
    engine = None
    filter_num = 1
    sort = 'title'
    completekey = 'Tab'
    cmdqueue = []
    stdout = sys.stdout
    sortedlist = []
    needed_args = {
        'altname':      (1, 2),
        'filter':       (0, 1),
        'sort':         1,
        'mediatype':    (0, 1),
        'info':         1,
        'search':       1,
        'add':          1,
        'delete':       1,
        'play':         (1, 2),
        'update':       2,
        'score':        2,
        'status':       2,
    }

    def __init__(self, account_num=None, debug=False):
        print('Trackma v'+utils.VERSION+'  Copyright (C) 2012  z411')
        print('This program comes with ABSOLUTELY NO WARRANTY; for details type `info\'')
        print('This is free software, and you are welcome to redistribute it')
        print('under certain conditions; see the file COPYING for details.')
        print()

        self.debug = debug

        self.accountman = Trackma_accounts()
        if account_num:
            try:
                self.account = self.accountman.get_account(int(account_num))
            except KeyError:
                print("Account {} doesn't exist.".format(account_num))
                self.account = self.accountman.select_account(True)
            except ValueError:
                print("Account {} must be numeric.".format(account_num))
                self.account = self.accountman.select_account(True)
        else:
            self.account = self.accountman.select_account(False)

    def _update_prompt(self):
        self.prompt = "{c_u}{u}{c_r} [{c_a}{a}{c_r}] ({c_mt}{mt}{c_r}) {c_s}{s}{c_r} >> ".format(
                u  = self.engine.get_userconfig('username'),
                a  = self.engine.api_info['shortname'],
                mt = self.engine.api_info['mediatype'],
                s  = self.engine.mediainfo['statuses_dict'][self.filter_num].lower().replace(' ', ''),
                c_r  = _PCOLOR_RESET,
                c_u  = _PCOLOR_USER,
                c_a  = _PCOLOR_API,
                c_mt = _PCOLOR_MEDIATYPE,
                c_s  = _COLOR_RESET
        )

    def _load_list(self, *args):
        showlist = self.engine.filter_list(self.filter_num)
        sortedlist = sorted(showlist, key=itemgetter(self.sort))
        self.sortedlist = list(enumerate(sortedlist, 1))

    def _get_show(self, title):
        # Attempt parsing list index
        # otherwise use title
        try:
            index = int(title)-1
            return self.sortedlist[index][1]
        except (ValueError, AttributeError, IndexError):
            return self.engine.get_show_info_title(title)

    def _ask_update(self, show, episode):
        do_update = input("Should I update %s to episode %d? [y/N] " % (show['title'], episode))
        if do_update.lower() == 'y':
            self.engine.set_episode(show['id'], episode)

    def start(self):
        """
        Initializes the engine

        Creates an Engine object and starts it.
        """
        print('Initializing engine...')
        self.engine = Engine(self.account, self.messagehandler)
        self.engine.connect_signal('show_added', self._load_list)
        self.engine.connect_signal('show_deleted', self._load_list)
        self.engine.connect_signal('status_changed', self._load_list)
        self.engine.connect_signal('episode_changed', self._load_list)
        self.engine.connect_signal('prompt_for_update', self._ask_update)
        self.engine.start()

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

        print()
        print("Ready. Type 'help' for a list of commands.")
        print("Press tab for autocompletion and up/down for command history.")
        self.do_filter(None) # Show available filters
        print()

    def do_help(self, arg):
        if arg:
            try:
                doc = getattr(self, 'do_' + arg).__doc__
                if doc:
                    (name, args, expl, usage) = self._parse_doc(arg, doc)

                    print()
                    print(name)
                    for line in expl:
                        print("  {}".format(line))
                    if args:
                        print("\n  Arguments:")
                        for arg in args:
                            if arg[2]:
                                print("    {}: {}".format(arg[0], arg[1]))
                            else:
                                print("    {} (optional): {}".format(arg[0], arg[1]))
                    if usage:
                        print("\n  Usage: " + usage)
                    print()
                    return
            except AttributeError:
                pass

            print("No help available.")
            return
        else:
            CMD_LENGTH = 11
            ARG_LENGTH = 13

            (height, width) = utils.get_terminal_size()
            prev_width = CMD_LENGTH + ARG_LENGTH + 3

            tw = textwrap.TextWrapper()
            tw.width = width - 2
            tw.subsequent_indent = ' ' * prev_width

            print()
            print(" {0:>{1}} {2:{3}} {4}".format(
                    'command', CMD_LENGTH,
                    'args', ARG_LENGTH,
                    'description'))
            print(" " + "-"*(min(prev_width+81, width-3)))

            names = self.get_names()
            names.sort()
            cmds = []
            for name in names:
                if name[:3] == 'do_':
                    doc = getattr(self, name).__doc__
                    if not doc:
                        continue

                    cmd = name[3:]
                    (name, args, expl, usage) = self._parse_doc(cmd, doc)

                    line = " {0:>{1}} {2:{3}} {4}".format(
                           name, CMD_LENGTH,
                           '<' + ','.join( a[0] for a in args) + '>', ARG_LENGTH,
                           expl[0])
                    print(tw.fill(line))

            print()
            print("Use `help <command>` for detailed information.")
            print()


    def do_account(self, args):
        """
        Switch to a different account.
        """

        self.account = self.accountman.select_account(True)
        self.engine.reload(account=self.account)

        # Start with default filter selected
        self.filter_num = self.engine.mediainfo['statuses'][0]
        self._load_list()
        self._update_prompt()

    def do_filter(self, args):
        """
        Changes the filtering of list by status.s

        :optparam status Name of status to filter
        :usage filter [filter type]
        """
        # Query the engine for the available statuses
        # that the user can choose
        if args:
            try:
                self.filter_num = self._guess_status(args[0].lower())
                self._load_list()
                self._update_prompt()
            except KeyError:
                print("Invalid filter.")
        else:
            print("Available statuses: %s" % ', '.join( v.lower().replace(' ', '') for v in self.engine.mediainfo['statuses_dict'].values() ))

    def do_sort(self, args):
        """
        Change of the lists

        :param type Sort type; available types: id, title, my_progress, total, my_score
        :usage sort <sort type>
        """
        sorts = ('id', 'title', 'my_progress', 'total', 'my_score')
        if args[0] in sorts:
            self.sort = args[0]
            self._load_list()
        else:
            print("Invalid sort.")

    def do_mediatype(self, args):
        """
        Reloads engine with different mediatype.
        Call with no arguments to see supported mediatypes.

        :optparam mediatype Mediatype name
        :usage mediatype [mediatype]
        """
        if args:
            if args[0] in self.engine.api_info['supported_mediatypes']:
                self.engine.reload(mediatype=args[0])

                # Start with default filter selected
                self.filter_num = self.engine.mediainfo['statuses'][0]
                self._load_list()
                self._update_prompt()
            else:
                print("Invalid mediatype.")
        else:
            print("Supported mediatypes: %s" % ', '.join(self.engine.api_info['supported_mediatypes']))

    def do_ls(self,args):
        self.do_list(args)

    def do_list(self, args):
        """
        Lists all shows available in the local list.

        :name list|ls
        """
        # Show the list in memory
        self._make_list(self.sortedlist)

    def do_info(self, args):
        """
        Gets detailed information about a local show.

        :param show Show index or title.
        :usage info <show index or title>
        """
        try:
            show = self._get_show(args[0])
            details = self.engine.get_show_details(show)
        except utils.TrackmaError as e:
            self.display_error(e)
            return

        print("Title: %s" % details['title'])
        for line in details['extra']:
            print("%s: %s" % line)

    def do_search(self, args):
        """
        Does a regex search on shows in the local lists.

        :param pattern Regex pattern to search for.
        :usage search <pattern>
        """
        sortedlist = list(v for v in self.sortedlist if re.search(args[0], v[1]['title'], re.I))
        self._make_list(sortedlist)

    def do_add(self, args):
        """
        Search for a show in the remote service and add it.

        :param pattern Show criteria to search.
        :usage add <pattern>
        """
        try:
            entries = self.engine.search(args[0])
        except utils.TrackmaError as e:
            self.display_error(e)
            return

        for i, entry in enumerate(entries, start=1):
            print("%d: (%s) %s" % (i, entry['type'], entry['title']))
        do_update = input("Choose show to add (blank to cancel): ")
        if do_update != '':
            try:
                show = entries[int(do_update)-1]
            except ValueError:
                print("Choice must be numeric.")
                return
            except IndexError:
                print("Invalid show.")
                return

            # Tell the engine to add the show
            try:
                self.engine.add_show(show, self.filter_num)
            except utils.TrackmaError as e:
                self.display_error(e)

    def do_delete(self, args):
        """
        Deletes a show from the local list.

        :param show Show index or title.
        :usage delete <show index or title>
        """
        try:
            show = self._get_show(args[0])

            do_delete = input("Delete %s? [y/N] " % show['title'])
            if do_delete.lower() == 'y':
                self.engine.delete_show(show)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_rescan(self, args):
        """
        Re-scans the local library.
        """
        self.engine.scan_library(rescan=True)

    def do_random(self, args):
        """
        Starts the media player with a random new episode.
        """
        try:
            self.engine.play_random()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_play(self, args):
        """
        Starts the media player with the specified episode number (next if not specified).

        :param show Episode index or title.
        :optparam ep Episode number. Assume next if not specified.
        :usage play <show index or title> [episode number]
        """
        try:
            episode = 0
            show = self._get_show(args[0])

            # If the user specified an episode, play it
            # otherwise play the next episode not watched yet
            try:
                episode = args[1]
                if episode == (show['my_progress'] + 1):
                    playing_next = True
                else:
                    playing_next = False
            except IndexError:
                playing_next = True

            played_episode = self.engine.play_episode(show, episode)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_update(self, args):
        """
        Updates the progress of a show to the specified episode.

        :param show Show index or name.
        :param ep Episode number (numeric).
        :usage update <show index or name> <episode number>
        """
        try:
            show = self._get_show(args[0])
            self.engine.set_episode(show['id'], args[1])
        except IndexError:
            print("Missing arguments.")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_score(self, args):
        """
        Changes the score of a show.

        :param show Show index or name.
        :param score Score to set (numeric/decimal).
        :usage score <show index or name> <score>
        """
        try:
            show = self._get_show(args[0])
            self.engine.set_score(show['id'], args[1])
        except IndexError:
            print("Missing arguments.")
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_status(self, args):
        """
        Changes the status of a show.
        Use the command `filter` without arguments to see the available statuses.

        :param show Show index or name.
        :param status Status name. Use `filter` without args to list them.
        :usage status <show index or name> <status name>
        """
        try:
            _showtitle = args[0]
            _filter = args[1]
        except IndexError:
            print("Missing arguments.")
            return

        try:
            _filter_num = self._guess_status(_filter)
        except KeyError:
            print("Invalid filter.")
            return

        try:
            show = self._get_show(_showtitle)
            self.engine.set_status(show['id'], _filter_num)
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_altname(self, args):
        """
        Changes the alternative name of a show.
        Use the command 'altname' without arguments to clear the alternative
        name.

        :param show Show index or name
        :param alt  The alternative name. Use `altname` without alt to clear it
        :usage altname <show index or name> <alternative name>
        """
        try:
            altnames = self.engine.altnames()
            show = self._get_show(args[0])
            altname = args[1] if len(args) > 1 else ''
            self.engine.altname(show['id'],altname)
        except IndexError:
            print("Missing arguments")
            return
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_send(self, args):
        """
        Sends queued changes to the remote service.
        """
        try:
            self.engine.list_upload()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_retrieve(self, args):
        """
        Retrieves the remote list overwrites the local one.
        """
        try:
            if self.engine.get_queue():
                answer = input("There are unqueued changes. Overwrite local list? [y/N] ")
                if answer.lower() == 'y':
                    self.engine.list_download()
            else:
                self.engine.list_download()
            self._load_list()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_undoall(self, args):
        """
        Undo all changes in queue.
        """
        try:
            self.engine.undoall()
        except utils.TrackmaError as e:
            self.display_error(e)

    def do_viewqueue(self, args):
        """
        List the queued changes.
        """
        queue = self.engine.get_queue()
        if len(queue):
            print("Queue:")
            for show in queue:
                print("- %s" % show['title'])
        else:
            print("Queue is empty.")

    def do_exit(self, args):
        self.do_quit(args)

    def do_quit(self, args):
        """
        Quits the program.

        :name quit|exit
        """
        try:
            self.engine.unload()
        except utils.TrackmaError as e:
            self.display_error(e)

        print('Bye!')
        sys.exit(0)

    def do_EOF(self, args):
        print()
        self.do_quit(args)

    def complete_update(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_play(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_score(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_status(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_delete(self, text, line, begidx, endidx):
        if text:
            return self.engine.regex_list_titles(text)

    def complete_filter(self, text, line, begidx, endidx):
        return [v.lower().replace(' ', '') for v in self.engine.mediainfo['statuses_dict'].values()]

    def parse_args(self, arg):
        if arg:
            return shlex.split(arg)
        else:
            return []

    def emptyline(self):
        return

    def onecmd(self, line):
        """ Override. """
        cmd, arg, line = self.parseline(line)
        if not line:
            return self.emptyline()
        if cmd is None:
            return self.default(line)
        self.lastcmd = line
        if line == 'EOF' :
            self.lastcmd = ''
        if cmd == '':
            return self.default(line)
        elif cmd == 'help':
            return self.do_help(arg)
        else:
            return self.execute(cmd, arg, line)

    def execute(self, cmd, arg, line):
        try:
            func = getattr(self, 'do_' + cmd)
        except AttributeError:
            return self.default(line)

        args = self.parse_args(arg)

        try:
            needed = self.needed_args[cmd]
        except KeyError:
            needed = 0

        if isinstance(needed, int):
            needed = (needed, needed)

        if needed[0] <= len(args) <= needed[1]:
            return func(args)
        else:
            print("Incorrent number of arguments. See `help %s`" % cmd)

    def display_error(self, e):
        print("%s%s: %s%s" % (_COLOR_ERROR, type(e), e, _COLOR_RESET))

    def messagehandler(self, classname, msgtype, msg):
        """
        Handles and shows messages coming from
        the engine messenger to provide feedback.
        """
        color_escape = ''
        color_reset = _COLOR_RESET

        if classname == 'Engine':
            color_escape = _COLOR_ENGINE
        elif classname == 'Data':
            color_escape = _COLOR_DATA
        elif classname == 'Tracker':
            color_escape = _COLOR_TRACKER
        elif classname.startswith('lib'):
            color_escape = _COLOR_API
        else:
            color_reset = ''

        if msgtype == messenger.TYPE_INFO:
            print("%s%s: %s%s" % (color_escape, classname, msg, color_reset))
        elif msgtype == messenger.TYPE_WARN:
            print("%s%s warning: %s%s" % (color_escape, classname, msg, color_reset))
        elif self.debug and msgtype == messenger.TYPE_DEBUG:
            print("%s%s: %s%s" % (color_escape, classname, msg, color_reset))

    def _guess_status(self, string):
        for k, v in self.engine.mediainfo['statuses_dict'].items():
            if string.lower() == v.lower().replace(' ', ''):
                return k
        raise KeyError

    def _parse_doc(self, cmd, doc):
        lines = doc.split('\n')
        name = cmd
        args = []
        expl = []
        usage = None

        for line in lines:
            line = line.strip()
            if line[:6] == ":param":
                args.append( line[7:].split(' ', 1) + [True] )
            elif line[:9] == ":optparam":
                args.append( line[10:].split(' ', 1) + [False] )
            elif line[:6] == ':usage':
                usage = line[7:]
            elif line[:5] == ':name':
                name = line[6:]
            elif line:
                expl.append(line)

        return (name, args, expl, usage)

    def _make_list(self, showlist):
        """
        Helper function for printing a formatted show list
        """
        # Fixed column widths
        col_id_length = 7
        col_index_length = 6
        col_title_length = 5
        col_episodes_length = 9
        col_score_length = 6
        altnames = self.engine.altnames()

        # Calculate maximum width for the title column
        # based on the width of the terminal
        (height, width) = utils.get_terminal_size()
        max_title_length = width - col_id_length - col_episodes_length - col_score_length - col_index_length - 5

        # Find the widest title so we can adjust the title column
        for index, show in showlist:
            if len(show['title']) > col_title_length:
                if len(show['title']) > max_title_length:
                    # Stop if we exceeded the maximum column width
                    col_title_length = max_title_length
                    break
                else:
                    col_title_length = len(show['title'])

        # Print header
        print("| {0:{1}} {2:{3}} {4:{5}} {6:{7}} |".format(
                'Index',    col_index_length,
                'Title',    max_title_length,
                'Progress', col_episodes_length,
                'Score',    col_score_length))

        # List shows
        for index, show in showlist:
            if self.engine.mediainfo['has_progress']:
                episodes_str = "{0:3} / {1}".format(show['my_progress'], show['total'])
            else:
                episodes_str = "-"

            #Get title (and alt. title) and if need be, truncate it
            title_str = show['title']
            if altnames.get(show['id']):
                title_str += "[{}]".format(altnames.get(show['id']))
            title_str = title_str[:max_title_length] if len(title_str) > max_title_length else title_str

            # Color title according to status
            if show['status'] == utils.STATUS_AIRING:
                colored_title = _COLOR_AIRING + title_str + _COLOR_RESET
            else:
                colored_title = title_str

            print("| {0:^{1}} {2}{3} {4:{5}} {6:^{7}} |".format(
                index, col_index_length,
                colored_title,
                '.' * (max_title_length-len(title_str)),
                episodes_str, col_episodes_length,
                show['my_score'], col_score_length))

        # Print result count
        print('%d results' % len(showlist))
        print()
Beispiel #28
0
class EngineWorker(QtCore.QThread):
    """
    Worker thread

    Contains the engine and manages every process in a separate thread.

    """
    engine = None
    function = None
    finished = QtCore.pyqtSignal(dict)

    # Message handler signals
    changed_status = QtCore.pyqtSignal(str, int, str)
    raised_error = QtCore.pyqtSignal(str)
    raised_fatal = QtCore.pyqtSignal(str)

    # Event handler signals
    changed_show = QtCore.pyqtSignal(dict)
    changed_show_status = QtCore.pyqtSignal(dict, object)
    changed_list = QtCore.pyqtSignal(dict)
    changed_queue = QtCore.pyqtSignal(int)
    tracker_state = QtCore.pyqtSignal(dict)
    playing_show = QtCore.pyqtSignal(dict, bool, int)
    prompt_for_update = QtCore.pyqtSignal(dict, int)
    prompt_for_add = QtCore.pyqtSignal(dict, int)

    def __init__(self):
        super(EngineWorker, self).__init__()

        self.overrides = {'start': self._start}

    def _messagehandler(self, classname, msgtype, msg):
        self.changed_status.emit(classname, msgtype, msg)

    def _error(self, msg):
        self.raised_error.emit(str(msg))

    def _fatal(self, msg):
        self.raised_fatal.emit(str(msg))

    def _changed_show(self, show, changes=None):
        self.changed_show.emit(show)

    def _changed_show_status(self, show, old_status=None):
        self.changed_show_status.emit(show, old_status)

    def _changed_list(self, show):
        self.changed_list.emit(show)

    def _changed_queue(self, queue):
        self.changed_queue.emit(len(queue))

    def _tracker_state(self, status):
        self.tracker_state.emit(status)

    def _playing_show(self, show, is_playing, episode):
        self.playing_show.emit(show, is_playing, episode)

    def _prompt_for_update(self, show, episode):
        self.prompt_for_update.emit(show, episode)

    def _prompt_for_add(self, show, episode):
        self.prompt_for_add.emit(show, episode)

    # Callable functions
    def _start(self, account):
        self.engine = Engine(account, self._messagehandler)

        self.engine.connect_signal('episode_changed', self._changed_show)
        self.engine.connect_signal('score_changed', self._changed_show)
        self.engine.connect_signal('tags_changed', self._changed_show)
        self.engine.connect_signal('status_changed', self._changed_show_status)
        self.engine.connect_signal('playing', self._playing_show)
        self.engine.connect_signal('show_added', self._changed_list)
        self.engine.connect_signal('show_deleted', self._changed_list)
        self.engine.connect_signal('show_synced', self._changed_show)
        self.engine.connect_signal('queue_changed', self._changed_queue)
        self.engine.connect_signal('prompt_for_update', self._prompt_for_update)
        self.engine.connect_signal('prompt_for_add', self._prompt_for_add)
        self.engine.connect_signal('tracker_state', self._tracker_state)

        self.engine.start()

    def set_function(self, function, ret_function, *args, **kwargs):
        if function in self.overrides:
            self.function = self.overrides[function]
        else:
            self.function = getattr(self.engine, function)

        try:
            self.finished.disconnect()
        except Exception:
            pass

        if ret_function:
            self.finished.connect(ret_function)

        self.args = args
        self.kwargs = kwargs

    def __del__(self):
        self.wait()

    def run(self):
        try:
            ret = self.function(*self.args, **self.kwargs)
            self.finished.emit({'success': True, 'result': ret})
        except utils.TrackmaError as e:
            self._error(e)
            self.finished.emit({'success': False})
        except utils.TrackmaFatal as e:
            self._fatal(e)