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