1.1 --- a/imiptools/period.py Wed May 11 14:04:30 2016 +0200
1.2 +++ b/imiptools/period.py Wed May 11 15:59:28 2016 +0200
1.3 @@ -34,9 +34,6 @@
1.4 if x is None: return y
1.5 else: return x
1.6
1.7 -def from_strings(t, encoding):
1.8 - return tuple([from_string(s, encoding) for s in t])
1.9 -
1.10 def from_string(s, encoding):
1.11 if s:
1.12 return unicode(s, encoding)
1.13 @@ -146,10 +143,17 @@
1.14 "A basic period abstraction."
1.15
1.16 def __init__(self, start, end):
1.17 +
1.18 + """
1.19 + Define a period according to 'start' and 'end' which may be special
1.20 + start/end of time values or iCalendar-format datetime strings.
1.21 + """
1.22 +
1.23 if isinstance(start, (date, PointInTime)):
1.24 self.start = start
1.25 else:
1.26 self.start = get_datetime(start) or StartOfTime()
1.27 +
1.28 if isinstance(end, (date, PointInTime)):
1.29 self.end = end
1.30 else:
1.31 @@ -358,16 +362,12 @@
1.32 "A free/busy record abstraction."
1.33
1.34 def __init__(self, start, end, uid=None, transp=None, recurrenceid=None,
1.35 - summary=None, organiser=None, expires=None):
1.36 + summary=None, organiser=None):
1.37
1.38 """
1.39 Initialise a free/busy period with the given 'start' and 'end' points,
1.40 plus any 'uid', 'transp', 'recurrenceid', 'summary' and 'organiser'
1.41 details.
1.42 -
1.43 - An additional 'expires' parameter can be used to indicate an expiry
1.44 - datetime in conjunction with free/busy offers made when countering
1.45 - event proposals.
1.46 """
1.47
1.48 PeriodBase.__init__(self, start, end)
1.49 @@ -376,7 +376,6 @@
1.50 self.recurrenceid = recurrenceid or None
1.51 self.summary = summary or None
1.52 self.organiser = organiser or None
1.53 - self.expires = expires or None
1.54
1.55 def as_tuple(self, strings_only=False, string_datetimes=False):
1.56
1.57 @@ -395,8 +394,7 @@
1.58 self.transp or strings_only and "OPAQUE" or None,
1.59 self.recurrenceid or null(self.recurrenceid),
1.60 self.summary or null(self.summary),
1.61 - self.organiser or null(self.organiser),
1.62 - self.expires or null(self.expires)
1.63 + self.organiser or null(self.organiser)
1.64 )
1.65
1.66 def __cmp__(self, other):
1.67 @@ -451,6 +449,79 @@
1.68 def make_corrected(self, start, end):
1.69 return self.__class__(start, end)
1.70
1.71 +class FreeBusyOfferPeriod(FreeBusyPeriod):
1.72 +
1.73 + "A free/busy record abstraction for an offer period."
1.74 +
1.75 + def __init__(self, start, end, uid=None, transp=None, recurrenceid=None,
1.76 + summary=None, organiser=None, expires=None):
1.77 +
1.78 + """
1.79 + Initialise a free/busy period with the given 'start' and 'end' points,
1.80 + plus any 'uid', 'transp', 'recurrenceid', 'summary' and 'organiser'
1.81 + details.
1.82 +
1.83 + An additional 'expires' parameter can be used to indicate an expiry
1.84 + datetime in conjunction with free/busy offers made when countering
1.85 + event proposals.
1.86 + """
1.87 +
1.88 + FreeBusyPeriod.__init__(self, start, end, uid, transp, recurrenceid,
1.89 + summary, organiser)
1.90 + self.expires = expires or None
1.91 +
1.92 + def as_tuple(self, strings_only=False, string_datetimes=False):
1.93 +
1.94 + """
1.95 + Return the initialisation parameter tuple, converting datetimes and
1.96 + false value parameters to strings if 'strings_only' is set to a true
1.97 + value. Otherwise, if 'string_datetimes' is set to a true value, only the
1.98 + datetime values are converted to strings.
1.99 + """
1.100 +
1.101 + null = lambda x: (strings_only and [""] or [x])[0]
1.102 + return FreeBusyPeriod.as_tuple(self, strings_only, string_datetimes) + (
1.103 + self.expires or null(self.expires),)
1.104 +
1.105 + def __repr__(self):
1.106 + return "FreeBusyOfferPeriod%r" % (self.as_tuple(),)
1.107 +
1.108 +class FreeBusyGroupPeriod(FreeBusyPeriod):
1.109 +
1.110 + "A free/busy record abstraction for a quota group period."
1.111 +
1.112 + def __init__(self, start, end, uid=None, transp=None, recurrenceid=None,
1.113 + summary=None, organiser=None, attendee=None):
1.114 +
1.115 + """
1.116 + Initialise a free/busy period with the given 'start' and 'end' points,
1.117 + plus any 'uid', 'transp', 'recurrenceid', 'summary' and 'organiser'
1.118 + details.
1.119 +
1.120 + An additional 'attendee' parameter can be used to indicate the identity
1.121 + of the attendee recording the period.
1.122 + """
1.123 +
1.124 + FreeBusyPeriod.__init__(self, start, end, uid, transp, recurrenceid,
1.125 + summary, organiser)
1.126 + self.attendee = attendee or None
1.127 +
1.128 + def as_tuple(self, strings_only=False, string_datetimes=False):
1.129 +
1.130 + """
1.131 + Return the initialisation parameter tuple, converting datetimes and
1.132 + false value parameters to strings if 'strings_only' is set to a true
1.133 + value. Otherwise, if 'string_datetimes' is set to a true value, only the
1.134 + datetime values are converted to strings.
1.135 + """
1.136 +
1.137 + null = lambda x: (strings_only and [""] or [x])[0]
1.138 + return FreeBusyPeriod.as_tuple(self, strings_only, string_datetimes) + (
1.139 + self.attendee or null(self.attendee),)
1.140 +
1.141 + def __repr__(self):
1.142 + return "FreeBusyGroupPeriod%r" % (self.as_tuple(),)
1.143 +
1.144 class RecurringPeriod(Period):
1.145
1.146 """
1.147 @@ -482,6 +553,13 @@
1.148
1.149 "Common operations on free/busy period collections."
1.150
1.151 + period_columns = [
1.152 + "start", "end", "object_uid", "transp", "object_recurrenceid",
1.153 + "summary", "organiser"
1.154 + ]
1.155 +
1.156 + period_class = FreeBusyPeriod
1.157 +
1.158 def __init__(self, mutable=True):
1.159 self.mutable = mutable
1.160
1.161 @@ -495,6 +573,30 @@
1.162
1.163 return FreeBusyCollection(list(self), True)
1.164
1.165 + def make_period(self, t):
1.166 +
1.167 + """
1.168 + Make a period using the given tuple of arguments and the collection's
1.169 + column details.
1.170 + """
1.171 +
1.172 + args = []
1.173 + for arg, column in zip(t, self.period_columns):
1.174 + args.append(from_string(arg, "utf-8"))
1.175 + return self.period_class(*args)
1.176 +
1.177 + def make_tuple(self, t):
1.178 +
1.179 + """
1.180 + Return a tuple from the given tuple 't' conforming to the collection's
1.181 + column details.
1.182 + """
1.183 +
1.184 + args = []
1.185 + for arg, column in zip(t, self.period_columns):
1.186 + args.append(arg)
1.187 + return tuple(args)
1.188 +
1.189 # List emulation methods.
1.190
1.191 def __iadd__(self, periods):
1.192 @@ -589,7 +691,7 @@
1.193 while True:
1.194 period = it.next()
1.195 if period.get_start_point() > end:
1.196 - fb.append(FreeBusyPeriod(start, end))
1.197 + fb.append(self.period_class(start, end))
1.198 start = period.get_start_point()
1.199 end = period.get_end_point()
1.200 else:
1.201 @@ -597,7 +699,7 @@
1.202 except StopIteration:
1.203 pass
1.204
1.205 - fb.append(FreeBusyPeriod(start, end))
1.206 + fb.append(self.period_class(start, end))
1.207 return FreeBusyCollection(fb)
1.208
1.209 def invert_freebusy(self):
1.210 @@ -605,7 +707,7 @@
1.211 "Return the free periods from the collection as a new collection."
1.212
1.213 if not self:
1.214 - return FreeBusyCollection([FreeBusyPeriod(None, None)])
1.215 + return FreeBusyCollection([self.period_class(None, None)])
1.216
1.217 # Coalesce periods that overlap or are adjacent.
1.218
1.219 @@ -616,21 +718,83 @@
1.220
1.221 first = fb[0].get_start_point()
1.222 if first:
1.223 - free.append(FreeBusyPeriod(None, first))
1.224 + free.append(self.period_class(None, first))
1.225
1.226 start = fb[0].get_end_point()
1.227
1.228 for period in fb[1:]:
1.229 - free.append(FreeBusyPeriod(start, period.get_start_point()))
1.230 + free.append(self.period_class(start, period.get_start_point()))
1.231 start = period.get_end_point()
1.232
1.233 # Add an end-of-time period if appropriate.
1.234
1.235 if start:
1.236 - free.append(FreeBusyPeriod(start, None))
1.237 + free.append(self.period_class(start, None))
1.238
1.239 return FreeBusyCollection(free)
1.240
1.241 + def _update_freebusy(self, periods, uid, recurrenceid):
1.242 +
1.243 + """
1.244 + Update the free/busy details with the given 'periods', using the given
1.245 + 'uid' plus 'recurrenceid' to remove existing periods.
1.246 + """
1.247 +
1.248 + self._check_mutable()
1.249 +
1.250 + self.remove_event_periods(uid, recurrenceid)
1.251 +
1.252 + for p in periods:
1.253 + self.insert_period(p)
1.254 +
1.255 + def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser):
1.256 +
1.257 + """
1.258 + Update the free/busy details with the given 'periods', 'transp' setting,
1.259 + 'uid' plus 'recurrenceid' and 'summary' and 'organiser' details.
1.260 + """
1.261 +
1.262 + new_periods = []
1.263 +
1.264 + for p in periods:
1.265 + new_periods.append(
1.266 + self.period_class(p.get_start_point(), p.get_end_point(), uid, transp, recurrenceid, summary, organiser)
1.267 + )
1.268 +
1.269 + self._update_freebusy(new_periods, uid, recurrenceid)
1.270 +
1.271 +class SupportAttendee:
1.272 +
1.273 + "A mix-in that supports the affected attendee in free/busy periods."
1.274 +
1.275 + period_columns = FreeBusyCollectionBase.period_columns + ["attendee"]
1.276 + period_class = FreeBusyGroupPeriod
1.277 +
1.278 + def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser, attendee=None):
1.279 +
1.280 + """
1.281 + Update the free/busy details with the given 'periods', 'transp' setting,
1.282 + 'uid' plus 'recurrenceid' and 'summary' and 'organiser' details.
1.283 +
1.284 + An optional 'attendee' indicates the attendee affected by the period.
1.285 + """
1.286 +
1.287 + new_periods = []
1.288 +
1.289 + for p in periods:
1.290 + new_periods.append(
1.291 + self.period_class(p.get_start_point(), p.get_end_point(), uid, transp, recurrenceid, summary, organiser, attendee)
1.292 + )
1.293 +
1.294 + self._update_freebusy(new_periods, uid, recurrenceid)
1.295 +
1.296 +class SupportExpires:
1.297 +
1.298 + "A mix-in that supports the expiry datetime in free/busy periods."
1.299 +
1.300 + period_columns = FreeBusyCollectionBase.period_columns + ["expires"]
1.301 + period_class = FreeBusyOfferPeriod
1.302 +
1.303 def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser, expires=None):
1.304
1.305 """
1.306 @@ -641,12 +805,14 @@
1.307 free/busy offer.
1.308 """
1.309
1.310 - self._check_mutable()
1.311 -
1.312 - self.remove_event_periods(uid, recurrenceid)
1.313 + new_periods = []
1.314
1.315 for p in periods:
1.316 - self.insert_period(FreeBusyPeriod(p.get_start_point(), p.get_end_point(), uid, transp, recurrenceid, summary, organiser, expires))
1.317 + new_periods.append(
1.318 + self.period_class(p.get_start_point(), p.get_end_point(), uid, transp, recurrenceid, summary, organiser, expires)
1.319 + )
1.320 +
1.321 + self._update_freebusy(new_periods, uid, recurrenceid)
1.322
1.323 class FreeBusyCollection(FreeBusyCollectionBase):
1.324
1.325 @@ -851,6 +1017,18 @@
1.326 for fb in overlapping:
1.327 self.periods.remove(fb)
1.328
1.329 +class FreeBusyGroupCollection(SupportAttendee, FreeBusyCollection):
1.330 +
1.331 + "A collection of quota group free/busy objects."
1.332 +
1.333 + pass
1.334 +
1.335 +class FreeBusyOffersCollection(SupportExpires, FreeBusyCollection):
1.336 +
1.337 + "A collection of offered free/busy objects."
1.338 +
1.339 + pass
1.340 +
1.341 class FreeBusyDatabaseCollection(FreeBusyCollectionBase, DatabaseOperations):
1.342
1.343 """
1.344 @@ -858,8 +1036,6 @@
1.345 system.
1.346 """
1.347
1.348 - period_columns = ["start", "end", "object_uid", "transp", "object_recurrenceid", "summary", "organiser", "expires"]
1.349 -
1.350 def __init__(self, cursor, table_name, column_names=None, filter_values=None,
1.351 mutable=True, paramstyle=None):
1.352
1.353 @@ -875,9 +1051,6 @@
1.354 self.cursor = cursor
1.355 self.table_name = table_name
1.356
1.357 - def make_period(self, t):
1.358 - return FreeBusyPeriod(*from_strings(t, "utf-8"))
1.359 -
1.360 # List emulation methods.
1.361
1.362 def __nonzero__(self):
1.363 @@ -1151,6 +1324,18 @@
1.364
1.365 return columns, values
1.366
1.367 +class FreeBusyGroupDatabaseCollection(SupportAttendee, FreeBusyDatabaseCollection):
1.368 +
1.369 + "A collection of quota group free/busy objects."
1.370 +
1.371 + pass
1.372 +
1.373 +class FreeBusyOffersDatabaseCollection(SupportExpires, FreeBusyDatabaseCollection):
1.374 +
1.375 + "A collection of offered free/busy objects."
1.376 +
1.377 + pass
1.378 +
1.379 # Period layout.
1.380
1.381 def get_scale(periods, tzid, view_period=None):