summaryrefslogtreecommitdiffstats
path: root/deluge/ui/console/cmdline/commands/info.py
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/ui/console/cmdline/commands/info.py')
-rw-r--r--deluge/ui/console/cmdline/commands/info.py488
1 files changed, 488 insertions, 0 deletions
diff --git a/deluge/ui/console/cmdline/commands/info.py b/deluge/ui/console/cmdline/commands/info.py
new file mode 100644
index 0000000..7ea9a67
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/info.py
@@ -0,0 +1,488 @@
+#
+# 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 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',
+ 'label',
+]
+
+# 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(
+ 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(f'{indent}{{!cyan!}}{subdir}')
+ elif subdir != prevpath[depth]:
+ self.console.write(f'{indent}{{!cyan!}}{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 += '[{}]:{}'.format(
+ ':'.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 += '{}{}\t{}{}'.format(
+ 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: {}{}'.format(
+ 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!}}{} ({})'.format(
+ status['num_seeds'],
+ status['total_seeds'],
+ )
+ s += sep
+ s += '{{!info!}}Peers: {{!input!}}{} ({})'.format(
+ 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 = f'{{!info!}}Size: {{!input!}}{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}% {}'.format(
+ status['progress'], pbar
+ )
+ self.console.write(s)
+
+ s = '{!info!}Download Folder: {!input!}%s' % status['download_location']
+ self.console.write(s)
+
+ if 'label' in status:
+ s = '{!info!}Label: {!input!}%s' % status['label']
+ self.console.write(s)
+
+ if detailed:
+ self.console.write('\n{!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 = '{}{}'.format(
+ 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 += ' @ {}{}'.format(
+ 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 += ' @ {}{}'.format(
+ 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)