Picasa patch



The other night I needed to send some picasaweb photos to
my shutterfly account, but the photos were from someone else,
not in my albums.  I changed a few things around in the
Picasa module so that you can use a private picasa album
where you know the guest user name, album name and auth
key (ie: the link a picasa user sends when you share a private
album).  I also added the ability to use a public album from a
guest user.

The major alteration that this involved was changing the init
logic to not require a login (google connection), so after creating
a picasaweb object, you must also call login with your credentials
if you want to access your private albums.  The other small
changes put a user variable under albums for tracking the
user that the album belongs to.

In order to allow for configuration I added a couple of new
helper functions to Image.py (basically modified copies of
the existing image resize helper).  This seems a little hack-y
but I think that the whole configuration ui logic is probably due
for some improvements anyway, so this should be okay for
now.

It looks like head is still broken perhaps?  So this patch is for
rev 1028, but I know the modifications fit in cleanly with head
and maybe the patch will apply cleanly, I don't know.

Jeremy
Index: conduit/dataproviders/Image.py
===================================================================
--- conduit/dataproviders/Image.py	(revision 1028)
+++ conduit/dataproviders/Image.py	(working copy)
@@ -60,7 +60,29 @@
             return self.NO_RESIZE
 
         return size
+
+    def _mode_combobox_build(self, combobox, selected):
+        import gtk
+        store = gtk.ListStore(str)
+        cell = gtk.CellRendererText()
+        combobox.pack_start(cell, True)
+        combobox.add_attribute(cell, 'text', 0)  
+        combobox.set_model(store)
+
+        for s in ["user", "guest", "private"]:
+            rowref = store.append( (s,) )
+            if s == selected:
+                combobox.set_active_iter(rowref)
         
+        if combobox.get_active() < 0:
+            combobox.set_active(0)
+
+    def _mode_combobox_get_active(self, combobox):
+        model = combobox.get_model()
+        active = combobox.get_active()
+        mode = model[active][0]
+        return mode
+        
     def _get_photo_info(self, photoID):
         """
         This should return the info for a given photo id,
Index: conduit/modules/PicasaModule/config.glade
===================================================================
--- conduit/modules/PicasaModule/config.glade	(revision 1028)
+++ conduit/modules/PicasaModule/config.glade	(working copy)
@@ -26,6 +26,30 @@
           </packing>
         </child>
         <child>
+          <widget class="GtkLabel" id="label2">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Access Mode:</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkComboBox" id="modecombobox">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+        <child>
           <widget class="GtkLabel" id="label75">
             <property name="visible">True</property>
             <property name="xalign">0</property>
@@ -34,7 +58,7 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">3</property>
+            <property name="position">5</property>
           </packing>
         </child>
         <child>
@@ -45,7 +69,7 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">4</property>
+            <property name="position">6</property>
           </packing>
         </child>
         <child>
@@ -57,7 +81,7 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">5</property>
+            <property name="position">7</property>
           </packing>
         </child>
         <child>
@@ -68,10 +92,60 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">6</property>
+            <property name="position">8</property>
           </packing>
         </child>
         <child>
+          <widget class="GtkLabel" id="label3">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Guest:</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">9</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkEntry" id="guest">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">10</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkLabel" id="label4">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Key:</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">11</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkEntry" id="key">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">12</property>
+          </packing>
+        </child>
+        <child>
           <widget class="GtkLabel" id="label78">
             <property name="visible">True</property>
             <property name="label" translatable="yes">&lt;b&gt;Saved Photo Settings&lt;/b&gt;</property>
@@ -80,7 +154,7 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">7</property>
+            <property name="position">13</property>
           </packing>
         </child>
         <child>
@@ -92,7 +166,7 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">8</property>
+            <property name="position">14</property>
           </packing>
         </child>
         <child>
@@ -103,7 +177,7 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">9</property>
+            <property name="position">15</property>
           </packing>
         </child>
         <child>
@@ -116,18 +190,18 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">10</property>
+            <property name="position">16</property>
           </packing>
         </child>
         <child>
-          <widget class="GtkComboBox" id="combobox1">
+          <widget class="GtkComboBox" id="resizecombobox">
             <property name="visible">True</property>
             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
           </widget>
           <packing>
             <property name="expand">False</property>
             <property name="fill">False</property>
-            <property name="position">11</property>
+            <property name="position">17</property>
           </packing>
         </child>
         <child internal-child="action_area">
Index: conduit/modules/PicasaModule/PicasaAPI/picasaweb.py
===================================================================
--- conduit/modules/PicasaModule/PicasaAPI/picasaweb.py	(revision 1028)
+++ conduit/modules/PicasaModule/PicasaAPI/picasaweb.py	(working copy)
@@ -17,6 +17,7 @@
 import urllib,urllib2,urlparse,mimetypes,mimetools
 from datetime import datetime
 from xml.dom.minidom import parseString
+import re
 
 # time format
 FORMAT_STRING = "%Y-%m-%dT%H:%M:%S"
@@ -128,6 +129,7 @@
     entry = "http://picasaweb.google.com/data/entry/api/";
     gallery = "user/%(userid)s?kind=album"
     album_by_id = "user/%(userid)s/albumid/%(aid)s?kind=photo"
+    album_by_name = "user/%(userid)s/album/%(aname)s?kind=photo"
     picture_by_id = "user/%(userid)s/albumid/%(aid)s/photoid/%(pid)s"
     delete_picture = entry + "user/%(userid)s/albumid/%(aid)s/photoid/%(pid)s/%(version)s" 
     post_url = feed + "user/%(userid)s"
@@ -138,7 +140,13 @@
 
     def _getalbumfeedbyid (userid, aid):
         return GDataApi.feed + GDataApi.album_by_id % {"userid" : userid, "aid" : aid }
+    
+    def _getalbumfeedbyname(userid, aname):
+        return GDataApi.feed + GDataApi.album_by_name % {"userid" : userid, "aname" : aname}
 
+    def _getprivatealbumfeed(userid, aname, key):
+        return GDataApi.getalbumfeedbyname(userid, aname) + "&authkey=%(key)s" % {"key" : key}
+
     def _getpicturefeed (userid, aid, pid):
         return GDataApi.feed + GDataApi.picture_by_id % {"userid" : userid, "aid" : aid, "pid" : pid }
 
@@ -153,6 +161,8 @@
 
     # create the static methods
     getalbumfeedbyid = staticmethod (_getalbumfeedbyid)
+    getalbumfeedbyname = staticmethod (_getalbumfeedbyname)
+    getprivatealbumfeed = staticmethod (_getprivatealbumfeed)
     getgalleryfeed = staticmethod (_getgalleryfeed)
     getpicturefeed = staticmethod (_getpicturefeed)
     getposturl = staticmethod (_getposturl)
@@ -208,7 +218,14 @@
 ###############################################################################
 class PicasaWeb:
 ###############################################################################
-    def __init__(self, user,password):
+    def __init__(self):
+        """ 
+        We do not need to be authenticated for anonymous retrieval or 
+        when a guests private url is known
+        """
+        self.__gc = None
+    
+    def login(self, user, password):
         """ Create a PicasaWeb instance for the account user/password """
         self.__gc=GoogleConnection(user,password)
 
@@ -217,9 +234,22 @@
         Get a dictionary of PicasaAlbum available on this account
         accessible by album name
         """
+        self.getAlbumsByUser(self.__gc.user)
+
+    def getAlbumsByUser(self, user):
+        """
+        Get a dictionary of PicasaAlbums available for a particular user
+        If the user is logged in, all albums will appear, if a guest is
+        specified only the guests shared albums will appear.
+        """
         l={}
+        
+        if self.__gc:
+            headers = self.__gc.getauthheaders()
+        else:
+            headers = {}
 
-        request = mkRequest (GDataApi.getgalleryfeed (self.__gc.user), headers=self.__gc.getauthheaders())
+        request = mkRequest (GDataApi.getgalleryfeed(user), headers = headers)
         response = opener.open(request)
         xml=response.read()
         response.close()
@@ -273,30 +303,59 @@
     def createAlbumFromXml (self, element):
         name = getText(element.getElementsByTagName("title")[0]) # title
         id = getText(element.getElementsByTagName("gphoto:id")[0])  # gphoto:id
+        user = getText(element.getElementsByTagName("gphoto:user")[0]) # gphoto:user
 
-        return PicasaAlbum (self.__gc, id, name)
+        return PicasaAlbum (self.__gc, user, id, name)
 
+    def getPrivatePhotos(self, user, name, key):
+        feed = GDataApi.getprivatealbumfeed(user, name, key)
 
+        request = mkRequest(feed)
+        response = opener.open(request)
+        xml = response.read()
+        response.close()
+
+        root=parseString(xml).documentElement
+        aid = getText(root.getElementsByTagName("gphoto:id")[0]) # gphoto.albumid
+        
+        album = PicasaAlbum(self.__gc, user, name, aid)
+        
+        l = {}
+
+        for i in root.getElementsByTagName("entry"):
+            photo = album.createPhotoFromXml(i)
+            l[photo.id] = photo
+
+        return l
+
 ###############################################################################
 class PicasaAlbum(object):
 ###############################################################################
     __name=None
     name=property(lambda s: s.__name)
     id=property(lambda s: s.__id)
+    user = property(lambda s: s.__user)
 
+    __user = None
     __id=None
     __gc=None
 
-    def __init__(self,gc,id,name):
+    def __init__(self, gc, user, id, name):
         """ should be only called by PicasaWeb """
         self.__gc = gc
+        self.__user = user					# Album owner (not logged-in user)
         self.__name = name
         self.__id = utf8(id)
 
     def getPhotos(self):
-        feed = GDataApi.getalbumfeedbyid (self.__gc.user, self.__id)
+        feed = GDataApi.getalbumfeedbyid (self.user, self.__id)
 
-        request = mkRequest(feed, headers=self.__gc.getauthheaders())
+        if self.__gc:
+            headers = self.__gc.getauthheaders()
+        else:
+            headers = {}
+
+        request = mkRequest(feed, headers = headers)
         response = opener.open(request)
         xml=response.read()
         response.close()
Index: conduit/modules/PicasaModule/PicasaModule.py
===================================================================
--- conduit/modules/PicasaModule/PicasaModule.py	(revision 1028)
+++ conduit/modules/PicasaModule/PicasaModule.py	(working copy)
@@ -30,8 +30,11 @@
         Image.ImageTwoWay.__init__(self)
         self.need_configuration(True)
         
+        self.mode = "None"
         self.username = ""
         self.password = ""
+        self.guest = ""
+        self.key = ""
         self.album = ""
         self.imageSize = "None"
 
@@ -52,18 +55,35 @@
         return ("image/jpeg", )
         
     def refresh(self):
+        """ 
+        Needs to accomodate for multiple usage scenarios:
+        1 - Login and get / create albums and photos
+        2 - No login and only get albums photos
+        3 - No login and only get photos (maybe 1 album?)
+        """
         Image.ImageTwoWay.refresh(self)
-        self.gapi = PicasaWeb(self.username, self.password)
+        self.gapi = PicasaWeb()
+        
+        if self.mode == "user":
+            self.gapi.login(self.username, self.password)
+            albums = self.gapi.getAlbumsByUser(self.username)
+            if albums.has_key(self.album):
+                self.galbum = albums[self.album]
+            else:
+                self.galbum = self.gapi.createAlbum(self.album, public=False)
+            self.gphotos = self.galbum.getPhotos()
+        
+        elif self.mode == "guest":
+            albums = self.gapi.getAlbumsByUser(self.guest)
+            if albums.has_key(self.album):
+                self.galbum = albums[self.album]
+            else:
+                raise Exceptions.SyncronizeError("Album does not exist.")
+            self.gphotos = self.galbum.getPhotos()
+        
+        elif self.mode == "private":
+            self.gphotos = self.gapi.getPrivatePhotos(self.guest, self.album, self.key)
 
-        albums = self.gapi.getAlbums ()
-        if not albums.has_key (self.album):
-            self.galbum = self.gapi.createAlbum (self.album, public=False)
-        else:
-            self.galbum = albums[self.album]
-
-        self.gphotos = self.galbum.getPhotos()
-
-
     def get_all (self):
         return self.gphotos.keys()
 
@@ -112,14 +132,20 @@
         #get a whole bunch of widgets
         username = widget.get_widget("username")
         password = widget.get_widget("password")
+        guest = widget.get_widget("guest")
+        key = widget.get_widget("key")
         album = widget.get_widget("album")                
         
-        #preload the widgets        
+        #preload the widgets
         username.set_text(self.username)
         password.set_text(self.password)
+        guest.set_text(self.guest)
+        key.set_text(self.key)
         album.set_text (self.album)
 
-        resizecombobox = widget.get_widget("combobox1")
+        modecombobox = widget.get_widget("modecombobox")
+        self._mode_combobox_build(modecombobox, self.mode)
+        resizecombobox = widget.get_widget("resizecombobox")
         self._resize_combobox_build(resizecombobox, self.imageSize)
         
         dlg = widget.get_widget("PicasaTwoWayConfigDialog")
@@ -127,8 +153,12 @@
         response = Utils.run_dialog (dlg, window)
 
         if response == True:
+            self.mode = self._mode_combobox_get_active(modecombobox)
+        
             self.username = username.get_text()
             self.password = password.get_text()
+            self.guest = guest.get_text()
+            self.key = key.get_text()
             self.album = album.get_text()
 
             self.imageSize = self._resize_combobox_get_active(resizecombobox)
@@ -139,24 +169,30 @@
         
     def get_configuration(self):
         return {
-            "imageSize" : self.imageSize,
+            "mode" : self.mode,
             "username" : self.username,
             "password" : self.password,
-            "album" : self.album
+            "guest" : self.guest,
+            "key" : self.key,
+            "album" : self.album,
+            "imageSize" : self.imageSize,
             }
-            
+    
     def is_configured (self):
-        if len(self.username) < 1:
-            return False
+        if (self.mode == "user") and self.username and self.password and self.album:
+            return True
         
-        if len(self.password) < 1:
-            return False
-            
-        if len(self.album) < 1:
-            return False
-            
-        return True
+        if (self.mode == "guest") and self.guest and self.album:
+            return True
+        
+        if (self.mode == "private") and self.guest and self.album and self.key:
+            return True
+        
+        return False
+    
+    def get_UID(self):
+        if self.mode == "user":
+            return self.username+":"+self.album
+        else:
+            return self.guest+":"+self.album
 
-    def get_UID(self):
-        return self.username
-            


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