rhythmbox r6158 - in trunk: . bindings/python lib plugins/artdisplay/artdisplay plugins/coherence/upnp_coherence plugins/jamendo/jamendo plugins/lyrics/lyrics plugins/magnatune/magnatune plugins/rb



Author: jmatthew
Date: Sun Feb 22 10:50:19 2009
New Revision: 6158
URL: http://svn.gnome.org/viewvc/rhythmbox?rev=6158&view=rev

Log:
2009-02-22  Jonathan Matthew  <jonathan d14n org>

	* plugins/rb/Loader.py:
	* plugins/rb/__init__.py:
	Restructure, rewrite, add two new classes: one for loading URI
	contents by chunks (for downloading large files, progressively parsing
	huge xml documents, etc.), one for performing file modification time
	checks.  Implement all of these using both GIO and gnome-vfs.  GIO
	implementations require pygobject 2.16.0.  The loader classes are now
	intended to be single use (they have state) and operations are
	cancellable.

	Add a helper function to use gtk.show_uri() if available, falling back
	to gnome-vfs otherwise.

	* plugins/lyrics/lyrics/__init__.py:
	* plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py:
	* plugins/artdisplay/artdisplay/LocalCoverArtSearch.py:
	* plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py:
	* plugins/artdisplay/artdisplay/__init__.py:
	Rewrite various bits to create loader instances as required, rather
	than sharing them between submodules.
	
	* plugins/artdisplay/artdisplay/Makefile.am:
	* plugins/artdisplay/artdisplay/LocalCoverArtSearchGIO.py:
	* plugins/artdisplay/artdisplay/CoverArtDatabase.py:
	Add a GIO implementation of the local cover art search.  Requires GIO
	2.15.3 or newer.
	
	* bindings/python/rb.defs:
	* bindings/python/rb.override:
	* lib/rb-file-helpers.c: (rb_find_user_file),
	(rb_find_user_data_file), (rb_find_user_cache_file):
	* lib/rb-file-helpers.h:
	Add function for migrating user cache files from ~/.gnome2 to XDG
	cache directory, add python bindings for various directory helper
	functions.

	* plugins/jamendo/jamendo/JamendoConfigureDialog.py:
	* plugins/jamendo/jamendo/JamendoSource.py:
	Rewrite the catalogue downloading and parsing stuff to use the new
	loader classes.  Rework the download/parse logic a bit so we don't
	parse the catalogue twice if we're downloading a new copy on startup.

	* plugins/magnatune/magnatune/MagnatuneSource.py:
	Rewrite all the catalogue and purchasing code using the new loader
	classes.  The purchasing code is completely untested, but at least the
	idea is right as far as I can tell.
	
	* plugins/coherence/upnp_coherence/__init__.py:
	Add code to get the mime type of the icon using gio, falling back to
	gnome-vfs if that doesn't work

	While gnome-vfs code remains, it's only there as a fallback.
	Fixes #510392.



Added:
   trunk/plugins/artdisplay/artdisplay/LocalCoverArtSearchGIO.py
Modified:
   trunk/ChangeLog
   trunk/bindings/python/rb.defs
   trunk/bindings/python/rb.override
   trunk/lib/rb-file-helpers.c
   trunk/lib/rb-file-helpers.h
   trunk/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py
   trunk/plugins/artdisplay/artdisplay/CoverArtDatabase.py
   trunk/plugins/artdisplay/artdisplay/LocalCoverArtSearch.py
   trunk/plugins/artdisplay/artdisplay/Makefile.am
   trunk/plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py
   trunk/plugins/artdisplay/artdisplay/__init__.py
   trunk/plugins/coherence/upnp_coherence/__init__.py
   trunk/plugins/jamendo/jamendo/JamendoConfigureDialog.py
   trunk/plugins/jamendo/jamendo/JamendoSource.py
   trunk/plugins/lyrics/lyrics/__init__.py
   trunk/plugins/magnatune/magnatune/MagnatuneSource.py
   trunk/plugins/rb/Loader.py
   trunk/plugins/rb/__init__.py

Modified: trunk/bindings/python/rb.defs
==============================================================================
--- trunk/bindings/python/rb.defs	(original)
+++ trunk/bindings/python/rb.defs	Sun Feb 22 10:50:19 2009
@@ -2430,6 +2430,18 @@
 
 ;; From rb-file-helpers.h
 
+(define-function music_dir
+  (in-module "rb")
+  (c-name "rb_music_dir")
+  (return-type "const-char*")
+)
+
+(define-function dot_dir
+  (in-module "rb")
+  (c-name "rb_dot_dir")
+  (return-type "const-char*")
+)
+
 (define-function user_data_dir
   (in-module "rb")
   (c-name "rb_user_data_dir")
@@ -2442,4 +2454,23 @@
   (return-type "const-char*")
 )
 
+(define-function find_user_data_file
+  (in-module "rb")
+  (c-name "rb_find_user_data_file")
+  (return-type "char*")
+  (parameters
+    '("const-char*" "name")
+    '("GError**" "error")
+  )
+)
+
+(define-function find_user_cache_file
+  (in-module "rb")
+  (c-name "rb_find_user_cache_file")
+  (return-type "char*")
+  (parameters
+    '("const-char*" "name")
+    '("GError**" "error")
+  )
+)
 

Modified: trunk/bindings/python/rb.override
==============================================================================
--- trunk/bindings/python/rb.override	(original)
+++ trunk/bindings/python/rb.override	Sun Feb 22 10:50:19 2009
@@ -16,6 +16,7 @@
 #include "rb-cut-and-paste-code.h"
 #include "rb-dialog.h"
 #include "rb-entry-view.h"
+#include "rb-file-helpers.h"
 #include "rb-library-browser.h"
 #include "rb-playlist-source.h"
 #include "rb-player.h"

Modified: trunk/lib/rb-file-helpers.c
==============================================================================
--- trunk/lib/rb-file-helpers.c	(original)
+++ trunk/lib/rb-file-helpers.c	Sun Feb 22 10:50:19 2009
@@ -166,24 +166,10 @@
 	return dir;
 }
 
-/**
- * rb_find_user_data_file:
- * @name: name of file to find
- * @error: returns error information
- *
- * Determines the full path to use for user-specific files, such as rhythmdb.xml.
- * This first checks in the user data directory (see @rb_user_data_dir).
- * If the file does not exist in the user data directory, it then checks the
- * old .gnome2 directory, moving the file to the user data directory if found there.
- * If an error occurs while moving the file, this will be reported through @error 
- * and the .gnome2 path will be returned.
- *
- * Returns: allocated string containing the location of the file to use, even if
- *  an error occurred.
- */
-char *
-rb_find_user_data_file (const char *name,
-			GError **error)
+static char *
+rb_find_user_file (const char *dir,
+		   const char *name,
+		   GError **error)
 {
 	GError *temp_err = NULL;
 	char *srcpath;
@@ -192,23 +178,23 @@
 	GFile *dest;
 	char *use_path;
 
-	/* if the file exists in the user data dir, return the path */
-	destpath = g_build_filename (rb_user_data_dir (), name, NULL);
+	/* if the file exists in the target dir, return the path */
+	destpath = g_build_filename (dir, name, NULL);
 	dest = g_file_new_for_path (destpath);
 	if (g_file_query_exists (dest, NULL) == TRUE) {
 		g_object_unref (dest);
-		rb_debug ("found user data dir path for '%s': %s", name, destpath);
+		rb_debug ("found user dir path for '%s': %s", name, destpath);
 		return destpath;
 	}
 
-	/* doesn't exist in the user data dir, so try to move it from the .gnome2 dir */
+	/* doesn't exist in the target dir, so try to move it from the .gnome2 dir */
 	srcpath = g_build_filename (rb_dot_dir (), name, NULL);
 	src = g_file_new_for_path (srcpath);
 
 	if (g_file_query_exists (src, NULL)) {
 		g_file_move (src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, &temp_err);
 		if (temp_err != NULL) {
-			rb_debug ("failed to move user data file '%s' from .gnome2 dir, returning .gnome2 path %s: %s",
+			rb_debug ("failed to move user file '%s' from .gnome2 dir, returning .gnome2 path %s: %s",
 				  name, srcpath, temp_err->message);
 
 			use_path = g_file_get_path (src);
@@ -219,12 +205,12 @@
 				     srcpath, destpath, temp_err->message);
 			g_error_free (temp_err);
 		} else {
-			rb_debug ("moved user data file '%s' from .gnome2 dir, returning user data dir path %s",
+			rb_debug ("moved user file '%s' from .gnome2 dir, returning user dir path %s",
 				  name, destpath);
 			use_path = g_file_get_path (dest);
 		}
 	} else {
-		rb_debug ("no existing file for '%s', returning user data dir path %s", name, destpath);
+		rb_debug ("no existing file for '%s', returning user dir path %s", name, destpath);
 		use_path = g_file_get_path (dest);
 	}
 
@@ -237,6 +223,50 @@
 	return use_path;
 }
 
+/**
+ * rb_find_user_data_file:
+ * @name: name of file to find
+ * @error: returns error information
+ *
+ * Determines the full path to use for user-specific files, such as rhythmdb.xml.
+ * This first checks in the user data directory (see @rb_user_data_dir).
+ * If the file does not exist in the user data directory, it then checks the
+ * old .gnome2 directory, moving the file to the user data directory if found there.
+ * If an error occurs while moving the file, this will be reported through @error 
+ * and the .gnome2 path will be returned.
+ *
+ * Returns: allocated string containing the location of the file to use, even if
+ *  an error occurred.
+ */
+char *
+rb_find_user_data_file (const char *name,
+			GError **error)
+{
+	return rb_find_user_file (rb_user_data_dir (), name, error);
+}
+
+/**
+ * rb_find_user_cache_file:
+ * @name: name of file to find
+ * @error: returns error information
+ *
+ * Determines the full path to use for user-specific cached files.
+ * This first checks in the user cache directory (see @rb_user_cache_dir).
+ * If the file does not exist in the user cache directory, it then checks the
+ * old .gnome2 directory, moving the file to the user cache directory if found there.
+ * If an error occurs while moving the file, this will be reported through @error 
+ * and the .gnome2 path will be returned.
+ *
+ * Returns: allocated string containing the location of the file to use, even if
+ *  an error occurred.
+ */
+char *
+rb_find_user_cache_file (const char *name,
+			 GError **error)
+{
+	return rb_find_user_file (rb_user_cache_dir (), name, error);
+}
+
 void
 rb_file_helpers_init (void)
 {

Modified: trunk/lib/rb-file-helpers.h
==============================================================================
--- trunk/lib/rb-file-helpers.h	(original)
+++ trunk/lib/rb-file-helpers.h	Sun Feb 22 10:50:19 2009
@@ -43,6 +43,8 @@
 
 char *		rb_find_user_data_file	(const char *name,
 					 GError **error);
+char *		rb_find_user_cache_file	(const char *name,
+					 GError **error);
 
 char *		rb_canonicalise_uri	(const char *uri);
 

Modified: trunk/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py
==============================================================================
--- trunk/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py	(original)
+++ trunk/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py	Sun Feb 22 10:50:19 2009
@@ -29,6 +29,7 @@
 import locale
 import urllib
 
+import rb
 import rhythmdb
 
 LICENSE_KEY = "18C3VZN9HCECM5G3HQG2"
@@ -43,10 +44,9 @@
 class Bag: pass
 
 class AmazonCoverArtSearch (object):
-	def __init__ (self, loader):
+	def __init__ (self):
 		self.searching = False
 		self.cancel = False
-		self.loader = loader
 		self.db = None
 		self.entry = None
 		(self.tld, self.encoding) = self.__get_locale ()
@@ -168,7 +168,8 @@
 			job += 1
 
 		# Retrieve search for keyword
-		self.loader.get_url (url, self.on_search_response)
+		l = rb.Loader()
+		l.get_url (url, self.on_search_response)
 		return True
 
 	def __unmarshal (self, element):

Modified: trunk/plugins/artdisplay/artdisplay/CoverArtDatabase.py
==============================================================================
--- trunk/plugins/artdisplay/artdisplay/CoverArtDatabase.py	(original)
+++ trunk/plugins/artdisplay/artdisplay/CoverArtDatabase.py	Sun Feb 22 10:50:19 2009
@@ -33,10 +33,18 @@
 
 from PodcastCoverArtSearch import PodcastCoverArtSearch
 from AmazonCoverArtSearch import AmazonCoverArtSearch
-from LocalCoverArtSearch import LocalCoverArtSearch
 
 from urllib import unquote
 
+try:
+	# try to use the gio implementation, fall back to gnome-vfs if that
+	# isn't available.
+	import gio
+	if gio.pygio_version > (2,15,2):	# probably
+		from LocalCoverArtSearchGIO import LocalCoverArtSearch
+except:
+	from LocalCoverArtSearch import LocalCoverArtSearch
+
 ART_SEARCHES_LOCAL = [LocalCoverArtSearch]
 ART_SEARCHES_REMOTE = [PodcastCoverArtSearch, AmazonCoverArtSearch]
 OLD_ART_FOLDER = '~/.gnome2/rhythmbox/covers'
@@ -88,7 +96,6 @@
 
 class CoverArtDatabase (object):
 	def __init__ (self):
-		self.loader = rb.Loader()
 		self.ticket = TicketSystem ()
 
 	def build_art_cache_filename (self, db, entry, extension):
@@ -109,15 +116,17 @@
 
 	def engines (self, blist):
 		for Engine in ART_SEARCHES_LOCAL:
-			yield Engine (self.loader), Engine.__name__, False
+			yield Engine (), Engine.__name__, False
 		for Engine in ART_SEARCHES_REMOTE:
 			if Engine.__name__ not in blist:
-				yield Engine (self.loader), Engine.__name__, True
+				yield Engine (), Engine.__name__, True
 	
 	def set_pixbuf_from_uri (self, db, entry, uri, callback):
 		def loader_cb (data):
 			self.set_pixbuf (db, entry, self.image_data_load (data), callback)
-		self.loader.get_url (str (uri), loader_cb)
+
+		l = rb.Loader()
+		l.get_url (str (uri), loader_cb)
 
 	def set_pixbuf (self, db, entry, pixbuf, callback):
 		if entry is None or pixbuf is None:
@@ -135,7 +144,7 @@
 		callback (entry, pixbuf, art_location)
 		for Engine in ART_SEARCHES_LOCAL:
 			try:
-				Engine (self.loader).save_pixbuf (db, entry, pixbuf)
+				Engine ().save_pixbuf (db, entry, pixbuf)
 			except AttributeError:
 				pass
 
@@ -191,7 +200,8 @@
 						print "got empty url from engine %s." % (engine)
 						continue
 
-					yield self.loader.get_url (str (url), plexer.send ())
+					l = rb.Loader()
+					yield l.get_url (str (url), plexer.send ())
 					_, (data, ) = plexer.receive ()
 					pixbuf = self.image_data_load (data)
 					if pixbuf:

Modified: trunk/plugins/artdisplay/artdisplay/LocalCoverArtSearch.py
==============================================================================
--- trunk/plugins/artdisplay/artdisplay/LocalCoverArtSearch.py	(original)
+++ trunk/plugins/artdisplay/artdisplay/LocalCoverArtSearch.py	Sun Feb 22 10:50:19 2009
@@ -48,8 +48,8 @@
 
 
 class LocalCoverArtSearch:
-	def __init__ (self, loader):
-		self.loader = loader
+	def __init__ (self):
+		pass
 
 	def _load_dir_cb (self, handle, files, exception, (results, on_search_completed_cb, entry, args)):
 		for f in files:

Added: trunk/plugins/artdisplay/artdisplay/LocalCoverArtSearchGIO.py
==============================================================================
--- (empty file)
+++ trunk/plugins/artdisplay/artdisplay/LocalCoverArtSearchGIO.py	Sun Feb 22 10:50:19 2009
@@ -0,0 +1,174 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- 
+#
+# Copyright (C) 2006 - Ed Catmur <ed catmur co uk>
+# Copyright (C) 2009 - Jonathan Matthew <jonathan d14n org>
+#
+# 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 os
+import rhythmdb
+import rb
+import gobject
+import gio
+
+IMAGE_NAMES = ["cover", "album", "albumart", ".folder", "folder"]
+ITEMS_PER_NOTIFICATION = 10
+ART_SAVE_NAME = 'Cover.jpg'
+ART_SAVE_FORMAT = 'jpeg'
+ART_SAVE_SETTINGS = {"quality": "100"}
+
+def file_root (f_name):
+	return os.path.splitext (f_name)[0].lower ()
+
+def shared_prefix_length (a, b):
+	l = 0
+	while a[l] == b[l]:
+		l = l+1
+	return l
+
+
+class LocalCoverArtSearch:
+	def __init__ (self):
+		pass
+
+	def _enum_dir_cb(self, fileenum, result, (results, on_search_completed_cb, entry, args)):
+		try:
+			files = fileenum.next_files_finish(result)
+			if files is None or len(files) == 0:
+				print "okay, done; got %d files" % len(results)
+				on_search_completed_cb(self, entry, results, *args)
+				return
+
+			for f in files:
+				ct = f.get_attribute_string("standard::fast-content-type")
+				if ct.startswith("image/") and f.get_attribute_boolean("access::can-read"):
+					results.append(f.get_name())	# hm
+
+			fileenum.next_files_async(ITEMS_PER_NOTIFICATION, callback = self._enum_dir_cb, user_data=(results, on_search_completed_cb, entry, args))
+		except Exception, e:
+			print "okay, probably done: %s" % e
+			on_search_completed_cb(self, entry, results, *args)
+
+	def search (self, db, entry, on_search_completed_cb, *args):
+
+		self.file = gio.File(entry.get_playback_uri())
+		if self.file.get_uri_scheme() in ('http','cdda'):
+			print 'not searching for local art for %s' % (self.file.get_uri())
+			on_search_completed_cb (self, entry, [], *args)
+			return
+
+		self.artist = db.entry_get (entry, rhythmdb.PROP_ARTIST)
+		self.album = db.entry_get (entry, rhythmdb.PROP_ALBUM)
+
+		print 'searching for local art for %s' % (self.file.get_uri())
+		parent = self.file.get_parent()
+		enumfiles = parent.enumerate_children(attributes="standard::fast-content-type,access::can-read,standard::name")
+		enumfiles.next_files_async(ITEMS_PER_NOTIFICATION, callback = self._enum_dir_cb, user_data=([], on_search_completed_cb, entry, args))
+
+	def search_next (self):
+		return False
+
+	def get_best_match_urls (self, results):
+		parent = self.file.get_parent()
+
+		# Compare lower case, without file extension
+		for name in [file_root (self.file.get_basename())] + IMAGE_NAMES:
+			for f_name in results:
+				if file_root (f_name) == name:
+					yield parent.resolve_relative_path(f_name).get_uri()
+
+		# look for file names containing the artist and album (case-insensitive)
+		# (mostly for jamendo downloads)
+		artist = self.artist.lower()
+		album = self.album.lower()
+		for f_name in results:
+			f_root = file_root (f_name).lower()
+			if f_root.find (artist) != -1 and f_root.find (album) != -1:
+				yield parent.resolve_relative_path(f_name).get_uri()
+
+		# if that didn't work, look for the longest shared prefix
+		# only accept matches longer than 2 to avoid weird false positives
+		match = (2, None)
+		for f_name in results:
+			pl = shared_prefix_length(f_name, self.file.get_basename())
+			if pl > match[0]:
+				match = (pl, f_name)
+
+		if match[1] is not None:
+			yield parent.resolve_relative_path(match[1]).get_uri()
+
+	def pixbuf_save (self, plexer, pixbuf, uri):
+		def pixbuf_cb(buf):
+			f = gio.File(uri)
+			f.replace_contents_async(buf, plexer.send())
+			yield None
+			_, (file, result) = plexer.receive()
+			try:
+				file.replace_contents_finish(result)
+			except Exception, e:
+				print "error creating \"%s\": %s" % (uri, e)
+
+		pixbuf.save_to_callback(pixbuf_cb, ART_SAVE_FORMAT, ART_SAVE_SETTINGS)
+
+	def _save_dir_cb (self, enum, result, (db, entry, dir, pixbuf)):
+		artist, album = [db.entry_get (entry, x) for x in [rhythmdb.PROP_ARTIST, rhythmdb.PROP_ALBUM]]
+		try:
+			files = enum.next_files_finish(result)
+			if len(files) == 0:
+				art_file = dir.resolve_relative_path(file.get_display_name())
+
+			for f in files:
+				ct = f.get_attribute_string("standard::fast-content-type")
+				if ct.startswith("image/") or ct.startswith("x-directory/"):
+					continue
+
+				uri = dir.resolve_relative_path(f.get_name())
+				u_entry = db.entry_lookup_by_location (uri)
+				if e_entry:
+					u_artist, u_album = [db.entry_get (u_entry, x) for x in [rhythmdb.PROP_ARTIST, rhythmdb.PROP_ALBUM]]
+					if album != u_album:
+						print "Not saving local art; encountered media with different album (%s, %s, %s)" % (uri, u_artist, u_album)
+						enum.close()
+						return
+					continue
+				print "Not saving local art; encountered unknown file (%s)" % uri
+				enum.close()
+				return
+		except Exception, e:
+			print "Error reading \"%s\": %s" % (dir, exception)
+
+	def save_pixbuf (self, db, entry, pixbuf):
+		uri = entry.get_playback_uri()
+		if uri is None or uri == '':
+			return
+
+		f = gio.File(uri)
+		if uri.get_uri_scheme() == 'http':
+			print "not saving local art for %s" % uri
+			return
+
+		print 'checking whether to save local art for %s' % uri
+		parent = f.get_parent()
+		enumfiles = parent.enumerate_children(attributes="standard::fast-content-type,access::can-read,standard::name")
+		enumfiles.next_files_async(ITEMS_PER_NOTIFICATION, callback = self._save_dir_cb, user_data=(db, entry, parent, pixbuf))
+

Modified: trunk/plugins/artdisplay/artdisplay/Makefile.am
==============================================================================
--- trunk/plugins/artdisplay/artdisplay/Makefile.am	(original)
+++ trunk/plugins/artdisplay/artdisplay/Makefile.am	Sun Feb 22 10:50:19 2009
@@ -5,5 +5,6 @@
 	PodcastCoverArtSearch.py	\
 	AmazonCoverArtSearch.py		\
 	LocalCoverArtSearch.py		\
+	LocalCoverArtSearchGIO.py	\
 	CoverArtDatabase.py		\
 	__init__.py

Modified: trunk/plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py
==============================================================================
--- trunk/plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py	(original)
+++ trunk/plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py	Sun Feb 22 10:50:19 2009
@@ -27,7 +27,7 @@
 import rhythmdb
 
 class PodcastCoverArtSearch (object):
-	def __init__ (self, loader):
+	def __init__ (self):
 		self.searching = False
 		self.cancel = False
 		self.entry = None

Modified: trunk/plugins/artdisplay/artdisplay/__init__.py
==============================================================================
--- trunk/plugins/artdisplay/artdisplay/__init__.py	(original)
+++ trunk/plugins/artdisplay/artdisplay/__init__.py	Sun Feb 22 10:50:19 2009
@@ -435,7 +435,8 @@
 					pass
 
 		print "got cover art URI notification: %s" % (uri)
-		rb.Loader().get_url (uri, loader_cb)
+		l = rb.Loader()
+		l.get_url (uri, loader_cb)
 
 	def cover_art_uri_request (self, db, entry):
 		if entry == self.current_entry:

Modified: trunk/plugins/coherence/upnp_coherence/__init__.py
==============================================================================
--- trunk/plugins/coherence/upnp_coherence/__init__.py	(original)
+++ trunk/plugins/coherence/upnp_coherence/__init__.py	Sun Feb 22 10:50:19 2009
@@ -15,7 +15,7 @@
 from coherence import log
 
 # For the icon
-import os.path, urllib, gnomevfs, gtk.gdk
+import os.path, urllib, gtk.gdk
 
 class CoherencePlugin(rb.Plugin,log.Loggable):
 
@@ -47,7 +47,16 @@
         face_path = os.path.join(os.path.expanduser('~'), ".face")
         if os.path.exists(face_path):
             url = "file://" + urllib.pathname2url(face_path)
-            mimetype = gnomevfs.get_mime_type(url)
+	    try:
+		    import gio
+		    f = gio.File(url)
+		    fi = f.query_info(gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE)
+		    ctype = fi.get_attribute_string(gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE)
+		    mimetype = gio.content_type_get_mime_type(ctype)
+	    except:
+		    import gnomevfs
+		    mimetype = gnomevfs.get_mime_type(url)
+
             pixbuf = gtk.gdk.pixbuf_new_from_file(face_path)
             width = "%s" % pixbuf.get_width()
             height = "%s" % pixbuf.get_height()

Modified: trunk/plugins/jamendo/jamendo/JamendoConfigureDialog.py
==============================================================================
--- trunk/plugins/jamendo/jamendo/JamendoConfigureDialog.py	(original)
+++ trunk/plugins/jamendo/jamendo/JamendoConfigureDialog.py	Sun Feb 22 10:50:19 2009
@@ -20,7 +20,7 @@
 
 import gobject
 import gtk, gtk.glade
-import gconf, gnomevfs, gnome
+import gconf, gnome
 
 gconf_keys = {	'format' : '/apps/rhythmbox/plugins/jamendo/format',
 		'sorting': '/apps/rhythmbox/plugins/jamendo/sorting'

Modified: trunk/plugins/jamendo/jamendo/JamendoSource.py
==============================================================================
--- trunk/plugins/jamendo/jamendo/JamendoSource.py	(original)
+++ trunk/plugins/jamendo/jamendo/JamendoSource.py	Sun Feb 22 10:50:19 2009
@@ -25,18 +25,18 @@
 from JamendoSaxHandler import JamendoSaxHandler
 import JamendoConfigureDialog
 
+import os.path
 import gobject
 import gtk.glade
-import gnomevfs, gnome, gconf
+import 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_artistalbumtrack.xml.gz";)
-local_song_info_uri = gnomevfs.URI(jamendo_dir + "dbdump.xml")
-local_song_info_temp_uri = gnomevfs.URI(jamendo_dir + "dbdump.xml.tmp")
+
+jamendo_song_info_uri = "http://img.jamendo.com/data/dbdump_artistalbumtrack.xml.gz";
+
 mp32_uri = "http://api.jamendo.com/get2/bittorrent/file/plain/?type=archive&class=mp32&album_id=";
 ogg3_uri = "http://api.jamendo.com/get2/bittorrent/file/plain/?type=archive&class=ogg3&album_id=";
 
@@ -64,22 +64,26 @@
 
 		rb.BrowserSource.__init__(self, name=_("Jamendo"))
 
-		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
 		self.__db_load_finished = False
 
+		self.__catalogue_loader = None
+		self.__catalogue_check = None
+
+		self.__jamendo_dir = rb.find_user_cache_file("jamendo")
+
+		self.__local_catalogue_path = os.path.join(self.__jamendo_dir, "dbdump.xml")
+		self.__local_catalogue_temp = os.path.join(self.__jamendo_dir, "dbdump.xml.tmp")
+
 	def do_set_property(self, property, value):
 		if property.name == 'plugin':
 			self.__plugin = value
@@ -127,7 +131,6 @@
 
 			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)
@@ -149,9 +152,13 @@
 			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
+		if self.__catalogue_loader:
+			self.__catalogue_loader.cancel()
+			self.__catalogue_loader = None
+
+		if self.__catalogue_check:
+			self.__catalogue_check.cancel()
+			self.__catalogue_check = 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)
@@ -160,136 +167,91 @@
 	#
 	# 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()
-					return False
-
-				if self.__db_load_finished is False:
-					gobject.idle_add (finish_loadscreen)
-					self.__db_load_finished = True
-			else:
-				# error reading file
-				raise exc_type
+
+	def __catalogue_chunk_cb(self, result, total, parser):
+		if result is None or isinstance (result, Exception):
+			if result:
+				# report error somehow?
+				print "error loading catalogue: %s" % result
 
 			parser.close()
-			handle.close(lambda handle, exc: None) # FIXME: report it?
-			self.__load_handle = None
+			self.__db_load_finished = True
 			self.__updating = False
-			self.__notify_status_changed()
- 		else:
-
-			parser.feed(data)
-			handle.read(64 * 1024, self.__load_catalogue_read_cb, parser)
+			self.__load_db ()
+			self.__show_loading_screen (False)
+			self.__catalogue_loader = None
+			return
 
+		parser.feed(result)
+		self.__load_current_size += len(result)
+		self.__load_total_size = total
 		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):
+		print "loading catalogue %s" % self.__local_catalogue_path
 		self.__notify_status_changed()
 		self.__db_load_finished = False
-		self.__load_handle = gnomevfs.async.open (local_song_info_uri, self.__load_catalogue_open_cb)
 
+		self.__saxHandler = JamendoSaxHandler()
+		parser = xml.sax.make_parser()
+		parser.setContentHandler(self.__saxHandler)
 
-	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()
+		self.__catalogue_loader = rb.ChunkLoader()
+		self.__catalogue_loader.get_url_chunks(self.__local_catalogue_path, 64*1024, True, self.__catalogue_chunk_cb, parser)
 
-		if info.phase == gnomevfs.XFER_PHASE_COMPLETED:
-			self.__xfer_handle = None
+
+	def __download_catalogue_chunk_cb (self, result, total, out):
+		if not result:
 			# 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 = gzip.open(self.__local_catalogue_temp)
+			out = open(self.__local_catalogue_path, 'w')
+
+			while True:
+				s = catalog.read(4096)
+				if s == "":
+					break
+				out.write(s)
+
 			out.close()
 			catalog.close()
-			gnomevfs.unlink(local_song_info_temp_uri)
-			self.__updating = False
-			self.__load_catalogue()
-		else:
-			#print info
+			os.unlink(self.__local_catalogue_temp)
+
+			self.__db_load_finished = True
+			self.__show_loading_screen (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
 
-		return 1
+		self.__notify_status_changed()
 
 	def __download_catalogue(self):
+		print "downloading catalogue"
 		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)
+		out = open(self.__local_catalogue_temp, 'w')
+
+		self.__catalogue_loader = rb.ChunkLoader()
+		self.__catalogue_loader.get_url_chunks(jamendo_song_info_uri, 4*1024, True, self.__download_catalogue_chunk_cb, out)
 
 	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
+		def update_cb (result):
+			self.__catalogue_check = None
+			if result is True:
+				self.__download_catalogue()
+			elif self.__db_load_finished is False:
+				self.__load_catalogue()
+
+		self.__catalogue_check = rb.UpdateCheck()
+		self.__catalogue_check.check_for_update(jamendo_song_info_uri, self.__local_catalogue_path, update_cb)
 
-		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:
@@ -389,14 +351,16 @@
 			formats["ogg3"] = ogg3_uri + albumid
 
 			p2plink = formats[format]
-			self.__loader.get_url(p2plink, self.__download_p2plink, albumid)
+			l = rb.Loader()
+			l.get_url(p2plink, self.__download_p2plink, albumid)
 
 	def __download_p2plink (self, result, albumid):
 		if result is None:
 			emsg = _("Error looking up p2plink for album %s on jamendo.com") % (albumid)
 			gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, emsg).run()
 			return
-		gnomevfs.url_show(result)
+
+		rb.show_uri(result)
 	
 	# Donate to Artist
 	def launch_donate (self):
@@ -410,20 +374,16 @@
 			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)
+
+			l = rb.Loader()
+			l.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
+		rb.show_uri(result + "donate/")
 
 	def playing_entry_changed (self, entry):
 		if not self.__db or not entry:
@@ -444,27 +404,3 @@
 
 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)

Modified: trunk/plugins/lyrics/lyrics/__init__.py
==============================================================================
--- trunk/plugins/lyrics/lyrics/__init__.py	(original)
+++ trunk/plugins/lyrics/lyrics/__init__.py	Sun Feb 22 10:50:19 2009
@@ -108,7 +108,6 @@
 
 class LyricGrabber(object):
 	def __init__(self, db, entry):
-		self.loader = rb.Loader ()
 		self.db = db
 		self.entry = entry
 		
@@ -128,7 +127,8 @@
 		status = self.verify_lyric()
 		
 		if status:
-			self.loader.get_url(self.cache_path, callback)
+			l = rb.Loader()
+			l.get_url(self.cache_path, callback)
 		else:
 			if cache_only:
 				self.callback(_("No lyrics found"))

Modified: trunk/plugins/magnatune/magnatune/MagnatuneSource.py
==============================================================================
--- trunk/plugins/magnatune/magnatune/MagnatuneSource.py	(original)
+++ trunk/plugins/magnatune/magnatune/MagnatuneSource.py	Sun Feb 22 10:50:19 2009
@@ -29,11 +29,14 @@
 from TrackListHandler import TrackListHandler
 from BuyAlbumHandler import BuyAlbumHandler, MagnatunePurchaseError
 
+import os
 import gobject
 import gtk.glade
-import gnomevfs, gnome, gconf
+import gnome, gconf
 import xml
-import urllib, zipfile
+import urllib
+import urlparse
+import zipfile
 
 has_gnome_keyring = False
 
@@ -47,10 +50,15 @@
 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")
+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):
@@ -76,11 +84,11 @@
 		self.__activated = False
 		self.__notify_id = 0
 		self.__update_id = 0
-		self.__xfer_handle = None
+		self.__catalogue_loader = None
+		self.__catalogue_check = 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
 
@@ -90,6 +98,7 @@
 		self.purchase_filesize = 0 # total amount of bytes to download
 
 
+
 	def do_set_property(self, property, value):
 		if property.name == 'plugin':
 			self.__plugin = value
@@ -129,9 +138,12 @@
 			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)
-			self.__load_catalogue()
 
 			# start our catalogue updates
 			self.__update_id = gobject.timeout_add(6 * 60 * 60 * 1000, self.__update_catalogue)
@@ -162,9 +174,13 @@
 			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
+		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())
 
@@ -182,7 +198,7 @@
 			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)
+				rb.show_uri(url)
 				urls.add(url)
 
 	def buy_cd(self):
@@ -193,7 +209,7 @@
 			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)
+				rb.show_uri(url)
 				urls.add(url)
 
 	def radio_toggled(self, gladexml):
@@ -281,62 +297,46 @@
 	#
 	# 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
+	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
+
+			parser.close ()
+			self.__show_loading_screen (False)
 			self.__updating = False
-			self.__notify_status_changed()
- 		else:
+			self.__catalogue_loader = None
 
-			parser.feed(data)
-			handle.read(64 * 1024, self.__load_catalogue_read_cb, parser)
+			# 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:
+			parser.feed(result)
+			self.__load_current_size += len(result)
+			self.__load_total_size = total
 
 		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
+	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))
-		handle.read (64 * 1024, self.__load_catalogue_read_cb, parser)
+		
+		self.__catalogue_loader = rb.ChunkLoader()
+		self.__catalogue_loader.get_url_chunks(magnatune_song_info, 64*1024, True, self.__catalogue_chunk_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():
@@ -344,76 +344,59 @@
 				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:
+	def __download_catalogue_chunk_cb (self, result, total, out):
+		if not result:
 			# 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)
+			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 catalogue", message=_("Rhythmbox could not understand the Magnatune catalogue, please file a bug."))
+				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)
+
+			os.unlink(magnatune_song_info_temp)
 			self.__updating = False
+			self.__catalogue_loader = None
 			self.__load_catalogue()
-			self.__xfer_handle = None
-		else:
-			#print info
+
+		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()
 
-		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)
-							  
+
+		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 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
+		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)
 
-		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:
@@ -457,137 +440,164 @@
 		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 = 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
+		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()
-			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))
+			# 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):
-			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]
+		fullpath = urlparse.urlparse(audio_dl_uri).path
+		basename = os.split(fullpath)[1]
+		destpath = os.path.join(magnatune_in_progress_dir, basename)
 
-			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
+		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 ()
 
-	def __purchase_download_progress_cb(self, info, data):
-		to_file_uri = data[0]
-		audio_dl_uri = data[1]
+		album = zipfile.ZipFile(dest)
+		for track in album.namelist():
+			track_uri = "file://" + urllib.pathname2url(os.path.join(library_dir, track))
 
-		if self.cancelled:
+			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[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
+				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 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
+			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
@@ -610,30 +620,29 @@
 		self.__db.emit_entry_extra_metadata_notify (entry, 'rb:coverArt-uri', url)
 		return False
 
-gobject.type_register(MagnatuneSource)
+	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_cache_dir, 0700)
+		os.mkdir(magnatune_in_progress_dir, 0700)
+
+		# move song info to cache dir
+		old_magnatune_dir = os.path.join(rb.dot_dir(), 'magnatune')
+
+		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)
 
-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)

Modified: trunk/plugins/rb/Loader.py
==============================================================================
--- trunk/plugins/rb/Loader.py	(original)
+++ trunk/plugins/rb/Loader.py	Sun Feb 22 10:50:19 2009
@@ -1,6 +1,6 @@
 # -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- 
 #
-# Copyright (C) 2006 - Jonathan Matthew
+# Copyright (C) 2009 - Jonathan Matthew
 #
 # 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
@@ -27,76 +27,298 @@
 import gobject
 import gtk
 
-use_gio = False
-try:
-	import gio
-	# before 2.15.5, file.load_contents_async didn't work correctly
-	if gio.pygio_version > (2,15,4):
-		use_gio = True
-except:
-	# probably don't have gio at all
-	pass
-
-if use_gio is False:
-	import gnomevfs
-
 def callback_with_gdk_lock(callback, data, args):
 	gtk.gdk.threads_enter()
-	callback(data, *args)
+	v = callback(data, *args)
 	gtk.gdk.threads_leave()
-	return False
+	return v
 
-class GioSrc(object):
+
+class GioLoader(object):
 	def __init__ (self):
-		pass
+		self.cancel = gio.Cancellable()
 
-	def _contents_cb(self, file, result, (callback, args)):
+	def _contents_cb (self, file, result):
 		try:
 			(contents, length, etag) = file.load_contents_finish(result)
-			callback_with_gdk_lock(callback, contents, args)
-		except Exception, e:
-			print "error getting file contents: %s" % e
-			callback_with_gdk_lock(callback, None, args)
+			callback_with_gdk_lock(self.callback, contents, self.args)
+		except gio.Error, e:
+			# somehow check if we just got cancelled
+			callback_with_gdk_lock(self.callback, None, self.args)
 
 	def get_url (self, url, callback, *args):
+		self.url = url
+		self.callback = callback
+		self.args = args
 		try:
 			file = gio.File(url)
-			file.load_contents_async(callback = self._contents_cb, user_data = (callback, args))
+			file.load_contents_async(callback = self._contents_cb, cancellable=self.cancel)
 		except Exception, e:
-			print "error getting file contents: %s" % e
+			print "error getting contents of %s: %s" % e
 			callback(None, *args)
 
+	def cancel (self):
+		self.cancel.cancel()
+
+
+class GioChunkLoader(object):
+	def __init__ (self):
+		self.cancel = gio.Cancellable()
+
+	def _callback(self, result):
+		return self.callback(result, self.total, *self.args)
+
+	def _callback_gdk(self, result):
+		gtk.gdk.threads_enter()
+		v = self._callback(result)
+		gtk.gdk.threads_leave()
+		return v
+
+	def _read_cb(self, stream, result):
+		try:
+			data = stream.read_finish(result)
+		except gio.Error, e:
+			print "error reading file %s: %s" % (self.uri, e.message)
+			stream.close()
+			self._callback_gdk(e)
+
+		if (self._callback_gdk(data) is not False) and data:
+			def again():
+				stream.read_async (self.chunksize, self._read_cb, cancellable=self.cancel)
+				return False
+			gobject.idle_add(again)
+		else:
+			# finished or cancelled by callback
+			stream.close()
+
+	def _open_cb(self, file, result):
+		try:
+			stream = file.read_finish(result)
+		except gio.Error, e:
+			print "error reading file %s: %s" % (self.uri, e.message)
+			self._callback_gdk(e)
+		
+		stream.read_async(self.chunksize, self._read_cb, cancellable=self.cancel)
+
+	def _info_cb(self, file, result):
+		try:
+			info = file.query_info_finish(result)
+			self.total = info.get_attribute_uint64(gio.FILE_ATTRIBUTE_STANDARD_SIZE)
+
+			file.read_async(self._open_cb, cancellable=self.cancel)
+		except gio.Error, e:
+			print "error checking size of source file %s: %s" % (self.uri, e.message)
+			self._callback_gdk(e)
+
+
+	def get_url_chunks (self, uri, chunksize, want_size, callback, *args):
+		try:
+			self.uri = uri
+			self.chunksize = chunksize
+			self.total = 0
+			self.callback = callback
+			self.args = args
+
+			file = gio.File(url)
+			if want_size:
+				file.query_info_async(gio.FILE_ATTRIBUTE_STANDARD_SIZE, self._info_cb, cancellable=self.cancel)
+			else:
+				file.read_async(self._open_cb, cancellable=self.cancel)
+		except gio.Error, e:
+			print "error reading file %s: %s" % (uri, e.message)
+			self._callback(e)
+
+
+class GioUpdateCheck(object):
+	def __init__ (self):
+		self.cancel = gio.Cancellable()
+
+	def _file_info_cb (self, result):
+		try:
+			rfi = file.query_info_finish(result)
+
+			remote_mod = rfi.get_attribute_uint64(gio.FILE_ATTRIBUTE_TIME_MODIFIED)
+			callback_with_gdk_lock(self.callback, remote_mod != self.local_mod, self.args)
+		except Exception, e:
+			print "error checking for update: %s" % e
+			callback_with_gdk_lock(self.callback, False, self.args)
+
+	def check_for_update (self, local, remote, callback, *args):
+		self.local = local
+		self.remote = remote
+		self.callback = callback
+		self.args = args
+
+		try:
+			lf = gio.File(local)
+			lfi = lf.query_info(gio.FILE_ATTRIBUTE_TIME_MODIFIED)
+			self.local_mod = lfi.get_attribute_uint64(gio.FILE_ATTRIBUTE_TIME_MODIFIED)
+
+			rf = gio.File(remote)
+			rf.query_info_async(gio.FILE_ATTRIBUTE_TIME_MODIFIED, self._file_info_cb, cancellable=self.cancel)
+		except Exception, e:
+			print "error checking for update: %s" % e
+			self.callback(True, self.args)
+
 
-class GnomeVFSAsyncSrc (object):  
+class GnomeVFSLoader (object):
 	def __init__ (self):
-	    	self.chunk = 4096
+		self.chunk = 4096
 
-	def read_cb (self, handle, buffer, exc_type, bytes_requested, (data, callback, args)):
+	def _read_cb (self, handle, buffer, exc_type, bytes_req):
 		if exc_type:
 			if issubclass (exc_type, gnomevfs.EOFError):
-				gobject.idle_add (callback_with_gdk_lock, callback, data, args)
+				callback_with_gdk_lock (self.callback, self.data, self.args)
 				handle.close (lambda *args: None)
 			else:
-				gobject.idle_add (callback_with_gdk_lock, callback, None, args)
+				callback_with_gdk_lock (self.callback, None, self.args)
 				handle.close (lambda *args: None)
 			return
  			
-		data += buffer
-		handle.read (self.chunk, self.read_cb, (data, callback, args))
+		self.data += buffer
+		handle.read (self.chunk, self._read_cb)
 
-	def open_cb (self, handle, exc_type, (data, callback, args)):
+	def _open_cb (self, handle, exc_type):
 		if exc_type:
-			gobject.idle_add (callback_with_gdk_lock, callback, None, args)
+			callback_with_gdk_lock (self.callback, None, self.args)
 			return
 
-		handle.read (self.chunk, self.read_cb, (data, callback, args))
+		self.handle = handle
+		self.data = ""
+		handle.read (self.chunk, self._read_cb)
     
 	def get_url (self, url, callback, *args):
-		gnomevfs.async.open (url, self.open_cb, data=("", callback, args))
+		self.url = url
+		self.callback = callback
+		self.args = args
+		gnomevfs.async.open (url, self._open_cb)
+
+	def cancel (self):
+		self.handle.cancel()
+
+
+class GnomeVFSChunkLoader (object):
+	def __init__ (self):
+		pass
+
+	def _callback(self, result):
+		return self.callback(result, self.total, *self.args)
+
+	def _callback_gdk(self, result):
+		gtk.gdk.threads_enter()
+		v = self._callback(result)
+		gtk.gdk.threads_leave()
+		return v
+
+	def _read_cb (self, handle, buffer, exc_type, bytes_requested):
+		if exc_type:
+			if issubclass (exc_type, gnomevfs.EOFError):
+				self._callback_gdk (None)
+			else:
+				self._callback_gdk (exc_type())
+			handle.close (lambda *args: None)
+		else:
+			if self._callback_gdk (buffer) is False:
+				handle.close(lambda *args: None)
+			else:
+				handle.read (self.chunksize, self._read_cb)
+
+
+	def _open_cb (self, handle, exc_type):
+		if exc_type:
+			self._callback_gdk (exc_type())
+		else:
+			handle.read (self.chunksize, self._read_cb)
+
+	def _info_cb (self, handle, results):
+		try:
+			(uri, exc, info) = results[0]
+			self.total = info.size
+		except ValueError:
+			pass
+
+		self.handle = gnomevfs.async.open (self.vfs_uri, self._open_cb)
+
+	def get_url_chunks (self, uri, chunksize, want_size, callback, *args):
+		self.uri = uri
+		self.chunksize = chunksize
+		self.total = 0
+		self.callback = callback
+		self.args = args
+		self.vfs_uri = gnomevfs.URI(uri)
+		if want_size:
+			self.handle = gnomevfs.async.get_file_info ((self.vfs_uri,), self._info_cb)
+		else:
+			self.handle = gnomevfs.async.open (self.vfs_uri, self._open_cb)
+
+	def cancel (self):
+		if self.handle:
+			self.handle.cancel()
+
+
+class GnomeVFSUpdateCheck (object):
+	def __init__ (self):
+		pass
 
-def Loader ():
-	if use_gio:
-		return GioSrc()
-	else:
-		return GnomeVFSAsyncSrc()
+	def _info_cb (self, handle, results):
+		(local_uri, local_exc, local_info) = results[0]
+		(remote_uri, remote_exc, remote_info) = results[1]
+
+		result = True
+
+		if remote_exc:
+			print "error checking remote URI %s: %s" % (self.remote, remote_exc)
+			result = False
+		elif local_exc:
+			if issubclass (local_exc, gnomevfs.NotFoundError):
+				print "local URI %s not found" % self.local
+			else:
+				print "error checking local URI %s: %s" % (self.local, local_exc)
+		else:
+			try:
+				result = (remote_info.mtime > local_info.mtime)
+			except ValueError, e:
+				print "error comparing modification times: %s" % e
+		
+		callback_with_gdk_lock (self.callback, result, self.args)
+
+
+	def check_for_update (self, local, remote, callback, *args):
+		self.callback = callback
+		self.args = args
+		self.local = local
+		self.remote = remote
+
+		uris = (gnomevfs.URI(local), gnomevfs.URI(remote))
+		self.handle = gnomevfs.async.get_file_info (uris, self._info_cb)
+
+	def cancel (self):
+		self.handle.cancel ()
+
+
+
+
+
+
+# now figure out which set of implementations to use
+
+use_gio = False
+try:
+	import gio
+	# before 2.16.0, file.load_contents_async didn't work correctly
+	if gio.pygio_version > (2,15,4):
+		use_gio = True
+except:
+	# probably don't have gio at all
+	pass
+
+if use_gio:
+	Loader = GioLoader
+	ChunkLoader = GioChunkLoader
+	UpdateCheck = GioUpdateCheck
+else:
+	import gnomevfs
+	Loader = GnomeVFSLoader
+	ChunkLoader = GnomeVFSChunkLoader
+	UpdateCheck = GnomeVFSUpdateCheck
 

Modified: trunk/plugins/rb/__init__.py
==============================================================================
--- trunk/plugins/rb/__init__.py	(original)
+++ trunk/plugins/rb/__init__.py	Sun Feb 22 10:50:19 2009
@@ -27,9 +27,15 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 import sys
+import os.path
+import os
+
+import gtk
 
 # rb classes
 from Loader import Loader
+from Loader import ChunkLoader
+from Loader import UpdateCheck
 from Coroutine import Coroutine
 
 #def _excepthandler (exc_class, exc_inst, trace):
@@ -46,7 +52,6 @@
 
 def append_plugin_source_path(theme, iconpath):
 	# check for a Makefile.am in the dir the file was loaded from
-	import sys, os
 	fr = sys._getframe(1)
 	co = fr.f_code
 	filename = co.co_filename
@@ -58,6 +63,15 @@
 		icondir = plugindir + iconpath
 		theme.append_search_path(icondir)
 
+def show_uri(uri):
+	# use gtk_show_uri if available, otherwise use gnome-vfs
+	if hasattr(gtk, 'show_uri'):
+		gtk.show_uri(gtk.gdk.Screen(), uri, 0)
+	else:
+		import gnomevfs
+		gnomevfs.url_show(uri)
+
+
 class _rbdebugfile:
 	def __init__(self, fn):
 		self.fn = fn
@@ -65,7 +79,7 @@
 	def write(self, str):
 		if str == '\n':
 			return
-		import sys, os, rb
+		import rb
 		fr = sys._getframe(1)
 
 		co = fr.f_code



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