From: Kristoffer Grönlund Date: Sun, 3 Jan 2010 20:12:55 +0000 (+0100) Subject: Major list upgrade - now images in most places, and when selecting a featured track... X-Git-Url: http://git.maemo.org/git/?p=jamaendo;a=commitdiff_plain;h=3f9780b2b25864881151f5d99b234587a65cf451 Major list upgrade - now images in most places, and when selecting a featured track, it queues the whole list instead of the single selected track --- diff --git a/debian/control b/debian/control index 6c717f1..be163ea 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: http://jamaendo.garage.maemo.org/ Package: jamaendo Architecture: all -Depends: ${python:Depends}, python2.5-gtk2, python2.5-gstreamer, python2.5-dbus, python2.5-hildon, python-lxml, python2.5-osso +Depends: ${python:Depends}, python2.5-gtk2, python2.5-gstreamer, python2.5-dbus, python2.5-hildon, python-lxml, python2.5-osso, python-pycurl Description: XB-Maemo-Display-Name: Jamaendo Player XB-Maemo-Icon-26: diff --git a/jamaendo/api.py b/jamaendo/api.py index f7cbc0e..3de54e9 100644 --- a/jamaendo/api.py +++ b/jamaendo/api.py @@ -24,6 +24,7 @@ # Image / cover downloads.. and more? import urllib, threading, os, time, simplejson, re import logging, hashlib +import pycurl, StringIO _CACHEDIR = None _COVERDIR = None @@ -52,6 +53,19 @@ _TRACK_FIELDS = ['id', 'name', 'album_image', 'artist_id', 'artist_name', 'album _RADIO_FIELDS = ['id', 'name', 'idstr', 'image'] _TAG_FIELDS = ['id', 'name'] +def curlGET(url): + c = pycurl.Curl() + s = StringIO.StringIO() + c.setopt(pycurl.FOLLOWLOCATION, 1) + c.setopt(pycurl.URL, url) + c.setopt(pycurl.WRITEFUNCTION, s.write) + try: + c.perform() + finally: + c.close() + s.seek(0) + return s.read() + class LazyQuery(object): def set_from_json(self, json): for key, value in json.iteritems(): @@ -225,9 +239,7 @@ class Query(object): log.info("%s", url) Query._ratelimit() try: - f = urllib.urlopen(url) - ret = simplejson.load(f) - f.close() + ret = simplejson.loads(curlGET(url)) except Exception, e: return None return ret @@ -245,13 +257,29 @@ class CoverFetcher(threading.Thread): self.cond = threading.Condition() self.work = [] + def _retrieve(self, url, fname): + f = open(fname, 'wb') + c = pycurl.Curl() + c.setopt(pycurl.FOLLOWLOCATION, 1) + c.setopt(pycurl.URL, str(url)) + c.setopt(pycurl.WRITEFUNCTION, f.write) + try: + c.perform() + except: + fname = None + finally: + c.close() + f.close() + log.debug("Coverfetch: %s -> %s", url, fname) + return fname + def _fetch_cover(self, albumid, size): try: coverdir = _COVERDIR if _COVERDIR else '/tmp' to = os.path.join(coverdir, '%d-%d.jpg'%(albumid, size)) if not os.path.isfile(to): url = _GET2+'image/album/redirect/?id=%d&imagesize=%d'%(albumid, size) - urllib.urlretrieve(url, to) + to = self._retrieve(url, to) return to except Exception, e: return None @@ -262,7 +290,7 @@ class CoverFetcher(threading.Thread): coverdir = _COVERDIR if _COVERDIR else '/tmp' to = os.path.join(coverdir, h+'.jpg') if not os.path.isfile(to): - urllib.urlretrieve(url, to) + to = self._retrieve(url, to) return to except Exception, e: return None @@ -276,7 +304,7 @@ class CoverFetcher(threading.Thread): def request_images(self, urls, cb): """cb([(url, image)])""" self.cond.acquire() - self.work.insert(0, ('images', urls, cb)) + self.work = [('image', url, cb) for url in urls] + self.work self.cond.notify() self.cond.release() @@ -294,22 +322,20 @@ class CoverFetcher(threading.Thread): multi = len(work) > 1 for job in work: - if job[0] == 'images': - self.process_images(job[1], job[2]) + if job[0] == 'image': + self.process_image(job[1], job[2]) else: self.process_cover(*job) - if multi: - time.sleep(1.0) def process_cover(self, albumid, size, cb): cover = self._fetch_cover(albumid, size) if cover: cb(albumid, size, cover) - def process_images(self, urls, cb): - results = [(url, image) for url, image in ((url, self._fetch_image(url)) for url in urls) if image is not None] - if results: - cb(results) + def process_image(self, url, cb): + image = self._fetch_image(url) + if image: + cb([(url, image)]) class CoverCache(object): """ diff --git a/jamaui/albumlist.py b/jamaui/albumlist.py index 5c82637..5653310 100644 --- a/jamaui/albumlist.py +++ b/jamaui/albumlist.py @@ -1,52 +1,166 @@ +import os import gtk +import gobject import hildon import jamaendo +import util from settings import settings from postoffice import postoffice import logging log = logging.getLogger(__name__) -class ImageDownloader(object): +class _BaseList(gtk.TreeView): """ - TODO: background downloader of images - for album lists, track lists, etc + TODO: unify the different lists into one """ + ICON_SIZE = 50 -class AlbumList(gtk.TreeView): def __init__(self): gtk.TreeView.__init__(self) - self.__store = gtk.ListStore(str, int) + self.__store = None + self.default_pixbuf = util.find_resource('album.png') + self.connect('destroy', self.on_destroy) + + def get_pixbuf(self, img): + try: + return gtk.gdk.pixbuf_new_from_file_at_size(img, + self.ICON_SIZE, + self.ICON_SIZE) + except gobject.GError: + log.error("Broken image in cache: %s", img) + try: + os.unlink(img) + except OSError, e: + log.warning("Failed to unlink broken image.") + if img != self.default_pixbuf: + return self.get_default_pixbuf() + else: + return None + + def get_default_pixbuf(self): + if self.default_pixbuf: + return self.get_pixbuf(self.default_pixbuf) + + def on_destroy(self, wnd): + pass + +class MusicList(_BaseList): + def __init__(self): + _BaseList.__init__(self) + (self.COL_ICON, self.COL_NAME, self.COL_ID, self.COL_IMAGE) = range(4) + self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int, str) + self.set_model(self.__store) + icon = gtk.TreeViewColumn('Icon') + self.append_column(icon) + cell = gtk.CellRendererPixbuf() + icon.pack_start(cell, True) + icon.add_attribute(cell, 'pixbuf', self.COL_ICON) + col = gtk.TreeViewColumn('Name') self.append_column(col) cell = gtk.CellRendererText() col.pack_start(cell, True) - col.add_attribute(cell, 'text', 0) - self.set_search_column(0) - col.set_sort_column_id(0) - + col.add_attribute(cell, 'text', self.COL_NAME) + self.set_search_column(self.COL_NAME) + col.set_sort_column_id(self.COL_NAME) + + postoffice.connect('images', self, self.on_images) + + def get_item_id(self, path): + return self.__store.get(self.__store.get_iter(path), self.COL_ID)[0] + + def on_destroy(self, wnd): + postoffice.disconnect('images', self) + + def on_images(self, images): + for url, image in images: + for row in self.__store: + if row[self.COL_IMAGE] == url: + pb = self.get_pixbuf(image) + if pb: + row[self.COL_ICON] = pb + + def add_items(self, items): + images = [x for x in (self.get_item_image(item) for item in items) if x] + for item in items: + txt = self.get_item_text(item) + self.__store.append([self.get_default_pixbuf(), txt, item.ID, self.get_item_image(item)]) + if images: + postoffice.notify('request-images', images) + + def get_item_text(self, item): + if isinstance(item, jamaendo.Album): + return "%s - %s" % (item.artist_name, item.name) + elif isinstance(item, jamaendo.Track): + return "%s - %s" % (item.artist_name, item.name) + else: + return item.name + + def get_item_image(self, item): + ret = None + if isinstance(item, jamaendo.Track): + ret = item.album_image + elif hasattr(item, 'image'): + ret = item.image + if ret: + ret = ret.replace('1.100.jpg', '1.%d.jpg'%(self.ICON_SIZE)) + return ret + +class AlbumList(_BaseList): + def __init__(self): + _BaseList.__init__(self) + (self.COL_ICON, self.COL_NAME, self.COL_ID) = range(3) + self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int) self.__show_artist = True + self.set_model(self.__store) + + icon = gtk.TreeViewColumn('Icon') + self.append_column(icon) + cell = gtk.CellRendererPixbuf() + icon.pack_start(cell, True) + icon.add_attribute(cell, 'pixbuf', self.COL_ICON) + + col = gtk.TreeViewColumn('Name') + self.append_column(col) + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.add_attribute(cell, 'text', self.COL_NAME) + self.set_search_column(self.COL_NAME) + col.set_sort_column_id(self.COL_NAME) + + postoffice.connect('album-cover', self, self.on_album_cover) + + def on_destroy(self, wnd): + _BaseList.on_destroy(self, wnd) + postoffice.disconnect('album-cover', self) + + def on_album_cover(self, albumid, size, cover): + if size == self.ICON_SIZE: + for row in self.__store: + if row[self.COL_ID] == albumid: + row[self.COL_ICON] = self.get_pixbuf(cover) + def add_album(self, album): if self.__show_artist: txt = "%s - %s" % (album.artist_name, album.name) else: txt = album.name - self.__store.append([txt, album.ID]) + self.__store.append([self.get_default_pixbuf(), txt, album.ID]) + postoffice.notify('request-album-cover', album.ID, self.ICON_SIZE) def get_album_id(self, path): - treeiter = self.__store.get_iter(path) - _, _id = self.__store.get(treeiter, 0, 1) - return _id + return self.__store.get(self.__store.get_iter(path), self.COL_ID)[0] def show_artist(self, show): self.__show_artist = show -class TrackList(gtk.TreeView): +class TrackList(_BaseList): def __init__(self, numbers = True): - gtk.TreeView.__init__(self) + _BaseList.__init__(self) self.track_numbers = numbers self.__store = gtk.ListStore(int, str, int) self.set_model(self.__store) @@ -75,29 +189,51 @@ class TrackList(gtk.TreeView): _, _, _id = self.__store.get(treeiter, 0, 1, 2) return _id -class RadioList(gtk.TreeView): +class RadioList(_BaseList): def __init__(self): - gtk.TreeView.__init__(self) - self.__store = gtk.ListStore(str, int) + _BaseList.__init__(self) + (self.COL_ICON, self.COL_NAME, self.COL_ID, self.COL_IMAGE) = range(4) + self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int, str) self.set_model(self.__store) + icon = gtk.TreeViewColumn('Icon') + self.append_column(icon) + cell = gtk.CellRendererPixbuf() + icon.pack_start(cell, True) + icon.add_attribute(cell, 'pixbuf', self.COL_ICON) + col = gtk.TreeViewColumn('Name') self.append_column(col) cell = gtk.CellRendererText() col.pack_start(cell, True) - col.add_attribute(cell, 'text', 0) + col.add_attribute(cell, 'text', self.COL_NAME) + self.set_search_column(self.COL_NAME) + col.set_sort_column_id(self.COL_NAME) - self.set_search_column(0) - col.set_sort_column_id(0) + postoffice.connect('images', self, self.on_images) + + def on_destroy(self, wnd): + postoffice.disconnect('images', self) + + def add_radios(self, radios): + for radio in radios: + self.__store.append([self.get_default_pixbuf(), self.radio_name(radio), radio.ID, radio.image]) + postoffice.notify('request-images', [radio.image for radio in radios]) - def add_radio(self, radio): - self.__store.append([self.radio_name(radio), radio.ID]) def get_radio_id(self, path): treeiter = self.__store.get_iter(path) - name, _id = self.__store.get(treeiter, 0, 1) + name, _id = self.__store.get(treeiter, self.COL_NAME, self.COL_ID) return name, _id + def on_images(self, images): + for url, image in images: + for row in self.__store: + if row[self.COL_IMAGE] == url: + pb = self.get_pixbuf(image) + if pb: + row[self.COL_ICON] = pb + def radio_name(self, radio): if radio.idstr: return radio.idstr.capitalize() diff --git a/jamaui/featured.py b/jamaui/featured.py index eec493b..7f20c39 100644 --- a/jamaui/featured.py +++ b/jamaui/featured.py @@ -27,6 +27,8 @@ import jamaendo from playerwindow import open_playerwindow from showartist import ShowArtist from showalbum import ShowAlbum +from albumlist import MusicList +from player import Playlist def _alist(l, match): for key, value in l: @@ -45,30 +47,21 @@ class FeaturedWindow(hildon.StackableWindow): def __init__(self, feature): hildon.StackableWindow.__init__(self) - self.set_title("Featured") + self.set_title(feature) self.featurefn = _alist(self.features, feature) # Results list self.panarea = hildon.PannableArea() - self.result_store = gtk.ListStore(str, int) - #self.result_store.append(['red']) - self.result_view = gtk.TreeView(self.result_store) - col = gtk.TreeViewColumn('Name') - self.result_view.append_column(col) - cell = gtk.CellRendererText() - col.pack_start(cell, True) - col.add_attribute(cell, 'text', 0) - self.result_view.set_search_column(0) - col.set_sort_column_id(0) - self.result_view.connect('row-activated', self.row_activated) - - self.panarea.add(self.result_view) + self.musiclist = MusicList() + self.musiclist.connect('row-activated', self.row_activated) + self.panarea.add(self.musiclist) self.idmap = {} - for item in self.featurefn(): + self.items = self.featurefn() + for item in self.items: self.idmap[item.ID] = item - self.result_store.append([self.get_item_text(item), item.ID]) + self.musiclist.add_items(self.items) self.add(self.panarea) @@ -86,33 +79,9 @@ class FeaturedWindow(hildon.StackableWindow): self.menu.show_all() self.set_app_menu(self.menu) - def get_item_text(self, item): - if isinstance(item, jamaendo.Album): - return "%s - %s" % (item.artist_name, item.name) - elif isinstance(item, jamaendo.Track): - return "%s - %s" % (item.artist_name, item.name) - else: - return item.name - - def make_button(self, text, subtext, callback): - button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, - hildon.BUTTON_ARRANGEMENT_VERTICAL) - button.set_text(text, subtext) - - if callback: - button.connect('clicked', callback) - - #image = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON) - #button.set_image(image) - #button.set_image_position(gtk.POS_RIGHT) - - return button - def row_activated(self, treeview, path, view_column): - treeiter = self.result_store.get_iter(path) - title, _id = self.result_store.get(treeiter, 0, 1) + _id = self.musiclist.get_item_id(path) item = self.idmap[_id] - #print _id, item self.open_item(item) def open_item(self, item): @@ -123,8 +92,10 @@ class FeaturedWindow(hildon.StackableWindow): wnd = ShowArtist(item) wnd.show_all() elif isinstance(item, jamaendo.Track): + playlist = Playlist(self.items) + playlist.jump_to(item.ID) wnd = open_playerwindow() - wnd.play_tracks([item]) + wnd.play_tracks(playlist) elif isinstance(item, jamaendo.Tag): wnd = open_playerwindow() wnd.play_tracks(jamaendo.get_tag_tracks(item.ID)) diff --git a/jamaui/playerwindow.py b/jamaui/playerwindow.py index ab6e95e..16bfffe 100644 --- a/jamaui/playerwindow.py +++ b/jamaui/playerwindow.py @@ -240,6 +240,7 @@ class PlayerWindow(hildon.StackableWindow): def set_default_cover(self): tmp = util.find_resource('album.png') if tmp: + log.debug("Setting cover to %s", tmp) self.cover.set_from_file(tmp) def update_state(self): @@ -259,6 +260,7 @@ class PlayerWindow(hildon.StackableWindow): if size == 300: playing = self.get_album_id() if playing and albumid and (int(playing) == int(albumid)): + log.debug("Setting cover to %s", cover) self.cover.set_from_file(cover) def play_radio(self, radio_name, radio_id): diff --git a/jamaui/radios.py b/jamaui/radios.py index dc3109c..da10c77 100644 --- a/jamaui/radios.py +++ b/jamaui/radios.py @@ -41,9 +41,10 @@ class RadiosWindow(hildon.StackableWindow): self.radios = {} hildon.hildon_gtk_window_set_progress_indicator(self, 1) - for item in jamaendo.starred_radios(): + radios = jamaendo.starred_radios() + for item in radios: self.radios[item.ID] = item - self.radiolist.add_radio(item) + self.radiolist.add_radios(radios) hildon.hildon_gtk_window_set_progress_indicator(self, 0) self.add(self.panarea) diff --git a/jamaui/search.py b/jamaui/search.py index e487765..4092048 100644 --- a/jamaui/search.py +++ b/jamaui/search.py @@ -27,6 +27,7 @@ import jamaendo from playerwindow import open_playerwindow from showartist import ShowArtist from showalbum import ShowAlbum +from albumlist import MusicList class SearchWindow(hildon.StackableWindow): def __init__(self): @@ -39,6 +40,7 @@ class SearchWindow(hildon.StackableWindow): # Results list self.panarea = hildon.PannableArea() + self.musiclist = MusicList() self.result_store = gtk.ListStore(str, int) #self.result_store.append(['red']) self.result_view = gtk.TreeView(self.result_store) diff --git a/jamaui/songposition.py b/jamaui/songposition.py index 705962c..668deec 100644 --- a/jamaui/songposition.py +++ b/jamaui/songposition.py @@ -6,7 +6,7 @@ log = logging.getLogger(__name__) # shows the current song position (looking a bit nicer than a default widget, hopefully) class SongPosition(gtk.DrawingArea): - WIDTH = 8.0 + WIDTH = 32.0 HEIGHT = 8.0 def __init__(self): @@ -29,9 +29,9 @@ class SongPosition(gtk.DrawingArea): darkclr.add_color_stop_rgba(1.0, 0.25, 0.25, 0.25, 1.0) markerclr = cairo.LinearGradient(0.0, 0.0, 0.0, self.HEIGHT) - markerclr.add_color_stop_rgba(0.0, *orange1) - markerclr.add_color_stop_rgba(0.5, *orange0) - markerclr.add_color_stop_rgba(1.0, *orange0) + markerclr.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 0.0) + markerclr.add_color_stop_rgba(0.5, 1.0, 1.0, 1.0, 0.75) + markerclr.add_color_stop_rgba(1.0, 1.0, 1.0, 1.0, 1.0) self.lightclr = lightclr self.darkclr = darkclr @@ -84,7 +84,10 @@ class SongPosition(gtk.DrawingArea): context.fill() def set_position(self, pos): - assert 0 <= pos <= 1 + if pos < 0.0: + pos = 0.0 + elif pos > 1.0: + pos = 1.0 self.pos = pos self.invalidate() diff --git a/jamaui/ui.py b/jamaui/ui.py index 9b6300d..2c0ad65 100644 --- a/jamaui/ui.py +++ b/jamaui/ui.py @@ -38,7 +38,7 @@ gobject.threads_init() log = logging.getLogger(__name__) -VERSION = '0.1' +VERSION = '0.2' try: import hildon @@ -95,6 +95,7 @@ class Jamaui(object): settings.load() postoffice.connect('request-album-cover', self, self.on_request_cover) + postoffice.connect('request-images', self, self.on_request_images) log.debug("Created main window.") def save_settings(self): @@ -176,9 +177,15 @@ class Jamaui(object): def on_request_cover(self, albumid, size): jamaendo.get_album_cover_async(self.got_album_cover, int(albumid), size) + def on_request_images(self, urls): + jamaendo.get_images_async(self.got_images, urls) + def got_album_cover(self, albumid, size, cover): postoffice.notify('album-cover', albumid, size, cover) + def got_images(self, images): + postoffice.notify('images', images) + #def add_featured_button(self): # self.featured_sel = hildon.TouchSelector(text=True) # self.featured_sel.append_text("Albums of the week") @@ -193,7 +200,7 @@ class Jamaui(object): # self.bbox.add(btn) def destroy(self, widget): - postoffice.disconnect('request-album-cover', self) + postoffice.disconnect(['request-album-cover', 'request-images'], self) self.save_settings() from player import the_player if the_player: @@ -261,11 +268,12 @@ JAMENDO is an online platform that distributes musical works under Creative Comm def on_featured(self, button): dialog = hildon.PickerDialog(self.window) - sel = hildon.TouchSelector(text=True) + sel = hildon.TouchSelectorEntry(text=True) for feature, _ in FeaturedWindow.features: sel.append_text(feature) dialog.set_selector(sel) dialog.set_title("Featured") + sel.unselect_all(0) if dialog.run() == gtk.RESPONSE_OK: txt = sel.get_current_text() self.featuredwnd = FeaturedWindow(txt)