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