EditTrackerDialog now works.

Save trackers in torrent state.
Load trackers on torrent add from state.
This commit is contained in:
Andrew Resch 2007-11-12 13:28:56 +00:00
parent 190d6d94ea
commit 6bced0fb8c
9 changed files with 373 additions and 35 deletions

12
TODO
View File

@ -2,20 +2,22 @@
* Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's
intended functionality.
* Figure out easy way for user-made plugins to add i18n support.
* Change the menubar.py gtkui component to menus.py and add support for plugins
to add menuitems to the torrentmenu in an easy way.
* Restart daemon function
* Docstrings!
* Implement caching in client.py
* Create a new add torrent dialog
* Create edit trackers dialog
* Implement open folder
* Maybe add pop-up menus to the status bar items
* Address issue where torrents will redownload if the storage is moved outside
of deluge.
* Hide open folder if not localhost
* Modify sensitivity of torrent/tray menu based on connection state
* Add classic/normal mode to preferences
* Implement 'Classic' mode
* Add remove torrent dialog and ability to remove data
* Tray tooltip
* Add DBUS to gtkui so we can add torrents to existing session
* Add LSD
* Add torrentfiles location config option
* Add autoload folder
* Add wizard
* Add a health indication to the statusbar

View File

@ -387,6 +387,10 @@ class Core(
def export_force_recheck(self, torrent_id):
"""Forces a data recheck on torrent_id"""
return self.torrents.force_recheck(torrent_id)
def export_set_torrent_trackers(self, torrent_id, trackers):
"""Sets a torrents tracker list. trackers will be [{"url", "tier"}]"""
return self.torrents.set_trackers(torrent_id, trackers)
# Signals
def torrent_added(self, torrent_id):

View File

@ -38,7 +38,8 @@ import deluge.common
class Torrent:
"""Torrent holds information about torrents added to the libtorrent session.
"""
def __init__(self, filename, handle, compact, save_path, total_uploaded=0):
def __init__(self, filename, handle, compact, save_path, total_uploaded=0,
trackers=None):
# Set the filename
self.filename = filename
# Set the libtorrent handle
@ -53,7 +54,18 @@ class Torrent:
self.save_path = save_path
# The tracker status
self.tracker_status = ""
# Tracker list
if trackers == None:
self.trackers = []
# Create a list of trackers
for value in self.handle.trackers():
tracker = {}
tracker["url"] = value.url
tracker["tier"] = value.tier
self.trackers.append(tracker)
else:
self.trackers = trackers
def set_tracker_status(self, status):
"""Sets the tracker status"""
self.tracker_status = status
@ -62,7 +74,8 @@ class Torrent:
"""Returns the state of this torrent for saving to the session state"""
status = self.handle.status()
return (self.torrent_id, self.filename, self.compact, status.paused,
self.save_path, self.total_uploaded + status.total_payload_upload)
self.save_path, self.total_uploaded + status.total_payload_upload,
self.trackers)
def get_eta(self):
"""Returns the ETA in seconds for this torrent"""
@ -135,7 +148,7 @@ class Torrent:
distributed_copies = status.distributed_copies
if distributed_copies < 0:
distributed_copies = 0.0
full_status = {
"name": self.handle.torrent_info().name(),
"total_size": self.handle.torrent_info().total_size(),
@ -161,6 +174,7 @@ class Torrent:
"eta": self.get_eta(),
"ratio": self.get_ratio(),
"tracker": status.current_tracker,
"trackers": self.trackers,
"tracker_status": self.tracker_status,
"save_path": self.save_path,
"files": self.get_files()

View File

@ -48,13 +48,14 @@ from deluge.log import LOG as log
class TorrentState:
def __init__(self, torrent_id, filename, compact, paused, save_path,
total_uploaded):
total_uploaded, trackers):
self.torrent_id = torrent_id
self.filename = filename
self.compact = compact
self.paused = paused
self.save_path = save_path
self.total_uploaded = total_uploaded
self.trackers = trackers
class TorrentManagerState:
def __init__(self):
@ -121,7 +122,7 @@ class TorrentManager:
return self.torrents.keys()
def add(self, filename, filedump=None, compact=None, paused=False,
save_path=None, total_uploaded=0):
save_path=None, total_uploaded=0, trackers=None):
"""Add a torrent to the manager and returns it's torrent_id"""
log.info("Adding torrent: %s", filename)
@ -167,14 +168,14 @@ class TorrentManager:
storage_mode = lt.storage_mode_t(1)
else:
storage_mode = lt.storage_mode_t(2)
try:
handle = self.session.add_torrent(
lt.torrent_info(filedump),
str(save_path),
resume_data=fastresume,
storage_mode=storage_mode,
paused=paused)
paused=True)
except RuntimeError, e:
log.warning("Error adding torrent: %s", e)
@ -184,15 +185,23 @@ class TorrentManager:
# Create a Torrent object
torrent = Torrent(filename, handle, compact,
save_path, total_uploaded)
save_path, total_uploaded, trackers)
# Add the torrent object to the dictionary
self.torrents[torrent.torrent_id] = torrent
# Set the trackers
if trackers != None:
self.set_trackers(str(handle.info_hash()), trackers)
# Set per-torrent limits
handle.set_max_connections(self.max_connections)
handle.set_max_uploads(self.max_uploads)
# Resume the torrent if needed
if paused == False:
handle.resume()
# Save the torrent file
self.save_torrent(filename, filedump)
@ -304,7 +313,28 @@ class TorrentManager:
log.warning("Unable to resume torrent %s", key)
return torrent_was_resumed
def set_trackers(self, torrent_id, trackers):
"""Sets trackers"""
if trackers == [] or trackers == None:
return
log.debug("Setting trackers for %s", torrent_id)
tracker_list = []
for tracker in trackers:
new_entry = lt.announce_entry(tracker["url"])
new_entry.tier = tracker["tier"]
tracker_list.append(new_entry)
self.torrents[torrent_id].handle.replace_trackers(tracker_list)
# Print out the trackers
for t in self.torrents[torrent_id].handle.trackers():
log.debug("tier: %s tracker: %s", t.tier, t.url)
# Set the tracker list in the torrent object
self.torrents[torrent_id].trackers = trackers
# Force a reannounce
self.force_reannounce(torrent_id)
def force_reannounce(self, torrent_id):
"""Force a tracker reannounce"""
try:
@ -381,7 +411,8 @@ class TorrentManager:
for torrent_state in state.torrents:
self.add(torrent_state.filename, compact=torrent_state.compact,
paused=torrent_state.paused, save_path=torrent_state.save_path,
total_uploaded=torrent_state.total_uploaded)
total_uploaded=torrent_state.total_uploaded,
trackers=torrent_state.trackers)
def save_state(self):
"""Save the state of the TorrentManager to the torrents.state file"""

View File

@ -337,6 +337,13 @@ def force_recheck(torrent_ids):
except (AttributeError, socket.error):
set_core_uri(None)
def set_torrent_trackers(torrent_id, trackers):
"""Sets the torrents trackers"""
try:
get_core().set_torrent_trackers(torrent_id, trackers)
except (AttributeError, socket.error):
set_core_uri(None)
def open_url_in_browser(url):
"""Opens link in the desktop's default browser"""
def start_browser():

View File

@ -41,11 +41,16 @@ from deluge.log import LOG as log
class EditTrackersDialog:
def __init__(self, torrent_id, parent=None):
self.torrent_id = torrent_id
self.glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/edit_trackers.glade"))
self.dialog = self.glade.get_widget("edit_trackers_dialog")
self.treeview = self.glade.get_widget("tracker_treeview")
self.add_tracker_dialog = self.glade.get_widget("add_tracker_dialog")
self.add_tracker_dialog.set_transient_for(self.dialog)
self.dialog.set_icon(deluge.common.get_logo(32))
if parent != None:
@ -58,21 +63,148 @@ class EditTrackersDialog:
"on_button_remove_clicked": self.on_button_remove_clicked,
"on_button_down_clicked": self.on_button_down_clicked,
"on_button_ok_clicked": self.on_button_ok_clicked,
"on_button_cancel_clicked": self.on_button_cancel_clicked
"on_button_cancel_clicked": self.on_button_cancel_clicked,
"on_button_add_ok_clicked": self.on_button_add_ok_clicked,
"on_button_add_cancel_clicked": self.on_button_add_cancel_clicked
})
# Create a liststore for tier, url
self.liststore = gtk.ListStore(int, str)
# Create the columns
self.treeview.append_column(
gtk.TreeViewColumn(_("Tier"), gtk.CellRendererText(), text=0))
self.treeview.append_column(
gtk.TreeViewColumn(_("Tracker"), gtk.CellRendererText(), text=1))
self.treeview.set_model(self.liststore)
self.liststore.set_sort_column_id(0, gtk.SORT_ASCENDING)
def run(self):
# Make sure we have a torrent_id.. if not just return
if self.torrent_id == None:
return
# Get the trackers for this torrent
trackers = client.get_torrent_status(self.torrent_id, ["trackers"])
for tracker in trackers["trackers"]:
self.add_tracker(tracker["tier"], tracker["url"])
self.dialog.show()
def add_tracker(self, tier, url):
"""Adds a tracker to the list"""
self.liststore.append([tier, url])
def get_selected(self):
"""Returns the selected tracker"""
return self.treeview.get_selection().get_selected()[1]
def on_button_up_clicked(self, widget):
log.debug("on_button_up_clicked")
selected = self.get_selected()
num_rows = self.liststore.iter_n_children(None)
if selected != None and num_rows > 1:
tier = self.liststore.get_value(selected, 0)
new_tier = tier - 1
# Return if the tier is already at the top
if tier == 0:
return
# Change the tier of the tracker we're surplanting
def change_tier(model, path, iter, data):
t = model.get_value(iter, 0)
if t == data:
model.set_value(iter, 0, data + 1)
self.liststore.foreach(change_tier, new_tier)
# Now change the tier for this tracker
self.liststore.set_value(selected, 0, new_tier)
def on_button_add_clicked(self, widget):
log.debug("on_button_add_clicked")
# Show the add tracker dialog
self.add_tracker_dialog.show()
self.glade.get_widget("entry_tracker").grab_focus()
def on_button_remove_clicked(self, widget):
log.debug("on_button_remove_clicked")
selected = self.get_selected()
if selected != None:
self.liststore.remove(selected)
def on_button_down_clicked(self, widget):
log.debug("on_button_down_clicked")
selected = self.get_selected()
num_rows = self.liststore.iter_n_children(None)
if selected != None and num_rows > 1:
tier = self.liststore.get_value(selected, 0)
new_tier = tier + 1
# This tracker is on the bottom already
if new_tier == num_rows:
return
# Change the tier of the tracker we're surplanting
def change_tier(model, path, iter, data):
t = model.get_value(iter, 0)
if t == data:
model.set_value(iter, 0, data - 1)
self.liststore.foreach(change_tier, new_tier)
# Now change the tier for this tracker
self.liststore.set_value(selected, 0, new_tier)
def on_button_ok_clicked(self, widget):
log.debug("on_button_ok_clicked")
self.trackers = []
def each(model, path, iter, data):
tracker = {}
tracker["tier"] = model.get_value(iter, 0)
tracker["url"] = model.get_value(iter, 1)
self.trackers.append(tracker)
self.liststore.foreach(each, None)
# Set the torrens trackers
client.set_torrent_trackers(self.torrent_id, self.trackers)
self.dialog.destroy()
def on_button_cancel_clicked(self, widget):
log.debug("on_button_cancel_clicked")
self.dialog.destroy()
def on_button_add_ok_clicked(self, widget):
log.debug("on_button_add_ok_clicked")
tracker = self.glade.get_widget("entry_tracker").get_text()
if tracker[:7] != "http://" or tracker[:8] != "https://":
# Bad url.. lets prepend http://
tracker = "http://" + tracker
# Figure out what tier number to use.. it's going to be the highest+1
# Also check for duplicates
self.highest_tier = 0
self.duplicate = False
def tier_count(model, path, iter, data):
tier = model.get_value(iter, 0)
if tier > self.highest_tier:
self.highest_tier = tier
tracker = model.get_value(iter, 1)
if data == tracker:
# We already have this tracker in the list
self.duplicate = True
# Check if there are any entries
if self.liststore.iter_n_children(None) > 0:
self.liststore.foreach(tier_count, tracker)
else:
self.highest_tier = -1
# If not a duplicate, then add it to the list
if not self.duplicate:
# Add the tracker to the list
self.add_tracker(self.highest_tier + 1, tracker)
# Clear the entry widget and hide the dialog
self.glade.get_widget("entry_tracker").set_text("")
self.add_tracker_dialog.hide()
def on_button_add_cancel_clicked(self, widget):
log.debug("on_button_add_cancel_clicked")
# Clear the entry widget and hide the dialog
self.glade.get_widget("entry_tracker").set_text("")
self.add_tracker_dialog.hide()

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.0 on Sat Nov 10 03:18:41 2007 -->
<!--Generated with glade3 3.4.0 on Mon Nov 12 03:27:39 2007 -->
<glade-interface>
<widget class="GtkDialog" id="edit_trackers_dialog">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -70,20 +70,12 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<child>
<widget class="GtkViewport" id="viewport3">
<widget class="GtkTreeView" id="tracker_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="resize_mode">GTK_RESIZE_QUEUE</property>
<property name="shadow_type">GTK_SHADOW_NONE</property>
<child>
<widget class="GtkTreeView" id="tracker_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="headers_clickable">True</property>
</widget>
</child>
</widget>
</child>
</widget>
@ -93,7 +85,7 @@
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">1</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<property name="layout_style">GTK_BUTTONBOX_CENTER</property>
<child>
<widget class="GtkButton" id="button_up">
<property name="visible">True</property>
@ -160,8 +152,6 @@
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
@ -211,4 +201,152 @@
</widget>
</child>
</widget>
<widget class="GtkDialog" id="add_tracker_dialog">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Add Tracker</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="decorated">False</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">2</property>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">5</property>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">5</property>
<child>
<widget class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-add</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">&lt;b&gt;Add Tracker&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">5</property>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Tracker:</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry_tracker">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="is_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="activates_default">True</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="button_add_cancel">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_add_cancel_clicked"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="button_add_ok">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">gtk-ok</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_add_ok_clicked"/>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View File

@ -184,7 +184,9 @@ class MenuBar(component.Component):
def on_menuitem_edittrackers_activate(self, data=None):
log.debug("on_menuitem_edittrackers_activate")
from edittrackersdialog import EditTrackersDialog
dialog = EditTrackersDialog(None, component.get("MainWindow").window)
dialog = EditTrackersDialog(
component.get("TorrentView").get_selected_torrent(),
component.get("MainWindow").window)
dialog.run()
def on_menuitem_remove_activate(self, data=None):

View File

@ -271,7 +271,15 @@ class TorrentView(listview.ListView, component.Component):
self.update()
break
row = self.liststore.iter_next(row)
def get_selected_torrent(self):
"""Returns a torrent_id or None. If multiple torrents are selected,
it will return the torrent_id of the first one."""
selected = self.get_selected_torrents()
if selected == None:
return selected
return selected[0]
def get_selected_torrents(self):
"""Returns a list of selected torrents or None"""
torrent_ids = []