# HG changeset patch # User Paul Boddie # Date 1438891211 -7200 # Node ID d1bcf16e6972a16d7b734e131876c32131afd54f # Parent d885c56a70741a0ce4ba4dfd2489e975afd664b1 Moved common free/busy functionality into the object class, fixing the computation of active periods. Moved the aggregation of event recurrence information into the store class. diff -r d885c56a7074 -r d1bcf16e6972 imip_store.py --- a/imip_store.py Thu Aug 06 20:02:14 2015 +0200 +++ b/imip_store.py Thu Aug 06 22:00:11 2015 +0200 @@ -43,6 +43,8 @@ def release_lock(self, user): FileBase.release_lock(self, user) + # Utility methods. + def _set_defaults(self, t, empty_defaults): for i, default in empty_defaults: if i >= len(t): @@ -103,6 +105,8 @@ finally: self.release_lock(user) + # Store object access. + def _get_object(self, user, filename): """ @@ -162,6 +166,8 @@ return True + # Event and event metadata access. + def get_events(self, user): "Return a list of event identifiers." @@ -172,6 +178,31 @@ return [name for name in listdir(filename) if isfile(join(filename, name))] + def get_all_events(self, user): + + "Return a set of (uid, recurrenceid) tuples for all events." + + uids = self.get_events(user) + + all_events = set() + for uid in uids: + all_events.add((uid, None)) + all_events.update([(uid, recurrenceid) for recurrenceid in self.get_recurrences(user, uid)]) + + return all_events + + def get_active_events(self, user): + + "Return a set of uncancelled events of the form (uid, recurrenceid)." + + all_events = self.get_all_events(user) + + # Filter out cancelled events. + + cancelled = self.get_cancellations(user) or [] + all_events.difference_update(cancelled) + return all_events + def get_event(self, user, uid, recurrenceid=None): """ @@ -310,6 +341,8 @@ return True + # Free/busy period access. + def get_freebusy(self, user): "Get free/busy details for the given 'user'." @@ -352,6 +385,8 @@ self._set_table(user, filename, map(lambda fb: fb.as_tuple(strings_only=True), freebusy)) return True + # Object status details access. + def _get_requests(self, user, queue): "Get requests for the given 'user' from the given 'queue'." diff -r d885c56a7074 -r d1bcf16e6972 imiptools/data.py --- a/imiptools/data.py Thu Aug 06 20:02:14 2015 +0200 +++ b/imiptools/data.py Thu Aug 06 22:00:11 2015 +0200 @@ -28,7 +28,7 @@ get_duration, get_period, get_period_item, \ get_recurrence_start_point, \ get_tzid, to_datetime, to_timezone, to_utc_datetime -from imiptools.period import Period, RecurringPeriod, period_overlaps +from imiptools.period import FreeBusyPeriod, Period, RecurringPeriod, period_overlaps from vCalendar import iterwrite, parse, ParseError, to_dict, to_node from vRecurrence import get_parameters, get_rule import email.utils @@ -213,11 +213,62 @@ # Subtract any recurrences from the free/busy details of a # parent object. - if not is_replaced(p, recurrenceids, tzid): + if not p.is_replaced(recurrenceids): active.append(p) return active + def get_freebusy_period(self, period, only_organiser=False): + + """ + Return a free/busy period for the given 'period' provided by this + object, using the 'only_organiser' status to produce a suitable + transparency value. + """ + + return FreeBusyPeriod( + period.get_start_point(), + period.get_end_point(), + self.get_value("UID"), + only_organiser and "ORG" or self.get_value("TRANSP") or "OPAQUE", + self.get_recurrenceid(), + self.get_value("SUMMARY"), + self.get_value("ORGANIZER") + ) + + def get_participation_status(self, participant): + + """ + Return the participation status of the given 'participant', with the + special value "ORG" indicating organiser-only participation. + """ + + attendees = self.get_value_map("ATTENDEE") + organiser = self.get_value("ORGANIZER") + + for attendee, attendee_attr in attendees.items(): + if attendee == participant: + return attendee_attr.get("PARTSTAT", "NEEDS-ACTION") + + else: + if organiser == participant: + return "ORG" + + return None + + def get_participation(self, partstat, include_needs_action=False): + + """ + Return whether 'partstat' indicates some kind of participation in an + event. If 'include_needs_action' is specified as a true value, events + not yet responded to will be treated as events with tentative + participation. + """ + + return not partstat in ("DECLINED", "DELEGATED", "NEEDS-ACTION") or \ + include_needs_action and partstat == "NEEDS-ACTION" or \ + partstat == "ORG" + def get_tzid(self): """ diff -r d885c56a7074 -r d1bcf16e6972 tools/make_freebusy.py --- a/tools/make_freebusy.py Thu Aug 06 20:02:14 2015 +0200 +++ b/tools/make_freebusy.py Thu Aug 06 22:00:11 2015 +0200 @@ -23,32 +23,10 @@ from imiptools.data import get_window_end, Object from imiptools.dates import get_default_timezone -from imiptools.period import FreeBusyPeriod, is_replaced from imiptools.profile import Preferences from imip_store import FileStore, FilePublisher import sys -def get_periods(fb, obj, tzid, window_end, only_organiser, recurrenceids): - - """ - Update free/busy details 'fb' with the actual periods associated with the - event 'obj'. - """ - - recurrenceid = obj.get_recurrenceid() - - for p in obj.get_periods(tzid, window_end): - if recurrenceid or not is_replaced(p, recurrenceids, tzid): - fb.append(FreeBusyPeriod( - p.get_start_point(), - p.get_end_point(), - obj.get_value("UID"), - only_organiser and "ORG" or obj.get_value("TRANSP") or "OPAQUE", - recurrenceid, - obj.get_value("SUMMARY"), - obj.get_value("ORGANIZER") - )) - # Main program. if __name__ == "__main__": @@ -83,19 +61,9 @@ store = FileStore() publisher = FilePublisher() - # Get all identifiers for events. - - uids = store.get_events(user) + # Get identifiers for uncancelled events. - all_events = set() - for uid in uids: - all_events.add((uid, None)) - all_events.update([(uid, recurrenceid) for recurrenceid in store.get_recurrences(user, uid)]) - - # Filter out cancelled events. - - cancelled = store.get_cancellations(user) or [] - all_events.difference_update(cancelled) + all_events = store.get_active_events(user) # Obtain event objects. @@ -111,30 +79,12 @@ fb = [] for obj in objs: - attendees = obj.get_value_map("ATTENDEE") - organiser = obj.get_value("ORGANIZER") - recurrenceids = store.get_recurrences(user, obj.get_value("UID")) - - for attendee, attendee_attr in attendees.items(): - - # Only consider events where the stated participant actually attends. - - if attendee == participant: - partstat = attendee_attr.get("PARTSTAT", "NEEDS-ACTION") + partstat = obj.get_participation_status(participant) + recurrenceids = not obj.get_recurrenceid() and store.get_recurrences(user, obj.get_uid()) - if partstat not in ("DECLINED", "DELEGATED", "NEEDS-ACTION") or \ - include_needs_action and partstat == "NEEDS-ACTION": - - get_periods(fb, obj, tzid, window_end, False, recurrenceids) - - break - - # Where not attending, retain the affected periods and mark them as - # organising periods. - - else: - if organiser == participant: - get_periods(fb, obj, tzid, window_end, True, recurrenceids) + if obj.get_participation(partstat, include_needs_action): + for p in obj.get_active_periods(recurrenceids, tzid, window_end): + fb.append(obj.get_freebusy_period(p, partstat == "ORG")) fb.sort()