From decf4a83ea025bcd40331d36815269d0d25da950 Mon Sep 17 00:00:00 2001 From: Jonathan Matthew Date: Sun, 22 Feb 2009 10:50:19 +0000 Subject: [PATCH] Restructure, rewrite, add two new classes: one for loading URI contents by 2009-02-22 Jonathan Matthew * 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. svn path=/trunk/; revision=6158 --- ChangeLog | 56 ++ bindings/python/rb.defs | 31 ++ bindings/python/rb.override | 1 + lib/rb-file-helpers.c | 80 ++- lib/rb-file-helpers.h | 2 + .../artdisplay/AmazonCoverArtSearch.py | 7 +- .../artdisplay/artdisplay/CoverArtDatabase.py | 24 +- .../artdisplay/LocalCoverArtSearch.py | 4 +- .../artdisplay/LocalCoverArtSearchGIO.py | 174 ++++++ plugins/artdisplay/artdisplay/Makefile.am | 1 + .../artdisplay/PodcastCoverArtSearch.py | 2 +- plugins/artdisplay/artdisplay/__init__.py | 3 +- plugins/coherence/upnp_coherence/__init__.py | 13 +- .../jamendo/jamendo/JamendoConfigureDialog.py | 2 +- plugins/jamendo/jamendo/JamendoSource.py | 244 +++------ plugins/lyrics/lyrics/__init__.py | 4 +- .../magnatune/magnatune/MagnatuneSource.py | 511 +++++++++--------- plugins/rb/Loader.py | 304 +++++++++-- plugins/rb/__init__.py | 18 +- 19 files changed, 989 insertions(+), 492 deletions(-) create mode 100644 plugins/artdisplay/artdisplay/LocalCoverArtSearchGIO.py diff --git a/ChangeLog b/ChangeLog index 85d3ec0e6..6b8a46f0a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,59 @@ +2009-02-22 Jonathan Matthew + + * 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. + 2009-02-19 Bastien Nocera * shell/rb-shell.c (construct_widgets), diff --git a/bindings/python/rb.defs b/bindings/python/rb.defs index 4eaeaf16d..7464645a0 100644 --- a/bindings/python/rb.defs +++ b/bindings/python/rb.defs @@ -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") + ) +) diff --git a/bindings/python/rb.override b/bindings/python/rb.override index 3e0be39c5..a0d7bd2c2 100644 --- a/bindings/python/rb.override +++ b/bindings/python/rb.override @@ -16,6 +16,7 @@ headers #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" diff --git a/lib/rb-file-helpers.c b/lib/rb-file-helpers.c index 016dfa721..72db08e0a 100644 --- a/lib/rb-file-helpers.c +++ b/lib/rb-file-helpers.c @@ -166,24 +166,10 @@ rb_music_dir (void) 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 @@ rb_find_user_data_file (const char *name, 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 @@ rb_find_user_data_file (const char *name, 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 @@ rb_find_user_data_file (const char *name, 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) { diff --git a/lib/rb-file-helpers.h b/lib/rb-file-helpers.h index 6b165ffc5..6ba7ec841 100644 --- a/lib/rb-file-helpers.h +++ b/lib/rb-file-helpers.h @@ -43,6 +43,8 @@ const char * rb_music_dir (void); 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); diff --git a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py index c4a818425..792e4a2a7 100644 --- a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py +++ b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py @@ -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 @@ def search_next (self): 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): diff --git a/plugins/artdisplay/artdisplay/CoverArtDatabase.py b/plugins/artdisplay/artdisplay/CoverArtDatabase.py index 1d4e30ad4..fe65d2b25 100644 --- a/plugins/artdisplay/artdisplay/CoverArtDatabase.py +++ b/plugins/artdisplay/artdisplay/CoverArtDatabase.py @@ -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 @@ def release (self, item, ticket): 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 build_art_cache_filename (self, db, entry, extension): 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 @@ def set_pixbuf (self, db, entry, pixbuf, callback): 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 @@ def image_search (self, plexer, db, st_album, st_artist, entry, callback): 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: diff --git a/plugins/artdisplay/artdisplay/LocalCoverArtSearch.py b/plugins/artdisplay/artdisplay/LocalCoverArtSearch.py index f17b7fa43..cb39779de 100644 --- a/plugins/artdisplay/artdisplay/LocalCoverArtSearch.py +++ b/plugins/artdisplay/artdisplay/LocalCoverArtSearch.py @@ -48,8 +48,8 @@ def shared_prefix_length (a, b): 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: diff --git a/plugins/artdisplay/artdisplay/LocalCoverArtSearchGIO.py b/plugins/artdisplay/artdisplay/LocalCoverArtSearchGIO.py new file mode 100644 index 000000000..fb4569ef5 --- /dev/null +++ b/plugins/artdisplay/artdisplay/LocalCoverArtSearchGIO.py @@ -0,0 +1,174 @@ +# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- +# +# Copyright (C) 2006 - Ed Catmur +# 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 +# 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)) + diff --git a/plugins/artdisplay/artdisplay/Makefile.am b/plugins/artdisplay/artdisplay/Makefile.am index 7265f86d8..c13aa88c7 100644 --- a/plugins/artdisplay/artdisplay/Makefile.am +++ b/plugins/artdisplay/artdisplay/Makefile.am @@ -5,5 +5,6 @@ plugin_PYTHON = \ PodcastCoverArtSearch.py \ AmazonCoverArtSearch.py \ LocalCoverArtSearch.py \ + LocalCoverArtSearchGIO.py \ CoverArtDatabase.py \ __init__.py diff --git a/plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py b/plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py index 794225536..263d7bcad 100644 --- a/plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py +++ b/plugins/artdisplay/artdisplay/PodcastCoverArtSearch.py @@ -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 diff --git a/plugins/artdisplay/artdisplay/__init__.py b/plugins/artdisplay/artdisplay/__init__.py index b102634cb..ec291a4e2 100644 --- a/plugins/artdisplay/artdisplay/__init__.py +++ b/plugins/artdisplay/artdisplay/__init__.py @@ -435,7 +435,8 @@ def loader_cb (data): 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: diff --git a/plugins/coherence/upnp_coherence/__init__.py b/plugins/coherence/upnp_coherence/__init__.py index af5c8ebd6..669d69ecd 100644 --- a/plugins/coherence/upnp_coherence/__init__.py +++ b/plugins/coherence/upnp_coherence/__init__.py @@ -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 @@ def activate(self, shell): 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() diff --git a/plugins/jamendo/jamendo/JamendoConfigureDialog.py b/plugins/jamendo/jamendo/JamendoConfigureDialog.py index 42cb373f7..cdbf01f4c 100644 --- a/plugins/jamendo/jamendo/JamendoConfigureDialog.py +++ b/plugins/jamendo/jamendo/JamendoConfigureDialog.py @@ -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' diff --git a/plugins/jamendo/jamendo/JamendoSource.py b/plugins/jamendo/jamendo/JamendoSource.py index ebb0f693a..e17104f25 100644 --- a/plugins/jamendo/jamendo/JamendoSource.py +++ b/plugins/jamendo/jamendo/JamendoSource.py @@ -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 @@ def __init__(self): 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 @@ def do_impl_activate(self): 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 @@ def do_impl_delete_thyself(self): 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 @@ def do_impl_delete_thyself(self): # # 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) + + self.__catalogue_loader = rb.ChunkLoader() + self.__catalogue_loader.get_url_chunks(self.__local_catalogue_path, 64*1024, True, self.__catalogue_chunk_cb, parser) - 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: - 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 @@ def download_album (self): 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 @@ def launch_donate (self): 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 @@ def emit_cover_art_uri (self, entry): 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) diff --git a/plugins/lyrics/lyrics/__init__.py b/plugins/lyrics/lyrics/__init__.py index c70530f00..1dd909ba6 100644 --- a/plugins/lyrics/lyrics/__init__.py +++ b/plugins/lyrics/lyrics/__init__.py @@ -108,7 +108,6 @@ def build_cache_path(artist, title): class LyricGrabber(object): def __init__(self, db, entry): - self.loader = rb.Loader () self.db = db self.entry = entry @@ -128,7 +127,8 @@ def search_lyrics(self, callback, cache_only=False): 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")) diff --git a/plugins/magnatune/magnatune/MagnatuneSource.py b/plugins/magnatune/magnatune/MagnatuneSource.py index 644495205..b635fe43e 100644 --- a/plugins/magnatune/magnatune/MagnatuneSource.py +++ b/plugins/magnatune/magnatune/MagnatuneSource.py @@ -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 @@ def __init__(self): 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 @@ def __init__(self): 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 @@ def do_impl_activate(self): 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 @@ def do_impl_delete_thyself(self): 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 @@ def display_artist_info(self): 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 @@ def buy_cd(self): 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 @@ def purchase_album(self): # # 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 @@ def __find_song_info(self, catalogue): 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 @@ def __buy_album(self, sku, pay, format, ccnumber, ccyear, ccmonth, name, email, 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("
", "") # get rid of any stray
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("
", "") # get rid of any stray
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 () + + album = zipfile.ZipFile(dest) + for track in album.namelist(): + track_uri = "file://" + urllib.pathname2url(os.path.join(library_dir, track)) - def __purchase_download_progress_cb(self, info, data): - to_file_uri = data[0] - audio_dl_uri = data[1] + track_uri = rb.sanitize_uri_for_filesystem(track_uri) + rb.uri_create_parent_dirs(track_uri) - if self.cancelled: + 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 @@ def emit_cover_art_uri (self, entry): 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) diff --git a/plugins/rb/Loader.py b/plugins/rb/Loader.py index f42bf4afd..e88244be1 100644 --- a/plugins/rb/Loader.py +++ b/plugins/rb/Loader.py @@ -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 GnomeVFSAsyncSrc (object): +class GioChunkLoader(object): def __init__ (self): - self.chunk = 4096 + 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) - def read_cb (self, handle, buffer, exc_type, bytes_requested, (data, callback, args)): + 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 GnomeVFSLoader (object): + def __init__ (self): + self.chunk = 4096 + + 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() -def Loader (): - if use_gio: - return GioSrc() - else: - return GnomeVFSAsyncSrc() + +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 _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 diff --git a/plugins/rb/__init__.py b/plugins/rb/__init__.py index 0a3805e0a..62b4f3da7 100644 --- a/plugins/rb/__init__.py +++ b/plugins/rb/__init__.py @@ -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 try_load_icon(theme, icon, size, flags): 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 @@ def append_plugin_source_path(theme, iconpath): 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 __init__(self, fn): def write(self, str): if str == '\n': return - import sys, os, rb + import rb fr = sys._getframe(1) co = fr.f_code