Package modules :: Module calendar_google
[hide private]
[frames] | no frames]

Source Code for Module modules.calendar_google

  1  """ 
  2  Syncronize with Google Calendar 
  3   
  4  This file is part of Pisi. 
  5   
  6  Pisi is free software: you can redistribute it and/or modify 
  7  it under the terms of the GNU General Public License as published by 
  8  the Free Software Foundation, either version 3 of the License, or 
  9  (at your option) any later version. 
 10   
 11  Pisi is distributed in the hope that it will be useful, 
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  GNU General Public License for more details. 
 15   
 16  You should have received a copy of the GNU General Public License 
 17  along with Pisi.  If not, see <http://www.gnu.org/licenses/> 
 18  """ 
 19   
 20  import gdata.calendar.service 
 21  import gdata.service 
 22  import atom 
 23  import sys,os 
 24  import datetime,time 
 25   
 26  import pisiprogress 
 27  from pisiconstants import * 
 28  from events import events 
 29   
30 -class SynchronizationModule(events.AbstractCalendarSynchronizationModule):
31 """ 32 The implementation of the interface L{events.AbstractCalendarSynchronizationModule} for the Google Calendar backend 33 """
34 - def __init__( self, modulesString, config, configsection, folder, verbose=False, soft=False):
35 """ 36 Constructor 37 38 Super class constructor (L{events.AbstractCalendarSynchronizationModule.__init__}) is called. 39 Local variables are initialized. 40 The settings from the configuration file are loaded. 41 The connection to the Google Gdata backend is established. 42 """ 43 events.AbstractCalendarSynchronizationModule.__init__(self, verbose, soft, modulesString, config, configsection, "Google Calendar") 44 self.folder = folder 45 self.newEvents = {} 46 self._googleevents = {} 47 self.batchOperations = gdata.calendar.CalendarEventFeed() 48 user = config.get(configsection,'user') 49 password = config.get(configsection, 'password') 50 self.calendarid = config.get(configsection,'calendarid') 51 self._login( user, password )
52
53 - def load(self):
54 """ 55 Load all data from backend 56 57 A single query is performed and the result set is parsed afterwards. 58 """ 59 feed = self.cal_client.GetCalendarEventFeed('/calendar/feeds/'+self.calendarid+'/private/full?max-results=%d' %(GOOGLE_CALENDAR_MAXRESULTS)) 60 for i, an_event in enumerate(feed.entry): 61 globalId, updated, attributes = self._geventToPisiEvent(an_event) 62 if globalId == None: 63 globalId = events.assembleID() 64 tmpEvent = events.Event( globalId, updated, attributes ) 65 tmpEvent.attributes['globalid'] = globalId 66 self.replaceEvent(globalId, tmpEvent) 67 else: 68 tmpEvent = events.Event( globalId, updated, attributes) 69 self._allEvents[globalId] = tmpEvent 70 self._googleevents[globalId] = an_event
71
72 - def _saveAddEvent(self, id ):
73 """ 74 Saves an event as part of saving modifications 75 """ 76 eventInstance = self.getEvent(id) 77 gevent = self._convertPisiEventToGoogle(eventInstance) 78 gevent.batch_id = gdata.BatchId(text=eventInstance.id) 79 self.batchOperations.AddInsert(entry=gevent)
80
81 - def _saveReplaceEvent(self, id):
82 """ 83 Replace event as part of saving modifications 84 """ 85 updatedevent = self.getEvent(id) 86 gevent = self._convertPisiEventToGoogle(updatedevent) 87 gevent.batch_id = gdata.BatchId(text=id) 88 gevent.id = atom.Id( id ) 89 editUri = self._googleevents[id].GetEditLink().href 90 gevent.link.append( atom.Link(editUri, 'edit', 'application/atom+xml') ) 91 self.batchOperations.AddUpdate(gevent)
92
93 - def _saveRemoveEvent( self, id ):
94 """ 95 Removes an event as part of saving modifications 96 """ 97 self._googleevents[id].batch_id = gdata.BatchId(text=id) 98 self.batchOperations.AddDelete(entry=self._googleevents[id])
99
100 - def _commitModifications(self):
101 """ 102 Makes changes permanent 103 """ 104 pisiprogress.getCallback().verbose("Commiting Google-calendar modifications") 105 response_feed = self.cal_client.ExecuteBatch(self.batchOperations, '/calendar/feeds/'+self.calendarid+'/private/full/batch') 106 for entry in response_feed.entry: 107 try: 108 pisiprogress.getCallback().verbose('batch id: %s' % (entry.batch_id.text)) 109 pisiprogress.getCallback().verbose('status: %s' % (entry.batch_status.code)) 110 pisiprogress.getCallback().verbose('reason: %s' % (entry.batch_status.reason)) 111 except AttributeError: 112 print "<<", entry.content.text, entry.title.text, "\n", entry
113
114 - def saveModifications(self ):
115 """ 116 Save whatever changes have come by 117 118 Iterates the history of actions and calls the corresponding supporting functions. 119 In the end, the commit supporting function L{_commitModifications} is called for writing through changes. 120 """ 121 pisiprogress.getCallback().verbose("\t\tSaving google calendar <%s> (%d)" %(self.getDescription(), len(self._history))) 122 i=0 123 for listItem in self._history: 124 action = listItem[0] 125 id = listItem[1] 126 if action == ACTIONID_ADD: 127 pisiprogress.getCallback().verbose("\t\t<google calendar> adding %s" %(id)) 128 self._saveAddEvent(id) 129 elif action == ACTIONID_DELETE: 130 pisiprogress.getCallback().verbose("\t\t<google calendar> deleting %s" %(id)) 131 self._saveRemoveEvent(id) 132 elif action == ACTIONID_MODIFY: 133 pisiprogress.getCallback().verbose("\t\t<google calendar> replacing %s" %(id)) 134 self._saveReplaceEvent(id) 135 i+=1 136 pisiprogress.getCallback().progress.setProgress(i * 80 / len(self._history)) 137 pisiprogress.getCallback().update('Storing') 138 139 self._commitModifications() 140 pisiprogress.getCallback().progress.setProgress(100) 141 pisiprogress.getCallback().update('Storing')
142 143
144 - def _convertToGoogle(self, dateTimeObject, allday ):
145 """ 146 Supporting function to assemble a date-time-object depending on the type of event (all day or special times) 147 """ 148 if not dateTimeObject: 149 return None 150 if allday: 151 return dateTimeObject.strftime('%Y-%m-%d') 152 else: 153 dt = dateTimeObject.strftime('%Y-%m-%dT%H:%M:%S.000%z') 154 if dateTimeObject.tzinfo: 155 dt = dt[:26] + ":" + dt[26:] 156 else: 157 dt+="Z" 158 return dt
159
160 - def _convertPisiEventToGoogle( self, event ):
161 """ 162 Supporting function to convert a PISI event (internal format) into a Google Gdata Calendar event 163 """ 164 gevent = gdata.calendar.CalendarEventEntry() 165 gevent.title = atom.Title(text=event.attributes['title']) 166 gevent.where.append(gdata.calendar.Where(value_string=event.attributes['location'])) 167 gevent.when.append(gdata.calendar.When(\ 168 start_time=self._convertToGoogle(event.attributes['start'],event.attributes['allday']), \ 169 end_time=self._convertToGoogle(event.attributes['end'],event.attributes['allday']))) 170 gevent.content = atom.Content(text=event.attributes['description']) 171 gevent.extended_property.append(gdata.calendar.ExtendedProperty(name='pisiid', value=event.attributes['globalid'] )) 172 if event.attributes.has_key('recurrence') and event.attributes['recurrence']: 173 gevent.recurrence = gdata.calendar.Recurrence(text=event.attributes['recurrence'].getData()) 174 return gevent
175
176 - def _geventToPisiEvent( self, event ):
177 """ 178 Converts a Google event to Pisi event (internal format) 179 """ 180 if event.recurrence: 181 recurrence = events.Recurrence() 182 recurrence.initFromData(event.recurrence.text) 183 # When there is a recurrence, the 'start' and 'end' is inside the recurrence text 184 start = recurrence.getDTStart() 185 end = recurrence.getDTEnd() 186 allday = recurrence.isAllDay() 187 else: 188 recurrence = None 189 (allday, start) = self._gtimeToDatetime(event.when[0].start_time ) 190 (allday, end) = self._gtimeToDatetime(event.when[0].end_time ) 191 attributes=\ 192 {'start':start, \ 193 'end':end, \ 194 'recurrence':recurrence, \ 195 'allday':allday, \ 196 'title':event.title.text, \ 197 'description':event.content.text, \ 198 'location':event.where[0].value_string,\ 199 'alarm':False, 'alarmmin':0 \ 200 } 201 (tmp, updated) = self._gtimeToDatetime(event.updated.text ) 202 203 pisiID = None 204 try: 205 for prop in event.extended_property: 206 if prop.name == 'pisiid': 207 pisiID = prop.value 208 attributes['globalid'] = pisiID 209 except BaseException: 210 pass # fair enough; this event hasn't been synchronized before 211 return pisiID, updated, attributes
212
213 - def _gtimeToDatetime(self, gtime):
214 """ 215 Converts Google normal way (RFC3339) to write date and time to a datetime instance 216 """ 217 allday = False 218 if len(gtime)==10: 219 allday = True 220 date = datetime.datetime(int(gtime[0:4]), int(gtime[5:7]), int(gtime[8:10]), tzinfo = events.UTC()) 221 return (allday, date.date()) 222 223 if gtime[23]=='Z': 224 tz = events.UTC() 225 else: 226 tz = events.CustomOffset("custom", gtime[23:]) 227 onlyDandT = datetime.datetime(int(gtime[0:4]), int(gtime[5:7]), int(gtime[8:10]), int(gtime[11:13]), int(gtime[14:16]), int(gtime[17:19]), 0, tz) 228 return (allday, onlyDandT)
229
230 - def _login( self, user, password ):
231 """ 232 Supporting function to perform login at Google's Calendar Web-Service API 233 """ 234 pisiprogress.getCallback().verbose("Google Calendar: Logging in with user %s" %(user)) 235 self.cal_client = gdata.calendar.service.CalendarService() 236 self.cal_client.email = user 237 self.cal_client.password = password 238 self.cal_client.source = GOOGLE_CALENDAR_APPNAME 239 self.cal_client.ProgrammaticLogin()
240