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