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 # 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): 516 517 """ 518 For the given 'user', remove all counter-proposals associated with the 519 given 'uid' and 'recurrenceid'. 520 """ 521 522 pass 523 524 def remove_counter(self, user, other, uid, recurrenceid=None): 525 526 """ 527 For the given 'user', remove any counter-proposal from 'other' 528 associated with the given 'uid' and 'recurrenceid'. 529 """ 530 531 pass 532 533 # Event cancellation. 534 535 def cancel_event(self, user, uid, recurrenceid=None): 536 537 """ 538 Cancel an event for 'user' having the given 'uid'. If the optional 539 'recurrenceid' is specified, a specific instance or occurrence of an 540 event is cancelled. 541 """ 542 543 pass 544 545 def uncancel_event(self, user, uid, recurrenceid=None): 546 547 """ 548 Uncancel an event for 'user' having the given 'uid'. If the optional 549 'recurrenceid' is specified, a specific instance or occurrence of an 550 event is uncancelled. 551 """ 552 553 pass 554 555 def remove_cancellations(self, user, uid, recurrenceid=None): 556 557 """ 558 Remove cancellations for 'user' for any event having the given 'uid'. If 559 the optional 'recurrenceid' is specified, a specific instance or 560 occurrence of an event is affected. 561 """ 562 563 # Remove all recurrence cancellations if a general event is indicated. 564 565 if not recurrenceid: 566 for _recurrenceid in self.get_cancelled_recurrences(user, uid): 567 self.remove_cancellation(user, uid, _recurrenceid) 568 569 return self.remove_cancellation(user, uid, recurrenceid) 570 571 def remove_cancellation(self, user, uid, recurrenceid=None): 572 573 """ 574 Remove a cancellation for 'user' for the event having the given 'uid'. 575 If the optional 'recurrenceid' is specified, a specific instance or 576 occurrence of an event is affected. 577 """ 578 579 pass 580 581 class PublisherBase: 582 583 "The core operations of a data publisher." 584 585 def set_freebusy(self, user, freebusy): 586 587 "For the given 'user', set 'freebusy' details." 588 589 pass 590 591 class JournalBase: 592 593 "The core operations of a journal system supporting quotas." 594 595 # Quota and user identity/group discovery. 596 597 def get_quotas(self): 598 599 "Return a list of quotas." 600 601 pass 602 603 def get_quota_users(self, quota): 604 605 "Return a list of quota users." 606 607 pass 608 609 # Delegate information for the quota. 610 611 def get_delegates(self, quota): 612 613 "Return a list of delegates for 'quota'." 614 615 pass 616 617 def set_delegates(self, quota, delegates): 618 619 "For the given 'quota', set the list of 'delegates'." 620 621 pass 622 623 # Groups of users sharing quotas. 624 625 def get_groups(self, quota): 626 627 "Return the identity mappings for the given 'quota' as a dictionary." 628 629 pass 630 631 def set_group(self, quota, store_user, user_group): 632 633 """ 634 For the given 'quota', set a mapping from 'store_user' to 'user_group'. 635 """ 636 637 pass 638 639 def get_limits(self, quota): 640 641 """ 642 Return the limits for the 'quota' as a dictionary mapping identities or 643 groups to durations. 644 """ 645 646 pass 647 648 def set_limit(self, quota, group, limit): 649 650 """ 651 For the given 'quota', set for a user 'group' the given 'limit' on 652 resource usage. 653 """ 654 655 pass 656 657 # Journal entry methods. 658 659 def get_entries(self, quota, group, mutable=False): 660 661 """ 662 Return a list of journal entries for the given 'quota' for the indicated 663 'group'. 664 """ 665 666 pass 667 668 def get_entries_for_update(self, quota, group): 669 670 """ 671 Return a list of journal entries for the given 'quota' for the indicated 672 'group' that may be updated, potentially affecting the stored 673 information directly. 674 """ 675 676 return self.get_entries(quota, group, True) 677 678 def set_entries(self, quota, group, entries): 679 680 """ 681 For the given 'quota' and indicated 'group', set the list of journal 682 'entries'. 683 """ 684 685 pass 686 687 # vim: tabstop=4 expandtab shiftwidth=4