1 #!/usr/bin/env python 2 3 from datetime import datetime 4 from imiptools.config import STORE_DIR, PUBLISH_DIR 5 from os.path import abspath, commonprefix, exists, isfile, join, split 6 from os import chmod, listdir, makedirs 7 from vCalendar import iterwrite 8 9 def check_dir(base, dir): 10 return commonprefix([base, abspath(dir)]) == base 11 12 def fix_permissions(filename): 13 try: 14 chmod(filename, 0660) 15 except OSError: 16 pass 17 18 def make_calendar(fragment, method=None): 19 20 """ 21 Return a complete calendar item wrapping the given 'fragment' and employing 22 the given 'method', if indicated. 23 """ 24 25 return ("VCALENDAR", {}, 26 (method and [("METHOD", {}, method)] or []) + 27 [("VERSION", {}, "2.0")] + 28 fragment 29 ) 30 31 def to_stream(out, fragment, encoding="utf-8"): 32 iterwrite(out, encoding=encoding).append(fragment) 33 34 class FileBase: 35 36 "Basic filesystem operations." 37 38 def __init__(self, store_dir=STORE_DIR): 39 self.store_dir = store_dir 40 if not exists(self.store_dir): 41 makedirs(self.store_dir) 42 43 def get_file_object(self, base, *parts): 44 pathname = join(base, *parts) 45 return check_dir(base, pathname) and pathname or None 46 47 def get_object_in_store(self, *parts): 48 49 """ 50 Return the name of any valid object stored within a hierarchy specified 51 by the given 'parts'. 52 """ 53 54 parent = expected = self.store_dir 55 56 for part in parts: 57 filename = self.get_file_object(expected, part) 58 if not filename: 59 return False 60 parent = expected 61 expected = filename 62 63 if not exists(parent): 64 makedirs(parent) 65 66 return filename 67 68 class FileStore(FileBase): 69 70 "A file store of tabular free/busy data and objects." 71 72 def get_events(self, user): 73 74 "Return a list of event identifiers." 75 76 filename = self.get_object_in_store(user, "objects") 77 if not filename or not exists(filename): 78 return None 79 80 return [name for name in listdir(filename) if isfile(join(filename, name))] 81 82 def get_event(self, user, uid): 83 84 "Get the event for the given 'user' with the given 'uid'." 85 86 filename = self.get_object_in_store(user, "objects", uid) 87 if not filename or not exists(filename): 88 return None 89 90 return open(filename) or None 91 92 def set_event(self, user, uid, node): 93 94 "Set an event for 'user' having the given 'uid' and 'node'." 95 96 filename = self.get_object_in_store(user, "objects", uid) 97 if not filename: 98 return False 99 100 f = open(filename, "w") 101 try: 102 to_stream(f, node) 103 finally: 104 f.close() 105 fix_permissions(filename) 106 107 return True 108 109 def get_freebusy(self, user): 110 111 "Get free/busy details for the given 'user'." 112 113 filename = self.get_object_in_store(user, "freebusy") 114 if not filename or not exists(filename): 115 return None 116 else: 117 return self._get_freebusy(filename) 118 119 def get_freebusy_for_other(self, user, other): 120 121 "For the given 'user', get free/busy details for the 'other' user." 122 123 filename = self.get_object_in_store(user, "freebusy-other", other) 124 if not filename: 125 return None 126 else: 127 return self._get_freebusy(filename) 128 129 def _get_freebusy(self, filename): 130 f = open(filename) 131 try: 132 l = [] 133 for line in f.readlines(): 134 l.append(tuple(line.strip().split("\t"))) 135 return l 136 finally: 137 f.close() 138 139 def set_freebusy(self, user, freebusy): 140 141 "For the given 'user', set 'freebusy' details." 142 143 filename = self.get_object_in_store(user, "freebusy") 144 if not filename: 145 return False 146 147 self._set_freebusy(filename, freebusy) 148 return True 149 150 def set_freebusy_for_other(self, user, freebusy, other): 151 152 "For the given 'user', set 'freebusy' details for the 'other' user." 153 154 filename = self.get_object_in_store(user, "freebusy-other", other) 155 if not filename: 156 return False 157 158 self._set_freebusy(filename, freebusy) 159 return True 160 161 def _set_freebusy(self, filename, freebusy): 162 f = open(filename, "w") 163 try: 164 for item in freebusy: 165 f.write("\t".join([(value or "OPAQUE") for value in item]) + "\n") 166 finally: 167 f.close() 168 fix_permissions(filename) 169 170 def _get_requests(self, user, queue): 171 172 "Get requests for the given 'user' from the given 'queue'." 173 174 filename = self.get_object_in_store(user, queue) 175 if not filename or not exists(filename): 176 return None 177 178 f = open(filename) 179 try: 180 return [line.strip() for line in f.readlines()] 181 finally: 182 f.close() 183 184 def get_requests(self, user): 185 186 "Get requests for the given 'user'." 187 188 return self._get_requests(user, "requests") 189 190 def get_cancellations(self, user): 191 192 "Get cancellations for the given 'user'." 193 194 return self._get_requests(user, "cancellations") 195 196 def _set_requests(self, user, requests, queue): 197 198 """ 199 For the given 'user', set the list of queued 'requests' in the given 200 'queue'. 201 """ 202 203 filename = self.get_object_in_store(user, queue) 204 if not filename: 205 return False 206 207 f = open(filename, "w") 208 try: 209 for request in requests: 210 print >>f, request 211 finally: 212 f.close() 213 fix_permissions(filename) 214 215 return True 216 217 def set_requests(self, user, requests): 218 219 "For the given 'user', set the list of queued 'requests'." 220 221 return self._set_requests(user, requests, "requests") 222 223 def set_cancellations(self, user, cancellations): 224 225 "For the given 'user', set the list of queued 'cancellations'." 226 227 return self._set_requests(user, cancellations, "cancellations") 228 229 def _set_request(self, user, request, queue): 230 231 "For the given 'user', set the queued 'request' in the given 'queue'." 232 233 filename = self.get_object_in_store(user, queue) 234 if not filename: 235 return False 236 237 f = open(filename, "a") 238 try: 239 print >>f, request 240 finally: 241 f.close() 242 fix_permissions(filename) 243 244 return True 245 246 def set_request(self, user, request): 247 248 "For the given 'user', set the queued 'request'." 249 250 return self._set_request(user, request, "requests") 251 252 def set_cancellation(self, user, cancellation): 253 254 "For the given 'user', set the queued 'cancellation'." 255 256 return self._set_request(user, cancellation, "cancellations") 257 258 def queue_request(self, user, uid): 259 260 "Queue a request for 'user' having the given 'uid'." 261 262 requests = self.get_requests(user) or [] 263 264 if uid not in requests: 265 return self.set_request(user, uid) 266 267 return False 268 269 def dequeue_request(self, user, uid): 270 271 "Dequeue a request for 'user' having the given 'uid'." 272 273 requests = self.get_requests(user) or [] 274 275 try: 276 requests.remove(uid) 277 self.set_requests(user, requests) 278 except ValueError: 279 return False 280 else: 281 return True 282 283 def cancel_event(self, user, uid): 284 285 "Queue an event for cancellation for 'user' having the given 'uid'." 286 287 cancellations = self.get_cancellations(user) or [] 288 289 if uid not in cancellations: 290 return self.set_cancellation(user, uid) 291 292 return False 293 294 class FilePublisher(FileBase): 295 296 "A publisher of objects." 297 298 def __init__(self, store_dir=PUBLISH_DIR): 299 FileBase.__init__(self, store_dir) 300 301 def set_freebusy(self, user, freebusy): 302 303 "For the given 'user', set 'freebusy' details." 304 305 filename = self.get_object_in_store(user, "freebusy") 306 if not filename: 307 return False 308 309 record = [] 310 rwrite = record.append 311 312 rwrite(("ORGANIZER", {}, user)) 313 rwrite(("UID", {}, user)) 314 rwrite(("DTSTAMP", {}, datetime.utcnow().strftime("%Y%m%dT%H%M%SZ"))) 315 316 for start, end, uid, transp in freebusy: 317 if not transp or transp == "OPAQUE": 318 rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join([start, end]))) 319 320 f = open(filename, "w") 321 try: 322 to_stream(f, make_calendar([("VFREEBUSY", {}, record)], "PUBLISH")) 323 finally: 324 f.close() 325 fix_permissions(filename) 326 327 return True 328 329 # vim: tabstop=4 expandtab shiftwidth=4