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