Package events :: Module events
[hide private]
[frames] | no frames]

Source Code for Module events.events

  1  """ 
  2  Our own calendar-event-object. 
  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 datetime 
 21  import random 
 22  import vobject 
 23   
 24  import pisiprogress 
 25  import pisiinterfaces 
 26  from pisiconstants import * 
 27   
 28  KNOWN_ATTRIBUTES = ['start', 'end', 'recurrence', 'allday', 'title', 'description', 'location', 'alarm', 'alarmmin'] 
 29  """List of attribute names, which have to be set for Event instances""" 
 30   
 31  UTC_STRING = """BEGIN:VTIMEZONE 
 32  TZID:UTC 
 33  BEGIN:STANDARD 
 34  DTSTART:20000101T000000 
 35  RRULE:FREQ=YEARLY;BYMONTH=1 
 36  TZNAME:UTC 
 37  TZOFFSETFROM:+0000 
 38  TZOFFSETTO:+0000 
 39  END:STANDARD 
 40  END:VTIMEZONE""" 
 41   
42 -class Event(pisiinterfaces.Syncable):
43 """ 44 Holds information for a single event (Calendar entry) instance 45 """ 46
47 - def __init__( self, id, updated, attributes, attributesToUTF = True):
48 """ 49 Initialize event. 50 @param id: is the id the module uses to id the event 51 @param updated: datetime instance 52 @param attributes: a dictionary with attributes. See U{http://projects.openmoko.org/plugins/wiki/index.php?Developer&id=156&type=g} for more help. 53 """ 54 pisiinterfaces.Syncable.__init__(self, id, attributes) 55 self.updated = updated 56 if attributesToUTF: 57 for key in attributes.keys(): 58 if type(attributes[key]) == str: 59 attributes[key] = attributes[key].decode("utf-8")
60
61 - def compare(self, e):
62 """ 63 Compares this event with another one 64 65 @return: True, if all attributes (L{KNOWN_ATTRIBUTES}) match, otherwise False 66 """ 67 for key,value in self.attributes.iteritems(): 68 if key not in KNOWN_ATTRIBUTES: 69 continue 70 if (value == "" or value == None) and (e.attributes[key] == "" or e.attributes[key] == None): 71 continue 72 # print key, value, e.attributes[key] 73 if value != e.attributes[key]: 74 return False 75 return True
76
77 - def merge( self, e ):
78 """ 79 Merges the event (e) with itself. If two sections are different, use the section from the newest updated. 80 """ 81 # Find which is newer 82 selfNew=False 83 if e.updated < self.updated: 84 # self is newer 85 selfNew=True 86 for key,value in self.attributes.iteritems(): 87 if value != e.attributes[key]: 88 # The events differ in this field 89 if not selfNew: 90 self.attributes[key] = e.attributes[key] 91 del e.attributes[key] 92 if not selfNew: 93 for key,value in e.attributes.iteritems(): 94 if value != self.attributes[key]: 95 # The events differ in this field 96 self.attributes[key] = e.attributes[key] 97 return self
98
99 - def prettyPrint ( self ):
100 """ 101 Prints all attributes 'nicely'.. 102 """ 103 print "\t_PrettyPrint of id: %s" %self.id 104 print "\t\t- Updated = ",self.updated 105 for key,value in self.attributes.iteritems(): 106 print "\t\t- ",key," = ",value
107 108 109 ZERO = datetime.timedelta(0) 110 """Pre-set timedelta of 0 for L{UTC}""" 111 HOUR = datetime.timedelta(hours=1) 112 """Pre-set timedelta of 1 for L{UTC}""" 113
114 -class UTC(datetime.tzinfo):
115 """ 116 Timezone-Info for UTC 117 118 See U{http://iorich.caltech.edu/~t/transfer/python-trunk-doc/library/datetime.html} for details. 119 """
120 - def utcoffset(self, dt):
121 return ZERO
122
123 - def tzname(self, dt):
124 return "UTC"
125
126 - def dst(self, dt):
127 return ZERO
128
129 -class CustomOffset(datetime.tzinfo):
130 """ 131 Custom Timezone-Info 132 133 Takes a String containing Timezone Information (e.g. +04:00) and creates the corresponding Timezone Info Object. 134 135 See U{http://iorich.caltech.edu/~t/transfer/python-trunk-doc/library/datetime.html} for more information. 136 """
137 - def __init__(self, name, st):
138 """ 139 Constructor 140 141 Parses the string and saves the details in local variables. 142 """ 143 self._name = name 144 self._hour = int(st[1:3]) 145 self._min = int(st[4:5]) 146 self._isPositive = st[0] == "+"
147
148 - def utcoffset(self, dt):
149 """ 150 Calculates timedelta 151 """ 152 if self._isPositive: 153 return datetime.timedelta(hours = self._hour, minutes = self._min) 154 else: 155 return datetime.timedelta(hours = - self._hour, minutes = - self._min)
156
157 - def tzname(self, dt):
158 return self._name
159
160 - def dst(self, dt):
161 return ZERO
162
163 -class Recurrence:
164 """ 165 Recurrence infomation for an event; this is attached as an attribute to a "normal" event 166 167 The entire ICS information is provided as one String in ICalendar format. This string is stored and returned on request; it is 168 as well parsed, so you can request single information chunks (DTStart, DTEnd, RROLE) from it. 169 170 The other way around is working as well; provide the information chunks and the ICalendar formatted string is computed. 171 """
172 - def __init__(self):
173 """ 174 Empty Constructor 175 176 Call L{initFromData} or L{initFromAttributes} to initialize. 177 """ 178 pass
179
180 - def initFromData(self, data):
181 """ 182 Initialize a recurrence from ICalendar formatted String 183 """ 184 self._data = data 185 186 try: 187 v = vobject.readComponents(data).next() 188 except: 189 # some stupid Google Calendar recurrence entries do come without time zone information 190 # this cause ParseError in vobject lib; therefore we do another attempt with manually attached 191 # UTC information (stupid, but seems to work) 192 v = vobject.readComponents(data + "\n" + UTC_STRING).next() 193 194 self._allDay = False 195 try: 196 self._dtstart = vobject.icalendar.DateOrDateTimeBehavior.transformToNative(v.dtstart).value 197 if type(self._dtstart) == datetime.date: 198 self._allDay = True 199 elif self._dtstart.tzinfo == None: 200 self._dtstart = self._dtstart.replace(tzinfo = UTC()) 201 except BaseException: 202 self._dtstart = None 203 try: 204 self._dtend = vobject.icalendar.DateOrDateTimeBehavior.transformToNative(v.dtend).value 205 if type(self._dtend) == datetime.datetime and self._dtend.tzinfo == None: 206 self._dtend = self._dtend.replace(tzinfo = UTC()) 207 except BaseException: 208 self._dtend = None 209 try: 210 self._rrule = v.rrule 211 except BaseException: 212 self._rrule = None
213
214 - def initFromAttributes(self, rrule, dtstart, dtend = None, isAllDay = False):
215 """ 216 Initialize a recurrence from the information chunks 217 """ 218 self._rrule = rrule 219 self._dtstart = dtstart 220 self._dtend = dtend 221 self._allDay = isAllDay 222 223 data = self._rrule.serialize() + self._dtstart.serialize() 224 if self._dtend: 225 data += dtend.serialize() 226 227 if type(self._dtstart.value) == datetime.date or self._dtstart.serialize().strip().endswith("Z"): # special handling for all day recurrences and UTCs 228 frame = vobject.iCalendar() 229 frame.add("standard") 230 frame.standard = vobject.icalendar.TimezoneComponent(UTC()) 231 data += frame.standard.serialize() 232 233 # file = open("/tmp/pisi-ics.data", "w") 234 # file.write("from attributes") 235 # file.write(data) 236 # file.close() 237 # import os 238 # os.system("gedit /tmp/pisi-ics.data") 239 self._data = data
240
241 - def getData(self):
242 """ 243 GETTER 244 """ 245 return self._data
246
247 - def getDTStart(self):
248 """ 249 GETTER 250 """ 251 return self._dtstart
252
253 - def getDTEnd(self):
254 """ 255 GETTER 256 """ 257 return self._dtend
258
259 - def getRRule(self):
260 """ 261 GETTER 262 """ 263 return self._rrule
264
265 - def isAllDay(self):
266 """ 267 GETTER 268 """ 269 return self._allDay
270
271 - def __eq__(self, other):
272 """ 273 Operator overload 274 275 Checks whether all items in the recurrences (rrule, dtstart, dtend) match. 276 """ 277 if other == None: 278 return False 279 if type(other) == str: 280 return False 281 return self._rrule == other._rrule and self._dtstart != other._dtstart and self._dtend != other._dtend
282
283 - def __ne__(self, other):
284 """ 285 Operator overload 286 287 @return: NOT L{__eq__} 288 """ 289 return not self.__eq__(other)
290
291 - def prettyPrint ( self ):
292 """ 293 Prints all attributes 'nicely'.. 294 """ 295 print "\t_PrettyPrint of Recurrence" 296 print "\t\tStart:\t%s" %(self._dtstart) 297 print "\t\tEnd:\t%s" %(self._dtend) 298 print "\t\tRRule:\t%s" %(self._rrule)
299 300
301 -class AbstractCalendarSynchronizationModule(pisiinterfaces.AbstractSynchronizationModule):
302 """ 303 Super class for all synchronization modules, which aim to synchronize contacts information. 304 305 Each Synchronization class implementing contact synchronization should inherit from this class. 306 @ivar _allEvents: Dictionary to hold all Calendar instances for the implementation. 307 @ivar _history: Keeps track of all changes applied to the container for this data source (for later write through) 308 """ 309
310 - def __init__(self, verbose, soft, modulesString, config, configsection, name = "unkown contact source"):
311 """ 312 Constructor 313 314 Instance variables are initialized. 315 """ 316 pisiinterfaces.AbstractSynchronizationModule.__init__(self, verbose, soft, modulesString, config, configsection, name) 317 self._allEvents = self._allEntries 318 self._history = []
319
320 - def allEvents( self ):
321 """ 322 Getter. 323 324 @return: The link to the instance variable L{_allEvents}. 325 """ 326 return self.allEntries()
327
328 - def getEvent(self, id):
329 """ 330 GETTER 331 332 @return: The link with the given ID. 333 """ 334 return self.getEntry(id)
335
336 - def addEvent( self, eventInstance ):
337 """ 338 Saves an event for later writing 339 340 One entry is added to the history list (L{_history}) with action id for 'add' and the new instance is passed on to the super class method L{addEntry}. 341 """ 342 self.addEntry(eventInstance) 343 self._history.append([ACTIONID_ADD, eventInstance.getID()])
344
345 - def replaceEvent( self, id, updatedEvent):
346 """ 347 Replaces an existing calendar entry with a new one. 348 349 One entry is added to the history list (L{_history}) with action id for 'replace' and the old instance is replaced by the new one by passing it on to the super class method L{replaceEntry}. 350 """ 351 self.replaceEntry(id, updatedEvent) 352 self._history.append([ACTIONID_MODIFY, id])
353
354 - def removeEvent( self, id ):
355 """ 356 Removes an event entry 357 358 One entry is added to the history list (L{_history}) with action id for 'delete' and the delete command is passed on to the super class method L{removeEntry}. 359 """ 360 self.removeEntry(id) 361 self._history.append([ACTIONID_DELETE, id])
362
363 -def assembleID():
364 """ 365 Assembles a unique ID for PISI events 366 367 A random number is generated. 368 """ 369 return str(random.randint(0, 100000000000000000000000))
370