1.1 --- a/imip_store.py Sun Sep 06 23:20:02 2015 +0200
1.2 +++ b/imip_store.py Mon Sep 07 18:54:54 2015 +0200
1.3 @@ -64,19 +64,32 @@
1.4 value.
1.5 """
1.6
1.7 + f = codecs.open(filename, "rb", encoding="utf-8")
1.8 + try:
1.9 + l = []
1.10 + for line in f.readlines():
1.11 + t = line.strip(" \r\n").split("\t")
1.12 + if empty_defaults:
1.13 + t = self._set_defaults(t, empty_defaults)
1.14 + l.append(tuple(t))
1.15 + return l
1.16 + finally:
1.17 + f.close()
1.18 +
1.19 + def _get_table_atomic(self, user, filename, empty_defaults=None):
1.20 +
1.21 + """
1.22 + From the file for the given 'user' having the given 'filename', return
1.23 + a list of tuples representing the file's contents.
1.24 +
1.25 + The 'empty_defaults' is a list of (index, value) tuples indicating the
1.26 + default value where a column either does not exist or provides an empty
1.27 + value.
1.28 + """
1.29 +
1.30 self.acquire_lock(user)
1.31 try:
1.32 - f = codecs.open(filename, "rb", encoding="utf-8")
1.33 - try:
1.34 - l = []
1.35 - for line in f.readlines():
1.36 - t = line.strip(" \r\n").split("\t")
1.37 - if empty_defaults:
1.38 - t = self._set_defaults(t, empty_defaults)
1.39 - l.append(tuple(t))
1.40 - return l
1.41 - finally:
1.42 - f.close()
1.43 + return self._get_table(user, filename, empty_defaults)
1.44 finally:
1.45 self.release_lock(user)
1.46
1.47 @@ -91,17 +104,30 @@
1.48 value.
1.49 """
1.50
1.51 + f = codecs.open(filename, "wb", encoding="utf-8")
1.52 + try:
1.53 + for item in items:
1.54 + if empty_defaults:
1.55 + item = self._set_defaults(list(item), empty_defaults)
1.56 + f.write("\t".join(item) + "\n")
1.57 + finally:
1.58 + f.close()
1.59 + fix_permissions(filename)
1.60 +
1.61 + def _set_table_atomic(self, user, filename, items, empty_defaults=None):
1.62 +
1.63 + """
1.64 + For the given 'user', write to the file having the given 'filename' the
1.65 + 'items'.
1.66 +
1.67 + The 'empty_defaults' is a list of (index, value) tuples indicating the
1.68 + default value where a column either does not exist or provides an empty
1.69 + value.
1.70 + """
1.71 +
1.72 self.acquire_lock(user)
1.73 try:
1.74 - f = codecs.open(filename, "wb", encoding="utf-8")
1.75 - try:
1.76 - for item in items:
1.77 - if empty_defaults:
1.78 - item = self._set_defaults(list(item), empty_defaults)
1.79 - f.write("\t".join(item) + "\n")
1.80 - finally:
1.81 - f.close()
1.82 - fix_permissions(filename)
1.83 + self._set_table(user, filename, items, empty_defaults)
1.84 finally:
1.85 self.release_lock(user)
1.86
1.87 @@ -420,7 +446,7 @@
1.88 # Attempt to read providers, with a declaration of the datetime
1.89 # from which such providers are considered as still being active.
1.90
1.91 - t = self._get_table(user, filename, [(1, None)])
1.92 + t = self._get_table_atomic(user, filename, [(1, None)])
1.93 try:
1.94 dt_string = t[0][0]
1.95 except IndexError:
1.96 @@ -469,7 +495,7 @@
1.97 return False
1.98
1.99 t.insert(0, (dt_string,))
1.100 - self._set_table(user, filename, t, [(1, "")])
1.101 + self._set_table_atomic(user, filename, t, [(1, "")])
1.102 return True
1.103
1.104 def set_freebusy_providers(self, user, dt, providers):
1.105 @@ -517,17 +543,28 @@
1.106
1.107 # Free/busy period access.
1.108
1.109 - def get_freebusy(self, user):
1.110 + def get_freebusy(self, user, name=None, get_table=None):
1.111
1.112 "Get free/busy details for the given 'user'."
1.113
1.114 - filename = self.get_object_in_store(user, "freebusy")
1.115 + filename = self.get_object_in_store(user, name or "freebusy")
1.116 if not filename or not exists(filename):
1.117 return []
1.118 else:
1.119 - return map(lambda t: FreeBusyPeriod(*t), self._get_table(user, filename, [(4, None)]))
1.120 + return map(lambda t: FreeBusyPeriod(*t),
1.121 + (get_table or self._get_table_atomic)(user, filename, [(4, None)]))
1.122 +
1.123 + def get_freebusy_for_update(self, user, name=None):
1.124
1.125 - def get_freebusy_for_other(self, user, other):
1.126 + """
1.127 + Get free/busy details for the given 'user', locking the table. Dependent
1.128 + code must release this lock regardless of it completing successfully.
1.129 + """
1.130 +
1.131 + self.acquire_lock(user)
1.132 + return self.get_freebusy(user, name, self._get_table)
1.133 +
1.134 + def get_freebusy_for_other(self, user, other, get_table=None):
1.135
1.136 "For the given 'user', get free/busy details for the 'other' user."
1.137
1.138 @@ -535,20 +572,39 @@
1.139 if not filename or not exists(filename):
1.140 return []
1.141 else:
1.142 - return map(lambda t: FreeBusyPeriod(*t), self._get_table(user, filename, [(4, None)]))
1.143 + return map(lambda t: FreeBusyPeriod(*t),
1.144 + (get_table or self._get_table_atomic)(user, filename, [(4, None)]))
1.145 +
1.146 + def get_freebusy_for_other_for_update(self, user, other):
1.147
1.148 - def set_freebusy(self, user, freebusy):
1.149 + """
1.150 + For the given 'user', get free/busy details for the 'other' user,
1.151 + locking the table. Dependent code must release this lock regardless of
1.152 + it completing successfully.
1.153 + """
1.154 +
1.155 + self.acquire_lock(user)
1.156 + return self.get_freebusy_for_other(user, other, self._get_table)
1.157 +
1.158 + def set_freebusy(self, user, freebusy, name=None, set_table=None):
1.159
1.160 "For the given 'user', set 'freebusy' details."
1.161
1.162 - filename = self.get_object_in_store(user, "freebusy")
1.163 + filename = self.get_object_in_store(user, name or "freebusy")
1.164 if not filename:
1.165 return False
1.166
1.167 - self._set_table(user, filename, map(lambda fb: fb.as_tuple(strings_only=True), freebusy))
1.168 + (set_table or self._set_table_atomic)(user, filename,
1.169 + map(lambda fb: fb.as_tuple(strings_only=True), freebusy))
1.170 return True
1.171
1.172 - def set_freebusy_for_other(self, user, freebusy, other):
1.173 + def set_freebusy_in_update(self, user, freebusy, name=None):
1.174 +
1.175 + "For the given 'user', set 'freebusy' details during a compound update."
1.176 +
1.177 + return self.set_freebusy(user, freebusy, name, self._set_table)
1.178 +
1.179 + def set_freebusy_for_other(self, user, freebusy, other, set_table=None):
1.180
1.181 "For the given 'user', set 'freebusy' details for the 'other' user."
1.182
1.183 @@ -556,9 +612,23 @@
1.184 if not filename:
1.185 return False
1.186
1.187 - self._set_table(user, filename, map(lambda fb: fb.as_tuple(strings_only=True), freebusy))
1.188 + (set_table or self._set_table_atomic)(user, filename,
1.189 + map(lambda fb: fb.as_tuple(strings_only=True), freebusy))
1.190 return True
1.191
1.192 + def set_freebusy_for_other_in_update(self, user, freebusy, other):
1.193 +
1.194 + """
1.195 + For the given 'user', set 'freebusy' details for the 'other' user during
1.196 + a compound update.
1.197 + """
1.198 +
1.199 + return self.set_freebusy_for_other(user, freebusy, other, self._set_table)
1.200 +
1.201 + # Release methods.
1.202 +
1.203 + release_freebusy = release_lock
1.204 +
1.205 # Object status details access.
1.206
1.207 def _get_requests(self, user, queue):
1.208 @@ -569,7 +639,7 @@
1.209 if not filename or not exists(filename):
1.210 return None
1.211
1.212 - return self._get_table(user, filename, [(1, None)])
1.213 + return self._get_table_atomic(user, filename, [(1, None)])
1.214
1.215 def get_requests(self, user):
1.216
3.1 --- a/imiptools/filesys.py Sun Sep 06 23:20:02 2015 +0200
3.2 +++ b/imiptools/filesys.py Mon Sep 07 18:54:54 2015 +0200
3.3 @@ -22,7 +22,7 @@
3.4 import errno
3.5 from imiptools.config import DEFAULT_PERMISSIONS, DEFAULT_DIR_PERMISSIONS
3.6 from os.path import abspath, commonprefix, exists, join, split
3.7 -from os import chmod, makedirs, mkdir, rename, rmdir
3.8 +from os import chmod, getpid, makedirs, mkdir, rename, rmdir
3.9 from time import sleep, time
3.10
3.11 def check_dir(base, filename):
3.12 @@ -149,13 +149,34 @@
3.13 # This uses the directory creation method exploited by MoinMoin.util.lock.
3.14 # However, a simple single lock type mechanism is employed here.
3.15
3.16 - def get_lock_dir(self, *parts):
3.17 + def make_lock_dir(self, *parts):
3.18
3.19 - "Return the lock directory defined by the given 'parts'."
3.20 + "Make the lock directory defined by the given 'parts'."
3.21
3.22 parts = parts and list(parts) or []
3.23 parts.append(self.lock_name)
3.24 - return self.get_object_in_store(*parts)
3.25 + parts.append(str(getpid()))
3.26 + makedirs(self.get_object_in_store(*parts))
3.27 +
3.28 + def remove_lock_dir(self, *parts):
3.29 +
3.30 + "Remove the lock directory defined by the given 'parts'."
3.31 +
3.32 + parts = parts and list(parts) or []
3.33 + parts.append(self.lock_name)
3.34 + parts.append(str(getpid()))
3.35 + rmdir(self.get_object_in_store(*parts))
3.36 + parts.pop()
3.37 + rmdir(self.get_object_in_store(*parts))
3.38 +
3.39 + def owning_lock_dir(self, *parts):
3.40 +
3.41 + "Return whether this process owns the lock directory."
3.42 +
3.43 + parts = parts and list(parts) or []
3.44 + parts.append(self.lock_name)
3.45 + parts.append(str(getpid()))
3.46 + return exists(self.get_object_in_store(*parts))
3.47
3.48 def acquire_lock(self, timeout=None, *parts):
3.49
3.50 @@ -168,11 +189,13 @@
3.51
3.52 while not timeout or now - start < timeout:
3.53 try:
3.54 - mkdir(self.get_lock_dir(*parts))
3.55 + self.make_lock_dir(*parts)
3.56 break
3.57 except OSError, exc:
3.58 if exc.errno != errno.EEXIST:
3.59 raise
3.60 + elif self.owning_lock_dir(*parts):
3.61 + break
3.62 sleep(1)
3.63 now = time()
3.64
3.65 @@ -184,7 +207,7 @@
3.66 """
3.67
3.68 try:
3.69 - rmdir(self.get_lock_dir(*parts))
3.70 + self.remove_lock_dir(*parts)
3.71 except OSError, exc:
3.72 if exc.errno != errno.ENOENT:
3.73 raise
4.1 --- a/imiptools/handlers/common.py Sun Sep 06 23:20:02 2015 +0200
4.2 +++ b/imiptools/handlers/common.py Mon Sep 07 18:54:54 2015 +0200
4.3 @@ -77,26 +77,29 @@
4.4 organiser of an event if 'for_organiser' is set to a true value.
4.5 """
4.6
4.7 - freebusy = self.store.get_freebusy(self.user)
4.8 + freebusy = self.store.get_freebusy_for_update(self.user)
4.9 + try:
4.10 + # Obtain the attendance attributes for this user, if available.
4.11
4.12 - # Obtain the attendance attributes for this user, if available.
4.13 + self.update_freebusy_for_participant(freebusy, self.user, for_organiser)
4.14
4.15 - self.update_freebusy_for_participant(freebusy, self.user, for_organiser)
4.16 + # Remove original recurrence details replaced by additional
4.17 + # recurrences, as well as obsolete additional recurrences.
4.18
4.19 - # Remove original recurrence details replaced by additional
4.20 - # recurrences, as well as obsolete additional recurrences.
4.21 + self.remove_freebusy_for_recurrences(freebusy, self.store.get_recurrences(self.user, self.uid))
4.22 + self.store.set_freebusy_in_update(self.user, freebusy)
4.23
4.24 - self.remove_freebusy_for_recurrences(freebusy, self.store.get_recurrences(self.user, self.uid))
4.25 - self.store.set_freebusy(self.user, freebusy)
4.26 + if self.publisher and self.is_sharing():
4.27 + self.publisher.set_freebusy(self.user, freebusy)
4.28
4.29 - if self.publisher and self.is_sharing():
4.30 - self.publisher.set_freebusy(self.user, freebusy)
4.31 + # Update free/busy provider information if the event may recur
4.32 + # indefinitely.
4.33
4.34 - # Update free/busy provider information if the event may recur
4.35 - # indefinitely.
4.36 + if self.possibly_recurring_indefinitely():
4.37 + self.store.append_freebusy_provider(self.user, self.obj)
4.38
4.39 - if self.possibly_recurring_indefinitely():
4.40 - self.store.append_freebusy_provider(self.user, self.obj)
4.41 + finally:
4.42 + self.store.release_freebusy(self.user)
4.43
4.44 return True
4.45
4.46 @@ -104,18 +107,22 @@
4.47
4.48 "Remove free/busy information when handling an object."
4.49
4.50 - freebusy = self.store.get_freebusy(self.user)
4.51 - self.remove_from_freebusy(freebusy)
4.52 - self.remove_freebusy_for_recurrences(freebusy)
4.53 - self.store.set_freebusy(self.user, freebusy)
4.54 + freebusy = self.store.get_freebusy_for_update(self.user)
4.55 + try:
4.56 + self.remove_from_freebusy(freebusy)
4.57 + self.remove_freebusy_for_recurrences(freebusy)
4.58 + self.store.set_freebusy_in_update(self.user, freebusy)
4.59 +
4.60 + if self.publisher and self.is_sharing():
4.61 + self.publisher.set_freebusy(self.user, freebusy)
4.62
4.63 - if self.publisher and self.is_sharing():
4.64 - self.publisher.set_freebusy(self.user, freebusy)
4.65 + # Update free/busy provider information if the event may recur
4.66 + # indefinitely.
4.67
4.68 - # Update free/busy provider information if the event may recur
4.69 - # indefinitely.
4.70 + if self.possibly_recurring_indefinitely():
4.71 + self.store.remove_freebusy_provider(self.user, self.obj)
4.72
4.73 - if self.possibly_recurring_indefinitely():
4.74 - self.store.remove_freebusy_provider(self.user, self.obj)
4.75 + finally:
4.76 + self.store.release_freebusy(self.user)
4.77
4.78 # vim: tabstop=4 expandtab shiftwidth=4