# Copyright (C) 2006 Adam Olsen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 1, or (at your option) # any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import httplib, re, urllib, md5, threading, sys, os, xlmisc import urllib2 import config import gobject __revision__ = ".01" #LOCALES = ['ca', 'de', 'fr', 'jp', 'uk', 'us'] def get_server(locale): if locale in ('en', 'us'): return "xml.amazon.com" elif locale in ('jp', 'uk'): return "webservices.amazon.co.%s" % locale else: return "webservices.amazon.%s" % locale def get_encoding(locale): if locale == 'jp': return 'utf-8' else: return 'iso-8859-1' KEY = "15VDQG80MCS2K1W2VRR2" # Adam Olsen's key (synic) QUERY = "/onca/xml3?t=webservices-20&dev-t=%s&mode=music&type=lite&" % (KEY) + \ "locale={locale}&page=1&f=xml&KeywordSearch=" IMAGE_PATTERN = re.compile( r"http://(\w+\.images-amazon\.com)" "(/images/.*?\.jpg)", re.DOTALL) """ Fetches album covers from Amazon.com """ class Cover(dict): """ Represents a single album cover """ def save(self, savepath='.'): """ Saves the image to a file """ if not os.path.isdir(savepath): os.mkdir(savepath) savepath = "%s%s%s.jpg" % (savepath, os.sep, self['md5']) handle = open(savepath, "w") handle.write(self['data']) handle.close() self['filename'] = savepath def filename(self): return "%s.jpg" % self['md5'] class CoverFetcherThread(threading.Thread): """ Fetches all covers for a search string """ def __init__(self, search_string, _done_func, fetch_all=False, locale='us'): """ Constructor expects a search string and a function to call when it's _done """ xlmisc.log("new thread created with %s" % search_string) threading.Thread.__init__(self) self.setDaemon(True) self._done = False self._done_func = _done_func self.search_string = search_string self.locale = locale self.fetch_all = fetch_all def abort(self): """ Aborts the download thread. Note that this does not happen immediately, but happens when the thread is done blocking on its current operation """ self._done = True def run(self): """ Actually connects and fetches the covers """ xlmisc.log("cover thread started") conn = httplib.HTTPConnection(get_server(self.locale)) if self._done: return try: query = QUERY.replace("{locale}", self.locale) # FIXME: always UTF-8? search_string = self.search_string.decode('utf-8') search_string = search_string.encode( get_encoding(self.locale), 'replace') string = query + urllib.quote(search_string, '') except KeyError: string = "" try: conn.request("GET", string) except urllib2.URLError: pass except: xlmisc.log_exception() pass if self._done: return response = conn.getresponse() if response.status != 200: print dir(response) print response.reason print get_server(self.locale), string xlmisc.log("Invalid response received: %s" % response.status) gobject.idle_add(self._done_func, []) return page = response.read() covers = [] for m in IMAGE_PATTERN.finditer(page): if self._done: return cover = Cover() conn = httplib.HTTPConnection(m.group(1)) try: conn.request("GET", m.group(2)) except urllib2.URLError: continue response = conn.getresponse() cover['status'] = response.status if response.status == 200: data = response.read() if self._done: return conn.close() cover['data'] = data cover['md5'] = md5.new(data).hexdigest() # find out if the cover is valid if len(data) > 1200: covers.append(cover) if not self.fetch_all: break conn.close() if len(covers) == 0: xlmisc.log("Thread done.... *shrug*, no covers found") if self._done: return # call after the current pending event in the gtk gui gobject.idle_add(self._done_func, covers) # test case functions def done(covers): for cover in covers: if(cover['status'] == 200): cover.save() if __name__ == "__main__": if(len(sys.argv) != 2): print "Usage: %s " % sys.argv[0] sys.exit(1) CoverFetcherThread(sys.argv[1], done).start()