1 #!/usr/bin/env python 2 3 """ 4 A database store of calendar data. 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.stores.common import StoreBase, JournalBase 23 24 from datetime import datetime 25 from imiptools.data import parse_string, to_string 26 from imiptools.dates import format_datetime, get_datetime, to_timezone 27 from imiptools.period import FreeBusyDatabaseCollection 28 from imiptools.sql import DatabaseOperations 29 30 class DatabaseStoreBase(DatabaseOperations): 31 32 "A database store supporting user-specific locking." 33 34 def __init__(self, connection, paramstyle=None): 35 DatabaseOperations.__init__(self, paramstyle=paramstyle) 36 self.connection = connection 37 self.cursor = connection.cursor() 38 39 def acquire_lock(self, user, timeout=None): 40 pass 41 42 def release_lock(self, user): 43 pass 44 45 class DatabaseStore(DatabaseStoreBase, StoreBase): 46 47 "A database store of tabular free/busy data and objects." 48 49 # User discovery. 50 51 def get_users(self): 52 53 "Return a list of users." 54 55 query = "select distinct store_user from freebusy" 56 self.cursor.execute(query) 57 return [r[0] for r in self.cursor.fetchall()] 58 59 # Event and event metadata access. 60 61 def get_events(self, user): 62 63 "Return a list of event identifiers." 64 65 columns = ["store_user", "status"] 66 values = [user, "active"] 67 68 query, values = self.get_query( 69 "select object_uid from objects :condition", 70 columns, values) 71 72 self.cursor.execute(query, values) 73 return [r[0] for r in self.cursor.fetchall()] 74 75 def get_all_events(self, user): 76 77 "Return a set of (uid, recurrenceid) tuples for all events." 78 79 query, values = self.get_query( 80 "select object_uid, null as object_recurrenceid from objects :condition " 81 "union all " 82 "select object_uid, object_recurrenceid from recurrences :condition", 83 ["store_user"], [user]) 84 85 self.cursor.execute(query, values) 86 return self.cursor.fetchall() 87 88 def get_event_table(self, recurrenceid=None, dirname=None): 89 90 "Get the table providing events for any specified 'dirname'." 91 92 if recurrenceid: 93 return self.get_recurrence_table(dirname) 94 else: 95 return self.get_complete_event_table(dirname) 96 97 def get_event_table_filters(self, dirname=None): 98 99 "Get filter details for any specified 'dirname'." 100 101 if dirname == "cancellations": 102 return ["status"], ["cancelled"] 103 else: 104 return ["status"], ["active"] 105 106 def get_event(self, user, uid, recurrenceid=None, dirname=None): 107 108 """ 109 Get the event for the given 'user' with the given 'uid'. If 110 the optional 'recurrenceid' is specified, a specific instance or 111 occurrence of an event is returned. 112 """ 113 114 table = self.get_event_table(recurrenceid, dirname) 115 columns, values = self.get_event_table_filters(dirname) 116 117 if recurrenceid: 118 columns += ["store_user", "object_uid", "object_recurrenceid"] 119 values += [user, uid, recurrenceid] 120 else: 121 columns += ["store_user", "object_uid"] 122 values += [user, uid] 123 124 query, values = self.get_query( 125 "select object_text from %(table)s :condition" % { 126 "table" : table 127 }, 128 columns, values) 129 130 self.cursor.execute(query, values) 131 result = self.cursor.fetchone() 132 return result and parse_string(result[0], "utf-8") 133 134 def get_complete_event_table(self, dirname=None): 135 136 "Get the table providing events for any specified 'dirname'." 137 138 if dirname == "counters": 139 return "countered_objects" 140 else: 141 return "objects" 142 143 def get_complete_event(self, user, uid): 144 145 "Get the event for the given 'user' with the given 'uid'." 146 147 columns = ["store_user", "object_uid"] 148 values = [user, uid] 149 150 query, values = self.get_query( 151 "select object_text from objects :condition", 152 columns, values) 153 154 self.cursor.execute(query, values) 155 result = self.cursor.fetchone() 156 return result and parse_string(result[0], "utf-8") 157 158 def set_complete_event(self, user, uid, node): 159 160 "Set an event for 'user' having the given 'uid' and 'node'." 161 162 columns = ["store_user", "object_uid"] 163 values = [user, uid] 164 setcolumns = ["object_text", "status"] 165 setvalues = [to_string(node, "utf-8"), "active"] 166 167 query, values = self.get_query( 168 "update objects :set :condition", 169 columns, values, setcolumns, setvalues) 170 171 self.cursor.execute(query, values) 172 173 if self.cursor.rowcount > 0 or self.get_complete_event(user, uid): 174 return True 175 176 columns = ["store_user", "object_uid", "object_text", "status"] 177 values = [user, uid, to_string(node, "utf-8"), "active"] 178 179 query, values = self.get_query( 180 "insert into objects (:columns) values (:values)", 181 columns, values) 182 183 self.cursor.execute(query, values) 184 return True 185 186 def remove_parent_event(self, user, uid): 187 188 "Remove the parent event for 'user' having the given 'uid'." 189 190 columns = ["store_user", "object_uid"] 191 values = [user, uid] 192 193 query, values = self.get_query( 194 "delete from objects :condition", 195 columns, values) 196 197 self.cursor.execute(query, values) 198 return self.cursor.rowcount > 0 199 200 def get_active_recurrences(self, user, uid): 201 202 """ 203 Get additional event instances for an event of the given 'user' with the 204 indicated 'uid'. Cancelled recurrences are not returned. 205 """ 206 207 columns = ["store_user", "object_uid", "status"] 208 values = [user, uid, "active"] 209 210 query, values = self.get_query( 211 "select object_recurrenceid from recurrences :condition", 212 columns, values) 213 214 self.cursor.execute(query, values) 215 return [t[0] for t in self.cursor.fetchall() or []] 216 217 def get_cancelled_recurrences(self, user, uid): 218 219 """ 220 Get additional event instances for an event of the given 'user' with the 221 indicated 'uid'. Only cancelled recurrences are returned. 222 """ 223 224 columns = ["store_user", "object_uid", "status"] 225 values = [user, uid, "cancelled"] 226 227 query, values = self.get_query( 228 "select object_recurrenceid from recurrences :condition", 229 columns, values) 230 231 self.cursor.execute(query, values) 232 return [t[0] for t in self.cursor.fetchall() or []] 233 234 def get_recurrence_table(self, dirname=None): 235 236 "Get the table providing recurrences for any specified 'dirname'." 237 238 if dirname == "counters": 239 return "countered_recurrences" 240 else: 241 return "recurrences" 242 243 def get_recurrence(self, user, uid, recurrenceid): 244 245 """ 246 For the event of the given 'user' with the given 'uid', return the 247 specific recurrence indicated by the 'recurrenceid'. 248 """ 249 250 columns = ["store_user", "object_uid", "object_recurrenceid"] 251 values = [user, uid, recurrenceid] 252 253 query, values = self.get_query( 254 "select object_text from recurrences :condition", 255 columns, values) 256 257 self.cursor.execute(query, values) 258 result = self.cursor.fetchone() 259 return result and parse_string(result[0], "utf-8") 260 261 def set_recurrence(self, user, uid, recurrenceid, node): 262 263 "Set an event for 'user' having the given 'uid' and 'node'." 264 265 columns = ["store_user", "object_uid", "object_recurrenceid"] 266 values = [user, uid, recurrenceid] 267 setcolumns = ["object_text", "status"] 268 setvalues = [to_string(node, "utf-8"), "active"] 269 270 query, values = self.get_query( 271 "update recurrences :set :condition", 272 columns, values, setcolumns, setvalues) 273 274 self.cursor.execute(query, values) 275 276 if self.cursor.rowcount > 0 or self.get_recurrence(user, uid, recurrenceid): 277 return True 278 279 columns = ["store_user", "object_uid", "object_recurrenceid", "object_text", "status"] 280 values = [user, uid, recurrenceid, to_string(node, "utf-8"), "active"] 281 282 query, values = self.get_query( 283 "insert into recurrences (:columns) values (:values)", 284 columns, values) 285 286 self.cursor.execute(query, values) 287 return True 288 289 def remove_recurrence(self, user, uid, recurrenceid): 290 291 """ 292 Remove a special recurrence from an event stored by 'user' having the 293 given 'uid' and 'recurrenceid'. 294 """ 295 296 columns = ["store_user", "object_uid", "object_recurrenceid"] 297 values = [user, uid, recurrenceid] 298 299 query, values = self.get_query( 300 "delete from recurrences :condition", 301 columns, values) 302 303 self.cursor.execute(query, values) 304 return True 305 306 def remove_recurrences(self, user, uid): 307 308 """ 309 Remove all recurrences for an event stored by 'user' having the given 310 'uid'. 311 """ 312 313 columns = ["store_user", "object_uid"] 314 values = [user, uid] 315 316 query, values = self.get_query( 317 "delete from recurrences :condition", 318 columns, values) 319 320 self.cursor.execute(query, values) 321 return True 322 323 # Free/busy period providers, upon extension of the free/busy records. 324 325 def _get_freebusy_providers(self, user): 326 327 """ 328 Return the free/busy providers for the given 'user'. 329 330 This function returns any stored datetime and a list of providers as a 331 2-tuple. Each provider is itself a (uid, recurrenceid) tuple. 332 """ 333 334 columns = ["store_user"] 335 values = [user] 336 337 query, values = self.get_query( 338 "select object_uid, object_recurrenceid from freebusy_providers :condition", 339 columns, values) 340 341 self.cursor.execute(query, values) 342 providers = self.cursor.fetchall() 343 344 columns = ["store_user"] 345 values = [user] 346 347 query, values = self.get_query( 348 "select start from freebusy_provider_datetimes :condition", 349 columns, values) 350 351 self.cursor.execute(query, values) 352 result = self.cursor.fetchone() 353 dt_string = result and result[0] 354 355 return dt_string, providers 356 357 def _set_freebusy_providers(self, user, dt_string, t): 358 359 "Set the given provider timestamp 'dt_string' and table 't'." 360 361 # NOTE: Locking? 362 363 columns = ["store_user"] 364 values = [user] 365 366 query, values = self.get_query( 367 "delete from freebusy_providers :condition", 368 columns, values) 369 370 self.cursor.execute(query, values) 371 372 columns = ["store_user", "object_uid", "object_recurrenceid"] 373 374 for uid, recurrenceid in t: 375 values = [user, uid, recurrenceid] 376 377 query, values = self.get_query( 378 "insert into freebusy_providers (:columns) values (:values)", 379 columns, values) 380 381 self.cursor.execute(query, values) 382 383 columns = ["store_user"] 384 values = [user] 385 setcolumns = ["start"] 386 setvalues = [dt_string] 387 388 query, values = self.get_query( 389 "update freebusy_provider_datetimes :set :condition", 390 columns, values, setcolumns, setvalues) 391 392 self.cursor.execute(query, values) 393 394 if self.cursor.rowcount > 0: 395 return True 396 397 columns = ["store_user", "start"] 398 values = [user, dt_string] 399 400 query, values = self.get_query( 401 "insert into freebusy_provider_datetimes (:columns) values (:values)", 402 columns, values) 403 404 self.cursor.execute(query, values) 405 return True 406 407 # Free/busy period access. 408 409 def get_freebusy(self, user, name=None, mutable=False): 410 411 "Get free/busy details for the given 'user'." 412 413 table = name or "freebusy" 414 return FreeBusyDatabaseCollection(self.cursor, table, ["store_user"], [user], mutable, self.paramstyle) 415 416 def get_freebusy_for_other(self, user, other, mutable=False): 417 418 "For the given 'user', get free/busy details for the 'other' user." 419 420 table = "freebusy_other" 421 return FreeBusyDatabaseCollection(self.cursor, table, ["store_user", "other"], [user, other], mutable, self.paramstyle) 422 423 def set_freebusy(self, user, freebusy, name=None): 424 425 "For the given 'user', set 'freebusy' details." 426 427 table = name or "freebusy" 428 429 if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table: 430 fbc = FreeBusyDatabaseCollection(self.cursor, table, ["store_user"], [user], True, self.paramstyle) 431 fbc += freebusy 432 433 return True 434 435 def set_freebusy_for_other(self, user, freebusy, other): 436 437 "For the given 'user', set 'freebusy' details for the 'other' user." 438 439 table = "freebusy_other" 440 441 if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table: 442 fbc = FreeBusyDatabaseCollection(self.cursor, table, ["store_user", "other"], [user, other], True, self.paramstyle) 443 fbc += freebusy 444 445 return True 446 447 # Tentative free/busy periods related to countering. 448 449 def get_freebusy_offers(self, user, mutable=False): 450 451 "Get free/busy offers for the given 'user'." 452 453 # Expire old offers and save the collection if modified. 454 455 now = format_datetime(to_timezone(datetime.utcnow(), "UTC")) 456 columns = ["store_user", "expires"] 457 values = [user, now] 458 459 query, values = self.get_query( 460 "delete from freebusy_offers :condition", 461 columns, values) 462 463 self.cursor.execute(query, values) 464 465 return self.get_freebusy(user, "freebusy_offers", mutable) 466 467 def set_freebusy_offers(self, user, freebusy): 468 469 "For the given 'user', set 'freebusy' offers." 470 471 return self.set_freebusy(user, freebusy, "freebusy_offers") 472 473 # Requests and counter-proposals. 474 475 def get_requests(self, user): 476 477 "Get requests for the given 'user'." 478 479 columns = ["store_user"] 480 values = [user] 481 482 query, values = self.get_query( 483 "select object_uid, object_recurrenceid, request_type from requests :condition", 484 columns, values) 485 486 self.cursor.execute(query, values) 487 return self.cursor.fetchall() 488 489 def set_request(self, user, uid, recurrenceid=None, type=None): 490 491 """ 492 For the given 'user', set the queued 'uid' and 'recurrenceid', 493 indicating a request, along with any given 'type'. 494 """ 495 496 columns = ["store_user", "object_uid", "object_recurrenceid", "request_type"] 497 values = [user, uid, recurrenceid, type] 498 499 query, values = self.get_query( 500 "insert into requests (:columns) values (:values)", 501 columns, values) 502 503 self.cursor.execute(query, values) 504 return True 505 506 def queue_request(self, user, uid, recurrenceid=None, type=None): 507 508 """ 509 Queue a request for 'user' having the given 'uid'. If the optional 510 'recurrenceid' is specified, the entry refers to a specific instance 511 or occurrence of an event. The 'type' parameter can be used to indicate 512 a specific type of request. 513 """ 514 515 if recurrenceid: 516 columns = ["store_user", "object_uid", "object_recurrenceid"] 517 values = [user, uid, recurrenceid] 518 else: 519 columns = ["store_user", "object_uid"] 520 values = [user, uid] 521 522 setcolumns = ["request_type"] 523 setvalues = [type] 524 525 query, values = self.get_query( 526 "update requests :set :condition", 527 columns, values, setcolumns, setvalues) 528 529 self.cursor.execute(query, values) 530 531 if self.cursor.rowcount > 0: 532 return 533 534 self.set_request(user, uid, recurrenceid, type) 535 536 def dequeue_request(self, user, uid, recurrenceid=None): 537 538 """ 539 Dequeue all requests for 'user' having the given 'uid'. If the optional 540 'recurrenceid' is specified, all requests for that specific instance or 541 occurrence of an event are dequeued. 542 """ 543 544 if recurrenceid: 545 columns = ["store_user", "object_uid", "object_recurrenceid"] 546 values = [user, uid, recurrenceid] 547 else: 548 columns = ["store_user", "object_uid"] 549 values = [user, uid] 550 551 query, values = self.get_query( 552 "delete from requests :condition", 553 columns, values) 554 555 self.cursor.execute(query, values) 556 return True 557 558 def get_counters(self, user, uid, recurrenceid=None): 559 560 """ 561 For the given 'user', return a list of users from whom counter-proposals 562 have been received for the given 'uid' and optional 'recurrenceid'. 563 """ 564 565 table = self.get_event_table(recurrenceid, "counters") 566 567 if recurrenceid: 568 columns = ["store_user", "object_uid", "object_recurrenceid"] 569 values = [user, uid, recurrenceid] 570 else: 571 columns = ["store_user", "object_uid"] 572 values = [user, uid] 573 574 query, values = self.get_query( 575 "select other from %(table)s :condition" % { 576 "table" : table 577 }, 578 columns, values) 579 580 self.cursor.execute(query, values) 581 return self.cursor.fetchall() 582 583 def get_counter(self, user, other, uid, recurrenceid=None): 584 585 """ 586 For the given 'user', return the counter-proposal from 'other' for the 587 given 'uid' and optional 'recurrenceid'. 588 """ 589 590 table = self.get_event_table(recurrenceid, "counters") 591 592 if recurrenceid: 593 columns = ["store_user", "other", "object_uid", "object_recurrenceid"] 594 values = [user, other, uid, recurrenceid] 595 else: 596 columns = ["store_user", "other", "object_uid"] 597 values = [user, other, uid] 598 599 query, values = self.get_query( 600 "select object_text from %(table)s :condition" % { 601 "table" : table 602 }, 603 columns, values) 604 605 self.cursor.execute(query, values) 606 result = self.cursor.fetchone() 607 return result and parse_string(result[0], "utf-8") 608 609 def set_counter(self, user, other, node, uid, recurrenceid=None): 610 611 """ 612 For the given 'user', store a counter-proposal received from 'other' the 613 given 'node' representing that proposal for the given 'uid' and 614 'recurrenceid'. 615 """ 616 617 table = self.get_event_table(recurrenceid, "counters") 618 619 if recurrenceid: 620 columns = ["store_user", "other", "object_uid", "object_recurrenceid", "object_text"] 621 values = [user, other, uid, recurrenceid, to_string(node, "utf-8")] 622 else: 623 columns = ["store_user", "other", "object_uid", "object_text"] 624 values = [user, other, uid, to_string(node, "utf-8")] 625 626 query, values = self.get_query( 627 "insert into %(table)s (:columns) values (:values)" % { 628 "table" : table 629 }, 630 columns, values) 631 632 self.cursor.execute(query, values) 633 return True 634 635 def remove_counters(self, user, uid, recurrenceid=None): 636 637 """ 638 For the given 'user', remove all counter-proposals associated with the 639 given 'uid' and 'recurrenceid'. 640 """ 641 642 table = self.get_event_table(recurrenceid, "counters") 643 644 if recurrenceid: 645 columns = ["store_user", "object_uid", "object_recurrenceid"] 646 values = [user, uid, recurrenceid] 647 else: 648 columns = ["store_user", "object_uid"] 649 values = [user, uid] 650 651 query, values = self.get_query( 652 "delete from %(table)s :condition" % { 653 "table" : table 654 }, 655 columns, values) 656 657 self.cursor.execute(query, values) 658 return True 659 660 def remove_counter(self, user, other, uid, recurrenceid=None): 661 662 """ 663 For the given 'user', remove any counter-proposal from 'other' 664 associated with the given 'uid' and 'recurrenceid'. 665 """ 666 667 table = self.get_event_table(recurrenceid, "counters") 668 669 if recurrenceid: 670 columns = ["store_user", "other", "object_uid", "object_recurrenceid"] 671 values = [user, other, uid, recurrenceid] 672 else: 673 columns = ["store_user", "other", "object_uid"] 674 values = [user, other, uid] 675 676 query, values = self.get_query( 677 "delete from %(table)s :condition" % { 678 "table" : table 679 }, 680 columns, values) 681 682 self.cursor.execute(query, values) 683 return True 684 685 # Event cancellation. 686 687 def cancel_event(self, user, uid, recurrenceid=None): 688 689 """ 690 Cancel an event for 'user' having the given 'uid'. If the optional 691 'recurrenceid' is specified, a specific instance or occurrence of an 692 event is cancelled. 693 """ 694 695 table = self.get_event_table(recurrenceid) 696 697 if recurrenceid: 698 columns = ["store_user", "object_uid", "object_recurrenceid"] 699 values = [user, uid, recurrenceid] 700 else: 701 columns = ["store_user", "object_uid"] 702 values = [user, uid] 703 704 setcolumns = ["status"] 705 setvalues = ["cancelled"] 706 707 query, values = self.get_query( 708 "update %(table)s :set :condition" % { 709 "table" : table 710 }, 711 columns, values, setcolumns, setvalues) 712 713 self.cursor.execute(query, values) 714 return True 715 716 def uncancel_event(self, user, uid, recurrenceid=None): 717 718 """ 719 Uncancel an event for 'user' having the given 'uid'. If the optional 720 'recurrenceid' is specified, a specific instance or occurrence of an 721 event is uncancelled. 722 """ 723 724 table = self.get_event_table(recurrenceid) 725 726 if recurrenceid: 727 columns = ["store_user", "object_uid", "object_recurrenceid"] 728 values = [user, uid, recurrenceid] 729 else: 730 columns = ["store_user", "object_uid"] 731 values = [user, uid] 732 733 setcolumns = ["status"] 734 setvalues = ["active"] 735 736 query, values = self.get_query( 737 "update %(table)s :set :condition" % { 738 "table" : table 739 }, 740 columns, values, setcolumns, setvalues) 741 742 self.cursor.execute(query, values) 743 return True 744 745 def remove_cancellation(self, user, uid, recurrenceid=None): 746 747 """ 748 Remove a cancellation for 'user' for the event having the given 'uid'. 749 If the optional 'recurrenceid' is specified, a specific instance or 750 occurrence of an event is affected. 751 """ 752 753 table = self.get_event_table(recurrenceid) 754 755 if recurrenceid: 756 columns = ["store_user", "object_uid", "object_recurrenceid", "status"] 757 values = [user, uid, recurrenceid, "cancelled"] 758 else: 759 columns = ["store_user", "object_uid", "status"] 760 values = [user, uid, "cancelled"] 761 762 query, values = self.get_query( 763 "delete from %(table)s :condition" % { 764 "table" : table 765 }, 766 columns, values) 767 768 self.cursor.execute(query, values) 769 return True 770 771 class DatabaseJournal(DatabaseStoreBase, JournalBase): 772 773 "A journal system to support quotas." 774 775 # Quota and user identity/group discovery. 776 777 def get_quotas(self): 778 779 "Return a list of quotas." 780 781 query = "select distinct journal_quota from quota_freebusy" 782 self.cursor.execute(query) 783 return [r[0] for r in self.cursor.fetchall()] 784 785 def get_quota_users(self, quota): 786 787 "Return a list of quota users." 788 789 columns = ["quota"] 790 values = [quota] 791 792 query, values = self.get_query( 793 "select distinct user_group from quota_freebusy :condition", 794 columns, values) 795 796 self.cursor.execute(query) 797 return [r[0] for r in self.cursor.fetchall()] 798 799 # Groups of users sharing quotas. 800 801 def get_groups(self, quota): 802 803 "Return the identity mappings for the given 'quota' as a dictionary." 804 805 columns = ["quota"] 806 values = [quota] 807 808 query, values = self.get_query( 809 "select store_user, user_group from user_groups :condition", 810 columns, values) 811 812 self.cursor.execute(query, values) 813 return dict(self.cursor.fetchall()) 814 815 def get_limits(self, quota): 816 817 """ 818 Return the limits for the 'quota' as a dictionary mapping identities or 819 groups to durations. 820 """ 821 822 columns = ["quota"] 823 values = [quota] 824 825 query, values = self.get_query( 826 "select user_group, quota_limit from quota_limits :condition", 827 columns, values) 828 829 self.cursor.execute(query, values) 830 return dict(self.cursor.fetchall()) 831 832 def set_limit(self, quota, group, limit): 833 834 """ 835 For the given 'quota', set for a user 'group' the given 'limit' on 836 resource usage. 837 """ 838 839 columns = ["quota", "user_group"] 840 values = [quota, group] 841 setcolumns = ["quota_limit"] 842 setvalues = [limit] 843 844 query, values = self.get_query( 845 "update quota_limits :set :condition", 846 columns, values, setcolumns, setvalues) 847 848 self.cursor.execute(query, values) 849 850 if self.cursor.rowcount > 0: 851 return True 852 853 columns = ["quota", "user_group", "quota_limit"] 854 values = [quota, group, limit] 855 856 query, values = self.get_query( 857 "insert into quota_limits (:columns) values (:values)", 858 columns, values) 859 860 self.cursor.execute(query, values) 861 return True 862 863 # Free/busy period access for users within quota groups. 864 865 def get_freebusy(self, quota, user, mutable=False): 866 867 "Get free/busy details for the given 'quota' and 'user'." 868 869 table = "user_freebusy" 870 return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], mutable, self.paramstyle) 871 872 def set_freebusy(self, quota, user, freebusy): 873 874 "For the given 'quota' and 'user', set 'freebusy' details." 875 876 table = "user_freebusy" 877 878 if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table: 879 fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], True, self.paramstyle) 880 fbc += freebusy 881 882 return True 883 884 # Journal entry methods. 885 886 def get_entries(self, quota, group, mutable=False): 887 888 """ 889 Return a list of journal entries for the given 'quota' for the indicated 890 'group'. 891 """ 892 893 table = "quota_freebusy" 894 return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], mutable, self.paramstyle) 895 896 def set_entries(self, quota, group, entries): 897 898 """ 899 For the given 'quota' and indicated 'group', set the list of journal 900 'entries'. 901 """ 902 903 table = "quota_freebusy" 904 905 if not isinstance(entries, FreeBusyDatabaseCollection) or entries.table_name != table: 906 fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], True, self.paramstyle) 907 fbc += entries 908 909 return True 910 911 # vim: tabstop=4 expandtab shiftwidth=4