1.1 --- a/imip_store.py Thu Feb 12 22:35:16 2015 +0100
1.2 +++ b/imip_store.py Thu Feb 12 22:41:50 2015 +0100
1.3 @@ -24,7 +24,7 @@
1.4 from imiptools.data import make_calendar, parse_object, to_stream
1.5 from imiptools.filesys import fix_permissions, FileBase
1.6 from os.path import exists, isfile, join
1.7 -from os import listdir, remove
1.8 +from os import listdir, remove, rmdir
1.9 from time import sleep
1.10
1.11 class FileStore(FileBase):
1.12 @@ -40,6 +40,66 @@
1.13 def release_lock(self, user):
1.14 FileBase.release_lock(self, user)
1.15
1.16 + def _set_defaults(self, t, empty_defaults):
1.17 + for i, default in empty_defaults:
1.18 + if i >= len(t):
1.19 + t += [None] * (i - len(t) + 1)
1.20 + if not t[i]:
1.21 + t[i] = default
1.22 + return t
1.23 +
1.24 + def _get_table(self, user, filename, empty_defaults=None):
1.25 +
1.26 + """
1.27 + From the file for the given 'user' having the given 'filename', return
1.28 + a list of tuples representing the file's contents.
1.29 +
1.30 + The 'empty_defaults' is a list of (index, value) tuples indicating the
1.31 + default value where a column either does not exist or provides an empty
1.32 + value.
1.33 + """
1.34 +
1.35 + self.acquire_lock(user)
1.36 + try:
1.37 + f = open(filename, "rb")
1.38 + try:
1.39 + l = []
1.40 + for line in f.readlines():
1.41 + t = line.strip().split("\t")
1.42 + if empty_defaults:
1.43 + t = self._set_defaults(t, empty_defaults)
1.44 + l.append(tuple(t))
1.45 + return l
1.46 + finally:
1.47 + f.close()
1.48 + finally:
1.49 + self.release_lock(user)
1.50 +
1.51 + def _set_table(self, user, filename, items, empty_defaults=None):
1.52 +
1.53 + """
1.54 + For the given 'user', write to the file having the given 'filename' the
1.55 + 'items'.
1.56 +
1.57 + The 'empty_defaults' is a list of (index, value) tuples indicating the
1.58 + default value where a column either does not exist or provides an empty
1.59 + value.
1.60 + """
1.61 +
1.62 + self.acquire_lock(user)
1.63 + try:
1.64 + f = open(filename, "wb")
1.65 + try:
1.66 + for item in items:
1.67 + if empty_defaults:
1.68 + item = self._set_defaults(list(item), empty_defaults)
1.69 + f.write("\t".join(item) + "\n")
1.70 + finally:
1.71 + f.close()
1.72 + fix_permissions(filename)
1.73 + finally:
1.74 + self.release_lock(user)
1.75 +
1.76 def _get_object(self, user, filename):
1.77
1.78 """
1.79 @@ -88,6 +148,17 @@
1.80
1.81 return True
1.82
1.83 + def _remove_collection(self, filename):
1.84 +
1.85 + "Remove the collection with the given 'filename'."
1.86 +
1.87 + try:
1.88 + rmdir(filename)
1.89 + except OSError:
1.90 + return False
1.91 +
1.92 + return True
1.93 +
1.94 def get_events(self, user):
1.95
1.96 "Return a list of event identifiers."
1.97 @@ -98,7 +169,20 @@
1.98
1.99 return [name for name in listdir(filename) if isfile(join(filename, name))]
1.100
1.101 - def get_event(self, user, uid):
1.102 + def get_event(self, user, uid, recurrenceid=None):
1.103 +
1.104 + """
1.105 + Get the event for the given 'user' with the given 'uid'. If
1.106 + the optional 'recurrenceid' is specified, a specific instance or
1.107 + occurrence of an event is returned.
1.108 + """
1.109 +
1.110 + if recurrenceid:
1.111 + return self.get_recurrence(user, uid, recurrenceid)
1.112 + else:
1.113 + return self.get_complete_event(user, uid)
1.114 +
1.115 + def get_complete_event(self, user, uid):
1.116
1.117 "Get the event for the given 'user' with the given 'uid'."
1.118
1.119 @@ -108,7 +192,20 @@
1.120
1.121 return self._get_object(user, filename)
1.122
1.123 - def set_event(self, user, uid, node):
1.124 + def set_event(self, user, uid, recurrenceid, node):
1.125 +
1.126 + """
1.127 + Set an event for 'user' having the given 'uid' and 'recurrenceid' (which
1.128 + if the latter is specified, a specific instance or occurrence of an
1.129 + event is referenced), using the given 'node' description.
1.130 + """
1.131 +
1.132 + if recurrenceid:
1.133 + return self.set_recurrence(user, uid, recurrenceid, node)
1.134 + else:
1.135 + return self.set_complete_event(user, uid, node)
1.136 +
1.137 + def set_complete_event(self, user, uid, node):
1.138
1.139 "Set an event for 'user' having the given 'uid' and 'node'."
1.140
1.141 @@ -120,12 +217,31 @@
1.142
1.143 def remove_event(self, user, uid):
1.144
1.145 + """
1.146 + Remove an event for 'user' having the given 'uid'. If the optional
1.147 + 'recurrenceid' is specified, a specific instance or occurrence of an
1.148 + event is removed.
1.149 + """
1.150 +
1.151 + if recurrenceid:
1.152 + return self.remove_recurrence(user, uid, recurrenceid)
1.153 + else:
1.154 + for recurrenceid in self.get_recurrences(user, uid) or []:
1.155 + self.remove_recurrence(user, uid, recurrenceid)
1.156 + return self.remove_complete_event(user, uid)
1.157 +
1.158 + def remove_complete_event(self, user, uid):
1.159 +
1.160 "Remove an event for 'user' having the given 'uid'."
1.161
1.162 filename = self.get_object_in_store(user, "objects", uid)
1.163 if not filename:
1.164 return False
1.165
1.166 + recurrences = self.get_object_in_store(user, "recurrences", uid)
1.167 + if recurrences:
1.168 + self._remove_collection(recurrences)
1.169 +
1.170 return self._remove_object(filename)
1.171
1.172 def get_recurrences(self, user, uid):
1.173 @@ -168,7 +284,7 @@
1.174
1.175 "Remove an event for 'user' having the given 'uid'."
1.176
1.177 - filename = self.get_object_in_store(user, "objects", uid)
1.178 + filename = self.get_object_in_store(user, "recurrences", uid)
1.179 if not filename:
1.180 return False
1.181
1.182 @@ -182,7 +298,7 @@
1.183 if not filename or not exists(filename):
1.184 return []
1.185 else:
1.186 - return self._get_freebusy(user, filename)
1.187 + return self._get_table(user, filename, [(4, None)])
1.188
1.189 def get_freebusy_for_other(self, user, other):
1.190
1.191 @@ -192,24 +308,7 @@
1.192 if not filename or not exists(filename):
1.193 return []
1.194 else:
1.195 - return self._get_freebusy(user, filename)
1.196 -
1.197 - def _get_freebusy(self, user, filename):
1.198 -
1.199 - "For the given 'user', get the free/busy details from 'filename'."
1.200 -
1.201 - self.acquire_lock(user)
1.202 - try:
1.203 - f = open(filename)
1.204 - try:
1.205 - l = []
1.206 - for line in f.readlines():
1.207 - l.append(tuple(line.strip().split("\t")))
1.208 - return l
1.209 - finally:
1.210 - f.close()
1.211 - finally:
1.212 - self.release_lock(user)
1.213 + return self._get_table(user, filename, [(4, None)])
1.214
1.215 def set_freebusy(self, user, freebusy):
1.216
1.217 @@ -219,7 +318,7 @@
1.218 if not filename:
1.219 return False
1.220
1.221 - self._set_freebusy(user, filename, freebusy)
1.222 + self._set_table(user, filename, freebusy, [(3, "OPAQUE"), (4, "")])
1.223 return True
1.224
1.225 def set_freebusy_for_other(self, user, freebusy, other):
1.226 @@ -230,28 +329,9 @@
1.227 if not filename:
1.228 return False
1.229
1.230 - self._set_freebusy(user, filename, freebusy)
1.231 + self._set_table(user, filename, freebusy, [(2, ""), (3, "OPAQUE"), (4, "")])
1.232 return True
1.233
1.234 - def _set_freebusy(self, user, filename, freebusy):
1.235 -
1.236 - """
1.237 - For the given 'user', write to the file having the given 'filename' the
1.238 - 'freebusy' details.
1.239 - """
1.240 -
1.241 - self.acquire_lock(user)
1.242 - try:
1.243 - f = open(filename, "w")
1.244 - try:
1.245 - for item in freebusy:
1.246 - f.write("\t".join([(value or "OPAQUE") for value in item]) + "\n")
1.247 - finally:
1.248 - f.close()
1.249 - fix_permissions(filename)
1.250 - finally:
1.251 - self.release_lock(user)
1.252 -
1.253 def _get_requests(self, user, queue):
1.254
1.255 "Get requests for the given 'user' from the given 'queue'."
1.256 @@ -260,15 +340,7 @@
1.257 if not filename or not exists(filename):
1.258 return None
1.259
1.260 - self.acquire_lock(user)
1.261 - try:
1.262 - f = open(filename)
1.263 - try:
1.264 - return [line.strip() for line in f.readlines()]
1.265 - finally:
1.266 - f.close()
1.267 - finally:
1.268 - self.release_lock(user)
1.269 + return self._get_table(user, filename, [(1, None)])
1.270
1.271 def get_requests(self, user):
1.272
1.273 @@ -298,7 +370,7 @@
1.274 f = open(filename, "w")
1.275 try:
1.276 for request in requests:
1.277 - print >>f, request
1.278 + print >>f, "\t".join([value or "" for value in request])
1.279 finally:
1.280 f.close()
1.281 fix_permissions(filename)
1.282 @@ -319,9 +391,12 @@
1.283
1.284 return self._set_requests(user, cancellations, "cancellations")
1.285
1.286 - def _set_request(self, user, request, queue):
1.287 + def _set_request(self, user, uid, recurrenceid, queue):
1.288
1.289 - "For the given 'user', set the queued 'request' in the given 'queue'."
1.290 + """
1.291 + For the given 'user', set the queued 'uid' and 'recurrenceid' in the
1.292 + given 'queue'.
1.293 + """
1.294
1.295 filename = self.get_object_in_store(user, queue)
1.296 if not filename:
1.297 @@ -331,7 +406,7 @@
1.298 try:
1.299 f = open(filename, "a")
1.300 try:
1.301 - print >>f, request
1.302 + print >>f, "\t".join([uid, recurrenceid or ""])
1.303 finally:
1.304 f.close()
1.305 fix_permissions(filename)
1.306 @@ -340,51 +415,63 @@
1.307
1.308 return True
1.309
1.310 - def set_request(self, user, request):
1.311 + def set_request(self, user, uid, recurrenceid=None):
1.312
1.313 - "For the given 'user', set the queued 'request'."
1.314 + "For the given 'user', set the queued 'uid' and 'recurrenceid'."
1.315
1.316 - return self._set_request(user, request, "requests")
1.317 + return self._set_request(user, uid, recurrenceid, "requests")
1.318
1.319 - def set_cancellation(self, user, cancellation):
1.320 + def set_cancellation(self, user, uid, recurrenceid=None):
1.321 +
1.322 + "For the given 'user', set the queued 'uid' and 'recurrenceid'."
1.323
1.324 - "For the given 'user', set the queued 'cancellation'."
1.325 + return self._set_request(user, uid, recurrenceid, "cancellations")
1.326
1.327 - return self._set_request(user, cancellation, "cancellations")
1.328 + def queue_request(self, user, uid, recurrenceid=None):
1.329
1.330 - def queue_request(self, user, uid):
1.331 -
1.332 - "Queue a request for 'user' having the given 'uid'."
1.333 + """
1.334 + Queue a request for 'user' having the given 'uid'. If the optional
1.335 + 'recurrenceid' is specified, the request refers to a specific instance
1.336 + or occurrence of an event.
1.337 + """
1.338
1.339 requests = self.get_requests(user) or []
1.340
1.341 - if uid not in requests:
1.342 - return self.set_request(user, uid)
1.343 + if (uid, recurrenceid) not in requests:
1.344 + return self.set_request(user, uid, recurrenceid)
1.345
1.346 return False
1.347
1.348 - def dequeue_request(self, user, uid):
1.349 + def dequeue_request(self, user, uid, recurrenceid=None):
1.350
1.351 - "Dequeue a request for 'user' having the given 'uid'."
1.352 + """
1.353 + Dequeue a request for 'user' having the given 'uid'. If the optional
1.354 + 'recurrenceid' is specified, the request refers to a specific instance
1.355 + or occurrence of an event.
1.356 + """
1.357
1.358 requests = self.get_requests(user) or []
1.359
1.360 try:
1.361 - requests.remove(uid)
1.362 + requests.remove((uid, recurrenceid))
1.363 self.set_requests(user, requests)
1.364 except ValueError:
1.365 return False
1.366 else:
1.367 return True
1.368
1.369 - def cancel_event(self, user, uid):
1.370 + def cancel_event(self, user, uid, recurrenceid=None):
1.371
1.372 - "Queue an event for cancellation for 'user' having the given 'uid'."
1.373 + """
1.374 + Queue an event for cancellation for 'user' having the given 'uid'. If
1.375 + the optional 'recurrenceid' is specified, a specific instance or
1.376 + occurrence of an event is cancelled.
1.377 + """
1.378
1.379 cancellations = self.get_cancellations(user) or []
1.380
1.381 - if uid not in cancellations:
1.382 - return self.set_cancellation(user, uid)
1.383 + if (uid, recurrenceid) not in cancellations:
1.384 + return self.set_cancellation(user, uid, recurrenceid)
1.385
1.386 return False
1.387
1.388 @@ -410,7 +497,7 @@
1.389 rwrite(("UID", {}, user))
1.390 rwrite(("DTSTAMP", {}, datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")))
1.391
1.392 - for start, end, uid, transp in freebusy:
1.393 + for start, end, uid, transp, recurrenceid in freebusy:
1.394 if not transp or transp == "OPAQUE":
1.395 rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join([start, end])))
1.396