1.1 --- a/imip_store.py Mon May 18 15:08:29 2015 +0200
1.2 +++ b/imip_store.py Mon May 18 17:13:46 2015 +0200
1.3 @@ -338,8 +338,7 @@
1.4 if not filename:
1.5 return False
1.6
1.7 - self._set_table(user, filename, map(lambda fb: fb.as_tuple(), freebusy),
1.8 - [(3, "OPAQUE"), (4, "")])
1.9 + self._set_table(user, filename, map(lambda fb: fb.as_tuple(strings_only=True), freebusy))
1.10 return True
1.11
1.12 def set_freebusy_for_other(self, user, freebusy, other):
1.13 @@ -350,8 +349,7 @@
1.14 if not filename:
1.15 return False
1.16
1.17 - self._set_table(user, filename, map(lambda fb: fb.as_tuple(), freebusy),
1.18 - [(2, ""), (3, "OPAQUE"), (4, ""), (5, ""), (6, "")])
1.19 + self._set_table(user, filename, map(lambda fb: fb.as_tuple(strings_only=True), freebusy))
1.20 return True
1.21
1.22 def _get_requests(self, user, queue):
1.23 @@ -522,7 +520,7 @@
1.24 for fb in freebusy:
1.25 if not fb.transp or fb.transp == "OPAQUE":
1.26 rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join(
1.27 - map(format_datetime, [fb.get_start(), fb.get_end()]))))
1.28 + map(format_datetime, [fb.get_start_point(), fb.get_end_point()]))))
1.29
1.30 f = open(filename, "wb")
1.31 try:
2.1 --- a/imiptools/__init__.py Mon May 18 15:08:29 2015 +0200
2.2 +++ b/imiptools/__init__.py Mon May 18 17:13:46 2015 +0200
2.3 @@ -55,6 +55,9 @@
2.4 self.handlers = handlers
2.5 self.messenger = messenger or Messenger()
2.6 self.lmtp_socket = None
2.7 + self.store_dir = None
2.8 + self.publishing_dir = None
2.9 + self.debug = False
2.10
2.11 def process(self, f, original_recipients, outgoing_only):
2.12
2.13 @@ -91,7 +94,11 @@
2.14 may be constructed according to individual preferences.
2.15 """
2.16
2.17 - handlers = dict([(name, cls(senders, recipient, self.messenger)) for name, cls in self.handlers])
2.18 + store = self.store_dir and imip_store.FileStore(self.store_dir) or None
2.19 + publisher = self.publishing_dir and imip_store.FilePublisher(self.publishing_dir) or None
2.20 +
2.21 + handlers = dict([(name, cls(senders, recipient, self.messenger, store, publisher))
2.22 + for name, cls in self.handlers])
2.23 handled = False
2.24
2.25 for part in msg.walk():
2.26 @@ -143,7 +150,7 @@
2.27 if fb: parts.append(fb)
2.28 message = self.messenger.make_outgoing_message(parts, [outgoing_recipient])
2.29
2.30 - if "-d" in sys.argv:
2.31 + if self.debug:
2.32 print >>sys.stderr, "Outgoing parts for %s..." % outgoing_recipient
2.33 print message
2.34 else:
2.35 @@ -174,7 +181,7 @@
2.36 messages = [self.messenger.wrap_message(msg, forwarded_parts)]
2.37
2.38 for message in messages:
2.39 - if "-d" in sys.argv:
2.40 + if self.debug:
2.41 print >>sys.stderr, "Forwarded parts..."
2.42 print message
2.43 elif self.lmtp_socket:
2.44 @@ -183,7 +190,7 @@
2.45 # Unhandled messages are delivered as they are.
2.46
2.47 if not handled:
2.48 - if "-d" in sys.argv:
2.49 + if self.debug:
2.50 print >>sys.stderr, "Unhandled parts..."
2.51 print msg
2.52 elif self.lmtp_socket:
2.53 @@ -238,6 +245,8 @@
2.54 recipients = []
2.55 senders = []
2.56 lmtp = []
2.57 + store_dir = []
2.58 + publishing_dir = []
2.59 outgoing_only = False
2.60
2.61 l = []
2.62 @@ -264,15 +273,27 @@
2.63 elif arg == "-l":
2.64 l = lmtp
2.65
2.66 + # Switch to getting the store directory.
2.67 +
2.68 + elif arg == "-S":
2.69 + l = store_dir
2.70 +
2.71 + # Switch to getting the publishing directory.
2.72 +
2.73 + elif arg == "-P":
2.74 + l = publishing_dir
2.75 +
2.76 # Ignore debugging options.
2.77
2.78 elif arg == "-d":
2.79 - pass
2.80 + self.debug = True
2.81 else:
2.82 l.append(arg)
2.83
2.84 self.messenger.sender = senders and senders[0] or self.messenger.sender
2.85 self.lmtp_socket = lmtp and lmtp[0] or None
2.86 + self.store_dir = store_dir and store_dir[0] or None
2.87 + self.publishing_dir = publishing_dir and publishing_dir[0] or None
2.88 self.process(stream, original_recipients, outgoing_only)
2.89
2.90 def __call__(self):
3.1 --- a/imiptools/data.py Mon May 18 15:08:29 2015 +0200
3.2 +++ b/imiptools/data.py Mon May 18 17:13:46 2015 +0200
3.3 @@ -46,7 +46,15 @@
3.4 return self.get_value("UID")
3.5
3.6 def get_recurrenceid(self):
3.7 - return format_datetime(self.get_utc_datetime("RECURRENCE-ID"))
3.8 +
3.9 + """
3.10 + Return the recurrence identifier, normalised to a UTC datetime if
3.11 + specified as a datetime, converted to a date object otherwise. If no
3.12 + recurrence identifier is present, None is returned.
3.13 + """
3.14 +
3.15 + recurrenceid = self.get_utc_datetime("RECURRENCE-ID")
3.16 + return recurrenceid and format_datetime(recurrenceid)
3.17
3.18 # Structure access.
3.19
3.20 @@ -180,12 +188,19 @@
3.21
3.22 # Get a constrained view if start and end limits are specified.
3.23
3.24 - periods = period and period_overlaps(freebusy, period, True) or freebusy
3.25 + if period:
3.26 + periods = period_overlaps(freebusy, period, True)
3.27 + else:
3.28 + periods = freebusy
3.29
3.30 # Write the limits of the resource.
3.31
3.32 - rwrite(("DTSTART", {"VALUE" : "DATE-TIME"}, format_datetime(periods[0].get_start_point())))
3.33 - rwrite(("DTEND", {"VALUE" : "DATE-TIME"}, format_datetime(periods[-1].get_end_point())))
3.34 + if periods:
3.35 + rwrite(("DTSTART", {"VALUE" : "DATE-TIME"}, format_datetime(periods[0].get_start_point())))
3.36 + rwrite(("DTEND", {"VALUE" : "DATE-TIME"}, format_datetime(periods[-1].get_end_point())))
3.37 + else:
3.38 + rwrite(("DTSTART", {"VALUE" : "DATE-TIME"}, format_datetime(period.get_start_point())))
3.39 + rwrite(("DTEND", {"VALUE" : "DATE-TIME"}, format_datetime(period.get_end_point())))
3.40
3.41 for p in periods:
3.42 if p.transp == "OPAQUE":
4.1 --- a/imiptools/handlers/__init__.py Mon May 18 15:08:29 2015 +0200
4.2 +++ b/imiptools/handlers/__init__.py Mon May 18 17:13:46 2015 +0200
4.3 @@ -26,7 +26,7 @@
4.4 from imiptools.data import Object, \
4.5 get_address, get_uri, get_value, \
4.6 is_new_object, uri_dict, uri_item, uri_values
4.7 -from imiptools.dates import format_datetime, to_recurrence_start_point, \
4.8 +from imiptools.dates import format_datetime, get_recurrence_start_point, \
4.9 to_timezone
4.10 from imiptools.period import can_schedule, remove_period, \
4.11 remove_additional_periods, remove_affected_period, \
4.12 @@ -51,11 +51,15 @@
4.13
4.14 "General handler support."
4.15
4.16 - def __init__(self, senders=None, recipient=None, messenger=None):
4.17 + def __init__(self, senders=None, recipient=None, messenger=None, store=None,
4.18 + publisher=None):
4.19
4.20 """
4.21 Initialise the handler with the calendar 'obj' and the 'senders' and
4.22 'recipient' of the object (if specifically indicated).
4.23 +
4.24 + The optional 'store' and 'publisher' can be specified to override the
4.25 + default store and publisher objects.
4.26 """
4.27
4.28 Client.__init__(self, recipient and get_uri(recipient))
4.29 @@ -73,10 +77,10 @@
4.30 self.sequence = None
4.31 self.dtstamp = None
4.32
4.33 - self.store = imip_store.FileStore()
4.34 + self.store = store or imip_store.FileStore()
4.35
4.36 try:
4.37 - self.publisher = imip_store.FilePublisher()
4.38 + self.publisher = publisher or imip_store.FilePublisher()
4.39 except OSError:
4.40 self.publisher = None
4.41
4.42 @@ -121,19 +125,19 @@
4.43
4.44 # Convenience methods for modifying free/busy collections.
4.45
4.46 - def to_recurrence_start_point(self, recurrenceid):
4.47 + def get_recurrence_start_point(self, recurrenceid):
4.48
4.49 "Get 'recurrenceid' in a form suitable for matching free/busy entries."
4.50
4.51 tzid = self.obj.get_tzid() or self.get_tzid()
4.52 - return to_recurrence_start_point(recurrenceid, tzid)
4.53 + return get_recurrence_start_point(recurrenceid, tzid)
4.54
4.55 def remove_from_freebusy(self, freebusy):
4.56
4.57 "Remove this event from the given 'freebusy' collection."
4.58
4.59 if not remove_period(freebusy, self.uid, self.recurrenceid) and self.recurrenceid:
4.60 - remove_affected_period(freebusy, self.uid, self.to_recurrence_start_point(self.recurrenceid))
4.61 + remove_affected_period(freebusy, self.uid, self.get_recurrence_start_point(self.recurrenceid))
4.62
4.63 def remove_freebusy_for_recurrences(self, freebusy, recurrenceids=None):
4.64
4.65 @@ -146,7 +150,7 @@
4.66 """
4.67
4.68 if self.recurrenceid:
4.69 - recurrenceid = self.to_recurrence_start_point(self.recurrenceid)
4.70 + recurrenceid = self.get_recurrence_start_point(self.recurrenceid)
4.71 remove_affected_period(freebusy, self.uid, recurrenceid)
4.72 else:
4.73 # Remove obsolete recurrence periods.
4.74 @@ -157,7 +161,7 @@
4.75
4.76 if recurrenceids:
4.77 for recurrenceid in recurrenceids:
4.78 - recurrenceid = self.to_recurrence_start_point(recurrenceid)
4.79 + recurrenceid = self.get_recurrence_start_point(recurrenceid)
4.80 remove_affected_period(freebusy, self.uid, recurrenceid)
4.81
4.82 def _update_freebusy(self, freebusy, periods, recurrenceid, transp=None):
5.1 --- a/imiptools/period.py Mon May 18 15:08:29 2015 +0200
5.2 +++ b/imiptools/period.py Mon May 18 17:13:46 2015 +0200
5.3 @@ -22,8 +22,11 @@
5.4 from bisect import bisect_left, bisect_right, insort_left
5.5 from datetime import date, datetime, timedelta
5.6 from imiptools.dates import format_datetime, get_datetime, \
5.7 - get_datetime_attributes, get_start_of_day, \
5.8 - get_tzid, to_timezone, to_utc_datetime
5.9 + get_datetime_attributes, \
5.10 + get_recurrence_start, get_recurrence_start_point, \
5.11 + get_start_of_day, \
5.12 + get_tzid, \
5.13 + to_timezone, to_utc_datetime
5.14
5.15 class Period:
5.16
5.17 @@ -110,10 +113,23 @@
5.18 self.summary = summary
5.19 self.organiser = organiser
5.20
5.21 - def as_tuple(self):
5.22 - return format_datetime(self.get_start_point()), \
5.23 - format_datetime(self.get_end_point()), \
5.24 - self.uid, self.transp, self.recurrenceid, self.summary, self.organiser
5.25 + def as_tuple(self, strings_only=False):
5.26 +
5.27 + """
5.28 + Return the initialisation parameter tuple, converting false value
5.29 + parameters to strings if 'strings_only' is set to a true value.
5.30 + """
5.31 +
5.32 + null = lambda x: (strings_only and [""] or [x])[0]
5.33 + return (
5.34 + format_datetime(self.get_start_point()),
5.35 + format_datetime(self.get_end_point()),
5.36 + self.uid or null(self.uid),
5.37 + self.transp or strings_only and "OPAQUE" or None,
5.38 + self.recurrenceid or null(self.recurrenceid),
5.39 + self.summary or null(self.summary),
5.40 + self.organiser or null(self.organiser)
5.41 + )
5.42
5.43 def __cmp__(self, other):
5.44
5.45 @@ -158,6 +174,35 @@
5.46 def __repr__(self):
5.47 return "RecurringPeriod(%r)" % (self.as_tuple(),)
5.48
5.49 +# Period and event recurrence logic.
5.50 +
5.51 +def is_replaced(period, recurrenceids, tzid):
5.52 +
5.53 + """
5.54 + Return whether 'period' refers to one of the 'recurrenceids', interpreted
5.55 + using 'tzid' if necessary.
5.56 + """
5.57 +
5.58 + for s in recurrenceids:
5.59 + if is_affected(period, s, tzid):
5.60 + return s
5.61 + return None
5.62 +
5.63 +def is_affected(period, recurrenceid, tzid):
5.64 +
5.65 + """
5.66 + Return whether 'period' refer to 'recurrenceid', interpreted using 'tzid' if
5.67 + necessary.
5.68 + """
5.69 +
5.70 + if not recurrenceid:
5.71 + return None
5.72 + d = get_recurrence_start(recurrenceid)
5.73 + dt = get_recurrence_start_point(recurrenceid, tzid)
5.74 + if period.get_start() == d or period.get_start_point() == dt:
5.75 + return recurrenceid
5.76 + return None
5.77 +
5.78 # Time and period management.
5.79
5.80 def can_schedule(freebusy, periods, uid, recurrenceid):
6.1 --- a/imipweb/resource.py Mon May 18 15:08:29 2015 +0200
6.2 +++ b/imipweb/resource.py Mon May 18 17:13:46 2015 +0200
6.3 @@ -22,9 +22,9 @@
6.4 from datetime import datetime
6.5 from imiptools.client import Client
6.6 from imiptools.data import get_uri, get_window_end, Object, uri_values
6.7 -from imiptools.dates import format_datetime, format_time, get_recurrence_start, \
6.8 - get_recurrence_start_point, to_recurrence_start_point
6.9 -from imiptools.period import FreeBusyPeriod, \
6.10 +from imiptools.dates import format_datetime, format_time, \
6.11 + get_recurrence_start_point
6.12 +from imiptools.period import FreeBusyPeriod, is_affected, is_replaced, \
6.13 remove_period, remove_affected_period, update_freebusy
6.14 from imipweb.env import CGIEnvironment
6.15 import babel.dates
6.16 @@ -156,21 +156,10 @@
6.17 # Period and recurrence testing.
6.18
6.19 def is_replaced(self, period, recurrenceids):
6.20 - for s in recurrenceids:
6.21 - d = get_recurrence_start(s)
6.22 - dt = get_recurrence_start_point(s, self.get_tzid())
6.23 - if period.get_start() == d or period.get_start_point() == dt:
6.24 - return s
6.25 - return None
6.26 + return is_replaced(period, recurrenceids, self.get_tzid())
6.27
6.28 def is_affected(self, period, recurrenceid):
6.29 - if not recurrenceid:
6.30 - return None
6.31 - d = get_recurrence_start(recurrenceid)
6.32 - dt = get_recurrence_start_point(recurrenceid, self.get_tzid())
6.33 - if period.get_start() == d or period.get_start_point() == dt:
6.34 - return recurrenceid
6.35 - return None
6.36 + return is_affected(period, recurrenceid, self.get_tzid())
6.37
6.38 # Preference methods.
6.39
6.40 @@ -226,7 +215,7 @@
6.41 # NOTE: The time zone may need obtaining using the object details.
6.42
6.43 for recurrenceid in self._get_recurrences(uid):
6.44 - remove_affected_period(freebusy, uid, to_recurrence_start_point(recurrenceid, self.get_tzid()))
6.45 + remove_affected_period(freebusy, uid, get_recurrence_start_point(recurrenceid, self.get_tzid()))
6.46
6.47 self.store.set_freebusy(self.user, freebusy)
6.48 self.publish_freebusy(freebusy)
7.1 --- a/tools/make_freebusy.py Mon May 18 15:08:29 2015 +0200
7.2 +++ b/tools/make_freebusy.py Mon May 18 17:13:46 2015 +0200
7.3 @@ -2,7 +2,7 @@
7.4
7.5 from imiptools.data import get_window_end, Object
7.6 from imiptools.dates import format_datetime, get_default_timezone, to_recurrence_start
7.7 -from imiptools.period import FreeBusyPeriod
7.8 +from imiptools.period import FreeBusyPeriod, is_replaced
7.9 from imiptools.profile import Preferences
7.10 from imip_store import FileStore, FilePublisher
7.11 import sys
7.12 @@ -11,18 +11,20 @@
7.13
7.14 # Update free/busy details with the actual periods associated with the event.
7.15
7.16 - recurrenceid = format_datetime(obj.get_utc_datetime("RECURRENCE-ID")) or ""
7.17 - recurrenceids = [to_recurrence_start(r, tzid) for r in recurrenceids]
7.18 + recurrenceid = obj.get_recurrenceid()
7.19 + recurrenceids = [to_recurrence_start(r) for r in recurrenceids]
7.20
7.21 - for p in obj.get_periods_for_freebusy(tzid, window_end):
7.22 - if recurrenceid or p.start not in recurrenceids:
7.23 + for p in obj.get_periods(tzid, window_end):
7.24 + if recurrenceid or not is_replaced(p, recurrenceids, tzid):
7.25 fb.append(FreeBusyPeriod(
7.26 - p.start, p.end,
7.27 + p.get_start(),
7.28 + p.get_end(),
7.29 obj.get_value("UID"),
7.30 only_organiser and "ORG" or obj.get_value("TRANSP") or "OPAQUE",
7.31 recurrenceid,
7.32 obj.get_value("SUMMARY"),
7.33 - obj.get_value("ORGANIZER")
7.34 + obj.get_value("ORGANIZER"),
7.35 + p.get_tzid()
7.36 ))
7.37
7.38 # Main program.
7.39 @@ -120,6 +122,6 @@
7.40 store.set_freebusy_for_other(user, fb, participant)
7.41 else:
7.42 for item in fb:
7.43 - print "\t".join(item.as_tuple())
7.44 + print "\t".join(item.as_tuple(strings_only=True))
7.45
7.46 # vim: tabstop=4 expandtab shiftwidth=4