[#2417] [UI] Add Last Transfer to torrent status and torrentview columns

- Create new status entry `time_since_transfer` and getter that is
   calculated from lt time_since_upload and time_since_download.
 - Add/update all UIs and formatters.
 - Included update to console layout to match other uis
This commit is contained in:
Calum Lind 2017-03-15 22:39:38 +00:00
commit 036154fc36
17 changed files with 285 additions and 192 deletions

View file

@ -427,7 +427,7 @@ def ftime(secs):
""" """
if secs == 0: if secs <= 0:
time_str = '' time_str = ''
elif secs < 60: elif secs < 60:
time_str = '{:d}s'.format(secs) time_str = '{:d}s'.format(secs)

View file

@ -907,6 +907,14 @@ class Torrent(object):
return progress return progress
def get_time_since_transfer(self):
"""The time since either upload/download from peers"""
time_since = (self.status.time_since_download, self.status.time_since_upload)
try:
return min(x for x in time_since if x != -1)
except ValueError:
return -1
def get_status(self, keys, diff=False, update=False, all_keys=False): def get_status(self, keys, diff=False, update=False, all_keys=False):
"""Returns the status of the torrent based on the keys provided """Returns the status of the torrent based on the keys provided
@ -1038,6 +1046,7 @@ class Torrent(object):
'super_seeding': lambda: self.status.super_seeding, 'super_seeding': lambda: self.status.super_seeding,
'time_since_download': lambda: self.status.time_since_download, 'time_since_download': lambda: self.status.time_since_download,
'time_since_upload': lambda: self.status.time_since_upload, 'time_since_upload': lambda: self.status.time_since_upload,
'time_since_transfer': self.get_time_since_transfer
} }
def pause(self): def pause(self):

View file

@ -47,48 +47,96 @@ STATE_TRANSLATION = {
} }
TORRENT_DATA_FIELD = { TORRENT_DATA_FIELD = {
'queue': {'name': '#', 'status': ['queue']}, 'queue':
'name': {'name': _('Name'), 'status': ['state', 'name']}, {'name': '#', 'status': ['queue']},
'progress_state': {'name': _('Progress'), 'status': ['progress', 'state']}, 'name':
'state': {'name': _('State'), 'status': ['state']}, {'name': _('Name'), 'status': ['state', 'name']},
'progress': {'name': _('Progress'), 'status': ['progress']}, 'progress_state':
'size': {'name': _('Size'), 'status': ['total_wanted']}, {'name': _('Progress'), 'status': ['progress', 'state']},
'downloaded': {'name': _('Downloaded'), 'status': ['all_time_download']}, 'state':
'uploaded': {'name': _('Uploaded'), 'status': ['total_uploaded']}, {'name': _('State'), 'status': ['state']},
'remaining': {'name': _('Remaining'), 'status': ['total_remaining']}, 'progress':
'ratio': {'name': _('Ratio'), 'status': ['ratio']}, {'name': _('Progress'), 'status': ['progress']},
'download_speed': {'name': _('Down Speed'), 'status': ['download_payload_rate']}, 'size':
'upload_speed': {'name': _('Up Speed'), 'status': ['upload_payload_rate']}, {'name': _('Size'), 'status': ['total_wanted']},
'max_download_speed': {'name': _('Down Limit'), 'status': ['max_download_speed']}, 'downloaded':
'max_upload_speed': {'name': _('Up Limit'), 'status': ['max_upload_speed']}, {'name': _('Downloaded'), 'status': ['all_time_download']},
'max_connections': {'name': _('Max Connections'), 'status': ['max_connections']}, 'uploaded':
'max_upload_slots': {'name': _('Max Upload Slots'), 'status': ['max_upload_slots']}, {'name': _('Uploaded'), 'status': ['total_uploaded']},
'peers': {'name': _('Peers'), 'status': ['num_peers', 'total_peers']}, 'remaining':
'seeds': {'name': _('Seeds'), 'status': ['num_seeds', 'total_seeds']}, {'name': _('Remaining'), 'status': ['total_remaining']},
'avail': {'name': _('Avail'), 'status': ['distributed_copies']}, 'ratio':
'seeds_peers_ratio': {'name': _('Seeds:Peers'), 'status': ['seeds_peers_ratio']}, {'name': _('Ratio'), 'status': ['ratio']},
'time_added': {'name': _('Added'), 'status': ['time_added']}, 'download_speed':
'tracker': {'name': _('Tracker'), 'status': ['tracker_host']}, {'name': _('Down Speed'), 'status': ['download_payload_rate']},
'download_location': {'name': _('Download Folder'), 'status': ['download_location']}, 'upload_speed':
'seeding_time': {'name': _('Seeding Time'), 'status': ['seeding_time']}, {'name': _('Up Speed'), 'status': ['upload_payload_rate']},
'active_time': {'name': _('Active Time'), 'status': ['active_time']}, 'max_download_speed':
'finished_time': {'name': _('Finished Time'), 'status': ['finished_time']}, {'name': _('Down Limit'), 'status': ['max_download_speed']},
'last_seen_complete': {'name': _('Complete Seen'), 'status': ['last_seen_complete']}, 'max_upload_speed':
'completed_time': {'name': _('Completed'), 'status': ['completed_time']}, {'name': _('Up Limit'), 'status': ['max_upload_speed']},
'eta': {'name': _('ETA'), 'status': ['eta']}, 'max_connections':
'shared': {'name': _('Shared'), 'status': ['shared']}, {'name': _('Max Connections'), 'status': ['max_connections']},
'prioritize_first_last': {'name': _('Prioritize First/Last'), 'status': ['prioritize_first_last']}, 'max_upload_slots':
'sequential_download': {'name': _('Sequential Download'), 'status': ['sequential_download']}, {'name': _('Max Upload Slots'), 'status': ['max_upload_slots']},
'is_auto_managed': {'name': _('Auto Managed'), 'status': ['is_auto_managed']}, 'peers':
'auto_managed': {'name': _('Auto Managed'), 'status': ['auto_managed']}, {'name': _('Peers'), 'status': ['num_peers', 'total_peers']},
'stop_at_ratio': {'name': _('Stop At Ratio'), 'status': ['stop_at_ratio']}, 'seeds':
'stop_ratio': {'name': _('Stop Ratio'), 'status': ['stop_ratio']}, {'name': _('Seeds'), 'status': ['num_seeds', 'total_seeds']},
'remove_at_ratio': {'name': _('Remove At Ratio'), 'status': ['remove_at_ratio']}, 'avail':
'move_completed': {'name': _('Move On Completed'), 'status': ['move_completed']}, {'name': _('Avail'), 'status': ['distributed_copies']},
'move_completed_path': {'name': _('Move Completed Path'), 'status': ['move_completed_path']}, 'seeds_peers_ratio':
'move_on_completed': {'name': _('Move On Completed'), 'status': ['move_on_completed']}, {'name': _('Seeds:Peers'), 'status': ['seeds_peers_ratio']},
'move_on_completed_path': {'name': _('Move On Completed Path'), 'status': ['move_on_completed_path']}, 'time_added':
'owner': {'name': _('Owner'), 'status': ['owner']} {'name': _('Added'), 'status': ['time_added']},
'tracker':
{'name': _('Tracker'), 'status': ['tracker_host']},
'download_location':
{'name': _('Download Folder'), 'status': ['download_location']},
'seeding_time':
{'name': _('Seeding Time'), 'status': ['seeding_time']},
'active_time':
{'name': _('Active Time'), 'status': ['active_time']},
'time_since_transfer':
{'name': _('Last Activity'), 'status': ['time_since_transfer']},
'finished_time':
{'name': _('Finished Time'), 'status': ['finished_time']},
'last_seen_complete':
{'name': _('Complete Seen'), 'status': ['last_seen_complete']},
'completed_time':
{'name': _('Completed'), 'status': ['completed_time']},
'eta':
{'name': _('ETA'), 'status': ['eta']},
'shared':
{'name': _('Shared'), 'status': ['shared']},
'prioritize_first_last':
{'name': _('Prioritize First/Last'), 'status': ['prioritize_first_last']},
'sequential_download':
{'name': _('Sequential Download'), 'status': ['sequential_download']},
'is_auto_managed':
{'name': _('Auto Managed'), 'status': ['is_auto_managed']},
'auto_managed':
{'name': _('Auto Managed'), 'status': ['auto_managed']},
'stop_at_ratio':
{'name': _('Stop At Ratio'), 'status': ['stop_at_ratio']},
'stop_ratio':
{'name': _('Stop Ratio'), 'status': ['stop_ratio']},
'remove_at_ratio':
{'name': _('Remove At Ratio'), 'status': ['remove_at_ratio']},
'move_completed':
{'name': _('Move On Completed'), 'status': ['move_completed']},
'move_completed_path':
{'name': _('Move Completed Path'), 'status': ['move_completed_path']},
'move_on_completed':
{'name': _('Move On Completed'), 'status': ['move_on_completed']},
'move_on_completed_path':
{'name': _('Move On Completed Path'), 'status': ['move_on_completed_path']},
'owner':
{'name': _('Owner'), 'status': ['owner']},
'pieces':
{'name': _('Pieces'), 'status': ['num_pieces', 'piece_length']},
'seed_rank':
{'name': _('Seed Rank'), 'status': ['seed_rank']}
} }
TRACKER_STATUS_TRANSLATION = [ TRACKER_STATUS_TRANSLATION = [

View file

@ -12,18 +12,17 @@ from __future__ import division, unicode_literals
from os.path import sep as dirsep from os.path import sep as dirsep
import deluge.common as common
import deluge.component as component import deluge.component as component
import deluge.ui.console.utils.colors as colors 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.client import client
from deluge.ui.common import FILE_PRIORITY from deluge.ui.common import FILE_PRIORITY
from deluge.ui.console.utils import format_utils 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 from . import BaseCommand
strwidth = format_utils.strwidth
STATUS_KEYS = [ STATUS_KEYS = [
'state', 'state',
'download_location', 'download_location',
@ -52,40 +51,18 @@ STATUS_KEYS = [
'is_seed', 'is_seed',
'is_finished', 'is_finished',
'active_time', 'active_time',
'seeding_time' 'seeding_time',
'time_since_transfer',
'last_seen_complete',
'seed_rank',
'all_time_download',
'total_uploaded',
'total_payload_download',
'total_payload_upload'
] ]
# Add filter specific state to torrent states # Add filter specific state to torrent states
STATES = ['Active'] + common.TORRENT_STATE STATES = ['Active'] + TORRENT_STATE
def format_progressbar(progress, width):
"""
Returns a string of a progress bar.
:param progress: float, a value between 0-100
:returns: str, a progress bar based on width
"""
w = width - 2 # we use a [] for the beginning and end
s = '['
p = int(round((progress / 100) * w))
s += '#' * p
s += '-' * (w - p)
s += ']'
return s
def format_time(seconds):
minutes = seconds // 60
seconds = seconds - minutes * 60
hours = minutes // 60
minutes = minutes - hours * 60
days = hours // 24
hours = hours - days * 24
return '%d days %02d:%02d:%02d' % (days, hours, minutes, seconds)
class Command(BaseCommand): class Command(BaseCommand):
@ -194,7 +171,7 @@ class Command(BaseCommand):
indent = ' ' * depth * spaces_per_level indent = ' ' * depth * spaces_per_level
col_filename = indent + filename col_filename = indent + filename
col_size = ' ({!cyan!}%s{!input!})' % common.fsize(torrent_file['size']) col_size = ' ({!cyan!}%s{!input!})' % fsize(torrent_file['size'])
col_progress = ' {!input!}%.2f%%' % (status['file_progress'][index] * 100) col_progress = ' {!input!}%.2f%%' % (status['file_progress'][index] * 100)
col_priority = ' {!info!}Priority: ' col_priority = ' {!info!}Priority: '
@ -210,7 +187,7 @@ class Command(BaseCommand):
col_priority += file_priority col_priority += file_priority
def tlen(string): def tlen(string):
return strwidth(format_utils.remove_formatting(string)) return strwidth(remove_formatting(string))
col_all_info = col_size + col_progress + col_priority col_all_info = col_size + col_progress + col_priority
# Check how much space we've got left after writing all the info # Check how much space we've got left after writing all the info
@ -231,13 +208,13 @@ class Command(BaseCommand):
col_all_info += col_priority col_all_info += col_priority
col_all_info += ' ' * spaces_to_add col_all_info += ' ' * spaces_to_add
# And remember to put it to the left! # And remember to put it to the left!
col_filename = format_utils.pad_string(col_filename, maxlen_space_left - 2, side='right') col_filename = pad_string(col_filename, maxlen_space_left - 2, side='right')
elif space_left > tlen(col_filename) + 1: elif space_left > tlen(col_filename) + 1:
# If there is enough space, put the info to the right # If there is enough space, put the info to the right
col_filename = format_utils.pad_string(col_filename, space_left - 2, side='right') col_filename = pad_string(col_filename, space_left - 2, side='right')
else: else:
# And if there is not, shorten the name # And if there is not, shorten the name
col_filename = format_utils.trim_string(col_filename, space_left, True) col_filename = trim_string(col_filename, space_left, True)
self.console.write(col_filename + col_all_info) self.console.write(col_filename + col_all_info)
prevpath = filepath prevpath = filepath
@ -271,9 +248,9 @@ class Command(BaseCommand):
s += '\t' s += '\t'
s += '%s%s\t%s%s' % ( s += '%s%s\t%s%s' % (
colors.state_color['Seeding'], colors.state_color['Seeding'],
common.fspeed(peer['up_speed']), fspeed(peer['up_speed']),
colors.state_color['Downloading'], colors.state_color['Downloading'],
common.fspeed(peer['down_speed'])) fspeed(peer['down_speed']))
s += '\n' s += '\n'
self.console.write(s[:-1]) self.console.write(s[:-1])
@ -291,40 +268,59 @@ class Command(BaseCommand):
else: else:
cols = 80 cols = 80
sep = ' '
if verbose or detailed: if verbose or detailed:
self.console.write(' ')
self.console.write('{!info!}Name: {!input!}%s' % (status['name'])) self.console.write('{!info!}Name: {!input!}%s' % (status['name']))
self.console.write('{!info!}ID: {!input!}%s' % (torrent_id)) self.console.write('{!info!}ID: {!input!}%s' % (torrent_id))
s = '{!info!}State: %s%s' % (colors.state_color[status['state']], status['state']) s = '{!info!}State: %s%s' % (colors.state_color[status['state']], status['state'])
# Only show speed if active # Only show speed if active
if status['state'] in ('Seeding', 'Downloading'): if status['state'] in ('Seeding', 'Downloading'):
if status['state'] != 'Seeding': if status['state'] != 'Seeding':
s += ' {!info!}Down Speed: {!input!}%s' % common.fspeed(status['download_payload_rate']) s += sep
s += ' {!info!}Up Speed: {!input!}%s' % common.fspeed(status['upload_payload_rate']) s += '{!info!}Down Speed: {!input!}%s' % fspeed(
status['download_payload_rate'], shortform=True)
if common.ftime(status['eta']): s += sep
s += ' {!info!}ETA: {!input!}%s' % common.ftime(status['eta']) s += '{!info!}Up Speed: {!input!}%s' % fspeed(
status['upload_payload_rate'], shortform=True)
self.console.write(s) self.console.write(s)
if status['state'] in ('Seeding', 'Downloading', 'Queued'): if status['state'] in ('Seeding', 'Downloading', 'Queued'):
s = '{!info!}Seeds: {!input!}%s (%s)' % (status['num_seeds'], status['total_seeds']) s = '{!info!}Seeds: {!input!}%s (%s)' % (status['num_seeds'], status['total_seeds'])
s += ' {!info!}Peers: {!input!}%s (%s)' % (status['num_peers'], status['total_peers']) s += sep
s += ' {!info!}Availability: {!input!}%.2f' % status['distributed_copies'] 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) self.console.write(s)
total_done = common.fsize(status['total_done']) total_done = fsize(status['total_done'], shortform=True)
total_size = common.fsize(status['total_size']) total_size = fsize(status['total_size'], shortform=True)
if total_done == total_size: if total_done == total_size:
s = '{!info!}Size: {!input!}%s' % (total_size) s = '{!info!}Size: {!input!}%s' % (total_size)
else: else:
s = '{!info!}Size: {!input!}%s/%s' % (total_done, total_size) s = '{!info!}Size: {!input!}%s/%s' % (total_done, total_size)
s += ' {!info!}Ratio: {!input!}%.3f' % status['ratio'] s += sep
s += ' {!info!}Uploaded: {!input!}%s' % common.fsize(status['ratio'] * status['total_done']) 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) self.console.write(s)
s = '{!info!}Seed time: {!input!}%s' % format_time(status['seeding_time']) s = '{!info!}ETA: {!input!}%s' % format_time(status['eta'])
s += ' {!info!}Active: {!input!}%s' % format_time(status['active_time']) 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) self.console.write(s)
s = '{!info!}Tracker: {!input!}%s' % status['tracker_host'] s = '{!info!}Tracker: {!input!}%s' % status['tracker_host']
@ -333,12 +329,12 @@ class Command(BaseCommand):
self.console.write('{!info!}Tracker status: {!input!}%s' % status['tracker_status']) self.console.write('{!info!}Tracker status: {!input!}%s' % status['tracker_status'])
if not status['is_finished']: if not status['is_finished']:
pbar = format_progressbar(status['progress'], cols - (13 + len('%.2f%%' % status['progress']))) pbar = f_progressbar(status['progress'], cols - (13 + len('%.2f%%' % status['progress'])))
s = '{!info!}Progress: {!input!}%.2f%% %s' % (status['progress'], pbar) s = '{!info!}Progress: {!input!}%.2f%% %s' % (status['progress'], pbar)
self.console.write(s) self.console.write(s)
s = '{!info!}Download Folder: {!input!}%s' % status['download_location'] s = '{!info!}Download Folder: {!input!}%s' % status['download_location']
self.console.write(s) self.console.write(s + '\n')
if detailed: if detailed:
self.console.write('{!info!}Files in torrent') self.console.write('{!info!}Files in torrent')
@ -346,57 +342,42 @@ class Command(BaseCommand):
self.console.write('{!info!}Connected peers') self.console.write('{!info!}Connected peers')
self.show_peer_info(torrent_id, status) self.show_peer_info(torrent_id, status)
else: else:
self.console.write(' ')
up_color = colors.state_color['Seeding'] up_color = colors.state_color['Seeding']
down_color = colors.state_color['Downloading'] down_color = colors.state_color['Downloading']
s = '%s%s' % (colors.state_color[status['state']], '[' + status['state'][0] + ']') s = '%s%s' % (colors.state_color[status['state']], '[' + status['state'][0] + ']')
s += ' {!info!}' + ('%.2f%%' % status['progress']).ljust(7, ' ') s += ' {!info!}' + format_progress(status['progress']).rjust(6, ' ')
s += ' {!input!}%s' % (status['name']) s += ' {!input!}%s' % (status['name'])
# Shorten the ID if it's necessary. Pretty hacky # Shorten the ID if it's necessary. Pretty hacky
# I _REALLY_ should make a nice function for it that can partition and shorten stuff # XXX: should make a nice function for it that can partition and shorten stuff
space_left = cols - strwidth('[s] 100.00% ' + status['name'] + ' ' * 3) - 2 space_left = cols - strwidth('[S] 99.99% ' + status['name'])
if space_left >= len(torrent_id) - 2: if self.console.interactive and space_left >= len(sep + torrent_id):
# There's enough space, print it # Not enough line space so shorten the hash (for interactive mode).
s += ' {!cyan!}%s' % torrent_id torrent_id = shorten_hash(torrent_id, space_left)
else: s += sep
# Shorten the ID s += '{!cyan!}%s' % torrent_id
a = space_left * 2 // 3
b = space_left - a
if a < 8:
b = b - (8 - a)
a = 8
if b < 0:
a += b
b = 0
if a > 8:
# Print the shortened ID
s += ' {!cyan!}%s' % (torrent_id[0:a] + '..' + torrent_id[-b - 1:-1])
else:
# It has wrapped over to the second row anyway
s += ' {!cyan!}%s' % torrent_id
self.console.write(s) self.console.write(s)
dl_info = '{!info!}DL: {!input!}' dl_info = '{!info!}DL: {!input!}'
dl_info += '%s' % common.fsize(status['total_done']) dl_info += '%s' % ftotal_sized(status['all_time_download'], status['total_payload_download'])
if status['total_done'] != status['total_size']:
dl_info += '/%s' % common.fsize(status['total_size'])
if status['download_payload_rate'] > 0: if status['download_payload_rate'] > 0:
dl_info += ' @ %s%s' % (down_color, common.fspeed(status['download_payload_rate'])) dl_info += ' @ %s%s' % (down_color, fspeed(
status['download_payload_rate'], shortform=True))
ul_info = ' {!info!}UL: {!input!}' ul_info = ' {!info!}UL: {!input!}'
ul_info += '%s' % common.fsize(status['ratio'] * status['total_done']) ul_info += '%s' % ftotal_sized(status['total_uploaded'], status['total_payload_upload'])
if status['upload_payload_rate'] > 0: if status['upload_payload_rate'] > 0:
ul_info += ' @ %s%s' % (up_color, common.fspeed(status['upload_payload_rate'])) ul_info += ' @ %s%s' % (up_color, fspeed(
status['upload_payload_rate'], shortform=True))
eta = '' eta = ' {!info!}ETA: {!magenta!}%s' % format_time(status['eta'])
if common.ftime(status['eta']):
eta = ' {!info!}ETA: {!magenta!}%s' % common.ftime(status['eta']) self.console.write(' ' + dl_info + ul_info + eta + '\n')
self.console.write(' ' + dl_info + ul_info + eta)
self.console.set_batch_write(False) self.console.set_batch_write(False)
def complete(self, line): def complete(self, line):

View file

@ -79,7 +79,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
'seeding_time', 'time_added', 'distributed_copies', 'num_pieces', 'seeding_time', 'time_added', 'distributed_copies', 'num_pieces',
'piece_length', 'download_location', 'file_progress', 'file_priorities', 'message', 'piece_length', 'download_location', 'file_progress', 'file_priorities', 'message',
'total_wanted', 'tracker_host', 'owner', 'seed_rank', 'last_seen_complete', 'total_wanted', 'tracker_host', 'owner', 'seed_rank', 'last_seen_complete',
'completed_time'] 'completed_time', 'time_since_transfer']
self.file_list = None self.file_list = None
self.current_file = None self.current_file = None
self.current_file_idx = 0 self.current_file_idx = 0
@ -481,10 +481,13 @@ class TorrentDetail(BaseMode, PopupsHandler):
row = add_field('seed_rank', row) row = add_field('seed_rank', row)
# Last seen complete # Last seen complete
row = add_field('last_seen_complete', row) row = add_field('last_seen_complete', row)
# Last activity
row = add_field('time_since_transfer', row)
# Owner # Owner
if status['owner']: if status['owner']:
row = add_field('owner', row) row = add_field('owner', row)
return row return row
# Last act
@overrides(BaseMode) @overrides(BaseMode)
def refresh(self, lines=None): def refresh(self, lines=None):

View file

@ -30,7 +30,7 @@ column_pref_names = ['queue', 'name', 'size', 'downloaded', 'uploaded', 'remaini
'download_speed', 'upload_speed', 'max_download_speed', 'max_upload_speed', 'download_speed', 'upload_speed', 'max_download_speed', 'max_upload_speed',
'eta', 'ratio', 'avail', 'time_added', 'completed_time', 'last_seen_complete', 'eta', 'ratio', 'avail', 'time_added', 'completed_time', 'last_seen_complete',
'tracker', 'download_location', 'active_time', 'seeding_time', 'finished_time', 'tracker', 'download_location', 'active_time', 'seeding_time', 'finished_time',
'shared', 'owner'] 'time_since_transfer', 'shared', 'owner']
class ColumnAndWidth(CheckedPlusInput): class ColumnAndWidth(CheckedPlusInput):

View file

@ -14,25 +14,17 @@ import logging
import deluge.common import deluge.common
from deluge.ui.common import TORRENT_DATA_FIELD from deluge.ui.common import TORRENT_DATA_FIELD
from deluge.ui.console.utils import format_utils
from deluge.ui.translations_util import setup_translations from deluge.ui.translations_util import setup_translations
from . import format_utils
setup_translations() setup_translations()
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
torrent_data_fields = copy.deepcopy(TORRENT_DATA_FIELD) torrent_data_fields = copy.deepcopy(TORRENT_DATA_FIELD)
def format_queue(qnum):
if qnum < 0:
return ''
return '%d' % (qnum + 1)
formatters = { formatters = {
'queue': format_queue, 'queue': format_utils.format_queue,
'name': lambda a, b: b, 'name': lambda a, b: b,
'state': None, 'state': None,
'tracker': None, 'tracker': None,
@ -42,10 +34,10 @@ formatters = {
'progress_state': format_utils.format_progress, 'progress_state': format_utils.format_progress,
'progress': format_utils.format_progress, 'progress': format_utils.format_progress,
'size': deluge.common.fsize, 'size': format_utils.format_size,
'downloaded': deluge.common.fsize, 'downloaded': format_utils.format_size,
'uploaded': deluge.common.fsize, 'uploaded': format_utils.format_size,
'remaining': deluge.common.fsize, 'remaining': format_utils.format_size,
'ratio': format_utils.format_float, 'ratio': format_utils.format_float,
'avail': format_utils.format_float, 'avail': format_utils.format_float,
@ -60,19 +52,17 @@ formatters = {
'seeds': format_utils.format_seeds_peers, 'seeds': format_utils.format_seeds_peers,
'time_added': deluge.common.fdate, 'time_added': deluge.common.fdate,
'seeding_time': deluge.common.ftime, 'seeding_time': format_utils.format_time,
'active_time': deluge.common.ftime, 'active_time': format_utils.format_time,
'time_since_transfer': format_utils.format_date_dash,
'finished_time': deluge.common.ftime, 'finished_time': deluge.common.ftime,
'last_seen_complete': format_utils.format_date_never, 'last_seen_complete': format_utils.format_date_never,
'completed_time': format_utils.format_date, 'completed_time': format_utils.format_date_dash,
'eta': format_utils.format_time, 'eta': format_utils.format_time,
'pieces': format_utils.format_pieces, 'pieces': format_utils.format_pieces,
} }
torrent_data_fields['pieces'] = {'name': _('Pieces'), 'status': ['num_pieces', 'piece_length']}
torrent_data_fields['seed_rank'] = {'name': _('Seed Rank'), 'status': ['seed_rank']}
for data_field in torrent_data_fields: for data_field in torrent_data_fields:
torrent_data_fields[data_field]['formatter'] = formatters.get(data_field, str) torrent_data_fields[data_field]['formatter'] = formatters.get(data_field, str)

View file

@ -17,9 +17,13 @@ import deluge.common
from deluge.ui.common import FILE_PRIORITY from deluge.ui.common import FILE_PRIORITY
def format_size(size):
return deluge.common.fsize(size, shortform=True)
def format_speed(speed): def format_speed(speed):
if speed > 0: if speed > 0:
return deluge.common.fspeed(speed) return deluge.common.fspeed(speed, shortform=True)
else: else:
return '-' return '-'
@ -31,16 +35,16 @@ def format_time(time):
return '-' return '-'
def format_date(time): def format_date_dash(time):
if time > 0: if time > 0:
return deluge.common.fdate(time) return deluge.common.fdate(time, date_only=True)
else: else:
return '' return '-'
def format_date_never(time): def format_date_never(time):
if time > 0: if time > 0:
return deluge.common.fdate(time) return deluge.common.fdate(time, date_only=True)
else: else:
return 'Never' return 'Never'
@ -56,15 +60,48 @@ def format_seeds_peers(num, total):
return '%d (%d)' % (num, total) return '%d (%d)' % (num, total)
def format_progress(perc): def format_progress(value):
if perc < 100: return ('%.2f' % value).rstrip('0').rstrip('.') + '%'
return '%.2f%%' % perc
def f_progressbar(progress, width):
"""
Returns a string of a progress bar.
:param progress: float, a value between 0-100
:returns: str, a progress bar based on width
"""
w = width - 2 # we use a [] for the beginning and end
s = '['
p = int(round((progress / 100) * w))
s += '#' * p
s += '-' * (w - p)
s += ']'
return s
def f_seedrank_dash(seed_rank, seeding_time):
"""Display value if seeding otherwise dash"""
if seeding_time > 0:
if seed_rank >= 1000:
return '%ik' % (seed_rank // 1000)
else:
return str(seed_rank)
else: else:
return '100%' return '-'
def ftotal_sized(first, second):
return '%s (%s)' % (deluge.common.fsize(first, shortform=True),
deluge.common.fsize(second, shortform=True))
def format_pieces(num, size): def format_pieces(num, size):
return '%d (%s)' % (num, deluge.common.fsize(size)) return '%d (%s)' % (num, deluge.common.fsize(size, shortform=True))
def format_priority(prio): def format_priority(prio):
@ -75,6 +112,12 @@ def format_priority(prio):
return FILE_PRIORITY[prio] return FILE_PRIORITY[prio]
def format_queue(qnum):
if qnum < 0:
return ''
return '%d' % (qnum + 1)
def trim_string(string, w, have_dbls): def trim_string(string, w, have_dbls):
if w <= 0: if w <= 0:
return '' return ''
@ -127,6 +170,23 @@ def remove_formatting(string):
return re.sub(_strip_re, '', string) return re.sub(_strip_re, '', string)
def shorten_hash(tid, space_left, min_width=13, placeholder='...'):
"""Shorten the supplied torrent infohash by removing chars from the middle.
Use a placeholder to indicate shortened.
If unable to shorten will justify so entire tid is on the next line.
"""
tid = tid.strip()
if space_left >= min_width:
mid = len(tid) // 2
trim, remain = divmod(len(tid) + len(placeholder) - space_left, 2)
return tid[0: mid - trim] + placeholder + tid[mid + trim + remain:]
else:
# Justity the tid so it is completely on the next line.
return tid.rjust(len(tid) + space_left)
def wrap_string(string, width, min_lines=0, strip_colors=True): def wrap_string(string, width, min_lines=0, strip_colors=True):
""" """
Wrap a string to fit in a particular width. Returns a list of output lines. Wrap a string to fit in a particular width. Returns a list of output lines.

View file

@ -322,11 +322,11 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label_last_active"> <object class="GtkLabel" id="label_last_transfer">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<property name="label" translatable="yes">Last Activity:</property> <property name="label" translatable="yes">Last Transfer:</property>
<attributes> <attributes>
<attribute name="weight" value="bold"/> <attribute name="weight" value="bold"/>
</attributes> </attributes>
@ -340,7 +340,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="summary_last_active"> <object class="GtkLabel" id="summary_last_transfer">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">0</property> <property name="xalign">0</property>

View file

@ -15,7 +15,7 @@ import deluge.component as component
from deluge.common import fpeer from deluge.common import fpeer
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.gtkui.piecesbar import PiecesBar from deluge.ui.gtkui.piecesbar import PiecesBar
from deluge.ui.gtkui.tab_data_funcs import (fdate_or_never, flast_active, fpcnt, fratio, fseed_rank_or_dash, fspeed_max, from deluge.ui.gtkui.tab_data_funcs import (fdate_or_never, fpcnt, fratio, fseed_rank_or_dash, fspeed_max,
ftime_or_dash, ftotal_sized) ftime_or_dash, ftotal_sized)
from deluge.ui.gtkui.torrentdetails import Tab from deluge.ui.gtkui.torrentdetails import Tab
@ -57,8 +57,7 @@ class StatusTab(Tab):
(main_builder.get_object('summary_seed_rank'), fseed_rank_or_dash, ('seed_rank', 'seeding_time')), (main_builder.get_object('summary_seed_rank'), fseed_rank_or_dash, ('seed_rank', 'seeding_time')),
(main_builder.get_object('progressbar'), fpcnt, ('progress', 'state', 'message')), (main_builder.get_object('progressbar'), fpcnt, ('progress', 'state', 'message')),
(main_builder.get_object('summary_last_seen_complete'), fdate_or_never, ('last_seen_complete',)), (main_builder.get_object('summary_last_seen_complete'), fdate_or_never, ('last_seen_complete',)),
(main_builder.get_object('summary_last_active'), flast_active, ('time_since_download', (main_builder.get_object('summary_last_transfer'), ftime_or_dash, ('time_since_transfer',)),
'time_since_upload')),
] ]
self.status_keys = [status for widget in self.label_widgets for status in widget[2]] self.status_keys = [status for widget in self.label_widgets for status in widget[2]]

View file

@ -65,17 +65,6 @@ def fseed_rank_or_dash(seed_rank, seeding_time):
return '-' return '-'
def flast_active(time_since_download, time_since_upload):
"""The last time the torrent was active as time e.g. 2h 30m or dash"""
try:
last_time_since = min((x for x in (time_since_download, time_since_upload) if x != -1))
except ValueError:
return '-'
else:
return ftime(last_time_since)
def fpieces_num_size(num_pieces, piece_size): def fpieces_num_size(num_pieces, piece_size):
return '%s (%s)' % (num_pieces, fsize(piece_size, precision=0)) return '%s (%s)' % (num_pieces, fsize(piece_size, precision=0))

View file

@ -97,7 +97,7 @@ Deluge.Formatters = {
* @return {String} a formatted time string. will return '' if seconds == 0 * @return {String} a formatted time string. will return '' if seconds == 0
*/ */
timeRemaining: function(time) { timeRemaining: function(time) {
if (time == 0) { return '&infin;' } if (time <= 0) { return '&infin;' }
time = time.toFixed(0); time = time.toFixed(0);
if (time < 60) { return time + 's'; } if (time < 60) { return time + 's'; }
else { time = time / 60; } else { time = time / 60; }

View file

@ -22,7 +22,7 @@ Deluge.Keys = {
* 'upload_payload_rate', 'eta', 'ratio', 'distributed_copies', * 'upload_payload_rate', 'eta', 'ratio', 'distributed_copies',
* 'is_auto_managed', 'time_added', 'tracker_host', 'download_location', 'last_seen_complete', * 'is_auto_managed', 'time_added', 'tracker_host', 'download_location', 'last_seen_complete',
* 'total_done', 'total_uploaded', 'max_download_speed', 'max_upload_speed', * 'total_done', 'total_uploaded', 'max_download_speed', 'max_upload_speed',
* 'seeds_peers_ratio', 'total_remaining', 'completed_time']</pre> * 'seeds_peers_ratio', 'total_remaining', 'completed_time', 'time_since_transfer']</pre>
*/ */
Grid: [ Grid: [
'queue', 'name', 'total_wanted', 'state', 'progress', 'num_seeds', 'queue', 'name', 'total_wanted', 'state', 'progress', 'num_seeds',
@ -30,7 +30,7 @@ Deluge.Keys = {
'upload_payload_rate', 'eta', 'ratio', 'distributed_copies', 'upload_payload_rate', 'eta', 'ratio', 'distributed_copies',
'is_auto_managed', 'time_added', 'tracker_host', 'download_location', 'last_seen_complete', 'is_auto_managed', 'time_added', 'tracker_host', 'download_location', 'last_seen_complete',
'total_done', 'total_uploaded', 'max_download_speed', 'max_upload_speed', 'total_done', 'total_uploaded', 'max_download_speed', 'max_upload_speed',
'seeds_peers_ratio', 'total_remaining', 'completed_time' 'seeds_peers_ratio', 'total_remaining', 'completed_time', 'time_since_transfer'
], ],
/** /**
@ -38,13 +38,13 @@ Deluge.Keys = {
* These get updated to include the keys in {@link #Grid}. * These get updated to include the keys in {@link #Grid}.
* <pre>['total_done', 'total_payload_download', 'total_uploaded', * <pre>['total_done', 'total_payload_download', 'total_uploaded',
* 'total_payload_upload', 'next_announce', 'tracker_status', 'num_pieces', * 'total_payload_upload', 'next_announce', 'tracker_status', 'num_pieces',
* 'piece_length', 'is_auto_managed', 'active_time', 'seeding_time', * 'piece_length', 'is_auto_managed', 'active_time', 'seeding_time', 'time_since_transfer',
* 'seed_rank', 'last_seen_complete', 'completed_time', 'owner', 'public', 'shared']</pre> * 'seed_rank', 'last_seen_complete', 'completed_time', 'owner', 'public', 'shared']</pre>
*/ */
Status: [ Status: [
'total_done', 'total_payload_download', 'total_uploaded', 'total_done', 'total_payload_download', 'total_uploaded',
'total_payload_upload', 'next_announce', 'tracker_status', 'num_pieces', 'total_payload_upload', 'next_announce', 'tracker_status', 'num_pieces',
'piece_length', 'is_auto_managed', 'active_time', 'seeding_time', 'piece_length', 'is_auto_managed', 'active_time', 'seeding_time', 'time_since_transfer',
'seed_rank', 'last_seen_complete', 'completed_time', 'owner', 'public', 'shared' 'seed_rank', 'last_seen_complete', 'completed_time', 'owner', 'public', 'shared'
], ],

View file

@ -251,6 +251,13 @@
sortable: true, sortable: true,
renderer: availRenderer, renderer: availRenderer,
dataIndex: 'seeds_peers_ratio' dataIndex: 'seeds_peers_ratio'
}, {
header: _('Last Transfer'),
hidden: true,
width: 75,
sortable: true,
renderer: ftime,
dataIndex: 'time_since_transfer'
}], }],
@ -280,7 +287,8 @@
{name: 'total_remaining', type: 'int'}, {name: 'total_remaining', type: 'int'},
{name: 'max_download_speed', type: 'int'}, {name: 'max_download_speed', type: 'int'},
{name: 'max_upload_speed', type: 'int'}, {name: 'max_upload_speed', type: 'int'},
{name: 'seeds_peers_ratio', type: 'float'} {name: 'seeds_peers_ratio', type: 'float'},
{name: 'time_since_transfer', type: 'int'}
] ]
}, },

View file

@ -90,5 +90,8 @@ Deluge.data.Torrent = Ext.data.Record.create([{
}, { }, {
name: 'seeds_peers_ratio', name: 'seeds_peers_ratio',
type: 'float' type: 'float'
}, {
name: 'time_since_transfer',
type: 'int'
} }
]); ]);

View file

@ -73,6 +73,7 @@ Deluge.details.StatusTab = Ext.extend(Ext.Panel, {
peers = status.total_peers > -1 ? status.num_peers + ' (' + status.total_peers + ')' : status.num_peers; peers = status.total_peers > -1 ? status.num_peers + ' (' + status.total_peers + ')' : status.num_peers;
last_seen_complete = status.last_seen_complete > 0.0 ? fdate(status.last_seen_complete) : 'Never'; last_seen_complete = status.last_seen_complete > 0.0 ? fdate(status.last_seen_complete) : 'Never';
completed_time = status.completed_time > 0.0 ? fdate(status.completed_time) : ''; completed_time = status.completed_time > 0.0 ? fdate(status.completed_time) : '';
var data = { var data = {
downloaded: fsize(status.total_done, true), downloaded: fsize(status.total_done, true),
uploaded: fsize(status.total_uploaded, true), uploaded: fsize(status.total_uploaded, true),
@ -91,7 +92,8 @@ Deluge.details.StatusTab = Ext.extend(Ext.Panel, {
seed_rank: status.seed_rank, seed_rank: status.seed_rank,
time_added: fdate(status.time_added), time_added: fdate(status.time_added),
last_seen_complete: last_seen_complete, last_seen_complete: last_seen_complete,
completed_time: completed_time completed_time: completed_time,
time_since_transfer: ftime(status.time_since_transfer)
} }
data.auto_managed = _((status.is_auto_managed) ? 'True' : 'False'); data.auto_managed = _((status.is_auto_managed) ? 'True' : 'False');

View file

@ -10,6 +10,7 @@
<dt class="upspeed">${_("Up Speed:")}</dt><dd class="upspeed"/> <dt class="upspeed">${_("Up Speed:")}</dt><dd class="upspeed"/>
<dt class="eta">${_("ETA:")}</dt><dd class="eta"/> <dt class="eta">${_("ETA:")}</dt><dd class="eta"/>
<dt class="pieces">${_("Pieces:")}</dt><dd class="pieces"/> <dt class="pieces">${_("Pieces:")}</dt><dd class="pieces"/>
<dt class="time_since_transfer">${_("Last Transfer:")}</dt><dd class="time_since_transfer"/>
</dl> </dl>
<dl> <dl>
<dt class="seeds">${_("Seeds:")}</dt><dd class="seeds"/> <dt class="seeds">${_("Seeds:")}</dt><dd class="seeds"/>