conduit r1181 - trunk/conduit/modules/PicasaDesktopModule



Author: jstowers
Date: Thu Jan 10 09:56:41 2008
New Revision: 1181
URL: http://svn.gnome.org/viewvc/conduit?rev=1181&view=rev

Log:
Add reverse engineered support for Picasa v2.7

Added:
   trunk/conduit/modules/PicasaDesktopModule/
   trunk/conduit/modules/PicasaDesktopModule/Makefile.am
   trunk/conduit/modules/PicasaDesktopModule/PicasaDesktopModule.py
   trunk/conduit/modules/PicasaDesktopModule/config.glade

Added: trunk/conduit/modules/PicasaDesktopModule/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/PicasaDesktopModule/Makefile.am	Thu Jan 10 09:56:41 2008
@@ -0,0 +1,9 @@
+conduit_handlersdir = $(libdir)/conduit/modules/PicasaDesktopModule
+conduit_handlers_PYTHON = PicasaDesktopModule.py
+
+conduit_handlers_DATA = config.glade
+EXTRA_DIST = config.glade
+
+clean-local:
+	rm -rf *.pyc *.pyo
+

Added: trunk/conduit/modules/PicasaDesktopModule/PicasaDesktopModule.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/PicasaDesktopModule/PicasaDesktopModule.py	Thu Jan 10 09:56:41 2008
@@ -0,0 +1,214 @@
+import os
+import csv 
+import logging
+import xml.dom.minidom
+log = logging.getLogger("modules.Picasa")
+
+import conduit
+import conduit.Utils as Utils
+import conduit.Vfs as Vfs
+import conduit.Exceptions as Exceptions
+import conduit.dataproviders.DataProvider as DataProvider
+import conduit.datatypes.Photo as Photo
+
+from gettext import gettext as _
+
+MODULES = {
+	"PicasaDesktopSource" : { "type": "dataprovider" }
+}
+
+FILENAME_IDX = 0
+DISPLAYNAME_IDX = 1
+PHOTOS_IDX = 2
+PICASA_DIR = os.path.join(os.path.expanduser("~"),".picasa")
+
+# check if path exists
+if not os.path.exists(PICASA_DIR):
+    raise Exceptions.NotSupportedError("Picasa Not Installed")
+
+class PicasaDesktopSource(DataProvider.DataSource):
+
+    _name_ = _("Picasa Desktop")
+    _description_ = _("Sync your Picasa photos")
+    _category_ = conduit.dataproviders.CATEGORY_PHOTOS
+    _module_type_ = "source"
+    _in_type_ = "file/photo"
+    _out_type_ = "file/photo"
+    _icon_ = "picasa"
+
+    def __init__(self, *args):
+        DataProvider.DataSource.__init__(self)
+        self.need_configuration(False)
+        self.albums = []
+        self.enabledAlbums = []
+        
+    def _fix_picasa_image_filename(self, filename):
+        #Picasa stores the image filename in some weird relative format
+        #with $ = $HOME and $My Pictures = xdg pictures dir
+        parts = filename.split("\\")
+        if parts[0] == "$My Pictures":
+            #FIXME: Use xdg user dirs to get localised photo dir
+            parts[0] = os.path.join(os.environ['HOME'],'Pictures')
+        elif parts[0][0] == "$":
+            #Take care of other photos in ~ by replacing $ with $HOME
+            parts[0] = os.path.join(os.environ['HOME'],parts[0][1:])
+        elif parts[0] == "[z]":
+            #absolute paths
+            parts[0] = "/"
+        else:
+            log.warn("Could not convert picasa photo path to unix path")
+            return None
+            
+        path = os.path.abspath(os.sep.join(parts))
+        return path            
+            
+    def _get_all_albums(self):
+        #only work if picasa has been configured to use a CSV DB
+        #http://www.zmarties.com/picasa/
+        dbfile = os.path.join(PICASA_DIR,'drive_c','Program Files','Picasa2','db','dirscanner.csv')
+        if not os.path.exists(dbfile):
+            raise Exceptions.RefreshError("Picasa Not Configured to use CSV Database")    
+    
+        pals = []
+        #Open the CSV file and find all entries with Type = 19 (albums)
+        f = open(dbfile, 'rt')
+        try:
+            reader = csv.DictReader(f)
+            for row in reader:
+                if row['Type'] == '19':
+                    #wine picasa stores all pal files (that describes an album)
+                    #in the following base dir
+                    parts = [PICASA_DIR,
+                            'drive_c',
+                            'Documents and Settings',
+                            os.getlogin(),
+                            'Local Settings']
+                    #and then as given in the csv file
+                    #but first change the windows path to a linux one
+                    parts += row['Name'].split("\\")
+                    path = os.path.abspath(os.sep.join(parts))
+                    pals.append(path)
+        finally:
+            f.close()
+            
+        #parse each pal file to get album info
+        albums = []
+        for pal in pals:
+            log.debug("Parsing album file %s" % pal)
+            doc = xml.dom.minidom.parse(pal)
+            #album name
+            for prop in doc.getElementsByTagName('property'):
+                if prop.hasAttribute("name") and prop.getAttribute("name") == "name":
+                    name = prop.getAttribute("value")
+            #image filenames
+            photos = []
+            for f in doc.getElementsByTagName('filename'):
+                filename = self._fix_picasa_image_filename(f.firstChild.data)
+                if filename != None:
+                    photos.append(filename)
+            
+            albums.append((
+                        pal,            #FILENAME_IDX
+                        name,           #DISPLAYNAME_IDX 
+                        photos))        #PHOTOS_IDX
+        
+        return albums
+        
+    def initialize(self):
+        return True
+        
+    def refresh(self):
+        DataProvider.DataSource.refresh(self)
+        self.albums = []
+        try:
+            self.albums = self._get_all_albums()
+        except: 
+            #re-raise the refresh error
+            raise
+            
+        print self.albums
+            
+    def get_all(self):
+        DataProvider.DataSource.get_all(self)
+        photos = []
+        for album in self.albums:
+            if album[FILENAME_IDX] in self.enabledAlbums:
+                for photouri in album[PHOTOS_IDX]:
+                    if Vfs.uri_exists(photouri):
+                        photos.append(photouri)
+        return photos
+        
+    def get(self, LUID):
+        DataProvider.DataSource.get(self, LUID)
+        f = Photo.Photo(URI=LUID)
+        f.set_UID(LUID)
+        f.set_open_URI(LUID)
+        return f
+
+    def finish(self, aborted, error, conflict):
+        DataProvider.DataSource.finish(self)
+        self.albums = []
+
+    def configure(self, window):
+        import gobject
+        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][FILENAME_IDX]
+            if checked and val not in self.enabledAlbums:
+                self.enabledAlbums.append(val)
+            elif not checked and val in self.enabledAlbums:
+                self.enabledAlbums.remove(val)
+
+        tree = Utils.dataprovider_glade_get_widget(
+                        __file__, 
+                        "config.glade",
+						"PicasaDesktopConfigDialog"
+						)
+        tagtreeview = tree.get_widget("albumtreeview")
+        #Build a list of all the tags
+        list_store = gtk.ListStore( gobject.TYPE_STRING,    #FILENAME_IDX
+                                    gobject.TYPE_STRING,    #DISLAYNAME_IDX
+                                    gobject.TYPE_BOOLEAN,   #active
+                                    )
+        #Fill the list store
+        for t in self._get_all_albums():
+            list_store.append((
+                        t[FILENAME_IDX],
+                        t[DISPLAYNAME_IDX],
+                        t[FILENAME_IDX] in self.enabledAlbums)
+                        )
+        #Set up the treeview
+        tagtreeview.set_model(list_store)
+        #column 1 is the album name
+        tagtreeview.append_column(  gtk.TreeViewColumn(_("Album Name"), 
+                                    gtk.CellRendererText(), 
+                                    text=DISPLAYNAME_IDX)
+                                    )
+        #column 2 is a checkbox for selecting the album 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("PicasaDesktopConfigDialog")
+        
+        response = Utils.run_dialog (dlg, window)
+        if response == True:
+            self.set_configured(True)
+        dlg.destroy()
+        
+        print self.enabledAlbums
+
+    def get_configuration(self):
+        return {"enabledAlbums": self.enabledAlbums}
+
+    def get_UID(self):
+        return Utils.get_user_string()
+
+

Added: trunk/conduit/modules/PicasaDesktopModule/config.glade
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/PicasaDesktopModule/config.glade	Thu Jan 10 09:56:41 2008
@@ -0,0 +1,86 @@
+<?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="PicasaDesktopConfigDialog">
+    <property name="visible">True</property>
+    <property name="title" translatable="yes">Picasa</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;Return Photos In The Following Albums&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="albumscrolledwindow">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <child>
+                  <widget class="GtkTreeView" id="albumtreeview">
+                    <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>



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