import rhythmdb, rb
import gobject, gtk, gtk.glade, gconf, gnomevfs, gnome
from gettext import gettext as _

import xml.sax, xml.sax.handler
import urllib
import datetime
import zipfile
import sys, os.path

magnatune_partner_id = "zimmerman"

user_dir = gnome.user_dir_get()
magnatune_dir = user_dir + "rhythmbox/magnatune/"
magnatune_dir_uri = gnomevfs.URI(magnatune_dir)
magnatune_song_info_uri = gnomevfs.URI("http://magnatune.com/info/song_info.xml")
local_song_info_uri = gnomevfs.URI(magnatune_dir + "song_info.xml")
lc_uri = gnomevfs.URI(magnatune_dir + "info_last_changed")
file_locn = sys.prefix + "/share/rhythmbox/glade"

sku_dict = {}

popup_ui = """
<ui>
  <popup name="BrowserSourceViewPopup">
    <menuitem name="AddToQueueLibraryPopup" action="AddToQueue"/>
    <menuitem name="MagnatunePurchaseAlbum" action="MagnatunePurchase"/>
    <separator/>
    <menuitem name="BrowseGenreLibraryPopup" action="BrowserSrcChooseGenre"/>
    <menuitem name="BrowseArtistLibraryPopup" action="BrowserSrcChooseArtist"/>
    <menuitem name="BrowseAlbumLibraryPopup" action="BrowserSrcChooseAlbum"/>
    <separator/>
    <menuitem name="PropertiesLibraryPopup" action="MusicProperties"/>
  </popup>
</ui>
"""

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

class TrackListHandler(xml.sax.handler.ContentHandler):
	
	def __init__(self, db, entry_type):
		xml.sax.handler.ContentHandler.__init__(self)
		self._track = {} # temporary dictionary for track info
		self._db = db
		self._entry_type = entry_type
	
	def startElement(self, name, attrs):
		self._text = ""
	
	def endElement(self, name):
		if name == "Track":
			try:
				# add the track to the source
				entry = self._db.entry_new(self._entry_type, self._track['url'])
				date = datetime.date(int(self._track['launchdate'][0:4]), 1, 1).toordinal() # year is sometimes 0, so we use launchdate
				
				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_DATE, date)
				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']))
				sku_dict[self._track['url']] = self._track['albumsku']
				
				self._db.commit()
			except Exception,e: # This happens on duplicate uris being added
				print _("Couldn't add %s - %s") % (self._track['artist'], self._track['trackname']) # TODO: This should be printed to debug
				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):
	
	info_file = None
	remote_info_file = None
	configure_dialog = None
	purchase_filesize = 0
	
	#
	# Core methods
	#
	
	def __init__(self):
		rb.Plugin.__init__(self)
		self.configure_callback_dic = {
				"rb_magnatune_name_entry_changed_cb" : self.name_entry_cb,
				"rb_magnatune_name_entry_activate_cb" : self.name_entry_cb,
				"rb_magnatune_email_entry_changed_cb" : self.email_entry_cb,
				"rb_magnatune_email_entry_activate_cb" : self.email_entry_cb,
				"rb_magnatune_cc_entry_changed_cb" : self.cc_entry_cb,
				"rb_magnatune_cc_entry_activate_cb" : self.cc_entry_cb,
				"rb_magnatune_mm_entry_changed_cb" : self.mm_entry_cb,
				"rb_magnatune_mm_entry_activate_cb" : self.mm_entry_cb,
				"rb_magnatune_yy_entry_changed_cb" : self.yy_entry_cb,
				"rb_magnatune_yy_entry_activate_cb" : self.yy_entry_cb,
				"rb_magnatune_pay_combobox_changed_cb" : self.pay_combobox_cb,
				"rb_magnatune_audio_combobox_changed_cb" : self.audio_combobox_cb
				}
		
	def activate(self, shell):
		self.shell = shell # so buy_track can update the progress bar
		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)
		self.source.info_screen = gtk.Label()
		
		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)
		ev = self.source.get_entry_view()
		ev.connect_object("show_popup", self.show_popup_cb, self.source)
		shell.append_source(self.source, None) # Add the source to the list
		
		manager = shell.get_player().get_property('ui-manager')
		action = gtk.Action('MagnatunePurchase', _('Purchase Album'),
				_("Purchase this album from Magnatune"),
				'gnome-mime-audio')
		action.connect('activate', self.confirm_track_purchase)
		self.action_group = gtk.ActionGroup('MagnatunePluginActions')
		self.action_group.add_action(action)
		manager.insert_action_group(self.action_group, 0)
		self.ui_id = manager.add_ui_from_string(popup_ui)
		manager.ensure_update()
		
		self.parser = xml.sax.make_parser()
		self.parser.setContentHandler(TrackListHandler(self.db, self.entry_type))
		if check_info():
			gobject.idle_add(self.idle_download_info)
		else:
			gobject.idle_add(self.idle_load_info)
		gobject.timeout_add(6 * 60 * 60 * 1000, self.check_info_updates) # every 6 hours.
	
	def deactivate(self, shell):
		# FIXME: should remove the ui stuff here
		self.db.entry_delete_by_type(self.entry_type)
		self.db.commit()
		self.source.delete_thyself()
		self.source = None
	
	def create_configure_dialog(self):
		client = gconf.client_get_default()
		
		if self.configure_dialog == None:
			self.configure_dialog_xml = gtk.glade.XML(os.path.join(file_locn, "magnatune-prefs.glade"), root="magnatune_vbox")
			self.configure_dialog_xml.signal_autoconnect(self.configure_callback_dic)
			self.configure_dialog = gtk.Dialog(title="Magnatune Preferences", flags=gtk.DIALOG_DESTROY_WITH_PARENT,
							buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
			
			self.configure_dialog.set_border_width(5)
			self.configure_dialog.vbox.set_spacing(2)
			self.configure_dialog.set_has_separator(False)
			self.configure_dialog.connect("response", self.prefs_response_cb)
			self.configure_dialog.hide_on_delete()
			self.configure_dialog.vbox.pack_start(self.configure_dialog_xml.get_widget("magnatune_vbox"))
			
			self.name_entry = self.configure_dialog_xml.get_widget("name_entry")
			self.email_entry = self.configure_dialog_xml.get_widget("email_entry")
			self.cc_entry = self.configure_dialog_xml.get_widget("cc_entry")
			self.mm_entry = self.configure_dialog_xml.get_widget("mm_entry")
			self.yy_entry = self.configure_dialog_xml.get_widget("yy_entry")
			self.pay_combobox = self.configure_dialog_xml.get_widget("pay_combobox")
			self.audio_combobox = self.configure_dialog_xml.get_widget("audio_combobox")
		
		self.set_pref_values()
		self.configure_dialog.show()
		return self.configure_dialog
	
	
	#
	# Callback/helper functions
	#
	
	def show_popup_cb(self, source, entry_clicked):
		entry_view = source.get_entry_view()
		self.sku = sku_dict[self.db.entry_get(entry_view.get_selected_entries()[0], rhythmdb.PROP_LOCATION)]
		self.artist = self.db.entry_get(entry_view.get_selected_entries()[0], rhythmdb.PROP_ARTIST)
		self.album = self.db.entry_get(entry_view.get_selected_entries()[0], rhythmdb.PROP_ALBUM)
		source.show_popup()
	
	def confirm_track_purchase(self, action):
		callbacks = {
			"rb_magnatune_purchase_button_clicked_cb" : self.purchase_clicked,
			"rb_magnatune_cancel_button_clicked_cb" : self.cancel_clicked
			}
		client = gconf.client_get_default()
		self.pay = client.get_int("/apps/rhythmbox/plugins/magnatune/pay")
		self.audio = client.get_int("/apps/rhythmbox/plugins/magnatune/audio")
		
		self.confirm_window = gtk.glade.XML(os.path.join(file_locn, "magnatune-confirm.glade"))
		self.confirm_window.signal_autoconnect(callbacks)
		self.confirm_window.get_widget("pay_combobox").set_active(self.pay)
		self.confirm_window.get_widget("audio_combobox").set_active(self.audio)
		self.confirm_window.get_widget("info_label").set_text(_("You are about to purchase the album '%s' by '%s'.") % (self.album, self.artist))
		self.confirm_window.get_widget("confirm_window").show()
	
	def check_info_updates(self): # TODO: if possible, make it so that the updated file is downloaded first, then the entries are switched
		if check_info():
			self.db.entry_delete_by_type(self.entry_type)
			self.db.commit()
			gobject.idle_add(self.idle_download_info)
		return True
	
	def idle_load_info(self):
		if self.info_file == None:
			self.info_file = gnomevfs.open(local_song_info_uri)
		try:
			self.parser.feed(self.info_file.read(128 * 1024))
			return True
		except gnomevfs.EOFError:
			self.info_file.close()
			self.info_file = None
			self.source.info_screen.hide()
			self.source.info_screen.destroy()
			self.source.info_screen = gtk.Label()
			self.source.get_entry_view().show()
			return False
	
	def idle_download_info(self):
		if self.info_file == None:
			self.remote_info_file = gnomevfs.open(magnatune_song_info_uri)
			self.info_file_length = self.remote_info_file.get_file_info().size
			self.downloaded = 0
			self.info_file = gnomevfs.create(local_song_info_uri, open_mode=gnomevfs.OPEN_WRITE)
			self.source.get_entry_view().hide()
			self.source.info_screen = gtk.Label()
			self.source.info_screen.set_markup(info_loading)
			self.source.pack_start(self.source.info_screen)
			self.source.info_screen.show()
		try:
			data = self.remote_info_file.read(64 * 1024)
			self.info_file.write(data)
			self.downloaded += len(data)
			progress = int(self.downloaded / float(self.info_file_length) * 100)
			self.source.info_screen.set_markup(info_loading + str(progress) + "%")
			return True
		except gnomevfs.EOFError:
			self.remote_info_file.close()
			self.info_file.close()
			self.remote_info_file = None
			self.info_file = None
			gobject.idle_add(self.idle_load_info)
			return False
	
	#
	# Purchasing Code
	#
	
	def buy_track(self, sku, amount, cc, name, email, format): # 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':	sku,
						'amount': amount,
						'cc':	cc['number'],
						'yy':	cc['year'],
						'mm':	cc['month'],
						'name': name,
						'email':email
					})
		
		buy_album_handler = BuyAlbumHandler(format) # so we can get the url and auth info
		parser = xml.sax.make_parser()
		parser.setContentHandler(buy_album_handler)
		data = gnomevfs.read_entire_file(url).replace("<br>", "") # get rid of that single pesky <br> tag
		parser.feed(data)
		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)
		
		library_location = client.get_list("/apps/rhythmbox/library_locations", gconf.VALUE_STRING)[0] # Just use the first library location
		to_file_uri = gnomevfs.URI(library_location + "/" + audio_dl_uri.short_name)
		
		dl_file = gnomevfs.open(audio_dl_uri)
		to_file = create_if_needed(to_file_uri, gnomevfs.OPEN_WRITE)
		gobject.idle_add(self.transfer_album, dl_file, to_file, to_file_uri, library_location)
	
	def transfer_album(self, dl_file, to_file, to_file_uri, library_location):
	# Download the album and unzip it into the library
		try:
			if self.purchase_filesize == 0:
				callbacks = {"rb_magnatune_cancel_button_clicked_cb" : self.dl_cancel_clicked,
					     "on_download_window_delete_event"	:	self.close_clicked}
				self.purchase_downloaded = 0
				self.purchase_filesize = dl_file.get_file_info().size
				
				self.progress_dialog = gtk.glade.XML(os.path.join(file_locn, "magnatune-download.glade"))
				self.progress_dialog.signal_autoconnect(callbacks)
				self.cancelled = False
				self.progress_dialog.get_widget("download_window").show()
			
			if self.cancelled:
				dl_file.close()
				to_file.close()
				gnomevfs.unlink(to_file_uri)
				self.progress_dialog.get_widget("download_window").destroy()
				self.progress_dialog = None
				self.purchase_filesize = 0
				return False
			
			data = dl_file.read(64 * 1024)
			to_file.write(data)
			self.purchase_downloaded += len(data)
			purchase_fraction = self.purchase_downloaded / float(self.purchase_filesize)
			self.progress_dialog.get_widget("download_pbar").set_fraction(purchase_fraction)
			self.progress_dialog.get_widget("download_pbar").set_text(str(int(purchase_fraction * 100)) + "%")
			return True
		except gnomevfs.EOFError:
			dl_file.close()
			to_file.close()
			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(to_file_uri)
			self.progress_dialog.get_widget("download_window").destroy()
			self.progress_dialog = None
			self.purchase_filesize = 0
			self.db.add_uri("file://" + urllib.quote(track_uri.dirname))
			return False
	
	def close_clicked(self, dialog, response):
		self.cancelled = True
	def dl_cancel_clicked(self, dialog):
		self.cancelled = True
	
	#
	# Preferences window callbacks
	#
	
	def name_entry_cb(self, widget):
		client = gconf.client_get_default()
		client.set_string("/apps/rhythmbox/plugins/magnatune/name", widget.get_text())
	def email_entry_cb(self, widget):
		client = gconf.client_get_default()
		client.set_string("/apps/rhythmbox/plugins/magnatune/email", widget.get_text())
	def cc_entry_cb(self, widget):
		client = gconf.client_get_default()
		client.set_string("/apps/rhythmbox/plugins/magnatune/cc", widget.get_text())
	def mm_entry_cb(self, widget):
		client = gconf.client_get_default()
		client.set_string("/apps/rhythmbox/plugins/magnatune/mm", widget.get_text())
	def yy_entry_cb(self, widget):
		client = gconf.client_get_default()
		client.set_string("/apps/rhythmbox/plugins/magnatune/yy", widget.get_text())
	def pay_combobox_cb(self, widget):
		client = gconf.client_get_default()
		client.set_int("/apps/rhythmbox/plugins/magnatune/pay", widget.get_active())
	def audio_combobox_cb(self, widget):
		client = gconf.client_get_default()
		client.set_int("/apps/rhythmbox/plugins/magnatune/audio", widget.get_active())
	def prefs_response_cb(self, dialog, response):
		dialog.hide()
	
	def set_pref_values(self):
		client = gconf.client_get_default()
		
		if client.get_string("/apps/rhythmbox/plugins/magnatune/name") == None:
			client.set_string("/apps/rhythmbox/plugins/magnatune/name", "")
			client.set_string("/apps/rhythmbox/plugins/magnatune/email", "")
			client.set_string("/apps/rhythmbox/plugins/magnatune/cc", "")
			client.set_string("/apps/rhythmbox/plugins/magnatune/mm", "")
			client.set_string("/apps/rhythmbox/plugins/magnatune/yy", "")
			client.set_int("/apps/rhythmbox/plugins/magnatune/pay", 3)
			client.set_int("/apps/rhythmbox/plugins/magnatune/audio", 0)
		
		self.name_entry.set_text(client.get_string("/apps/rhythmbox/plugins/magnatune/name"))
		self.email_entry.set_text(client.get_string("/apps/rhythmbox/plugins/magnatune/email"))
		self.cc_entry.set_text(client.get_string("/apps/rhythmbox/plugins/magnatune/cc"))
		self.mm_entry.set_text(client.get_string("/apps/rhythmbox/plugins/magnatune/mm"))
		self.yy_entry.set_text(client.get_string("/apps/rhythmbox/plugins/magnatune/yy"))
		self.pay_combobox.set_active(client.get_int("/apps/rhythmbox/plugins/magnatune/pay"))
		self.audio_combobox.set_active(client.get_int("/apps/rhythmbox/plugins/magnatune/audio"))
	
	def purchase_clicked(self, widget):
		client = gconf.client_get_default()
		format_list = ['ogg', 'flac', 'wav', 'mp3-vbr', 'mp3-cbr']
		cc = {}
		cc['number'] = client.get_string("/apps/rhythmbox/plugins/magnatune/cc")
		cc['year'] = client.get_string("/apps/rhythmbox/plugins/magnatune/yy")
		cc['month'] = client.get_string("/apps/rhythmbox/plugins/magnatune/mm")
		name = client.get_string("/apps/rhythmbox/plugins/magnatune/name")
		email = client.get_string("/apps/rhythmbox/plugins/magnatune/email")
		
		amount = self.confirm_window.get_widget("pay_combobox").get_active() + 5
		format = format_list[self.confirm_window.get_widget("audio_combobox").get_active()]
		self.confirm_window.get_widget("confirm_window").destroy()
		dlg = gtk.Dialog(title="Connecting: Please Wait")
		lbl = gtk.Label("Connecting to the Magnatune server: please wait...")
		dlg.vbox.pack_start(lbl)
		lbl.show()
		dlg.show()
		dlg.set_modal(True)
		try:
			self.buy_track(self.sku, amount, cc, name, email, format)
			dlg.destroy()
		except MagnatuneError, e:
			dlg.destroy()
			error_dlg = gtk.Dialog(title="Error", flags=gtk.DIALOG_DESTROY_WITH_PARENT, buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK))
			label = gtk.Label(_("An error occurred while trying to purchase the album.\nThe Magnatune server returned:\n%s") % str(e))
			error_dlg.vbox.pack_start(label)
			label.set_selectable(True)
			label.show()
			error_dlg.connect("response", self.ok_clicked)
			error_dlg.show()
	
	def ok_clicked(self, dialog, response):
		dialog.destroy()
	
	def cancel_clicked(self, widget):
		self.confirm_window.get_widget("confirm_window").destroy()

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

gobject.type_register(MagnatuneSource)

################################################
# Methods for downloading the song info        #
################################################

def check_info():
# returns whether or not info has changed
	if not gnomevfs.exists(magnatune_dir_uri):
		gnomevfs.make_directory(magnatune_dir_uri, 0755)
	if not gnomevfs.exists(lc_uri):
		t = gnomevfs.create(lc_uri, open_mode=gnomevfs.OPEN_WRITE)
		t.write("never") # there needs to be something in the file, otherwise it throws an exception when read from
		t.close()
	modified = str(gnomevfs.get_file_info(magnatune_song_info_uri).mtime)
	lc_file = gnomevfs.open(lc_uri)
	last_changed = lc_file.read(100) # file should be less than 100 chars
	lc_file.close()
	if not last_changed.strip() == modified.strip():
		lc_file = gnomevfs.open(lc_uri, open_mode=gnomevfs.OPEN_WRITE)
		lc_file.write(modified)
		lc_file.close()
		return True
	return False

################################################
# Purchasing code - XML stuff.                 #
################################################

class BuyAlbumHandler(xml.sax.handler.ContentHandler): # Class to download the track, etc.
	
	format_map =	{
			'ogg'		:	'URL_OGGZIP',
			'flac'		:	'URL_FLACZIP',
			'wav'		:	'URL_WAVZIP',
			'mp3-cbr'	:	'URL_128KMP3ZIP',
			'mp3-vbr'	:	'URL_VBRZIP'
			}
	
	def __init__(self, format):
		xml.sax.handler.ContentHandler.__init__(self)
		self._format_tag = self.format_map[format] # format of audio to download
	
	def startElement(self, name, attrs):
		self._text = ""
	
	def endElement(self, name):
		if name == "ERROR": # Something went wrong. Display error message to user.
			raise MagnatuneError(self._text)
		elif name == "DL_USERNAME":
			self.username = self._text
		elif name == "DL_PASSWORD":
			self.password = self._text
		elif name == self._format_tag:
			self.url = self._text
	
	def characters(self, content):
		self._text = self._text + content

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 MagnatuneError(Exception):
	pass

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)

def progress_info_cb(info, data):
	assert data == 0x1234
	try:
		print "%s: %f %%\r" % (info.target_name,
			info.bytes_copied/float(info.bytes_total)*100),
	except Exception, ex: # Sometimes the method throws an exception, for no apparent reason
		pass
	return True

################################################
# Magnatune Logo.                              #
################################################

# (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
"................................",
"................................",
"................................",
"................................",
"................................",
"................................",
"............++@@@@++............",
"..........+@@@@@@@@@@+..........",
".........+@@@+....+@@@+.........",
"........+@@+...++...+@@+........",
".......+@@+....@@....+@@+.......",
".......@@+.....@@.....+@@.......",
"......+@@......@@......@@+......",
"......+@+......@@......+@+......",
"......@@...@@..@@..@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"......@@...@@+.@@.+@@...@@......",
"......+@+..@@+.@@.+@@..+@+......",
"......+@@..@@+.@@.+@@..@@+......",
".......@@+.@@+.@@.+@@.+@@.......",
".......+@@+.+..+...+.+@@+.......",
"........+@@+........+@@+........",
".........+@@@+....+@@@+.........",
"..........+@@@@@@@@@@+..........",
"............++@@@@++............",
"................................",
"................................",
"................................",
"................................",
"................................",
"................................"
]

info_loading = _("""<b>Loading Magnatune Catalog</b>

Magnatune is an online record label that is not evil. Some of their key attributes are:

    * Free listening of all songs
    * All albums and artists hand-picked
    * Very simple user interface, quick to play music
    * Radio stations and "genre mix" playlists allow background listening - can do work while listening to our music
    * No need to "register" to listen or buy
    * Smaller selection means easier to find good music
    * Wide variety of genres, can fit any mood
    * Our genres are hard to find in record stores and not on radio (though do appear on college radio)
    * Downloads and CDs are both available (no other site on the internet sells both)
    * Perfect quality downloads (CD copy) are available when you download (not inferior quality sound)
    * No copy protection on the music (DRM) which allows playing music on any device (unlike iTunes/MSN/etc)
    * Extensive biographical info about each musician, and artist photo -- feel a strong connection to the artist
    * Full color, high quality cover art PDF available for most albums - easy to print
    * Variable pricing scheme means you can pay as little as $5 for an album if you choose
    * 50% of payment goes to artist (makes buyer feel good: they're helping the world)
    * Not venture-capital backed big business
    * Not part of the "evil" major label machine - for those that hate the music biz and want to help topple it
    * Founder/owner runs it -- support a small business
    * Low pressure environment - nothing flashing, no audio ads while listening to albums
    * Music selection is unique to Magnatune, unlike most on-line stores that have more-or-less the same (gigantic) selection

You can find more information at http://www.magnatune.com/


""")
