1.1 --- a/imiptools/freebusy/__init__.py Fri May 26 23:58:06 2017 +0200
1.2 +++ b/imiptools/freebusy/__init__.py Thu Jun 01 23:26:38 2017 +0200
1.3 @@ -25,6 +25,7 @@
1.4 FreeBusyCollection, \
1.5 FreeBusyGroupCollection, \
1.6 FreeBusyOffersCollection, \
1.7 - SupportAttendee, SupportExpires
1.8 + SupportAttendee, SupportExpires, \
1.9 + period_from_tuple, period_to_tuple
1.10
1.11 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/imiptools/freebusy/common.py Fri May 26 23:58:06 2017 +0200
2.2 +++ b/imiptools/freebusy/common.py Thu Jun 01 23:26:38 2017 +0200
2.3 @@ -30,7 +30,10 @@
2.4 "Interpret 's' using 'encoding', preserving None."
2.5
2.6 if s:
2.7 - return unicode(s, encoding)
2.8 + if isinstance(s, unicode):
2.9 + return s
2.10 + else:
2.11 + return unicode(s, encoding)
2.12 else:
2.13 return s
2.14
2.15 @@ -43,6 +46,39 @@
2.16 else:
2.17 return s
2.18
2.19 +class period_from_tuple:
2.20 +
2.21 + "Convert a tuple to an instance of the given 'period_class'."
2.22 +
2.23 + def __init__(self, period_class):
2.24 + self.period_class = period_class
2.25 + def __call__(self, t):
2.26 + return make_period(t, self.period_class)
2.27 +
2.28 +def period_to_tuple(p):
2.29 +
2.30 + "Convert period 'p' to a tuple for serialisation."
2.31 +
2.32 + return p.as_tuple(strings_only=True)
2.33 +
2.34 +def make_period(t, period_class):
2.35 +
2.36 + "Convert tuple 't' to an instance of the given 'period_class'."
2.37 +
2.38 + args = []
2.39 + for arg, column in zip(t, period_class.period_columns):
2.40 + args.append(from_string(arg, "utf-8"))
2.41 + return period_class(*args)
2.42 +
2.43 +def make_tuple(t, period_class):
2.44 +
2.45 + "Restrict tuple 't' to the columns appropriate for 'period_class'."
2.46 +
2.47 + args = []
2.48 + for arg, column in zip(t, period_class.period_columns):
2.49 + args.append(arg)
2.50 + return tuple(args)
2.51 +
2.52
2.53
2.54 # Period abstractions.
2.55 @@ -51,6 +87,11 @@
2.56
2.57 "A free/busy record abstraction."
2.58
2.59 + period_columns = [
2.60 + "start", "end", "object_uid", "transp", "object_recurrenceid",
2.61 + "summary", "organiser"
2.62 + ]
2.63 +
2.64 def __init__(self, start, end, uid=None, transp=None, recurrenceid=None,
2.65 summary=None, organiser=None):
2.66
2.67 @@ -61,7 +102,7 @@
2.68 """
2.69
2.70 PeriodBase.__init__(self, start, end)
2.71 - self.uid = uid
2.72 + self.uid = uid or None
2.73 self.transp = transp or None
2.74 self.recurrenceid = recurrenceid or None
2.75 self.summary = summary or None
2.76 @@ -143,6 +184,8 @@
2.77
2.78 "A free/busy record abstraction for an offer period."
2.79
2.80 + period_columns = FreeBusyPeriod.period_columns + ["expires"]
2.81 +
2.82 def __init__(self, start, end, uid=None, transp=None, recurrenceid=None,
2.83 summary=None, organiser=None, expires=None):
2.84
2.85 @@ -180,6 +223,8 @@
2.86
2.87 "A free/busy record abstraction for a quota group period."
2.88
2.89 + period_columns = FreeBusyPeriod.period_columns + ["attendee"]
2.90 +
2.91 def __init__(self, start, end, uid=None, transp=None, recurrenceid=None,
2.92 summary=None, organiser=None, attendee=None):
2.93
2.94 @@ -225,15 +270,14 @@
2.95 def __repr__(self):
2.96 return "FreeBusyGroupPeriod%r" % (self.as_tuple(),)
2.97
2.98 +
2.99 +
2.100 +# Collection abstractions.
2.101 +
2.102 class FreeBusyCollectionBase:
2.103
2.104 "Common operations on free/busy period collections."
2.105
2.106 - period_columns = [
2.107 - "start", "end", "object_uid", "transp", "object_recurrenceid",
2.108 - "summary", "organiser"
2.109 - ]
2.110 -
2.111 period_class = FreeBusyPeriod
2.112
2.113 def __init__(self, mutable=True):
2.114 @@ -243,6 +287,12 @@
2.115 if not self.mutable:
2.116 raise TypeError, "Cannot mutate this collection."
2.117
2.118 + def close(self):
2.119 +
2.120 + "Close the collection."
2.121 +
2.122 + pass
2.123 +
2.124 def copy(self):
2.125
2.126 "Make an independent mutable copy of the collection."
2.127 @@ -256,10 +306,7 @@
2.128 column details.
2.129 """
2.130
2.131 - args = []
2.132 - for arg, column in zip(t, self.period_columns):
2.133 - args.append(from_string(arg, "utf-8"))
2.134 - return self.period_class(*args)
2.135 + return make_period(t, self.period_class)
2.136
2.137 def make_tuple(self, t):
2.138
2.139 @@ -268,10 +315,7 @@
2.140 column details.
2.141 """
2.142
2.143 - args = []
2.144 - for arg, column in zip(t, self.period_columns):
2.145 - args.append(arg)
2.146 - return tuple(args)
2.147 + return make_tuple(t, self.period_class)
2.148
2.149 # List emulation methods.
2.150
2.151 @@ -284,6 +328,16 @@
2.152
2.153 # Operations.
2.154
2.155 + def insert_period(self, period):
2.156 +
2.157 + """
2.158 + Insert the given 'period' into the collection.
2.159 +
2.160 + This should be implemented in subclasses.
2.161 + """
2.162 +
2.163 + pass
2.164 +
2.165 def insert_periods(self, periods):
2.166
2.167 "Insert the given 'periods' into the collection."
2.168 @@ -448,7 +502,6 @@
2.169
2.170 "A mix-in that supports the affected attendee in free/busy periods."
2.171
2.172 - period_columns = FreeBusyCollectionBase.period_columns + ["attendee"]
2.173 period_class = FreeBusyGroupPeriod
2.174
2.175 def _update_freebusy(self, periods, uid, recurrenceid, attendee=None):
2.176 @@ -486,7 +539,6 @@
2.177
2.178 "A mix-in that supports the expiry datetime in free/busy periods."
2.179
2.180 - period_columns = FreeBusyCollectionBase.period_columns + ["expires"]
2.181 period_class = FreeBusyOfferPeriod
2.182
2.183 def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser, expires=None):
2.184 @@ -526,7 +578,27 @@
2.185 """
2.186
2.187 FreeBusyCollectionBase.__init__(self, mutable)
2.188 - self.periods = periods or []
2.189 +
2.190 + if periods is not None:
2.191 + self.periods = periods
2.192 + else:
2.193 + self.periods = []
2.194 +
2.195 + def get_filename(self):
2.196 +
2.197 + "Return any filename for the periods collection."
2.198 +
2.199 + if hasattr(self.periods, "filename"):
2.200 + return self.periods.filename
2.201 + else:
2.202 + return None
2.203 +
2.204 + def close(self):
2.205 +
2.206 + "Close the collection."
2.207 +
2.208 + if hasattr(self.periods, "close"):
2.209 + self.periods.close()
2.210
2.211 # List emulation methods.
2.212
2.213 @@ -542,6 +614,11 @@
2.214 def __getitem__(self, i):
2.215 return self.periods[i]
2.216
2.217 + # Dictionary emulation methods (even though this is not a mapping).
2.218 +
2.219 + def clear(self):
2.220 + del self.periods[:]
2.221 +
2.222 # Operations.
2.223
2.224 def insert_period(self, period):
3.1 --- a/imiptools/freebusy/database.py Fri May 26 23:58:06 2017 +0200
3.2 +++ b/imiptools/freebusy/database.py Thu Jun 01 23:26:38 2017 +0200
3.3 @@ -90,6 +90,8 @@
3.4 self.cursor = cursor
3.5 self.table_name = table_name
3.6
3.7 + self.period_columns = self.period_class.period_columns
3.8 +
3.9 # List emulation methods.
3.10
3.11 def __nonzero__(self):
3.12 @@ -116,6 +118,15 @@
3.13 def __getitem__(self, i):
3.14 return list(iter(self))[i]
3.15
3.16 + # Dictionary emulation methods (even though this is not a mapping).
3.17 +
3.18 + def clear(self):
3.19 + query, values = self.get_query(
3.20 + "delete from %(table)s :condition" % {
3.21 + "table" : self.table_name
3.22 + })
3.23 + self.cursor.execute(query, values)
3.24 +
3.25 # Operations.
3.26
3.27 def insert_period(self, period):
4.1 --- a/imiptools/stores/database/common.py Fri May 26 23:58:06 2017 +0200
4.2 +++ b/imiptools/stores/database/common.py Thu Jun 01 23:26:38 2017 +0200
4.3 @@ -31,6 +31,12 @@
4.4
4.5 def first(l): return l[0]
4.6
4.7 +def have_table(obj, collection, table_name):
4.8 +
4.9 + "Return whether 'obj' is a 'collection' using the given 'table_name'."
4.10 +
4.11 + return isinstance(obj, collection) and obj.table_name == table_name
4.12 +
4.13 # Store classes.
4.14
4.15 class DatabaseStoreBase(DatabaseOperations):
4.16 @@ -463,42 +469,42 @@
4.17
4.18 # Free/busy period access.
4.19
4.20 - def get_freebusy(self, user, name=None, mutable=False, cls=None):
4.21 + def get_freebusy(self, user, name=None, mutable=False, collection=None):
4.22
4.23 "Get free/busy details for the given 'user'."
4.24
4.25 table = name or "freebusy"
4.26 - cls = cls or FreeBusyDatabaseCollection
4.27 - return cls(self.cursor, table, ["store_user"], [user], mutable, self.paramstyle)
4.28 + collection = collection or FreeBusyDatabaseCollection
4.29 + return collection(self.cursor, table, ["store_user"], [user], mutable, self.paramstyle)
4.30
4.31 - def get_freebusy_for_other(self, user, other, mutable=False, cls=None):
4.32 + def get_freebusy_for_other(self, user, other, mutable=False, collection=None):
4.33
4.34 "For the given 'user', get free/busy details for the 'other' user."
4.35
4.36 - cls = cls or FreeBusyDatabaseCollection
4.37 - return cls(self.cursor, self.freebusy_other_table, ["store_user", "other"], [user, other], mutable, self.paramstyle)
4.38 + collection = collection or FreeBusyDatabaseCollection
4.39 + return collection(self.cursor, self.freebusy_other_table, ["store_user", "other"], [user, other], mutable, self.paramstyle)
4.40
4.41 - def set_freebusy(self, user, freebusy, name=None, cls=None):
4.42 + def set_freebusy(self, user, freebusy, name=None, collection=None):
4.43
4.44 "For the given 'user', set 'freebusy' details."
4.45
4.46 table = name or "freebusy"
4.47 - cls = cls or FreeBusyDatabaseCollection
4.48 + collection = collection or FreeBusyDatabaseCollection
4.49
4.50 - if not isinstance(freebusy, cls) or freebusy.table_name != table:
4.51 - fbc = cls(self.cursor, table, ["store_user"], [user], True, self.paramstyle)
4.52 + if not have_table(freebusy, collection, table):
4.53 + fbc = collection(self.cursor, table, ["store_user"], [user], True, self.paramstyle)
4.54 fbc += freebusy
4.55
4.56 return True
4.57
4.58 - def set_freebusy_for_other(self, user, freebusy, other, cls=None):
4.59 + def set_freebusy_for_other(self, user, freebusy, other, collection=None):
4.60
4.61 "For the given 'user', set 'freebusy' details for the 'other' user."
4.62
4.63 - cls = cls or FreeBusyDatabaseCollection
4.64 + collection = collection or FreeBusyDatabaseCollection
4.65
4.66 - if not isinstance(freebusy, cls) or freebusy.table_name != self.freebusy_other_table:
4.67 - fbc = cls(self.cursor, self.freebusy_other_table, ["store_user", "other"], [user, other], True, self.paramstyle)
4.68 + if not have_table(freebusy, collection, self.freebusy_other_table):
4.69 + fbc = collection(self.cursor, self.freebusy_other_table, ["store_user", "other"], [user, other], True, self.paramstyle)
4.70 fbc += freebusy
4.71
4.72 return True
4.73 @@ -544,7 +550,7 @@
4.74
4.75 "For the given 'user', set 'freebusy' offers."
4.76
4.77 - return self.set_freebusy(user, freebusy, "freebusy_offers", cls=FreeBusyOffersDatabaseCollection)
4.78 + return self.set_freebusy(user, freebusy, "freebusy_offers", collection=FreeBusyOffersDatabaseCollection)
4.79
4.80 # Requests and counter-proposals.
4.81
4.82 @@ -1037,9 +1043,9 @@
4.83 # Compatibility methods.
4.84
4.85 def get_freebusy_for_other(self, user, other, mutable=False):
4.86 - return DatabaseStore.get_freebusy_for_other(self, user, other, mutable, cls=FreeBusyGroupDatabaseCollection)
4.87 + return DatabaseStore.get_freebusy_for_other(self, user, other, mutable, collection=FreeBusyGroupDatabaseCollection)
4.88
4.89 def set_freebusy_for_other(self, user, freebusy, other):
4.90 - return DatabaseStore.set_freebusy_for_other(self, user, freebusy, other, cls=FreeBusyGroupDatabaseCollection)
4.91 + return DatabaseStore.set_freebusy_for_other(self, user, freebusy, other, collection=FreeBusyGroupDatabaseCollection)
4.92
4.93 # vim: tabstop=4 expandtab shiftwidth=4
5.1 --- a/imiptools/stores/file.py Fri May 26 23:58:06 2017 +0200
5.2 +++ b/imiptools/stores/file.py Thu Jun 01 23:26:38 2017 +0200
5.3 @@ -26,15 +26,18 @@
5.4 from imiptools.data import Object, make_calendar, parse_object, to_stream
5.5 from imiptools.dates import format_datetime, get_datetime, to_timezone
5.6 from imiptools.filesys import fix_permissions, FileBase
5.7 -from imiptools.freebusy import FreeBusyPeriod, FreeBusyGroupPeriod, \
5.8 - FreeBusyOfferPeriod, \
5.9 - FreeBusyCollection, \
5.10 +
5.11 +from imiptools.freebusy import FreeBusyCollection, \
5.12 FreeBusyGroupCollection, \
5.13 - FreeBusyOffersCollection
5.14 -from imiptools.text import get_table, set_defaults
5.15 + FreeBusyOffersCollection, \
5.16 + period_from_tuple, \
5.17 + period_to_tuple
5.18 +
5.19 +from imiptools.text import FileTable, FileTableDict, FileTableSingle, \
5.20 + have_table
5.21 +
5.22 from os.path import isdir, isfile, join
5.23 from os import listdir, remove, rmdir
5.24 -import codecs
5.25
5.26 # Obtain defaults from the settings.
5.27
5.28 @@ -46,7 +49,7 @@
5.29
5.30 class FileStoreBase(FileBase):
5.31
5.32 - "A file store supporting user-specific locking and tabular data."
5.33 + "A file store supporting user-specific locking."
5.34
5.35 def acquire_lock(self, user, timeout=None):
5.36 FileBase.acquire_lock(self, timeout, user)
5.37 @@ -54,104 +57,6 @@
5.38 def release_lock(self, user):
5.39 FileBase.release_lock(self, user)
5.40
5.41 - # Utility methods.
5.42 -
5.43 - def _set_defaults(self, t, empty_defaults):
5.44 - return set_defaults(t, empty_defaults)
5.45 -
5.46 - def _get_table(self, filename, empty_defaults=None, tab_separated=True):
5.47 -
5.48 - """
5.49 - From the file having the given 'filename', return a list of tuples
5.50 - representing the file's contents.
5.51 -
5.52 - The 'empty_defaults' is a list of (index, value) tuples indicating the
5.53 - default value where a column either does not exist or provides an empty
5.54 - value.
5.55 -
5.56 - If 'tab_separated' is specified and is a false value, line parsing using
5.57 - the imiptools.text.parse_line function will be performed instead of
5.58 - splitting each line of the file using tab characters as separators.
5.59 - """
5.60 -
5.61 - return get_table(filename, empty_defaults, tab_separated)
5.62 -
5.63 - def _get_table_atomic(self, user, filename, empty_defaults=None, tab_separated=True):
5.64 -
5.65 - """
5.66 - From the file for the given 'user' having the given 'filename', return
5.67 - a list of tuples representing the file's contents.
5.68 -
5.69 - The 'empty_defaults' is a list of (index, value) tuples indicating the
5.70 - default value where a column either does not exist or provides an empty
5.71 - value.
5.72 -
5.73 - If 'tab_separated' is specified and is a false value, line parsing using
5.74 - the imiptools.text.parse_line function will be performed instead of
5.75 - splitting each line of the file using tab characters as separators.
5.76 - """
5.77 -
5.78 - self.acquire_lock(user)
5.79 - try:
5.80 - return self._get_table(filename, empty_defaults, tab_separated)
5.81 - finally:
5.82 - self.release_lock(user)
5.83 -
5.84 - def _set_table(self, filename, items, empty_defaults=None):
5.85 -
5.86 - """
5.87 - Write to the file having the given 'filename' the 'items'.
5.88 -
5.89 - The 'empty_defaults' is a list of (index, value) tuples indicating the
5.90 - default value where a column either does not exist or provides an empty
5.91 - value.
5.92 - """
5.93 -
5.94 - f = codecs.open(filename, "wb", encoding="utf-8")
5.95 - try:
5.96 - for item in items:
5.97 - self._set_table_item(f, item, empty_defaults)
5.98 - finally:
5.99 - f.close()
5.100 - fix_permissions(filename)
5.101 -
5.102 - def _set_table_item(self, f, item, empty_defaults=None):
5.103 -
5.104 - "Set in table 'f' the given 'item', using any 'empty_defaults'."
5.105 -
5.106 - if empty_defaults:
5.107 - item = self._set_defaults(list(item), empty_defaults)
5.108 - f.write("\t".join(item) + "\n")
5.109 -
5.110 - def _set_table_atomic(self, user, filename, items, empty_defaults=None):
5.111 -
5.112 - """
5.113 - For the given 'user', write to the file having the given 'filename' the
5.114 - 'items'.
5.115 -
5.116 - The 'empty_defaults' is a list of (index, value) tuples indicating the
5.117 - default value where a column either does not exist or provides an empty
5.118 - value.
5.119 - """
5.120 -
5.121 - self.acquire_lock(user)
5.122 - try:
5.123 - self._set_table(filename, items, empty_defaults)
5.124 - finally:
5.125 - self.release_lock(user)
5.126 -
5.127 - def _set_freebusy(self, user, freebusy, filename):
5.128 -
5.129 - """
5.130 - For the given 'user', convert the 'freebusy' details to a form suitable
5.131 - for writing to 'filename'.
5.132 - """
5.133 -
5.134 - # Obtain tuples from the free/busy objects.
5.135 -
5.136 - self._set_table_atomic(user, filename,
5.137 - map(lambda fb: freebusy.make_tuple(fb.as_tuple(strings_only=True)), list(freebusy)))
5.138 -
5.139 class Store(FileStoreBase, StoreBase):
5.140
5.141 "A file store of tabular free/busy data and objects."
5.142 @@ -438,64 +343,86 @@
5.143 """
5.144
5.145 filename = self.get_object_in_store(user, "freebusy-providers")
5.146 - if not filename or not isfile(filename):
5.147 + if not filename:
5.148 return None
5.149
5.150 # Attempt to read providers, with a declaration of the datetime
5.151 # from which such providers are considered as still being active.
5.152
5.153 - t = self._get_table_atomic(user, filename, [(1, None)])
5.154 - try:
5.155 - dt_string = t[0][0]
5.156 - except IndexError:
5.157 + t = self._get_freebusy_providers_table(filename)
5.158 + header = t.get_header_values()
5.159 + if not header:
5.160 return None
5.161
5.162 - return dt_string, t[1:]
5.163 + return header[0], t
5.164 +
5.165 + def _get_freebusy_providers_table(self, filename):
5.166 +
5.167 + "Return a file-based table for storing providers in 'filename'."
5.168
5.169 - def _set_freebusy_providers(self, user, dt_string, t):
5.170 + return FileTable(filename,
5.171 + in_defaults=[(1, None)],
5.172 + out_defaults=[(1, "")],
5.173 + headers=1)
5.174
5.175 - "Set the given provider timestamp 'dt_string' and table 't'."
5.176 + def _set_freebusy_providers(self, user, dt_string, providers):
5.177 +
5.178 + "Set the given provider timestamp 'dt_string' and 'providers'."
5.179
5.180 filename = self.get_object_in_store(user, "freebusy-providers")
5.181 if not filename:
5.182 return False
5.183
5.184 - t.insert(0, (dt_string,))
5.185 - self._set_table_atomic(user, filename, t, [(1, "")])
5.186 + self.acquire_lock(user)
5.187 + try:
5.188 + if not have_table(providers, filename):
5.189 + pr = self._get_freebusy_providers_table(filename)
5.190 + pr.replaceall(providers)
5.191 + providers = pr
5.192 + providers.set_header_values([dt_string])
5.193 + providers.close()
5.194 + finally:
5.195 + self.release_lock(user)
5.196 return True
5.197
5.198 # Free/busy period access.
5.199
5.200 - def get_freebusy(self, user, name=None, mutable=False, cls=None):
5.201 + def get_freebusy(self, user, name=None, mutable=False):
5.202
5.203 "Get free/busy details for the given 'user'."
5.204
5.205 filename = self.get_object_in_store(user, name or "freebusy")
5.206
5.207 - if not filename or not isfile(filename):
5.208 - periods = []
5.209 - else:
5.210 - cls = cls or FreeBusyPeriod
5.211 - periods = map(lambda t: cls(*t),
5.212 - self._get_table_atomic(user, filename))
5.213 + if not filename:
5.214 + return []
5.215
5.216 - return FreeBusyCollection(periods, mutable)
5.217 + return self._get_freebusy(filename, mutable, FreeBusyCollection)
5.218
5.219 - def get_freebusy_for_other(self, user, other, mutable=False, cls=None, collection=None):
5.220 + def get_freebusy_for_other(self, user, other, mutable=False, collection=None):
5.221
5.222 "For the given 'user', get free/busy details for the 'other' user."
5.223
5.224 filename = self.get_object_in_store(user, "freebusy-other", other)
5.225
5.226 - if not filename or not isfile(filename):
5.227 - periods = []
5.228 - else:
5.229 - cls = cls or FreeBusyPeriod
5.230 - periods = map(lambda t: cls(*t),
5.231 - self._get_table_atomic(user, filename))
5.232 + if not filename:
5.233 + return []
5.234 +
5.235 + return self._get_freebusy(filename, mutable, collection or FreeBusyCollection)
5.236 +
5.237 + def _get_freebusy(self, filename, mutable=False, collection=None):
5.238 +
5.239 + """
5.240 + Return a free/busy collection for 'filename' with the given 'mutable'
5.241 + condition, employing the specified 'collection' class.
5.242 + """
5.243
5.244 collection = collection or FreeBusyCollection
5.245 - return collection(periods, mutable)
5.246 +
5.247 + periods = FileTable(filename, mutable=mutable,
5.248 + in_converter=period_from_tuple(collection.period_class),
5.249 + out_converter=period_to_tuple)
5.250 +
5.251 + return collection(periods, mutable=mutable)
5.252
5.253 def set_freebusy(self, user, freebusy, name=None):
5.254
5.255 @@ -505,10 +432,9 @@
5.256 if not filename:
5.257 return False
5.258
5.259 - self._set_freebusy(user, freebusy, filename)
5.260 - return True
5.261 + return self._set_freebusy(user, freebusy, filename)
5.262
5.263 - def set_freebusy_for_other(self, user, freebusy, other):
5.264 + def set_freebusy_for_other(self, user, freebusy, other, collection=None):
5.265
5.266 "For the given 'user', set 'freebusy' details for the 'other' user."
5.267
5.268 @@ -516,7 +442,24 @@
5.269 if not filename:
5.270 return False
5.271
5.272 - self._set_freebusy(user, freebusy, filename)
5.273 + return self._set_freebusy(user, freebusy, filename, collection)
5.274 +
5.275 + def _set_freebusy(self, user, freebusy, filename, collection=None):
5.276 +
5.277 + "For the given 'user', set 'freebusy' details for the given 'filename'."
5.278 +
5.279 + # Copy to the specified table if different from that given.
5.280 +
5.281 + self.acquire_lock(user)
5.282 + try:
5.283 + if not have_table(freebusy, filename):
5.284 + fbc = self._get_freebusy(filename, True, collection)
5.285 + fbc += freebusy
5.286 + freebusy = fbc
5.287 + freebusy.close()
5.288 + finally:
5.289 + self.release_lock(user)
5.290 +
5.291 return True
5.292
5.293 def get_freebusy_others(self, user):
5.294 @@ -539,7 +482,11 @@
5.295
5.296 "Get free/busy offers for the given 'user'."
5.297
5.298 - offers = []
5.299 + filename = self.get_object_in_store(user, "freebusy-offers")
5.300 +
5.301 + if not filename:
5.302 + return []
5.303 +
5.304 expired = []
5.305 now = to_timezone(datetime.utcnow(), "UTC")
5.306
5.307 @@ -547,39 +494,43 @@
5.308
5.309 self.acquire_lock(user)
5.310 try:
5.311 - l = self.get_freebusy(user, "freebusy-offers", cls=FreeBusyOfferPeriod)
5.312 - for fb in l:
5.313 + offers = self._get_freebusy(filename, True, FreeBusyOffersCollection)
5.314 + for fb in offers:
5.315 if fb.expires and get_datetime(fb.expires) <= now:
5.316 - expired.append(fb)
5.317 - else:
5.318 - offers.append(fb)
5.319 -
5.320 + offers.remove(fb)
5.321 if expired:
5.322 - self.set_freebusy_offers(user, offers)
5.323 + offers.close()
5.324 finally:
5.325 self.release_lock(user)
5.326
5.327 - return FreeBusyOffersCollection(offers, mutable)
5.328 + offers.mutable = mutable
5.329 + return offers
5.330
5.331 # Requests and counter-proposals.
5.332
5.333 - def _get_requests(self, user, queue):
5.334 + def get_requests(self, user, queue="requests"):
5.335
5.336 "Get requests for the given 'user' from the given 'queue'."
5.337
5.338 filename = self.get_object_in_store(user, queue)
5.339 - if not filename or not isfile(filename):
5.340 + if not filename:
5.341 return []
5.342
5.343 - return self._get_table_atomic(user, filename, [(1, None), (2, None)])
5.344 + return FileTable(filename,
5.345 + in_defaults=[(1, None), (2, None)],
5.346 + out_defaults=[(1, ""), (2, "")])
5.347
5.348 - def get_requests(self, user):
5.349 + def set_request(self, user, uid, recurrenceid=None, type=None):
5.350
5.351 - "Get requests for the given 'user'."
5.352 + """
5.353 + For the given 'user', set the queued 'uid' and 'recurrenceid',
5.354 + indicating a request, along with any given 'type'.
5.355 + """
5.356
5.357 - return self._get_requests(user, "requests")
5.358 + requests = self.get_requests(user)
5.359 + return self.set_requests(user, [(uid, recurrenceid, type)])
5.360
5.361 - def _set_requests(self, user, requests, queue):
5.362 + def set_requests(self, user, requests, queue="requests"):
5.363
5.364 """
5.365 For the given 'user', set the list of queued 'requests' in the given
5.366 @@ -590,47 +541,20 @@
5.367 if not filename:
5.368 return False
5.369
5.370 - self._set_table_atomic(user, filename, requests, [(1, ""), (2, "")])
5.371 - return True
5.372 -
5.373 - def set_requests(self, user, requests):
5.374 -
5.375 - "For the given 'user', set the list of queued 'requests'."
5.376 -
5.377 - return self._set_requests(user, requests, "requests")
5.378 -
5.379 - def _set_request(self, user, request, queue):
5.380 -
5.381 - """
5.382 - For the given 'user', set the given 'request' in the given 'queue'.
5.383 - """
5.384 -
5.385 - filename = self.get_object_in_store(user, queue)
5.386 - if not filename:
5.387 - return False
5.388 + # Copy to the specified table if different from that given.
5.389
5.390 self.acquire_lock(user)
5.391 try:
5.392 - f = codecs.open(filename, "ab", encoding="utf-8")
5.393 - try:
5.394 - self._set_table_item(f, request, [(1, ""), (2, "")])
5.395 - finally:
5.396 - f.close()
5.397 - fix_permissions(filename)
5.398 + if not have_table(requests, filename):
5.399 + req = self.get_requests(user, queue)
5.400 + req.replaceall(requests)
5.401 + requests = req
5.402 + requests.close()
5.403 finally:
5.404 self.release_lock(user)
5.405
5.406 return True
5.407
5.408 - def set_request(self, user, uid, recurrenceid=None, type=None):
5.409 -
5.410 - """
5.411 - For the given 'user', set the queued 'uid' and 'recurrenceid',
5.412 - indicating a request, along with any given 'type'.
5.413 - """
5.414 -
5.415 - return self._set_request(user, (uid, recurrenceid, type), "requests")
5.416 -
5.417 def get_counters(self, user, uid, recurrenceid=None):
5.418
5.419 """
5.420 @@ -807,10 +731,10 @@
5.421 "Return a list of delegates for 'quota'."
5.422
5.423 filename = self.get_object_in_store(quota, "delegates")
5.424 - if not filename or not isfile(filename):
5.425 + if not filename:
5.426 return []
5.427
5.428 - return [value for (value,) in self._get_table_atomic(quota, filename)]
5.429 + return FileTableSingle(filename)
5.430
5.431 def set_delegates(self, quota, delegates):
5.432
5.433 @@ -820,7 +744,16 @@
5.434 if not filename:
5.435 return False
5.436
5.437 - self._set_table_atomic(quota, filename, [(value,) for value in delegates])
5.438 + self.acquire_lock(quota)
5.439 + try:
5.440 + if not have_table(delegates, filename):
5.441 + de = self.get_delegates(quota)
5.442 + de.replaceall(delegates)
5.443 + delegates = de
5.444 + delegates.close()
5.445 + finally:
5.446 + self.release_lock(quota)
5.447 +
5.448 return True
5.449
5.450 # Groups of users sharing quotas.
5.451 @@ -830,10 +763,10 @@
5.452 "Return the identity mappings for the given 'quota' as a dictionary."
5.453
5.454 filename = self.get_object_in_store(quota, "groups")
5.455 - if not filename or not isfile(filename):
5.456 + if not filename:
5.457 return {}
5.458
5.459 - return dict(self._get_table_atomic(quota, filename, tab_separated=False))
5.460 + return FileTableDict(filename, tab_separated=False)
5.461
5.462 def set_groups(self, quota, groups):
5.463
5.464 @@ -843,7 +776,16 @@
5.465 if not filename:
5.466 return False
5.467
5.468 - self._set_table_atomic(quota, filename, groups.items())
5.469 + self.acquire_lock(quota)
5.470 + try:
5.471 + if not have_table(groups, filename):
5.472 + gr = self.get_groups(quota)
5.473 + gr.updateall(groups)
5.474 + groups = gr
5.475 + groups.close()
5.476 + finally:
5.477 + self.release_lock(quota)
5.478 +
5.479 return True
5.480
5.481 def get_limits(self, quota):
5.482 @@ -854,10 +796,10 @@
5.483 """
5.484
5.485 filename = self.get_object_in_store(quota, "limits")
5.486 - if not filename or not isfile(filename):
5.487 + if not filename:
5.488 return {}
5.489
5.490 - return dict(self._get_table_atomic(quota, filename, tab_separated=False))
5.491 + return FileTableDict(filename, tab_separated=False)
5.492
5.493 def set_limits(self, quota, limits):
5.494
5.495 @@ -870,7 +812,16 @@
5.496 if not filename:
5.497 return False
5.498
5.499 - self._set_table_atomic(quota, filename, limits.items())
5.500 + self.acquire_lock(quota)
5.501 + try:
5.502 + if not have_table(limits, filename):
5.503 + li = self.get_limits(quota)
5.504 + li.updateall(limits)
5.505 + limits = li
5.506 + limits.close()
5.507 + finally:
5.508 + self.release_lock(quota)
5.509 +
5.510 return True
5.511
5.512 # Journal entry methods.
5.513 @@ -896,6 +847,9 @@
5.514 # Compatibility methods.
5.515
5.516 def get_freebusy_for_other(self, user, other, mutable=False):
5.517 - return Store.get_freebusy_for_other(self, user, other, mutable, cls=FreeBusyGroupPeriod, collection=FreeBusyGroupCollection)
5.518 + return Store.get_freebusy_for_other(self, user, other, mutable, collection=FreeBusyGroupCollection)
5.519 +
5.520 + def set_freebusy_for_other(self, user, entries, other):
5.521 + Store.set_freebusy_for_other(self, user, entries, other, collection=FreeBusyGroupCollection)
5.522
5.523 # vim: tabstop=4 expandtab shiftwidth=4
6.1 --- a/imiptools/text.py Fri May 26 23:58:06 2017 +0200
6.2 +++ b/imiptools/text.py Thu Jun 01 23:26:38 2017 +0200
6.3 @@ -19,47 +19,305 @@
6.4 this program. If not, see <http://www.gnu.org/licenses/>.
6.5 """
6.6
6.7 +from imiptools.filesys import fix_permissions
6.8 +from os.path import isfile
6.9 import codecs
6.10 import re
6.11
6.12 -# Parsing of lines to obtain functions and arguments.
6.13 +def have_table(obj, filename):
6.14 +
6.15 + "Return whether 'obj' is a table using the given 'filename'."
6.16 +
6.17 + return hasattr(obj, "get_filename") and obj.get_filename() == filename
6.18 +
6.19 +class FileTable:
6.20 +
6.21 + "A file-based data table."
6.22 +
6.23 + def __init__(self, filename, mutable=True,
6.24 + in_defaults=None, out_defaults=None,
6.25 + in_converter=None, out_converter=None,
6.26 + tab_separated=True, headers=False):
6.27 +
6.28 + """
6.29 + Open the table from the file having the given 'filename'. If 'mutable'
6.30 + is given as a true value (as is the default), the table can be modified.
6.31 +
6.32 + The 'in_defaults' is a list of (index, value) tuples indicating the
6.33 + default value where a column either does not exist or provides an empty
6.34 + value. The 'out_defaults' is a corresponding list used to serialise
6.35 + missing and empty values.
6.36 +
6.37 + The 'in_converter' is a callable accepting a tuple of values and
6.38 + returning an object. The corresponding 'out_converter' accepts an object
6.39 + and returns a tuple of values.
6.40 +
6.41 + If 'tab_separated' is specified and is a false value, line parsing using
6.42 + the imiptools.text.parse_line function will be performed instead of
6.43 + splitting each line of the file using tab characters as separators.
6.44 +
6.45 + If 'headers' is specified and is not false, the first line in the table
6.46 + will provide header value information.
6.47 + """
6.48 +
6.49 + self.filename = filename
6.50 + self.mutable = mutable
6.51 + self.in_defaults = in_defaults
6.52 + self.out_defaults = out_defaults
6.53 + self.in_converter = in_converter
6.54 + self.out_converter = out_converter
6.55 + self.tab_separated = tab_separated
6.56 +
6.57 + # Obtain the items. In subsequent implementations, the items could be
6.58 + # retrieved dynamically.
6.59 +
6.60 + items = []
6.61 +
6.62 + if isfile(filename):
6.63 + for item in get_table(filename, in_defaults, tab_separated):
6.64 + if self.in_converter:
6.65 + item = self.in_converter(item)
6.66 + items.append(item)
6.67 +
6.68 + # Obtain header values and separate them from the rest of the data.
6.69 +
6.70 + self.table = items[headers and 1 or 0:]
6.71 + self.header_values = headers and items and items[0] or []
6.72 + self.headers = headers
6.73 +
6.74 + def get_filename(self):
6.75 + return self.filename
6.76 +
6.77 + def get_header_values(self):
6.78 + return self.header_values
6.79 +
6.80 + def set_header_values(self, values):
6.81 + self.header_values = values
6.82 +
6.83 + def close(self):
6.84
6.85 -line_pattern_str = (
6.86 - r"(?:"
6.87 - r"(?:'(.*?)')" # single-quoted text
6.88 - r"|"
6.89 - r'(?:"(.*?)")' # double-quoted text
6.90 - r"|"
6.91 - r"([^\s]+)" # non-whitespace characters
6.92 - r")+"
6.93 - r"(?:\s+|$)" # optional trailing whitespace before line end
6.94 - )
6.95 + "Write any modifications and close the table."
6.96 +
6.97 + if self.mutable:
6.98 + f = codecs.open(self.filename, "wb", encoding="utf-8")
6.99 + try:
6.100 + sep = self.tab_separated and "\t" or " "
6.101 +
6.102 + # Include any headers in the output.
6.103 +
6.104 + if self.headers:
6.105 + self.table.insert(0, self.header_values)
6.106 +
6.107 + for item in self.table:
6.108 + if self.out_converter:
6.109 + item = self.out_converter(item)
6.110 +
6.111 + # Insert defaults for empty columns.
6.112 +
6.113 + if self.out_defaults:
6.114 + item = set_defaults(list(item), self.out_defaults)
6.115 +
6.116 + # Separate the columns and write to the file.
6.117 +
6.118 + print >>f, sep.join(item)
6.119 +
6.120 + # Remove the headers from the items in case the table is
6.121 + # accessed again.
6.122 +
6.123 + if self.headers:
6.124 + del self.table[0]
6.125 +
6.126 + finally:
6.127 + f.close()
6.128 + fix_permissions(self.filename)
6.129 +
6.130 + # General collection methods.
6.131
6.132 -line_pattern = re.compile(line_pattern_str)
6.133 + def __nonzero__(self):
6.134 + return bool(self.table)
6.135 +
6.136 + # List emulation methods.
6.137 +
6.138 + def __iadd__(self, other):
6.139 + for value in other:
6.140 + self.append(value)
6.141 + return self
6.142 +
6.143 + def __iter__(self):
6.144 + return iter(self.table)
6.145 +
6.146 + def __len__(self):
6.147 + return len(self.table)
6.148
6.149 -def parse_line(text):
6.150 + def __delitem__(self, i):
6.151 + del self.table[i]
6.152 +
6.153 + def __delslice__(self, start, end):
6.154 + del self.table[start:end]
6.155 +
6.156 + def __getitem__(self, i):
6.157 + return self.table[i]
6.158 +
6.159 + def __getslice__(self, start, end):
6.160 + return self.table[start:end]
6.161 +
6.162 + def __setitem__(self, i, value):
6.163 + self.table[i] = value
6.164 +
6.165 + def __setslice__(self, start, end, values):
6.166 + self.table[start:end] = values
6.167 +
6.168 + def append(self, value):
6.169 + self.table.append(value)
6.170
6.171 - """
6.172 - Parse the given 'text', returning a list of words separated by whitespace in
6.173 - the input, where whitespace may occur inside words if quoted using single or
6.174 - double quotes.
6.175 + def insert(self, i, value):
6.176 + self.table.insert(i, value)
6.177 +
6.178 + def remove(self, value):
6.179 + self.table.remove(value)
6.180 +
6.181 + # Dictionary emulation methods (even though this is not a mapping).
6.182 +
6.183 + def clear(self):
6.184 + del self.table[:]
6.185 +
6.186 + # Additional modification methods.
6.187 +
6.188 + def replaceall(self, values):
6.189 + self.table[:] = values
6.190 +
6.191 +class FileTableDict(FileTable):
6.192 +
6.193 + "A file-based table acting as a dictionary."
6.194 +
6.195 + def __init__(self, filename, mutable=True,
6.196 + in_defaults=None, out_defaults=None,
6.197 + in_converter=None, out_converter=None,
6.198 + tab_separated=True, headers=False):
6.199 +
6.200 + FileTable.__init__(self, filename, mutable, in_defaults, out_defaults,
6.201 + in_converter, out_converter, tab_separated, headers)
6.202 + self.mapping = dict(self.table)
6.203 +
6.204 + def close(self):
6.205 + self.table = self.mapping.items()
6.206 + FileTable.close(self)
6.207 +
6.208 + # General collection methods.
6.209
6.210 - Hello world -> ['Hello', 'world']
6.211 - Hello ' world' -> ['Hello', ' world']
6.212 - Hello' 'world -> ["'Hello'", "'world']
6.213 - """
6.214 + def __nonzero__(self):
6.215 + return bool(self.mapping)
6.216 +
6.217 + # List emulation methods.
6.218 +
6.219 + def __iter__(self):
6.220 + return iter(self.mapping)
6.221 +
6.222 + def __len__(self):
6.223 + return len(self.mapping)
6.224 +
6.225 + def append(self, value):
6.226 + key, value = value
6.227 + self.mapping[key] = value
6.228 +
6.229 + def insert(self, i, value):
6.230 + self.append(value)
6.231 +
6.232 + def remove(self, value):
6.233 + key, value = value
6.234 + del self.mapping[key]
6.235 +
6.236 + # Unimplemented methods.
6.237 +
6.238 + def __delslice__(self, start, end):
6.239 + raise NotImplementedError, "__delslice__"
6.240 +
6.241 + def __getslice__(self, start, end):
6.242 + raise NotImplementedError, "__getslice__"
6.243 +
6.244 + def __setslice__(self, start, end, values):
6.245 + raise NotImplementedError, "__setslice__"
6.246 +
6.247 + # Dictionary emulation methods.
6.248 +
6.249 + def clear(self):
6.250 + self.mapping.clear()
6.251
6.252 - parts = []
6.253 + def get(self, i, default=None):
6.254 + return self.mapping.get(i, default)
6.255 +
6.256 + def keys(self):
6.257 + return self.mapping.keys()
6.258 +
6.259 + def items(self):
6.260 + return self.mapping.items()
6.261 +
6.262 + def update(self, other):
6.263 + self.mapping.update(other)
6.264 +
6.265 + def values(self):
6.266 + return self.mapping.values()
6.267 +
6.268 + def __delitem__(self, i):
6.269 + del self.mapping[i]
6.270
6.271 - # Match the components of each part.
6.272 + def __getitem__(self, i):
6.273 + return self.mapping[i]
6.274 +
6.275 + def __setitem__(self, i, value):
6.276 + if self.mutable:
6.277 + self.mapping[i] = value
6.278 +
6.279 + # Additional modification methods.
6.280
6.281 - for match in line_pattern.finditer(text):
6.282 + def replaceall(self, values):
6.283 + self.mapping = {}
6.284 + self.mapping.update(dict(values))
6.285 +
6.286 + def updateall(self, mapping):
6.287 + self.mapping = {}
6.288 + self.mapping.update(mapping)
6.289 +
6.290 +def first(t):
6.291 + return t[0]
6.292
6.293 - # Combine the components by traversing the matching groups.
6.294 +def tuplevalue(v):
6.295 + return (v,)
6.296 +
6.297 +class FileTableSingle(FileTable):
6.298 +
6.299 + "A file-based table providing single value items."
6.300 +
6.301 + def __iter__(self):
6.302 + return iter(self[:])
6.303 +
6.304 + def __getitem__(self, i):
6.305 + return self.table[i][0]
6.306 +
6.307 + def __getslice__(self, start, end):
6.308 + return map(first, self.table[start:end])
6.309 +
6.310 + def __setitem__(self, i, value):
6.311 + self.table[i] = [(value,)]
6.312
6.313 - parts.append(reduce(lambda a, b: (a or "") + (b or ""), match.groups()))
6.314 + def __setslice__(self, start, end, values):
6.315 + self.table[start:end] = map(tuplevalue, values)
6.316 +
6.317 + def append(self, value):
6.318 + self.table.append((value,))
6.319 +
6.320 + def insert(self, i, value):
6.321 + self.table.insert(i, (value,))
6.322
6.323 - return parts
6.324 + def remove(self, value):
6.325 + self.table.remove((value,))
6.326 +
6.327 + # Additional modification methods.
6.328 +
6.329 + def replaceall(self, values):
6.330 + self.table[:] = map(tuplevalue, values)
6.331 +
6.332 +
6.333
6.334 # Parsing of tabular files.
6.335
6.336 @@ -129,4 +387,45 @@
6.337
6.338 return l
6.339
6.340 +
6.341 +
6.342 +# Parsing of lines to obtain functions and arguments.
6.343 +
6.344 +line_pattern_str = (
6.345 + r"(?:"
6.346 + r"(?:'(.*?)')" # single-quoted text
6.347 + r"|"
6.348 + r'(?:"(.*?)")' # double-quoted text
6.349 + r"|"
6.350 + r"([^\s]+)" # non-whitespace characters
6.351 + r")+"
6.352 + r"(?:\s+|$)" # optional trailing whitespace before line end
6.353 + )
6.354 +
6.355 +line_pattern = re.compile(line_pattern_str)
6.356 +
6.357 +def parse_line(text):
6.358 +
6.359 + """
6.360 + Parse the given 'text', returning a list of words separated by whitespace in
6.361 + the input, where whitespace may occur inside words if quoted using single or
6.362 + double quotes.
6.363 +
6.364 + Hello world -> ['Hello', 'world']
6.365 + Hello ' world' -> ['Hello', ' world']
6.366 + Hello' 'world -> ["'Hello'", "'world']
6.367 + """
6.368 +
6.369 + parts = []
6.370 +
6.371 + # Match the components of each part.
6.372 +
6.373 + for match in line_pattern.finditer(text):
6.374 +
6.375 + # Combine the components by traversing the matching groups.
6.376 +
6.377 + parts.append(reduce(lambda a, b: (a or "") + (b or ""), match.groups()))
6.378 +
6.379 + return parts
6.380 +
6.381 # vim: tabstop=4 expandtab shiftwidth=4
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/tests/internal/file_store.py Thu Jun 01 23:26:38 2017 +0200
7.3 @@ -0,0 +1,160 @@
7.4 +#!/usr/bin/env python
7.5 +
7.6 +"""
7.7 +Test file-based storage.
7.8 +
7.9 +Copyright (C) 2017 Paul Boddie <paul@boddie.org.uk>
7.10 +
7.11 +This program is free software; you can redistribute it and/or modify it under
7.12 +the terms of the GNU General Public License as published by the Free Software
7.13 +Foundation; either version 3 of the License, or (at your option) any later
7.14 +version.
7.15 +
7.16 +This program is distributed in the hope that it will be useful, but WITHOUT
7.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
7.19 +details.
7.20 +
7.21 +You should have received a copy of the GNU General Public License along with
7.22 +this program. If not, see <http://www.gnu.org/licenses/>.
7.23 +"""
7.24 +
7.25 +from imiptools.dates import get_datetime
7.26 +from imiptools.freebusy.common import *
7.27 +from imiptools.text import FileTable, FileTableDict, FileTableSingle
7.28 +from imiptools.stores.file import Store
7.29 +
7.30 +# Test free/busy collection.
7.31 +
7.32 +fb = FreeBusyCollection(FileTable("testfb.tmp",
7.33 + in_converter=period_from_tuple(FreeBusyPeriod),
7.34 + out_converter=period_to_tuple))
7.35 +fb.clear()
7.36 +
7.37 +start = get_datetime("20170530T210700Z")
7.38 +end = get_datetime("20170530T210900Z")
7.39 +p1 = FreeBusyPeriod(start, end)
7.40 +fb.insert_period(p1)
7.41 +
7.42 +start = get_datetime("20170530T205600Z")
7.43 +end = get_datetime("20170530T205800Z")
7.44 +p2 = FreeBusyPeriod(start, end)
7.45 +fb.insert_period(p2)
7.46 +
7.47 +print list(fb)
7.48 +fb.close()
7.49 +
7.50 +fb = FreeBusyCollection(FileTable("testfb.tmp",
7.51 + in_converter=period_from_tuple(FreeBusyPeriod),
7.52 + out_converter=period_to_tuple))
7.53 +print list(fb)
7.54 +
7.55 +print "----"
7.56 +
7.57 +
7.58 +
7.59 +# Test single value table.
7.60 +
7.61 +values = FileTableSingle("testsv.tmp")
7.62 +values.clear()
7.63 +
7.64 +values.append("Hello")
7.65 +values.insert(0, "world")
7.66 +
7.67 +print list(values)
7.68 +values.close()
7.69 +
7.70 +values = FileTableSingle("testsv.tmp")
7.71 +print list(values)
7.72 +
7.73 +print "----"
7.74 +
7.75 +
7.76 +
7.77 +# Test dictionary table.
7.78 +
7.79 +limits = FileTableDict("testdt.tmp")
7.80 +limits.clear()
7.81 +
7.82 +limits["mailto:paul.boddie@example.com"] = "PT1H"
7.83 +
7.84 +print list(limits)
7.85 +limits.close()
7.86 +
7.87 +limits = FileTableDict("testdt.tmp")
7.88 +print list(limits)
7.89 +
7.90 +print "----"
7.91 +
7.92 +
7.93 +
7.94 +# Test store.
7.95 +
7.96 +s = Store("store.tmp")
7.97 +
7.98 +fb = s.get_freebusy("mailto:paul.boddie@example.com")
7.99 +try:
7.100 + fb.insert_period(p1)
7.101 +except TypeError:
7.102 + print "Free/busy collection not mutable, as expected."
7.103 +
7.104 +fb = s.get_freebusy("mailto:paul.boddie@example.com", mutable=True)
7.105 +fb.insert_period(p1)
7.106 +fb.insert_period(p2)
7.107 +s.set_freebusy("mailto:paul.boddie@example.com", fb)
7.108 +s.set_freebusy("mailto:harvey.horse@example.com", fb)
7.109 +
7.110 +print list(fb)
7.111 +
7.112 +s = Store("store.tmp")
7.113 +
7.114 +fb = s.get_freebusy("mailto:paul.boddie@example.com")
7.115 +print list(fb)
7.116 +fb = s.get_freebusy("mailto:harvey.horse@example.com")
7.117 +print list(fb)
7.118 +
7.119 +
7.120 +
7.121 +# Test store.
7.122 +
7.123 +s = Store("store.tmp")
7.124 +
7.125 +req = s.get_requests("mailto:paul.boddie@example.com")
7.126 +req.clear()
7.127 +
7.128 +req.append(("uid1@example.com", None, None))
7.129 +req.append(("uid2@example.com", None, None))
7.130 +req.append(("uid2@example.com", "20170531T140100Z", None))
7.131 +req.append(("uid2@example.com", "20170531T140900Z", "COUNTER"))
7.132 +s.set_requests("mailto:paul.boddie@example.com", req)
7.133 +s.set_requests("mailto:harvey.horse@example.com", req)
7.134 +
7.135 +print list(req)
7.136 +
7.137 +s = Store("store.tmp")
7.138 +
7.139 +req = s.get_requests("mailto:paul.boddie@example.com")
7.140 +print list(req)
7.141 +req = s.get_requests("mailto:harvey.horse@example.com")
7.142 +print list(req)
7.143 +
7.144 +
7.145 +
7.146 +# Test store.
7.147 +
7.148 +s = Store("store.tmp")
7.149 +
7.150 +fb = s.get_freebusy_for_other("mailto:paul.boddie@example.com", "mailto:harvey.horse@example.com", mutable=True)
7.151 +fb.clear()
7.152 +fb.insert_period(p1)
7.153 +fb.insert_period(p2)
7.154 +s.set_freebusy_for_other("mailto:paul.boddie@example.com", fb, "mailto:harvey.horse@example.com")
7.155 +
7.156 +print list(fb)
7.157 +
7.158 +s = Store("store.tmp")
7.159 +
7.160 +fb = s.get_freebusy_for_other("mailto:paul.boddie@example.com", "mailto:harvey.horse@example.com")
7.161 +print list(fb)
7.162 +
7.163 +# vim: tabstop=4 expandtab shiftwidth=4