| Server IP : 182.53.201.61 / Your IP : 216.73.217.175 Web Server : Apache/2.2.15 (Fedora) System : Linux km10.dyndns.org 2.6.31.5-127.fc12.i686.PAE #1 SMP Sat Nov 7 21:25:57 EST 2009 i686 User : apache ( 48) PHP Version : 5.3.3 Disable Function : NONE MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /usr/lib/rhythmbox/plugins/magnatune/ |
Upload File : |
# -*- 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 grant 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 os
import gobject
import gtk
import gnome, gconf
import xml
import urllib
import urlparse
import zipfile
has_gnome_keyring = False
#try:
# import gnomekeyring
# has_gnome_keyring = True
#except:
# pass
magnatune_partner_id = "rhythmbox"
# URIs
magnatune_song_info_uri = "http://magnatune.com/info/song_info_xml.zip"
magnatune_in_progress_dir = os.path.join(rb.user_data_dir(), 'magnatune')
magnatune_cache_dir = os.path.join(rb.user_cache_dir(), 'magnatune')
magnatune_song_info = os.path.join(magnatune_cache_dir, 'song_info.xml')
magnatune_song_info_temp = os.path.join(magnatune_cache_dir, 'song_info.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.__catalogue_loader = None
self.__catalogue_check = None
self.__info_screen = None
self.__updating = True
self.__has_loaded = False
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 catalog"), 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')
# move files from old ~/.gnome2 paths
if os.path.exists(magnatune_in_progress_dir) is False:
self.__move_data_files()
self.__activated = True
self.__show_loading_screen (True)
# 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.__catalogue_loader is not None:
self.__catalogue_loader.cancel()
self.__catalogue_loader = None
if self.__catalogue_check is not None:
self.__catalogue_check.cancel()
self.__catalogue_check = 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:
rb.show_uri(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:
rb.show_uri(url)
urls.add(url)
def radio_toggled(self, builder):
gc = builder.get_object("radio_gc").get_active()
builder.get_object("remember_cc_details").set_sensitive(not gc)
builder.get_object("name_entry").set_sensitive(not gc)
builder.get_object("cc_entry").set_sensitive(not gc)
builder.get_object("mm_entry").set_sensitive(not gc)
builder.get_object("yy_entry").set_sensitive(not gc)
builder.get_object("gc_entry").set_sensitive(gc)
if not gc:
builder.get_object("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 = []
builder = gtk.Builder()
builder.add_from_file(self.__plugin.find_file("magnatune-purchase.ui"))
# .. argh autoconnect ..
#cb_dict = {"rb_magnatune_on_radio_cc_toggled_cb":lambda w:self.radio_toggled(gladexml)}
#gladexml.signal_autoconnect(cb_dict)
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)
builder.get_object("gc_entry").set_sensitive(False)
builder.get_object("pay_combobox").set_active(self.__client.get_int(self.__plugin.gconf_keys['pay']) - 5)
builder.get_object("audio_combobox").set_active(self.__plugin.format_list.index(self.__client.get_string(self.__plugin.gconf_keys['format'])))
builder.get_object("info_label").set_markup(_("Would you like to purchase the album <i>%(album)s</i> by '%(artist)s'?") % {"album":album, "artist":artist})
builder.get_object("remember_cc_details").props.visible = has_gnome_keyring
try:
(ccnumber, ccyear, ccmonth, name, email) = self.__plugin.get_cc_details()
builder.get_object("cc_entry").set_text(ccnumber)
builder.get_object("yy_entry").set_text(ccyear)
builder.get_object("mm_entry").set_active(ccmonth-1)
builder.get_object("name_entry").set_text(name)
builder.get_object("email_entry").set_text(email)
builder.get_object("remember_cc_details").set_active(True)
except Exception, e:
print e
builder.get_object("cc_entry").set_text("")
builder.get_object("yy_entry").set_text("")
builder.get_object("mm_entry").set_active(0)
builder.get_object("name_entry").set_text("")
builder.get_object("email_entry").set_text("")
builder.get_object("remember_cc_details").set_active(False)
window = builder.get_object("purchase_dialog")
if window.run() == gtk.RESPONSE_ACCEPT:
amount = builder.get_object("pay_combobox").get_active() + 5
format = self.__plugin.format_list[builder.get_object("audio_combobox").get_active()]
ccnumber = builder.get_object("cc_entry").get_text()
ccyear = builder.get_object("yy_entry").get_text()
ccmonth = str(builder.get_object("mm_entry").get_active() + 1).zfill(2)
name = builder.get_object("name_entry").get_text()
email = builder.get_object("email_entry").get_text()
gc = builder.get_object("radio_gc").get_active()
gc_text = builder.get_object("gc_entry").get_text()
if builder.get_object("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 __catalogue_chunk_cb(self, result, total, parser):
if not result or isinstance (result, Exception):
if result:
# report error somehow?
print "error loading catalogue: %s" % result
try:
parser.close ()
except xml.sax.SAXParseException, e:
# there isn't much we can do here
print "error parsing catalogue: %s" % e
self.__show_loading_screen (False)
self.__updating = False
self.__catalogue_loader = None
# restart in-progress downloads
# (doesn't really belong here)
inprogress = os.listdir(magnatune_in_progress_dir)
inprogress = filter(lambda x: x.startswith("in_progress_"), inprogress)
for ip in inprogress:
for uri in open(ip).readlines():
print "restarting download from %s" % uri
self.__download_album(uri)
else:
# hack around some weird chars that show up in the catalogue for some reason
result = result.replace("\x19", "'")
result = result.replace("\x13", "-")
try:
parser.feed(result)
except xml.sax.SAXParseException, e:
print "error parsing catalogue: %s" % e
self.__load_current_size += len(result)
self.__load_total_size = total
self.__notify_status_changed()
def __load_catalogue(self):
self.__notify_status_changed()
self.__has_loaded = True
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))
self.__catalogue_loader = rb.ChunkLoader()
self.__catalogue_loader.get_url_chunks(magnatune_song_info, 64*1024, True, self.__catalogue_chunk_cb, parser)
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_catalogue_chunk_cb (self, result, total, out):
if not result:
# done downloading, unzip to real location
out.close()
catalog = zipfile.ZipFile(magnatune_song_info_temp)
out = open(magnatune_song_info, 'w')
filename = self.__find_song_info(catalog)
if filename is None:
rb.error_dialog(title=_("Unable to load catalog"),
message=_("Rhythmbox could not understand the Magnatune catalog, please file a bug."))
return
out.write(catalog.read(filename))
out.close()
catalog.close()
os.unlink(magnatune_song_info_temp)
self.__updating = False
self.__catalogue_loader = None
self.__load_catalogue()
elif isinstance(result, Exception):
# complain
pass
else:
out.write(result)
self.__load_current_size += len(result)
self.__load_total_size = total
self.__notify_status_changed()
def __download_catalogue(self):
self.__updating = True
out = open(magnatune_song_info_temp, 'w')
self.__catalogue_loader = rb.ChunkLoader()
self.__catalogue_loader.get_url_chunks(magnatune_song_info_uri, 4*1024, True, self.__download_catalogue_chunk_cb, out)
def __update_catalogue(self):
def update_cb (result):
self.__catalogue_check = None
if result is True:
self.__download_catalogue()
elif self.__has_loaded is False:
self.__load_catalogue()
self.__catalogue_check = rb.UpdateCheck()
self.__catalogue_check.check_for_update(magnatune_song_info, magnatune_song_info_uri, update_cb)
def __show_loading_screen(self, show):
if self.__info_screen is None:
# load the builder stuff
builder = gtk.Builder()
builder.add_from_file(self.__plugin.find_file("magnatune-loading.ui"))
self.__info_screen = builder.get_object("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)
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()
l = rb.Loader()
l.get_url (url, self.__auth_data_cb, format)
def __auth_data_cb (self, data, format):
buy_album_handler = BuyAlbumHandler(format)
auth_parser = xml.sax.make_parser()
auth_parser.setContentHandler(buy_album_handler)
if data is None:
# hmm.
return
self.__wait_dlg.destroy()
try:
data = data.replace("<br>", "") # get rid of any stray <br> tags that will mess up the parser
# print data
auth_parser.feed(data)
auth_parser.close()
# process the URI: add authentication info, quote the filename component for some reason
parsed = urlparse.urlparse(buy_album_handler.url)
netloc = "%s:%s@%s" % (str(buy_album_handler.username), str(buy_album_handler.password), parsed.hostname)
spath = os.path.split(urllib.url2pathname(parsed.path))
basename = spath[1]
path = urllib.pathname2url(os.path.join(spath[0], urllib.quote(basename)))
authed = (parsed[0], netloc, path) + parsed[3:]
audio_dl_uri = urlparse.urlunparse(authed)
in_progress = open(os.path.join(magnatune_in_progress_dir, "in_progress_" + basename), 'w')
in_progress.write(str(audio_dl_uri))
in_progress.close()
self.__download_album(audio_dl_uri)
except MagnatunePurchaseError, e:
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:
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):
fullpath = urlparse.urlparse(audio_dl_uri).path
basename = os.split(fullpath)[1]
destpath = os.path.join(magnatune_in_progress_dir, basename)
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
self.__downloads[audio_dl_uri] = 0
# no way to resume downloads, sadly
out = open(destpath, 'w')
dl = rb.ChunkLoader()
dl.get_url_chunks(audio_dl_uri, 4*1024, True, self.__download_album_chunk, (audio_dl_uri, destpath, out))
def __remove_download_files (self, dest):
sp = os.path.split(dest)
inprogress = os.path.join(sp[0], "in_progress_" + sp[1])
os.unlink(inprogress)
os.unlink(dest)
def __download_finished (self, total, audio_dl_uri, dest, out):
try:
del self.__downloads[audio_dl_uri]
except:
return 0
out.close()
self.purchase_filesize -= total
# just use the first library location
# not quite prepared to use gio here directly yet, so we can only deal with
# local libraries here.
library_location = self.__client.get_list("/apps/rhythmbox/library_locations", gconf.VALUE_STRING)[0]
if library_location.startswith("file://"):
urlpath = urlparse.urlparse(library_location).path
library_dir = urllib.url2pathname(urlpath)
else:
library_dir = rb.music_dir ()
album = zipfile.ZipFile(dest)
for track in album.namelist():
track_uri = "file://" + urllib.pathname2url(os.path.join(library_dir, track))
track_uri = rb.sanitize_uri_for_filesystem(track_uri)
rb.uri_create_parent_dirs(track_uri)
track_path = urllib.url2pathname(urlparse.urlparse(track_uri).path)
track_out = open(track_path, 'w')
track_out.write(album.read(track))
track_out.close()
album.close()
self.__remove_download_files (dest)
if self.purchase_filesize == 0:
self.__downloading = False
self.__db.add_uri(os.path.split(track_path)[0])
def __download_album_chunk(self, result, total, (audio_dl_uri, dest, out)):
if not result:
self.__download_finished (total, audio_dl_uri, dest, out)
elif isinstance(result, Exception):
# probably report this somehow?
pass
elif self.cancelled:
try:
del self.__downloads[audio_dl_uri]
self.purchase_filesize -= total
self.__remove_download_files (dest)
except:
pass
if self.purchase_filesize == 0:
self.__downloading = False
return False
else:
if self.__downloads[audio_dl_uri] == 0:
self.purchase_filesize += total
out.write(result)
self.__downloads[audio_dl_uri] += len(result)
self.__download_progress = sum(self.__downloads.values()) / float(self.purchase_filesize)
self.__notify_status_changed()
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
def __move_data_files (self):
# create cache and data directories
# (we know they don't already exist, and we know the parent dirs do)
os.mkdir(magnatune_in_progress_dir, 0700)
if os.path.exists(magnatune_cache_dir) is False:
os.mkdir(magnatune_cache_dir, 0700)
# move song info to cache dir
old_magnatune_dir = os.path.join(rb.dot_dir(), 'magnatune')
if os.path.exists(old_magnatune_dir) is False:
print "old magnatune directory does not exist"
return
old_song_info = os.path.join(old_magnatune_dir, 'song_info.xml')
if os.path.exists(old_song_info):
print "moving existing song_info.xml to cache dir"
os.rename(old_song_info, magnatune_song_info)
else:
print "no song_info.xml found (%s)" % old_song_info
# move in progress downloads to data dir
otherfiles = os.listdir(old_magnatune_dir)
for f in otherfiles:
print "moving file %s to new in-progress dir" % f
os.rename(os.path.join(old_magnatune_dir, f),
os.path.join(magnatune_in_progress_dir, f))
gobject.type_register(MagnatuneSource)