Ejemplo n.º 1
0
    def __init__(self):
        """ Initialize rtcontrol.
        """
        super(RtorrentControl, self).__init__()

        self.prompt = PromptDecorator(self)
        self.plain_output_format = False
Ejemplo n.º 2
0
class RtorrentControl(ScriptBaseWithConfig):
    ### Keep things wrapped to fit under this comment... ##############################
    """
        Control and inspect rTorrent from the command line.

        Filter expressions take the form "<field>=<value>", and all expressions must
        be met (AND). If a field name is omitted, "name" is assumed. You can also use
        uppercase OR to build a list of alternative conditions.

        For numeric fields, a leading "+" means greater than, a leading "-" means less
        than. For string fields, the value is a glob pattern (*, ?, [a-z], [!a-z]), or
        a regex match enclosed by slashes. All string comparisons are case-ignoring.
        Multiple values separated by a comma indicate several possible choices (OR).
        "!" in front of a filter value negates it (NOT).

        See https://pyrocore.readthedocs.io/en/latest/usage.html#rtcontrol for more.

        Examples:
          - All 1:1 seeds         ratio=+1
          - All active torrents   xfer=+0
          - All seeding torrents  up=+0
          - Slow torrents         down=+0 down=-5k
          - Older than 2 weeks    completed=+2w
          - Big stuff             size=+4g
          - 1:1 seeds not on NAS  ratio=+1 'realpath=!/mnt/*'
          - Music                 kind=flac,mp3
    """

    # argument description for the usage information
    ARGS_HELP = "<filter>..."

    # additonal stuff appended after the command handler's docstring
    ADDITIONAL_HELP = [
        "",
        "",
        "Use --help to get a list of all options.",
        "Use --help-fields to list all fields and their description.",
    ]

    # additional values for output formatting
    FORMATTER_DEFAULTS = dict(now=time.time(), )

    # choices for --ignore
    IGNORE_OPTIONS = ('0', '1')

    # choices for --prio
    PRIO_OPTIONS = ('0', '1', '2', '3')

    # choices for --alter
    ALTER_MODES = ('append', 'remove')

    # action options that perform some change on selected items
    ACTION_MODES = (
        Bunch(name="start", options=("--start", ), help="start torrent"),
        Bunch(name="close",
              options=("--close", "--stop"),
              help="stop torrent",
              method="stop"),
        Bunch(name="hash_check",
              label="HASH",
              options=("-H", "--hash-check"),
              help="hash-check torrent",
              interactive=True),
        # TODO: Bunch(name="announce", options=("--announce",), help="announce right now", interactive=True),
        # TODO: --pause, --resume?
        # TODO: implement --clean-partial
        #self.add_bool_option("--clean-partial",
        #    help="remove partially downloaded 'off'ed files (also stops downloads)")
        Bunch(name="delete",
              options=("--delete", ),
              help="remove torrent from client",
              interactive=True),
        Bunch(name="purge",
              options=("--purge", "--delete-partial"),
              help="delete PARTIAL data files and remove torrent from client",
              interactive=True),
        Bunch(name="cull",
              options=("--cull", "--exterminate", "--delete-all"),
              help="delete ALL data files and remove torrent from client",
              interactive=True),
        Bunch(
            name="throttle",
            options=(
                "-T",
                "--throttle",
            ),
            argshelp="NAME",
            method="set_throttle",
            help="assign to named throttle group (NULL=unlimited, NONE=global)",
            interactive=True),
        Bunch(name="tag",
              options=("--tag", ),
              argshelp='"TAG +TAG -TAG..."',
              help="add or remove tag(s)",
              interactive=False),
        Bunch(name="custom",
              label="SET_CUSTOM",
              options=("--custom", ),
              argshelp='KEY=VALUE',
              method="set_custom",
              help="set value of 'custom_KEY' field (KEY might also be 1..5)",
              interactive=False),
        Bunch(name="exec",
              label="EXEC",
              options=("--exec", "--xmlrpc"),
              argshelp='CMD',
              method="execute",
              help="execute XMLRPC command pattern",
              interactive=True),
        # TODO: --move / --link output_format / the formatted result is the target path
        #           if the target contains a '//' in place of a '/', directories
        #           after that are auto-created
        #           "--move tracker_dated", with a custom output format
        #           like "tracker_dated = ~/done//$(alias)s/$(completed).7s",
        #           will move to ~/done/OBT/2010-08 for example
        #        self.add_value_option("--move", "TARGET",
        #            help="move data to given target directory (implies -i, can be combined with --delete)")
        # TODO: --copy, and --move/--link across devices
    )

    def __init__(self):
        """ Initialize rtcontrol.
        """
        super(RtorrentControl, self).__init__()

        self.prompt = PromptDecorator(self)
        self.plain_output_format = False
        self.raw_output_format = None

    def add_options(self):
        """ Add program options.
        """
        super(RtorrentControl, self).add_options()

        # basic options
        self.add_bool_option(
            "--help-fields",
            help="show available fields and their description")
        self.add_bool_option(
            "-n",
            "--dry-run",
            help="don't commit changes, just tell what would happen")
        self.add_bool_option("--detach",
                             help="run the process in the background")
        self.prompt.add_options()

        # output control
        self.add_bool_option("-S",
                             "--shell",
                             help="escape output following shell rules")
        self.add_bool_option(
            "-0",
            "--nul",
            "--print0",
            help="use a NUL character instead of a linebreak after items")
        self.add_bool_option("-c",
                             "--column-headers",
                             help="print column headers")
        self.add_bool_option("-+",
                             "--stats",
                             help="add sum / avg / median of numerical fields")
        self.add_bool_option(
            "--summary",
            help="print only statistical summary, without the items")
        #self.add_bool_option("-f", "--full",
        #    help="print full torrent details")
        self.add_bool_option(
            "--json",
            help=
            "dump default fields of all items as JSON (use '-o f1,f2,...' to specify fields)"
        )
        self.add_value_option(
            "-o",
            "--output-format",
            "FORMAT",
            help="specify display format (use '-o-' to disable item display)")
        self.add_value_option(
            "-O",
            "--output-template",
            "FILE",
            help="pass control of output formatting to the specified template")
        self.add_value_option(
            "-s",
            "--sort-fields",
            "[-]FIELD[,...] [-s...]",
            action='append',
            default=[],
            help=
            "fields used for sorting, descending if prefixed with a '-'; '-s*' uses output field list"
        )
        self.add_bool_option("-r",
                             "--reverse-sort",
                             help="reverse the sort order")
        self.add_value_option(
            "-A",
            "--anneal",
            "MODE [-A...]",
            type='choice',
            action='append',
            default=[],
            choices=('dupes+', 'dupes-', 'dupes=', 'invert', 'unique'),
            help="modify result set using some pre-defined methods")
        self.add_value_option(
            "-/",
            "--select",
            "[N-]M",
            help="select result subset by item position (counting from 1)")
        self.add_bool_option(
            "-V",
            "--view-only",
            help="show search result only in default ncurses view")
        self.add_value_option(
            "--to-view",
            "--to",
            "NAME",
            help="show search result only in named ncurses view")
        self.add_bool_option("--append-view",
                             "--append",
                             help="DEPRECATED: use '--alter append' instead")
        self.add_value_option(
            "--alter-view",
            "--alter",
            "MODE",
            type='choice',
            default=None,
            choices=self.ALTER_MODES,
            help=
            "alter view according to mode: {} (modifies -V and --to behaviour)"
            .format(', '.join(self.ALTER_MODES)))
        self.add_bool_option(
            "--tee-view",
            "--tee",
            help=
            "ADDITIONALLY show search results in ncurses view (modifies -V and --to behaviour)"
        )
        self.add_value_option(
            "--from-view",
            "--from",
            "NAME",
            help=
            "select only items that are on view NAME (NAME can be an info hash to quickly select a single item)"
        )
        self.add_value_option(
            "-M",
            "--modify-view",
            "NAME",
            help=
            "get items from given view and write result back to it (short-cut to combine --from-view and --to-view)"
        )
        self.add_value_option(
            "-Q",
            "--fast-query",
            "LEVEL",
            type='choice',
            default='=',
            choices=('=', '0', '1', '2'),
            help=
            "enable query optimization (=: use config; 0: off; 1: safe; 2: danger seeker)"
        )
        self.add_value_option("--call",
                              "CMD",
                              help="call an OS command pattern in the shell")
        self.add_value_option("--spawn",
                              "CMD [--spawn ...]",
                              action="append",
                              default=[],
                              help="execute OS command pattern(s) directly")
        # TODO: implement -S
        #        self.add_bool_option("-S", "--summary",
        #            help="print statistics")

        # torrent state change (actions)
        for action in self.ACTION_MODES:
            action.setdefault("label", action.name.upper())
            action.setdefault("method", action.name)
            action.setdefault("interactive", False)
            action.setdefault("argshelp", "")
            action.setdefault("args", ())
            if action.argshelp:
                self.add_value_option(
                    *action.options + (action.argshelp, ), **{
                        "help":
                        action.help +
                        (" (implies -i)" if action.interactive else "")
                    })
            else:
                self.add_bool_option(
                    *action.options, **{
                        "help":
                        action.help +
                        (" (implies -i)" if action.interactive else "")
                    })
        self.add_value_option("--ignore",
                              "|".join(self.IGNORE_OPTIONS),
                              type="choice",
                              choices=self.IGNORE_OPTIONS,
                              help="set 'ignore commands' status on torrent")
        self.add_value_option("--prio",
                              "|".join(self.PRIO_OPTIONS),
                              type="choice",
                              choices=self.PRIO_OPTIONS,
                              help="set priority of torrent")
        self.add_bool_option(
            "-F",
            "--flush",
            help="flush changes immediately (save session data)")

    def help_completion_fields(self):
        """ Return valid field names.
        """
        for name, field in sorted(engine.FieldDefinition.FIELDS.items()):
            if issubclass(field._matcher, matching.BoolFilter):
                yield "%s=no" % (name, )
                yield "%s=yes" % (name, )
                continue
            elif issubclass(field._matcher, matching.PatternFilter):
                yield "%s=" % (name, )
                yield "%s=/" % (name, )
                yield "%s=?" % (name, )
                yield "%s=\"'*'\"" % (name, )
                continue
            elif issubclass(field._matcher, matching.NumericFilterBase):
                for i in range(10):
                    yield "%s=%d" % (name, i)
            else:
                yield "%s=" % (name, )

            yield r"%s=+" % (name, )
            yield r"%s=-" % (name, )

        yield "custom_"
        yield "kind_"

    # TODO: refactor to engine.TorrentProxy as format() method
    def format_item(self, item, defaults=None, stencil=None):
        """ Format an item.
        """
        from pyrobase.osutil import shell_escape

        try:
            item_text = fmt.to_console(
                formatting.format_item(self.options.output_format, item,
                                       defaults))
        except (NameError, ValueError, TypeError), exc:
            self.fatal(
                "Trouble with formatting item %r\n\n  FORMAT = %r\n\n  REASON ="
                % (item, self.options.output_format), exc)
            raise  # in --debug mode

        if self.options.shell:
            item_text = '\t'.join(
                shell_escape(i) for i in item_text.split('\t'))

        # Justify headers according to stencil
        if stencil:
            item_text = '\t'.join(
                i.ljust(len(s))
                for i, s in zip(item_text.split('\t'), stencil))

        return item_text
Ejemplo n.º 3
0
class RtorrentControl(ScriptBaseWithConfig):
    ### Keep things wrapped to fit under this comment... ##############################
    """
        Control and inspect rTorrent from the command line.

        Filter expressions take the form "<field>=<value>", and all expressions must
        be met (AND). If a field name is omitted, "name" is assumed. You can also use
        uppercase OR to build a list of alternative conditions.

        For numeric fields, a leading "+" means greater than, a leading "-" means less
        than. For string fields, the value is a glob pattern (*, ?, [a-z], [!a-z]).
        Multiple values separated by a comma indicate several possible choices (OR).
        "!" in front of a filter value negates it (NOT).

        Examples:
          - All 1:1 seeds         ratio=+1
          - All active torrents   xfer=+0
          - All seeding torrents  up=+0
          - Slow torrents         down=+0 down=-5k
          - Older than 2 weeks    completed=+2w
          - Big stuff             size=+4g
          - 1:1 seeds not on NAS  ratio=+1 'realpath=!/mnt/*'
          - Music                 kind=flac,mp3
    """

    # argument description for the usage information
    ARGS_HELP = "<filter>..."

    # additonal stuff appended after the command handler's docstring
    ADDITIONAL_HELP = ["", "",
        "Use --help to get a list of all options.",
        "Use --help-fields to list all fields and their description.",
    ]

    # additional values for output formatting
    FORMATTER_DEFAULTS = dict(
        now = time.time(),
    )

    # choices for --ignore
    IGNORE_OPTIONS = ('0', '1')

    # choices for --prio
    PRIO_OPTIONS = ('0', '1', '2', '3')

    # action options that perform some change on selected items
    ACTION_MODES = (
        Bunch(name="start", options=("--start",), help="start torrent"),
        Bunch(name="close", options=("--close", "--stop"), help="stop torrent", method="stop"),
        Bunch(name="hash_check", label="HASH", options=("-H", "--hash-check"), help="hash-check torrent", interactive=True),
        # TODO: Bunch(name="announce", options=("--announce",), help="announce right now", interactive=True),
        # TODO: --pause, --resume?
        # TODO: implement --clean-partial
        #self.add_bool_option("--clean-partial",
        #    help="remove partially downloaded 'off'ed files (also stops downloads)")
        Bunch(name="delete", options=("--delete",), help="remove torrent from client", interactive=True),
        Bunch(name="purge", options=("--purge", "--delete-partial"),
              help="delete PARTIAL data files and remove torrent from client", interactive=True),
        Bunch(name="cull", options=("--cull", "--exterminate", "--delete-all"),
            help="delete ALL data files and remove torrent from client", interactive=True),
        Bunch(name="throttle", options=("-T", "--throttle",), argshelp="NAME", method="set_throttle",
            help="assign to named throttle group (NULL=unlimited, NONE=global)", interactive=True),
        Bunch(name="tag", options=("--tag",), argshelp='"TAG +TAG -TAG..."',
            help="add or remove tag(s)", interactive=False),
        Bunch(name="custom", label="SET_CUSTOM", options=("--custom",), argshelp='KEY=VALUE', method="set_custom",
            help="set value of 'custom_KEY' field (KEY might also be 1..5)", interactive=False),
        Bunch(name="exec", label="EXEC", options=("--exec", "--xmlrpc"), argshelp='CMD', method="execute",
            help="execute XMLRPC command pattern", interactive=True),
        # TODO: --with-dupes / -D Include any items that reference the same data paths as the selected ones
        # TODO: --no-dupes / --unique / -U Exclude any items that reference the same data paths as any other (from deletion, etc.)
        # TODO: --move / --link output_format / the formatted result is the target path
        #           if the target contains a '//' in place of a '/', directories
        #           after that are auto-created
        #           "--move tracker_dated", with a custom output format
        #           like "tracker_dated = ~/done//$(alias)s/$(completed).7s",
        #           will move to ~/done/OBT/2010-08 for example
        #        self.add_value_option("--move", "TARGET",
        #            help="move data to given target directory (implies -i, can be combined with --delete)")
        # TODO: --copy, and --move/--link across devices
    )


    def __init__(self):
        """ Initialize rtcontrol.
        """
        super(RtorrentControl, self).__init__()

        self.prompt = PromptDecorator(self)
        self.plain_output_format = False


    def add_options(self):
        """ Add program options.
        """
        super(RtorrentControl, self).add_options()

        # basic options
        self.add_bool_option("--help-fields",
            help="show available fields and their description")
        self.add_bool_option("-n", "--dry-run",
            help="don't commit changes, just tell what would happen")
        self.add_bool_option("--detach",
            help="run the process in the background")
        self.prompt.add_options()

        # output control
        self.add_bool_option("-S", "--shell",
            help="escape output following shell rules")
        self.add_bool_option("-0", "--nul", "--print0",
            help="use a NUL character instead of a linebreak after items")
        self.add_bool_option("-c", "--column-headers",
            help="print column headers")
        self.add_bool_option("-+", "--stats",
            help="add sum / avg / median of numerical fields")
        self.add_bool_option("--summary",
            help="print only statistical summary, without the items")
        #self.add_bool_option("-f", "--full",
        #    help="print full torrent details")
        self.add_bool_option("--json",
            help="dump all items as JSON (use '-o f1,f2,...' to specify fields)")
        self.add_value_option("-o", "--output-format", "FORMAT",
            help="specify display format (use '-o-' to disable item display)")
        self.add_value_option("-O", "--output-template", "FILE",
            help="pass control of output formatting to the specified template")
        self.add_value_option("-s", "--sort-fields", "[-]FIELD[,...]",
            help="fields used for sorting, descending if prefixed with a '-'; '-s*' uses output field list")
        self.add_bool_option("-r", "--reverse-sort",
            help="reverse the sort order")
        self.add_value_option("-/", "--select", "[N-]M",
            help="select result subset by item position (counting from 1)")
        self.add_bool_option("-V", "--view-only",
            help="show search result only in default ncurses view")
        self.add_value_option("--to-view", "NAME",
            help="show search result only in named ncurses view")
        self.add_bool_option("--tee-view",
            help="ADDITIONALLY show search results in ncurses view (modifies -V and --to-view behaviour)")
        self.add_value_option("--from-view", "NAME",
            help="select only items that are on view NAME")
        self.add_value_option("-M", "--modify-view", "NAME",
            help="get items from given view and write result back to it (short-cut to combine --from-view and --to-view)")
        self.add_value_option("--call", "CMD",
            help="call an OS command pattern in the shell")
        self.add_value_option("--spawn", "CMD [--spawn ...]",
            action="append", default=[],
            help="execute OS command pattern(s) directly")
# TODO: implement -S
#        self.add_bool_option("-S", "--summary",
#            help="print statistics")

        # torrent state change (actions)
        for action in self.ACTION_MODES:
            action.setdefault("label", action.name.upper())
            action.setdefault("method", action.name)
            action.setdefault("interactive", False)
            action.setdefault("argshelp", "")
            action.setdefault("args", ())
            if action.argshelp:
                self.add_value_option(*action.options + (action.argshelp,),
                    **{"help": action.help + (" (implies -i)" if action.interactive else "")})
            else:
                self.add_bool_option(*action.options,
                    **{"help": action.help + (" (implies -i)" if action.interactive else "")})
        self.add_value_option("--ignore", "|".join(self.IGNORE_OPTIONS),
            type="choice", choices=self.IGNORE_OPTIONS,
            help="set 'ignore commands' status on torrent")
        self.add_value_option("--prio", "|".join(self.PRIO_OPTIONS),
            type="choice", choices=self.PRIO_OPTIONS,
            help="set priority of torrent")
        self.add_bool_option("-F", "--flush", help="flush changes immediately (save session data)")


    def help_completion_fields(self):
        """ Return valid field names.
        """
        for name, field in sorted(engine.FieldDefinition.FIELDS.items()):
            if issubclass(field._matcher, matching.BoolFilter):
                yield "%s=no" % (name,)
                yield "%s=yes" % (name,)
                continue
            elif issubclass(field._matcher, matching.PatternFilter):
                yield "%s=" % (name,)
                yield "%s=/" % (name,)
                yield "%s=?" % (name,)
                yield "%s=\"'*'\"" % (name,)
                continue
            elif issubclass(field._matcher, matching.NumericFilterBase):
                for i in range(10):
                    yield "%s=%d" % (name, i)
            else:
                yield "%s=" % (name,)

            yield r"%s=+" % (name,)
            yield r"%s=-" % (name,)

        yield "custom_"
        yield "kind_"


    # TODO: refactor to engine.TorrentProxy as format() method
    def format_item(self, item, defaults=None, stencil=None):
        """ Format an item.
        """
        try:
            item_text = fmt.to_console(formatting.format_item(self.options.output_format, item, defaults))
        except (NameError, ValueError, TypeError), exc:
            self.fatal("Trouble with formatting item %r\n\n  FORMAT = %r\n\n  REASON =" % (item, self.options.output_format), exc)
            raise # in --debug mode

        # Escape for shell use?
        def shell_escape(text, safe=re.compile(r"^[-._,+a-zA-Z0-9]+$")):
            "Escape helper"
            return text if safe.match(text) else "'%s'" % text.replace("'", r"'\''")

        if self.options.shell:
            item_text = '\t'.join(shell_escape(i) for i in item_text.split('\t'))

        # Justify headers according to stencil
        if stencil:
            item_text = '\t'.join(i.ljust(len(s)) for i, s in zip(item_text.split('\t'), stencil))

        return item_text