[#637] Add a Moving storage state along with progress

Uses attr Torrent.moving_storage for now but can be replaced with
future lt1.0 status field.
Refactored the code to use the common.TORRENT_STATE list.
Added a translating dict to ui.common to aid translation of state text.
This commit is contained in:
Calum Lind 2014-07-16 17:35:55 +01:00
parent bd119bccf4
commit 62cca045be
11 changed files with 94 additions and 56 deletions

View File

@ -99,7 +99,8 @@ TORRENT_STATE = [
"Seeding", "Seeding",
"Paused", "Paused",
"Error", "Error",
"Queued" "Queued",
"Moving"
] ]
FILE_PRIORITY = { FILE_PRIORITY = {

View File

@ -36,11 +36,12 @@
import logging import logging
import copy import copy
import deluge.component as component import deluge.component as component
from deluge.common import TORRENT_STATE
STATE_SORT = ["All", "Downloading", "Seeding", "Active", "Paused", "Queued"]
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
STATE_SORT = ["All", "Active"] + TORRENT_STATE
# Special purpose filters: # Special purpose filters:
def filter_keywords(torrent_ids, values): def filter_keywords(torrent_ids, values):
@ -249,15 +250,12 @@ class FilterManager(component.Component):
return sorted_items return sorted_items
def _init_state_tree(self): def _init_state_tree(self):
return {"All": len(self.torrents.get_torrent_list()), init_state = {}
"Downloading": 0, init_state["All"] = len(self.torrents.get_torrent_list())
"Seeding": 0, for state in TORRENT_STATE:
"Paused": 0, init_state[state] = 0
"Checking": 0, init_state["Active"] = len(self.filter_state_active(self.torrents.get_torrent_list()))
"Queued": 0, return init_state
"Error": 0,
"Active": len(self.filter_state_active(self.torrents.get_torrent_list()))
}
def register_filter(self, id, filter_func, filter_value=None): def register_filter(self, id, filter_func, filter_value=None):
self.registered_filters[id] = filter_func self.registered_filters[id] = filter_func

View File

@ -197,6 +197,8 @@ class Torrent(object):
self.statusmsg = "OK" self.statusmsg = "OK"
self.state = None self.state = None
self.moving_storage = False
self.moving_storage_dest_path = None
self.tracker_status = "" self.tracker_status = ""
self.tracker_host = None self.tracker_host = None
self.forcing_recheck = False self.forcing_recheck = False
@ -571,6 +573,10 @@ class Torrent(object):
else: else:
self.set_status_message("OK") self.set_status_message("OK")
if self.moving_storage:
self.state = "Moving"
return
if ltstate in (LTSTATE["Queued"], LTSTATE["Checking"]): if ltstate in (LTSTATE["Queued"], LTSTATE["Checking"]):
self.state = "Checking" self.state = "Checking"
if status.paused: if status.paused:
@ -788,6 +794,21 @@ class Torrent(object):
"""Returns a magnet uri for this torrent""" """Returns a magnet uri for this torrent"""
return lt.make_magnet_uri(self.handle) return lt.make_magnet_uri(self.handle)
def get_progress(self):
def get_size(files, path):
"""Returns total size of 'files' currently located in 'path'"""
files = [os.path.join(path, f) for f in files]
return sum(os.stat(f).st_size for f in files if os.path.exists(f))
if self.moving_storage:
torrent_status = self.get_status(["files", "total_done"])
torrent_files = [f['path'] for f in torrent_status["files"]]
dest_path_size = get_size(torrent_files, self.moving_storage_dest_path)
progress = dest_path_size / torrent_status["total_done"] * 100
else:
progress = self.status.progress * 100
return progress
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
@ -889,7 +910,7 @@ class Torrent(object):
"paused": lambda: self.status.paused, "paused": lambda: self.status.paused,
"prioritize_first_last": lambda: self.options["prioritize_first_last_pieces"], "prioritize_first_last": lambda: self.options["prioritize_first_last_pieces"],
"sequential_download": lambda: self.options["sequential_download"], "sequential_download": lambda: self.options["sequential_download"],
"progress": lambda: self.status.progress * 100, "progress": self.get_progress,
"shared": lambda: self.options["shared"], "shared": lambda: self.options["shared"],
"remove_at_ratio": lambda: self.options["remove_at_ratio"], "remove_at_ratio": lambda: self.options["remove_at_ratio"],
"save_path": lambda: self.options["download_location"], # Deprecated, use download_location "save_path": lambda: self.options["download_location"], # Deprecated, use download_location
@ -1013,6 +1034,7 @@ class Torrent(object):
Returns: Returns:
bool: True if successful, otherwise False bool: True if successful, otherwise False
""" """
dest = decode_string(dest) dest = decode_string(dest)
if not os.path.exists(dest): if not os.path.exists(dest):
@ -1034,6 +1056,9 @@ class Torrent(object):
except RuntimeError, ex: except RuntimeError, ex:
log.error("Error calling libtorrent move_storage: %s", ex) log.error("Error calling libtorrent move_storage: %s", ex)
return False return False
self.moving_storage = True
self.moving_storage_dest_path = dest
self.update_state()
return True return True
def save_resume_data(self, flush_disk_cache=False): def save_resume_data(self, flush_disk_cache=False):

View File

@ -245,6 +245,7 @@ class TorrentManager(component.Component):
def update(self): def update(self):
for torrent_id, torrent in self.torrents.items(): for torrent_id, torrent in self.torrents.items():
# XXX: Should the state check be those that _can_ be stopped at ratio
if torrent.options["stop_at_ratio"] and torrent.state not in ( if torrent.options["stop_at_ratio"] and torrent.state not in (
"Checking", "Allocating", "Paused", "Queued"): "Checking", "Allocating", "Paused", "Queued"):
# If the global setting is set, but the per-torrent isn't... # If the global setting is set, but the per-torrent isn't...
@ -1066,6 +1067,8 @@ class TorrentManager(component.Component):
return return
torrent.set_download_location(os.path.normpath(alert.handle.save_path())) torrent.set_download_location(os.path.normpath(alert.handle.save_path()))
torrent.set_move_completed(False) torrent.set_move_completed(False)
torrent.moving_storage = False
torrent.update_state()
if torrent in self.waiting_on_finish_moving: if torrent in self.waiting_on_finish_moving:
self.waiting_on_finish_moving.remove(torrent_id) self.waiting_on_finish_moving.remove(torrent_id)
@ -1080,8 +1083,10 @@ class TorrentManager(component.Component):
except (RuntimeError, KeyError): except (RuntimeError, KeyError):
return return
# Set an Error message and pause the torrent # Set an Error message and pause the torrent
torrent.moving_storage = False
torrent.set_status_message("Error: moving storage location failed") torrent.set_status_message("Error: moving storage location failed")
torrent.pause() torrent.pause()
torrent.update_state()
if torrent in self.waiting_on_finish_moving: if torrent in self.waiting_on_finish_moving:
self.waiting_on_finish_moving.remove(torrent_id) self.waiting_on_finish_moving.remove(torrent_id)

View File

@ -51,6 +51,24 @@ import deluge.configmanager
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Dummy tranlation dict so Torrent states text is available for Translators
# All entries in deluge.common.TORRENT_STATE should be here. It does not need importing
# as the string matches the translation text so using the _() function is enough.
def _(message): return message
STATE_TRANSLATION = {
"All": _("All"),
"Active": _("Active"),
"Allocating": _("Allocating"),
"Checking": _("Checking"),
"Downloading": _("Downloading"),
"Seeding": _("Seeding"),
"Paused": _("Paused"),
"Checking": _("Checking"),
"Queued": _("Queued"),
"Error": _("Error"),
}
del _
class TorrentInfo(object): class TorrentInfo(object):
""" """
Collects information about a torrent file. Collects information about a torrent file.

View File

@ -79,7 +79,8 @@ state_color = {
"Paused": "{!white,black!}", "Paused": "{!white,black!}",
"Checking": "{!green,black!}", "Checking": "{!green,black!}",
"Queued": "{!yellow,black!}", "Queued": "{!yellow,black!}",
"Error": "{!red,black,bold!}" "Error": "{!red,black,bold!}",
"Moving": "{!green,black,bold}"
} }
type_color = { type_color = {

View File

@ -36,7 +36,7 @@ from optparse import make_option
from twisted.internet import defer from twisted.internet import defer
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
from deluge.ui.client import client from deluge.ui.client import client
import deluge.common from deluge.common import fspeed, TORRENT_STATE
import deluge.component as component import deluge.component as component
@ -98,23 +98,22 @@ class Command(BaseCommand):
self.console.write("{!info!}Total upload: %f" % self.status["payload_upload_rate"]) self.console.write("{!info!}Total upload: %f" % self.status["payload_upload_rate"])
self.console.write("{!info!}Total download: %f" % self.status["payload_download_rate"]) self.console.write("{!info!}Total download: %f" % self.status["payload_download_rate"])
else: else:
self.console.write("{!info!}Total upload: %s" % deluge.common.fspeed(self.status["payload_upload_rate"])) self.console.write("{!info!}Total upload: %s" % fspeed(self.status["payload_upload_rate"]))
self.console.write("{!info!}Total download: %s" % self.console.write("{!info!}Total download: %s" % fspeed(self.status["payload_download_rate"]))
deluge.common.fspeed(self.status["payload_download_rate"]))
self.console.write("{!info!}DHT Nodes: %i" % self.status["dht_nodes"]) self.console.write("{!info!}DHT Nodes: %i" % self.status["dht_nodes"])
self.console.write("{!info!}Total connections: %i" % self.connections) self.console.write("{!info!}Total connections: %i" % self.connections)
if self.torrents == -1: if self.torrents == -1:
self.console.write("{!error!}Error getting torrent info") self.console.write("{!error!}Error getting torrent info")
elif self.torrents != -2: elif self.torrents != -2:
self.console.write("{!info!}Total torrents: %i" % len(self.torrents)) self.console.write("{!info!}Total torrents: %i" % len(self.torrents))
states = ["Downloading", "Seeding", "Paused", "Checking", "Error", "Queued"]
state_counts = {} state_counts = {}
for state in states: for state in TORRENT_STATE:
state_counts[state] = 0 state_counts[state] = 0
for t in self.torrents: for t in self.torrents:
s = self.torrents[t] s = self.torrents[t]
state_counts[s["state"]] += 1 state_counts[s["state"]] += 1
for state in states: for state in TORRENT_STATE:
self.console.write("{!info!} %s: %i" % (state, state_counts[state])) self.console.write("{!info!} %s: %i" % (state, state_counts[state]))
self.console.set_batch_write(False) self.console.set_batch_write(False)

View File

@ -144,6 +144,8 @@ class FILTER:
CHECKING=5 CHECKING=5
ERROR=6 ERROR=6
QUEUED=7 QUEUED=7
ALLOCATING=8
MOVING=9
DEFAULT_PREFS = { DEFAULT_PREFS = {
"show_queue": True, "show_queue": True,
@ -703,6 +705,13 @@ class AllTorrents(BaseMode, component.Component):
elif data==FILTER.QUEUED: elif data==FILTER.QUEUED:
self.__status_dict = {"state":"Queued"} self.__status_dict = {"state":"Queued"}
self._curr_filter = "Queued" self._curr_filter = "Queued"
elif data==FILTER.ALLOCATING:
self.__status_dict = {"state":"Allocating"}
self._curr_filter = "Allocating"
elif data==FILTER.MOVING:
self.__status_dict = {"state":"Moving"}
self._curr_filter = "Moving"
self._go_top = True self._go_top = True
return True return True
@ -716,6 +725,8 @@ class AllTorrents(BaseMode, component.Component):
self.popup.add_line("_Error",data=FILTER.ERROR,foreground="red") self.popup.add_line("_Error",data=FILTER.ERROR,foreground="red")
self.popup.add_line("_Checking",data=FILTER.CHECKING,foreground="blue") self.popup.add_line("_Checking",data=FILTER.CHECKING,foreground="blue")
self.popup.add_line("Q_ueued",data=FILTER.QUEUED,foreground="yellow") self.popup.add_line("Q_ueued",data=FILTER.QUEUED,foreground="yellow")
self.popup.add_line("A_llocating",data=FILTER.ALLOCATING,foreground="yellow")
self.popup.add_line("_Moving",data=FILTER.MOVING,foreground="green")
def _report_add_status(self, succ_cnt, fail_cnt, fail_msgs): def _report_add_status(self, succ_cnt, fail_cnt, fail_msgs):
if fail_cnt == 0: if fail_cnt == 0:
@ -951,6 +962,8 @@ class AllTorrents(BaseMode, component.Component):
fg = "yellow" fg = "yellow"
elif row[1] == "Checking": elif row[1] == "Checking":
fg = "blue" fg = "blue"
elif row[1] == "Moving":
fg = "green"
if self.entering_search and len(self.search_string) > 1: if self.entering_search and len(self.search_string) > 1:
lcase_name = self.torrent_names[tidx-1].lower() lcase_name = self.torrent_names[tidx-1].lower()

View File

@ -41,7 +41,7 @@ import warnings
from gobject import GError from gobject import GError
import deluge.component as component import deluge.component as component
import deluge.common from deluge.common import resource_filename, get_pixmap, TORRENT_STATE
from deluge.ui.client import client from deluge.ui.client import client
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
@ -55,7 +55,9 @@ STATE_PIX = {
"Checking": "checking", "Checking": "checking",
"Queued": "queued", "Queued": "queued",
"Error": "alert", "Error": "alert",
"Active": "active" "Active": "active",
"Allocating": "checking",
"Moving": "checking"
} }
TRACKER_PIX = { TRACKER_PIX = {
@ -85,9 +87,7 @@ class FilterTreeView(component.Component):
#menu #menu
builder = gtk.Builder() builder = gtk.Builder()
builder.add_from_file(deluge.common.resource_filename( builder.add_from_file(resource_filename("deluge.ui.gtkui", os.path.join("glade", "filtertree_menu.ui")))
"deluge.ui.gtkui", os.path.join("glade", "filtertree_menu.ui")
))
self.menu = builder.get_object("filtertree_menu") self.menu = builder.get_object("filtertree_menu")
builder.connect_signals({ builder.connect_signals({
"select_all": self.on_select_all, "select_all": self.on_select_all,
@ -141,12 +141,8 @@ class FilterTreeView(component.Component):
#initial order of state filter: #initial order of state filter:
self.cat_nodes["state"] = self.treestore.append(None, ["cat", "state", _("States"), 0, None, False]) self.cat_nodes["state"] = self.treestore.append(None, ["cat", "state", _("States"), 0, None, False])
self.update_row("state", "All" , 0, _("All")) for state in ["All", "Active"] + TORRENT_STATE:
self.update_row("state", "Downloading" , 0, _("Downloading")) self.update_row("state", state, 0, _(state))
self.update_row("state", "Seeding" , 0, _("Seeding"))
self.update_row("state", "Active" , 0, _("Active"))
self.update_row("state", "Paused" , 0, _("Paused"))
self.update_row("state", "Queued" , 0, _("Queued"))
self.cat_nodes["tracker_host"] = self.treestore.append(None, ["cat", "tracker_host", _("Trackers"), 0, None, False]) self.cat_nodes["tracker_host"] = self.treestore.append(None, ["cat", "tracker_host", _("Trackers"), 0, None, False])
self.update_row("tracker_host", "All" , 0, _("All")) self.update_row("tracker_host", "All" , 0, _("All"))
@ -274,7 +270,7 @@ class FilterTreeView(component.Component):
if pix: if pix:
try: try:
return gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("%s16.png" % pix)) return gtk.gdk.pixbuf_new_from_file(get_pixmap("%s16.png" % pix))
except GError, e: except GError, e:
log.warning(e) log.warning(e)
return self.get_transparent_pix(16, 16) return self.get_transparent_pix(16, 16)

View File

@ -42,7 +42,6 @@ import logging
import deluge.component as component import deluge.component as component
from deluge.ui.client import client from deluge.ui.client import client
from deluge.common import TORRENT_STATE
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View File

@ -48,7 +48,7 @@ icon_alert = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("alert16.png"))
icon_queued = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("queued16.png")) icon_queued = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("queued16.png"))
icon_checking = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("checking16.png")) icon_checking = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("checking16.png"))
# Holds the info for which status icon to display based on state # Holds the info for which status icon to display based on TORRENT_STATE
ICON_STATE = { ICON_STATE = {
"Allocating": icon_checking, "Allocating": icon_checking,
"Checking": icon_checking, "Checking": icon_checking,
@ -57,27 +57,9 @@ ICON_STATE = {
"Paused": icon_inactive, "Paused": icon_inactive,
"Error": icon_alert, "Error": icon_alert,
"Queued": icon_queued, "Queued": icon_queued,
"Checking Resume Data": icon_checking "Moving": icon_checking
} }
def _(message): return message
TRANSLATE = {
"Downloading": _("Downloading"),
"Seeding": _("Seeding"),
"Paused": _("Paused"),
"Checking": _("Checking"),
"Queued": _("Queued"),
"Error": _("Error"),
}
del _
def _t(text):
if text in TRANSLATE:
text = TRANSLATE[text]
return _(text)
# Cache the key used to calculate the current value set for the specific cell # Cache the key used to calculate the current value set for the specific cell
# renderer. This is much cheaper than fetch the current value and test if # renderer. This is much cheaper than fetch the current value and test if
# it's equal. # it's equal.
@ -169,9 +151,10 @@ def cell_data_progress(column, cell, model, row, data):
func_last_value["cell_data_progress"][0] = value func_last_value["cell_data_progress"][0] = value
cell.set_property("value", value) cell.set_property("value", value)
textstr = _t(state_str) # Marked for translate states text are in filtertreeview
textstr = _(state_str)
if state_str != "Seeding" and value < 100: if state_str != "Seeding" and value < 100:
textstr = textstr + " %.2f%%" % value textstr = "%s %.2f%%" % (textstr, value)
if func_last_value["cell_data_progress"][1] != textstr: if func_last_value["cell_data_progress"][1] != textstr:
func_last_value["cell_data_progress"][1] = textstr func_last_value["cell_data_progress"][1] = textstr