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, 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 return open(filename) or None 55 56 def set_event(self, user, uid, node): 57 58 "Set an event for 'user' having the given 'uid' and 'node'." 59 60 filename = self.get_object_in_store(user, "objects", uid) 61 if not filename: 62 return False 63 64 f = open(filename, "w") 65 try: 66 to_stream(f, node) 67 finally: 68 f.close() 69 fix_permissions(filename) 70 71 return True 72 73 def remove_event(self, user, uid): 74 75 "Remove an event for 'user' having the given 'uid'." 76 77 filename = self.get_object_in_store(user, "objects", uid) 78 if not filename: 79 return False 80 81 try: 82 remove(filename) 83 except OSError: 84 return False 85 86 return True 87 88 def get_freebusy(self, user): 89 90 "Get free/busy details for the given 'user'." 91 92 filename = self.get_object_in_store(user, "freebusy") 93 if not filename or not exists(filename): 94 return [] 95 else: 96 return self._get_freebusy(filename) 97 98 def get_freebusy_for_other(self, user, other): 99 100 "For the given 'user', get free/busy details for the 'other' user." 101 102 filename = self.get_object_in_store(user, "freebusy-other", other) 103 if not filename or not exists(filename): 104 return [] 105 else: 106 return self._get_freebusy(filename) 107 108 def _get_freebusy(self, filename): 109 f = open(filename) 110 try: 111 l = [] 112 for line in f.readlines(): 113 l.append(tuple(line.strip().split("\t"))) 114 return l 115 finally: 116 f.close() 117 118 def set_freebusy(self, user, freebusy): 119 120 "For the given 'user', set 'freebusy' details." 121 122 filename = self.get_object_in_store(user, "freebusy") 123 if not filename: 124 return False 125 126 self._set_freebusy(filename, freebusy) 127 return True 128 129 def set_freebusy_for_other(self, user, freebusy, other): 130 131 "For the given 'user', set 'freebusy' details for the 'other' user." 132 133 filename = self.get_object_in_store(user, "freebusy-other", other) 134 if not filename: 135 return False 136 137 self._set_freebusy(filename, freebusy) 138 return True 139 140 def _set_freebusy(self, filename, freebusy): 141 f = open(filename, "w") 142 try: 143 for item in freebusy: 144 f.write("\t".join([(value or "OPAQUE") for value in item]) + "\n") 145 finally: 146 f.close() 147 fix_permissions(filename) 148 149 def _get_requests(self, user, queue): 150 151 "Get requests for the given 'user' from the given 'queue'." 152 153 filename = self.get_object_in_store(user, queue) 154 if not filename or not exists(filename): 155 return None 156 157 f = open(filename) 158 try: 159 return [line.strip() for line in f.readlines()] 160 finally: 161 f.close() 162 163 def get_requests(self, user): 164 165 "Get requests for the given 'user'." 166 167 return self._get_requests(user, "requests") 168 169 def get_cancellations(self, user): 170 171 "Get cancellations for the given 'user'." 172 173 return self._get_requests(user, "cancellations") 174 175 def _set_requests(self, user, requests, queue): 176 177 """ 178 For the given 'user', set the list of queued 'requests' in the given 179 'queue'. 180 """ 181 182 filename = self.get_object_in_store(user, queue) 183 if not filename: 184 return False 185 186 f = open(filename, "w") 187 try: 188 for request in requests: 189 print >>f, request 190 finally: 191 f.close() 192 fix_permissions(filename) 193 194 return True 195 196 def set_requests(self, user, requests): 197 198 "For the given 'user', set the list of queued 'requests'." 199 200 return self._set_requests(user, requests, "requests") 201 202 def set_cancellations(self, user, cancellations): 203 204 "For the given 'user', set the list of queued 'cancellations'." 205 206 return self._set_requests(user, cancellations, "cancellations") 207 208 def _set_request(self, user, request, queue): 209 210 "For the given 'user', set the queued 'request' in the given 'queue'." 211 212 filename = self.get_object_in_store(user, queue) 213 if not filename: 214 return False 215 216 f = open(filename, "a") 217 try: 218 print >>f, request 219 finally: 220 f.close() 221 fix_permissions(filename) 222 223 return True 224 225 def set_request(self, user, request): 226 227 "For the given 'user', set the queued 'request'." 228 229 return self._set_request(user, request, "requests") 230 231 def set_cancellation(self, user, cancellation): 232 233 "For the given 'user', set the queued 'cancellation'." 234 235 return self._set_request(user, cancellation, "cancellations") 236 237 def queue_request(self, user, uid): 238 239 "Queue a request for 'user' having the given 'uid'." 240 241 requests = self.get_requests(user) or [] 242 243 if uid not in requests: 244 return self.set_request(user, uid) 245 246 return False 247 248 def dequeue_request(self, user, uid): 249 250 "Dequeue a request for 'user' having the given 'uid'." 251 252 requests = self.get_requests(user) or [] 253 254 try: 255 requests.remove(uid) 256 self.set_requests(user, requests) 257 except ValueError: 258 return False 259 else: 260 return True 261 262 def cancel_event(self, user, uid): 263 264 "Queue an event for cancellation for 'user' having the given 'uid'." 265 266 cancellations = self.get_cancellations(user) or [] 267 268 if uid not in cancellations: 269 return self.set_cancellation(user, uid) 270 271 return False 272 273 class FilePublisher(FileBase): 274 275 "A publisher of objects." 276 277 def __init__(self, store_dir=PUBLISH_DIR): 278 FileBase.__init__(self, store_dir) 279 280 def set_freebusy(self, user, freebusy): 281 282 "For the given 'user', set 'freebusy' details." 283 284 filename = self.get_object_in_store(user, "freebusy") 285 if not filename: 286 return False 287 288 record = [] 289 rwrite = record.append 290 291 rwrite(("ORGANIZER", {}, user)) 292 rwrite(("UID", {}, user)) 293 rwrite(("DTSTAMP", {}, datetime.utcnow().strftime("%Y%m%dT%H%M%SZ"))) 294 295 for start, end, uid, transp in freebusy: 296 if not transp or transp == "OPAQUE": 297 rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join([start, end]))) 298 299 f = open(filename, "w") 300 try: 301 to_stream(f, make_calendar([("VFREEBUSY", {}, record)], "PUBLISH")) 302 finally: 303 f.close() 304 fix_permissions(filename) 305 306 return True 307 308 # vim: tabstop=4 expandtab shiftwidth=4