Re: [Rhythmbox-devel] Magnatune catalog/purchasing plugin



On Mon, 2006-19-06 at 14:33 +1000, James "Doc" Livingston wrote:
> I've just converted RhythmDBEntry and RhythmDBEntryType to be boxed
> types, which allows Python to use them. By changing
> "rhythmdb.rhythmdb_register_entry_type" to
> "rhythmdb.register_entry_type" in the last patch you posted, it seems to
> work now.

Alright, now we're getting somewhere! The tracks now show up in the
source, and they play :D ! But they have no metadata. The first
self._db.entry_set_uninserted call (and presumably the others as well)
throws an exception with the message "entry should be a RhythmDBEntry".
However, entry is a RhythmDBEntry (checked with "print entry", which
gives something to the effect of <RhythmDBEntry at 0x88307b8>), so I'm
not sure what's going on there.

The other thing that's going weird has to do with the gnomevfs.async
code I'm writing, but it's quite possible I'm doing something wrong
there. I get the following message a bunch of times:

(rhythmbox:7409): libgnomevfs-WARNING **: Unknown job kind 9

(strangely, the UI still seems to block while loading the songs,
although the activate method returns beforehand (or at least, the
gnomevfs.async.open call returns))
and then rhythmbox segfaults:

GLib-ERROR **: gmem.c:135: failed to allocate 524288 bytes
aborting...
Segmentation fault!
Cannot display crash dialogue

The only result I found on google for the libgnomevfs error didn't
really help much.

--
Adam Zimmerman <adam_zimmerman sfu ca>

CREATIVITY  - http://mirrors.creativecommons.org/movingimages/Building_on_the_Past.mpg
ALWAYS      - http://www.musiccreators.ca/
BUILDS      - http://www.ubuntu.com/
ON THE PAST - http://www.theopencd.org/
--

 Most people will listen to your unreasonable demands, if you'll consider
their unacceptable offer.
import rhythmdb, rb
import gobject, gtk, gconf, gnomevfs
from gettext import gettext as _

import xml.sax, xml.sax.handler
import urllib

magnatune_partner_id = "rhythmbox" # this needs to be set up with magnatune

################################################
# Class to add Magnatune catalog to the source #
################################################

class TrackListHandler(xml.sax.handler.ContentHandler):
	
	def __init__(self, source, db, entry_type):
		xml.sax.handler.ContentHandler.__init__(self)
		self._track = {} # temporary dictionary for track info
		self._source = source
		self._db = db
		self._entry_type = entry_type
	
	def startElement(self, name, attrs):
		self._text = ""
	
	def endElement(self, name):
		if name == "Track":
			
			try:
				print "Adding: %s - %s" % (self._track['artist'], self._track['trackname'])
				# add the track to the source
				entry = self._db.entry_new(self._entry_type, self._track['url'])
				
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_ARTIST, self._track['artist'])
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_ALBUM, self._track['albumname'])
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_TITLE, self._track['trackname'])
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_TRACK_NUMBER, int(self._track['tracknum']))
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_YEAR, int(self._track['year']))
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_GENRE, self._track['mp3genre'])
				self._db.entry_set_uninserted(entry, rhythmdb.PROP_DURATION, int(self._track['seconds']))
				# somehow associate the sku with the track as well, so we can buy it.
				
				self._db.commit()
				# temporary, until we can properly make proper query models in python
				#model = self._source.get_property("query-model")
				#model.add_entry(entry, -1)
			except Exception,e: # This happens on duplicate uris being added (and now on the set_uninserted call)
				print e
			
			self._track = {}
		elif name == "AllSongs":
			pass # end of the file
		else:
			self._track[name] = self._text
	
	def characters(self, content):
		self._text = self._text + content


################################################
# Main Magnatune Plugin Class                  #
################################################

class Magnatune(rb.Plugin):
	
	_preferences = None
	
	#
	# Core methods
	#
	
	def __init__(self):
		rb.Plugin.__init__(self)
		
	def activate(self, shell):
		self.db = shell.get_property("db")
		self.entry_type = rhythmdb.entry_register_type("MagnatuneEntryType")
		self.source = gobject.new (MagnatuneSource, shell=shell, name=_("Magnatune"), entry_type=self.entry_type)
		shell.register_entry_type_for_source(self.source, self.entry_type)
		
		icon = gtk.gdk.pixbuf_new_from_xpm_data(magnatune_logo_xpm) # Include a flashy Magnatune logo for the source
		self.source.set_property("icon", icon)
		shell.append_source(self.source, None) # Add the source to the list
		
		# http://magnatune.com/info/song_info.xml
		self.parser = xml.sax.make_parser()
		self.parser.setContentHandler(TrackListHandler(self.source, self.db, self.entry_type))
		#gnomevfs.async.open("/home/adam/Desktop/song_info.xml", self.open_callback)
		self.parser.parse("/home/adam/Desktop/song_info.xml")
	
	def deactivate(self, shell):
		self.db.entry_delete_by_type(self.entry_type)
		self.db.commit()
		self.source.delete_thyself()
		self.source = None
	
	#
	# Callback/helper functions
	#
	
	def open_callback(self, handle, exc_type):
		times = 0
		if not exc_type:
			try:
				while True:
					handle.read(512*1024, self.read_callback) # file is about 5MB
			except EOFError:
				handle.close(lambda *args: None)
		else:
			handle.close(lambda *args: None)
	
	def read_callback(self, handle, buf, exc_type, bytes_requested):
		self.parser.feed(buf)


class MagnatuneSource(rb.BrowserSource):
	def __init__(self):
		rb.Source.__init__(self)

gobject.type_register(MagnatuneSource)


################################################
# Purchasing code. Do this later               #
################################################

class BuyAlbumHandler(xml.sax.handler.ContentHandler): # Class to download the track, etc.
	
	def __init__(self):
		xml.sax.handler.ContentHandler.__init__(self)
	
	def startElement(self, name, attrs):
		self._text = ""
	
	def endElement(self, name): # need to figure out the format of what gets returned, there's no documentation on the site.
		pass
	
	def characters(self, content):
		self._text = self._text + content

def buy_track(track, amount, cc, name, email): # http://magnatune.com/info/api#purchase
	client = gconf.client_get_default()
	url = "https://magnatune.com/buy/buy_dl_cc_xml?";
	url = url + urllib.urlencode({
					'id':	magnatune_partner_id,
					'sku':	track['albumsku'],
					'amount': amount,
					'cc':	cc['number'],
					'yy':	cc['year'],
					'mm':	cc['month'],
					'name': name,
					'email':email
				})
	xml.sax.parse(url, BuyAlbumHandler())
	
	url = "" # get download url
	# transfer the track to the library with track-transfer

################################################
# Magnatune Logo. Seems to work well enough... #
################################################

# (converted from http://www.magnatune.com/favicon.ico)

magnatune_logo_xpm = [
"32 32 4 1",
" 	c None", #Original colours:
".	c None", #FFFFFF
"+	c #303030", #C0C0C0 
"@	c #000000", #808080
"................................",
"................................",
"................................",
"................................",
"................................",
"................................",
"............++@@@@++............",
"..........+@@@@@@@@@@+..........",
".........+@@@+....+@@@+.........",
"........+@@+...++...+@@+........",
".......+@@+....@@....+@@+.......",
".......@@+.....@@.....+@@.......",
"......+@@......@@......@@+......",
"      + +      @@      + +      ",
"......@@...@@..@@..@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"      + +  @@+.@@.+@@  + +      ",
"......+@@..@@+.@@.+@@..@@+......",
".......@@+.@@+.@@.+@@.+@@.......",
".......+@@+.+..+...+.+@@+.......",
"........+@@+........+@@+........",
".........+@@@+....+@@@+.........",
"..........+@@@@@@@@@@+..........",
"............++@@@@++............",
"................................",
"................................",
"................................",
"................................",
"................................",
"................................"
]



###
### preferences, ugly and gross. Someone else who knows what they're doing should probably fix this
###

#	def create_configure_dialog(self): # return a gtk dialog with configure options
#		if self._preferences == None:
#			client = gconf.client_get_default()
#			self._preferences = gtk.Dialog(title=_("Magnatune Preferences"), flags=gtk.DIALOG_MODAL)
#			
#			label = gtk.Label("<b>Purchase Information</b>")
#			self._preferences.vbox.pack_start(label, False, False, 0)
#			label.show()
#			
#			hbox = gtk.HBox()
#			label = gtk.Label(_("Name"))
#			entry = gtk.Entry()
#			self.setup_entry(entry, "name")
#			hbox.pack_start(label, False, False, 0)
#			hbox.pack_start(entry, False, False, 0)
#			label.show()
#			entry.show()
#			self._preferences.vbox.pack_start(hbox, True, True, 0)
#			hbox.show()
#			
#			hbox = gtk.HBox()
#			label = gtk.Label(_("E-mail Address"))
#			entry = gtk.Entry()
#			self.setup_entry(entry, "email")
#			hbox.pack_start(label, False, False, 0)
#			hbox.pack_start(entry, False, False, 0)
#			label.show()
#			entry.show()
#			self._preferences.vbox.pack_start(hbox, True, True, 0)
#			hbox.show()
#			
#			button = gtk.CheckButton(_("Remember Credit Card Information"))
#			credit_entry = gtk.Entry(max=16)
#			month_entry = gtk.Entry(max=2)
#			year_entry = gtk.Entry(max=4)
#			button.connect("toggled", self.check_toggle, (credit_entry, month_entry, year_entry))
#			set = client.get_bool("/apps/rhythmbox/plugins/magnatune/forget")
#			if set is not None:
#				button.set_active(set)
#			self._preferences.vbox.pack_start(button, False, False, 0)
#			button.show()
#			
#			hbox = gtk.HBox()
#			label = gtk.Label(_("Credit Card Number"))
#			# entry has already been created
#			self.setup_entry(credit_entry, "cc_num")
#			hbox.pack_start(label, False, False, 0)
#			hbox.pack_start(credit_entry, False, False, 0)
#			label.show()
#			credit_entry.show()
#			self._preferences.vbox.pack_start(hbox, True, True, 0)
#			hbox.show()
#			
#			hbox = gtk.HBox()
#			label = gtk.Label(_("Expiration: mm/yy "))
#			# entries already created
#			sep = gtk.Label(" / ")
#			self.setup_entry(month_entry, "cc_mm")
#			self.setup_entry(year_entry, "cc_yy")
#			hbox.pack_start(label, False, False, 0)
#			hbox.pack_start(month_entry, False, False, 0)
#			hbox.pack_start(sep, False, False, 0)
#			hbox.pack_start(year_entry, False, False, 0)
#			label.show()
#			month_entry.show()
#			sep.show()
#			year_entry.show()
#			self._preferences.vbox.pack_start(hbox, True, True, 0)
#			hbox.show()
#			
#			hbox = gtk.HBox()
#			button = gtk.Button(stock=gtk.STOCK_CLOSE)
#			button.connect("clicked", self.close_clicked, None)
#			self._preferences.action_area.pack_end(button, True, True, 0)
#			button.show()
#			
#		self._preferences.show()
#		return self._preferences
#
#	def check_toggle(self, widget, data=None):
#		active = not widget.get_active() # this method gets called before the widget changes
#		client = gconf.client_get_default()
#		client.set_bool("/apps/rhythmbox/plugins/magnatune/forget", active)
#		if active:
#			for entry in data:
#				entry.set_text("")
#				entry.set_sensitive(False)
#			for field in ('cc_num', 'cc_mm', 'cc_yy'):
#				client.unset("/apps/rhythmbox/plugins/magnatune/" + field)
#		else:
#			for entry in data:
#				entry.set_sensitive(True)
#	
#	def pref_changed(self, widget, gdk_event, data=None):
#		client = gconf.client_get_default()
#		client.set_string("/apps/rhythmbox/plugins/magnatune/" + data, widget.get_text())
#	
#	def close_clicked(self, widget, data=None):
#		self._preferences.hide()
#	
#	def setup_entry(self, entry, data):
#		client = gconf.client_get_default()
#		text = client.get_string("/apps/rhythmbox/plugins/magnatune/" + data)
#		if text is not None:
#			entry.set_text(text)
#		entry.connect("focus-out-event", self.pref_changed, data)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]