# HG changeset patch # User Paul Boddie # Date 1431962026 -7200 # Node ID b8342c472aa9d2c794d5b5f77ecc0d7977c8f0f4 # Parent fac3b41b0ccbff359480c55f83b71266a40cc02f Made recurrence identifier acquisition and free/busy production more robust. Changed remove_affected_period usage to use a datetime as the start argument. Changed free/busy period serialisation to choose appropriate string representations instead of doing so in the store. Added program options to choose the store and publishing directories. Introduced a proper debug flag into the processor. Moved the is_affected and is_replaced methods into the period module as functions. Updated the free/busy tool to work with recent API changes. diff -r fac3b41b0ccb -r b8342c472aa9 imip_store.py --- a/imip_store.py Mon May 18 15:08:29 2015 +0200 +++ b/imip_store.py Mon May 18 17:13:46 2015 +0200 @@ -338,8 +338,7 @@ if not filename: return False - self._set_table(user, filename, map(lambda fb: fb.as_tuple(), freebusy), - [(3, "OPAQUE"), (4, "")]) + self._set_table(user, filename, map(lambda fb: fb.as_tuple(strings_only=True), freebusy)) return True def set_freebusy_for_other(self, user, freebusy, other): @@ -350,8 +349,7 @@ if not filename: return False - self._set_table(user, filename, map(lambda fb: fb.as_tuple(), freebusy), - [(2, ""), (3, "OPAQUE"), (4, ""), (5, ""), (6, "")]) + self._set_table(user, filename, map(lambda fb: fb.as_tuple(strings_only=True), freebusy)) return True def _get_requests(self, user, queue): @@ -522,7 +520,7 @@ for fb in freebusy: if not fb.transp or fb.transp == "OPAQUE": rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join( - map(format_datetime, [fb.get_start(), fb.get_end()])))) + map(format_datetime, [fb.get_start_point(), fb.get_end_point()])))) f = open(filename, "wb") try: diff -r fac3b41b0ccb -r b8342c472aa9 imiptools/__init__.py --- a/imiptools/__init__.py Mon May 18 15:08:29 2015 +0200 +++ b/imiptools/__init__.py Mon May 18 17:13:46 2015 +0200 @@ -55,6 +55,9 @@ self.handlers = handlers self.messenger = messenger or Messenger() self.lmtp_socket = None + self.store_dir = None + self.publishing_dir = None + self.debug = False def process(self, f, original_recipients, outgoing_only): @@ -91,7 +94,11 @@ may be constructed according to individual preferences. """ - handlers = dict([(name, cls(senders, recipient, self.messenger)) for name, cls in self.handlers]) + store = self.store_dir and imip_store.FileStore(self.store_dir) or None + publisher = self.publishing_dir and imip_store.FilePublisher(self.publishing_dir) or None + + handlers = dict([(name, cls(senders, recipient, self.messenger, store, publisher)) + for name, cls in self.handlers]) handled = False for part in msg.walk(): @@ -143,7 +150,7 @@ if fb: parts.append(fb) message = self.messenger.make_outgoing_message(parts, [outgoing_recipient]) - if "-d" in sys.argv: + if self.debug: print >>sys.stderr, "Outgoing parts for %s..." % outgoing_recipient print message else: @@ -174,7 +181,7 @@ messages = [self.messenger.wrap_message(msg, forwarded_parts)] for message in messages: - if "-d" in sys.argv: + if self.debug: print >>sys.stderr, "Forwarded parts..." print message elif self.lmtp_socket: @@ -183,7 +190,7 @@ # Unhandled messages are delivered as they are. if not handled: - if "-d" in sys.argv: + if self.debug: print >>sys.stderr, "Unhandled parts..." print msg elif self.lmtp_socket: @@ -238,6 +245,8 @@ recipients = [] senders = [] lmtp = [] + store_dir = [] + publishing_dir = [] outgoing_only = False l = [] @@ -264,15 +273,27 @@ elif arg == "-l": l = lmtp + # Switch to getting the store directory. + + elif arg == "-S": + l = store_dir + + # Switch to getting the publishing directory. + + elif arg == "-P": + l = publishing_dir + # Ignore debugging options. elif arg == "-d": - pass + self.debug = True else: l.append(arg) self.messenger.sender = senders and senders[0] or self.messenger.sender self.lmtp_socket = lmtp and lmtp[0] or None + self.store_dir = store_dir and store_dir[0] or None + self.publishing_dir = publishing_dir and publishing_dir[0] or None self.process(stream, original_recipients, outgoing_only) def __call__(self): diff -r fac3b41b0ccb -r b8342c472aa9 imiptools/data.py --- a/imiptools/data.py Mon May 18 15:08:29 2015 +0200 +++ b/imiptools/data.py Mon May 18 17:13:46 2015 +0200 @@ -46,7 +46,15 @@ return self.get_value("UID") def get_recurrenceid(self): - return format_datetime(self.get_utc_datetime("RECURRENCE-ID")) + + """ + Return the recurrence identifier, normalised to a UTC datetime if + specified as a datetime, converted to a date object otherwise. If no + recurrence identifier is present, None is returned. + """ + + recurrenceid = self.get_utc_datetime("RECURRENCE-ID") + return recurrenceid and format_datetime(recurrenceid) # Structure access. @@ -180,12 +188,19 @@ # Get a constrained view if start and end limits are specified. - periods = period and period_overlaps(freebusy, period, True) or freebusy + if period: + periods = period_overlaps(freebusy, period, True) + else: + periods = freebusy # Write the limits of the resource. - rwrite(("DTSTART", {"VALUE" : "DATE-TIME"}, format_datetime(periods[0].get_start_point()))) - rwrite(("DTEND", {"VALUE" : "DATE-TIME"}, format_datetime(periods[-1].get_end_point()))) + if periods: + rwrite(("DTSTART", {"VALUE" : "DATE-TIME"}, format_datetime(periods[0].get_start_point()))) + rwrite(("DTEND", {"VALUE" : "DATE-TIME"}, format_datetime(periods[-1].get_end_point()))) + else: + rwrite(("DTSTART", {"VALUE" : "DATE-TIME"}, format_datetime(period.get_start_point()))) + rwrite(("DTEND", {"VALUE" : "DATE-TIME"}, format_datetime(period.get_end_point()))) for p in periods: if p.transp == "OPAQUE": diff -r fac3b41b0ccb -r b8342c472aa9 imiptools/handlers/__init__.py --- a/imiptools/handlers/__init__.py Mon May 18 15:08:29 2015 +0200 +++ b/imiptools/handlers/__init__.py Mon May 18 17:13:46 2015 +0200 @@ -26,7 +26,7 @@ from imiptools.data import Object, \ get_address, get_uri, get_value, \ is_new_object, uri_dict, uri_item, uri_values -from imiptools.dates import format_datetime, to_recurrence_start_point, \ +from imiptools.dates import format_datetime, get_recurrence_start_point, \ to_timezone from imiptools.period import can_schedule, remove_period, \ remove_additional_periods, remove_affected_period, \ @@ -51,11 +51,15 @@ "General handler support." - def __init__(self, senders=None, recipient=None, messenger=None): + def __init__(self, senders=None, recipient=None, messenger=None, store=None, + publisher=None): """ Initialise the handler with the calendar 'obj' and the 'senders' and 'recipient' of the object (if specifically indicated). + + The optional 'store' and 'publisher' can be specified to override the + default store and publisher objects. """ Client.__init__(self, recipient and get_uri(recipient)) @@ -73,10 +77,10 @@ self.sequence = None self.dtstamp = None - self.store = imip_store.FileStore() + self.store = store or imip_store.FileStore() try: - self.publisher = imip_store.FilePublisher() + self.publisher = publisher or imip_store.FilePublisher() except OSError: self.publisher = None @@ -121,19 +125,19 @@ # Convenience methods for modifying free/busy collections. - def to_recurrence_start_point(self, recurrenceid): + def get_recurrence_start_point(self, recurrenceid): "Get 'recurrenceid' in a form suitable for matching free/busy entries." tzid = self.obj.get_tzid() or self.get_tzid() - return to_recurrence_start_point(recurrenceid, tzid) + return get_recurrence_start_point(recurrenceid, tzid) def remove_from_freebusy(self, freebusy): "Remove this event from the given 'freebusy' collection." if not remove_period(freebusy, self.uid, self.recurrenceid) and self.recurrenceid: - remove_affected_period(freebusy, self.uid, self.to_recurrence_start_point(self.recurrenceid)) + remove_affected_period(freebusy, self.uid, self.get_recurrence_start_point(self.recurrenceid)) def remove_freebusy_for_recurrences(self, freebusy, recurrenceids=None): @@ -146,7 +150,7 @@ """ if self.recurrenceid: - recurrenceid = self.to_recurrence_start_point(self.recurrenceid) + recurrenceid = self.get_recurrence_start_point(self.recurrenceid) remove_affected_period(freebusy, self.uid, recurrenceid) else: # Remove obsolete recurrence periods. @@ -157,7 +161,7 @@ if recurrenceids: for recurrenceid in recurrenceids: - recurrenceid = self.to_recurrence_start_point(recurrenceid) + recurrenceid = self.get_recurrence_start_point(recurrenceid) remove_affected_period(freebusy, self.uid, recurrenceid) def _update_freebusy(self, freebusy, periods, recurrenceid, transp=None): diff -r fac3b41b0ccb -r b8342c472aa9 imiptools/period.py --- a/imiptools/period.py Mon May 18 15:08:29 2015 +0200 +++ b/imiptools/period.py Mon May 18 17:13:46 2015 +0200 @@ -22,8 +22,11 @@ from bisect import bisect_left, bisect_right, insort_left from datetime import date, datetime, timedelta from imiptools.dates import format_datetime, get_datetime, \ - get_datetime_attributes, get_start_of_day, \ - get_tzid, to_timezone, to_utc_datetime + get_datetime_attributes, \ + get_recurrence_start, get_recurrence_start_point, \ + get_start_of_day, \ + get_tzid, \ + to_timezone, to_utc_datetime class Period: @@ -110,10 +113,23 @@ self.summary = summary self.organiser = organiser - def as_tuple(self): - return format_datetime(self.get_start_point()), \ - format_datetime(self.get_end_point()), \ - self.uid, self.transp, self.recurrenceid, self.summary, self.organiser + def as_tuple(self, strings_only=False): + + """ + Return the initialisation parameter tuple, converting false value + parameters to strings if 'strings_only' is set to a true value. + """ + + null = lambda x: (strings_only and [""] or [x])[0] + return ( + format_datetime(self.get_start_point()), + format_datetime(self.get_end_point()), + self.uid or null(self.uid), + self.transp or strings_only and "OPAQUE" or None, + self.recurrenceid or null(self.recurrenceid), + self.summary or null(self.summary), + self.organiser or null(self.organiser) + ) def __cmp__(self, other): @@ -158,6 +174,35 @@ def __repr__(self): return "RecurringPeriod(%r)" % (self.as_tuple(),) +# Period and event recurrence logic. + +def is_replaced(period, recurrenceids, tzid): + + """ + Return whether 'period' refers to one of the 'recurrenceids', interpreted + using 'tzid' if necessary. + """ + + for s in recurrenceids: + if is_affected(period, s, tzid): + return s + return None + +def is_affected(period, recurrenceid, tzid): + + """ + Return whether 'period' refer to 'recurrenceid', interpreted using 'tzid' if + necessary. + """ + + if not recurrenceid: + return None + d = get_recurrence_start(recurrenceid) + dt = get_recurrence_start_point(recurrenceid, tzid) + if period.get_start() == d or period.get_start_point() == dt: + return recurrenceid + return None + # Time and period management. def can_schedule(freebusy, periods, uid, recurrenceid): diff -r fac3b41b0ccb -r b8342c472aa9 imipweb/resource.py --- a/imipweb/resource.py Mon May 18 15:08:29 2015 +0200 +++ b/imipweb/resource.py Mon May 18 17:13:46 2015 +0200 @@ -22,9 +22,9 @@ from datetime import datetime from imiptools.client import Client from imiptools.data import get_uri, get_window_end, Object, uri_values -from imiptools.dates import format_datetime, format_time, get_recurrence_start, \ - get_recurrence_start_point, to_recurrence_start_point -from imiptools.period import FreeBusyPeriod, \ +from imiptools.dates import format_datetime, format_time, \ + get_recurrence_start_point +from imiptools.period import FreeBusyPeriod, is_affected, is_replaced, \ remove_period, remove_affected_period, update_freebusy from imipweb.env import CGIEnvironment import babel.dates @@ -156,21 +156,10 @@ # Period and recurrence testing. def is_replaced(self, period, recurrenceids): - for s in recurrenceids: - d = get_recurrence_start(s) - dt = get_recurrence_start_point(s, self.get_tzid()) - if period.get_start() == d or period.get_start_point() == dt: - return s - return None + return is_replaced(period, recurrenceids, self.get_tzid()) def is_affected(self, period, recurrenceid): - if not recurrenceid: - return None - d = get_recurrence_start(recurrenceid) - dt = get_recurrence_start_point(recurrenceid, self.get_tzid()) - if period.get_start() == d or period.get_start_point() == dt: - return recurrenceid - return None + return is_affected(period, recurrenceid, self.get_tzid()) # Preference methods. @@ -226,7 +215,7 @@ # NOTE: The time zone may need obtaining using the object details. for recurrenceid in self._get_recurrences(uid): - remove_affected_period(freebusy, uid, to_recurrence_start_point(recurrenceid, self.get_tzid())) + remove_affected_period(freebusy, uid, get_recurrence_start_point(recurrenceid, self.get_tzid())) self.store.set_freebusy(self.user, freebusy) self.publish_freebusy(freebusy) diff -r fac3b41b0ccb -r b8342c472aa9 tools/make_freebusy.py --- a/tools/make_freebusy.py Mon May 18 15:08:29 2015 +0200 +++ b/tools/make_freebusy.py Mon May 18 17:13:46 2015 +0200 @@ -2,7 +2,7 @@ from imiptools.data import get_window_end, Object from imiptools.dates import format_datetime, get_default_timezone, to_recurrence_start -from imiptools.period import FreeBusyPeriod +from imiptools.period import FreeBusyPeriod, is_replaced from imiptools.profile import Preferences from imip_store import FileStore, FilePublisher import sys @@ -11,18 +11,20 @@ # Update free/busy details with the actual periods associated with the event. - recurrenceid = format_datetime(obj.get_utc_datetime("RECURRENCE-ID")) or "" - recurrenceids = [to_recurrence_start(r, tzid) for r in recurrenceids] + recurrenceid = obj.get_recurrenceid() + recurrenceids = [to_recurrence_start(r) for r in recurrenceids] - for p in obj.get_periods_for_freebusy(tzid, window_end): - if recurrenceid or p.start not in recurrenceids: + for p in obj.get_periods(tzid, window_end): + if recurrenceid or not is_replaced(p, recurrenceids, tzid): fb.append(FreeBusyPeriod( - p.start, p.end, + p.get_start(), + p.get_end(), obj.get_value("UID"), only_organiser and "ORG" or obj.get_value("TRANSP") or "OPAQUE", recurrenceid, obj.get_value("SUMMARY"), - obj.get_value("ORGANIZER") + obj.get_value("ORGANIZER"), + p.get_tzid() )) # Main program. @@ -120,6 +122,6 @@ store.set_freebusy_for_other(user, fb, participant) else: for item in fb: - print "\t".join(item.as_tuple()) + print "\t".join(item.as_tuple(strings_only=True)) # vim: tabstop=4 expandtab shiftwidth=4