1 #!/usr/bin/env python 2 3 """ 4 General support for calendar data storage. 5 6 Copyright (C) 2014, 2015, 2016 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 imiptools.dates import format_datetime 23 24 class StoreBase: 25 26 "The core operations of a data store." 27 28 def acquire_lock(self, user, timeout=None): 29 pass 30 31 def release_lock(self, user): 32 pass 33 34 # User discovery. 35 36 def get_users(self): 37 38 "Return a list of users." 39 40 pass 41 42 # Event and event metadata access. 43 44 def get_events(self, user): 45 46 "Return a list of event identifiers." 47 48 pass 49 50 def get_all_events(self, user): 51 52 "Return a set of (uid, recurrenceid) tuples for all events." 53 54 uids = self.get_events(user) 55 if not uids: 56 return set() 57 58 all_events = set() 59 for uid in uids: 60 all_events.add((uid, None)) 61 all_events.update([(uid, recurrenceid) for recurrenceid in self.get_recurrences(user, uid)]) 62 63 return all_events 64 65 def get_event(self, user, uid, recurrenceid=None, dirname=None): 66 67 """ 68 Get the event for the given 'user' with the given 'uid'. If 69 the optional 'recurrenceid' is specified, a specific instance or 70 occurrence of an event is returned. 71 """ 72 73 pass 74 75 def get_complete_event(self, user, uid): 76 77 "Get the event for the given 'user' with the given 'uid'." 78 79 pass 80 81 def set_event(self, user, uid, recurrenceid, node): 82 83 """ 84 Set an event for 'user' having the given 'uid' and 'recurrenceid' (which 85 if the latter is specified, a specific instance or occurrence of an 86 event is referenced), using the given 'node' description. 87 """ 88 89 if recurrenceid: 90 return self.set_recurrence(user, uid, recurrenceid, node) 91 else: 92 return self.set_complete_event(user, uid, node) 93 94 def set_complete_event(self, user, uid, node): 95 96 "Set an event for 'user' having the given 'uid' and 'node'." 97 98 pass 99 100 def remove_event(self, user, uid, recurrenceid=None): 101 102 """ 103 Remove an event for 'user' having the given 'uid'. If the optional 104 'recurrenceid' is specified, a specific instance or occurrence of an 105 event is removed. 106 """ 107 108 if recurrenceid: 109 return self.remove_recurrence(user, uid, recurrenceid) 110 else: 111 for recurrenceid in self.get_recurrences(user, uid) or []: 112 self.remove_recurrence(user, uid, recurrenceid) 113 return self.remove_complete_event(user, uid) 114 115 def remove_complete_event(self, user, uid): 116 117 "Remove an event for 'user' having the given 'uid'." 118 119 self.remove_recurrences(user, uid) 120 return self.remove_parent_event(user, uid) 121 122 def remove_parent_event(self, user, uid): 123 124 "Remove the parent event for 'user' having the given 'uid'." 125 126 pass 127 128 def get_recurrences(self, user, uid): 129 130 """ 131 Get additional event instances for an event of the given 'user' with the 132 indicated 'uid'. Both active and cancelled recurrences are returned. 133 """ 134 135 return self.get_active_recurrences(user, uid) + self.get_cancelled_recurrences(user, uid) 136 137 def get_active_recurrences(self, user, uid): 138 139 """ 140 Get additional event instances for an event of the given 'user' with the 141 indicated 'uid'. Cancelled recurrences are not returned. 142 """ 143 144 pass 145 146 def get_cancelled_recurrences(self, user, uid): 147 148 """ 149 Get additional event instances for an event of the given 'user' with the 150 indicated 'uid'. Only cancelled recurrences are returned. 151 """ 152 153 pass 154 155 def get_recurrence(self, user, uid, recurrenceid): 156 157 """ 158 For the event of the given 'user' with the given 'uid', return the 159 specific recurrence indicated by the 'recurrenceid'. 160 """ 161 162 pass 163 164 def set_recurrence(self, user, uid, recurrenceid, node): 165 166 "Set an event for 'user' having the given 'uid' and 'node'." 167 168 pass 169 170 def remove_recurrence(self, user, uid, recurrenceid): 171 172 """ 173 Remove a special recurrence from an event stored by 'user' having the 174 given 'uid' and 'recurrenceid'. 175 """ 176 177 pass 178 179 def remove_recurrences(self, user, uid): 180 181 """ 182 Remove all recurrences for an event stored by 'user' having the given 183 'uid'. 184 """ 185 186 for recurrenceid in self.get_recurrences(user, uid): 187 self.remove_recurrence(user, uid, recurrenceid) 188 189 return self.remove_recurrence_collection(user, uid) 190 191 def remove_recurrence_collection(self, user, uid): 192 193 """ 194 Remove the collection of recurrences stored by 'user' having the given 195 'uid'. 196 """ 197 198 pass 199 200 # Free/busy period providers, upon extension of the free/busy records. 201 202 def _get_freebusy_providers(self, user): 203 204 """ 205 Return the free/busy providers for the given 'user'. 206 207 This function returns any stored datetime and a list of providers as a 208 2-tuple. Each provider is itself a (uid, recurrenceid) tuple. 209 """ 210 211 pass 212 213 def get_freebusy_providers(self, user, dt=None): 214 215 """ 216 Return a set of uncancelled events of the form (uid, recurrenceid) 217 providing free/busy details beyond the given datetime 'dt'. 218 219 If 'dt' is not specified, all events previously found to provide 220 details will be returned. Otherwise, if 'dt' is earlier than the 221 datetime recorded for the known providers, None is returned, indicating 222 that the list of providers must be recomputed. 223 224 This function returns a list of (uid, recurrenceid) tuples upon success. 225 """ 226 227 t = self._get_freebusy_providers(user) 228 if not t: 229 return None 230 231 dt_string, t = t 232 233 # If the requested datetime is earlier than the stated datetime, the 234 # providers will need to be recomputed. 235 236 if dt: 237 providers_dt = get_datetime(dt_string) 238 if not providers_dt or providers_dt > dt: 239 return None 240 241 # Otherwise, return the providers. 242 243 return t[1:] 244 245 def _set_freebusy_providers(self, user, dt_string, t): 246 247 "Set the given provider timestamp 'dt_string' and table 't'." 248 249 pass 250 251 def set_freebusy_providers(self, user, dt, providers): 252 253 """ 254 Define the uncancelled events providing free/busy details beyond the 255 given datetime 'dt'. 256 """ 257 258 t = [] 259 260 for obj in providers: 261 t.append((obj.get_uid(), obj.get_recurrenceid())) 262 263 return self._set_freebusy_providers(user, format_datetime(dt), t) 264 265 def append_freebusy_provider(self, user, provider): 266 267 "For the given 'user', append the free/busy 'provider'." 268 269 t = self._get_freebusy_providers(user) 270 if not t: 271 return False 272 273 dt_string, t = t 274 t.append((provider.get_uid(), provider.get_recurrenceid())) 275 276 return self._set_freebusy_providers(user, dt_string, t) 277 278 def remove_freebusy_provider(self, user, provider): 279 280 "For the given 'user', remove the free/busy 'provider'." 281 282 t = self._get_freebusy_providers(user) 283 if not t: 284 return False 285 286 dt_string, t = t 287 try: 288 t.remove((provider.get_uid(), provider.get_recurrenceid())) 289 except ValueError: 290 return False 291 292 return self._set_freebusy_providers(user, dt_string, t) 293 294 # Free/busy period access. 295 296 def get_freebusy(self, user, name=None): 297 298 "Get free/busy details for the given 'user'." 299 300 pass 301 302 def get_freebusy_for_other(self, user, other): 303 304 "For the given 'user', get free/busy details for the 'other' user." 305 306 pass 307 308 def set_freebusy(self, user, freebusy, name=None): 309 310 "For the given 'user', set 'freebusy' details." 311 312 pass 313 314 def set_freebusy_for_other(self, user, freebusy, other): 315 316 "For the given 'user', set 'freebusy' details for the 'other' user." 317 318 pass 319 320 # Tentative free/busy periods related to countering. 321 322 def get_freebusy_offers(self, user): 323 324 "Get free/busy offers for the given 'user'." 325 326 pass 327 328 def set_freebusy_offers(self, user, freebusy): 329 330 "For the given 'user', set 'freebusy' offers." 331 332 return self.set_freebusy(user, freebusy, "freebusy-offers") 333 334 # Requests and counter-proposals. 335 336 def get_requests(self, user): 337 338 "Get requests for the given 'user'." 339 340 pass 341 342 def set_requests(self, user, requests): 343 344 "For the given 'user', set the list of queued 'requests'." 345 346 pass 347 348 def set_request(self, user, uid, recurrenceid=None, type=None): 349 350 """ 351 For the given 'user', set the queued 'uid' and 'recurrenceid', 352 indicating a request, along with any given 'type'. 353 """ 354 355 pass 356 357 def queue_request(self, user, uid, recurrenceid=None, type=None): 358 359 """ 360 Queue a request for 'user' having the given 'uid'. If the optional 361 'recurrenceid' is specified, the entry refers to a specific instance 362 or occurrence of an event. The 'type' parameter can be used to indicate 363 a specific type of request. 364 """ 365 366 requests = self.get_requests(user) or [] 367 368 if not self.have_request(requests, uid, recurrenceid): 369 return self.set_request(user, uid, recurrenceid, type) 370 371 return False 372 373 def dequeue_request(self, user, uid, recurrenceid=None): 374 375 """ 376 Dequeue all requests for 'user' having the given 'uid'. If the optional 377 'recurrenceid' is specified, all requests for that specific instance or 378 occurrence of an event are dequeued. 379 """ 380 381 requests = self.get_requests(user) or [] 382 result = [] 383 384 for request in requests: 385 if request[:2] != (uid, recurrenceid): 386 result.append(request) 387 388 self.set_requests(user, result) 389 return True 390 391 def has_request(self, user, uid, recurrenceid=None, type=None, strict=False): 392 return self.have_request(self.get_requests(user) or [], uid, recurrenceid, type, strict) 393 394 def have_request(self, requests, uid, recurrenceid=None, type=None, strict=False): 395 396 """ 397 Return whether 'requests' contains a request with the given 'uid' and 398 any specified 'recurrenceid' and 'type'. If 'strict' is set to a true 399 value, the precise type of the request must match; otherwise, any type 400 of request for the identified object may be matched. 401 """ 402 403 for request in requests: 404 if request[:2] == (uid, recurrenceid) and ( 405 not strict or 406 not request[2:] and not type or 407 request[2:] and request[2] == type): 408 409 return True 410 411 return False 412 413 def get_counters(self, user, uid, recurrenceid=None): 414 415 """ 416 For the given 'user', return a list of users from whom counter-proposals 417 have been received for the given 'uid' and optional 'recurrenceid'. 418 """ 419 420 pass 421 422 def get_counter(self, user, other, uid, recurrenceid=None): 423 424 """ 425 For the given 'user', return the counter-proposal from 'other' for the 426 given 'uid' and optional 'recurrenceid'. 427 """ 428 429 pass 430 431 def set_counter(self, user, other, node, uid, recurrenceid=None): 432 433 """ 434 For the given 'user', store a counter-proposal received from 'other' the 435 given 'node' representing that proposal for the given 'uid' and 436 'recurrenceid'. 437 """ 438 439 pass 440 441 def remove_counters(self, user, uid, recurrenceid=None): 442 443 """ 444 For the given 'user', remove all counter-proposals associated with the 445 given 'uid' and 'recurrenceid'. 446 """ 447 448 pass 449 450 def remove_counter(self, user, other, uid, recurrenceid=None): 451 452 """ 453 For the given 'user', remove any counter-proposal from 'other' 454 associated with the given 'uid' and 'recurrenceid'. 455 """ 456 457 pass 458 459 # Event cancellation. 460 461 def cancel_event(self, user, uid, recurrenceid=None): 462 463 """ 464 Cancel an event for 'user' having the given 'uid'. If the optional 465 'recurrenceid' is specified, a specific instance or occurrence of an 466 event is cancelled. 467 """ 468 469 pass 470 471 def uncancel_event(self, user, uid, recurrenceid=None): 472 473 """ 474 Uncancel an event for 'user' having the given 'uid'. If the optional 475 'recurrenceid' is specified, a specific instance or occurrence of an 476 event is uncancelled. 477 """ 478 479 pass 480 481 def remove_cancellations(self, user, uid, recurrenceid=None): 482 483 """ 484 Remove cancellations for 'user' for any event having the given 'uid'. If 485 the optional 'recurrenceid' is specified, a specific instance or 486 occurrence of an event is affected. 487 """ 488 489 # Remove all recurrence cancellations if a general event is indicated. 490 491 if not recurrenceid: 492 for _recurrenceid in self.get_cancelled_recurrences(user, uid): 493 self.remove_cancellation(user, uid, _recurrenceid) 494 495 return self.remove_cancellation(user, uid, recurrenceid) 496 497 def remove_cancellation(self, user, uid, recurrenceid=None): 498 499 """ 500 Remove a cancellation for 'user' for the event having the given 'uid'. 501 If the optional 'recurrenceid' is specified, a specific instance or 502 occurrence of an event is affected. 503 """ 504 505 pass 506 507 class PublisherBase: 508 509 "The core operations of a data publisher." 510 511 def set_freebusy(self, user, freebusy): 512 513 "For the given 'user', set 'freebusy' details." 514 515 pass 516 517 class JournalBase: 518 519 "The core operations of a journal system supporting quotas." 520 521 # Quota and user identity/group discovery. 522 523 def get_quotas(self): 524 525 "Return a list of quotas." 526 527 pass 528 529 def get_quota_users(self, quota): 530 531 "Return a list of quota users." 532 533 pass 534 535 # Groups of users sharing quotas. 536 537 def get_groups(self, quota): 538 539 "Return the identity mappings for the given 'quota' as a dictionary." 540 541 pass 542 543 def get_limits(self, quota): 544 545 """ 546 Return the limits for the 'quota' as a dictionary mapping identities or 547 groups to durations. 548 """ 549 550 pass 551 552 # Free/busy period access for users within quota groups. 553 554 def get_freebusy(self, quota, user): 555 556 "Get free/busy details for the given 'quota' and 'user'." 557 558 pass 559 560 def set_freebusy(self, quota, user, freebusy): 561 562 "For the given 'quota' and 'user', set 'freebusy' details." 563 564 pass 565 566 # Journal entry methods. 567 568 def get_entries(self, quota, group): 569 570 """ 571 Return a list of journal entries for the given 'quota' for the indicated 572 'group'. 573 """ 574 575 pass 576 577 def set_entries(self, quota, group, entries): 578 579 """ 580 For the given 'quota' and indicated 'group', set the list of journal 581 'entries'. 582 """ 583 584 pass 585 586 # vim: tabstop=4 expandtab shiftwidth=4