1 #!/usr/bin/env python 2 3 """ 4 A simple filesystem-based store of calendar data. 5 6 Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from datetime import datetime 23 from imiptools.config import STORE_DIR, PUBLISH_DIR 24 from imiptools.data import make_calendar, parse_object, to_stream 25 from imiptools.filesys import fix_permissions, FileBase 26 from os.path import exists, isfile, join 27 from os import listdir, remove 28 29 class FileStore(FileBase): 30 31 "A file store of tabular free/busy data and objects." 32 33 def __init__(self, store_dir=STORE_DIR): 34 FileBase.__init__(self, store_dir) 35 36 def get_events(self, user): 37 38 "Return a list of event identifiers." 39 40 filename = self.get_object_in_store(user, "objects") 41 if not filename or not exists(filename): 42 return None 43 44 return [name for name in listdir(filename) if isfile(join(filename, name))] 45 46 def get_event(self, user, uid): 47 48 "Get the event for the given 'user' with the given 'uid'." 49 50 filename = self.get_object_in_store(user, "objects", uid) 51 if not filename or not exists(filename): 52 return None 53 54 f = open(filename, "rb") 55 try: 56 return parse_object(f, "utf-8") 57 finally: 58 f.close() 59 60 def set_event(self, user, uid, node): 61 62 "Set an event for 'user' having the given 'uid' and 'node'." 63 64 filename = self.get_object_in_store(user, "objects", uid) 65 if not filename: 66 return False 67 68 f = open(filename, "w") 69 try: 70 to_stream(f, node) 71 finally: 72 f.close() 73 fix_permissions(filename) 74 75 return True 76 77 def remove_event(self, user, uid): 78 79 "Remove an event for 'user' having the given 'uid'." 80 81 filename = self.get_object_in_store(user, "objects", uid) 82 if not filename: 83 return False 84 85 try: 86 remove(filename) 87 except OSError: 88 return False 89 90 return True 91 92 def get_freebusy(self, user): 93 94 "Get free/busy details for the given 'user'." 95 96 filename = self.get_object_in_store(user, "freebusy") 97 if not filename or not exists(filename): 98 return [] 99 else: 100 return self._get_freebusy(filename) 101 102 def get_freebusy_for_other(self, user, other): 103 104 "For the given 'user', get free/busy details for the 'other' user." 105 106 filename = self.get_object_in_store(user, "freebusy-other", other) 107 if not filename or not exists(filename): 108 return [] 109 else: 110 return self._get_freebusy(filename) 111 112 def _get_freebusy(self, filename): 113 f = open(filename) 114 try: 115 l = [] 116 for line in f.readlines(): 117 l.append(tuple(line.strip().split("\t"))) 118 return l 119 finally: 120 f.close() 121 122 def set_freebusy(self, user, freebusy): 123 124 "For the given 'user', set 'freebusy' details." 125 126 filename = self.get_object_in_store(user, "freebusy") 127 if not filename: 128 return False 129 130 self._set_freebusy(filename, freebusy) 131 return True 132 133 def set_freebusy_for_other(self, user, freebusy, other): 134 135 "For the given 'user', set 'freebusy' details for the 'other' user." 136 137 filename = self.get_object_in_store(user, "freebusy-other", other) 138 if not filename: 139 return False 140 141 self._set_freebusy(filename, freebusy) 142 return True 143 144 def _set_freebusy(self, filename, freebusy): 145 f = open(filename, "w") 146 try: 147 for item in freebusy: 148 f.write("\t".join([(value or "OPAQUE") for value in item]) + "\n") 149 finally: 150 f.close() 151 fix_permissions(filename) 152 153 def _get_requests(self, user, queue): 154 155 "Get requests for the given 'user' from the given 'queue'." 156 157 filename = self.get_object_in_store(user, queue) 158 if not filename or not exists(filename): 159 return None 160 161 f = open(filename) 162 try: 163 return [line.strip() for line in f.readlines()] 164 finally: 165 f.close() 166 167 def get_requests(self, user): 168 169 "Get requests for the given 'user'." 170 171 return self._get_requests(user, "requests") 172 173 def get_cancellations(self, user): 174 175 "Get cancellations for the given 'user'." 176 177 return self._get_requests(user, "cancellations") 178 179 def _set_requests(self, user, requests, queue): 180 181 """ 182 For the given 'user', set the list of queued 'requests' in the given 183 'queue'. 184 """ 185 186 filename = self.get_object_in_store(user, queue) 187 if not filename: 188 return False 189 190 f = open(filename, "w") 191 try: 192 for request in requests: 193 print >>f, request 194 finally: 195 f.close() 196 fix_permissions(filename) 197 198 return True 199 200 def set_requests(self, user, requests): 201 202 "For the given 'user', set the list of queued 'requests'." 203 204 return self._set_requests(user, requests, "requests") 205 206 def set_cancellations(self, user, cancellations): 207 208 "For the given 'user', set the list of queued 'cancellations'." 209 210 return self._set_requests(user, cancellations, "cancellations") 211 212 def _set_request(self, user, request, queue): 213 214 "For the given 'user', set the queued 'request' in the given 'queue'." 215 216 filename = self.get_object_in_store(user, queue) 217 if not filename: 218 return False 219 220 f = open(filename, "a") 221 try: 222 print >>f, request 223 finally: 224 f.close() 225 fix_permissions(filename) 226 227 return True 228 229 def set_request(self, user, request): 230 231 "For the given 'user', set the queued 'request'." 232 233 return self._set_request(user, request, "requests") 234 235 def set_cancellation(self, user, cancellation): 236 237 "For the given 'user', set the queued 'cancellation'." 238 239 return self._set_request(user, cancellation, "cancellations") 240 241 def queue_request(self, user, uid): 242 243 "Queue a request for 'user' having the given 'uid'." 244 245 requests = self.get_requests(user) or [] 246 247 if uid not in requests: 248 return self.set_request(user, uid) 249 250 return False 251 252 def dequeue_request(self, user, uid): 253 254 "Dequeue a request for 'user' having the given 'uid'." 255 256 requests = self.get_requests(user) or [] 257 258 try: 259 requests.remove(uid) 260 self.set_requests(user, requests) 261 except ValueError: 262 return False 263 else: 264 return True 265 266 def cancel_event(self, user, uid): 267 268 "Queue an event for cancellation for 'user' having the given 'uid'." 269 270 cancellations = self.get_cancellations(user) or [] 271 272 if uid not in cancellations: 273 return self.set_cancellation(user, uid) 274 275 return False 276 277 class FilePublisher(FileBase): 278 279 "A publisher of objects." 280 281 def __init__(self, store_dir=PUBLISH_DIR): 282 FileBase.__init__(self, store_dir) 283 284 def set_freebusy(self, user, freebusy): 285 286 "For the given 'user', set 'freebusy' details." 287 288 filename = self.get_object_in_store(user, "freebusy") 289 if not filename: 290 return False 291 292 record = [] 293 rwrite = record.append 294 295 rwrite(("ORGANIZER", {}, user)) 296 rwrite(("UID", {}, user)) 297 rwrite(("DTSTAMP", {}, datetime.utcnow().strftime("%Y%m%dT%H%M%SZ"))) 298 299 for start, end, uid, transp in freebusy: 300 if not transp or transp == "OPAQUE": 301 rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join([start, end]))) 302 303 f = open(filename, "w") 304 try: 305 to_stream(f, make_calendar([("VFREEBUSY", {}, record)], "PUBLISH")) 306 finally: 307 f.close() 308 fix_permissions(filename) 309 310 return True 311 312 # vim: tabstop=4 expandtab shiftwidth=4