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