1.1 --- a/imip_store.py Mon Sep 14 19:58:51 2015 +0200
1.2 +++ b/imip_store.py Tue Sep 15 00:28:09 2015 +0200
1.3 @@ -19,6 +19,7 @@
1.4 this program. If not, see <http://www.gnu.org/licenses/>.
1.5 """
1.6
1.7 +from bisect import bisect_left
1.8 from datetime import datetime
1.9 from imiptools.config import STORE_DIR, PUBLISH_DIR
1.10 from imiptools.data import make_calendar, parse_object, to_stream
1.11 @@ -107,13 +108,19 @@
1.12 f = codecs.open(filename, "wb", encoding="utf-8")
1.13 try:
1.14 for item in items:
1.15 - if empty_defaults:
1.16 - item = self._set_defaults(list(item), empty_defaults)
1.17 - f.write("\t".join(item) + "\n")
1.18 + self._set_table_item(f, item, empty_defaults)
1.19 finally:
1.20 f.close()
1.21 fix_permissions(filename)
1.22
1.23 + def _set_table_item(self, f, item, empty_defaults=None):
1.24 +
1.25 + "Set in table 'f' the given 'item', using any 'empty_defaults'."
1.26 +
1.27 + if empty_defaults:
1.28 + item = self._set_defaults(list(item), empty_defaults)
1.29 + f.write("\t".join(item) + "\n")
1.30 +
1.31 def _set_table_atomic(self, user, filename, items, empty_defaults=None):
1.32
1.33 """
1.34 @@ -623,7 +630,7 @@
1.35
1.36 return self.set_freebusy(user, freebusy, "freebusy-offers")
1.37
1.38 - # Object status details access.
1.39 + # Requests and counter-proposals.
1.40
1.41 def _get_requests(self, user, queue):
1.42
1.43 @@ -633,7 +640,7 @@
1.44 if not filename or not exists(filename):
1.45 return None
1.46
1.47 - return self._get_table_atomic(user, filename, [(1, None)])
1.48 + return self._get_table_atomic(user, filename, [(1, None), (2, None)])
1.49
1.50 def get_requests(self, user):
1.51
1.52 @@ -652,18 +659,7 @@
1.53 if not filename:
1.54 return False
1.55
1.56 - self.acquire_lock(user)
1.57 - try:
1.58 - f = open(filename, "w")
1.59 - try:
1.60 - for request in requests:
1.61 - print >>f, "\t".join([value or "" for value in request])
1.62 - finally:
1.63 - f.close()
1.64 - fix_permissions(filename)
1.65 - finally:
1.66 - self.release_lock(user)
1.67 -
1.68 + self._set_table_atomic(user, filename, requests, [(1, ""), (2, "")])
1.69 return True
1.70
1.71 def set_requests(self, user, requests):
1.72 @@ -672,11 +668,10 @@
1.73
1.74 return self._set_requests(user, requests, "requests")
1.75
1.76 - def _set_request(self, user, uid, recurrenceid, queue):
1.77 + def _set_request(self, user, request, queue):
1.78
1.79 """
1.80 - For the given 'user', set the queued 'uid' and 'recurrenceid' in the
1.81 - given 'queue'.
1.82 + For the given 'user', set the given 'request' in the given 'queue'.
1.83 """
1.84
1.85 filename = self.get_object_in_store(user, queue)
1.86 @@ -685,9 +680,9 @@
1.87
1.88 self.acquire_lock(user)
1.89 try:
1.90 - f = open(filename, "a")
1.91 + f = codecs.open(filename, "ab", encoding="utf-8")
1.92 try:
1.93 - print >>f, "\t".join([uid, recurrenceid or ""])
1.94 + self._set_table_item(f, request, [(1, ""), (2, "")])
1.95 finally:
1.96 f.close()
1.97 fix_permissions(filename)
1.98 @@ -696,44 +691,92 @@
1.99
1.100 return True
1.101
1.102 - def set_request(self, user, uid, recurrenceid=None):
1.103 -
1.104 - "For the given 'user', set the queued 'uid' and 'recurrenceid'."
1.105 + def set_request(self, user, uid, recurrenceid=None, type=None):
1.106
1.107 - return self._set_request(user, uid, recurrenceid, "requests")
1.108 + """
1.109 + For the given 'user', set the queued 'uid' and 'recurrenceid',
1.110 + indicating a request, along with any given 'type'.
1.111 + """
1.112
1.113 - def queue_request(self, user, uid, recurrenceid=None):
1.114 + return self._set_request(user, (uid, recurrenceid, type), "requests")
1.115 +
1.116 + def queue_request(self, user, uid, recurrenceid=None, type=None):
1.117
1.118 """
1.119 Queue a request for 'user' having the given 'uid'. If the optional
1.120 - 'recurrenceid' is specified, the request refers to a specific instance
1.121 - or occurrence of an event.
1.122 + 'recurrenceid' is specified, the entry refers to a specific instance
1.123 + or occurrence of an event. The 'type' parameter can be used to indicate
1.124 + a specific type of request.
1.125 + """
1.126 +
1.127 + requests = self.get_requests(user) or []
1.128 +
1.129 + if not self.have_request(requests, uid, recurrenceid):
1.130 + return self.set_request(user, uid, recurrenceid, type)
1.131 +
1.132 + return False
1.133 +
1.134 + def dequeue_request(self, user, uid, recurrenceid=None, type=None):
1.135 +
1.136 + """
1.137 + Dequeue all requests for 'user' having the given 'uid'. If the optional
1.138 + 'recurrenceid' is specified, all requests for that specific instance or
1.139 + occurrence of an event are dequeued.
1.140 """
1.141
1.142 requests = self.get_requests(user) or []
1.143
1.144 - if (uid, recurrenceid) not in requests:
1.145 - return self.set_request(user, uid, recurrenceid)
1.146 + if not self.have_request(requests, uid, recurrenceid):
1.147 + return False
1.148 +
1.149 + i = bisect_left(requests, (uid, recurrenceid))
1.150 + while i < len(requests) and requests[i][:2] == (uid, recurrenceid):
1.151 +
1.152 + # Remove associated objects.
1.153 +
1.154 + type = requests[i][2]
1.155 + if type == "COUNTER":
1.156 + self.remove_counter(user, uid, recurrenceid)
1.157
1.158 - return False
1.159 + # Remove the request.
1.160 +
1.161 + del requests[i]
1.162 + i += 1
1.163
1.164 - def dequeue_request(self, user, uid, recurrenceid=None):
1.165 + self.set_requests(user, requests)
1.166 + return True
1.167 +
1.168 + def have_request(self, requests, uid, recurrenceid=None):
1.169 + i = bisect_left(requests, (uid, recurrenceid))
1.170 + return i < len(requests) and requests[i][:2] == (uid, recurrenceid)
1.171 +
1.172 + def set_counter(self, user, node, uid, recurrenceid=None):
1.173
1.174 """
1.175 - Dequeue a request for 'user' having the given 'uid'. If the optional
1.176 - 'recurrenceid' is specified, the request refers to a specific instance
1.177 - or occurrence of an event.
1.178 + For the given 'user', store the given 'node' for the given 'uid' and
1.179 + 'recurrenceid' as a counter-proposal.
1.180 """
1.181
1.182 - requests = self.get_requests(user) or []
1.183 + filename = self.get_event_filename(user, uid, recurrenceid, "counters")
1.184 + if not filename:
1.185 + return False
1.186 +
1.187 + return self._set_object(user, filename, node)
1.188 +
1.189 + def remove_counter(self, user, uid, recurrenceid=None):
1.190
1.191 - try:
1.192 - requests.remove((uid, recurrenceid))
1.193 - self.set_requests(user, requests)
1.194 - except ValueError:
1.195 + """
1.196 + For the given 'user', remove any counter-proposal associated with the
1.197 + given 'uid' and 'recurrenceid'.
1.198 + """
1.199 +
1.200 + filename = self.get_event_filename(user, uid, recurrenceid, "counters")
1.201 + if not filename:
1.202 return False
1.203 - else:
1.204 - return True
1.205 +
1.206 + return self._remove_object(filename)
1.207 +
1.208 + # Event cancellation.
1.209
1.210 def cancel_event(self, user, uid, recurrenceid=None):
1.211