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