conduit r1418 - in trunk: . conduit/datatypes conduit/modules/EvolutionModule conduit/modules/FlickrModule conduit/modules/GoogleModule test/python-tests
- From: jstowers svn gnome org
- To: svn-commits-list gnome org
- Subject: conduit r1418 - in trunk: . conduit/datatypes conduit/modules/EvolutionModule conduit/modules/FlickrModule conduit/modules/GoogleModule test/python-tests
- Date: Fri, 11 Apr 2008 12:52:08 +0100 (BST)
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"><b>Account Details</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="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]