1 import urllib, threading, os, gzip, time, simplejson, re
2 _DUMP_URL = '''http://img.jamendo.com/data/dbdump_artistalbumtrack.xml.gz'''
3 _DUMP = os.path.expanduser('''~/.cache/jamaendo/dbdump.xml.gz''')
4 _DUMP_TMP = os.path.expanduser('''~/.cache/jamaendo/new_dbdump.xml.gz''')
7 os.makedirs(os.path.dirname(_DUMP))
12 return os.path.isfile(_DUMP)
14 def _file_is_old(fil, old_age):
15 return os.path.getmtime(fil) < (time.time() - old_age)
18 return not has_dump() or _file_is_old(_DUMP, 60*60*24) # 1 day
20 def refresh_dump(complete_callback, progress_callback=None, force=False):
21 if force or _dump_is_old():
22 downloader = Downloader(complete_callback, progress_callback)
25 complete_callback(True)
27 class Downloader(threading.Thread):
28 def __init__(self, complete_callback, progress_callback):
29 threading.Thread.__init__(self)
30 self.complete_callback = complete_callback
31 self.progress_callback = progress_callback
33 def actual_callback(self, numblocks, blocksize, filesize):
34 if self.progress_callback:
36 percent = min((numblocks*blocksize*100)/filesize, 100)
39 self.progress_callback(percent)
44 urllib.urlretrieve(_DUMP_URL, _DUMP_TMP, self.actual_callback)
45 if os.path.isfile(_DUMP):
47 os.rename(_DUMP_TMP, _DUMP)
50 self.complete_callback(success)
52 def fast_iter(context, func):
53 for event, elem in context:
56 while elem.getprevious() is not None:
57 del elem.getparent()[0]
60 from lxml import etree
65 if isinstance(v, basestring):
66 return v.encode('utf-8')
69 return "{%s}" % (", ".join("%s=%s"%(k.encode('utf-8'), printable(v)) \
70 for k,v in self.__dict__.iteritems() if not k.startswith('_')))
72 class LocalDB(object):
77 self.fil = gzip.open(_DUMP)
82 def make_album_brief(self, element):
86 ret['id'] = int(info.text)
87 elif info.tag == 'name':
88 ret['name'] = info.text
91 def make_artist_obj(self, element):
95 ret['id'] = int(child.text)
96 elif child.tag in ('name', 'image'):
97 ret[child.tag] = child.text
98 elif child.tag == 'Albums':
99 ret['albums'] = [self.make_album_brief(a) for a in child]
102 def make_track_obj(self, element):
108 ret['mp3'] = Query.track_mp3(_id)
109 ret['ogg'] = Query.track_ogg(_id)
110 elif info.tag in ('name', 'numalbum'):
111 ret[info.tag] = info.text
114 def make_album_obj(self, element):
116 artist = element.getparent().getparent()
117 if artist is not None:
119 if child.tag == 'name':
120 ret['artist'] = child.text
121 elif child.tag == 'id':
122 ret['artist_id'] = int(child.text)
123 for child in element:
124 if child.tag == 'id':
125 ret['id'] = int(child.text)
126 elif child.tag in ('name', 'image'):
128 ret[child.tag] = child.text
131 elif child.tag == 'Tracks':
132 ret['tracks'] = [self.make_track_obj(t) for t in child]
135 def artist_walker(self, name_match):
136 for event, element in etree.iterparse(self.fil, tag="artist"):
137 name = element.xpath('./name')[0].text.lower()
138 if name and name.find(name_match) > -1:
139 yield self.make_artist_obj(element)
141 while element.getprevious() is not None:
142 del element.getparent()[0]
145 def album_walker(self, name_match):
146 for event, element in etree.iterparse(self.fil, tag="album"):
147 name = element.xpath('./name')[0].text
148 if name and name.lower().find(name_match) > -1:
149 yield self.make_album_obj(element)
151 while element.getprevious() is not None:
152 del element.getparent()[0]
155 def artistid_walker(self, artistids):
156 for event, element in etree.iterparse(self.fil, tag="artist"):
157 _id = element.xpath('./id')[0].text
158 if _id and int(_id) in artistids:
159 yield self.make_artist_obj(element)
161 while element.getprevious() is not None:
162 del element.getparent()[0]
165 def albumid_walker(self, albumids):
166 for event, element in etree.iterparse(self.fil, tag="album"):
167 _id = element.xpath('./id')[0].text
168 if _id and (int(_id) in albumids):
169 yield self.make_album_obj(element)
171 while element.getprevious() is not None:
172 del element.getparent()[0]
175 def search_artists(self, substr):
176 substr = substr.lower()
177 return (artist for artist in self.artist_walker(substr))
179 def search_albums(self, substr):
180 substr = substr.lower()
181 return (album for album in self.album_walker(substr))
183 def get_artists(self, artistids):
184 return (artist for artist in self.artistid_walker(artistids))
186 def get_albums(self, albumids):
187 return (album for album in self.albumid_walker(albumids))
189 _GET2 = '''http://api.jamendo.com/get2/'''
192 last_query = time.time()
194 cache_time = 60*60*24
195 rate_limit = 1.0 # max queries per second
197 def __init__(self, order,
198 select=['id', 'name', 'image', 'artist_name'],
200 track=['track_album', 'album_artist'],
202 if request == 'track':
203 self.url = "%s%s/%s/json/%s?n=%s&order=%s" % (_GET2, '+'.join(select), request, '+'.join(track), count, order)
205 self.url = "%s%s/%s/json/?n=%s&order=%s" % (_GET2, '+'.join(select), request, count, order)
208 def _ratelimit(self):
210 if now - self.last_query < self.rate_limit:
211 time.sleep(self.rate_limit - (now - self.last_query))
212 self.last_query = now
215 """ratelimited query"""
217 f = urllib.urlopen(self.url)
218 ret = simplejson.load(f)
223 def album_cover(albumid, size=200):
224 to = '~/.cache/jamaendo/cover-%d-%d.jpg'%(albumid, size)
225 if not os.path.isfile(to):
226 url = _GET2+'image/album/redirect/?id=%d&imagesize=%d'%(albumid, size)
227 urllib.urlretrieve(url, to)
231 def track_ogg(trackid):
232 return _GET2+ 'stream/track/redirect/?id=%d&streamencoding=ogg2'%(trackid)
235 def track_mp3(trackid):
236 return _GET2+ 'stream/track/redirect/?id=%d&streamencoding=mp31'%(trackid)
238 class Queries(object):
239 albums_this_week = Query(order='ratingweek_desc')
240 albums_all_time = Query(order='ratingtotal_desc')
241 albums_this_month = Query(order='ratingmonth_desc')
242 albums_today = Query(order='ratingday_desc')
243 playlists_all_time = Query(select=['id','name', 'user_idstr'], request='playlist', order='ratingtotal_desc')
244 tracks_this_month = Query(select=['id', 'name',
246 'album_name', 'artist_name',
247 'album_id', 'artist_id'],
249 order='ratingmonth_desc')