From cb8125aa1b9915a0e26ba10c88b7f9b19dbc72cd Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 20 Oct 2008 18:57:38 +0000 Subject: [PATCH] gtk tracker icons, some eye-candy --- deluge/core/torrent.py | 66 +++++++++--------- deluge/tests/test_tracker_icons.py | 15 +++++ deluge/tracker_icons.py | 105 +++++++++++++++++++++++++++++ deluge/ui/gtkui/filtertreeview.py | 15 ++++- 4 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 deluge/tests/test_tracker_icons.py create mode 100644 deluge/tracker_icons.py diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 788cec978..d3bad5f00 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -69,7 +69,7 @@ class TorrentOptions(dict): "download_location": "download_location", "add_paused": "add_paused" } - + def items(self): i = super(TorrentOptions, self).items() for k in self.default_keys: @@ -77,27 +77,27 @@ class TorrentOptions(dict): i.append((k, self.__getitem__(k))) return i - + def keys(self): k = super(TorrentOptions, self).keys() for key in self.default_keys.keys(): if key not in k: k.append(key) return k - + def iteritems(self): return self.items().itermitems() - + def has_key(self, key): if super(TorrentOptions, self).has_key(key): return True elif self.default_keys.has_key(key): return True return False - + def __setitem__(self, key, value): super(TorrentOptions, self).__setitem__(key, value) - + def __getitem__(self, key): if super(TorrentOptions, self).has_key(key): return super(TorrentOptions, self).__getitem__(key) @@ -106,7 +106,7 @@ class TorrentOptions(dict): return self.config[self.default_keys[key]] else: return self.default_keys[key] - + class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ @@ -124,22 +124,22 @@ class Torrent: # Let's us know if we're waiting on a lt alert self.waiting_on_resume_data = False - + # Keep a list of file indexes we're waiting for file_rename alerts on # This is so we can send one folder_renamed signal instead of multiple # file_renamed signals. self.waiting_on_folder_rename = [] - + # We store the filename just in case we need to make a copy of the torrentfile if not filename: # If no filename was provided, then just use the infohash filename = self.torrent_id - + self.filename = filename # Store the magnet uri used to add this torrent if available self.magnet = magnet - + # Holds status info so that we don't need to keep getting it from lt self.status = self.handle.status() @@ -147,17 +147,17 @@ class Torrent: self.torrent_info = self.handle.get_torrent_info() except RuntimeError: self.torrent_info = None - + # Files dictionary self.files = self.get_files() - + # Default total_uploaded to 0, this may be changed by the state self.total_uploaded = 0 # Set the default options self.options = TorrentOptions() self.options.update(options) - + # We need to keep track if the torrent is finished in the state to prevent # some weird things on state load. self.is_finished = False @@ -188,7 +188,7 @@ class Torrent: # Various torrent options self.handle.resolve_countries(True) self.set_options(self.options) - + # Status message holds error info about the torrent self.statusmsg = "OK" @@ -216,13 +216,13 @@ class Torrent: for (key, value) in options.items(): if OPTIONS_FUNCS.has_key(key): OPTIONS_FUNCS[key](value) - + self.options.update(options) - + def get_options(self): return self.options - + def set_max_connections(self, max_connections): self.options["max_connections"] = int(max_connections) self.handle.set_max_connections(max_connections) @@ -286,7 +286,7 @@ class Torrent: return log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities) - + self.handle.prioritize_files(file_priorities) if 0 in self.options["file_priorities"]: @@ -303,7 +303,7 @@ class Torrent: # Set the first/last priorities if needed self.set_prioritize_first_last(self.options["prioritize_first_last_pieces"]) - + def set_trackers(self, trackers): """Sets trackers""" if trackers == None: @@ -315,7 +315,7 @@ class Torrent: trackers.append(tracker) self.trackers = trackers return - + log.debug("Setting trackers for %s: %s", self.torrent_id, trackers) tracker_list = [] @@ -339,7 +339,7 @@ class Torrent: def set_save_path(self, save_path): self.options["download_location"] = save_path - + def set_tracker_status(self, status): """Sets the tracker status""" self.tracker_status = status @@ -430,10 +430,10 @@ class Torrent: torrent_info = self.handle.get_torrent_info() else: torrent_info = self.torrent_info - + if not torrent_info: return [] - + ret = [] files = torrent_info.files() for index, file in enumerate(files): @@ -487,7 +487,7 @@ class Torrent: """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): @@ -511,7 +511,7 @@ class Torrent: if tracker: url = urlparse(tracker) if hasattr(url, "hostname"): - host = (url.hostname or 'unknown?') + host = (url.hostname or 'DHT') parts = host.split(".") if len(parts) > 2: host = ".".join(parts[-2:]) @@ -575,7 +575,7 @@ class Torrent: "move_on_completed": self.options["move_completed"], "move_on_completed_path": self.options["move_completed_path"] } - + def ti_name(): if self.handle.has_metadata(): return self.torrent_info.name() @@ -600,7 +600,7 @@ class Torrent: if self.handle.has_metadata(): return self.torrent_info.piece_length() return 0 - + fns = { "name": ti_name, "private": ti_priv, @@ -633,7 +633,7 @@ class Torrent: self.status = None self.torrent_info = None - + return status_dict def apply_options(self): @@ -721,7 +721,7 @@ class Torrent: returned in a libtorrent alert""" self.handle.save_resume_data() self.waiting_on_resume_data = True - + def write_resume_data(self, resume_data): """Writes the .fastresume file for the torrent""" resume_data = lt.bencode(resume_data) @@ -736,7 +736,7 @@ class Torrent: fastresume.close() except IOError: log.warning("Error trying to save fastresume file") - + self.waiting_on_resume_data = False def delete_fastresume(self): @@ -765,7 +765,7 @@ class Torrent: open(path, "wb").write(lt.bencode(torrent_file)) except Exception, e: log.warning("Unable to save torrent file: %s", e) - + def delete_torrentfile(self): """Deletes the .torrent file in the state""" path = "%s/%s.torrent" % ( @@ -807,7 +807,7 @@ class Torrent: return True def rename_files(self, filenames): - """Renames files in the torrent. 'filenames' should be a list of + """Renames files in the torrent. 'filenames' should be a list of (index, filename) pairs.""" for index, filename in filenames: self.handle.rename_file(index, filename) diff --git a/deluge/tests/test_tracker_icons.py b/deluge/tests/test_tracker_icons.py new file mode 100644 index 000000000..a1a477886 --- /dev/null +++ b/deluge/tests/test_tracker_icons.py @@ -0,0 +1,15 @@ +from deluge.tracker_icons import TrackerIcons + +def test(): + trackericons = TrackerIcons() + + print trackericons._fetch_icon("unknown?") #exception, Returns None + print trackericons._fetch_icon("deluge-torrent.org") #404 , returns False + print trackericons._fetch_icon("legaltorrents.com") + print trackericons.get("thepiratebay.com") + print trackericons.get("unknown2") #exception, returns None + print trackericons.get("legaltorrents.com") #logs cached + print trackericons.get("google.com") + + +test() diff --git a/deluge/tracker_icons.py b/deluge/tracker_icons.py new file mode 100644 index 000000000..028e21d07 --- /dev/null +++ b/deluge/tracker_icons.py @@ -0,0 +1,105 @@ +# +# tracker_icons.py +# +# Copyright (C) 2008 Martijn Voncken +# +# 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., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from urllib import urlopen +from deluge.log import LOG as log +from deluge.common import get_default_config_dir, get_pixmap +import os + +#some servers don't have their favicon at the expected location +RENAMES = {"legaltorrents.com":"beta.legaltorrents.com"} +VALID_TYPES = ["octet-stream","x-icon"] + +class TrackerIcons(object): + def __init__(self): + #set image cache dir + self.image_dir = get_default_config_dir("trackers") + if not os.path.exists(self.image_dir): + os.mkdir(self.image_dir) + + #self.images : {tracker_host:filename} + self.images = {"DHT":get_pixmap("dht16.png" )} + + #load image-names in cache-dir + for icon in os.listdir(self.image_dir): + if icon.endswith(".ico"): + self.add_icon(icon[:-4], os.path.join(self.image_dir, icon)) + + + def _fetch_icon(self, tracker_host): + """ + gets new icon from the internet. + used by get(). + calls add_icon() + returns True or False + """ + try: + host_name = RENAMES.get(tracker_host, tracker_host) + icon = urlopen("http://%s/favicon.ico" % host_name) + icon_data = icon.read() + + #validate icon: + if icon.info().getsubtype() not in VALID_TYPES: + raise Exception("Unexpected type: %s" % icon.info().getsubtype()) + if not icon_data: + raise Exception("No data") + except Exception, e: + log.debug("%s %s %s" % (tracker_host, e, e.message)) + self.add_icon(tracker_host, None) + return False + + filename = os.path.join(get_default_config_dir("trackers"),"%s.ico" % tracker_host) + + f = open(filename,"wb") + f.write(icon_data) + f.close() + self.add_icon(tracker_host, filename) + return True + + def add_icon(self, tracker_host, filename): + self.images[tracker_host] = filename + + def get(self, tracker_host): + """ + use this method to get the filename of an icon. + """ + if not tracker_host in self.images: + self._fetch_icon(tracker_host) + else: + log.debug("cached tracker icon:%s" % tracker_host) + return self.images[tracker_host] + + + + + diff --git a/deluge/ui/gtkui/filtertreeview.py b/deluge/ui/gtkui/filtertreeview.py index 7aaf4ec41..7a1649516 100644 --- a/deluge/ui/gtkui/filtertreeview.py +++ b/deluge/ui/gtkui/filtertreeview.py @@ -38,6 +38,7 @@ import pkg_resources import deluge.component as component import deluge.common +from deluge.tracker_icons import TrackerIcons from deluge.log import LOG as log from deluge.ui.client import aclient from deluge.configmanager import ConfigManager @@ -78,7 +79,7 @@ class FilterTreeView(component.Component): self.scrolled = glade.get_widget("scrolledwindow_sidebar") self.sidebar = component.get("SideBar") self.config = ConfigManager("gtkui.conf") - + self.tracker_icons = TrackerIcons() self.filters = {} self.label_view = gtk.TreeView() @@ -201,12 +202,13 @@ class FilterTreeView(component.Component): value = model.get_value(row, 1) label = model.get_value(row, 2) count = model.get_value(row, 3) + pix = model.get_value(row, 4) if (label == "") and cat == "label": label = _("no label") - if cat == "state": + if pix: self.renderpix.set_property("visible", True) else: self.renderpix.set_property("visible", False) @@ -227,6 +229,15 @@ class FilterTreeView(component.Component): if cat == "state": pix = STATE_PIX.get(value, "dht") return gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("%s16.png" % pix)) + + elif cat == "tracker_host": + ico = self.tracker_icons.get(value) + if ico: + try: #assume we could get trashed images here.. + return gtk.gdk.pixbuf_new_from_file(ico) + except: + log.debug(e.message) + return None def on_selection_changed(self, selection):