summaryrefslogtreecommitdiffstats
path: root/deluge/ui/console/cmdline/commands
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/ui/console/cmdline/commands')
-rw-r--r--deluge/ui/console/cmdline/commands/__init__.py6
-rw-r--r--deluge/ui/console/cmdline/commands/add.py126
-rw-r--r--deluge/ui/console/cmdline/commands/cache.py31
-rw-r--r--deluge/ui/console/cmdline/commands/config.py168
-rw-r--r--deluge/ui/console/cmdline/commands/connect.py86
-rw-r--r--deluge/ui/console/cmdline/commands/debug.py40
-rw-r--r--deluge/ui/console/cmdline/commands/gui.py30
-rw-r--r--deluge/ui/console/cmdline/commands/halt.py35
-rw-r--r--deluge/ui/console/cmdline/commands/help.py74
-rw-r--r--deluge/ui/console/cmdline/commands/info.py484
-rw-r--r--deluge/ui/console/cmdline/commands/manage.py119
-rw-r--r--deluge/ui/console/cmdline/commands/move.py97
-rw-r--r--deluge/ui/console/cmdline/commands/pause.py48
-rw-r--r--deluge/ui/console/cmdline/commands/plugin.py143
-rw-r--r--deluge/ui/console/cmdline/commands/quit.py25
-rw-r--r--deluge/ui/console/cmdline/commands/recheck.py47
-rw-r--r--deluge/ui/console/cmdline/commands/resume.py48
-rw-r--r--deluge/ui/console/cmdline/commands/rm.py83
-rw-r--r--deluge/ui/console/cmdline/commands/status.py114
-rw-r--r--deluge/ui/console/cmdline/commands/update_tracker.py47
20 files changed, 1851 insertions, 0 deletions
diff --git a/deluge/ui/console/cmdline/commands/__init__.py b/deluge/ui/console/cmdline/commands/__init__.py
new file mode 100644
index 0000000..628fae5
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from deluge.ui.console.cmdline.command import BaseCommand
+
+__all__ = ['BaseCommand']
diff --git a/deluge/ui/console/cmdline/commands/add.py b/deluge/ui/console/cmdline/commands/add.py
new file mode 100644
index 0000000..34881d8
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/add.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import os
+from base64 import b64encode
+
+from twisted.internet import defer
+
+import deluge.common
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+try:
+ from urllib.parse import urlparse
+ from urllib.request import url2pathname
+except ImportError:
+ # PY2 fallback
+ from urlparse import urlparse # pylint: disable=ungrouped-imports
+ from urllib import url2pathname # pylint: disable=ungrouped-imports
+
+
+class Command(BaseCommand):
+ """Add torrents"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-p', '--path', dest='path', help=_('Download folder for torrent')
+ )
+ parser.add_argument(
+ '-m',
+ '--move-path',
+ dest='move_completed_path',
+ help=_('Move the completed torrent to this folder'),
+ )
+ parser.add_argument(
+ 'torrents',
+ metavar='<torrent>',
+ nargs='+',
+ help=_('One or more torrent files, URLs or magnet URIs'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ t_options = {}
+ if options.path:
+ t_options['download_location'] = os.path.abspath(
+ os.path.expanduser(options.path)
+ )
+
+ if options.move_completed_path:
+ t_options['move_completed'] = True
+ t_options['move_completed_path'] = os.path.abspath(
+ os.path.expanduser(options.move_completed_path)
+ )
+
+ def on_success(result):
+ if not result:
+ self.console.write('{!error!}Torrent was not added: Already in session')
+ else:
+ self.console.write('{!success!}Torrent added!')
+
+ def on_fail(result):
+ self.console.write('{!error!}Torrent was not added: %s' % result)
+
+ # Keep a list of deferreds to make a DeferredList
+ deferreds = []
+ for torrent in options.torrents:
+ if not torrent.strip():
+ continue
+ if deluge.common.is_url(torrent):
+ self.console.write(
+ '{!info!}Attempting to add torrent from url: %s' % torrent
+ )
+ deferreds.append(
+ client.core.add_torrent_url(torrent, t_options)
+ .addCallback(on_success)
+ .addErrback(on_fail)
+ )
+ elif deluge.common.is_magnet(torrent):
+ self.console.write(
+ '{!info!}Attempting to add torrent from magnet uri: %s' % torrent
+ )
+ deferreds.append(
+ client.core.add_torrent_magnet(torrent, t_options)
+ .addCallback(on_success)
+ .addErrback(on_fail)
+ )
+ else:
+ # Just a file
+ if urlparse(torrent).scheme == 'file':
+ torrent = url2pathname(urlparse(torrent).path)
+ path = os.path.abspath(os.path.expanduser(torrent))
+ if not os.path.exists(path):
+ self.console.write('{!error!}%s does not exist!' % path)
+ continue
+ if not os.path.isfile(path):
+ self.console.write('{!error!}This is a directory!')
+ continue
+ self.console.write('{!info!}Attempting to add torrent: %s' % path)
+ filename = os.path.split(path)[-1]
+ with open(path, 'rb') as _file:
+ filedump = b64encode(_file.read())
+ deferreds.append(
+ client.core.add_torrent_file_async(filename, filedump, t_options)
+ .addCallback(on_success)
+ .addErrback(on_fail)
+ )
+
+ return defer.DeferredList(deferreds)
+
+ def complete(self, line):
+ return component.get('ConsoleUI').tab_complete_path(
+ line, ext='.torrent', sort='date'
+ )
diff --git a/deluge/ui/console/cmdline/commands/cache.py b/deluge/ui/console/cmdline/commands/cache.py
new file mode 100644
index 0000000..e427f08
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/cache.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+from deluge.ui.common import DISK_CACHE_KEYS
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Show information about the disk cache"""
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ def on_cache_status(status):
+ for key, value in sorted(status.items()):
+ self.console.write('{!info!}%s: {!input!}%s' % (key, value))
+
+ return client.core.get_session_status(DISK_CACHE_KEYS).addCallback(
+ on_cache_status
+ )
diff --git a/deluge/ui/console/cmdline/commands/config.py b/deluge/ui/console/cmdline/commands/config.py
new file mode 100644
index 0000000..bd0a1e1
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/config.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import tokenize
+from io import StringIO
+
+import deluge.component as component
+import deluge.ui.console.utils.colors as colors
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+def atom(src, token):
+ """taken with slight modifications from http://effbot.org/zone/simple-iterator-parser.htm"""
+ if token[1] == '(':
+ out = []
+ token = next(src)
+ while token[1] != ')':
+ out.append(atom(src, token))
+ token = next(src)
+ if token[1] == ',':
+ token = next(src)
+ return tuple(out)
+ elif token[0] is tokenize.NUMBER or token[1] == '-':
+ try:
+ if token[1] == '-':
+ return int(token[-1], 0)
+ else:
+ if token[1].startswith('0x'):
+ # Hex number so return unconverted as string.
+ return token[1].decode('string-escape')
+ else:
+ return int(token[1], 0)
+ except ValueError:
+ try:
+ return float(token[-1])
+ except ValueError:
+ return str(token[-1])
+ elif token[1].lower() == 'true':
+ return True
+ elif token[1].lower() == 'false':
+ return False
+ elif token[0] is tokenize.STRING or token[1] == '/':
+ return token[-1].decode('string-escape')
+ elif token[1].isalpha():
+ # Parse Windows paths e.g. 'C:\\xyz' or 'C:/xyz'.
+ if next()[1] == ':' and next()[1] in '\\/':
+ return token[-1].decode('string-escape')
+
+ raise SyntaxError('malformed expression (%s)' % token[1])
+
+
+def simple_eval(source):
+ """ evaluates the 'source' string into a combination of primitive python objects
+ taken from http://effbot.org/zone/simple-iterator-parser.htm"""
+ src = StringIO(source).readline
+ src = tokenize.generate_tokens(src)
+ src = (token for token in src if token[0] is not tokenize.NL)
+ res = atom(src, next(src))
+ return res
+
+
+class Command(BaseCommand):
+ """Show and set configuration values"""
+
+ usage = _('Usage: config [--set <key> <value>] [<key> [<key>...] ]')
+
+ def add_arguments(self, parser):
+ set_group = parser.add_argument_group('setting a value')
+ set_group.add_argument(
+ '-s',
+ '--set',
+ action='store',
+ metavar='<key>',
+ help=_('set value for this key'),
+ )
+ set_group.add_argument(
+ 'values', metavar='<value>', nargs='+', help=_('Value to set')
+ )
+ get_group = parser.add_argument_group('getting values')
+ get_group.add_argument(
+ 'keys',
+ metavar='<keys>',
+ nargs='*',
+ help=_('one or more keys separated by space'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ if options.set:
+ return self._set_config(options)
+ else:
+ return self._get_config(options)
+
+ def _get_config(self, options):
+ def _on_get_config(config):
+ string = ''
+ for key in sorted(config):
+ if key not in options.values:
+ continue
+
+ color = '{!white,black,bold!}'
+ value = config[key]
+ try:
+ color = colors.type_color[type(value)]
+ except KeyError:
+ pass
+
+ # We need to format dicts for printing
+ if isinstance(value, dict):
+ import pprint
+
+ value = pprint.pformat(value, 2, 80)
+ new_value = []
+ for line in value.splitlines():
+ new_value.append('%s%s' % (color, line))
+ value = '\n'.join(new_value)
+
+ string += '%s: %s%s\n' % (key, color, value)
+ self.console.write(string.strip())
+
+ return client.core.get_config().addCallback(_on_get_config)
+
+ def _set_config(self, options):
+ config = component.get('CoreConfig')
+ key = options.set
+ val = ' '.join(options.values)
+
+ try:
+ val = simple_eval(val)
+ except SyntaxError as ex:
+ self.console.write('{!error!}%s' % ex)
+ return
+
+ if key not in config:
+ self.console.write('{!error!}Invalid key: %s' % key)
+ return
+
+ if not isinstance(config[key], type(val)):
+ try:
+ val = type(config[key])(val)
+ except TypeError:
+ self.config.write(
+ '{!error!}Configuration value provided has incorrect type.'
+ )
+ return
+
+ def on_set_config(result):
+ self.console.write('{!success!}Configuration value successfully updated.')
+
+ self.console.write('Setting "%s" to: %s' % (key, val))
+ return client.core.set_config({key: val}).addCallback(on_set_config)
+
+ def complete(self, text):
+ return [k for k in component.get('CoreConfig') if k.startswith(text)]
diff --git a/deluge/ui/console/cmdline/commands/connect.py b/deluge/ui/console/cmdline/commands/connect.py
new file mode 100644
index 0000000..6588f7a
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/connect.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Connect to a new deluge server"""
+
+ usage = _('Usage: connect <host[:port]> [<username>] [<password>]')
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'host', help=_('Daemon host and port'), metavar='<host[:port]>'
+ )
+ parser.add_argument(
+ 'username', help=_('Username'), metavar='<username>', nargs='?', default=''
+ )
+ parser.add_argument(
+ 'password', help=_('Password'), metavar='<password>', nargs='?', default=''
+ )
+
+ def add_parser(self, subparsers):
+ parser = subparsers.add_parser(
+ self.name, help=self.__doc__, description=self.__doc__, prog='connect'
+ )
+ self.add_arguments(parser)
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ host = options.host
+ try:
+ host, port = host.split(':')
+ port = int(port)
+ except ValueError:
+ port = 58846
+
+ def do_connect():
+ d = client.connect(host, port, options.username, options.password)
+
+ def on_connect(result):
+ if self.console.interactive:
+ self.console.write('{!success!}Connected to %s:%s!' % (host, port))
+ return component.start()
+
+ def on_connect_fail(result):
+ try:
+ msg = result.value.exception_msg
+ except AttributeError:
+ msg = result.value.message
+ self.console.write(
+ '{!error!}Failed to connect to %s:%s with reason: %s'
+ % (host, port, msg)
+ )
+ return result
+
+ d.addCallbacks(on_connect, on_connect_fail)
+ return d
+
+ if client.connected():
+
+ def on_disconnect(result):
+ if self.console.statusbars:
+ self.console.statusbars.update_statusbars()
+ return do_connect()
+
+ return client.disconnect().addCallback(on_disconnect)
+ else:
+ return do_connect()
diff --git a/deluge/ui/console/cmdline/commands/debug.py b/deluge/ui/console/cmdline/commands/debug.py
new file mode 100644
index 0000000..3ca06ed
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/debug.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from twisted.internet import defer
+
+import deluge.component as component
+import deluge.log
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Enable and disable debugging"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'state', metavar='<on|off>', choices=['on', 'off'], help=_('The new state')
+ )
+
+ def handle(self, options):
+ if options.state == 'on':
+ deluge.log.set_logger_level('debug')
+ elif options.state == 'off':
+ deluge.log.set_logger_level('error')
+ else:
+ component.get('ConsoleUI').write('{!error!}%s' % self.usage)
+
+ return defer.succeed(True)
+
+ def complete(self, text):
+ return [x for x in ['on', 'off'] if x.startswith(text)]
diff --git a/deluge/ui/console/cmdline/commands/gui.py b/deluge/ui/console/cmdline/commands/gui.py
new file mode 100644
index 0000000..10e4c49
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/gui.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Enable interactive mode"""
+
+ interactive_only = True
+
+ def handle(self, options):
+ console = component.get('ConsoleUI')
+ at = console.set_mode('TorrentList')
+ at.go_top = True
+ at.resume()
diff --git a/deluge/ui/console/cmdline/commands/halt.py b/deluge/ui/console/cmdline/commands/halt.py
new file mode 100644
index 0000000..6355958
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/halt.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Shutdown the deluge server."""
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ def on_shutdown(result):
+ self.console.write('{!success!}Daemon was shutdown')
+
+ def on_shutdown_fail(reason):
+ self.console.write('{!error!}Unable to shutdown daemon: %s' % reason)
+
+ return (
+ client.daemon.shutdown()
+ .addCallback(on_shutdown)
+ .addErrback(on_shutdown_fail)
+ )
diff --git a/deluge/ui/console/cmdline/commands/help.py b/deluge/ui/console/cmdline/commands/help.py
new file mode 100644
index 0000000..2711eea
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/help.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from twisted.internet import defer
+
+import deluge.component as component
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Displays help on other commands"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'commands', metavar='<command>', nargs='*', help=_('One or more commands')
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ self._commands = self.console._commands
+ deferred = defer.succeed(True)
+ if options.commands:
+ for arg in options.commands:
+ try:
+ cmd = self._commands[arg]
+ except KeyError:
+ self.console.write('{!error!}Unknown command %s' % arg)
+ continue
+ try:
+ parser = cmd.create_parser()
+ self.console.write(parser.format_help())
+ except AttributeError:
+ self.console.write(cmd.__doc__ or 'No help for this command')
+ self.console.write(' ')
+ else:
+ self.console.set_batch_write(True)
+ cmds_doc = ''
+ for cmd in sorted(self._commands):
+ if cmd in self._commands[cmd].aliases:
+ continue
+ parser = self._commands[cmd].create_parser()
+ cmd_doc = (
+ '{!info!}'
+ + '%-9s' % self._commands[cmd].name_with_alias
+ + '{!input!} - '
+ + self._commands[cmd].__doc__
+ + '\n '
+ + parser.format_usage()
+ or ''
+ )
+ cmds_doc += parser.formatter.format_colors(cmd_doc)
+ self.console.write(cmds_doc)
+ self.console.write(' ')
+ self.console.write('For help on a specific command, use `<command> --help`')
+ self.console.set_batch_write(False)
+
+ return deferred
+
+ def complete(self, line):
+ return [x for x in component.get('ConsoleUI')._commands if x.startswith(line)]
diff --git a/deluge/ui/console/cmdline/commands/info.py b/deluge/ui/console/cmdline/commands/info.py
new file mode 100644
index 0000000..0d22f76
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/info.py
@@ -0,0 +1,484 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+from os.path import sep as dirsep
+
+import deluge.component as component
+import deluge.ui.console.utils.colors as colors
+from deluge.common import TORRENT_STATE, fsize, fspeed
+from deluge.ui.client import client
+from deluge.ui.common import FILE_PRIORITY
+from deluge.ui.console.utils.format_utils import (
+ f_progressbar,
+ f_seedrank_dash,
+ format_date_never,
+ format_progress,
+ format_time,
+ ftotal_sized,
+ pad_string,
+ remove_formatting,
+ shorten_hash,
+ strwidth,
+ trim_string,
+)
+
+from . import BaseCommand
+
+STATUS_KEYS = [
+ 'state',
+ 'download_location',
+ 'tracker_host',
+ 'tracker_status',
+ 'next_announce',
+ 'name',
+ 'total_size',
+ 'progress',
+ 'num_seeds',
+ 'total_seeds',
+ 'num_peers',
+ 'total_peers',
+ 'eta',
+ 'download_payload_rate',
+ 'upload_payload_rate',
+ 'ratio',
+ 'distributed_copies',
+ 'num_pieces',
+ 'piece_length',
+ 'total_done',
+ 'files',
+ 'file_priorities',
+ 'file_progress',
+ 'peers',
+ 'is_seed',
+ 'is_finished',
+ 'active_time',
+ 'seeding_time',
+ 'time_since_transfer',
+ 'last_seen_complete',
+ 'seed_rank',
+ 'all_time_download',
+ 'total_uploaded',
+ 'total_payload_download',
+ 'total_payload_upload',
+ 'time_added',
+]
+
+# Add filter specific state to torrent states
+STATES = ['Active'] + TORRENT_STATE
+
+
+class Command(BaseCommand):
+ """Show information about the torrents"""
+
+ sort_help = 'sort items. Possible keys: ' + ', '.join(STATUS_KEYS)
+
+ epilog = """
+ You can give the first few characters of a torrent-id to identify the torrent.
+
+ Tab Completion in interactive mode (info *pattern*<tab>):\n
+ | First press of <tab> will output up to 15 matches;
+ | hitting <tab> a second time, will print 15 more matches;
+ | and a third press will print all remaining matches.
+ | (To modify behaviour of third <tab>, set `third_tab_lists_all` to False)
+"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-v',
+ '--verbose',
+ action='store_true',
+ default=False,
+ dest='verbose',
+ help=_('Show more information per torrent.'),
+ )
+ parser.add_argument(
+ '-d',
+ '--detailed',
+ action='store_true',
+ default=False,
+ dest='detailed',
+ help=_('Show more detailed information including files and peers.'),
+ )
+ parser.add_argument(
+ '-s',
+ '--state',
+ action='store',
+ dest='state',
+ help=_('Show torrents with state STATE: %s.' % (', '.join(STATES))),
+ )
+ parser.add_argument(
+ '--sort',
+ action='store',
+ type=str,
+ default='',
+ dest='sort',
+ help=self.sort_help,
+ )
+ parser.add_argument(
+ '--sort-reverse',
+ action='store',
+ type=str,
+ default='',
+ dest='sort_rev',
+ help=_('Same as --sort but items are in reverse order.'),
+ )
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='*',
+ help=_('One or more torrent ids. If none is given, list all'),
+ )
+
+ def add_subparser(self, subparsers):
+ parser = subparsers.add_parser(
+ self.name,
+ prog=self.name,
+ help=self.__doc__,
+ description=self.__doc__,
+ epilog=self.epilog,
+ )
+ self.add_arguments(parser)
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ # Compile a list of torrent_ids to request the status of
+ torrent_ids = []
+
+ if options.torrent_ids:
+ for t_id in options.torrent_ids:
+ torrent_ids.extend(self.console.match_torrent(t_id))
+ else:
+ torrent_ids.extend(self.console.match_torrent(''))
+
+ def on_torrents_status(status):
+ # Print out the information for each torrent
+ sort_key = options.sort
+ sort_reverse = False
+ if not sort_key:
+ sort_key = options.sort_rev
+ sort_reverse = True
+ if not sort_key:
+ sort_key = 'name'
+ sort_reverse = False
+ if sort_key not in STATUS_KEYS:
+ self.console.write('')
+ self.console.write(
+ '{!error!}Unknown sort key: ' + sort_key + ', will sort on name'
+ )
+ sort_key = 'name'
+ sort_reverse = False
+ for key, value in sorted(
+ list(status.items()),
+ key=lambda x: x[1].get(sort_key),
+ reverse=sort_reverse,
+ ):
+ self.show_info(key, status[key], options.verbose, options.detailed)
+
+ def on_torrents_status_fail(reason):
+ self.console.write('{!error!}Error getting torrent info: %s' % reason)
+
+ status_dict = {'id': torrent_ids}
+
+ if options.state:
+ options.state = options.state.capitalize()
+ if options.state in STATES:
+ status_dict.state = options.state
+ else:
+ self.console.write('Invalid state: %s' % options.state)
+ self.console.write('Possible values are: %s.' % (', '.join(STATES)))
+ return
+
+ d = client.core.get_torrents_status(status_dict, STATUS_KEYS)
+ d.addCallback(on_torrents_status)
+ d.addErrback(on_torrents_status_fail)
+ return d
+
+ def show_file_info(self, torrent_id, status):
+ spaces_per_level = 2
+
+ if hasattr(self.console, 'screen'):
+ cols = self.console.screen.cols
+ else:
+ cols = 80
+
+ prevpath = []
+ for index, torrent_file in enumerate(status['files']):
+ filename = torrent_file['path'].split(dirsep)[-1]
+ filepath = torrent_file['path'].split(dirsep)[:-1]
+
+ for depth, subdir in enumerate(filepath):
+ indent = ' ' * depth * spaces_per_level
+ if depth >= len(prevpath):
+ self.console.write('%s{!cyan!}%s' % (indent, subdir))
+ elif subdir != prevpath[depth]:
+ self.console.write('%s{!cyan!}%s' % (indent, subdir))
+
+ depth = len(filepath)
+
+ indent = ' ' * depth * spaces_per_level
+
+ col_filename = indent + filename
+ col_size = ' ({!cyan!}%s{!input!})' % fsize(torrent_file['size'])
+ col_progress = ' {!input!}%.2f%%' % (status['file_progress'][index] * 100)
+
+ col_priority = ' {!info!}Priority: '
+
+ file_priority = FILE_PRIORITY[status['file_priorities'][index]]
+
+ if status['file_progress'][index] != 1.0:
+ if file_priority == 'Skip':
+ col_priority += '{!error!}'
+ else:
+ col_priority += '{!success!}'
+ else:
+ col_priority += '{!input!}'
+ col_priority += file_priority
+
+ def tlen(string):
+ return strwidth(remove_formatting(string))
+
+ col_all_info = col_size + col_progress + col_priority
+ # Check how much space we've got left after writing all the info
+ space_left = cols - tlen(col_all_info)
+ # And how much we will potentially have with the longest possible column
+ maxlen_space_left = cols - tlen(' (1000.0 MiB) 100.00% Priority: Normal')
+ if maxlen_space_left > tlen(col_filename) + 1:
+ # If there is enough space, pad it all nicely
+ col_all_info = ''
+ col_all_info += ' ('
+ spaces_to_add = tlen(' (1000.0 MiB)') - tlen(col_size)
+ col_all_info += ' ' * spaces_to_add
+ col_all_info += col_size[2:]
+ spaces_to_add = tlen(' 100.00%') - tlen(col_progress)
+ col_all_info += ' ' * spaces_to_add
+ col_all_info += col_progress
+ spaces_to_add = tlen(' Priority: Normal') - tlen(col_priority)
+ col_all_info += col_priority
+ col_all_info += ' ' * spaces_to_add
+ # And remember to put it to the left!
+ col_filename = pad_string(
+ col_filename, maxlen_space_left - 2, side='right'
+ )
+ elif space_left > tlen(col_filename) + 1:
+ # If there is enough space, put the info to the right
+ col_filename = pad_string(col_filename, space_left - 2, side='right')
+ else:
+ # And if there is not, shorten the name
+ col_filename = trim_string(col_filename, space_left, True)
+ self.console.write(col_filename + col_all_info)
+
+ prevpath = filepath
+
+ def show_peer_info(self, torrent_id, status):
+ if len(status['peers']) == 0:
+ self.console.write(' None')
+ else:
+ s = ''
+ for peer in status['peers']:
+ if peer['seed']:
+ s += '%sSeed\t{!input!}' % colors.state_color['Seeding']
+ else:
+ s += '%sPeer\t{!input!}' % colors.state_color['Downloading']
+
+ s += peer['country'] + '\t'
+
+ if peer['ip'].count(':') == 1:
+ # IPv4
+ s += peer['ip']
+ else:
+ # IPv6
+ s += '[%s]:%s' % (
+ ':'.join(peer['ip'].split(':')[:-1]),
+ peer['ip'].split(':')[-1],
+ )
+
+ c = peer['client']
+ s += '\t' + c
+
+ if len(c) < 16:
+ s += '\t\t'
+ else:
+ s += '\t'
+ s += '%s%s\t%s%s' % (
+ colors.state_color['Seeding'],
+ fspeed(peer['up_speed']),
+ colors.state_color['Downloading'],
+ fspeed(peer['down_speed']),
+ )
+ s += '\n'
+
+ self.console.write(s[:-1])
+
+ def show_info(self, torrent_id, status, verbose=False, detailed=False):
+ """
+ Writes out the torrents information to the screen.
+
+ Format depends on switches given.
+ """
+ self.console.set_batch_write(True)
+
+ if hasattr(self.console, 'screen'):
+ cols = self.console.screen.cols
+ else:
+ cols = 80
+
+ sep = ' '
+
+ if verbose or detailed:
+ self.console.write('{!info!}Name: {!input!}%s' % (status['name']))
+ self.console.write('{!info!}ID: {!input!}%s' % (torrent_id))
+ s = '{!info!}State: %s%s' % (
+ colors.state_color[status['state']],
+ status['state'],
+ )
+ # Only show speed if active
+ if status['state'] in ('Seeding', 'Downloading'):
+ if status['state'] != 'Seeding':
+ s += sep
+ s += '{!info!}Down Speed: {!input!}%s' % fspeed(
+ status['download_payload_rate'], shortform=True
+ )
+ s += sep
+ s += '{!info!}Up Speed: {!input!}%s' % fspeed(
+ status['upload_payload_rate'], shortform=True
+ )
+ self.console.write(s)
+
+ if status['state'] in ('Seeding', 'Downloading', 'Queued'):
+ s = '{!info!}Seeds: {!input!}%s (%s)' % (
+ status['num_seeds'],
+ status['total_seeds'],
+ )
+ s += sep
+ s += '{!info!}Peers: {!input!}%s (%s)' % (
+ status['num_peers'],
+ status['total_peers'],
+ )
+ s += sep
+ s += (
+ '{!info!}Availability: {!input!}%.2f' % status['distributed_copies']
+ )
+ s += sep
+ s += '{!info!}Seed Rank: {!input!}%s' % f_seedrank_dash(
+ status['seed_rank'], status['seeding_time']
+ )
+ self.console.write(s)
+
+ total_done = fsize(status['total_done'], shortform=True)
+ total_size = fsize(status['total_size'], shortform=True)
+ if total_done == total_size:
+ s = '{!info!}Size: {!input!}%s' % (total_size)
+ else:
+ s = '{!info!}Size: {!input!}%s/%s' % (total_done, total_size)
+ s += sep
+ s += '{!info!}Downloaded: {!input!}%s' % fsize(
+ status['all_time_download'], shortform=True
+ )
+ s += sep
+ s += '{!info!}Uploaded: {!input!}%s' % fsize(
+ status['total_uploaded'], shortform=True
+ )
+ s += sep
+ s += '{!info!}Share Ratio: {!input!}%.2f' % status['ratio']
+ self.console.write(s)
+
+ s = '{!info!}ETA: {!input!}%s' % format_time(status['eta'])
+ s += sep
+ s += '{!info!}Seeding: {!input!}%s' % format_time(status['seeding_time'])
+ s += sep
+ s += '{!info!}Active: {!input!}%s' % format_time(status['active_time'])
+ self.console.write(s)
+
+ s = '{!info!}Last Transfer: {!input!}%s' % format_time(
+ status['time_since_transfer']
+ )
+ s += sep
+ s += '{!info!}Complete Seen: {!input!}%s' % format_date_never(
+ status['last_seen_complete']
+ )
+ self.console.write(s)
+
+ s = '{!info!}Tracker: {!input!}%s' % status['tracker_host']
+ self.console.write(s)
+
+ self.console.write(
+ '{!info!}Tracker status: {!input!}%s' % status['tracker_status']
+ )
+
+ if not status['is_finished']:
+ pbar = f_progressbar(
+ status['progress'], cols - (13 + len('%.2f%%' % status['progress']))
+ )
+ s = '{!info!}Progress: {!input!}%.2f%% %s' % (status['progress'], pbar)
+ self.console.write(s)
+
+ s = '{!info!}Download Folder: {!input!}%s' % status['download_location']
+ self.console.write(s + '\n')
+
+ if detailed:
+ self.console.write('{!info!}Files in torrent')
+ self.show_file_info(torrent_id, status)
+ self.console.write('{!info!}Connected peers')
+ self.show_peer_info(torrent_id, status)
+ else:
+ up_color = colors.state_color['Seeding']
+ down_color = colors.state_color['Downloading']
+
+ s = '%s%s' % (
+ colors.state_color[status['state']],
+ '[' + status['state'][0] + ']',
+ )
+
+ s += ' {!info!}' + format_progress(status['progress']).rjust(6, ' ')
+ s += ' {!input!}%s' % (status['name'])
+
+ # Shorten the ID if it's necessary. Pretty hacky
+ # XXX: should make a nice function for it that can partition and shorten stuff
+ space_left = cols - strwidth('[S] 99.99% ' + status['name'])
+
+ if self.console.interactive and space_left >= len(sep + torrent_id):
+ # Not enough line space so shorten the hash (for interactive mode).
+ torrent_id = shorten_hash(torrent_id, space_left)
+ s += sep
+ s += '{!cyan!}%s' % torrent_id
+ self.console.write(s)
+
+ dl_info = '{!info!}DL: {!input!}'
+ dl_info += '%s' % ftotal_sized(
+ status['all_time_download'], status['total_payload_download']
+ )
+
+ if status['download_payload_rate'] > 0:
+ dl_info += ' @ %s%s' % (
+ down_color,
+ fspeed(status['download_payload_rate'], shortform=True),
+ )
+
+ ul_info = ' {!info!}UL: {!input!}'
+ ul_info += '%s' % ftotal_sized(
+ status['total_uploaded'], status['total_payload_upload']
+ )
+ if status['upload_payload_rate'] > 0:
+ ul_info += ' @ %s%s' % (
+ up_color,
+ fspeed(status['upload_payload_rate'], shortform=True),
+ )
+
+ eta = ' {!info!}ETA: {!magenta!}%s' % format_time(status['eta'])
+
+ self.console.write(' ' + dl_info + ul_info + eta + '\n')
+
+ self.console.set_batch_write(False)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/manage.py b/deluge/ui/console/cmdline/commands/manage.py
new file mode 100644
index 0000000..6375a74
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/manage.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from twisted.internet import defer
+
+import deluge.component as component
+from deluge.ui.client import client
+from deluge.ui.console.utils.common import TORRENT_OPTIONS
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Show and manage per-torrent options"""
+
+ usage = _('Usage: manage <torrent-id> [--set <key> <value>] [<key> [<key>...] ]')
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent',
+ metavar='<torrent>',
+ help=_('an expression matched against torrent ids and torrent names'),
+ )
+ set_group = parser.add_argument_group('setting a value')
+ set_group.add_argument(
+ '-s',
+ '--set',
+ action='store',
+ metavar='<key>',
+ help=_('set value for this key'),
+ )
+ set_group.add_argument(
+ 'values', metavar='<value>', nargs='+', help=_('Value to set')
+ )
+ get_group = parser.add_argument_group('getting values')
+ get_group.add_argument(
+ 'keys',
+ metavar='<keys>',
+ nargs='*',
+ help=_('one or more keys separated by space'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ if options.set:
+ return self._set_option(options)
+ else:
+ return self._get_option(options)
+
+ def _get_option(self, options):
+ def on_torrents_status(status):
+ for torrentid, data in status.items():
+ self.console.write('')
+ if 'name' in data:
+ self.console.write('{!info!}Name: {!input!}%s' % data.get('name'))
+ self.console.write('{!info!}ID: {!input!}%s' % torrentid)
+ for k, v in data.items():
+ if k != 'name':
+ self.console.write('{!info!}%s: {!input!}%s' % (k, v))
+
+ def on_torrents_status_fail(reason):
+ self.console.write('{!error!}Failed to get torrent data.')
+
+ torrent_ids = self.console.match_torrent(options.torrent)
+
+ request_options = []
+ for opt in options.values:
+ if opt not in TORRENT_OPTIONS:
+ self.console.write('{!error!}Unknown torrent option: %s' % opt)
+ return
+ request_options.append(opt)
+ if not request_options:
+ request_options = list(TORRENT_OPTIONS)
+ request_options.append('name')
+
+ d = client.core.get_torrents_status({'id': torrent_ids}, request_options)
+ d.addCallbacks(on_torrents_status, on_torrents_status_fail)
+ return d
+
+ def _set_option(self, options):
+ deferred = defer.Deferred()
+ key = options.set
+ val = ' '.join(options.values)
+ torrent_ids = self.console.match_torrent(options.torrent)
+
+ if key not in TORRENT_OPTIONS:
+ self.console.write('{!error!}Invalid key: %s' % key)
+ return
+
+ val = TORRENT_OPTIONS[key](val)
+
+ def on_set_config(result):
+ self.console.write('{!success!}Torrent option successfully updated.')
+ deferred.callback(True)
+
+ self.console.write(
+ 'Setting %s to %s for torrents %s..' % (key, val, torrent_ids)
+ )
+ client.core.set_torrent_options(torrent_ids, {key: val}).addCallback(
+ on_set_config
+ )
+ return deferred
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/move.py b/deluge/ui/console/cmdline/commands/move.py
new file mode 100644
index 0000000..13e475e
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/move.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os.path
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Move torrents' storage location"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids'),
+ )
+ parser.add_argument(
+ 'path', metavar='<path>', help=_('The path to move the torrents to')
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if os.path.exists(options.path) and not os.path.isdir(options.path):
+ self.console.write(
+ '{!error!}Cannot Move Download Folder: %s exists and is not a directory'
+ % options.path
+ )
+ return
+
+ ids = []
+ names = []
+ for t_id in options.torrent_ids:
+ tid = self.console.match_torrent(t_id)
+ ids.extend(tid)
+ names.append(self.console.get_torrent_name(tid))
+
+ def on_move(res):
+ msg = 'Moved "%s" to %s' % (', '.join(names), options.path)
+ self.console.write(msg)
+ log.info(msg)
+
+ d = client.core.move_storage(ids, options.path)
+ d.addCallback(on_move)
+ return d
+
+ def complete(self, line):
+ line = os.path.abspath(os.path.expanduser(line))
+ ret = []
+ if os.path.exists(line):
+ # This is a correct path, check to see if it's a directory
+ if os.path.isdir(line):
+ # Directory, so we need to show contents of directory
+ # ret.extend(os.listdir(line))
+ for f in os.listdir(line):
+ # Skip hidden
+ if f.startswith('.'):
+ continue
+ f = os.path.join(line, f)
+ if os.path.isdir(f):
+ f += '/'
+ ret.append(f)
+ else:
+ # This is a file, but we could be looking for another file that
+ # shares a common prefix.
+ for f in os.listdir(os.path.dirname(line)):
+ if f.startswith(os.path.split(line)[1]):
+ ret.append(os.path.join(os.path.dirname(line), f))
+ else:
+ # This path does not exist, so lets do a listdir on it's parent
+ # and find any matches.
+ ret = []
+ if os.path.isdir(os.path.dirname(line)):
+ for f in os.listdir(os.path.dirname(line)):
+ if f.startswith(os.path.split(line)[1]):
+ p = os.path.join(os.path.dirname(line), f)
+
+ if os.path.isdir(p):
+ p += '/'
+ ret.append(p)
+ return ret
diff --git a/deluge/ui/console/cmdline/commands/pause.py b/deluge/ui/console/cmdline/commands/pause.py
new file mode 100644
index 0000000..1f7ef31
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/pause.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Pause torrents"""
+
+ usage = 'pause [ * | <torrent-id> [<torrent-id> ...] ]'
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids. Use "*" to pause all torrents'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if options.torrent_ids[0] == '*':
+ client.core.pause_session()
+ return
+
+ torrent_ids = []
+ for arg in options.torrent_ids:
+ torrent_ids.extend(self.console.match_torrent(arg))
+
+ if torrent_ids:
+ return client.core.pause_torrent(torrent_ids)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/plugin.py b/deluge/ui/console/cmdline/commands/plugin.py
new file mode 100644
index 0000000..fafc77a
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/plugin.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+import deluge.configmanager
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Manage plugins"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-l',
+ '--list',
+ action='store_true',
+ default=False,
+ dest='list',
+ help=_('Lists available plugins'),
+ )
+ parser.add_argument(
+ '-s',
+ '--show',
+ action='store_true',
+ default=False,
+ dest='show',
+ help=_('Shows enabled plugins'),
+ )
+ parser.add_argument(
+ '-e', '--enable', dest='enable', nargs='+', help=_('Enables a plugin')
+ )
+ parser.add_argument(
+ '-d', '--disable', dest='disable', nargs='+', help=_('Disables a plugin')
+ )
+ parser.add_argument(
+ '-r',
+ '--reload',
+ action='store_true',
+ default=False,
+ dest='reload',
+ help=_('Reload list of available plugins'),
+ )
+ parser.add_argument(
+ '-i', '--install', help=_('Install a plugin from an .egg file')
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if options.reload:
+ client.core.pluginmanager.rescan_plugins()
+ self.console.write('{!green!}Plugin list successfully reloaded')
+ return
+
+ elif options.list:
+
+ def on_available_plugins(result):
+ self.console.write('{!info!}Available Plugins:')
+ for p in result:
+ self.console.write('{!input!} ' + p)
+
+ return client.core.get_available_plugins().addCallback(on_available_plugins)
+
+ elif options.show:
+
+ def on_enabled_plugins(result):
+ self.console.write('{!info!}Enabled Plugins:')
+ for p in result:
+ self.console.write('{!input!} ' + p)
+
+ return client.core.get_enabled_plugins().addCallback(on_enabled_plugins)
+
+ elif options.enable:
+
+ def on_available_plugins(result):
+ plugins = {}
+ for p in result:
+ plugins[p.lower()] = p
+ for arg in options.enable:
+ if arg.lower() in plugins:
+ client.core.enable_plugin(plugins[arg.lower()])
+
+ return client.core.get_available_plugins().addCallback(on_available_plugins)
+
+ elif options.disable:
+
+ def on_enabled_plugins(result):
+ plugins = {}
+ for p in result:
+ plugins[p.lower()] = p
+ for arg in options.disable:
+ if arg.lower() in plugins:
+ client.core.disable_plugin(plugins[arg.lower()])
+
+ return client.core.get_enabled_plugins().addCallback(on_enabled_plugins)
+
+ elif options.install:
+ import os.path
+ from base64 import b64encode
+ import shutil
+
+ filepath = options.install
+
+ if not os.path.exists(filepath):
+ self.console.write('{!error!}Invalid path: %s' % filepath)
+ return
+
+ config_dir = deluge.configmanager.get_config_dir()
+ filename = os.path.split(filepath)[1]
+ shutil.copyfile(filepath, os.path.join(config_dir, 'plugins', filename))
+
+ client.core.rescan_plugins()
+
+ if not client.is_localhost():
+ # We need to send this plugin to the daemon
+ with open(filepath, 'rb') as _file:
+ filedump = b64encode(_file.read())
+ try:
+ client.core.upload_plugin(filename, filedump)
+ client.core.rescan_plugins()
+ except Exception:
+ self.console.write(
+ '{!error!}An error occurred, plugin was not installed'
+ )
+
+ self.console.write(
+ '{!green!}Plugin was successfully installed: %s' % filename
+ )
+
+ def complete(self, line):
+ return component.get('ConsoleUI').tab_complete_path(
+ line, ext='.egg', sort='name', dirs_first=-1
+ )
diff --git a/deluge/ui/console/cmdline/commands/quit.py b/deluge/ui/console/cmdline/commands/quit.py
new file mode 100644
index 0000000..261a01a
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/quit.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Exit the client"""
+
+ aliases = ['exit']
+ interactive_only = True
+
+ def handle(self, options):
+ component.get('ConsoleUI').quit()
diff --git a/deluge/ui/console/cmdline/commands/recheck.py b/deluge/ui/console/cmdline/commands/recheck.py
new file mode 100644
index 0000000..c9b6360
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/recheck.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Forces a recheck of the torrent data"""
+
+ usage = 'recheck [ * | <torrent-id> [<torrent-id> ...] ]'
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if options.torrent_ids[0] == '*':
+ client.core.force_recheck(self.console.match_torrent(''))
+ return
+
+ torrent_ids = []
+ for arg in options.torrent_ids:
+ torrent_ids.extend(self.console.match_torrent(arg))
+
+ if torrent_ids:
+ return client.core.force_recheck(torrent_ids)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/resume.py b/deluge/ui/console/cmdline/commands/resume.py
new file mode 100644
index 0000000..1f62c5f
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/resume.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Resume torrents"""
+
+ usage = _('Usage: resume [ * | <torrent-id> [<torrent-id> ...] ]')
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids. Use "*" to resume all torrents'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if options.torrent_ids[0] == '*':
+ client.core.resume_session()
+ return
+
+ torrent_ids = []
+ for t_id in options.torrent_ids:
+ torrent_ids.extend(self.console.match_torrent(t_id))
+
+ if torrent_ids:
+ return client.core.resume_torrent(torrent_ids)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/rm.py b/deluge/ui/console/cmdline/commands/rm.py
new file mode 100644
index 0000000..ff3125d
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/rm.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Remove a torrent"""
+
+ aliases = ['del']
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--remove_data',
+ action='store_true',
+ default=False,
+ help=_('Also removes the torrent data'),
+ )
+ parser.add_argument(
+ '-c',
+ '--confirm',
+ action='store_true',
+ default=False,
+ help=_('List the matching torrents without removing.'),
+ )
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ torrent_ids = self.console.match_torrents(options.torrent_ids)
+
+ if not options.confirm:
+ self.console.write(
+ '{!info!}%d %s %s{!info!}'
+ % (
+ len(torrent_ids),
+ _n('torrent', 'torrents', len(torrent_ids)),
+ _n('match', 'matches', len(torrent_ids)),
+ )
+ )
+ for t_id in torrent_ids:
+ name = self.console.get_torrent_name(t_id)
+ self.console.write('* %-50s (%s)' % (name, t_id))
+ self.console.write(
+ _('Confirm with -c to remove the listed torrents (Count: %d)')
+ % len(torrent_ids)
+ )
+ return
+
+ def on_removed_finished(errors):
+ if errors:
+ self.console.write('Error(s) occured when trying to delete torrent(s).')
+ for t_id, e_msg in errors:
+ self.console.write('Error removing torrent %s : %s' % (t_id, e_msg))
+
+ log.info('Removing %d torrents', len(torrent_ids))
+ d = client.core.remove_torrents(torrent_ids, options.remove_data)
+ d.addCallback(on_removed_finished)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/status.py b/deluge/ui/console/cmdline/commands/status.py
new file mode 100644
index 0000000..948ad6b
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/status.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from twisted.internet import defer
+
+import deluge.component as component
+from deluge.common import TORRENT_STATE, fspeed
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Shows various status information from the daemon"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-r',
+ '--raw',
+ action='store_true',
+ default=False,
+ dest='raw',
+ help=_(
+ 'Raw values for upload/download rates (without KiB/s suffix)'
+ '(useful for scripts that want to do their own parsing)'
+ ),
+ )
+ parser.add_argument(
+ '-n',
+ '--no-torrents',
+ action='store_false',
+ default=True,
+ dest='show_torrents',
+ help=_('Do not show torrent status (Improves command speed)'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ self.status = None
+ self.torrents = 1 if options.show_torrents else 0
+ self.raw = options.raw
+
+ def on_session_status(status):
+ self.status = status
+
+ def on_torrents_status(status):
+ self.torrents = status
+
+ def on_torrents_status_fail(reason):
+ log.warning('Failed to retrieve session status: %s', reason)
+ self.torrents = -2
+
+ deferreds = []
+
+ ds = client.core.get_session_status(
+ ['num_peers', 'payload_upload_rate', 'payload_download_rate', 'dht_nodes']
+ )
+ ds.addCallback(on_session_status)
+ deferreds.append(ds)
+
+ if options.show_torrents:
+ dt = client.core.get_torrents_status({}, ['state'])
+ dt.addCallback(on_torrents_status)
+ dt.addErrback(on_torrents_status_fail)
+ deferreds.append(dt)
+
+ return defer.DeferredList(deferreds).addCallback(self.print_status)
+
+ def print_status(self, *args):
+ self.console.set_batch_write(True)
+ if self.raw:
+ self.console.write(
+ '{!info!}Total upload: %f' % self.status['payload_upload_rate']
+ )
+ self.console.write(
+ '{!info!}Total download: %f' % self.status['payload_download_rate']
+ )
+ else:
+ self.console.write(
+ '{!info!}Total upload: %s' % fspeed(self.status['payload_upload_rate'])
+ )
+ self.console.write(
+ '{!info!}Total download: %s'
+ % fspeed(self.status['payload_download_rate'])
+ )
+ self.console.write('{!info!}DHT Nodes: %i' % self.status['dht_nodes'])
+
+ if isinstance(self.torrents, int):
+ if self.torrents == -2:
+ self.console.write('{!error!}Error getting torrent info')
+ else:
+ self.console.write('{!info!}Total torrents: %i' % len(self.torrents))
+ state_counts = {}
+ for state in TORRENT_STATE:
+ state_counts[state] = 0
+ for t in self.torrents:
+ s = self.torrents[t]
+ state_counts[s['state']] += 1
+ for state in TORRENT_STATE:
+ self.console.write('{!info!} %s: %i' % (state, state_counts[state]))
+
+ self.console.set_batch_write(False)
diff --git a/deluge/ui/console/cmdline/commands/update_tracker.py b/deluge/ui/console/cmdline/commands/update_tracker.py
new file mode 100644
index 0000000..591b951
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/update_tracker.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Update tracker for torrent(s)"""
+
+ usage = 'update_tracker [ * | <torrent-id> [<torrent-id> ...] ]'
+ aliases = ['reannounce']
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help='One or more torrent ids. "*" updates all torrents',
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ args = options.torrent_ids
+ if options.torrent_ids[0] == '*':
+ args = ['']
+
+ torrent_ids = []
+ for arg in args:
+ torrent_ids.extend(self.console.match_torrent(arg))
+
+ client.core.force_reannounce(torrent_ids)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)