From 88929d4821cd1cf0de688cc4eb38a62f3749afa6 Mon Sep 17 00:00:00 2001 From: John Garland Date: Sun, 21 Feb 2010 17:20:34 +1100 Subject: [PATCH] Rewrite tracker_icons.py (#995) --- ChangeLog | 1 + deluge/ui/tracker_icons.py | 606 +++++++++++++++++++++++++++--------- tests/deluge.png | Bin 0 -> 722 bytes tests/google.ico | Bin 0 -> 1150 bytes tests/test_tracker_icons.py | 38 +++ 5 files changed, 497 insertions(+), 148 deletions(-) create mode 100644 tests/deluge.png create mode 100644 tests/google.ico create mode 100644 tests/test_tracker_icons.py diff --git a/ChangeLog b/ChangeLog index 3a9a743af..4b546ecf8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,7 @@ * #496: Remove deprecated functions in favour of get_session_status() * #1112: Fix renaming files in add torrent dialog * #1247: Fix deluge-gtk from hanging on shutdown + * #995: Rewrote tracker_icons ==== Blocklist ==== * Implement local blocklist support diff --git a/deluge/ui/tracker_icons.py b/deluge/ui/tracker_icons.py index 8d38c9c2c..168ebaa10 100644 --- a/deluge/ui/tracker_icons.py +++ b/deluge/ui/tracker_icons.py @@ -1,7 +1,7 @@ # # tracker_icons.py # -# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2010 John Garland # # Deluge is free software. # @@ -33,174 +33,484 @@ # # - - -import threading -from twisted.internet import reactor - -from urllib import urlopen -from deluge.log import LOG as log -from deluge.common import get_pixmap import os -import deluge.configmanager -import deluge.component as component +from HTMLParser import HTMLParser +from urlparse import urljoin, urlparse +from tempfile import mkstemp -#some servers don't have their favicon at the expected location -RENAMES = { - "legaltorrents.com":"beta.legaltorrents.com", - "aelitis.com":"www.vuze.com" - } +from twisted.internet import defer, threads +from twisted.web import error -VALID_ICO_TYPES = ["octet-stream", "x-icon", "image/vnd.microsoft.icon", "vnd.microsoft.icon", "plain"] -VALID_PNG_TYPES = ["octet-stream", "png"] +from deluge.component import Component +from deluge.configmanager import get_config_dir +from deluge.httpdownloader import download_file +from deluge.decorators import proxy +from deluge.log import LOG as log -def fetch_url(url, valid_subtypes=None): +try: + import PIL.Image as Image + import deluge.ui.Win32IconImagePlugin +except ImportError: + PIL_INSTALLED = False +else: + PIL_INSTALLED = True + +class TrackerIcon(object): """ - returns: data or None + Represents a tracker's icon """ - try: - url_file = urlopen(url) - data = url_file.read() - - #validate: - if valid_subtypes and (url_file.info().getsubtype() not in valid_subtypes): - raise Exception("Unexpected type for %s : %s" % (url, url_file.info().getsubtype())) - if not data: - raise Exception("No data") - except Exception, e: - log.debug("%s %s" % (url, e)) - return None - - return data - -class TrackerIcons(component.Component): - def __init__(self): - component.Component.__init__(self, "TrackerIcons") - #set image cache dir - self.image_dir = os.path.join(deluge.configmanager.get_config_dir(), "icons") - if not os.path.exists(self.image_dir): - os.makedirs(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.images[icon[:-4]] = os.path.join(self.image_dir, icon) - if icon.endswith(".png"): - self.images[icon[:-4]] = os.path.join(self.image_dir, icon) - - def _fetch_icon(self, tracker_host): + def __init__(self, filename): """ - returns (ext, data) + Initialises a new TrackerIcon object + + :param filename: the filename of the icon + :type filename: string """ - host_name = RENAMES.get(tracker_host, tracker_host) #HACK! - - ico = fetch_url("http://%s/favicon.ico" % host_name, VALID_ICO_TYPES) - if ico: - return ("ico", ico) - - png = fetch_url("http://%s/favicon.png" % host_name, VALID_PNG_TYPES) - if png: - return ("png", png) - - # FIXME: This should be cleaned up and not copy the top code - - try: - html = urlopen("http://%s/" % (host_name,)) - except Exception, e: - log.debug(e) - html = None - - if html: - icon_path = "" - line = html.readline() - while line: - if ' (16, 16): + new_filename = filename.rpartition('.')[0]+".png" + img = img.resize((16, 16), Image.ANTIALIAS) + img.save(new_filename) + if new_filename != filename: + os.remove(filename) + icon = TrackerIcon(new_filename) + return icon + + def store_icon(self, icon, host): + """ + Stores the icon for the given host + Callbacks any pending deferreds waiting on this icon + + :param icon: the icon to store + :type icon: TrackerIcon or None + :param host: the host to store it for + :type host: string + :returns: the stored icon + :rtype: TrackerIcon or None + """ + self.icons[host] = icon + for d in self.pending[host]: + d.callback(icon) + del self.pending[host] + return icon + +################################ HELPER CLASSES ############################### + +class FaviconParser(HTMLParser): + """ + A HTMLParser which extracts favicons from a HTML page + """ + def __init__(self): + self.icons = [] + HTMLParser.__init__(self) + + def handle_starttag(self, tag, attrs): + if tag == "link" and ("rel", "icon") in attrs or ("rel", "shortcut icon") in attrs: + href = None + type = None + for attr, value in attrs: + if attr == "href": + href = value + elif attr == "type": + type = value + if href and type: + self.icons.append((href, type)) + + def get_icons(self): + """ + Returns a list of favicons extracted from the HTML page + + :returns: a list of favicons + :rtype: list + """ + return self.icons + + +############################### HELPER FUNCTIONS ############################## + +def host_to_url(host): + """ + Given a host, returns the URL to fetch + + :param host: the tracker host + :type host: string + :returns: the url of the tracker + :rtype: string + """ + return "http://%s/" % host + +def url_to_host(url): + """ + Given a URL, returns the host it belongs to + + :param url: the URL in question + :type url: string + :returns: the host of the given URL + :rtype:string + """ + return urlparse(url).hostname + +def host_to_icon_name(host, mimetype): + """ + Given a host, returns the appropriate icon name + + :param host: the host in question + :type host: string + :param mimetype: the mimetype of the icon + :type mimetype: string + :returns: the icon's filename + :rtype: string + """ + return host+'.'+mimetype_to_ext(mimetype) + +def icon_name_to_host(icon): + """ + Given a host's icon name, returns the host name + + :param icon: the icon name + :type icon: string + :returns: the host name + :rtype: string + """ + return icon.rpartition('.')[0] + +def mimetype_to_ext(mimetype): + """ + Given a mimetype, returns the appropriate filename extension + + :param mimetype: the mimetype + :type mimetype: string + :returns: the filename extension for the given mimetype + :rtype: string + :raises KeyError: if given an invalid mimetype + """ + return { + "image/gif" : "gif", + "image/jpeg" : "jpg", + "image/png" : "png", + "image/vnd.microsoft.icon" : "ico", + "image/x-icon" : "ico" + }[mimetype] + +def ext_to_mimetype(ext): + """ + Given a filename extension, returns the appropriate mimetype + + :param ext: the filename extension + :type ext: string + :returns: the mimetype for the given filename extension + :rtype: string + :raises KeyError: if given an invalid filename extension + """ + return { + "gif" : "image/gif", + "jpg" : "image/jpeg", + "jpeg" : "image/jpeg", + "png" : "image/png", + "ico" : "image/vnd.microsoft.icon" + }[ext.lower()] diff --git a/tests/deluge.png b/tests/deluge.png new file mode 100644 index 0000000000000000000000000000000000000000..e39cd0c7eebdf68688339b82aebdeecfe9f5fc9c GIT binary patch literal 722 zcmV;@0xkWCP)}ncbZHx z(_~CE3L3E%3zlkKR5$9vQm6~b&euv%i*DR>*M*26Sg=KRA|i?uH(iJeMMX+|K&ez( z=)<(u+D_86X_A>tW+oXo7HpIBEbsZw$2sTT3v3hJJ^k(1dj^he$GB=GF5J2&v>n_( z&AHuVY+}Cg(cNon)i|b>pus0S(QPKIl}RYFJ33U0t19T~ei-Nd%DE+tK-F0=_5R~G z`d;6z#*(cEC$98`WjXZr+jxRk)m6CQz$A5$B(Xixa(4K~hZl+3g5F*xR*=W%rV}AQ zbwd;gY|{XYEOG`IWry5B_nv;5`0`5IC}IDvKbnvR%2?lXxG{o|>;o520G7fs@>pG5 zfSM%1=&sOn0H><%z|Xrb^DIHQK}6G`ChUwxp@aem1Qi6LF_iOZu!K2z=H|2W6@qZ{J374e9nid1{IxsQ-09)nWI@ef zYEH#+at+H%Nvx-nU`s{JOniScGW4JyKnke*OhGZ!*wWf@=??D^nsb`LQI8uQo^uLT zDfe@7?B(pI$9({109e%o8^Hna0igf7GyvoQ@yX|&cgxJ_d0|o(W0wkiuMEv;W=W3v( z)q+3~th6X-V-^ZT+iD9Nw5Wh0X<8r_wUvGWQrV^LZkvVebm#HR_?tMBvv>E*ec$uG zXU<)v47oiXC7+q<#T2DxDy6a{Bqf>u^0M6Wii}$XPK8<0I>KX3QJ$1_XGfg4m7sUv z9K~w*c>_2 zz(cQ__{t3m%S;O1RTKz*@mq@CNQ_zaf8Yt-AT1Ony(LC^^Qg=5;GwIGC4|9>Q&KU9 z+)Alip~;tZ;RcOgbc|a!$qBZjz8yiIvPnH*F|93*I(Ute#vW|jhDn>{JMLvcmBP1G z;TQbkjZ;L20ZBOf&luju4pJNYP%W3xUkx(9;R1g~q7Ib)q2DR2yq(-?@&2KSze-b3 zAs#j=&K?Vr91goayGAEyyf{QnXD`>rZb%NS$q8N#7BhdVfp@3W7mnO5;^70~5{_-f zck|h5FU{|*;D_p081C+rjwR)`3CF^`bfAcA!RFTp*1=2ow@Yl6*rcd0oyV8UXS28D zHg-RrL3F6k^?W%pz*BW|$=)UPKGFCjyz<@mk@Ri5fnPLur5^`Y%;3`{9)hnfqOE?D z>(>(9zjhzzt~WFf?@{E`O4NzoU7|z6>(t+3Q1HINzSZ-%&~_MG`k07~(0lxIt`By* z9%_$P@IaZ=tTV~mZIUA%awYP1$Q+1|MS{OR&?D7s;?W^m)@O0-X+>z&bS`&>-29%V zS~6doL(a!ivqok}aF0|?Bm0Fs((m8wygoP1HnErit}HWJGR`}D=O;8PzjVdiD4v!V^lc8!qMjp zh-|VsTE3LfN=5rx&3DgDC-(PW?z`yvElz6MRoR=wG;F~+_2Z%^A|CqPGl-nQ!Jc-4 vYcl!bi4@Kqt`tj2MxqJw3Qx%XF0`I-Ikc-^&W9Ryu^WtA{w@6P0|)yrC_;$3 literal 0 HcmV?d00001 diff --git a/tests/test_tracker_icons.py b/tests/test_tracker_icons.py new file mode 100644 index 000000000..9a68dc935 --- /dev/null +++ b/tests/test_tracker_icons.py @@ -0,0 +1,38 @@ +from twisted.trial import unittest + +from deluge.ui.tracker_icons import TrackerIcons, TrackerIcon +from deluge.log import setupLogger + +# Must come before import common +setupLogger("debug", "debug.log") + +import common + +common.set_tmp_config_dir() +icons = TrackerIcons() + +class TrackerIconsTestCase(unittest.TestCase): + def test_get_png(self): + # Deluge has a png favicon link + icon = TrackerIcon("../deluge.png") + d = icons.get("deluge-torrent.org") + d.addCallback(self.assertNotIdentical, None) + d.addCallback(self.assertEquals, icon) + return d + + def test_get_ico(self): + # Google doesn't have any icon links + # So instead we'll grab its favicon.ico + icon = TrackerIcon("../google.ico") + d = icons.get("www.google.com") + d.addCallback(self.assertNotIdentical, None) + d.addCallback(self.assertEquals, icon) + return d + + def test_get_ico_with_redirect(self): + # google.com redirects to www.google.com + icon = TrackerIcon("../google.ico") + d = icons.get("google.com") + d.addCallback(self.assertNotIdentical, None) + d.addCallback(self.assertEquals, icon) + return d