| Viewing file:  JamendoSource.py (13.64 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
# -*- coding: utf-8 -*-
 # JamendoSource.py
 #
 # Copyright (C) 2007 - Guillaume Desmottes
 #
 # 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 2, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 # Parts from "Magnatune Rhythmbox plugin" (stolen from rhythmbox's MagnatuneSource.py)
 #     Copyright (C), 2006 Adam Zimmerman <adam_zimmerman@sfu.ca>
 
 import rb, rhythmdb
 from JamendoSaxHandler import JamendoSaxHandler
 import JamendoConfigureDialog
 
 import gobject
 import gtk.glade
 import gnomevfs, gnome, gconf
 import xml
 import gzip
 import datetime
 
 # URIs
 jamendo_dir = gnome.user_dir_get() + "rhythmbox/jamendo/"
 jamendo_song_info_uri = gnomevfs.URI("http://img.jamendo.com/data/dbdump.en.xml.gz")
 local_song_info_uri = gnomevfs.URI(jamendo_dir + "dbdump.en.xml")
 local_song_info_temp_uri = gnomevfs.URI(jamendo_dir + "dbdump.en.xml.tmp")
 
 stream_url = "http://www.jamendo.com/get/track/id/track/audio/redirect/%s/?aue=ogg2"
 artwork_url = "http://www.jamendo.com/get/album/id/album/artworkurl/redirect/%s/?artwork_size=200"
 artist_url = "http://www.jamendo.com/get/artist/id/album/page/plain/"
 
 class JamendoSource(rb.BrowserSource):
 __gproperties__ = {
 'plugin': (rb.Plugin, 'plugin', 'plugin', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
 }
 
 def __init__(self):
 
 rb.BrowserSource.__init__(self, name=_("Jamendo"))
 
 self.__p2plinks = {}
 self.__loader = rb.Loader()
 
 # catalogue stuff
 self.__db = None
 self.__saxHandler = None
 self.__activated = False
 self.__notify_id = 0
 self.__update_id = 0
 self.__xfer_handle = None
 self.__info_screen = None
 self.__updating = True
 self.__load_handle = None
 self.__load_current_size = 0
 self.__load_total_size = 0
 
 def do_set_property(self, property, value):
 if property.name == 'plugin':
 self.__plugin = value
 else:
 raise AttributeError, 'unknown property %s' % property.name
 
 def do_impl_get_browser_key (self):
 return "/apps/rhythmbox/plugins/jamendo/show_browser"
 
 def do_impl_get_paned_key (self):
 return "/apps/rhythmbox/plugins/jamendo/paned_position"
 
 def do_impl_pack_paned (self, paned):
 self.__paned_box = gtk.VBox(False, 5)
 self.pack_start(self.__paned_box)
 self.__paned_box.pack_start(paned)
 
 #
 # RBSource methods
 #
 
 def do_impl_show_entry_popup(self):
 self.show_source_popup ("/JamendoSourceViewPopup")
 
 def do_impl_get_ui_actions(self):
 return ["JamendoDownloadAlbum","JamendoDonateArtist"]
 
 
 def do_impl_get_status(self):
 if self.__updating:
 if self.__load_total_size > 0:
 progress = min (float(self.__load_current_size) / self.__load_total_size, 1.0)
 else:
 progress = -1.0
 return (_("Loading Jamendo catalogue"), None, progress)
 else:
 qm = self.get_property("query-model")
 return (qm.compute_status_normal("%d song", "%d songs"), None, 0.0)
 
 def do_impl_activate(self):
 if not self.__activated:
 shell = self.get_property('shell')
 self.__db = shell.get_property('db')
 self.__entry_type = self.get_property('entry-type')
 
 self.__activated = True
 self.__show_loading_screen (True)
 self.__load_catalogue()
 
 # start our catalogue updates
 self.__update_id = gobject.timeout_add(6 * 60 * 60 * 1000, self.__update_catalogue)
 self.__update_catalogue()
 
 sort_key = gconf.client_get_default().get_string(JamendoConfigureDialog.gconf_keys['sorting'])
 if not sort_key:
 sort_key = "Artist,ascending"
 self.get_entry_view().set_sorting_type(sort_key)
 
 rb.BrowserSource.do_impl_activate (self)
 
 def do_impl_delete_thyself(self):
 if self.__update_id != 0:
 gobject.source_remove (self.__update_id)
 self.__update_id = 0
 
 if self.__notify_id != 0:
 gobject.source_remove (self.__notify_id)
 self.__notify_id = 0
 
 if self.__xfer_handle is not None:
 self.__xfer_handle.cancel()
 self.__xfer_handle = None
 
 gconf.client_get_default().set_string(JamendoConfigureDialog.gconf_keys['sorting'], self.get_entry_view().get_sorting_type())
 rb.BrowserSource.do_impl_delete_thyself (self)
 
 
 #
 # internal catalogue downloading and loading
 #
 def __load_catalogue_read_cb (self, handle, data, exc_type, bytes_requested, parser):
 if exc_type:
 if issubclass (exc_type, gnomevfs.EOFError):
 def finish_loadscreen():
 # successfully loaded
 gtk.gdk.threads_enter()
 self.__load_db ()
 self.__show_loading_screen (False)
 
 in_progress_dir = gnomevfs.DirectoryHandle(gnomevfs.URI(jamendo_dir))
 in_progress = in_progress_dir.next()
 while True:
 if in_progress.name[0:12] == "in_progress_":
 in_progress = gnomevfs.read_entire_file(jamendo_dir + in_progress.name)
 for uri in in_progress.split("\n"):
 if uri == '':
 continue
 self.__download_album(gnomevfs.URI(uri))
 try:
 in_progress = in_progress_dir.next()
 except:
 break
 gtk.gdk.threads_leave()
 gobject.idle_add (finish_loadscreen)
 else:
 # error reading file
 raise exc_type
 
 parser.close()
 handle.close(lambda handle, exc: None) # FIXME: report it?
 self.__load_handle = None
 self.__updating = False
 self.__notify_status_changed()
 else:
 
 parser.feed(data)
 handle.read(64 * 1024, self.__load_catalogue_read_cb, parser)
 
 self.__notify_status_changed()
 
 def __load_catalogue_open_cb (self, handle, exc_type):
 if exc_type:
 self.__load_handle = None
 self.__notify_status_changed()
 
 if gnomevfs.exists(local_song_info_uri):
 raise exc_type
 else:
 return
 
 parser = xml.sax.make_parser()
 self.__saxHandler = JamendoSaxHandler()
 parser.setContentHandler(self.__saxHandler)
 handle.read (64 * 1024, self.__load_catalogue_read_cb, parser)
 
 def __load_catalogue(self):
 self.__notify_status_changed()
 self.__load_handle = gnomevfs.async.open (local_song_info_uri, self.__load_catalogue_open_cb)
 
 
 def __download_update_cb (self, _reserved, info, moving):
 self.__load_current_size = info.bytes_copied
 self.__load_total_size = info.bytes_total
 self.__notify_status_changed()
 
 if info.phase == gnomevfs.XFER_PHASE_COMPLETED:
 self.__xfer_handle = None
 # done downloading, unzip to real location
 catalog = gzip.open(local_song_info_temp_uri.path)
 out = create_if_needed(local_song_info_uri, gnomevfs.OPEN_WRITE)
 out.write(catalog.read())
 out.close()
 catalog.close()
 gnomevfs.unlink(local_song_info_temp_uri)
 self.__updating = False
 self.__load_catalogue()
 else:
 #print info
 pass
 
 return 1
 
 def __download_catalogue(self):
 self.__updating = True
 create_if_needed(local_song_info_temp_uri, gnomevfs.OPEN_WRITE).close()
 self.__xfer_handle = gnomevfs.async.xfer (source_uri_list = [jamendo_song_info_uri],
 target_uri_list = [local_song_info_temp_uri],
 xfer_options = gnomevfs.XFER_FOLLOW_LINKS_RECURSIVE,
 error_mode = gnomevfs.XFER_ERROR_MODE_ABORT,
 overwrite_mode = gnomevfs.XFER_OVERWRITE_MODE_REPLACE,
 progress_update_callback = self.__download_update_cb,
 update_callback_data = False)
 
 def __update_catalogue(self):
 def info_cb (handle, results):
 (remote_uri, remote_exc, remote_info) = results[0]
 (local_uri, local_exc, local_info) = results[1]
 
 if remote_exc:
 # error locating remote file
 print "error locating remote catalogue", remote_exc
 elif local_exc:
 if issubclass (local_exc, gnomevfs.NotFoundError):
 # we haven't got it yet
 print "no local copy of catalogue"
 self.__download_catalogue()
 else:
 # error locating local file
 print "error locating local catalogue", local_exc
 self.__download_catalogue()
 else:
 try:
 if remote_info.mtime > local_info.mtime:
 # newer version available
 self.__download_catalogue()
 else:
 # up to date
 pass
 except ValueError, e:
 # couldn't get the mtimes. download?
 print "error checking times", e
 self.__download_catalogue()
 return
 
 gnomevfs.async.get_file_info ((jamendo_song_info_uri, local_song_info_uri), info_cb)
 
 def __show_loading_screen(self, show):
 if self.__info_screen is None:
 # load the glade stuff
 gladexml = gtk.glade.XML(self.__plugin.find_file("jamendo-loading.glade"), root="jamendo_loading_scrolledwindow")
 self.__info_screen = gladexml.get_widget("jamendo_loading_scrolledwindow")
 self.pack_start(self.__info_screen)
 self.get_entry_view().set_no_show_all (True)
 self.__info_screen.set_no_show_all (True)
 
 self.__info_screen.set_property("visible", show)
 self.__paned_box.set_property("visible", not show)
 
 def __load_db(self):
 tracks = self.__saxHandler.tracks
 albums = self.__saxHandler.albums
 artists = self.__saxHandler.artists
 
 # map album ID -> { format -> torrent URL }
 for album_key in albums.keys():
 album = albums[album_key]
 id = album['id']
 formats = {}
 self.__p2plinks[id] = formats
 for p2plink in album['P2PLinks']:
 if p2plink['network'] == 'bittorrent':
 fmt = p2plink['audioEncoding']
 link = p2plink['p2plink']
 formats[fmt] = link
 
 for track_key in tracks.keys():
 track = tracks[track_key]
 album = albums.get(track['albumID'])
 if album != None:
 artist = artists.get(album['artistID'])
 stream = stream_url % (track_key)
 
 entry = self.__db.entry_lookup_by_location (stream)
 if entry == None:
 entry = self.__db.entry_new(self.__entry_type, stream)
 
 release_date = album['releaseDate']
 if release_date:
 year = int(release_date[0:4])
 date = datetime.date(year, 1, 1).toordinal()
 self.__db.set(entry, rhythmdb.PROP_DATE, date)
 
 self.__db.set(entry, rhythmdb.PROP_TITLE, track['dispname'])
 if artist != None:
 self.__db.set(entry, rhythmdb.PROP_ARTIST, artist['dispname'])
 self.__db.set(entry, rhythmdb.PROP_GENRE, artist['genre'])
 self.__db.set(entry, rhythmdb.PROP_ALBUM, album['dispname'])
 
 trackno = int(track['trackno'])
 if trackno >= 0:
 self.__db.set(entry, rhythmdb.PROP_TRACK_NUMBER, trackno)
 self.__db.set(entry, rhythmdb.PROP_DURATION, int(track['lengths']))
 # slight misuse, but this is far more efficient than having a python dict
 # containing this data.
 self.__db.set(entry, rhythmdb.PROP_MUSICBRAINZ_ALBUMID, track['albumID'])
 
 self.__db.commit()
 self.__saxHandler = None
 
 
 def __notify_status_changed(self):
 def change_idle_cb():
 self.notify_status_changed()
 self.__notify_id = 0
 return False
 
 if self.__notify_id == 0:
 self.__notify_id = gobject.idle_add(change_idle_cb)
 
 
 # Download album
 def download_album (self):
 tracks = self.get_entry_view().get_selected_entries()
 format = gconf.client_get_default().get_string(JamendoConfigureDialog.gconf_keys['format'])
 if not format or format not in JamendoConfigureDialog.format_list:
 format = 'ogg3'
 
 #TODO: this should work if the album was selected in the browser
 #without any track selected
 if len(tracks) == 1:
 track = tracks[0]
 albumid = self.__db.entry_get(track, rhythmdb.PROP_MUSICBRAINZ_ALBUMID)
 formats = self.__p2plinks[albumid]
 p2plink = formats[format]
 self.__download_p2plink (p2plink)
 
 def __download_p2plink (self, link):
 gnomevfs.url_show(link)
 
 # Donate to Artist
 def launch_donate (self):
 tracks = self.get_entry_view().get_selected_entries()
 
 #TODO: this should work if the artist was selected in the browser
 #without any track selected
 if len(tracks) == 1:
 track = tracks[0]
 # The Album ID can be used to lookup the artist, and issue a clean redirect.
 albumid = self.__db.entry_get(track, rhythmdb.PROP_MUSICBRAINZ_ALBUMID)
 artist = self.__db.entry_get(track, rhythmdb.PROP_ARTIST)
 url = artist_url + albumid.__str__() + "/"
 self.__loader.get_url(url, self.__open_donate, artist)
 
 def __open_donate (self, result, artist):
 if result is None:
 emsg = _("Error looking up artist %s on jamendo.com") % (artist)
 gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, emsg).run()
 return
 gnomevfs.url_show(result + "donate/")
 
 def __p2plink_download_update_cb (self, _reserved, info, moving):
 if info.phase == gnomevfs.XFER_PHASE_COMPLETED:
 print info
 
 return 1
 
 def playing_entry_changed (self, entry):
 if not self.__db or not entry:
 return
 
 if entry.get_entry_type() != self.__db.entry_type_get_by_name("JamendoEntryType"):
 return
 
 gobject.idle_add(self.emit_cover_art_uri, entry)
 
 def emit_cover_art_uri (self, entry):
 stream = self.__db.entry_get (entry, rhythmdb.PROP_LOCATION)
 albumid = self.__db.entry_get (entry, rhythmdb.PROP_MUSICBRAINZ_ALBUMID)
 url = artwork_url % albumid
 
 self.__db.emit_entry_extra_metadata_notify (entry, "rb:coverArt-uri", str(url))
 return False
 
 gobject.type_register(JamendoSource)
 
 def create_if_needed(uri, mode):
 if not gnomevfs.exists(uri):
 for directory in URIIterator(uri):
 if not gnomevfs.exists(directory):
 gnomevfs.make_directory(directory, 0755)
 out = gnomevfs.create(uri, open_mode=mode)
 else:
 out = gnomevfs.open(uri, open_mode=mode)
 return out
 
 class URIIterator:
 def __init__(self, uri):
 self.uri_list = uri.dirname.split("/")[1:] # dirname starts with /
 self.counter = 0
 def __iter__(self):
 return self
 def next(self):
 if self.counter == len(self.uri_list) + 1:
 raise StopIteration
 value = "file://"
 for i in range(self.counter):
 value += "/" + self.uri_list[i]
 self.counter += 1
 return gnomevfs.URI(value)
 
 |