1.1 --- a/imip_store.py Thu Aug 06 20:02:14 2015 +0200
1.2 +++ b/imip_store.py Thu Aug 06 22:00:11 2015 +0200
1.3 @@ -43,6 +43,8 @@
1.4 def release_lock(self, user):
1.5 FileBase.release_lock(self, user)
1.6
1.7 + # Utility methods.
1.8 +
1.9 def _set_defaults(self, t, empty_defaults):
1.10 for i, default in empty_defaults:
1.11 if i >= len(t):
1.12 @@ -103,6 +105,8 @@
1.13 finally:
1.14 self.release_lock(user)
1.15
1.16 + # Store object access.
1.17 +
1.18 def _get_object(self, user, filename):
1.19
1.20 """
1.21 @@ -162,6 +166,8 @@
1.22
1.23 return True
1.24
1.25 + # Event and event metadata access.
1.26 +
1.27 def get_events(self, user):
1.28
1.29 "Return a list of event identifiers."
1.30 @@ -172,6 +178,31 @@
1.31
1.32 return [name for name in listdir(filename) if isfile(join(filename, name))]
1.33
1.34 + def get_all_events(self, user):
1.35 +
1.36 + "Return a set of (uid, recurrenceid) tuples for all events."
1.37 +
1.38 + uids = self.get_events(user)
1.39 +
1.40 + all_events = set()
1.41 + for uid in uids:
1.42 + all_events.add((uid, None))
1.43 + all_events.update([(uid, recurrenceid) for recurrenceid in self.get_recurrences(user, uid)])
1.44 +
1.45 + return all_events
1.46 +
1.47 + def get_active_events(self, user):
1.48 +
1.49 + "Return a set of uncancelled events of the form (uid, recurrenceid)."
1.50 +
1.51 + all_events = self.get_all_events(user)
1.52 +
1.53 + # Filter out cancelled events.
1.54 +
1.55 + cancelled = self.get_cancellations(user) or []
1.56 + all_events.difference_update(cancelled)
1.57 + return all_events
1.58 +
1.59 def get_event(self, user, uid, recurrenceid=None):
1.60
1.61 """
1.62 @@ -310,6 +341,8 @@
1.63
1.64 return True
1.65
1.66 + # Free/busy period access.
1.67 +
1.68 def get_freebusy(self, user):
1.69
1.70 "Get free/busy details for the given 'user'."
1.71 @@ -352,6 +385,8 @@
1.72 self._set_table(user, filename, map(lambda fb: fb.as_tuple(strings_only=True), freebusy))
1.73 return True
1.74
1.75 + # Object status details access.
1.76 +
1.77 def _get_requests(self, user, queue):
1.78
1.79 "Get requests for the given 'user' from the given 'queue'."
2.1 --- a/imiptools/data.py Thu Aug 06 20:02:14 2015 +0200
2.2 +++ b/imiptools/data.py Thu Aug 06 22:00:11 2015 +0200
2.3 @@ -28,7 +28,7 @@
2.4 get_duration, get_period, get_period_item, \
2.5 get_recurrence_start_point, \
2.6 get_tzid, to_datetime, to_timezone, to_utc_datetime
2.7 -from imiptools.period import Period, RecurringPeriod, period_overlaps
2.8 +from imiptools.period import FreeBusyPeriod, Period, RecurringPeriod, period_overlaps
2.9 from vCalendar import iterwrite, parse, ParseError, to_dict, to_node
2.10 from vRecurrence import get_parameters, get_rule
2.11 import email.utils
2.12 @@ -213,11 +213,62 @@
2.13 # Subtract any recurrences from the free/busy details of a
2.14 # parent object.
2.15
2.16 - if not is_replaced(p, recurrenceids, tzid):
2.17 + if not p.is_replaced(recurrenceids):
2.18 active.append(p)
2.19
2.20 return active
2.21
2.22 + def get_freebusy_period(self, period, only_organiser=False):
2.23 +
2.24 + """
2.25 + Return a free/busy period for the given 'period' provided by this
2.26 + object, using the 'only_organiser' status to produce a suitable
2.27 + transparency value.
2.28 + """
2.29 +
2.30 + return FreeBusyPeriod(
2.31 + period.get_start_point(),
2.32 + period.get_end_point(),
2.33 + self.get_value("UID"),
2.34 + only_organiser and "ORG" or self.get_value("TRANSP") or "OPAQUE",
2.35 + self.get_recurrenceid(),
2.36 + self.get_value("SUMMARY"),
2.37 + self.get_value("ORGANIZER")
2.38 + )
2.39 +
2.40 + def get_participation_status(self, participant):
2.41 +
2.42 + """
2.43 + Return the participation status of the given 'participant', with the
2.44 + special value "ORG" indicating organiser-only participation.
2.45 + """
2.46 +
2.47 + attendees = self.get_value_map("ATTENDEE")
2.48 + organiser = self.get_value("ORGANIZER")
2.49 +
2.50 + for attendee, attendee_attr in attendees.items():
2.51 + if attendee == participant:
2.52 + return attendee_attr.get("PARTSTAT", "NEEDS-ACTION")
2.53 +
2.54 + else:
2.55 + if organiser == participant:
2.56 + return "ORG"
2.57 +
2.58 + return None
2.59 +
2.60 + def get_participation(self, partstat, include_needs_action=False):
2.61 +
2.62 + """
2.63 + Return whether 'partstat' indicates some kind of participation in an
2.64 + event. If 'include_needs_action' is specified as a true value, events
2.65 + not yet responded to will be treated as events with tentative
2.66 + participation.
2.67 + """
2.68 +
2.69 + return not partstat in ("DECLINED", "DELEGATED", "NEEDS-ACTION") or \
2.70 + include_needs_action and partstat == "NEEDS-ACTION" or \
2.71 + partstat == "ORG"
2.72 +
2.73 def get_tzid(self):
2.74
2.75 """
3.1 --- a/tools/make_freebusy.py Thu Aug 06 20:02:14 2015 +0200
3.2 +++ b/tools/make_freebusy.py Thu Aug 06 22:00:11 2015 +0200
3.3 @@ -23,32 +23,10 @@
3.4
3.5 from imiptools.data import get_window_end, Object
3.6 from imiptools.dates import get_default_timezone
3.7 -from imiptools.period import FreeBusyPeriod, is_replaced
3.8 from imiptools.profile import Preferences
3.9 from imip_store import FileStore, FilePublisher
3.10 import sys
3.11
3.12 -def get_periods(fb, obj, tzid, window_end, only_organiser, recurrenceids):
3.13 -
3.14 - """
3.15 - Update free/busy details 'fb' with the actual periods associated with the
3.16 - event 'obj'.
3.17 - """
3.18 -
3.19 - recurrenceid = obj.get_recurrenceid()
3.20 -
3.21 - for p in obj.get_periods(tzid, window_end):
3.22 - if recurrenceid or not is_replaced(p, recurrenceids, tzid):
3.23 - fb.append(FreeBusyPeriod(
3.24 - p.get_start_point(),
3.25 - p.get_end_point(),
3.26 - obj.get_value("UID"),
3.27 - only_organiser and "ORG" or obj.get_value("TRANSP") or "OPAQUE",
3.28 - recurrenceid,
3.29 - obj.get_value("SUMMARY"),
3.30 - obj.get_value("ORGANIZER")
3.31 - ))
3.32 -
3.33 # Main program.
3.34
3.35 if __name__ == "__main__":
3.36 @@ -83,19 +61,9 @@
3.37 store = FileStore()
3.38 publisher = FilePublisher()
3.39
3.40 - # Get all identifiers for events.
3.41 -
3.42 - uids = store.get_events(user)
3.43 + # Get identifiers for uncancelled events.
3.44
3.45 - all_events = set()
3.46 - for uid in uids:
3.47 - all_events.add((uid, None))
3.48 - all_events.update([(uid, recurrenceid) for recurrenceid in store.get_recurrences(user, uid)])
3.49 -
3.50 - # Filter out cancelled events.
3.51 -
3.52 - cancelled = store.get_cancellations(user) or []
3.53 - all_events.difference_update(cancelled)
3.54 + all_events = store.get_active_events(user)
3.55
3.56 # Obtain event objects.
3.57
3.58 @@ -111,30 +79,12 @@
3.59
3.60 fb = []
3.61 for obj in objs:
3.62 - attendees = obj.get_value_map("ATTENDEE")
3.63 - organiser = obj.get_value("ORGANIZER")
3.64 - recurrenceids = store.get_recurrences(user, obj.get_value("UID"))
3.65 -
3.66 - for attendee, attendee_attr in attendees.items():
3.67 -
3.68 - # Only consider events where the stated participant actually attends.
3.69 -
3.70 - if attendee == participant:
3.71 - partstat = attendee_attr.get("PARTSTAT", "NEEDS-ACTION")
3.72 + partstat = obj.get_participation_status(participant)
3.73 + recurrenceids = not obj.get_recurrenceid() and store.get_recurrences(user, obj.get_uid())
3.74
3.75 - if partstat not in ("DECLINED", "DELEGATED", "NEEDS-ACTION") or \
3.76 - include_needs_action and partstat == "NEEDS-ACTION":
3.77 -
3.78 - get_periods(fb, obj, tzid, window_end, False, recurrenceids)
3.79 -
3.80 - break
3.81 -
3.82 - # Where not attending, retain the affected periods and mark them as
3.83 - # organising periods.
3.84 -
3.85 - else:
3.86 - if organiser == participant:
3.87 - get_periods(fb, obj, tzid, window_end, True, recurrenceids)
3.88 + if obj.get_participation(partstat, include_needs_action):
3.89 + for p in obj.get_active_periods(recurrenceids, tzid, window_end):
3.90 + fb.append(obj.get_freebusy_period(p, partstat == "ORG"))
3.91
3.92 fb.sort()
3.93