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 StoreBase: 25 26 "The core operations of a data store." 27 28 # User discovery. 29 30 def get_users(self): 31 32 "Return a list of users." 33 34 pass 35 36 # Event and event metadata access. 37 38 def get_all_events(self, user, dirname=None): 39 40 """ 41 Return a set of (uid, recurrenceid) tuples for all events. Unless 42 'dirname' is specified, only active events are returned; otherwise, 43 events from the given 'dirname' are returned. 44 """ 45 46 cancelled = self.get_cancelled_events(user) 47 active = self.get_events(user) 48 49 if dirname == "cancellations": 50 uids = cancelled 51 else: 52 uids = active 53 54 if not uids: 55 return set() 56 57 all_events = set() 58 59 # Add all qualifying parent events to the result set. 60 61 for uid in uids: 62 all_events.add((uid, None)) 63 64 # Process all parent events regardless of status to find those in the 65 # category/directory of interest. 66 67 for uid in active + cancelled: 68 69 if dirname == "cancellations": 70 recurrenceids = self.get_cancelled_recurrences(user, uid) 71 else: 72 recurrenceids = self.get_active_recurrences(user, uid) 73 74 all_events.update([(uid, recurrenceid) for recurrenceid in recurrenceids]) 75 76 return all_events 77 78 def get_events(self, user): 79 80 "Return a list of event identifiers." 81 82 pass 83 84 def get_cancelled_events(self, user): 85 86 "Return a list of event identifiers for cancelled events." 87 88 pass 89 90 def get_event(self, user, uid, recurrenceid=None, dirname=None): 91 92 """ 93 Get the event for the given 'user' with the given 'uid'. If 94 the optional 'recurrenceid' is specified, a specific instance or 95 occurrence of an event is returned. 96 """ 97 98 pass 99 100 def get_complete_event(self, user, uid): 101 102 "Get the event for the given 'user' with the given 'uid'." 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_complete_event(user, uid, node) 118 119 def set_complete_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 obj.set_modifying(self.get_active_recurrences(user, uid)) 240 if not obj.cancelling: 241 obj.set_cancelling(self.get_cancelled_recurrences(user, uid)) 242 243 # Free/busy period providers, upon extension of the free/busy records. 244 245 def _get_freebusy_providers(self, user): 246 247 """ 248 Return the free/busy providers for the given 'user'. 249 250 This function returns any stored datetime and a list of providers as a 251 2-tuple. Each provider is itself a (uid, recurrenceid) tuple. 252 """ 253 254 pass 255 256 def get_freebusy_providers(self, user, dt=None): 257 258 """ 259 Return a set of uncancelled events of the form (uid, recurrenceid) 260 providing free/busy details beyond the given datetime 'dt'. 261 262 If 'dt' is not specified, all events previously found to provide 263 details will be returned. Otherwise, if 'dt' is earlier than the 264 datetime recorded for the known providers, None is returned, indicating 265 that the list of providers must be recomputed. 266 267 This function returns a list of (uid, recurrenceid) tuples upon success. 268 """ 269 270 t = self._get_freebusy_providers(user) 271 if not t: 272 return None 273 274 dt_string, t = t 275 276 # If the requested datetime is earlier than the stated datetime, the 277 # providers will need to be recomputed. 278 279 if dt: 280 providers_dt = get_datetime(dt_string) 281 if not providers_dt or providers_dt > dt: 282 return None 283 284 # Otherwise, return the providers. 285 286 return t 287 288 def _set_freebusy_providers(self, user, dt_string, t): 289 290 "Set the given provider timestamp 'dt_string' and table 't'." 291 292 pass 293 294 def set_freebusy_providers(self, user, dt, providers): 295 296 """ 297 Define the uncancelled events providing free/busy details beyond the 298 given datetime 'dt'. 299 """ 300 301 t = [] 302 303 for obj in providers: 304 t.append((obj.get_uid(), obj.get_recurrenceid())) 305 306 return self._set_freebusy_providers(user, format_datetime(dt), t) 307 308 def append_freebusy_provider(self, user, provider): 309 310 "For the given 'user', append the free/busy 'provider'." 311 312 t = self._get_freebusy_providers(user) 313 if not t: 314 return False 315 316 dt_string, t = t 317 details = (provider.get_uid(), provider.get_recurrenceid()) 318 319 if not details in t: 320 t.append(details) 321 322 return self._set_freebusy_providers(user, dt_string, t) 323 324 def remove_freebusy_provider(self, user, provider): 325 326 "For the given 'user', remove the free/busy 'provider'." 327 328 t = self._get_freebusy_providers(user) 329 if not t: 330 return False 331 332 dt_string, t = t 333 try: 334 t.remove((provider.get_uid(), provider.get_recurrenceid())) 335 except ValueError: 336 return False 337 338 return self._set_freebusy_providers(user, dt_string, t) 339 340 # Free/busy period access. 341 342 def get_freebusy(self, user, name=None, mutable=False): 343 344 "Get free/busy details for the given 'user'." 345 346 pass 347 348 def get_freebusy_for_other(self, user, other, mutable=False): 349 350 "For the given 'user', get free/busy details for the 'other' user." 351 352 pass 353 354 def get_freebusy_for_update(self, user, name=None): 355 356 """ 357 Get free/busy details for the given 'user' that may be updated, 358 potentially affecting the stored information directly. 359 """ 360 361 return self.get_freebusy(user, name, True) 362 363 def get_freebusy_for_other_for_update(self, user, other): 364 365 """ 366 For the given 'user', get free/busy details for the 'other' user 367 that may be updated, potentially affecting the stored information 368 directly. 369 """ 370 371 return self.get_freebusy_for_other(user, other, True) 372 373 def set_freebusy(self, user, freebusy, name=None): 374 375 "For the given 'user', set 'freebusy' details." 376 377 pass 378 379 def set_freebusy_for_other(self, user, freebusy, other): 380 381 "For the given 'user', set 'freebusy' details for the 'other' user." 382 383 pass 384 385 def get_freebusy_others(self, user): 386 387 """ 388 For the given 'user', return a list of other users for whom free/busy 389 information is retained. 390 """ 391 392 pass 393 394 # Tentative free/busy periods related to countering. 395 396 def get_freebusy_offers(self, user, mutable=False): 397 398 "Get free/busy offers for the given 'user'." 399 400 pass 401 402 def get_freebusy_offers_for_update(self, user): 403 404 """ 405 Get free/busy offers for the given 'user' that may be updated, 406 potentially affecting the stored information directly. 407 """ 408 409 return self.get_freebusy_offers(user, True) 410 411 def set_freebusy_offers(self, user, freebusy): 412 413 "For the given 'user', set 'freebusy' offers." 414 415 return self.set_freebusy(user, freebusy, "freebusy-offers") 416 417 # Requests and counter-proposals. 418 419 def get_requests(self, user): 420 421 "Get requests for the given 'user'." 422 423 pass 424 425 def set_requests(self, user, requests): 426 427 "For the given 'user', set the list of queued 'requests'." 428 429 pass 430 431 def set_request(self, user, uid, recurrenceid=None, type=None): 432 433 """ 434 For the given 'user', set the queued 'uid' and 'recurrenceid', 435 indicating a request, along with any given 'type'. 436 """ 437 438 pass 439 440 def queue_request(self, user, uid, recurrenceid=None, type=None): 441 442 """ 443 Queue a request for 'user' having the given 'uid'. If the optional 444 'recurrenceid' is specified, the entry refers to a specific instance 445 or occurrence of an event. The 'type' parameter can be used to indicate 446 a specific type of request. 447 """ 448 449 requests = self.get_requests(user) or [] 450 451 if not self.have_request(requests, uid, recurrenceid): 452 return self.set_request(user, uid, recurrenceid, type) 453 454 return False 455 456 def dequeue_request(self, user, uid, recurrenceid=None): 457 458 """ 459 Dequeue all requests for 'user' having the given 'uid'. If the optional 460 'recurrenceid' is specified, all requests for that specific instance or 461 occurrence of an event are dequeued. 462 """ 463 464 requests = self.get_requests(user) or [] 465 result = [] 466 467 for request in requests: 468 if request[:2] != (uid, recurrenceid): 469 result.append(request) 470 471 self.set_requests(user, result) 472 return True 473 474 def has_request(self, user, uid, recurrenceid=None, type=None, strict=False): 475 return self.have_request(self.get_requests(user) or [], uid, recurrenceid, type, strict) 476 477 def have_request(self, requests, uid, recurrenceid=None, type=None, strict=False): 478 479 """ 480 Return whether 'requests' contains a request with the given 'uid' and 481 any specified 'recurrenceid' and 'type'. If 'strict' is set to a true 482 value, the precise type of the request must match; otherwise, any type 483 of request for the identified object may be matched. 484 """ 485 486 for request in requests: 487 if request[:2] == (uid, recurrenceid) and ( 488 not strict or 489 not request[2:] and not type or 490 request[2:] and request[2] == type): 491 492 return True 493 494 return False 495 496 def get_counters(self, user, uid, recurrenceid=None): 497 498 """ 499 For the given 'user', return a list of users from whom counter-proposals 500 have been received for the given 'uid' and optional 'recurrenceid'. 501 """ 502 503 pass 504 505 def get_counter_recurrences(self, user, uid): 506 507 """ 508 For the given 'user', return a list of recurrence identifiers describing 509 counter-proposals for the parent event with the given 'uid'. 510 """ 511 512 pass 513 514 def get_counter(self, user, other, uid, recurrenceid=None): 515 516 """ 517 For the given 'user', return the counter-proposal from 'other' for the 518 given 'uid' and optional 'recurrenceid'. 519 """ 520 521 pass 522 523 def set_counter(self, user, other, node, uid, recurrenceid=None): 524 525 """ 526 For the given 'user', store a counter-proposal received from 'other' the 527 given 'node' representing that proposal for the given 'uid' and 528 'recurrenceid'. 529 """ 530 531 pass 532 533 def remove_counters(self, user, uid, recurrenceid=None, attendee=None): 534 535 """ 536 For the given 'user', remove all counter-proposals associated with the 537 given 'uid' and 'recurrenceid'. If 'attendee' is specified, only objects 538 provided by this attendee will be removed. 539 """ 540 541 pass 542 543 def remove_counter(self, user, other, uid, recurrenceid=None): 544 545 """ 546 For the given 'user', remove any counter-proposal from 'other' 547 associated with the given 'uid' and 'recurrenceid'. 548 """ 549 550 pass 551 552 # Event cancellation. 553 554 def cancel_event(self, user, uid, recurrenceid=None): 555 556 """ 557 Cancel an event for 'user' having the given 'uid'. If the optional 558 'recurrenceid' is specified, a specific instance or occurrence of an 559 event is cancelled. 560 """ 561 562 pass 563 564 def uncancel_event(self, user, uid, recurrenceid=None): 565 566 """ 567 Uncancel an event for 'user' having the given 'uid'. If the optional 568 'recurrenceid' is specified, a specific instance or occurrence of an 569 event is uncancelled. 570 """ 571 572 pass 573 574 def remove_cancellations(self, user, uid, recurrenceid=None): 575 576 """ 577 Remove cancellations for 'user' for any event having the given 'uid'. If 578 the optional 'recurrenceid' is specified, a specific instance or 579 occurrence of an event is affected. 580 """ 581 582 # Remove all recurrence cancellations if a general event is indicated. 583 584 if not recurrenceid: 585 for _recurrenceid in self.get_cancelled_recurrences(user, uid): 586 self.remove_cancellation(user, uid, _recurrenceid) 587 588 return self.remove_cancellation(user, uid, recurrenceid) 589 590 def remove_cancellation(self, user, uid, recurrenceid=None): 591 592 """ 593 Remove a cancellation for 'user' for the event having the given 'uid'. 594 If the optional 'recurrenceid' is specified, a specific instance or 595 occurrence of an event is affected. 596 """ 597 598 pass 599 600 class PublisherBase: 601 602 "The core operations of a data publisher." 603 604 def set_freebusy(self, user, freebusy): 605 606 "For the given 'user', set 'freebusy' details." 607 608 pass 609 610 class JournalBase: 611 612 "The core operations of a journal system supporting quotas." 613 614 # Quota and user identity/group discovery. 615 616 def get_quotas(self): 617 618 "Return a list of quotas." 619 620 pass 621 622 def get_quota_users(self, quota): 623 624 "Return a list of quota users." 625 626 pass 627 628 # Delegate information for the quota. 629 630 def get_delegates(self, quota): 631 632 "Return a list of delegates for 'quota'." 633 634 pass 635 636 def set_delegates(self, quota, delegates): 637 638 "For the given 'quota', set the list of 'delegates'." 639 640 pass 641 642 # Groups of users sharing quotas. 643 644 def get_groups(self, quota): 645 646 "Return the identity mappings for the given 'quota' as a dictionary." 647 648 pass 649 650 def set_group(self, quota, store_user, user_group): 651 652 """ 653 For the given 'quota', set a mapping from 'store_user' to 'user_group'. 654 """ 655 656 pass 657 658 def get_limits(self, quota): 659 660 """ 661 Return the limits for the 'quota' as a dictionary mapping identities or 662 groups to durations. 663 """ 664 665 pass 666 667 def set_limit(self, quota, group, limit): 668 669 """ 670 For the given 'quota', set for a user 'group' the given 'limit' on 671 resource usage. 672 """ 673 674 pass 675 676 # Journal entry methods. 677 678 def get_entries(self, quota, group, mutable=False): 679 680 """ 681 Return a list of journal entries for the given 'quota' for the indicated 682 'group'. 683 """ 684 685 pass 686 687 def get_entries_for_update(self, quota, group): 688 689 """ 690 Return a list of journal entries for the given 'quota' for the indicated 691 'group' that may be updated, potentially affecting the stored 692 information directly. 693 """ 694 695 return self.get_entries(quota, group, True) 696 697 def set_entries(self, quota, group, entries): 698 699 """ 700 For the given 'quota' and indicated 'group', set the list of journal 701 'entries'. 702 """ 703 704 pass 705 706 # vim: tabstop=4 expandtab shiftwidth=4