diff --git a/deluge/core/core.py b/deluge/core/core.py index 1ef9e9fa3..238d6ef88 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -68,7 +68,7 @@ STATUS_KEYS = ['active_time', 'compact', 'distributed_copies', 'download_payload 'move_on_completed_path', 'name', 'next_announce', 'num_files', 'num_peers', 'num_pieces', 'num_seeds', 'paused', 'peers', 'piece_length', 'prioritize_first_last', 'private', 'progress', 'queue', 'ratio', 'remove_at_ratio', 'save_path', 'seed_rank', 'seeding_time', 'state', 'stop_at_ratio', - 'stop_ratio', 'total_done', 'total_payload_download', 'total_payload_upload', 'total_peers', + 'stop_ratio', 'time_added', 'total_done', 'total_payload_download', 'total_payload_upload', 'total_peers', 'total_seeds', 'total_size', 'total_uploaded', 'total_wanted', 'tracker', 'tracker_host', 'tracker_status', 'trackers', 'upload_payload_rate'] diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index ee214adb9..f1c76117f 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -34,6 +34,7 @@ """Internal Torrent class""" import os +import time from urlparse import urlparse try: @@ -198,6 +199,11 @@ class Torrent: # The tracker status self.tracker_status = "" + if state: + self.time_added = state.time_added + else: + self.time_added = time.time() + log.debug("Torrent object created.") ## Options methods ## @@ -566,7 +572,8 @@ class Torrent: "stop_at_ratio": self.options["stop_at_ratio"], "remove_at_ratio": self.options["remove_at_ratio"], "move_on_completed": self.options["move_completed"], - "move_on_completed_path": self.options["move_completed_path"] + "move_on_completed_path": self.options["move_completed_path"], + "time_added": self.time_added } def ti_name(): diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 65233f144..fbb4bd4f1 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -2,19 +2,19 @@ # torrentmanager.py # # Copyright (C) 2007, 2008 Andrew Resch ('andar') -# +# # Deluge is free software. -# +# # You may redistribute it and/or modify it under the terms of the # GNU General Public License, as published by the Free Software # Foundation; either version 3 of the License, or (at your option) # any later version. -# +# # deluge is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with deluge. If not, write to: # The Free Software Foundation, Inc., @@ -53,7 +53,7 @@ from deluge.core.torrent import TorrentOptions import deluge.core.oldstateupgrader from deluge.log import LOG as log - + class TorrentState: def __init__(self, torrent_id=None, @@ -61,7 +61,7 @@ class TorrentState: total_uploaded=0, trackers=None, compact=False, - paused=False, + paused=False, save_path=None, max_connections=-1, max_upload_slots=-1, @@ -75,7 +75,8 @@ class TorrentState: stop_ratio=2.00, stop_at_ratio=False, remove_at_ratio=False, - magnet=None + magnet=None, + time_added=-1 ): self.torrent_id = torrent_id self.filename = filename @@ -84,6 +85,7 @@ class TorrentState: self.queue = queue self.is_finished = is_finished self.magnet = magnet + self.time_added = time_added # Options self.compact = compact @@ -108,7 +110,7 @@ class TorrentManager(component.Component): """TorrentManager contains a list of torrents in the current libtorrent session. This object is also responsible for saving the state of the session for use on restart.""" - + def __init__(self, session, alerts): component.Component.__init__(self, "TorrentManager", interval=5000, depend=["PluginManager"]) log.debug("TorrentManager init..") @@ -121,12 +123,12 @@ class TorrentManager(component.Component): # Create the torrents dict { torrent_id: Torrent } self.torrents = {} - + # This is a list of torrent_id when we shutdown the torrentmanager. # We use this list to determine if all active torrents have been paused # and that their resume data has been written. self.shutdown_torrent_pause_list = [] - + # Register set functions self.config.register_set_function("max_connections_per_torrent", self.on_set_max_connections_per_torrent) @@ -136,9 +138,9 @@ class TorrentManager(component.Component): self.on_set_max_upload_speed_per_torrent) self.config.register_set_function("max_download_speed_per_torrent", self.on_set_max_download_speed_per_torrent) - + # Register alert functions - self.alerts.register_handler("torrent_finished_alert", + self.alerts.register_handler("torrent_finished_alert", self.on_alert_torrent_finished) self.alerts.register_handler("torrent_paused_alert", self.on_alert_torrent_paused) @@ -155,7 +157,7 @@ class TorrentManager(component.Component): self.on_alert_tracker_error) self.alerts.register_handler("storage_moved_alert", self.on_alert_storage_moved) - self.alerts.register_handler("torrent_resumed_alert", + self.alerts.register_handler("torrent_resumed_alert", self.on_alert_torrent_resumed) self.alerts.register_handler("state_changed_alert", self.on_alert_state_changed) @@ -167,16 +169,16 @@ class TorrentManager(component.Component): self.on_alert_file_renamed) self.alerts.register_handler("metadata_received_alert", self.on_alert_metadata_received) - + def start(self): # Get the pluginmanager reference self.plugins = component.get("PluginManager") - + self.signals = component.get("SignalManager") # Run the old state upgrader before loading state deluge.core.oldstateupgrader.OldStateUpgrader() - + # Try to load the state from file self.load_state() @@ -187,7 +189,7 @@ class TorrentManager(component.Component): def stop(self): # Save state on shutdown self.save_state() - + for key in self.torrents.keys(): if not self.torrents[key].handle.is_paused(): # We set auto_managed false to prevent lt from resuming the torrent @@ -209,7 +211,7 @@ class TorrentManager(component.Component): time.sleep(0.01) # Wait for all alerts self.alerts.handle_alerts(True) - + def update(self): for torrent_id, torrent in self.torrents.items(): if self.config["stop_seed_at_ratio"] or torrent.options["stop_at_ratio"]: @@ -226,11 +228,11 @@ class TorrentManager(component.Component): def __getitem__(self, torrent_id): """Return the Torrent with torrent_id""" return self.torrents[torrent_id] - + def get_torrent_list(self): """Returns a list of torrent_ids""" return self.torrents.keys() - + def get_torrent_info_from_file(self, filepath): """Returns a torrent_info for the file specified or None""" torrent_info = None @@ -242,16 +244,16 @@ class TorrentManager(component.Component): _file.close() except (IOError, RuntimeError), e: log.warning("Unable to open %s: %s", filepath, e) - + return torrent_info - + def get_resume_data_from_file(self, torrent_id): """Returns an entry with the resume data or None""" fastresume = "" try: _file = open( os.path.join( - self.config["state_location"], + self.config["state_location"], torrent_id + ".fastresume"), "rb") fastresume = _file.read() @@ -260,18 +262,18 @@ class TorrentManager(component.Component): log.debug("Unable to load .fastresume: %s", e) return str(fastresume) - + def add(self, torrent_info=None, state=None, options=None, save_state=True, filedump=None, filename=None, magnet=None): """Add a torrent to the manager and returns it's torrent_id""" - + if torrent_info is None and state is None and filedump is None and magnet is None: log.debug("You must specify a valid torrent_info, torrent state or magnet.") return log.debug("torrentmanager.add") add_torrent_params = {} - + if filedump is not None: try: torrent_info = lt.torrent_info(lt.bdecode(filedump)) @@ -294,18 +296,18 @@ class TorrentManager(component.Component): options["download_location"] = state.save_path options["auto_managed"] = state.auto_managed options["add_paused"] = state.paused - + if not state.magnet: add_torrent_params["ti"] =\ self.get_torrent_info_from_file( os.path.join(self.config["state_location"], state.torrent_id + ".torrent")) - + if not add_torrent_params["ti"]: log.error("Unable to add torrent!") return else: magnet = state.magnet - + add_torrent_params["resume_data"] = self.get_resume_data_from_file(state.torrent_id) else: # We have a torrent_info object so we're not loading from state. @@ -319,16 +321,16 @@ class TorrentManager(component.Component): add_torrent_params["ti"] = torrent_info add_torrent_params["resume_data"] = "" - + #log.info("Adding torrent: %s", filename) log.debug("options: %s", options) - + # Set the right storage_mode if options["compact_allocation"]: storage_mode = lt.storage_mode_t(2) else: storage_mode = lt.storage_mode_t(1) - + # Fill in the rest of the add_torrent_params dictionary add_torrent_params["save_path"] = options["download_location"].encode("utf8") @@ -336,11 +338,11 @@ class TorrentManager(component.Component): add_torrent_params["paused"] = True add_torrent_params["auto_managed"] = False add_torrent_params["duplicate_is_error"] = True - + # We need to pause the AlertManager momentarily to prevent alerts # for this torrent being generated before a Torrent object is created. component.pause("AlertManager") - + handle = None try: if magnet: @@ -349,13 +351,13 @@ class TorrentManager(component.Component): handle = self.session.add_torrent(add_torrent_params) except RuntimeError, e: log.warning("Error adding torrent: %s", e) - + if not handle or not handle.is_valid(): log.debug("torrent handle is invalid!") # The torrent was not added to the session component.resume("AlertManager") return - + log.debug("handle id: %s", str(handle.info_hash())) # Set auto_managed to False because the torrent is paused handle.auto_managed(False) @@ -365,7 +367,7 @@ class TorrentManager(component.Component): self.torrents[torrent.torrent_id] = torrent if self.config["queue_new_to_top"]: handle.queue_position_top() - + component.resume("AlertManager") # Resume the torrent if needed @@ -375,7 +377,7 @@ class TorrentManager(component.Component): # Write the .torrent file to the state directory if filedump: try: - save_file = open(os.path.join(self.config["state_location"], + save_file = open(os.path.join(self.config["state_location"], torrent.torrent_id + ".torrent"), "wb") save_file.write(filedump) @@ -398,10 +400,10 @@ class TorrentManager(component.Component): if save_state: # Save the session state self.save_state() - + # Emit the torrent_added signal self.signals.emit("torrent_added", torrent.torrent_id) - + return torrent.torrent_id def load_torrent(self, torrent_id): @@ -412,16 +414,16 @@ class TorrentManager(component.Component): log.debug("Attempting to open %s for add.", torrent_id) _file = open( os.path.join( - self.config["state_location"], torrent_id + ".torrent"), + self.config["state_location"], torrent_id + ".torrent"), "rb") filedump = lt.bdecode(_file.read()) _file.close() except (IOError, RuntimeError), e: log.warning("Unable to open %s: %s", torrent_id, e) return False - + return filedump - + def remove(self, torrent_id, remove_torrent=False, remove_data=False): """Remove a torrent from the manager""" try: @@ -430,17 +432,17 @@ class TorrentManager(component.Component): # Remove data if set if remove_data: option = 1 - self.session.remove_torrent(self.torrents[torrent_id].handle, + self.session.remove_torrent(self.torrents[torrent_id].handle, option) except (RuntimeError, KeyError), e: log.warning("Error removing torrent: %s", e) return False - + # Remove the .torrent file if requested if remove_torrent: try: torrent_file = os.path.join( - self.config["torrentfiles_location"], + self.config["torrentfiles_location"], self.torrents[torrent_id].filename) os.remove(torrent_file) except Exception, e: @@ -448,24 +450,24 @@ class TorrentManager(component.Component): # Remove the .fastresume if it exists self.torrents[torrent_id].delete_fastresume() - + # Remove the .torrent file in the state self.torrents[torrent_id].delete_torrentfile() - + # Remove the torrent from deluge's session try: del self.torrents[torrent_id] except KeyError, ValueError: return False - + # Save the session state self.save_state() - + # Emit the signal to the clients self.signals.emit("torrent_removed", torrent_id) - + return True - + def load_state(self): """Load the state of the TorrentManager from the torrents.state file""" state = TorrentManagerState() @@ -478,7 +480,7 @@ class TorrentManager(component.Component): state_file.close() except (EOFError, IOError, Exception), e: log.warning("Unable to load state file: %s", e) - + # Try to use an old state try: if dir(state.torrents[0]) != dir(TorrentState()): @@ -487,7 +489,7 @@ class TorrentManager(component.Component): setattr(s, attr, getattr(TorrentState(), attr, None)) except Exception, e: log.warning("Unable to update state file to a compatible version: %s", e) - + # Reorder the state.torrents list to add torrents in the correct queue # order. ordered_state = [] @@ -498,14 +500,14 @@ class TorrentManager(component.Component): break if torrent_state not in ordered_state: ordered_state.append(torrent_state) - + for torrent_state in ordered_state: try: self.add(state=torrent_state, save_state=False) except AttributeError, e: log.error("Torrent state file is either corrupt or incompatible!") break - + # Run the post_session_load plugin hooks self.plugins.run_post_session_load() @@ -517,14 +519,14 @@ class TorrentManager(component.Component): paused = False if torrent.state == "Paused": paused = True - + torrent_state = TorrentState( torrent.torrent_id, torrent.filename, - torrent.get_status(["total_uploaded"])["total_uploaded"], + torrent.get_status(["total_uploaded"])["total_uploaded"], torrent.trackers, - torrent.options["compact_allocation"], - paused, + torrent.options["compact_allocation"], + paused, torrent.options["download_location"], torrent.options["max_connections"], torrent.options["max_upload_slots"], @@ -538,21 +540,22 @@ class TorrentManager(component.Component): torrent.options["stop_ratio"], torrent.options["stop_at_ratio"], torrent.options["remove_at_ratio"], - torrent.magnet + torrent.magnet, + torrent.time_added ) state.torrents.append(torrent_state) - + # Pickle the TorrentManagerState object try: log.debug("Saving torrent state file.") state_file = open( - os.path.join(self.config["state_location"], "torrents.state"), + os.path.join(self.config["state_location"], "torrents.state"), "wb") cPickle.dump(state, state_file) state_file.close() except IOError: log.warning("Unable to save state file.") - + # We return True so that the timer thread will continue return True @@ -560,7 +563,7 @@ class TorrentManager(component.Component): """Saves resume data for all the torrents""" for torrent in self.torrents.values(): torrent.save_resume_data() - + def queue_top(self, torrent_id): """Queue torrent to top""" if self.torrents[torrent_id].get_queue_position() == 0: @@ -581,7 +584,7 @@ class TorrentManager(component.Component): """Queue torrent down one position""" if self.torrents[torrent_id].get_queue_position() == (len(self.torrents) - 1): return False - + self.torrents[torrent_id].handle.queue_position_down() return True @@ -592,13 +595,13 @@ class TorrentManager(component.Component): self.torrents[torrent_id].handle.queue_position_bottom() return True - + def on_set_max_connections_per_torrent(self, key, value): """Sets the per-torrent connection limit""" log.debug("max_connections_per_torrent set to %s..", value) for key in self.torrents.keys(): self.torrents[key].set_max_connections(value) - + def on_set_max_upload_slots_per_torrent(self, key, value): """Sets the per-torrent upload slot limit""" log.debug("max_upload_slots_per_torrent set to %s..", value) @@ -609,7 +612,7 @@ class TorrentManager(component.Component): log.debug("max_upload_speed_per_torrent set to %s..", value) for key in self.torrents.keys(): self.torrents[key].set_max_upload_speed(value) - + def on_set_max_download_speed_per_torrent(self, key, value): log.debug("max_download_speed_per_torrent set to %s..", value) for key in self.torrents.keys(): @@ -637,7 +640,7 @@ class TorrentManager(component.Component): torrent.update_state() torrent.save_resume_data() component.get("SignalManager").emit("torrent_finished", torrent_id) - + def on_alert_torrent_paused(self, alert): log.debug("on_alert_torrent_paused") # Get the torrent_id @@ -645,20 +648,20 @@ class TorrentManager(component.Component): # Set the torrent state self.torrents[torrent_id].update_state() component.get("SignalManager").emit("torrent_paused", torrent_id) - + # Write the fastresume file self.torrents[torrent_id].save_resume_data() - + if torrent_id in self.shutdown_torrent_pause_list: self.shutdown_torrent_pause_list.remove(torrent_id) - + def on_alert_torrent_checked(self, alert): log.debug("on_alert_torrent_checked") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) # Set the torrent state self.torrents[torrent_id].update_state() - + def on_alert_tracker_reply(self, alert): log.debug("on_alert_tracker_reply: %s", alert.message()) # Get the torrent_id @@ -669,7 +672,7 @@ class TorrentManager(component.Component): self.torrents[torrent_id].set_tracker_status(_("Announce OK")) except KeyError: log.debug("torrent_id doesn't exist.") - + # Check to see if we got any peer information from the tracker if alert.handle.status().num_complete == -1 or \ alert.handle.status().num_incomplete == -1: @@ -684,7 +687,7 @@ class TorrentManager(component.Component): except RuntimeError: log.debug("Invalid torrent handle.") return - + # Set the tracker status for the torrent try: self.torrents[torrent_id].set_tracker_status(_("Announce Sent")) @@ -702,7 +705,7 @@ class TorrentManager(component.Component): self.torrents[torrent_id].set_tracker_status(tracker_status) except KeyError: log.debug("torrent_id doesn't exist.") - + def on_alert_tracker_warning(self, alert): log.debug("on_alert_tracker_warning") # Get the torrent_id @@ -722,7 +725,7 @@ class TorrentManager(component.Component): torrent.set_tracker_status(tracker_status) except KeyError: log.debug("torrent_id doesn't exist.") - + def on_alert_storage_moved(self, alert): log.debug("on_alert_storage_moved") log.debug("save_path: %s", alert.handle.save_path()) @@ -739,7 +742,7 @@ class TorrentManager(component.Component): torrent = self.torrents[str(alert.handle.info_hash())] torrent.is_finished = torrent.handle.is_seed() torrent.update_state() - + def on_alert_state_changed(self, alert): log.debug("on_alert_state_changed") torrent_id = str(alert.handle.info_hash()) @@ -750,7 +753,7 @@ class TorrentManager(component.Component): log.debug("on_alert_save_resume_data") torrent = self.torrents[str(alert.handle.info_hash())] torrent.write_resume_data(alert.resume_data) - + def on_alert_save_resume_data_failed(self, alert): log.debug("on_alert_save_resume_data_failed: %s", alert.message()) torrent = self.torrents[str(alert.handle.info_hash())] @@ -764,7 +767,7 @@ class TorrentManager(component.Component): old_folder = os.path.split(torrent.files[alert.index]["path"])[0] new_folder = os.path.split(alert.name)[0] torrent.files[alert.index]["path"] = alert.name - + if alert.index in torrent.waiting_on_folder_rename: if torrent.waiting_on_folder_rename == [alert.index]: # This is the last alert we were waiting for, time to send signal @@ -778,4 +781,3 @@ class TorrentManager(component.Component): log.debug("on_alert_metadata_received") torrent = self.torrents[str(alert.handle.info_hash())] torrent.write_torrentfile() - diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 693043a58..654a2a4d5 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -685,293 +685,165 @@ 10 5 - - - - - - - + True 0 - 5 - 6 + 7 + 8 3 4 GTK_FILL - + True 0 - <b>Auto Managed:</b> - True - - - 4 - 5 - 3 - 4 - GTK_FILL - - - - - True - - - 7 - 8 - 2 - 3 - GTK_FILL - - - - - True - - - 7 - 8 - 1 - 2 - GTK_FILL - - - - - True - 0 - <b>Seed Rank:</b> + <b>Date Added:</b> True 6 7 - 2 - 3 - GTK_FILL - - - - - True - 0 - <b>Seeding Time:</b> - True - - - 6 - 7 - 1 - 2 - GTK_FILL - - - - - True - - - 7 - 8 - GTK_FILL - - - - - True - 0 - <b>Active Time:</b> - True - - - 6 - 7 - GTK_FILL - - - - - True - 0 - True - - - 1 - 2 3 4 GTK_FILL - - True - 0 - - - 3 - 4 - 3 - 4 - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_CHAR - True - - - 1 - 8 - 4 - 5 - GTK_FILL - - - - - True - 0 - 0 - <b>Tracker Status:</b> - True - - - 4 - 5 - GTK_FILL - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 5 - 6 - 2 - 3 - GTK_FILL - - - - - True - 0 - 1 - <b>Availability:</b> - True - - - 4 - 5 - 2 - 3 - GTK_FILL - - - - - True - 0 - - - 3 - 4 - 2 - 3 - GTK_FILL - - - - + True 0 1 2 + GTK_FILL + + + + + True + 0 + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + 0 + + + 3 + 4 + 1 + 2 + GTK_FILL + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + GTK_FILL + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + 2 3 GTK_FILL - + True - 0 + 5 + + + True + 0 + <b>Next Announce:</b> + True + + - 5 - 6 - 1 - 2 + 3 + 4 GTK_FILL - - True - 0 - <b>Peers:</b> - True - - - 4 - 5 - 1 - 2 - GTK_FILL - - - - - True - 0 - - - 5 - 6 - GTK_FILL - - - - - True - 0 - <b>Seeders:</b> - True - - - 4 - 5 - GTK_FILL - - - - + True 15 5 - + True 0 - <b>Pieces:</b> + <b>Speed:</b> True @@ -979,30 +851,6 @@ 2 3 - 3 - 4 - GTK_FILL - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 2 - 3 GTK_FILL @@ -1029,15 +877,15 @@ - + True 15 5 - + True 0 - <b>Speed:</b> + <b>ETA:</b> True @@ -1045,128 +893,305 @@ 2 3 - GTK_FILL - - - - - True - 5 - - - True - 0 - <b>Next Announce:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - 2 3 GTK_FILL - + True + 15 5 - + True 0 - <b>Uploaded:</b> + <b>Pieces:</b> True - 1 - 2 + 2 + 3 + 3 + 4 GTK_FILL - + True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - + 0 + <b>Seeders:</b> + True + 4 + 5 GTK_FILL - + True 0 - 3 - 4 + 5 + 6 + GTK_FILL + + + + + True + 0 + <b>Peers:</b> + True + + + 4 + 5 1 2 GTK_FILL - + + True + 0 + + + 5 + 6 + 1 + 2 + GTK_FILL + + + + True 0 1 2 - 1 - 2 + 2 + 3 GTK_FILL - + True 0 3 4 + 2 + 3 GTK_FILL - + + True + 0 + 1 + <b>Availability:</b> + True + + + 4 + 5 + 2 + 3 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 5 + 6 + 2 + 3 + GTK_FILL + + + + + True + 0 + 0 + <b>Tracker Status:</b> + True + + + 4 + 5 + GTK_FILL + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_CHAR + True + + + 1 + 8 + 4 + 5 + GTK_FILL + + + + True 0 + + 3 + 4 + 3 + 4 + GTK_FILL + + + + + True + 0 + True + 1 2 + 3 + 4 + GTK_FILL + + + + + True + 0 + <b>Active Time:</b> + True + + + 6 + 7 + GTK_FILL + + + + + True + 0 + + + 7 + 8 + GTK_FILL + + + + + True + 0 + <b>Seeding Time:</b> + True + + + 6 + 7 + 1 + 2 + GTK_FILL + + + + + True + 0 + <b>Seed Rank:</b> + True + + + 6 + 7 + 2 + 3 + GTK_FILL + + + + + True + 0 + + + 7 + 8 + 1 + 2 + GTK_FILL + + + + + True + 0 + + + 7 + 8 + 2 + 3 + GTK_FILL + + + + + True + 0 + <b>Auto Managed:</b> + True + + + 4 + 5 + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 5 + 6 + 3 + 4 GTK_FILL @@ -1254,7 +1279,7 @@ - + True 0 True @@ -1262,114 +1287,45 @@ 1 2 - 4 - 5 - - - - - - True - 0 - 1 - <b># of files:</b> - True - - - 4 - 5 - GTK_FILL - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_CHAR - True - - - 1 - 4 - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Hash:</b> - True - - - 1 - 2 - GTK_FILL - - - - - - True - 0 - PANGO_WRAP_CHAR - True - - - 1 - 4 - 6 - 7 - - - - - - True - 0 - 1 - <b>Tracker:</b> - True - - - 6 - 7 - GTK_FILL - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - 3 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + + + 1 + 4 + 5 + 6 + + + + + + True + 0 + 1 + <b>Status:</b> + True + + + 5 + 6 GTK_FILL - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 True PANGO_WRAP_CHAR @@ -1378,27 +1334,8 @@ 1 4 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL + 2 + 3 @@ -1425,7 +1362,110 @@ - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK + 0 + True + PANGO_WRAP_CHAR + True + + + 1 + 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + + True + 0 + 1 + <b>Tracker:</b> + True + + + 6 + 7 + GTK_FILL + + + + + + True + 0 + PANGO_WRAP_CHAR + True + + + 1 + 4 + 6 + 7 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Hash:</b> + True + + + 1 + 2 + GTK_FILL + + + + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 @@ -1436,43 +1476,28 @@ 1 4 - 2 - 3 + 1 + 2 - + True 0 1 - <b>Status:</b> + <b># of files:</b> True - 5 - 6 + 4 + 5 GTK_FILL - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - - - 1 - 4 - 5 - 6 - - - - - + True 0 True @@ -1480,8 +1505,8 @@ 1 2 - 3 - 4 + 4 + 5 @@ -1664,7 +1689,7 @@ - + True True 6 @@ -1674,8 +1699,100 @@ 1 2 + 3 + 4 + + + + + + + True + 0 + Max Upload Slots: + + + 3 + 4 + GTK_FILL + + + + + + True + KiB/s + + + 2 + 3 + 1 + 2 + + + + + + + True + KiB/s + + + 2 + 3 + + + + + + + True + 0 + Max Download Speed: + + + GTK_FILL + + + + + + True + 0 + Max Upload Speed: + + + 1 + 2 + GTK_FILL + + + + + + True + 0 + Max Connections: + + 2 3 + GTK_FILL + + + + + + True + True + 6 + 1 + -1 -1 999999 1 10 10 + 1 + + + 1 + 2 @@ -1699,110 +1816,18 @@ - + True True 6 1 -1 -1 999999 1 10 10 - 1 1 2 - - - - - - - True - 0 - Max Connections: - - 2 3 - GTK_FILL - - - - - - True - 0 - Max Upload Speed: - - - 1 - 2 - GTK_FILL - - - - - - True - 0 - Max Download Speed: - - - GTK_FILL - - - - - - True - KiB/s - - - 2 - 3 - - - - - - - True - KiB/s - - - 2 - 3 - 1 - 2 - - - - - - - True - 0 - Max Upload Slots: - - - 3 - 4 - GTK_FILL - - - - - - True - True - 6 - 1 - -1 -1 999999 1 10 10 - - - 1 - 2 - 3 - 4 @@ -2440,14 +2465,23 @@ 10 2 - + + True + 0 + <i>Current Version:</i> + True + + + GTK_FILL + + + + True 1 2 - 1 - 2 @@ -2465,26 +2499,17 @@ - + True 1 2 + 1 + 2 - - - True - 0 - <i>Current Version:</i> - True - - - GTK_FILL - - diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 32737634c..3cebc89b9 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -34,6 +34,7 @@ import cPickle import os.path +import time import pygtk pygtk.require('2.0') @@ -88,6 +89,14 @@ def cell_data_ratio(column, cell, model, row, data): cell.set_property('text', ratio_str) +def cell_data_date(column, cell, model, row, data): + """Display value as date, eg 2008/05/05""" + time_val = model.get_value(row, data) + time_str = "" + if time_val > -1: + time_str = time.strftime("%d/%m/%y", time.localtime(time_val)) + cell.set_property('text', time_str) + class ListViewColumnState: """Used for saving/loading column state""" def __init__(self, name, position, width, visible, sort, sort_order): diff --git a/deluge/ui/gtkui/statistics_tab.py b/deluge/ui/gtkui/statistics_tab.py index 31083a079..b51b1b9a9 100644 --- a/deluge/ui/gtkui/statistics_tab.py +++ b/deluge/ui/gtkui/statistics_tab.py @@ -33,6 +33,7 @@ # statement from all source files in the program, then also delete it here. import gtk, gtk.glade +import time from deluge.ui.client import aclient as client import deluge.component as component @@ -60,6 +61,11 @@ def fspeed(value, max_value=-1): else: return deluge.common.fspeed(value) +def fdate(value): + if value < 0: + return "" + return time.strftime("%d/%m/%y", time.localtime(value)) + class StatisticsTab(Tab): def __init__(self): Tab.__init__(self) @@ -88,7 +94,8 @@ class StatisticsTab(Tab): (glade.get_widget("summary_seed_time"), deluge.common.ftime, ("seeding_time",)), (glade.get_widget("summary_seed_rank"), str, ("seed_rank",)), (glade.get_widget("summary_auto_managed"), str, ("is_auto_managed",)), - (glade.get_widget("progressbar"), fpcnt, ("progress",)) + (glade.get_widget("progressbar"), fpcnt, ("progress",)), + (glade.get_widget("summary_date_added"), fdate, ("time_added",)) ] def update(self): @@ -110,7 +117,7 @@ class StatisticsTab(Tab): "total_seeds", "eta", "ratio", "next_announce", "tracker_status", "max_connections", "max_upload_slots", "max_upload_speed", "max_download_speed", "active_time", - "seeding_time", "seed_rank", "is_auto_managed"] + "seeding_time", "seed_rank", "is_auto_managed", "time_added"] client.get_torrent_status( self._on_get_torrent_status, selected, status_keys) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index c7ca2cd15..dfe2582a5 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -180,6 +180,10 @@ class TorrentView(listview.ListView, component.Component): listview.cell_data_ratio, [float], status_field=["distributed_copies"]) + self.add_func_column(_("Added"), + listview.cell_data_date, + [float], + status_field=["time_added"]) self.add_text_column(_("Tracker"), status_field=["tracker_host"]) # Set filter to None for now