1 #!/usr/bin/env python 2 3 """ 4 General support for calendar data storage. 5 6 Copyright (C) 2014, 2015, 2016, 2017 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, get_datetime 23 24 class StoreInitialisationError(Exception): 25 26 "An error occurring when the initialisation of a store fails." 27 28 pass 29 30 class StoreBase: 31 32 "The core operations of a data store." 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_all_events(self, user, dirname=None): 45 46 """ 47 Return a set of (uid, recurrenceid) tuples for all events. Unless 48 'dirname' is specified, only active events are returned; otherwise, 49 events from the given 'dirname' are returned. 50 """ 51 52 cancelled = self.get_cancelled_events(user) 53 active = self.get_events(user) 54 55 if dirname == "cancellations": 56 uids = cancelled 57 else: 58 uids = active 59 60 if not uids: 61 return set() 62 63 all_events = set() 64 65 # Add all qualifying parent events to the result set. 66 67 for uid in uids: 68 all_events.add((uid, None)) 69 70 # Process all parent events regardless of status to find those in the 71 # category/directory of interest. 72 73 for uid in active + cancelled: 74 75 if dirname == "cancellations": 76 recurrenceids = self.get_cancelled_recurrences(user, uid) 77 else: 78 recurrenceids = self.get_active_recurrences(user, uid) 79 80 all_events.update([(uid, recurrenceid) for recurrenceid in recurrenceids]) 81 82 return all_events 83 84 def get_events(self, user): 85 86 "Return a list of event identifiers." 87 88 pass 89 90 def get_cancelled_events(self, user): 91 92 "Return a list of event identifiers for cancelled events." 93 94 pass 95 96 def get_event(self, user, uid, recurrenceid=None, dirname=None): 97 98 """ 99 Get the event for the given 'user' with the given 'uid'. If 100 the optional 'recurrenceid' is specified, a specific instance or 101 occurrence of an event is returned. 102 """ 103 104 pass 105 106 def set_event(self, user, uid, recurrenceid, node): 107 108 """ 109 Set an event for 'user' having the given 'uid' and 'recurrenceid' (which 110 if the latter is specified, a specific instance or occurrence of an 111 event is referenced), using the given 'node' description. 112 """ 113 114 if recurrenceid: 115 return self.set_recurrence(user, uid, recurrenceid, node) 116 else: 117 return self.set_parent_event(user, uid, node) 118 119 def set_parent_event(self, user, uid, node): 120 121 "Set an event for 'user' having the given 'uid' and 'node'." 122 123 pass 124 125 def remove_event(self, user, uid, recurrenceid=None): 126 127 """ 128 Remove an event for 'user' having the given 'uid'. If the optional 129 'recurrenceid' is specified, a specific instance or occurrence of an 130 event is removed. 131 """ 132 133 if recurrenceid: 134 return self.remove_recurrence(user, uid, recurrenceid) 135 else: 136 for recurrenceid in self.get_recurrences(user, uid) or []: 137 self.remove_recurrence(user, uid, recurrenceid) 138 return self.remove_complete_event(user, uid) 139 140 def remove_complete_event(self, user, uid): 141 142 "Remove an event for 'user' having the given 'uid'." 143 144 self.remove_recurrences(user, uid) 145 return self.remove_parent_event(user, uid) 146 147 def remove_parent_event(self, user, uid): 148 149 "Remove the parent event for 'user' having the given 'uid'." 150 151 pass 152 153 def get_recurrences(self, user, uid): 154 155 """ 156 Get additional event instances for an event of the given 'user' with the 157 indicated 'uid'. Both active and cancelled recurrences are returned. 158 """ 159 160 return self.get_active_recurrences(user, uid) + self.get_cancelled_recurrences(user, uid) 161 162 def get_active_recurrences(self, user, uid): 163 164 """ 165 Get additional event instances for an event of the given 'user' with the 166 indicated 'uid'. Cancelled recurrences are not returned. 167 """ 168 169 pass 170 171 def get_cancelled_recurrences(self, user, uid): 172 173 """ 174 Get additional event instances for an event of the given 'user' with the 175 indicated 'uid'. Only cancelled recurrences are returned. 176 """ 177 178 pass 179 180 def get_recurrence(self, user, uid, recurrenceid): 181 182 """ 183 For the event of the given 'user' with the given 'uid', return the 184 specific recurrence indicated by the 'recurrenceid'. 185 """ 186 187 pass 188 189 def set_recurrence(self, user, uid, recurrenceid, node): 190 191 "Set an event for 'user' having the given 'uid' and 'node'." 192 193 pass 194 195 def remove_recurrence(self, user, uid, recurrenceid): 196 197 """ 198 Remove a special recurrence from an event stored by 'user' having the 199 given 'uid' and 'recurrenceid'. 200 """ 201 202 pass 203 204 def remove_recurrences(self, user, uid): 205 206 """ 207 Remove all recurrences for an event stored by 'user' having the given 208 'uid'. 209 """ 210 211 for recurrenceid in self.get_recurrences(user, uid): 212 self.remove_recurrence(user, uid, recurrenceid) 213 214 return self.remove_recurrence_collection(user, uid) 215 216 def remove_recurrence_collection(self, user, uid): 217 218 """ 219 Remove the collection of recurrences stored by 'user' having the given 220 'uid'. 221 """ 222 223 pass 224 225 def update_event_from_recurrences(self, user, obj): 226 227 """ 228 Apply separate recurrence information to the event stored by 'user' 229 represented by 'obj'. Recurrences cannot be updated in this way and are 230 not modified by this method. 231 """ 232 233 if obj.get_recurrenceid(): 234 return 235 236 uid = obj.get_uid() 237 238 if not obj.modifying: 239 objects = [] 240 for recurrenceid in self.get_active_recurrences(user, uid): 241 objects.append(self.get_event(user, uid, recurrenceid)) 242 obj.set_modifying(objects) 243 244 if not obj.cancelling: 245 objects = [] 246 for recurrenceid in self.get_cancelled_recurrences(user, uid): 247 objects.append(self.get_event(user, uid, recurrenceid, "cancellations")) 248 obj.set_cancelling(objects) 249 250 # Free/busy period providers, upon extension of the free/busy records. 251 252 def _get_freebusy_providers(self, user): 253 254 """ 255 Return the free/busy providers for the given 'user'. 256 257 This function returns any stored datetime and a list of providers as a 258 2-tuple. Each provider is itself a (uid, recurrenceid) tuple. 259 """ 260 261 pass 262 263 def get_freebusy_providers(self, user, dt=None): 264 265 """ 266 Return a set of uncancelled events of the form (uid, recurrenceid) 267 providing free/busy details beyond the given datetime 'dt'. 268 269 If 'dt' is not specified, all events previously found to provide 270 details will be returned. Otherwise, if 'dt' is earlier than the 271 datetime recorded for the known providers, None is returned, indicating 272 that the list of providers must be recomputed. 273 274 This function returns a list of (uid, recurrenceid) tuples upon success. 275 """ 276 277 t = self._get_freebusy_providers(user) 278 if not t: 279 return None 280 281 dt_string, t = t 282 283 # If the requested datetime is earlier than the stated datetime, the 284 # providers will need to be recomputed. 285 286 if dt: 287 providers_dt = get_datetime(dt_string) 288 if not providers_dt or providers_dt > dt: 289 return None 290 291 # Otherwise, return the providers. 292 293 return t 294 295 def _set_freebusy_providers(self, user, dt_string, t): 296 297 "Set the given provider timestamp 'dt_string' and table 't'." 298 299 pass 300 301 def set_freebusy_providers(self, user, dt, providers): 302 303 """ 304 Define the uncancelled events providing free/busy details beyond the 305 given datetime 'dt'. 306 """ 307 308 t = [] 309 310 for obj in providers: 311 t.append((obj.get_uid(), obj.get_recurrenceid())) 312 313 return self._set_freebusy_providers(user, format_datetime(dt), t) 314 315 def append_freebusy_provider(self, user, provider): 316 317 "For the given 'user', append the free/busy 'provider'." 318 319 t = self._get_freebusy_providers(user) 320 if not t: 321 return False 322 323 dt_string, t = t 324 details = (provider.get_uid(), provider.get_recurrenceid()) 325 326 if not details in t: 327 t.append(details) 328 329 return self._set_freebusy_providers(user, dt_string, t) 330 331 def remove_freebusy_provider(self, user, provider): 332 333 "For the given 'user', remove the free/busy 'provider'." 334 335 t = self._get_freebusy_providers(user) 336 if not t: 337 return False 338 339 dt_string, t = t 340 try: 341 t.remove((provider.get_uid(), provider.get_recurrenceid())) 342 except ValueError: 343 return False 344 345 return self._set_freebusy_providers(user, dt_string, t) 346 347 # Free/busy period access. 348 349 def get_freebusy(self, user, name=None, mutable=False): 350 351 "Get free/busy details for the given 'user'." 352 353 pass 354 355 def get_freebusy_for_other(self, user, other, mutable=False): 356 357 "For the given 'user', get free/busy details for the 'other' user." 358 359 pass 360 361 def get_freebusy_for_update(self, user, name=None): 362 363 """ 364 Get free/busy details for the given 'user' that may be updated, 365 potentially affecting the stored information directly. 366 """ 367 368 return self.get_freebusy(user, name, True) 369 370 def get_freebusy_for_other_for_update(self, user, other): 371 372 """ 373 For the given 'user', get free/busy details for the 'other' user 374 that may be updated, potentially affecting the stored information 375 directly. 376 """ 377 378 return self.get_freebusy_for_other(user, other, True) 379 380 def set_freebusy(self, user, freebusy, name=None): 381 382 "For the given 'user', set 'freebusy' details." 383 384 pass 385 386 def set_freebusy_for_other(self, user, freebusy, other): 387 388 "For the given 'user', set 'freebusy' details for the 'other' user." 389 390 pass 391 392 def get_freebusy_others(self, user): 393 394 """ 395 For the given 'user', return a list of other users for whom free/busy 396 information is retained. 397 """ 398 399 pass 400 401 # Tentative free/busy periods related to countering. 402 403 def get_freebusy_offers(self, user, mutable=False): 404 405 "Get free/busy offers for the given 'user'." 406 407 pass 408 409 def get_freebusy_offers_for_update(self, user): 410 411 """ 412 Get free/busy offers for the given 'user' that may be updated, 413 potentially affecting the stored information directly. 414 """ 415 416 return self.get_freebusy_offers(user, True) 417 418 def set_freebusy_offers(self, user, freebusy): 419 420 "For the given 'user', set 'freebusy' offers." 421 422 return self.set_freebusy(user, freebusy, "freebusy-offers") 423 424 # Requests and counter-proposals. 425 426 def get_requests(self, user): 427 428 "Get requests for the given 'user'." 429 430 pass 431 432 def set_requests(self, user, requests): 433 434 "For the given 'user', set the list of queued 'requests'." 435 436 pass 437 438 def set_request(self, user, uid, recurrenceid=None, type=None): 439 440 """ 441 For the given 'user', set the queued 'uid' and 'recurrenceid', 442 indicating a request, along with any given 'type'. 443 """ 444 445 pass 446 447 def queue_request(self, user, uid, recurrenceid=None, type=None): 448 449 """ 450 Queue a request for 'user' having the given 'uid'. If the optional 451 'recurrenceid' is specified, the entry refers to a specific instance 452 or occurrence of an event. The 'type' parameter can be used to indicate 453 a specific type of request. 454 """ 455 456 requests = self.get_requests(user) or [] 457 458 if not self.have_request(requests, uid, recurrenceid): 459 return self.set_request(user, uid, recurrenceid, type) 460 461 return False 462 463 def dequeue_request(self, user, uid, recurrenceid=None): 464 465 """ 466 Dequeue all requests for 'user' having the given 'uid'. If the optional 467 'recurrenceid' is specified, all requests for that specific instance or 468 occurrence of an event are dequeued. 469 """ 470 471 requests = self.get_requests(user) or [] 472 result = [] 473 474 for request in requests: 475 if request[:2] != (uid, recurrenceid): 476 result.append(request) 477 478 self.set_requests(user, result) 479 return True 480 481 def has_request(self, user, uid, recurrenceid=None, type=None, strict=False): 482 return self.have_request(self.get_requests(user) or [], uid, recurrenceid, type, strict) 483 484 def have_request(self, requests, uid, recurrenceid=None, type=None, strict=False): 485 486 """ 487 Return whether 'requests' contains a request with the given 'uid' and 488 any specified 'recurrenceid' and 'type'. If 'strict' is set to a true 489 value, the precise type of the request must match; otherwise, any type 490 of request for the identified object may be matched. 491 """ 492 493 for request in requests: 494 if request[:2] == (uid, recurrenceid) and ( 495 not strict or 496 not request[2:] and not type or 497 request[2:] and request[2] == type): 498 499 return True 500 501 return False 502 503 def get_counters(self, user, uid, recurrenceid=None): 504 505 """ 506 For the given 'user', return a list of users from whom counter-proposals 507 have been received for the given 'uid' and optional 'recurrenceid'. 508 """ 509 510 pass 511 512 def get_counter_recurrences(self, user, uid): 513 514 """ 515 For the given 'user', return a list of recurrence identifiers describing 516 counter-proposals for the parent event with the given 'uid'. 517 """ 518 519 pass 520 521 def get_counter(self, user, other, uid, recurrenceid=None): 522 523 """ 524 For the given 'user', return the counter-proposal from 'other' for the 525 given 'uid' and optional 'recurrenceid'. 526 """ 527 528 pass 529 530 def set_counter(self, user, other, node, uid, recurrenceid=None): 531 532 """ 533 For the given 'user', store a counter-proposal received from 'other' the 534 given 'node' representing that proposal for the given 'uid' and 535 'recurrenceid'. 536 """ 537 538 pass 539 540 def remove_counters(self, user, uid, recurrenceid=None, attendee=None): 541 542 """ 543 For the given 'user', remove all counter-proposals associated with the 544 given 'uid' and 'recurrenceid'. If 'attendee' is specified, only objects 545 provided by this attendee will be removed. 546 """ 547 548 pass 549 550 def remove_counter(self, user, other, uid, recurrenceid=None): 551 552 """ 553 For the given 'user', remove any counter-proposal from 'other' 554 associated with the given 'uid' and 'recurrenceid'. 555 """ 556 557 pass 558 559 # Event cancellation. 560 561 def cancel_event(self, user, uid, recurrenceid=None): 562 563 """ 564 Cancel an event for 'user' having the given 'uid'. If the optional 565 'recurrenceid' is specified, a specific instance or occurrence of an 566 event is cancelled. 567 """ 568 569 pass 570 571 def uncancel_event(self, user, uid, recurrenceid=None): 572 573 """ 574 Uncancel an event for 'user' having the given 'uid'. If the optional 575 'recurrenceid' is specified, a specific instance or occurrence of an 576 event is uncancelled. 577 """ 578 579 pass 580 581 def remove_cancellations(self, user, uid, recurrenceid=None): 582 583 """ 584 Remove cancellations for 'user' for any event having the given 'uid'. If 585 the optional 'recurrenceid' is specified, a specific instance or 586 occurrence of an event is affected. 587 """ 588 589 # Remove all recurrence cancellations if a general event is indicated. 590 591 if not recurrenceid: 592 for _recurrenceid in self.get_cancelled_recurrences(user, uid): 593 self.remove_cancellation(user, uid, _recurrenceid) 594 595 return self.remove_cancellation(user, uid, recurrenceid) 596 597 def remove_cancellation(self, user, uid, recurrenceid=None): 598 599 """ 600 Remove a cancellation for 'user' for the event having the given 'uid'. 601 If the optional 'recurrenceid' is specified, a specific instance or 602 occurrence of an event is affected. 603 """ 604 605 pass 606 607 class PublisherBase: 608 609 "The core operations of a data publisher." 610 611 def set_freebusy(self, user, freebusy): 612 613 "For the given 'user', set 'freebusy' details." 614 615 pass 616 617 class JournalBase: 618 619 "The core operations of a journal system supporting quotas." 620 621 # Quota and user identity/group discovery. 622 623 def get_quotas(self): 624 625 "Return a list of quotas." 626 627 pass 628 629 def get_quota_users(self, quota): 630 631 "Return a list of quota users." 632 633 pass 634 635 # Delegate information for the quota. 636 637 def get_delegates(self, quota): 638 639 "Return a list of delegates for 'quota'." 640 641 pass 642 643 def set_delegates(self, quota, delegates): 644 645 "For the given 'quota', set the list of 'delegates'." 646 647 pass 648 649 # Groups of users sharing quotas. 650 651 def get_groups(self, quota): 652 653 "Return the identity mappings for the given 'quota' as a dictionary." 654 655 pass 656 657 def set_group(self, quota, store_user, user_group): 658 659 """ 660 For the given 'quota', set a mapping from 'store_user' to 'user_group'. 661 """ 662 663 pass 664 665 def get_limits(self, quota): 666 667 """ 668 Return the limits for the 'quota' as a dictionary mapping identities or 669 groups to durations. 670 """ 671 672 pass 673 674 def set_limit(self, quota, group, limit): 675 676 """ 677 For the given 'quota', set for a user 'group' the given 'limit' on 678 resource usage. 679 """ 680 681 pass 682 683 # Journal entry methods. 684 685 def get_entries(self, quota, group, mutable=False): 686 687 """ 688 Return a list of journal entries for the given 'quota' for the indicated 689 'group'. 690 """ 691 692 pass 693 694 def get_entries_for_update(self, quota, group): 695 696 """ 697 Return a list of journal entries for the given 'quota' for the indicated 698 'group' that may be updated, potentially affecting the stored 699 information directly. 700 """ 701 702 return self.get_entries(quota, group, True) 703 704 def set_entries(self, quota, group, entries): 705 706 """ 707 For the given 'quota' and indicated 'group', set the list of journal 708 'entries'. 709 """ 710 711 pass 712 713 # vim: tabstop=4 expandtab shiftwidth=4