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
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
73 if value != e.attributes[key]:
74 return False
75 return True
76
78 """
79 Merges the event (e) with itself. If two sections are different, use the section from the newest updated.
80 """
81
82 selfNew=False
83 if e.updated < self.updated:
84
85 selfNew=True
86 for key,value in self.attributes.iteritems():
87 if value != e.attributes[key]:
88
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
96 self.attributes[key] = e.attributes[key]
97 return self
98
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 """
122
125
128
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 """
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
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
159
162
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 """
173 """
174 Empty Constructor
175
176 Call L{initFromData} or L{initFromAttributes} to initialize.
177 """
178 pass
179
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
190
191
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
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"):
228 frame = vobject.iCalendar()
229 frame.add("standard")
230 frame.standard = vobject.icalendar.TimezoneComponent(UTC())
231 data += frame.standard.serialize()
232
233
234
235
236
237
238
239 self._data = data
240
242 """
243 GETTER
244 """
245 return self._data
246
248 """
249 GETTER
250 """
251 return self._dtstart
252
254 """
255 GETTER
256 """
257 return self._dtend
258
260 """
261 GETTER
262 """
263 return self._rrule
264
266 """
267 GETTER
268 """
269 return self._allDay
270
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
284 """
285 Operator overload
286
287 @return: NOT L{__eq__}
288 """
289 return not self.__eq__(other)
290
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
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"):
319
321 """
322 Getter.
323
324 @return: The link to the instance variable L{_allEvents}.
325 """
326 return self.allEntries()
327
329 """
330 GETTER
331
332 @return: The link with the given ID.
333 """
334 return self.getEntry(id)
335
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
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
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
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