=== removed file 'conduit/modules/RhythmboxDBusModule/RhythmboxDBusModule.py'
--- conduit/modules/RhythmboxDBusModule/RhythmboxDBusModule.py	2008-03-26 09:16:32 +0000
+++ conduit/modules/RhythmboxDBusModule/RhythmboxDBusModule.py	1970-01-01 00:00:00 +0000
@@ -1,257 +0,0 @@
-"""
-Connects to Rhythmbox with the DBus plugin
-
-Based upon code from
-
-Copyright 2007: John Stowers
-Copyright 2008: Alexandre Rosenfeld
-License: GPLv2
-"""
-import urllib
-import os
-import logging
-log = logging.getLogger("modules.RhythmboxDBus")
-
-import conduit
-import conduit.Exceptions as Exceptions
-import conduit.dataproviders.DataProvider as DataProvider
-import conduit.utils as Utils
-import conduit.datatypes.Audio as Audio
-import dbus
-try:
-    import dbus.glib
-except ImportError:
-    from dbus.mainloop.glib import DBusGMainLoop
-    DBusGMainLoop(set_as_default=True)
-
-from gettext import gettext as _
-
-MODULES = {
-    "RhythmboxDBusSource" :              { "type": "dataprovider" },
-}
-
-#list store column define
-NAME_IDX=0
-CHECK_IDX=1
-
-DBusPath = "com.googecode.airmindprojects.Rhythmbox"
-
-class DBusClosedConnectionError(Exception):
-    '''
-    Raised when a connection to Rhythmbox doesnt exist.
-    '''
-    pass
-
-class DBusConnection:
-    """
-    Shared connection to Rhythmbox DBus. Detects when Rhythmbox is closed and
-    opened.
-    """
-
-    # Shared Rhythmbox interface
-    __dbus_connection = None
-    # Shared DBus Session Bus
-    __session_bus = None
-    # True when the DBus connection is being watched. Made to true on the first
-    # instance created.
-    __watch = False
-
-    def _dbus_connect(self):
-        """
-        Connect to the Rhythmbox DBus object. Only availiable when Rhythmbox
-        is running with the DBus plugin activated.
-        """
-        rb_obj = DBusConnection.__session_bus.get_object(DBusPath, "/")
-        return dbus.Interface(rb_obj, DBusPath)
-
-    def __init__(self):
-        if not DBusConnection.__watch:
-            def watch(name):
-                '''
-                Watch when the DBus connection is availiable, and connects or
-                disconnects accordingly.
-                '''
-                if name:
-                    DBusConnection.__dbus_connection = self._dbus_connect()
-                else:
-                    DBusConnection.__dbus_connection = None
-            DBusConnection.__session_bus = dbus.SessionBus()
-            # The owner changes when the plugin is activated or deactivated,
-            # including when Rhythmbox starts or shutdown with the plugin active.
-            DBusConnection.__session_bus.watch_name_owner(DBusPath, watch)
-            DBusConnection.__watch = True
-
-
-    def __getattr__(self, attr):
-        """ Delegate access to DBus interface """
-        if not DBusConnection:
-            raise DBusClosedConnectionError()
-        else:
-            return getattr(DBusConnection.__dbus_connection, attr)
-
-    def __setattr__(self, attr, value):
-        """ Delegate access to DBus interface """
-        if not DBusConnection:
-            raise DBusClosedConnectionError()
-        else:
-            return setattr(DBusConnection.__dbus_connection, attr, value)
-
-class RhythmboxDBusSource(DataProvider.DataSource):
-
-    _name_ = _("Rhythmbox Music (DBus)")
-    _description_ = _("Sync songs from your Rhythmbox playlists")
-    _category_ = conduit.dataproviders.CATEGORY_MEDIA
-    _module_type_ = "source"
-    _in_type_ = "file/audio"
-    _out_type_ = "file/audio"
-    _icon_ = "rhythmbox"
-
-    def __init__(self, *args):
-        DataProvider.DataSource.__init__(self)
-        self.dbus = DBusConnection()
-        #Names of the playlists we know
-        self.allPlaylists = []
-        #Names we wish to sync
-        self.playlists = []
-
-    def _get_playlists(self):
-        return [str(p) for p in self.dbus.GetPlaylists()]
-
-    def configure(self, window):
-        import gtk
-        import gobject
-        def col1_toggled_cb(cell, path, model ):
-            #not because we get this cb before change state
-            checked = not cell.get_active()
-            model[path][CHECK_IDX] = checked
-            val = model[path][NAME_IDX]
-            if checked and val not in self.playlists:
-                self.playlists.append(val)
-            elif not checked and val in self.playlists:
-                self.playlists.remove(val)
-
-            log.debug("Toggle '%s' to: %s" % (val, checked))
-            return
-
-        self.allPlaylists = self._get_playlists()
-        tree = Utils.dataprovider_glade_get_widget(
-                        __file__,
-                        "config.glade",
-                        "RBConfigDialog")
-        tagtreeview = tree.get_widget("tagtreeview")
-        #Build a list of all the playlists
-        list_store = gtk.ListStore( gobject.TYPE_STRING,    #name
-                                    gobject.TYPE_BOOLEAN,   #active
-                                    )
-        #Fill the list store, preselect some playlists
-        for p in self.allPlaylists:
-            list_store.append( (p, p in self.playlists) )
-        #Set up the treeview
-        tagtreeview.set_model(list_store)
-        #column 1 is the tag name
-        tagtreeview.append_column(  gtk.TreeViewColumn(_("Tag Name"),
-                                    gtk.CellRendererText(),
-                                    text=NAME_IDX)
-                                    )
-        #column 2 is a checkbox for selecting the tag to sync
-        renderer1 = gtk.CellRendererToggle()
-        renderer1.set_property('activatable', True)
-        renderer1.connect( 'toggled', col1_toggled_cb, list_store )
-        tagtreeview.append_column(  gtk.TreeViewColumn(_("Enabled"),
-                                    renderer1,
-                                    active=CHECK_IDX)
-                                    )
-
-        dlg = tree.get_widget("RBConfigDialog")
-
-        response = Utils.run_dialog (dlg, window)
-        dlg.destroy()
-
-    def refresh(self):
-        DataProvider.DataSource.refresh(self)
-        try:
-            self.allPlaylists = self._get_playlists()
-        except DBusClosedConnectionError:
-            raise Exceptions.RefreshError("Could not connect to Rhythmbox")
-
-    def get_all(self):
-        DataProvider.DataSource.get_all(self)
-        try:
-            # LUID is now a Rhythmbox ID, that only makes sense to Rhythmbox
-            songs = []
-            #only consider enabled playlists
-            for playlist in [p for p in self.allPlaylists if p in self.playlists]:
-                for song in self.dbus.GetPlaylistTracks(playlist):
-                    songs.append(str(song))
-            return songs
-        except DBusClosedConnectionError:
-            raise Exceptions.SyncronizeFatalError()
-
-    def get(self, LUID):
-        try:
-            DataProvider.DataSource.get(self, LUID)
-            songuri = str(self.dbus.GetTrackInfo(LUID, 'location'))
-            f = RhythmboxAudio(URI=songuri)
-            f.set_UID(LUID)
-            f.set_open_URI(songuri)
-
-            return f
-        except DBusClosedConnectionError:
-            raise Exceptions.SyncronizeFatalError()
-
-    def get_configuration(self):
-        return { "playlists" : self.playlists }
-
-    def get_UID(self):
-        return ""
-
-class RhythmboxAudio(Audio.Audio):
-
-    COVERS_PATH = os.path.expanduser("~/.gnome2/rhythmbox/covers/")
-
-    def __init__(self, URI, **kwargs):
-        Audio.Audio.__init__(self, URI, **kwargs)
-        self.dbus = DBusConnection()
-
-    def _get_info(self, info):
-        try:
-            return self.dbus.GetTrackInfo(self.get_UID(), info)
-        except DBusClosedConnectionError:
-            raise Exceptions.SyncronizeFatalError()
-
-    def get_audio_artist(self):
-        return self._get_info('artist')
-
-    def get_audio_album(self):
-        return self._get_info('album')
-
-    def get_audio_title(self):
-        return self._get_info('title')
-
-    def get_audio_duration(self):
-        '''
-        Returns the audio duration in seconds
-        '''
-        return int(self._get_info('duration'))
-
-    def get_audio_rating(self):
-        '''
-        Returns an rating from 0.0 to 5.0
-        '''
-        return float(self._get_info('rating'))
-
-    # This code has been disabled in the DBus plugin
-    #def set_audio_rating(self, value):
-    #    self.dbus.SetTrackInfo(self.get_UID(), 'rating', value)
-
-    def get_audio_cover_location(self):
-        '''
-        Returns a valid cover location or None if not availiable
-        '''
-        artist = self.get_audio_artist()
-        album = self.get_audio_album()
-        filename = str(os.path.join(RhythmboxAudio.COVERS_PATH, "%s - %s.jpg" % (artist, album)))
-        if os.path.exists(filename):
-            return filename
-        else:
-            return None

=== renamed directory 'conduit/modules/RhythmboxDBusModule' => 'conduit/modules/RhythmboxModule'
=== removed directory 'conduit/modules/RhythmboxModule'
=== removed file 'conduit/modules/RhythmboxModule/Makefile.am'
--- conduit/modules/RhythmboxModule/Makefile.am	2008-01-03 08:34:37 +0000
+++ conduit/modules/RhythmboxModule/Makefile.am	1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
-conduit_handlersdir = $(libdir)/conduit/modules/RhythmboxModule
-conduit_handlers_PYTHON = RhythmboxModule.py
-
-conduit_handlers_DATA = config.glade
-EXTRA_DIST = config.glade
-
-clean-local:
-	rm -rf *.pyc *.pyo

=== added file 'conduit/modules/RhythmboxModule/RhythmboxDBusDB.py'
--- conduit/modules/RhythmboxModule/RhythmboxDBusDB.py	1970-01-01 00:00:00 +0000
+++ conduit/modules/RhythmboxModule/RhythmboxDBusDB.py	2008-05-24 04:34:19 +0000
@@ -0,0 +1,197 @@
+"""
+Connects to Rhythmbox with the DBus plugin
+
+Based upon code from
+
+Copyright 2007: John Stowers
+Copyright 2008: Alexandre Rosenfeld
+License: GPLv2
+"""
+import urllib
+import os
+import logging
+log = logging.getLogger("modules.RhythmboxDBus")
+
+import conduit
+import conduit.Exceptions as Exceptions
+import conduit.dataproviders.DataProvider as DataProvider
+import conduit.utils as Utils
+import conduit.datatypes.Audio as Audio
+import dbus
+try:
+    import dbus.glib
+except ImportError:
+    from dbus.mainloop.glib import DBusGMainLoop
+    DBusGMainLoop(set_as_default=True)
+
+from gettext import gettext as _
+
+MODULES = {
+    "RhythmboxDBusSource" :              { "type": "dataprovider" },
+}
+
+#list store column define
+NAME_IDX=0
+CHECK_IDX=1
+
+DBusPath = "com.googecode.airmindprojects.Rhythmbox"
+RequiredVersion = [0, 1]
+
+class DBusClosedConnectionError(Exception):
+    '''
+    Raised when a connection to Rhythmbox doesnt exist.
+    '''
+    pass
+
+class DBusConnection:
+    """
+    Shared connection to Rhythmbox DBus. Detects when Rhythmbox is closed and
+    opened.
+    """
+
+    # Shared Rhythmbox interface
+    __dbus_connection = None
+    # Shared DBus Session Bus
+    __session_bus = None
+    # True when the DBus connection is being watched. Made to true on the first
+    # instance created.
+    __watch = False
+
+    def _dbus_connect(self):
+        """
+        Connect to the Rhythmbox DBus object. Only availiable when Rhythmbox
+        is running with the DBus plugin activated.
+        """
+        rb_obj = DBusConnection.__session_bus.get_object(DBusPath, "/")
+        return dbus.Interface(rb_obj, DBusPath)
+
+    def __init__(self):
+        if not DBusConnection.__watch:
+            def watch(name):
+                '''
+                Watch when the DBus connection is availiable, and connects or
+                disconnects accordingly.
+                '''
+                if name:
+                    DBusConnection.__dbus_connection = self._dbus_connect()
+                else:
+                    DBusConnection.__dbus_connection = None
+            DBusConnection.__session_bus = dbus.SessionBus()
+            # The owner changes when the plugin is activated or deactivated,
+            # including when Rhythmbox starts or shutdown with the plugin active.
+            DBusConnection.__session_bus.watch_name_owner(DBusPath, watch)
+            DBusConnection.__watch = True
+
+    def is_connected(self):
+        return DBusConnection.__dbus_connection is not None
+
+    def __getattr__(self, attr):
+        """ Delegate access to DBus interface """
+        if not DBusConnection:
+            raise DBusClosedConnectionError()
+        else:
+            return getattr(DBusConnection.__dbus_connection, attr)
+
+    def __setattr__(self, attr, value):
+        """ Delegate access to DBus interface """
+        if not DBusConnection:
+            raise DBusClosedConnectionError()
+        else:
+            return setattr(DBusConnection.__dbus_connection, attr, value)
+
+class RhythmboxDBusDB:
+
+    def __init__(self, *args):
+        self.dbus = DBusConnection()
+        #Names of the playlists we know
+        self.playlists = []
+
+    @classmethod
+    def is_availiable(self):
+        dbus = DBusConnection()
+        return dbus.is_connected() and dbus.GetVersion() >= RequiredVersion
+
+    def get_playlists(self):
+        self.playlists = [str(p) for p in self.dbus.GetPlaylists()]
+        return self.playlists
+
+    def get_songs(self, playlists):
+        try:
+            # LUID is now a Rhythmbox ID, that only makes sense to Rhythmbox
+            songs = []
+            #only consider enabled playlists
+            for playlist in [p for p in self.playlists if p in playlists]:
+                for song in self.dbus.GetPlaylistTracks(playlist):
+                    songs.append(str(song))
+            return songs
+        except DBusClosedConnectionError:
+            raise Exceptions.SyncronizeFatalError()
+
+    def get_audio(self, LUID):
+        try:
+            DataProvider.DataSource.get(self, LUID)
+            songuri = str(self.dbus.GetTrackInfo(LUID, 'location'))
+            f = RhythmboxAudio(URI=songuri)
+            f.set_UID(LUID)
+            f.set_open_URI(songuri)
+
+            return f
+        except DBusClosedConnectionError:
+            raise Exceptions.SyncronizeFatalError()
+
+class RhythmboxAudio(Audio.Audio):
+
+    COVERS_PATH = os.path.expanduser("~/.gnome2/rhythmbox/covers/")
+    # Cached track info
+    INFO = ['artist', 'album', 'title', 'duration', 'rating']
+
+    def __init__(self, URI, **kwargs):
+        Audio.Audio.__init__(self, URI, **kwargs)
+        self.dbus = DBusConnection()
+
+    def _get_info(self, info):
+        try:
+            # Save a few DBus calls by cacheing the info we need
+            self.cached = self.dbus.GetMultipleTrackInfo(self.get_UID(), INFO)
+            return self.cached[info]
+        except DBusClosedConnectionError:
+            raise Exceptions.SyncronizeFatalError()
+        except KeyError:
+            raise Exceptions.SyncronizeFatalError('Failed to get property %s' % (info))
+
+    def get_audio_artist(self):
+        return str(self._get_info('artist'))
+
+    def get_audio_album(self):
+        return str(self._get_info('album'))
+
+    def get_audio_title(self):
+        return str(self._get_info('title'))
+
+    def get_audio_duration(self):
+        '''
+        Returns the audio duration in seconds
+        '''
+        return int(self._get_info('duration'))
+
+    def get_audio_rating(self):
+        '''
+        Returns an rating from 0.0 to 5.0
+        '''
+        return float(self._get_info('rating'))
+
+    # This code has been disabled in the DBus plugin
+    #def set_audio_rating(self, value):
+    #    self.dbus.SetTrackInfo(self.get_UID(), 'rating', value)
+
+    def get_audio_cover_location(self):
+        '''
+        Returns a valid cover location or None if not availiable
+        '''
+        artist = self.get_audio_artist()
+        album = self.get_audio_album()
+        filename = str(os.path.join(RhythmboxAudio.COVERS_PATH, "%s - %s.jpg" % (artist, album)))
+        if os.path.exists(filename):
+            return filename
+        else:
+            return None

=== added file 'conduit/modules/RhythmboxModule/RhythmboxFileDB.py'
--- conduit/modules/RhythmboxModule/RhythmboxFileDB.py	1970-01-01 00:00:00 +0000
+++ conduit/modules/RhythmboxModule/RhythmboxFileDB.py	2008-05-29 04:35:09 +0000
@@ -0,0 +1,100 @@
+"""
+Provides a simple means for reading rhythmbox static playlists.
+
+Based upon code from
+
+Copyright 2007: John Stowers
+Copyright 2008: Alexandre Rosenfeld
+License: GPLv2
+"""
+import urllib
+import os
+import logging
+log = logging.getLogger("modules.Rhythmbox")
+
+from gettext import gettext as _
+
+
+try:
+    import elementtree.ElementTree as ET
+except:
+    import xml.etree.ElementTree as ET
+
+import conduit
+import conduit.dataproviders.DataProvider as DataProvider
+import conduit.utils as Utils
+import conduit.datatypes.Audio as Audio
+
+class RhythmboxFileDB:
+    PLAYLIST_PATH=os.path.expanduser("~/.gnome2/rhythmbox/playlists.xml")
+
+    def __init__(self, *args):
+        #Names of the playlists we know
+        self.playlists = []
+
+    @classmethod
+    def is_availiable(self):
+        return os.path.exists(self.PLAYLIST_PATH)
+
+    def _parse_playlists(self, path, allowed=[]):
+        playlists = []
+        songs = []
+        playlist_name = _(u"Unknown")
+        is_static = False
+
+        path = os.path.expanduser(path)
+
+        root = ET.ElementTree(file=path)
+        iter = root.getiterator()
+        for element in iter:
+
+            if element.tag == "playlist":
+                is_static = False
+
+                if element.keys():
+                    for name, value in element.items():
+                        if name == "type" and value == "static":
+                            is_static = True
+                        if name == "name":
+                            temp_name = value
+
+                if is_static == True: # new playlist found
+                    playlist_name = temp_name
+                    songs = []
+                    playlists.append( [playlist_name, songs] )
+
+            #Text that precedes all child elements (may be None)
+            if element.text:
+                text = element.text
+            if element.tag == "location":
+                song_location = ''.join(urllib.url2pathname(text).split("://")[1:])
+
+                if not os.path.exists(song_location):
+                    print "WARNING: A song referred to from the playlist '%s' cannot be found on the harddrive." % playlist_name
+                    continue
+
+                songs.append( song_location )
+
+        return playlists
+
+    def get_playlists(self):
+        self.playlists = self._parse_playlists(self.PLAYLIST_PATH)
+        # Return the playlists names
+        return [playlist[0] for playlist in self.playlists]
+
+    def get_songs(self, playlists):
+        #in this case the luid for the song is its path
+        songs = []
+        #only consider enabled playlists
+        for playlist in [p for p in self.playlists if p[0] in playlists]:
+            for song in playlist[1]:
+                songs.append(song)
+
+        return songs
+
+    def get_audio(self, songuri):
+        f = Audio.Audio(URI=songuri)
+        f.set_UID(songuri)
+        f.set_open_URI(songuri)
+
+        return f

=== added file 'conduit/modules/RhythmboxModule/RhythmboxModule.py'
--- conduit/modules/RhythmboxModule/RhythmboxModule.py	1970-01-01 00:00:00 +0000
+++ conduit/modules/RhythmboxModule/RhythmboxModule.py	2008-05-29 04:36:35 +0000
@@ -0,0 +1,136 @@
+"""
+Connects to Rhythmbox
+
+Based upon code from
+
+Copyright 2007: John Stowers
+Copyright 2008: Alexandre Rosenfeld
+License: GPLv2
+"""
+import urllib
+import os
+import logging
+log = logging.getLogger("modules.Rhythmbox")
+
+import conduit
+import conduit.Exceptions as Exceptions
+import conduit.dataproviders.DataProvider as DataProvider
+import conduit.utils as Utils
+import conduit.datatypes.Audio as Audio
+
+Utils.dataprovider_add_dir_to_path(__file__)
+from RhythmboxDBusDB import RhythmboxDBusDB
+from RhythmboxFileDB import RhythmboxFileDB
+
+from gettext import gettext as _
+
+MODULES = {
+    "RhythmboxSource" :              { "type": "dataprovider" },
+}
+
+#list store column define
+NAME_IDX=0
+CHECK_IDX=1
+
+
+class RhythmboxSource(DataProvider.DataSource):
+
+    _name_ = _("Rhythmbox Music")
+    _description_ = _("Sync songs from your Rhythmbox playlists")
+    _category_ = conduit.dataproviders.CATEGORY_MEDIA
+    _module_type_ = "source"
+    _in_type_ = "file/audio"
+    _out_type_ = "file/audio"
+    _icon_ = "rhythmbox"
+
+    def __init__(self, *args):
+        DataProvider.DataSource.__init__(self)
+        self.db = None
+        #Names we wish to sync
+        self.playlists = []
+
+    def _get_db(self):
+        '''
+        Finds an availiable Rhythmbox DB. The DBus DB is tried first
+        '''
+        for DB in [RhythmboxDBusDB, RhythmboxFileDB]:
+            if DB.is_availiable():
+                # Only create a new DB if the old one is different.
+                # This allows the DBus plugin to be found even if it wasnt
+                # availiable from the start.
+                if not (self.db and isinstance(self.db, DB)):
+                    self.db = DB()
+                    break
+        else:
+            raise Exceptions.RefreshError("Could not find a Rhythmbox database.")
+
+    def _get_playlists(self):
+        self._get_db()
+        return self.db.get_playlists()
+
+    def configure(self, window):
+        import gtk
+        import gobject
+        def col1_toggled_cb(cell, path, model ):
+            #not because we get this cb before change state
+            checked = not cell.get_active()
+            model[path][CHECK_IDX] = checked
+            val = model[path][NAME_IDX]
+            if checked and val not in self.playlists:
+                self.playlists.append(val)
+            elif not checked and val in self.playlists:
+                self.playlists.remove(val)
+
+            log.debug("Toggle '%s' to: %s" % (val, checked))
+            return
+
+        self.allPlaylists = self._get_playlists()
+        tree = Utils.dataprovider_glade_get_widget(
+                        __file__,
+                        "config.glade",
+                        "RBConfigDialog")
+        tagtreeview = tree.get_widget("tagtreeview")
+        #Build a list of all the playlists
+        list_store = gtk.ListStore( gobject.TYPE_STRING,    #name
+                                    gobject.TYPE_BOOLEAN,   #active
+                                    )
+        #Fill the list store, preselect some playlists
+        for p in self.allPlaylists:
+            list_store.append( (p, p in self.playlists) )
+        #Set up the treeview
+        tagtreeview.set_model(list_store)
+        #column 1 is the tag name
+        tagtreeview.append_column(  gtk.TreeViewColumn(_("Tag Name"),
+                                    gtk.CellRendererText(),
+                                    text=NAME_IDX)
+                                    )
+        #column 2 is a checkbox for selecting the tag to sync
+        renderer1 = gtk.CellRendererToggle()
+        renderer1.set_property('activatable', True)
+        renderer1.connect( 'toggled', col1_toggled_cb, list_store )
+        tagtreeview.append_column(  gtk.TreeViewColumn(_("Enabled"),
+                                    renderer1,
+                                    active=CHECK_IDX)
+                                    )
+
+        dlg = tree.get_widget("RBConfigDialog")
+
+        response = Utils.run_dialog (dlg, window)
+        dlg.destroy()
+
+    def refresh(self):
+        DataProvider.DataSource.refresh(self)
+        self.allPlaylists = self._get_playlists()
+
+    def get_all(self):
+        DataProvider.DataSource.get_all(self)
+        return self.db.get_songs(self.playlists)
+
+    def get(self, LUID):
+        return self.db.get_audio(LUID)
+
+    def get_configuration(self):
+        return { "playlists" : self.playlists }
+
+    def get_UID(self):
+        return ""

=== removed file 'conduit/modules/RhythmboxModule/RhythmboxModule.py'
--- conduit/modules/RhythmboxModule/RhythmboxModule.py	2008-03-26 09:16:32 +0000
+++ conduit/modules/RhythmboxModule/RhythmboxModule.py	1970-01-01 00:00:00 +0000
@@ -1,174 +0,0 @@
-"""
-Provides a simple means for reading rhythmbox static playlists.
-
-Based upon code from 
-
-Copyright 2007: John Stowers
-License: GPLv2
-"""
-import urllib
-import os
-import logging
-log = logging.getLogger("modules.Rhythmbox")
-
-
-try:
-    import elementtree.ElementTree as ET
-except:
-    import xml.etree.ElementTree as ET
-
-import conduit
-import conduit.dataproviders.DataProvider as DataProvider
-import conduit.utils as Utils
-import conduit.datatypes.Audio as Audio
-
-from gettext import gettext as _ 
-
-MODULES = {
-    "RhythmboxSource" :              { "type": "dataprovider" },
-}
-
-#list store column define
-NAME_IDX=0
-CHECK_IDX=1
-
-class RhythmboxSource(DataProvider.DataSource):
-
-    _name_ = _("Rhythmbox Music")
-    _description_ = _("Sync songs from your Rhythmbox playlists")
-    _category_ = conduit.dataproviders.CATEGORY_MEDIA
-    _module_type_ = "source"
-    _in_type_ = "file/audio"
-    _out_type_ = "file/audio"
-    _icon_ = "rhythmbox"
-
-    PLAYLIST_PATH="~/.gnome2/rhythmbox/playlists.xml"
-
-    def __init__(self, *args):
-        DataProvider.DataSource.__init__(self)
-        #Names of the playlists we know
-        self.allPlaylists = []
-        #Names we wish to sync
-        self.playlists = []
-
-    def _parse_playlists(self, path, allowed=[]):
-        playlists = []
-        songs = []
-        playlist_name = _(u"Unknown")
-        is_static = False
-
-        path = os.path.expanduser(path) 
-        
-        root = ET.ElementTree(file=path)
-        iter = root.getiterator()
-        for element in iter:
-
-            if element.tag == "playlist":
-                is_static = False
-
-                if element.keys():
-                    for name, value in element.items():
-                        if name == "type" and value == "static":
-                            is_static = True
-                        if name == "name":
-                            temp_name = value
-
-                if is_static == True: # new playlist found
-                    playlist_name = temp_name
-                    songs = []
-                    playlists.append( [playlist_name, songs] )
-
-            #Text that precedes all child elements (may be None)
-            if element.text:
-                text = element.text
-            if element.tag == "location":
-                song_location = ''.join(urllib.url2pathname(text).split("://")[1:])
-
-                if not os.path.exists(song_location):
-                    print "WARNING: A song referred to from the playlist '%s' cannot be found on the harddrive." % playlist_name
-                    continue
-
-                songs.append( song_location )
-
-        return playlists
-
-    def configure(self, window):
-        import gtk
-        import gobject
-        def col1_toggled_cb(cell, path, model ):
-            #not because we get this cb before change state
-            checked = not cell.get_active()
-            model[path][CHECK_IDX] = checked
-            val = model[path][NAME_IDX]
-            if checked and val not in self.playlists:
-                self.playlists.append(val)
-            elif not checked and val in self.playlists:
-                self.playlists.remove(val)
-
-            log.debug("Toggle '%s' to: %s" % (val, checked))
-            return
-
-        #FIXME: This should not run here, it should run in initialize() instead
-        self.allPlaylists = self._parse_playlists(RhythmboxSource.PLAYLIST_PATH)
-        tree = Utils.dataprovider_glade_get_widget(
-                        __file__, 
-                        "config.glade",
-						"RBConfigDialog"
-						)
-        tagtreeview = tree.get_widget("tagtreeview")
-        #Build a list of all the playlists
-        list_store = gtk.ListStore( gobject.TYPE_STRING,    #name
-                                    gobject.TYPE_BOOLEAN,   #active
-                                    )
-        #Fill the list store, preselect some playlists
-        for p in self.allPlaylists:
-            list_store.append( (p[0],p[0] in self.playlists) )
-        #Set up the treeview
-        tagtreeview.set_model(list_store)
-        #column 1 is the tag name
-        tagtreeview.append_column(  gtk.TreeViewColumn(_("Tag Name"), 
-                                    gtk.CellRendererText(), 
-                                    text=NAME_IDX)
-                                    )
-        #column 2 is a checkbox for selecting the tag to sync
-        renderer1 = gtk.CellRendererToggle()
-        renderer1.set_property('activatable', True)
-        renderer1.connect( 'toggled', col1_toggled_cb, list_store )
-        tagtreeview.append_column(  gtk.TreeViewColumn(_("Enabled"), 
-                                    renderer1, 
-                                    active=CHECK_IDX)
-                                    )
-
-        dlg = tree.get_widget("RBConfigDialog")
-
-        response = Utils.run_dialog (dlg, window)
-        dlg.destroy()
-
-    def refresh(self):
-        DataProvider.DataSource.refresh(self)
-        self.allPlaylists = self._parse_playlists(RhythmboxSource.PLAYLIST_PATH)
-       
-    def get_all(self):
-        DataProvider.DataSource.get_all(self)
-        #in this case the luid for the song is its path
-        songs = []
-        #only consider enabled playlists
-        for playlist in [p for p in self.allPlaylists if p[0] in self.playlists]:
-            for song in playlist[1]:
-                songs.append(song)
-
-        return songs
-
-    def get(self, songuri):
-        DataProvider.DataSource.get(self, songuri)
-        f = Audio.Audio(URI=songuri)
-        f.set_UID(songuri)
-        f.set_open_URI(songuri)
-
-        return f
-    def get_configuration(self):
-        return { "playlists" : self.playlists }
- 
-    def get_UID(self):
-        return ""
-

=== removed file 'conduit/modules/RhythmboxModule/config.glade'
--- conduit/modules/RhythmboxModule/config.glade	2007-10-29 23:48:03 +0000
+++ conduit/modules/RhythmboxModule/config.glade	1970-01-01 00:00:00 +0000
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--*- mode: xml -*-->
-<glade-interface>
-  <widget class="GtkDialog" id="RBConfigDialog">
-    <property name="visible">True</property>
-    <property name="title" translatable="yes">Rhythmbox Playlists</property>
-    <property name="default_width">250</property>
-    <property name="default_height">350</property>
-    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
-    <child internal-child="vbox">
-      <widget class="GtkVBox" id="vbox26">
-        <property name="visible">True</property>
-        <property name="spacing">5</property>
-        <child>
-          <widget class="GtkVBox" id="vbox27">
-            <property name="visible">True</property>
-            <property name="spacing">5</property>
-            <child>
-              <widget class="GtkLabel" id="label64">
-                <property name="visible">True</property>
-                <property name="label" translatable="yes">&lt;b&gt;Synchronize the Following Playlists&lt;/b&gt;</property>
-                <property name="use_markup">True</property>
-              </widget>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-              </packing>
-            </child>
-            <child>
-              <widget class="GtkScrolledWindow" id="tagscrolledwindow">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <child>
-                  <widget class="GtkTreeView" id="tagtreeview">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                  </widget>
-                </child>
-              </widget>
-              <packing>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </widget>
-          <packing>
-            <property name="position">2</property>
-          </packing>
-        </child>
-        <child internal-child="action_area">
-          <widget class="GtkHButtonBox" id="hbuttonbox11">
-            <property name="visible">True</property>
-            <property name="layout_style">GTK_BUTTONBOX_END</property>
-            <child>
-              <widget class="GtkButton" id="button28">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="label">gtk-cancel</property>
-                <property name="use_stock">True</property>
-                <property name="response_id">-6</property>
-              </widget>
-            </child>
-            <child>
-              <widget class="GtkButton" id="button29">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="label">gtk-ok</property>
-                <property name="use_stock">True</property>
-                <property name="response_id">-5</property>
-              </widget>
-              <packing>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </widget>
-          <packing>
-            <property name="expand">False</property>
-            <property name="pack_type">GTK_PACK_END</property>
-          </packing>
-        </child>
-      </widget>
-    </child>
-  </widget>
-</glade-interface>

=== added directory 'tools/rhythmbox'
=== added file 'tools/rhythmbox/README'
--- tools/rhythmbox/README	1970-01-01 00:00:00 +0000
+++ tools/rhythmbox/README	2008-06-03 01:31:33 +0000
@@ -0,0 +1,13 @@
+This is a small Rhythmbox plugin that enables access to the Rhythmbox library
+by DBus. It is used by Conduit automatically if availiable.
+The Rhythmbox database file is parsed if this plugin is not active.
+
+Features over the default access:
+- Ratings and play-counts can be accessed by Conduit. This is not used in
+  Conduit yet, but the iPod module will make use of it.
+- Smart playlists can be accessed by Conduit.
+
+ == Installation ==
+
+Just copy the conduit_sync folder to ~/.gnome2/rhythmbox/plugins and activate
+the plugin in Rhythmbox -> Edit -> Plugins

=== added directory 'tools/rhythmbox/conduit_sync'
=== added file 'tools/rhythmbox/conduit_sync/__init__.py'
--- tools/rhythmbox/conduit_sync/__init__.py	1970-01-01 00:00:00 +0000
+++ tools/rhythmbox/conduit_sync/__init__.py	2008-05-24 04:14:51 +0000
@@ -0,0 +1,156 @@
+# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2008 - Alexandre Rosenfeld
+#
+# 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.
+#
+# 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 string, sys
+import gobject, gtk
+import rb, rhythmdb
+import dbus, dbus.service
+try:
+    import dbus.glib
+except ImportError:
+    from dbus.mainloop.glib import DBusGMainLoop
+    DBusGMainLoop(set_as_default=True)
+
+DBusPath = "com.googecode.airmindprojects.Rhythmbox"
+APIVersion = [0, 1]
+
+class ConduitSyncPlugin (rb.Plugin):
+    def __init__(self):
+        rb.Plugin.__init__(self)
+        self.dbus_object = None
+        self.bus = dbus.SessionBus()
+    def activate(self, shell):
+        if not self.dbus_object:
+            bus_name = dbus.service.BusName(DBusPath, self.bus, True, True)
+            self.dbus_object = DBusRhythmbox(bus_name, shell, shell.get_property('db'))
+
+    def deactivate(self, shell):
+        if self.dbus_object:
+            self.dbus_object.remove_from_connection()
+            self.dbus_object = None
+
+class DBusRhythmboxException(dbus.DBusException):
+    _dbus_error_name = DBusPath
+
+class DBusRhythmbox(dbus.service.Object):
+    def __init__(self, bus_name, shell, db):
+        dbus.service.Object.__init__(self, object_path = '/', bus_name = bus_name)
+        self.shell = shell
+        self.db = db
+        self.source_model = shell.props.sourcelist_model
+
+    def _get_playlists(self):
+        playlist_group = self.source_model[1]
+        playlists = {}
+        for playlist_row in playlist_group.iterchildren():
+            (playlist_name, playlist_source) = self.source_model.get(playlist_row.iter, 2, 3)
+            playlists[playlist_name] = playlist_source
+        return playlists
+
+    def _get_entry(self, entry_id):
+        entry = self.db.entry_lookup_by_id(int(entry_id))
+        if not entry:
+            raise DBusRhythmboxException("Entry not found with id=%s" % entry_id)
+        return entry
+
+    @dbus.service.method(DBusPath, in_signature='', out_signature='ii')
+    def GetVersion(self):
+        return APIVersion
+
+    @dbus.service.method(DBusPath, in_signature='', out_signature='as')
+    def GetPlaylists(self):
+        return sorted(self._get_playlists().keys())
+
+    @dbus.service.method(DBusPath, in_signature='s', out_signature='i')
+    def GetPlaylistCount(self, name):
+        playlist_source = self._get_playlists()[str(name)]
+        playlist_model = playlist_source.props.query_model
+        return playlist_model.iter_n_children(None)
+
+    @dbus.service.method(DBusPath, in_signature='s', out_signature='as')
+    def GetPlaylistTracks(self, name):
+        playlist_source = self._get_playlists()[str(name)]
+        playlist_model = playlist_source.props.query_model
+        tracks = []
+        for track_row in playlist_model:
+            track_entry = track_row[0]
+            track_id = self.db.entry_get(track_entry, rhythmdb.PROP_ENTRY_ID)
+            tracks.append(str(track_id))
+        return tracks
+
+    QUERIES = [s[11:] for s in dir(rhythmdb) if s.startswith('QUERY_PROP_')]
+    PROPS = [s[5:] for s in dir(rhythmdb) if s.startswith('PROP_')]
+
+    def _query(self, query_props):
+        query = self.db.query_new()
+        self.db.query_append(query, tuple(query_props))
+        query_model = self.db.query_model_new_empty()
+        self.db.do_full_query_parsed(query_model, query)
+        # Get the ids from the entries (row has the entry in its first position)
+        return [str(self.db.entry_get(row[0], rhythmdb.PROP_ENTRY_ID)) for row in query_model]
+
+    @dbus.service.method(DBusPath, in_signature='a{ss}', out_signature='as')
+    def FindTracks(self, props):
+        query_props = []
+        for key, value in props.iteritems():
+            prop = getattr(rhythmdb, 'PROP_%s' % str(key).upper())
+            query_props += [rhythmdb.QUERY_PROP_EQUALS, prop, str(value)]
+        return self._query(query_props)
+
+    @dbus.service.method(DBusPath, in_signature='as', out_signature='as')
+    def QueryTracks(self, query_attrs):
+        query_props = []
+        for value in query_attrs:
+            if str(value).upper() in self.QUERIES:
+                value = getattr(rhythmdb, 'QUERY_PROP_%s' % value.upper())
+            elif str(value).upper() in self.PROPS:
+                value = getattr(rhythmdb, 'PROP_%s' % value.upper())
+            query_props.append(value)
+        return self._query(query_props)
+
+    @dbus.service.method(DBusPath, in_signature='', out_signature='as')
+    def GetAvailiableInfo(self):
+    	return self.PROPS
+
+    def _get_prop(self, entry, prop_name):
+        prop = 'PROP_%s' % str(prop_name).upper()
+        if hasattr(rhythmdb, prop):
+            return str(self.db.entry_get(entry, getattr(rhythmdb, prop)))
+        else:
+            raise DBusRhythmboxException("Property %s not found" % prop_name)
+
+    @dbus.service.method(DBusPath, in_signature='ss', out_signature='s')
+    def GetTrackInfo(self, entry_id, info):
+        entry = self._get_entry(entry_id)
+        return self._get_prop(entry, info)
+
+    @dbus.service.method(DBusPath, in_signature='sas', out_signature='as')
+    def GetMultipleTrackInfo(self, entry_id, info_names):
+        entry = self._get_entry(entry_id)
+        return [self._get_prop(entry, prop) for prop in info_names]
+
+#    This code was NOT tested, but it could work.
+
+#    @dbus.service.method(DBusPath, in_signature='sss', out_signature='')
+#    def SetTrackInfo(self, entry_id, info, value):
+#        entry = self._get_entry(entry_id)
+#        prop_name = 'PROP_%s' % str(info).upper()
+#        if hasattr(rhythmdb, prop_name):
+#            self.db.entry_set(getattr(rhythmdb, prop_name), value)
+#        else:
+#            raise DBusRhythmboxException("Property %s not found" % info)

=== added file 'tools/rhythmbox/conduit_sync/client_test.py'
--- tools/rhythmbox/conduit_sync/client_test.py	1970-01-01 00:00:00 +0000
+++ tools/rhythmbox/conduit_sync/client_test.py	2008-06-03 01:31:50 +0000
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2008 - Alexandre Rosenfeld
+#
+# 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.
+#
+# 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 sys
+import dbus
+
+DBusPath = "com.googecode.airmindprojects.Rhythmbox"
+Info = ['artist', 'title', 'album', 'rating', 'image']
+# If set to true it uses a single call to get a track info. It can greatly
+# speed-up the processing
+MultipleInfo = True
+RequiredVersion = [0, 1]
+ShowAvailiableInfo = False
+
+def main():
+    bus = dbus.SessionBus()
+
+    try:
+        rb_obj = bus.get_object(DBusPath, "/")
+        rb = dbus.Interface(rb_obj, DBusPath)
+    except:
+        print 'Make sure Rhythmbox is running and the Conduit DBus plugin is active.'
+        sys.exit(-1)
+
+    rb_version = rb.GetVersion()
+    print 'Rhythmbox DBus API Version %d.%d' % (rb_version[0], rb_version[1])
+    if rb.GetVersion() < RequiredVersion:
+        print 'The DBus plugin is outdated'
+
+    if ShowAvailiableInfo:
+        for info in rb.GetAvailiableInfo():
+            print info
+
+    index = 0
+    for playlist in rb.GetPlaylists():
+        count = rb.GetPlaylistCount(playlist)
+
+        print "%i - %s: %s items" % (index, playlist, count)
+        index += 1
+
+    count = index
+    print range(count)
+    index = int(raw_input("\nChoose a playlist to show songs, or -1 to search: "))
+
+    def show_track(track):
+        print ''
+        if MultipleInfo:
+            info = rb.GetMultipleTrackInfo(track, Info)
+        else:
+            info = [rb.GetTrackInfo(track, name) for name in Info]
+        for name, value in zip(Info, info):
+            print " %s = %s" % (' '.join(name.split('_')).title(), value)
+
+    if not (index in range(count)):
+        print "\nYou can search for both an artist and a title, or only one of them.\nOnly exact matches are supported now."
+        artist = raw_input("Artist: ")
+        title = raw_input("Title: ")
+        query = {}
+        if artist:
+            query['artist'] = artist
+        if title:
+            query['title'] = title
+        if query:
+            for track in rb.FindTracks(query):
+                show_track(track)
+    else:
+        playlist = rb.GetPlaylists()[int(index)]
+        [show_track(track) for track in rb.GetPlaylistTracks(playlist)]
+
+if __name__ == '__main__':
+    main()

=== added file 'tools/rhythmbox/conduit_sync/conduit_sync.rb-plugin'
--- tools/rhythmbox/conduit_sync/conduit_sync.rb-plugin	1970-01-01 00:00:00 +0000
+++ tools/rhythmbox/conduit_sync/conduit_sync.rb-plugin	2008-02-26 01:46:53 +0000
@@ -0,0 +1,10 @@
+[RB Plugin]
+Loader=python
+Module=conduit_sync
+IAge=1
+Version=0.1.0.0
+Name=Conduit Sync
+Description=Sync playlists and songs using Conduit
+Authors=Alexandre Rosenfeld <airmind@gmail.com>
+Copyright=Copyright © 2007 Alexandre Rosenfeld
+Website=http://www.rhythmbox.org/

