conduit r1418 - in trunk: . conduit/datatypes conduit/modules/EvolutionModule conduit/modules/FlickrModule conduit/modules/GoogleModule test/python-tests



Author: jstowers
Date: Fri Apr 11 12:52:07 2008
New Revision: 1418
URL: http://svn.gnome.org/viewvc/conduit?rev=1418&view=rev

Log:
2008-04-11  John Stowers  <john stowers gmail com>

	* conduit/modules/FlickrModule/FlickrModule.py: Set the caption
	of returned photos.

	* conduit/datatypes/Contact.py: Add prototypes for setters for 
	common contact data.
	
	* conduit/modules/GoogleModule/GoogleModule.py:
	* conduit/modules/GoogleModule/Makefile.am:
	* conduit/modules/GoogleModule/contacts-config.glade:
	* test/python-tests/TestDataProviderGoogle.py: First try at a gdata based
	google contacts dataprovider. Patch by Kevin Kubasik



Added:
   trunk/conduit/modules/GoogleModule/contacts-config.glade
Modified:
   trunk/ChangeLog
   trunk/conduit/datatypes/Contact.py
   trunk/conduit/modules/EvolutionModule/EvolutionModule.py
   trunk/conduit/modules/FlickrModule/FlickrModule.py
   trunk/conduit/modules/GoogleModule/GoogleModule.py
   trunk/conduit/modules/GoogleModule/Makefile.am
   trunk/test/python-tests/TestDataProviderGoogle.py

Modified: trunk/conduit/datatypes/Contact.py
==============================================================================
--- trunk/conduit/datatypes/Contact.py	(original)
+++ trunk/conduit/datatypes/Contact.py	Fri Apr 11 12:52:07 2008
@@ -1,6 +1,5 @@
 import vobject 
 import conduit.datatypes.DataType as DataType
-
 def parse_vcf(string):
     """
     Parses a vcf string, potentially containing many vcards
@@ -21,6 +20,7 @@
     def __init__(self, **kwargs):
         DataType.DataType.__init__(self)
         self.vcard = kwargs.get('vcard',vobject.vCard())
+      #  self.g_data = ''
 
     def set_from_vcard_string(self, string):
         self.vcard = vobject.readOne(string)
@@ -43,6 +43,15 @@
             if len(name) > 0:
                 return name
         return None
+        
+    def set_name(self, **kwargs):
+        raise NotImplementedError
+        
+    def set_email(self, **kwargs):
+        raise NotImplementedError
+        
+    def set_address(self, **kwargs):
+        raise NotImplementedError
 
     def __getstate__(self):
         data = DataType.DataType.__getstate__(self)
@@ -58,4 +67,5 @@
         
     def get_hash(self):
         return hash(self.get_vcard_string())
+    
 

Modified: trunk/conduit/modules/EvolutionModule/EvolutionModule.py
==============================================================================
--- trunk/conduit/modules/EvolutionModule/EvolutionModule.py	(original)
+++ trunk/conduit/modules/EvolutionModule/EvolutionModule.py	Fri Apr 11 12:52:07 2008
@@ -201,7 +201,7 @@
     _module_type_ = "twoway"
     _in_type_ = "event"
     _out_type_ = "event"
-    _icon_ = "contact-new"
+    _icon_ = "appointment-new"
 
     def __init__(self, *args):
         EvoBase.__init__(self, EvoCalendarTwoWay.DEFAULT_CALENDAR_URI)

Modified: trunk/conduit/modules/FlickrModule/FlickrModule.py
==============================================================================
--- trunk/conduit/modules/FlickrModule/FlickrModule.py	(original)
+++ trunk/conduit/modules/FlickrModule/FlickrModule.py	Fri Apr 11 12:52:07 2008
@@ -221,7 +221,9 @@
         title = str(photoInfo.photo[0].title[0].elementText)
         # get tags
         tagsNode = photoInfo.photo[0].tags[0]
-
+        # get caption
+        caption = photoInfo.photo[0].description[0].elementText
+        
         if hasattr(tagsNode, 'tag'):
             tags = tuple(tag.elementText for tag in tagsNode.tag)
         else:
@@ -230,6 +232,7 @@
         # create the file
         f = Photo.Photo (URI=url)
         f.set_open_URI(url)
+        f.set_caption(caption)
 
         # try to rename if a title is available
         # FIXME: this is far from optimal, also there should be 

Modified: trunk/conduit/modules/GoogleModule/GoogleModule.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/GoogleModule.py	(original)
+++ trunk/conduit/modules/GoogleModule/GoogleModule.py	Fri Apr 11 12:52:07 2008
@@ -3,6 +3,8 @@
 import datetime
 import dateutil.parser
 import vobject
+import operator
+import time
 from dateutil.tz import tzutc, tzlocal
 from gettext import gettext as _
 import logging
@@ -14,9 +16,11 @@
 import conduit.utils as Utils
 import conduit.Exceptions as Exceptions
 from conduit.datatypes import Rid
+import conduit.datatypes.Contact as Contact
 import conduit.datatypes.Event as Event
 import conduit.datatypes.Photo as Photo
 import conduit.datatypes.Video as Video
+import conduit.datatypes.File as File
 
 #Distributors, if you ship python gdata >= 1.0.10 then remove this line
 #and the appropriate directories
@@ -33,7 +37,8 @@
     MODULES = {
         "GoogleCalendarTwoWay" : { "type": "dataprovider" },
         "PicasaTwoWay" :         { "type": "dataprovider" },
-        "YouTubeSource" :        { "type": "dataprovider" },       
+        "YouTubeSource" :        { "type": "dataprovider" },    
+        "ContactsSource" :       { "type": "dataprovider" },    
     }
     log.info("Module Information: %s" % Utils.get_module_information(gdata, None))
 except (ImportError, AttributeError):
@@ -117,6 +122,8 @@
         return '/calendar/feeds/' + self.get_uri() + '/private/full'
 
 def convert_madness_to_datetime(inputDate):
+    log.debug('Attempting to parse the following: %s'%inputDate)
+    logging.debug('Attempting to parse the following: %s'%inputDate)
     dateStr = None
     dateDate = None
     dateDateTime = None
@@ -142,6 +149,9 @@
         
     if dateDateTime is not None:
         if dateDateTime.tzinfo is not None:
+            logging.warn("returning: %s",dateDateTime)
+            ts = dateDateTime.timetuple()
+            dateDateTime = dateDateTime.fromtimestamp(time.mktime(ts))
             return dateDateTime
         elif dateTZInfo is not None:
             return dateDateTime.replace(tzinfo=dateTZInfo)
@@ -149,6 +159,7 @@
             log.warn('Waring, assuming datetime ('+dateDateTime.isoformat()+') is UTC')
             return dateDateTime.replace(tzinfo=tzutc())
                 
+    
     raise TypeError('Unable to convert to datetime')
 
 def parse_google_recur(recurString, args):
@@ -256,7 +267,10 @@
     def get_mtime(self):
         #mtimes need to be naive and local
         #Shouldn't Conduit use non-naive mTimes?
-        mTimeLocal = self.mTime.astimezone(tzlocal())
+        try:
+            mTimeLocal = self.mTime.astimezone(tzlocal())
+        except ValueError:
+            mTimeLocal = self.mTime
         mTimeLocalWithoutTZ = mTimeLocal.replace(tzinfo=None)
         return mTimeLocalWithoutTZ
         
@@ -317,9 +331,13 @@
         if self.visibility is not None:
             iCalEvent.add('class').value = self.visibility
         if self.created is not None:
-            iCalEvent.add('created').value = self.created.astimezone(tzutc())    
+            try:
+                iCalEvent.add('created').value = self.created.astimezone(tzutc())
+            except ValueError: pass
         if self.mTime is not None:
-            iCalEvent.add('last-modified').value = self.mTime.astimezone(tzutc())
+            try:
+                iCalEvent.add('last-modified').value = self.mTime.astimezone(tzutc())
+            except ValueError: pass
         #iCalEvent.vevent.add('dtstamp').value = 
         if self.recurrence is not None:
             iCalEvent.add('rrule').value = self.recurrence
@@ -330,7 +348,6 @@
         returnStr = iCalEvent.serialize()
         log.debug("Created ICal Format :\n"+returnStr)
         return returnStr
-
     
 class GoogleCalendarTwoWay(GoogleBase, DataProvider.TwoWay):
 
@@ -340,7 +357,7 @@
     _module_type_ = "twoway"
     _in_type_ = "event"
     _out_type_ = "event"
-    _icon_ = "contact-new"
+    _icon_ = "appointment-new"
     
     def __init__(self):
         GoogleBase.__init__(self)
@@ -739,6 +756,119 @@
             return False
         return True
 
+
+class ContactsSource(GoogleBase, DataProvider.DataSource):
+    """
+    Contacts GData provider
+    """
+    _name_ = _("Google Contacts")
+    _description_ = _("Sync contacts from Google")
+    _category_ = conduit.dataproviders.CATEGORY_OFFICE
+    _module_type_ = "source"
+    _out_type_ = "contact"
+    _icon_ = "contact-new"
+
+    FEED = "http://www.google.com/m8/feeds/contacts/%s/base?max-results=25000";
+
+    def __init__(self, *args):
+        GoogleBase.__init__(self)
+        DataProvider.DataSource.__init__(self)
+        self.entries = None
+        self.feed = ""
+
+    def _set_username(self, username):
+        GoogleBase._set_username(self, username)
+        self.feed = self.FEED % self.username
+
+    def _do_login(self):
+        self.service = gdata.service.GDataService(service="cp", server="www.google.com")
+        self.service.ClientLogin(self.username, self.password)
+        
+    def _get_contact(self, LUID):
+        #get the gdata contact from google
+        gdc = self.service.Get(LUID)
+        if gdc is None or len(gdc.ToString()) < 1:
+            log.warn("Error getting/parsing gdata contact")
+            return None
+            
+        #FIXME: We should not be accessing the contact vcard directly.
+        #once we know what we can get from the gdata information, we should
+        #add the appropriate methods to the contact class to allow us to
+        #set these things
+        c = Contact.Contact()
+
+        c.vcard = vobject.vCard()
+        c.vcard.add('n')
+        c.vcard.n.value = vobject.vcard.Name(given="%s"%gdc.title.text)
+        
+        c.vcard.add('fn')
+        c.vcard.fn.value = "%s"%gdc.title.text
+        #isinstance(gdc, atom.Entry)
+        ee_names = map(operator.attrgetter('tag'),gdc.extension_elements)
+        if len(gdc.extension_elements) >0:
+            for e in [e for e in ee_names if e == 'email']:
+                c.vcard.add('email')
+                c.vcard.email.value = gdc.extension_elements[ee_names.index('email')].attributes['address']
+                c.vcard.email.type_param = 'INTERNET'
+            for e in [e for e in ee_names if e == 'phoneNumber']:
+                c.vcard.add('tel')
+                c.vcard.tel.value = gdc.extension_elements[ee_names.index('phoneNumber')].text
+                c.vcard.tel.type_param = gdc.extension_elements[ee_names.index('phoneNumber')].attributes['rel'].split('#')[1]
+            for e in [e for e in ee_names if e == 'postalAddress']:
+                c.vcard.add('adr')
+                c.vcard.adr.value = vobject.vcard.Address(gdc.extension_elements[ee_names.index('postalAddress')].text)
+               # c.vcard.adr.value =
+                c.vcard.adr.type_param = gdc.extension_elements[ee_names.index('postalAddress')].attributes['rel'].split('#')[1]
+        
+        #c.vcard.add('uid').value = gdc.id.text
+        c.set_UID(LUID)
+        c.set_mtime(convert_madness_to_datetime(gdc.updated.text))
+        c.set_open_URI(gdc.link[1].href)
+        return c    
+
+    def get_all(self):
+        DataProvider.DataSource.get_all(self)
+        # we can ask the server for everything thats changed after a certain date, including deletions
+        self._login()
+        contacts = self.service.GetFeed(self.feed).entry
+        return [str(contact.id.text) for contact in contacts]
+
+    def get(self, LUID):
+        DataProvider.DataSource.get(self, LUID)
+        return self._get_contact(LUID)
+
+    def delete(self, LUID):
+        self._login()
+        self.service.Delete(LUID)
+
+    def finish(self, aborted, error, conflict):
+        DataProvider.DataSource.finish(self)
+
+    def configure(self, window):
+        """
+        Configures the PicasaTwoWay
+        """
+        widget = Utils.dataprovider_glade_get_widget(
+                        __file__, 
+                        "contacts-config.glade", 
+                        "GoogleContactsConfigDialog")
+                        
+        #get a whole bunch of widgets
+        username = widget.get_widget("username")
+        password = widget.get_widget("password")
+        
+        #preload the widgets        
+        username.set_text(self.username)
+        password.set_text(self.password)
+        
+        dlg = widget.get_widget("GoogleContactsConfigDialog")
+        response = Utils.run_dialog (dlg, window)
+        if response == True:
+            self._set_username(username.get_text())
+            self._set_password(password.get_text())
+        dlg.destroy()    
+
+
 class YouTubeSource(DataProvider.DataSource):
     """
     Downloads YouTube videos using the gdata API.

Modified: trunk/conduit/modules/GoogleModule/Makefile.am
==============================================================================
--- trunk/conduit/modules/GoogleModule/Makefile.am	(original)
+++ trunk/conduit/modules/GoogleModule/Makefile.am	Fri Apr 11 12:52:07 2008
@@ -3,8 +3,8 @@
 conduit_handlersdir = $(libdir)/conduit/modules/GoogleModule
 conduit_handlers_PYTHON = GoogleModule.py
 
-conduit_handlers_DATA = calendar-config.glade picasa-config.glade youtube-config.glade
-EXTRA_DIST = calendar-config.glade picasa-config.glade youtube-config.glade
+conduit_handlers_DATA = calendar-config.glade picasa-config.glade youtube-config.glade contacts-config.glade
+EXTRA_DIST = calendar-config.glade picasa-config.glade youtube-config.glade contacts-config.glade
 
 clean-local:
 	rm -rf *.pyc *.pyo

Added: trunk/conduit/modules/GoogleModule/contacts-config.glade
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/contacts-config.glade	Fri Apr 11 12:52:07 2008
@@ -0,0 +1,199 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd";>
+
+<glade-interface>
+
+<widget class="GtkDialog" id="GoogleContactsConfigDialog">
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">Google Contacts</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">False</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="vbox30">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">5</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="hbuttonbox12">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="button32">
+	      <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="okBtn">
+	      <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="GtkLabel" id="label74">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">&lt;b&gt;Account Details&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="GtkLabel" id="label75">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">Email:</property>
+	  <property name="use_underline">False</property>
+	  <property name="use_markup">False</property>
+	  <property name="justify">GTK_JUSTIFY_LEFT</property>
+	  <property name="wrap">False</property>
+	  <property name="selectable">False</property>
+	  <property name="xalign">0</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="GtkEntry" id="username">
+	  <property name="visible">True</property>
+	  <property name="can_focus">True</property>
+	  <property name="editable">True</property>
+	  <property name="visibility">True</property>
+	  <property name="max_length">0</property>
+	  <property name="text" translatable="yes"></property>
+	  <property name="has_frame">True</property>
+	  <property name="invisible_char">â</property>
+	  <property name="activates_default">False</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkLabel" id="label76">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">Password:</property>
+	  <property name="use_underline">False</property>
+	  <property name="use_markup">False</property>
+	  <property name="justify">GTK_JUSTIFY_LEFT</property>
+	  <property name="wrap">False</property>
+	  <property name="selectable">False</property>
+	  <property name="xalign">0</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="GtkEntry" id="password">
+	  <property name="visible">True</property>
+	  <property name="can_focus">True</property>
+	  <property name="editable">True</property>
+	  <property name="visibility">False</property>
+	  <property name="max_length">0</property>
+	  <property name="text" translatable="yes"></property>
+	  <property name="has_frame">True</property>
+	  <property name="invisible_char">â</property>
+	  <property name="activates_default">False</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkHButtonBox" id="hbuttonbox13">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+	  <property name="spacing">0</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>

Modified: trunk/test/python-tests/TestDataProviderGoogle.py
==============================================================================
--- trunk/test/python-tests/TestDataProviderGoogle.py	(original)
+++ trunk/test/python-tests/TestDataProviderGoogle.py	Fri Apr 11 12:52:07 2008
@@ -7,7 +7,7 @@
 import random
 
 SAFE_CALENDAR_NAME="Conduit Project"
-SAFE_EVENT_UID="cee1p5i0ta73dfgjcn9l94allg google com"
+SAFE_EVENT_UID="2bh7mbagsc880g64qaps06tbp4 google com"
 MAX_YOUTUBE_VIDEOS=5
 
 if not is_online():
@@ -34,7 +34,7 @@
 ok("Found calendar: '%s'" % SAFE_CALENDAR_NAME, found)    
 
 #make a simple event
-hour=random.randint(12,24)
+hour=random.randint(12,23)
 ics="""BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -58,6 +58,23 @@
         name="event"
         )
         
+#setup the test
+test = SimpleTest(sourceName="ContactsSource")
+config = {
+    "username":     os.environ.get("TEST_USERNAME","conduitproject gmail com"),
+    "password":     os.environ["TEST_PASSWORD"],
+}
+test.configure(source=config)
+google = test.get_source().module
+
+#check we can get a contacts list
+contacts = google.get_all()
+num = len(contacts)
+ok("Got %s contacts" % num, num > 0)
+
+c = google.get(contacts[0])
+ok("Got contact", c != None)
+
 #Now a very simple youtube test...
 test = SimpleTest(sourceName="YouTubeSource")
 config = {



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