Viewing file: MagnatuneSource.py (21.93 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- # # Copyright (C) 2006 Adam Zimmerman <adam_zimmerman@sfu.ca> # Copyright (C) 2006 James Livingston <doclivingston@gmail.com> # # 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. # # The Rhythmbox authors hereby grants permission for non-GPL compatible # GStreamer plugins to be used and distributed together with GStreamer # and Rhythmbox. This permission is above and beyond the permissions granted # by the GPL license by which Rhythmbox is covered. If you modify this code # you may extend this exception to your version of the code, but you are not # obligated to do so. If you do not wish to do so, delete this exception # statement from your 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import rb, rhythmdb from TrackListHandler import TrackListHandler from BuyAlbumHandler import BuyAlbumHandler, MagnatunePurchaseError
import gobject import gtk.glade import gnomevfs, gnome, gconf import xml import urllib, zipfile
has_gnome_keyring = False
#try: # import gnomekeyring # has_gnome_keyring = True #except: # pass
magnatune_partner_id = "rhythmbox"
# URIs magnatune_dir = gnome.user_dir_get() + "rhythmbox/magnatune/" magnatune_song_info_uri = gnomevfs.URI("http://magnatune.com/info/song_info_xml.zip") local_song_info_uri = gnomevfs.URI(magnatune_dir + "song_info.xml") local_song_info_temp_uri = gnomevfs.URI(magnatune_dir + "song_info.xml.zip.tmp") ALBUM_ART_URL = 'http://www.magnatune.com/music/%s/%s/cover.jpg'
class MagnatuneSource(rb.BrowserSource): __gproperties__ = { 'plugin': (rb.Plugin, 'plugin', 'plugin', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY), }
__client = gconf.client_get_default()
def __init__(self):
rb.BrowserSource.__init__(self, name=_("Magnatune")) self.__db = None
# track data self.__sku_dict = {} self.__home_dict = {} self.__buy_dict = {} self.__art_dict = {}
# catalogue stuff self.__activated = False self.__notify_id = 0 self.__update_id = 0 self.__xfer_handle = None self.__info_screen = None self.__updating = True self.__has_loaded = False self.__load_handle = None self.__load_current_size = 0 self.__load_total_size = 0
self.__downloads = {} # keeps track of amount downloaded for each file self.__downloading = False # keeps track of whether we are currently downloading an album self.__download_progress = 0.0 # progress of current download(s) self.purchase_filesize = 0 # total amount of bytes to download
def do_set_property(self, property, value): if property.name == 'plugin': self.__plugin = value else: raise AttributeError, 'unknown property %s' % property.name
# # RBSource methods #
def do_impl_show_entry_popup(self): self.show_source_popup ("/MagnatuneSourceViewPopup")
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 Magnatune catalogue"), None, progress) elif self.__downloading: progress = min (self.__download_progress, 1.0) return (_("Downloading Magnatune Album(s)"), None, progress) else: qm = self.get_property("query-model") return (qm.compute_status_normal("%d song", "%d songs"), None, 0.0)
def do_impl_get_ui_actions(self): return ["MagnatunePurchaseAlbum", "MagnatunePurchaseCD", "MagnatuneArtistInfo", "MagnatuneCancelDownload"]
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()
self.get_entry_view().set_sorting_type(self.__client.get_string("/apps/rhythmbox/plugins/magnatune/sorting"))
rb.BrowserSource.do_impl_activate (self)
def do_impl_get_browser_key (self): return "/apps/rhythmbox/plugins/magnatune/show_browser"
def do_impl_get_paned_key (self): return "/apps/rhythmbox/plugins/magnatune/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)
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
self.__client.set_string("/apps/rhythmbox/plugins/magnatune/sorting", self.get_entry_view().get_sorting_type())
rb.BrowserSource.do_impl_delete_thyself (self)
# # methods for use by plugin and UI #
def display_artist_info(self): tracks = self.get_entry_view().get_selected_entries() urls = set([])
for tr in tracks: sku = self.__sku_dict[self.__db.entry_get(tr, rhythmdb.PROP_LOCATION)] url = self.__home_dict[sku] if url not in urls: gnomevfs.url_show(url) urls.add(url)
def buy_cd(self): tracks = self.get_entry_view().get_selected_entries() urls = set([])
for tr in tracks: sku = self.__sku_dict[self.__db.entry_get(tr, rhythmdb.PROP_LOCATION)] url = self.__buy_dict[sku] if url not in urls: gnomevfs.url_show(url) urls.add(url)
def radio_toggled(self, gladexml): gc = gladexml.get_widget("radio_gc").get_active() gladexml.get_widget("remember_cc_details").set_sensitive(not gc) gladexml.get_widget("name_entry").set_sensitive(not gc) gladexml.get_widget("cc_entry").set_sensitive(not gc) gladexml.get_widget("mm_entry").set_sensitive(not gc) gladexml.get_widget("yy_entry").set_sensitive(not gc) gladexml.get_widget("gc_entry").set_sensitive(gc) if not gc: gladexml.get_widget("gc_entry").set_text("") def purchase_album(self): try: library_location = self.__client.get_list("/apps/rhythmbox/library_locations", gconf.VALUE_STRING)[0] # Just use the first library location except IndexError, e: rb.error_dialog(title = _("Couldn't purchase album"), message = _("You must have a library location set to purchase an album.")) return
tracks = self.get_entry_view().get_selected_entries() skus = []
for track in tracks: sku = self.__sku_dict[self.__db.entry_get(track, rhythmdb.PROP_LOCATION)] if sku in skus: continue skus.append(sku) artist = self.__db.entry_get(track, rhythmdb.PROP_ARTIST) album = self.__db.entry_get(track, rhythmdb.PROP_ALBUM)
gladexml = gtk.glade.XML(self.__plugin.find_file("magnatune-purchase.glade")) cb_dict = {"rb_magnatune_on_radio_cc_toggled_cb":lambda w:self.radio_toggled(gladexml)} gladexml.signal_autoconnect(cb_dict) gladexml.get_widget("gc_entry").set_sensitive(False) gladexml.get_widget("pay_combobox").set_active(self.__client.get_int(self.__plugin.gconf_keys['pay']) - 5) gladexml.get_widget("audio_combobox").set_active(self.__plugin.format_list.index(self.__client.get_string(self.__plugin.gconf_keys['format']))) gladexml.get_widget("info_label").set_markup(_("Would you like to purchase the album <i>%(album)s</i> by '%(artist)s'?") % {"album":album, "artist":artist}) gladexml.get_widget("remember_cc_details").props.visible = has_gnome_keyring
try: (ccnumber, ccyear, ccmonth, name, email) = self.__plugin.get_cc_details() gladexml.get_widget("cc_entry").set_text(ccnumber) gladexml.get_widget("yy_entry").set_text(ccyear) gladexml.get_widget("mm_entry").set_active(ccmonth-1) gladexml.get_widget("name_entry").set_text(name) gladexml.get_widget("email_entry").set_text(email)
gladexml.get_widget("remember_cc_details").set_active(True) except Exception, e: print e
gladexml.get_widget("cc_entry").set_text("") gladexml.get_widget("yy_entry").set_text("") gladexml.get_widget("mm_entry").set_active(0) gladexml.get_widget("name_entry").set_text("") gladexml.get_widget("email_entry").set_text("")
gladexml.get_widget("remember_cc_details").set_active(False)
window = gladexml.get_widget("purchase_dialog") if window.run() == gtk.RESPONSE_ACCEPT: amount = gladexml.get_widget("pay_combobox").get_active() + 5 format = self.__plugin.format_list[gladexml.get_widget("audio_combobox").get_active()] ccnumber = gladexml.get_widget("cc_entry").get_text() ccyear = gladexml.get_widget("yy_entry").get_text() ccmonth = str(gladexml.get_widget("mm_entry").get_active() + 1).zfill(2) name = gladexml.get_widget("name_entry").get_text() email = gladexml.get_widget("email_entry").get_text() gc = gladexml.get_widget("radio_gc").get_active() gc_text = gladexml.get_widget("gc_entry").get_text()
if gladexml.get_widget("remember_cc_details").props.active: self.__plugin.store_cc_details(ccnumber, ccyear, ccmonth, name, email) else: self.__plugin.clear_cc_details()
self.__buy_album (sku, amount, format, ccnumber, ccyear, ccmonth, name, email, gc, gc_text)
window.destroy()
# # 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.__show_loading_screen (False)
in_progress_dir = gnomevfs.DirectoryHandle(gnomevfs.URI(magnatune_dir)) in_progress = in_progress_dir.next() while True: if in_progress.name[0:12] == "in_progress_": in_progress = gnomevfs.read_entire_file(magnatune_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() parser.setContentHandler(TrackListHandler(self.__db, self.__entry_type, self.__sku_dict, self.__home_dict, self.__buy_dict, self.__art_dict)) 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 __find_song_info(self, catalogue): for info in catalogue.infolist(): if info.filename.endswith("song_info.xml"): return info.filename; return None
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: # done downloading, unzip to real location catalog = zipfile.ZipFile(local_song_info_temp_uri.path) out = create_if_needed(local_song_info_uri, gnomevfs.OPEN_WRITE) filename = self.__find_song_info(catalog) if filename is None: rb.error_dialog(title="Unable to load catalogue", message=_("Rhythmbox could not understand the Magnatune catalogue, please file a bug.")) return out.write(catalog.read(filename)) out.close() catalog.close() gnomevfs.unlink(local_song_info_temp_uri) self.__updating = False self.__load_catalogue() self.__xfer_handle = None 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 = [magnatune_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 ((magnatune_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("magnatune-loading.glade"), root="magnatune_loading_scrolledwindow") self.__info_screen = gladexml.get_widget("magnatune_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 __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)
# # internal purchasing code # def __buy_album(self, sku, pay, format, ccnumber, ccyear, ccmonth, name, email, gc, gc_text): # http://magnatune.com/info/api#purchase print "purchasing tracks:", sku, pay, format, name, email url_dict = { 'id': magnatune_partner_id, 'sku': sku, 'amount': pay, 'name': name, 'email':email } if gc: url_dict['gc'] = gc else: url_dict['cc'] = ccnumber url_dict['yy'] = ccyear url_dict['mm'] = ccmonth url = "https://magnatune.com/buy/buy_dl_cc_xml?" url = url + urllib.urlencode(url_dict)
buy_album_handler = BuyAlbumHandler(format) # so we can get the url and auth info auth_parser = xml.sax.make_parser() auth_parser.setContentHandler(buy_album_handler)
self.__wait_dlg = gtk.Dialog(title="Authorizing Purchase", flags=gtk.DIALOG_NO_SEPARATOR|gtk.DIALOG_DESTROY_WITH_PARENT) lbl = gtk.Label("Authorizing purchase with the Magnatune server. Please wait...") self.__wait_dlg.vbox.pack_start(lbl) lbl.show() self.__wait_dlg.show() gnomevfs.async.open(gnomevfs.URI(url), self.__auth_open_cb, data=(buy_album_handler, auth_parser))
def __auth_open_cb(self, handle, exc_type, data): if exc_type: raise exc_type
handle.read(64 * 1024, self.__auth_read_cb, data)
def __auth_read_cb (self, handle, data, exc_type, bytes_requested, parser): buy_album_handler = parser[0] auth_parser = parser[1] data = data.replace("<br>", "") # get rid of any stray <br> tags that will mess up the parser if exc_type: if issubclass (exc_type, gnomevfs.EOFError): def start_download (): # successfully loaded gtk.gdk.threads_enter() audio_dl_uri = gnomevfs.URI(buy_album_handler.url) audio_dl_uri = gnomevfs.URI(buy_album_handler.url[0:buy_album_handler.url.rfind("/") + 1] + urllib.quote(audio_dl_uri.short_name)) audio_dl_uri.user_name = str(buy_album_handler.username) # URI objects don't like unicode strings audio_dl_uri.password = str(buy_album_handler.password)
in_progress = create_if_needed(gnomevfs.URI(magnatune_dir + "in_progress_" + audio_dl_uri.short_name), gnomevfs.OPEN_WRITE) in_progress.write(str(audio_dl_uri)) in_progress.close() self.__download_album(audio_dl_uri) self.__wait_dlg.destroy() gtk.gdk.threads_leave() gobject.idle_add (start_download) else: # error reading file raise exc_type
auth_parser.close() handle.close(lambda handle, exc: None) # FIXME: report it?
else: try : print data auth_parser.feed(data) handle.read(64 * 1024, self.__auth_read_cb, parser) except MagnatunePurchaseError, e: self.__wait_dlg.destroy() rb.error_dialog(title = _("Purchase Error"), message = _("An error occurred while trying to purchase the album.\nThe Magnatune server returned:\n%s") % str(e)) except Exception, e: self.__wait_dlg.destroy() rb.error_dialog(title = _("Error"), message = _("An error occurred while trying to purchase the album.\nThe error text is:\n%s") % str(e))
def __download_album(self, audio_dl_uri): library_location = self.__client.get_list("/apps/rhythmbox/library_locations", gconf.VALUE_STRING)[0] # Just use the first library location to_file_uri = gnomevfs.URI(magnatune_dir + audio_dl_uri.short_name)
shell = self.get_property('shell') manager = shell.get_player().get_property('ui-manager') manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(True) self.__downloading = True self.cancelled = False gtk.gdk.threads_leave() self.purchase_filesize += gnomevfs.get_file_info(audio_dl_uri).size gtk.gdk.threads_enter() create_if_needed(to_file_uri, gnomevfs.OPEN_WRITE).close() gnomevfs.async.xfer (source_uri_list = [audio_dl_uri], target_uri_list = [to_file_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.__purchase_download_update_cb, update_callback_data = (to_file_uri, library_location, audio_dl_uri), progress_sync_callback = self.__purchase_download_progress_cb, sync_callback_data = (to_file_uri, audio_dl_uri))
def __purchase_download_update_cb(self, _reserved, info, data): if (info.phase == gnomevfs.XFER_PHASE_COMPLETED): to_file_uri = data[0] library_location = data[1] audio_dl_uri = data[2]
try: del self.__downloads[str(audio_dl_uri)] except: return 0 self.purchase_filesize -= gnomevfs.get_file_info(audio_dl_uri).size album = zipfile.ZipFile(to_file_uri.path) for track in album.namelist(): track_uri = gnomevfs.URI(library_location + "/" + track) out = create_if_needed(track_uri, gnomevfs.OPEN_WRITE) out.write(album.read(track)) out.close() album.close() gnomevfs.unlink(gnomevfs.URI(magnatune_dir + "in_progress_" + to_file_uri.short_name)) gnomevfs.unlink(to_file_uri) if self.purchase_filesize == 0: self.__downloading = False self.__db.add_uri("file://" + urllib.quote(track_uri.dirname)) return 1
def __purchase_download_progress_cb(self, info, data): to_file_uri = data[0] audio_dl_uri = data[1]
if self.cancelled: try: del self.__downloads[str(audio_dl_uri)] self.purchase_filesize -= gnomevfs.get_file_info(audio_dl_uri).size gnomevfs.unlink(gnomevfs.URI(magnatune_dir + "in_progress_" + to_file_uri.short_name)) gnomevfs.unlink(to_file_uri) except: # this may get run more than once pass if self.purchase_filesize == 0: self.__downloading = False return 0
self.__downloads[str(audio_dl_uri)] = info.bytes_copied purchase_downloaded = 0 for i in self.__downloads.values(): purchase_downloaded += i self.__download_progress = purchase_downloaded / float(self.purchase_filesize) self.__notify_status_changed() return 1
def cancel_downloads(self): self.cancelled = True shell = self.get_property('shell') manager = shell.get_player().get_property('ui-manager') manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(False)
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("MagnatuneEntryType"): return
gobject.idle_add (self.emit_cover_art_uri, entry)
def emit_cover_art_uri (self, entry): sku = self.__sku_dict[self.__db.entry_get(entry, rhythmdb.PROP_LOCATION)] url = self.__art_dict[sku] self.__db.emit_entry_extra_metadata_notify (entry, 'rb:coverArt-uri', url) return False
gobject.type_register(MagnatuneSource)
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)
|