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