def __set_uri(self, u): if u != self.__uri: # Stop the existing timer (if any) self.__remove_polling_timer() self.__uri = u isDAV = \ ((u[:7] == "http://") or (u[:8] == "https://")) if isDAV: # This may raise an error, but if it does it indicates a larger # problem that the user needs to address (or not use CalDAV) self.__backend = CalDAVbackend() else: self.__backend = iCalendarBackend() # Setup re-read callback self.__backend.registerChangeCallback(self.__file_change_callback, self.__uri) self.__reread_timer = self._add_timer(FileChangeEventPollingPeriod, \ self.__backend.pollForChanges) # Initialize events list self.__reload_events()
class iCalendarEvent(Control, IiCalendarEvent): # Core def __init__(self): # Init self.__uri = "" # We'll use the local timezone for naive datetimes by default, # but if there exists a VTIMEZONE element in the iCalendar file, # we use that instead # Not sure what to use for CalDAV self.__tz = tzlocal # a valid tzinfo subclass self.__cal = None self.__all_events = [] self.__events_this_month = [] self.__reread_timer = -1 # Setup the calendar backend self.__backend = iCalendarBackend # Reset the date we test against self.__current_date = datetime.min self.__current_month = datetime.now().month self.__current_year = datetime.now().year Control.__init__(self) def _shutdown(self): """Clean up when shutting down the Control. Necessary if ever this Control can be dynamically loaded and unloaded. """ self.__remove_polling_timer() def __getUID(self): unique = ''.join([random.choice(ascii_letters + digits) for i in range(16)]) return '%s-%s@%s' % (vDatetime(datetime.today()).ical(), unique, 'gdesklets') def __key(self, vevent): """Returns a key suitable for sorting on. It's a tuple of (start date, end date). This test is designed to give higher priority to sooner events. """ def vevent_as_datetime(vevent, prop, default): """Turns a VEVENT into a datetime object""" retval = vevent.decoded(prop, default) if type(retval) is date: retval = datetime(retval.year, retval.month, retval.day, tzinfo=tzlocal()) elif retval.tzinfo == None: retval = retval.replace(tzinfo=tzlocal()) return retval return (vevent_as_datetime(vevent, 'dtstart', datetime.min), \ vevent_as_datetime(vevent, 'dtend', datetime.max)) # Helpers def __remove_polling_timer(self): if self.__reread_timer >= 0: self._remove_timer(self.__reread_timer) self.__reread_timer = -1 def __file_change_callback(self, path, event): if event == FILE_CHANGED or event == FILE_CREATED: self.__reload_events() elif event == FILE_MOVED: print "File moved...new location: %s" % path self.__set_uri(path) elif event == FILE_DELETED: # don't panic...maybe it'll come back return def __reload_events(self): #TODO: remove when done debugging print "-------------" print "reloading events" self.__backend.refresh(self.__uri) # Try reading the calendar file try: self.__cal = self.__backend.read(self.__uri) except: # Don't bother updating "events" print "Failed to re-read calendar \"%s\" for (%s, %s); using existing information if we have any" % (self.__uri, self.__current_year, self.__current_month) return if (self.__cal != None) and (self.__uri != ""): # Grab all events, and sort them based on end date self.__all_events = [] min_date = datetime(self.__current_year, self.__current_month, 1, tzinfo=self.__tz()) max_date = min_date + relativedelta(months=+1) + relativedelta(microseconds=-1) # Is there a better way to do this than creating a # dummy event? min_event = Event() min_event.add('dtstart', min_date) min_event.add('dtend', min_date) max_event = Event() max_event.add('dtstart', max_date) max_event.add('dtend', max_date) print "stepping through events" # Step through all events for event in self.__cal.walk('vevent'): # Filter out events not in this month st = event.decoded('dtstart', datetime.min) if (not isinstance(st, datetime)): st = datetime(st.year, st.month, st.day, \ 0, 0, 0, 0, self.__tz()) en = event.decoded('dtend', st + relativedelta(days=+1)) if (not isinstance(en, datetime)): en = datetime(en.year, en.month, en.day, \ 0, 0, 0, 0, self.__tz()) event_length = relativedelta(en, st) # Non-recurring events if (not event.has_key('rrule') and \ not event.has_key('rdate') and \ not event.has_key('exrule') and \ not event.has_key('exdate')): # Make sure this event is within this month if self.__key(event) <= self.__key(min_event) >= 0 and \ self.__key(event) >= self.__key(max_event) <= 0: #print "%s - %s: %s" % (event.decoded('dtstart', " "), event.decoded('dtend'," "), event.decoded('summary', " ")) self.__all_events.append(event) # Recurring events # We have to check out each event since its start and # end dates don't correspond to how long the event # actually lasts else: #TODO: how about this? rule = '' for key in ['rrule', 'rdate', 'exrule', 'exdate']: if (not event.has_key(key)): continue if (isinstance(event[key], list)): for r in event[key]: rule += str(r) + '\n' else: rule = str(event[key]) r = rrulestr(rule, dtstart=st, tzinfos=self.__tz, \ forceset=True) events = r.between(min_date, max_date, inc=True) if (not events): continue for dt in events: new_event = Event() try: new_event.add('uid', event.decoded('uid')) except: pass new_event.add('dtstart', dt) new_event.add('dtend', dt + event_length) new_event.add('summary', event.decoded('summary', "")) new_event.add('location', event.decoded('location', "")) new_event.add('attendees', event.decoded('attendees', "")) #print "%s - %s: %s" % (dt, dt + event_length, event.decoded('summary', "")) self.__all_events.append(new_event) # sort the added events self.__all_events.sort(key=self.__key) # notify the desklet self._update("events") # Setters def __clear_latest_error(self, *args): pass def __set_uri(self, u): if u != self.__uri: # Stop the existing timer (if any) self.__remove_polling_timer() self.__uri = u isDAV = \ ((u[:7] == "http://") or (u[:8] == "https://")) if isDAV: # This may raise an error, but if it does it indicates a larger # problem that the user needs to address (or not use CalDAV) self.__backend = CalDAVbackend() else: self.__backend = iCalendarBackend() # Setup re-read callback self.__backend.registerChangeCallback(self.__file_change_callback, self.__uri) self.__reread_timer = self._add_timer(FileChangeEventPollingPeriod, \ self.__backend.pollForChanges) # Initialize events list self.__reload_events() def __set_year_and_month(self, (y, m)): """Expects a month from 1 - 12 and a year more recent than 1900""" if self.__current_month != m or \ self.__current_year != y: self.__current_month = m if 1 <= m <= 12 else datetime.now().month self.__current_year = y if 1900 <= y else datetime.now().year self.__reload_events()