[PATCH] First pass at a banshee provider



Attached is a first pass at a banshee provider.
I've basically copied the f-spot provider and modified it to banshee's
db. This is my second python contribution, and my first attempt to deal
with conduit. As I don't have a music player, I've only used the test
audio sync. Files from the selected playlists are transcoded and placed
next to the originals with an extension mp3Test.




Index: Makefile.am
===================================================================
--- Makefile.am	(revision 1014)
+++ Makefile.am	(working copy)
@@ -1,5 +1,6 @@
 SUBDIRS = \
 	BackpackModule \
+	BansheeModule \
 	BoxDotNetModule \
 	FacebookModule \
 	FileModule \
Index: BansheeModule/config.glade
===================================================================
--- BansheeModule/config.glade	(revision 0)
+++ BansheeModule/config.glade	(revision 0)
@@ -0,0 +1,142 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd";>
+
+<glade-interface>
+<requires lib="gnome"/>
+
+<widget class="GtkDialog" id="BansheeConfigDialog">
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">Banshee</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="default_width">250</property>
+  <property name="default_height">350</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+  <property name="has_separator">True</property>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="vbox26">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">5</property>
+
+      <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_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-cancel</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-6</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="button29">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-ok</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-5</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVBox" id="vbox27">
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</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;Return  Playlists&lt;/b&gt;</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">True</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">False</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	      <property name="width_chars">-1</property>
+	      <property name="single_line_mode">False</property>
+	      <property name="angle">0</property>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <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>
+	      <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+	      <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+	      <property name="shadow_type">GTK_SHADOW_NONE</property>
+	      <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+	      <child>
+		<widget class="GtkTreeView" id="tagtreeview">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="headers_visible">True</property>
+		  <property name="rules_hint">False</property>
+		  <property name="reorderable">False</property>
+		  <property name="enable_search">True</property>
+		  <property name="fixed_height_mode">False</property>
+		  <property name="hover_selection">False</property>
+		  <property name="hover_expand">False</property>
+		</widget>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
Index: BansheeModule/BansheeModule.py
===================================================================
--- BansheeModule/BansheeModule.py	(revision 0)
+++ BansheeModule/BansheeModule.py	(revision 0)
@@ -0,0 +1,163 @@
+import os
+import gobject
+
+try:
+    #python 2.4
+    from pysqlite2 import dbapi2 as sqlite
+except ImportError:
+    #python 2.5
+    from sqlite3 import dbapi2 as sqlite
+
+import conduit
+from conduit import logd
+import conduit.Utils as Utils
+import conduit.Exceptions as Exceptions
+import conduit.dataproviders.DataProvider as DataProvider
+import conduit.datatypes.File as File
+
+MODULES = {
+	"BansheeSource" : { "type": "dataprovider" }
+}
+
+ID_IDX = 0
+NAME_IDX = 1
+
+class BansheeSource(DataProvider.DataSource):
+
+    _name_ = "Banshee Playlists"
+    _description_ = "Sync your Banshee Playlists"
+    _category_ = conduit.dataproviders.CATEGORY_MEDIA
+    _module_type_ = "source"
+    _in_type_ = "file/audio"
+    _out_type_ = "file/audio"
+    _icon_ = "banshee"
+
+    MUSIC_DB = os.path.join(os.path.expanduser("~"),".config", "banshee", "banshee.db")
+
+    def __init__(self, *args):
+        DataProvider.DataSource.__init__(self)
+        #Names of the playlists we know
+        self.allPlaylists = []
+        #Names we wish to sync
+        self.playlists = []
+        self.tracks = []
+
+
+    def _get_all_playlists(self):
+        allPlaylists = []
+        if os.path.exists(BansheeSource.MUSIC_DB):
+            #Create a connection to the database
+            con = sqlite.connect(BansheeSource.MUSIC_DB)
+            cur = con.cursor()
+            #Get a list of all playlists for the config dialog
+            cur.execute("SELECT PlaylistID, Name FROM Playlists")
+            for playlistid, playlistname in cur:
+                allPlaylists.append( (playlistid,playlistname) )
+            con.close()
+
+        return allPlaylists
+
+    def initialize(self):
+        return True
+        
+    def refresh(self):
+        DataProvider.DataSource.refresh(self)
+        #only work if Banshee is installed
+        if not os.path.exists(BansheeSource.MUSIC_DB):
+            raise Exceptions.RefreshError("Banshee is not installed")
+
+        #Stupid pysqlite thread stuff. 
+        #Connection must be made in the same thread
+        #as any execute statements
+        con = sqlite.connect(BansheeSource.MUSIC_DB)
+        cur = con.cursor()
+
+        for playlistid in self.playlists:
+            cur.execute("select Uri from Tracks INNER JOIN PlaylistEntries ON PlaylistEntries.TrackID=Tracks.TrackID where PlaylistID=%s" % (playlistid))
+            for Uri in cur:
+                self.tracks.append(Uri[0])
+
+        con.close()
+
+    def get_all(self):
+        DataProvider.DataSource.get_all(self)
+        return self.tracks
+
+    def get(self, LUID):
+        f = File.File(URI=LUID)
+        f.set_UID(LUID)
+        f.set_open_URI(LUID)
+
+        return f
+    
+    def finish(self):
+        DataProvider.DataSource.finish(self)
+        self.playlists = []
+
+    def configure(self, window):
+        import gtk
+        def col1_toggled_cb(cell, path, model ):
+            #not because we get this cb before change state
+            checked = not cell.get_active()
+            model[path][2] = checked
+            val = model[path][ID_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)
+
+            logd("Toggle '%s'(%s) to: %s" % (model[path][NAME_IDX], val, checked))
+            return
+
+        tree = Utils.dataprovider_glade_get_widget(
+                        __file__, 
+                        "config.glade",
+						"BansheeConfigDialog"
+						)
+        tagtreeview = tree.get_widget("tagtreeview")
+        #Build a list of all the tags
+        list_store = gtk.ListStore( gobject.TYPE_INT,       #ID_IDX
+                                    gobject.TYPE_STRING,    #NAME_IDX
+                                    gobject.TYPE_BOOLEAN,   #active
+                                    )
+        #Fill the list store
+        i = 0
+        for t in self._get_all_playlists():
+            list_store.append((t[ID_IDX],t[NAME_IDX],t[ID_IDX] in self.playlists))
+            i += 1
+        #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=2)
+                                    )
+
+        dlg = tree.get_widget("BansheeConfigDialog")
+        
+        response = Utils.run_dialog (dlg, window)
+        if response == True:
+            self.set_configured(True)
+        dlg.destroy()
+
+    def set_configuration(self, config):
+        self.playlists = []
+        for playlist in config.get("playlists", []):
+            self.playlists.append(playlist)
+        self.set_configured(True)
+            
+    def get_configuration(self):
+        return { "playlists" : self.playlists }
+
+    def get_UID(self):
+        return Utils.get_user_string()
+
+
Index: BansheeModule/Makefile.am
===================================================================
--- BansheeModule/Makefile.am	(revision 0)
+++ BansheeModule/Makefile.am	(revision 0)
@@ -0,0 +1,9 @@
+conduit_handlersdir = $(libdir)/conduit/modules/BansheeModule
+conduit_handlers_PYTHON = BansheeModule.py
+
+conduit_handlers_DATA = config.glade
+EXTRA_DIST = config.glade
+
+clean-local:
+	rm -rf *.pyc *.pyo
+


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