From ee9a5a19c11b6cfff50a5ce3b997d34e1a210c1d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 12 Sep 2008 08:47:33 +0000 Subject: [PATCH] Added support for adding torrents by infohash Fixes to magnet uri stuff --- deluge/common.py | 19 ++ deluge/core/core.py | 7 +- deluge/core/torrent.py | 3 + deluge/ui/gtkui/addtorrentdialog.py | 117 +++++----- deluge/ui/gtkui/dbusinterface.py | 4 +- .../ui/gtkui/glade/add_torrent_dialog.glade | 216 +++++++++--------- deluge/ui/gtkui/ipcinterface.py | 10 +- deluge/ui/gtkui/queuedtorrents.py | 21 +- 8 files changed, 234 insertions(+), 163 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 15dff0288..d5b848947 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -319,6 +319,12 @@ def is_url(url): import re return bool(re.search('^(https?|ftp)://', url)) +def is_magnet(uri): + """Returns True if uri is a valid bittorrent magnet uri.""" + if uri[:20] == "magnet:?xt=urn:btih:": + return True + return False + def fetch_url(url): """Downloads a torrent file from a given URL and checks the file's validity.""" @@ -351,3 +357,16 @@ def pythonize(var): if isinstance(var, klass): return klass(var) return var + +def create_magnet_uri(infohash, name=None, trackers=[]): + from base64 import b32encode + uri = "magnet:?xt=urn:btih:" + b32encode(infohash.decode("hex")) + if name: + uri = uri + "&dn=" + name + if trackers: + for t in trackers: + uri = uri + "&tr=" + t + + return uri + + diff --git a/deluge/core/core.py b/deluge/core/core.py index 21d900f75..e58f6075a 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -401,7 +401,12 @@ class Core( def export_add_torrent_magnets(self, uris, options): for uri in uris: log.debug("Attempting to add by magnet uri: %s", uri) - torrent_id = self.torrents.add(magnet=uri, options=options[uris.index(uri)]) + try: + option = options[uris.index(uri)] + except IndexError: + option = None + + torrent_id = self.torrents.add(magnet=uri, options=option) # Run the plugin hooks for 'post_torrent_add' self.plugins.run_post_torrent_add(torrent_id) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 6cba44fc3..e0edfa53f 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -419,6 +419,9 @@ class Torrent: def get_file_progress(self): """Returns the file progress as a list of floats.. 0.0 -> 1.0""" + if not self.handle.has_metadata(): + return 0.0 + file_progress = self.handle.file_progress() ret = [] for i,f in enumerate(self.files): diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 133a3696d..1a83dace5 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -61,7 +61,6 @@ class AddTorrentDialog(component.Component): "on_button_file_clicked": self._on_button_file_clicked, "on_button_url_clicked": self._on_button_url_clicked, "on_button_hash_clicked": self._on_button_hash_clicked, - "on_button_magnet_clicked": self._on_button_magnet_clicked, "on_button_remove_clicked": self._on_button_remove_clicked, "on_button_trackers_clicked": self._on_button_trackers_clicked, "on_button_cancel_clicked": self._on_button_cancel_clicked, @@ -70,9 +69,6 @@ class AddTorrentDialog(component.Component): "on_button_revert_clicked": self._on_button_revert_clicked }) - self.glade.get_widget("image_magnet").set_from_file( - deluge.common.get_pixmap("magnet.png")) - self.torrent_liststore = gtk.ListStore(str, str, str) #download?, path, filesize, sequence number, inconsistent? self.files_treestore = gtk.TreeStore(bool, str, gobject.TYPE_UINT64, @@ -258,11 +254,14 @@ class AddTorrentDialog(component.Component): def _on_torrent_changed(self, treeselection): (model, row) = treeselection.get_selected() - self.files_treestore.clear() if row is None: + self.files_treestore.clear() return + # Save the previous torrents options + self.save_torrent_options() + # Update files list files_list = self.files[model.get_value(row, 0)] @@ -271,32 +270,33 @@ class AddTorrentDialog(component.Component): if self.core_config == {}: self.update_core_config() - # Save the previous torrents options - self.save_torrent_options() # Update the options frame self.update_torrent_options(model.get_value(row, 0)) self.previous_selected_torrent = row def prepare_file_store(self, files): + self.listview_files.set_model(None) + self.files_treestore.clear() split_files = { } i = 0 for file in files: - self.prepare_file(file, file["path"], i, split_files) + self.prepare_file(file, file["path"], i, file["download"], split_files) i += 1 self.add_files(None, split_files) - - def prepare_file(self, file, file_name, file_num, files_storage): - log.debug("file_name: %s", file_name) + self.listview_files.set_model(self.files_treestore) + self.listview_files.expand_row("0", False) + + def prepare_file(self, file, file_name, file_num, download, files_storage): first_slash_index = file_name.find("/") if first_slash_index == -1: - files_storage[file_name] = (file_num, file) + files_storage[file_name] = (file_num, file, download) else: file_name_chunk = file_name[:first_slash_index+1] if file_name_chunk not in files_storage: files_storage[file_name_chunk] = { } self.prepare_file(file, file_name[first_slash_index+1:], - file_num, files_storage[file_name_chunk]) + file_num, download, files_storage[file_name_chunk]) def add_files(self, parent_iter, split_files): ret = 0 @@ -308,8 +308,29 @@ class AddTorrentDialog(component.Component): self.files_treestore.set(chunk_iter, 2, chunk_size) ret += chunk_size else: - self.files_treestore.append(parent_iter, [True, key, + self.files_treestore.append(parent_iter, [value[2], key, value[1]["size"], value[0], False, gtk.STOCK_FILE]) + + # Iterate through the children and see what we should label the + # folder, download true, download false or inconsistent. + itr = self.files_treestore.iter_children(parent_iter) + download = [] + download_value = False + inconsistent = False + while itr: + download.append(self.files_treestore.get_value(itr, 0)) + itr = self.files_treestore.iter_next(itr) + + if sum(download) == len(download): + download_value = True + elif sum(download) == 0: + download_value = False + else: + inconsistent = True + + self.files_treestore.set_value(parent_iter, 0, download_value) + self.files_treestore.set_value(parent_iter, 4, inconsistent) + ret += value[1]["size"] return ret @@ -379,9 +400,11 @@ class AddTorrentDialog(component.Component): # Save the file priorities files_priorities = self.build_priorities( self.files_treestore.get_iter_first(), {}) - - for i, file_dict in enumerate(self.files[torrent_id]): - file_dict["download"] = files_priorities[i] + log.debug("fp: %s", len(files_priorities)) + + if len(files_priorities) > 0: + for i, file_dict in enumerate(self.files[torrent_id]): + file_dict["download"] = files_priorities[i] def build_priorities(self, iter, priorities): while iter is not None: @@ -532,7 +555,7 @@ class AddTorrentDialog(component.Component): text = clip.wait_for_text() if text: text = text.strip() - if deluge.common.is_url(text): + if deluge.common.is_url(text) or deluge.common.is_magnet(text): entry.set_text(text) dialog.show_all() @@ -547,7 +570,10 @@ class AddTorrentDialog(component.Component): # add it to the list. log.debug("url: %s", url) if url != None: - self.add_from_url(url) + if deluge.common.is_url(url): + self.add_from_url(url) + elif deluge.common.is_magnet(url): + self.add_from_magnets([url]) entry.set_text("") dialog.hide() @@ -566,45 +592,34 @@ class AddTorrentDialog(component.Component): def _on_button_hash_clicked(self, widget): log.debug("_on_button_hash_clicked") + dialog = self.glade.get_widget("dialog_infohash") + entry = self.glade.get_widget("entry_hash") + textview = self.glade.get_widget("text_trackers") - def _on_button_magnet_clicked(self, widget): - log.debug("_on_button_magnet_clicked") - dialog = self.glade.get_widget("dialog_magnet") - entry = self.glade.get_widget("entry_magnet") - self.glade.get_widget("image_dialog_magnet").set_from_file( - deluge.common.get_pixmap("magnet.png")) - dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_transient_for(self.dialog) entry.grab_focus() - - if deluge.common.windows_check(): - import win32clipboard as clip - import win32con - clip.OpenClipboard() - text = clip.GetClipboardData(win32con.CF_UNICODETEXT) - clip.CloseClipboard() - else: - clip = gtk.clipboard_get(selection='PRIMARY') - text = clip.wait_for_text() - if text: - text = text.strip() - if text[:20] == "magnet:?xt=urn:btih:": - entry.set_text(text) - dialog.show_all() response = dialog.run() - - if response == gtk.RESPONSE_OK: - uri = entry.get_text().decode("utf_8") - else: - uri = None - - log.debug("magnet uri: %s", uri) - if uri: - self.add_from_magnets([uri]) + if response == gtk.RESPONSE_OK and len(entry.get_text()) == 40: + trackers = [] + b = textview.get_buffer() + lines = b.get_text(b.get_start_iter(), b.get_end_iter()).strip().split("\n") + log.debug("lines: %s", lines) + for l in lines: + if deluge.common.is_url(l): + trackers.append(l) + # Convert the information to a magnet uri, this is just easier to + # handle this way. + log.debug("trackers: %s", trackers) + magnet = deluge.common.create_magnet_uri( + infohash=entry.get_text().decode("utf_8"), + trackers=trackers) + log.debug("magnet uri: %s", magnet) + self.add_from_magnets([magnet]) entry.set_text("") + textview.get_buffer().set_text("") dialog.hide() def _on_button_remove_clicked(self, widget): @@ -651,7 +666,7 @@ class AddTorrentDialog(component.Component): if options != None: options["file_priorities"] = file_priorities - if filename[:20] == "magnet:?xt=urn:btih:": + if deluge.common.is_magnet(filename): torrent_magnets.append(filename) del options["file_priorities"] torrent_magnet_options.append(options) diff --git a/deluge/ui/gtkui/dbusinterface.py b/deluge/ui/gtkui/dbusinterface.py index 0518fe435..5de59ba91 100644 --- a/deluge/ui/gtkui/dbusinterface.py +++ b/deluge/ui/gtkui/dbusinterface.py @@ -61,8 +61,10 @@ class DbusInterface(dbus.service.Object, component.Component): # Convert the paths to absolutes new_args = [] for arg in args: - if not deluge.common.is_url(arg): + if not deluge.common.is_url(arg) and not deluge.common.is_magnet(arg): new_args.append(os.path.abspath(arg)) + else: + new_args.append(arg) args = new_args # Send the args to the running session diff --git a/deluge/ui/gtkui/glade/add_torrent_dialog.glade b/deluge/ui/gtkui/glade/add_torrent_dialog.glade index 305c367f6..e873b6e27 100644 --- a/deluge/ui/gtkui/glade/add_torrent_dialog.glade +++ b/deluge/ui/gtkui/glade/add_torrent_dialog.glade @@ -1,6 +1,6 @@ - + 560 @@ -255,39 +255,6 @@ 2 - - - True - True - True - 0 - - - - True - - - True - - - - - True - _Magnet URI - True - True - - - 1 - - - - - - - 3 - - True @@ -331,7 +298,7 @@ False False - 4 + 3 @@ -571,7 +538,7 @@ 2 10 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -581,26 +548,61 @@ 1 2 - 3 - 4 - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - -1 -1 9999 1 10 10 + 0 + Max Down Speed: + + + GTK_FILL + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Up Speed: + + + 1 + 2 + GTK_FILL + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Connections: - 1 - 2 2 3 - + GTK_FILL + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Upload Slots: + + + 3 + 4 + GTK_FILL @@ -623,61 +625,7 @@ - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Upload Slots: - - - 3 - 4 - GTK_FILL - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Connections: - - - 2 - 3 - GTK_FILL - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Up Speed: - - - 1 - 2 - GTK_FILL - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Down Speed: - - - GTK_FILL - - - - - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -687,6 +635,25 @@ 1 2 + 2 + 3 + + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + + + 1 + 2 + 3 + 4 @@ -1145,11 +1112,11 @@ - + 462 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - Add Magnet URI + Add Infohash GTK_WIN_POS_CENTER_ON_PARENT True GDK_WINDOW_TYPE_HINT_DIALOG @@ -1174,6 +1141,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-revert-to-saved False @@ -1184,7 +1152,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>From Magnet URI</b> + <b>From Infohash</b> True @@ -1218,7 +1186,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - URI: + Infohash: False @@ -1226,7 +1194,7 @@ - + True True True @@ -1245,6 +1213,44 @@ 2 + + + True + 5 + + + True + 0 + Trackers: + + + False + False + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + + + + + 1 + + + + + 3 + + 1 diff --git a/deluge/ui/gtkui/ipcinterface.py b/deluge/ui/gtkui/ipcinterface.py index a9a6f5cfb..b65439c5c 100644 --- a/deluge/ui/gtkui/ipcinterface.py +++ b/deluge/ui/gtkui/ipcinterface.py @@ -88,6 +88,7 @@ def process_args(args): return config = ConfigManager("gtkui.conf") for arg in args: + log.debug("arg: %s", arg) if deluge.common.is_url(arg): log.debug("Attempting to add %s from external source..", arg) @@ -95,7 +96,14 @@ def process_args(args): component.get("AddTorrentDialog").add_from_url(arg) component.get("AddTorrentDialog").show(config["focus_add_dialog"]) else: - client.add_torrent_url(arg) + client.add_torrent_url(arg, None) + elif deluge.common.is_magnet(arg): + log.debug("Attempting to add %s from external source..", arg) + if config["interactive_add"]: + component.get("AddTorrentDialog").add_from_magnets([arg]) + component.get("AddTorrentDialog").show(config["focus_add_dialog"]) + else: + client.add_torrent_magnets([arg], []) else: # Just a file log.debug("Attempting to add %s from external source..", diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py index 784793775..0f244d7ca 100644 --- a/deluge/ui/gtkui/queuedtorrents.py +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -168,11 +168,24 @@ class QueuedTorrents(component.Component): # Add all the torrents in the liststore def add_torrent(model, path, iter, data): torrent_path = model.get_value(iter, 1) - if self.config["interactive_add"]: - component.get("AddTorrentDialog").add_from_files([torrent_path]) - component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) + if deluge.common.is_url(torrent_path): + if self.config["interactive_add"]: + component.get("AddTorrentDialog").add_from_url(torrent_path) + component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) + else: + client.add_torrent_url(torrent_path, None) + elif deluge.common.is_magnet(torrent_path): + if self.config["interactive_add"]: + component.get("AddTorrentDialog").add_from_magnets([torrent_path]) + component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) + else: + client.add_magnet_uris([torrent_path], []) else: - client.add_torrent_file([torrent_path]) + if self.config["interactive_add"]: + component.get("AddTorrentDialog").add_from_files([torrent_path]) + component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) + else: + client.add_torrent_file([torrent_path]) self.liststore.foreach(add_torrent, None) del self.queue[:]