[PATCH] First pass at a banshee provider
- From: Don Smith <dcsmith gmail com>
- To: conduit-list gnome org
- Subject: [PATCH] First pass at a banshee provider
- Date: Thu, 01 Nov 2007 05:00:53 -0400
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"><b>Return Playlists</b></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]