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) 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, 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, 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): 171 172 "Get requests for the given 'user'." 173 174 filename = self.get_object_in_store(user, "requests") 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 set_requests(self, user, requests): 185 186 "For the given 'user', set the list of queued 'requests'." 187 188 filename = self.get_object_in_store(user, "requests") 189 if not filename: 190 return False 191 192 f = open(filename, "w") 193 try: 194 for request in requests: 195 print >>f, request 196 finally: 197 f.close() 198 fix_permissions(filename) 199 200 return True 201 202 def set_request(self, user, request): 203 204 "For the given 'user', set the queued 'request'." 205 206 filename = self.get_object_in_store(user, "requests") 207 if not filename: 208 return False 209 210 f = open(filename, "a") 211 try: 212 print >>f, request 213 finally: 214 f.close() 215 fix_permissions(filename) 216 217 return True 218 219 def queue_request(self, user, uid): 220 221 "Queue a request for 'user' having the given 'uid'." 222 223 requests = self.get_requests(user) or [] 224 225 if uid not in requests: 226 return self.set_request(user, uid) 227 228 return False 229 230 def dequeue_request(self, user, uid): 231 232 "Dequeue a request for 'user' having the given 'uid'." 233 234 requests = self.get_requests(user) or [] 235 236 try: 237 requests.remove(uid) 238 self.set_requests(user, requests) 239 except ValueError: 240 return False 241 else: 242 return True 243 244 class FilePublisher(FileBase): 245 246 "A publisher of objects." 247 248 def __init__(self, store_dir=PUBLISH_DIR): 249 FileBase.__init__(self, store_dir) 250 251 def set_freebusy(self, user, freebusy): 252 253 "For the given 'user', set 'freebusy' details." 254 255 filename = self.get_object_in_store(user, "freebusy") 256 if not filename: 257 return False 258 259 record = [] 260 rwrite = record.append 261 262 rwrite(("ORGANIZER", {}, user)) 263 rwrite(("UID", {}, user)) 264 rwrite(("DTSTAMP", {}, datetime.utcnow().strftime("%Y%m%dT%H%M%SZ"))) 265 266 for start, end, uid, transp in freebusy: 267 if not transp or transp == "OPAQUE": 268 rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join([start, end]))) 269 270 f = open(filename, "w") 271 try: 272 to_stream(f, make_calendar([("VFREEBUSY", {}, record)], "PUBLISH")) 273 finally: 274 f.close() 275 fix_permissions(filename) 276 277 return True 278 279 # vim: tabstop=4 expandtab shiftwidth=4