1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/imiptools/stores/database/common.py Thu Mar 10 01:43:31 2016 +0100
1.3 @@ -0,0 +1,880 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +A database store of calendar data.
1.8 +
1.9 +Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This program is free software; you can redistribute it and/or modify it under
1.12 +the terms of the GNU General Public License as published by the Free Software
1.13 +Foundation; either version 3 of the License, or (at your option) any later
1.14 +version.
1.15 +
1.16 +This program is distributed in the hope that it will be useful, but WITHOUT
1.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 +details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along with
1.22 +this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +"""
1.24 +
1.25 +from imiptools.stores.common import StoreBase, JournalBase
1.26 +
1.27 +from datetime import datetime
1.28 +from imiptools.data import parse_string, to_string
1.29 +from imiptools.dates import format_datetime, get_datetime, to_timezone
1.30 +from imiptools.period import FreeBusyDatabaseCollection
1.31 +from imiptools.sql import DatabaseOperations
1.32 +
1.33 +class DatabaseStoreBase(DatabaseOperations):
1.34 +
1.35 + "A database store supporting user-specific locking."
1.36 +
1.37 + def __init__(self, connection, paramstyle=None):
1.38 + DatabaseOperations.__init__(self, paramstyle=paramstyle)
1.39 + self.connection = connection
1.40 + self.cursor = connection.cursor()
1.41 +
1.42 + def acquire_lock(self, user, timeout=None):
1.43 + pass
1.44 +
1.45 + def release_lock(self, user):
1.46 + pass
1.47 +
1.48 +class DatabaseStore(DatabaseStoreBase, StoreBase):
1.49 +
1.50 + "A database store of tabular free/busy data and objects."
1.51 +
1.52 + # User discovery.
1.53 +
1.54 + def get_users(self):
1.55 +
1.56 + "Return a list of users."
1.57 +
1.58 + query = "select distinct store_user from freebusy"
1.59 + self.cursor.execute(query)
1.60 + return [r[0] for r in self.cursor.fetchall()]
1.61 +
1.62 + # Event and event metadata access.
1.63 +
1.64 + def get_events(self, user):
1.65 +
1.66 + "Return a list of event identifiers."
1.67 +
1.68 + columns = ["store_user", "status"]
1.69 + values = [user, "active"]
1.70 +
1.71 + query, values = self.get_query(
1.72 + "select object_uid from objects :condition",
1.73 + columns, values)
1.74 +
1.75 + self.cursor.execute(query, values)
1.76 + return [r[0] for r in self.cursor.fetchall()]
1.77 +
1.78 + def get_all_events(self, user):
1.79 +
1.80 + "Return a set of (uid, recurrenceid) tuples for all events."
1.81 +
1.82 + query, values = self.get_query(
1.83 + "select object_uid, null as object_recurrenceid from objects :condition "
1.84 + "union all "
1.85 + "select object_uid, object_recurrenceid from recurrences :condition",
1.86 + ["store_user"], [user])
1.87 +
1.88 + self.cursor.execute(query, values)
1.89 + return self.cursor.fetchall()
1.90 +
1.91 + def get_event_table(self, recurrenceid=None, dirname=None):
1.92 +
1.93 + "Get the table providing events for any specified 'dirname'."
1.94 +
1.95 + if recurrenceid:
1.96 + return self.get_recurrence_table(dirname)
1.97 + else:
1.98 + return self.get_complete_event_table(dirname)
1.99 +
1.100 + def get_event_table_filters(self, dirname=None):
1.101 +
1.102 + "Get filter details for any specified 'dirname'."
1.103 +
1.104 + if dirname == "cancellations":
1.105 + return ["status"], ["cancelled"]
1.106 + else:
1.107 + return ["status"], ["active"]
1.108 +
1.109 + def get_event(self, user, uid, recurrenceid=None, dirname=None):
1.110 +
1.111 + """
1.112 + Get the event for the given 'user' with the given 'uid'. If
1.113 + the optional 'recurrenceid' is specified, a specific instance or
1.114 + occurrence of an event is returned.
1.115 + """
1.116 +
1.117 + table = self.get_event_table(recurrenceid, dirname)
1.118 + columns, values = self.get_event_table_filters(dirname)
1.119 +
1.120 + if recurrenceid:
1.121 + columns += ["store_user", "object_uid", "object_recurrenceid"]
1.122 + values += [user, uid, recurrenceid]
1.123 + else:
1.124 + columns += ["store_user", "object_uid"]
1.125 + values += [user, uid]
1.126 +
1.127 + query, values = self.get_query(
1.128 + "select object_text from %(table)s :condition" % {
1.129 + "table" : table
1.130 + },
1.131 + columns, values)
1.132 +
1.133 + self.cursor.execute(query, values)
1.134 + result = self.cursor.fetchone()
1.135 + return result and parse_string(result[0], "utf-8")
1.136 +
1.137 + def get_complete_event_table(self, dirname=None):
1.138 +
1.139 + "Get the table providing events for any specified 'dirname'."
1.140 +
1.141 + if dirname == "counters":
1.142 + return "countered_objects"
1.143 + else:
1.144 + return "objects"
1.145 +
1.146 + def get_complete_event(self, user, uid):
1.147 +
1.148 + "Get the event for the given 'user' with the given 'uid'."
1.149 +
1.150 + columns = ["store_user", "object_uid"]
1.151 + values = [user, uid]
1.152 +
1.153 + query, values = self.get_query(
1.154 + "select object_text from objects :condition",
1.155 + columns, values)
1.156 +
1.157 + self.cursor.execute(query, values)
1.158 + result = self.cursor.fetchone()
1.159 + return result and parse_string(result[0], "utf-8")
1.160 +
1.161 + def set_complete_event(self, user, uid, node):
1.162 +
1.163 + "Set an event for 'user' having the given 'uid' and 'node'."
1.164 +
1.165 + columns = ["store_user", "object_uid"]
1.166 + values = [user, uid]
1.167 + setcolumns = ["object_text", "status"]
1.168 + setvalues = [to_string(node, "utf-8"), "active"]
1.169 +
1.170 + query, values = self.get_query(
1.171 + "update objects :set :condition",
1.172 + columns, values, setcolumns, setvalues)
1.173 +
1.174 + self.cursor.execute(query, values)
1.175 +
1.176 + if self.cursor.rowcount > 0 or self.get_complete_event(user, uid):
1.177 + return True
1.178 +
1.179 + columns = ["store_user", "object_uid", "object_text", "status"]
1.180 + values = [user, uid, to_string(node, "utf-8"), "active"]
1.181 +
1.182 + query, values = self.get_query(
1.183 + "insert into objects (:columns) values (:values)",
1.184 + columns, values)
1.185 +
1.186 + self.cursor.execute(query, values)
1.187 + return True
1.188 +
1.189 + def remove_parent_event(self, user, uid):
1.190 +
1.191 + "Remove the parent event for 'user' having the given 'uid'."
1.192 +
1.193 + columns = ["store_user", "object_uid"]
1.194 + values = [user, uid]
1.195 +
1.196 + query, values = self.get_query(
1.197 + "delete from objects :condition",
1.198 + columns, values)
1.199 +
1.200 + self.cursor.execute(query, values)
1.201 + return self.cursor.rowcount > 0
1.202 +
1.203 + def get_active_recurrences(self, user, uid):
1.204 +
1.205 + """
1.206 + Get additional event instances for an event of the given 'user' with the
1.207 + indicated 'uid'. Cancelled recurrences are not returned.
1.208 + """
1.209 +
1.210 + columns = ["store_user", "object_uid", "status"]
1.211 + values = [user, uid, "active"]
1.212 +
1.213 + query, values = self.get_query(
1.214 + "select object_recurrenceid from recurrences :condition",
1.215 + columns, values)
1.216 +
1.217 + self.cursor.execute(query, values)
1.218 + return [t[0] for t in self.cursor.fetchall() or []]
1.219 +
1.220 + def get_cancelled_recurrences(self, user, uid):
1.221 +
1.222 + """
1.223 + Get additional event instances for an event of the given 'user' with the
1.224 + indicated 'uid'. Only cancelled recurrences are returned.
1.225 + """
1.226 +
1.227 + columns = ["store_user", "object_uid", "status"]
1.228 + values = [user, uid, "cancelled"]
1.229 +
1.230 + query, values = self.get_query(
1.231 + "select object_recurrenceid from recurrences :condition",
1.232 + columns, values)
1.233 +
1.234 + self.cursor.execute(query, values)
1.235 + return [t[0] for t in self.cursor.fetchall() or []]
1.236 +
1.237 + def get_recurrence_table(self, dirname=None):
1.238 +
1.239 + "Get the table providing recurrences for any specified 'dirname'."
1.240 +
1.241 + if dirname == "counters":
1.242 + return "countered_recurrences"
1.243 + else:
1.244 + return "recurrences"
1.245 +
1.246 + def get_recurrence(self, user, uid, recurrenceid):
1.247 +
1.248 + """
1.249 + For the event of the given 'user' with the given 'uid', return the
1.250 + specific recurrence indicated by the 'recurrenceid'.
1.251 + """
1.252 +
1.253 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.254 + values = [user, uid, recurrenceid]
1.255 +
1.256 + query, values = self.get_query(
1.257 + "select object_text from recurrences :condition",
1.258 + columns, values)
1.259 +
1.260 + self.cursor.execute(query, values)
1.261 + result = self.cursor.fetchone()
1.262 + return result and parse_string(result[0], "utf-8")
1.263 +
1.264 + def set_recurrence(self, user, uid, recurrenceid, node):
1.265 +
1.266 + "Set an event for 'user' having the given 'uid' and 'node'."
1.267 +
1.268 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.269 + values = [user, uid, recurrenceid]
1.270 + setcolumns = ["object_text", "status"]
1.271 + setvalues = [to_string(node, "utf-8"), "active"]
1.272 +
1.273 + query, values = self.get_query(
1.274 + "update recurrences :set :condition",
1.275 + columns, values, setcolumns, setvalues)
1.276 +
1.277 + self.cursor.execute(query, values)
1.278 +
1.279 + if self.cursor.rowcount > 0 or self.get_recurrence(user, uid, recurrenceid):
1.280 + return True
1.281 +
1.282 + columns = ["store_user", "object_uid", "object_recurrenceid", "object_text", "status"]
1.283 + values = [user, uid, recurrenceid, to_string(node, "utf-8"), "active"]
1.284 +
1.285 + query, values = self.get_query(
1.286 + "insert into recurrences (:columns) values (:values)",
1.287 + columns, values)
1.288 +
1.289 + self.cursor.execute(query, values)
1.290 + return True
1.291 +
1.292 + def remove_recurrence(self, user, uid, recurrenceid):
1.293 +
1.294 + """
1.295 + Remove a special recurrence from an event stored by 'user' having the
1.296 + given 'uid' and 'recurrenceid'.
1.297 + """
1.298 +
1.299 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.300 + values = [user, uid, recurrenceid]
1.301 +
1.302 + query, values = self.get_query(
1.303 + "delete from recurrences :condition",
1.304 + columns, values)
1.305 +
1.306 + self.cursor.execute(query, values)
1.307 + return True
1.308 +
1.309 + def remove_recurrences(self, user, uid):
1.310 +
1.311 + """
1.312 + Remove all recurrences for an event stored by 'user' having the given
1.313 + 'uid'.
1.314 + """
1.315 +
1.316 + columns = ["store_user", "object_uid"]
1.317 + values = [user, uid]
1.318 +
1.319 + query, values = self.get_query(
1.320 + "delete from recurrences :condition",
1.321 + columns, values)
1.322 +
1.323 + self.cursor.execute(query, values)
1.324 + return True
1.325 +
1.326 + # Free/busy period providers, upon extension of the free/busy records.
1.327 +
1.328 + def _get_freebusy_providers(self, user):
1.329 +
1.330 + """
1.331 + Return the free/busy providers for the given 'user'.
1.332 +
1.333 + This function returns any stored datetime and a list of providers as a
1.334 + 2-tuple. Each provider is itself a (uid, recurrenceid) tuple.
1.335 + """
1.336 +
1.337 + columns = ["store_user"]
1.338 + values = [user]
1.339 +
1.340 + query, values = self.get_query(
1.341 + "select object_uid, object_recurrenceid from freebusy_providers :condition",
1.342 + columns, values)
1.343 +
1.344 + self.cursor.execute(query, values)
1.345 + providers = self.cursor.fetchall()
1.346 +
1.347 + columns = ["store_user"]
1.348 + values = [user]
1.349 +
1.350 + query, values = self.get_query(
1.351 + "select start from freebusy_provider_datetimes :condition",
1.352 + columns, values)
1.353 +
1.354 + self.cursor.execute(query, values)
1.355 + result = self.cursor.fetchone()
1.356 + dt_string = result and result[0]
1.357 +
1.358 + return dt_string, providers
1.359 +
1.360 + def _set_freebusy_providers(self, user, dt_string, t):
1.361 +
1.362 + "Set the given provider timestamp 'dt_string' and table 't'."
1.363 +
1.364 + # NOTE: Locking?
1.365 +
1.366 + columns = ["store_user"]
1.367 + values = [user]
1.368 +
1.369 + query, values = self.get_query(
1.370 + "delete from freebusy_providers :condition",
1.371 + columns, values)
1.372 +
1.373 + self.cursor.execute(query, values)
1.374 +
1.375 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.376 +
1.377 + for uid, recurrenceid in t:
1.378 + values = [user, uid, recurrenceid]
1.379 +
1.380 + query, values = self.get_query(
1.381 + "insert into freebusy_providers (:columns) values (:values)",
1.382 + columns, values)
1.383 +
1.384 + self.cursor.execute(query, values)
1.385 +
1.386 + columns = ["store_user"]
1.387 + values = [user]
1.388 + setcolumns = ["start"]
1.389 + setvalues = [dt_string]
1.390 +
1.391 + query, values = self.get_query(
1.392 + "update freebusy_provider_datetimes :set :condition",
1.393 + columns, values, setcolumns, setvalues)
1.394 +
1.395 + self.cursor.execute(query, values)
1.396 +
1.397 + if self.cursor.rowcount > 0:
1.398 + return True
1.399 +
1.400 + columns = ["store_user", "start"]
1.401 + values = [user, dt_string]
1.402 +
1.403 + query, values = self.get_query(
1.404 + "insert into freebusy_provider_datetimes (:columns) values (:values)",
1.405 + columns, values)
1.406 +
1.407 + self.cursor.execute(query, values)
1.408 + return True
1.409 +
1.410 + # Free/busy period access.
1.411 +
1.412 + def get_freebusy(self, user, name=None, mutable=False):
1.413 +
1.414 + "Get free/busy details for the given 'user'."
1.415 +
1.416 + table = name or "freebusy"
1.417 + return FreeBusyDatabaseCollection(self.cursor, table, ["store_user"], [user], mutable, self.paramstyle)
1.418 +
1.419 + def get_freebusy_for_other(self, user, other, mutable=False):
1.420 +
1.421 + "For the given 'user', get free/busy details for the 'other' user."
1.422 +
1.423 + table = "freebusy_other"
1.424 + return FreeBusyDatabaseCollection(self.cursor, table, ["store_user", "other"], [user, other], mutable, self.paramstyle)
1.425 +
1.426 + def set_freebusy(self, user, freebusy, name=None):
1.427 +
1.428 + "For the given 'user', set 'freebusy' details."
1.429 +
1.430 + table = name or "freebusy"
1.431 +
1.432 + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table:
1.433 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["store_user"], [user], True, self.paramstyle)
1.434 + fbc += freebusy
1.435 +
1.436 + return True
1.437 +
1.438 + def set_freebusy_for_other(self, user, freebusy, other):
1.439 +
1.440 + "For the given 'user', set 'freebusy' details for the 'other' user."
1.441 +
1.442 + table = "freebusy_other"
1.443 +
1.444 + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table:
1.445 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["store_user", "other"], [user, other], True, self.paramstyle)
1.446 + fbc += freebusy
1.447 +
1.448 + return True
1.449 +
1.450 + # Tentative free/busy periods related to countering.
1.451 +
1.452 + def get_freebusy_offers(self, user, mutable=False):
1.453 +
1.454 + "Get free/busy offers for the given 'user'."
1.455 +
1.456 + # Expire old offers and save the collection if modified.
1.457 +
1.458 + now = format_datetime(to_timezone(datetime.utcnow(), "UTC"))
1.459 + columns = ["store_user", "expires"]
1.460 + values = [user, now]
1.461 +
1.462 + query, values = self.get_query(
1.463 + "delete from freebusy_offers :condition",
1.464 + columns, values)
1.465 +
1.466 + self.cursor.execute(query, values)
1.467 +
1.468 + return self.get_freebusy(user, "freebusy_offers", mutable)
1.469 +
1.470 + def set_freebusy_offers(self, user, freebusy):
1.471 +
1.472 + "For the given 'user', set 'freebusy' offers."
1.473 +
1.474 + return self.set_freebusy(user, freebusy, "freebusy_offers")
1.475 +
1.476 + # Requests and counter-proposals.
1.477 +
1.478 + def get_requests(self, user):
1.479 +
1.480 + "Get requests for the given 'user'."
1.481 +
1.482 + columns = ["store_user"]
1.483 + values = [user]
1.484 +
1.485 + query, values = self.get_query(
1.486 + "select object_uid, object_recurrenceid, request_type from requests :condition",
1.487 + columns, values)
1.488 +
1.489 + self.cursor.execute(query, values)
1.490 + return self.cursor.fetchall()
1.491 +
1.492 + def set_request(self, user, uid, recurrenceid=None, type=None):
1.493 +
1.494 + """
1.495 + For the given 'user', set the queued 'uid' and 'recurrenceid',
1.496 + indicating a request, along with any given 'type'.
1.497 + """
1.498 +
1.499 + columns = ["store_user", "object_uid", "object_recurrenceid", "request_type"]
1.500 + values = [user, uid, recurrenceid, type]
1.501 +
1.502 + query, values = self.get_query(
1.503 + "insert into requests (:columns) values (:values)",
1.504 + columns, values)
1.505 +
1.506 + self.cursor.execute(query, values)
1.507 + return True
1.508 +
1.509 + def queue_request(self, user, uid, recurrenceid=None, type=None):
1.510 +
1.511 + """
1.512 + Queue a request for 'user' having the given 'uid'. If the optional
1.513 + 'recurrenceid' is specified, the entry refers to a specific instance
1.514 + or occurrence of an event. The 'type' parameter can be used to indicate
1.515 + a specific type of request.
1.516 + """
1.517 +
1.518 + if recurrenceid:
1.519 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.520 + values = [user, uid, recurrenceid]
1.521 + else:
1.522 + columns = ["store_user", "object_uid"]
1.523 + values = [user, uid]
1.524 +
1.525 + setcolumns = ["request_type"]
1.526 + setvalues = [type]
1.527 +
1.528 + query, values = self.get_query(
1.529 + "update requests :set :condition",
1.530 + columns, values, setcolumns, setvalues)
1.531 +
1.532 + self.cursor.execute(query, values)
1.533 +
1.534 + if self.cursor.rowcount > 0:
1.535 + return
1.536 +
1.537 + self.set_request(user, uid, recurrenceid, type)
1.538 +
1.539 + def dequeue_request(self, user, uid, recurrenceid=None):
1.540 +
1.541 + """
1.542 + Dequeue all requests for 'user' having the given 'uid'. If the optional
1.543 + 'recurrenceid' is specified, all requests for that specific instance or
1.544 + occurrence of an event are dequeued.
1.545 + """
1.546 +
1.547 + if recurrenceid:
1.548 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.549 + values = [user, uid, recurrenceid]
1.550 + else:
1.551 + columns = ["store_user", "object_uid"]
1.552 + values = [user, uid]
1.553 +
1.554 + query, values = self.get_query(
1.555 + "delete from requests :condition",
1.556 + columns, values)
1.557 +
1.558 + self.cursor.execute(query, values)
1.559 + return True
1.560 +
1.561 + def get_counters(self, user, uid, recurrenceid=None):
1.562 +
1.563 + """
1.564 + For the given 'user', return a list of users from whom counter-proposals
1.565 + have been received for the given 'uid' and optional 'recurrenceid'.
1.566 + """
1.567 +
1.568 + table = self.get_event_table(recurrenceid, "counters")
1.569 +
1.570 + if recurrenceid:
1.571 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.572 + values = [user, uid, recurrenceid]
1.573 + else:
1.574 + columns = ["store_user", "object_uid"]
1.575 + values = [user, uid]
1.576 +
1.577 + query, values = self.get_query(
1.578 + "select other from %(table)s :condition" % {
1.579 + "table" : table
1.580 + },
1.581 + columns, values)
1.582 +
1.583 + self.cursor.execute(query, values)
1.584 + return self.cursor.fetchall()
1.585 +
1.586 + def get_counter(self, user, other, uid, recurrenceid=None):
1.587 +
1.588 + """
1.589 + For the given 'user', return the counter-proposal from 'other' for the
1.590 + given 'uid' and optional 'recurrenceid'.
1.591 + """
1.592 +
1.593 + table = self.get_event_table(recurrenceid, "counters")
1.594 +
1.595 + if recurrenceid:
1.596 + columns = ["store_user", "other", "object_uid", "object_recurrenceid"]
1.597 + values = [user, other, uid, recurrenceid]
1.598 + else:
1.599 + columns = ["store_user", "other", "object_uid"]
1.600 + values = [user, other, uid]
1.601 +
1.602 + query, values = self.get_query(
1.603 + "select object_text from %(table)s :condition" % {
1.604 + "table" : table
1.605 + },
1.606 + columns, values)
1.607 +
1.608 + self.cursor.execute(query, values)
1.609 + result = self.cursor.fetchone()
1.610 + return result and parse_string(result[0], "utf-8")
1.611 +
1.612 + def set_counter(self, user, other, node, uid, recurrenceid=None):
1.613 +
1.614 + """
1.615 + For the given 'user', store a counter-proposal received from 'other' the
1.616 + given 'node' representing that proposal for the given 'uid' and
1.617 + 'recurrenceid'.
1.618 + """
1.619 +
1.620 + table = self.get_event_table(recurrenceid, "counters")
1.621 +
1.622 + if recurrenceid:
1.623 + columns = ["store_user", "other", "object_uid", "object_recurrenceid", "object_text"]
1.624 + values = [user, other, uid, recurrenceid, to_string(node, "utf-8")]
1.625 + else:
1.626 + columns = ["store_user", "other", "object_uid", "object_text"]
1.627 + values = [user, other, uid, to_string(node, "utf-8")]
1.628 +
1.629 + query, values = self.get_query(
1.630 + "insert into %(table)s (:columns) values (:values)" % {
1.631 + "table" : table
1.632 + },
1.633 + columns, values)
1.634 +
1.635 + self.cursor.execute(query, values)
1.636 + return True
1.637 +
1.638 + def remove_counters(self, user, uid, recurrenceid=None):
1.639 +
1.640 + """
1.641 + For the given 'user', remove all counter-proposals associated with the
1.642 + given 'uid' and 'recurrenceid'.
1.643 + """
1.644 +
1.645 + table = self.get_event_table(recurrenceid, "counters")
1.646 +
1.647 + if recurrenceid:
1.648 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.649 + values = [user, uid, recurrenceid]
1.650 + else:
1.651 + columns = ["store_user", "object_uid"]
1.652 + values = [user, uid]
1.653 +
1.654 + query, values = self.get_query(
1.655 + "delete from %(table)s :condition" % {
1.656 + "table" : table
1.657 + },
1.658 + columns, values)
1.659 +
1.660 + self.cursor.execute(query, values)
1.661 + return True
1.662 +
1.663 + def remove_counter(self, user, other, uid, recurrenceid=None):
1.664 +
1.665 + """
1.666 + For the given 'user', remove any counter-proposal from 'other'
1.667 + associated with the given 'uid' and 'recurrenceid'.
1.668 + """
1.669 +
1.670 + table = self.get_event_table(recurrenceid, "counters")
1.671 +
1.672 + if recurrenceid:
1.673 + columns = ["store_user", "other", "object_uid", "object_recurrenceid"]
1.674 + values = [user, other, uid, recurrenceid]
1.675 + else:
1.676 + columns = ["store_user", "other", "object_uid"]
1.677 + values = [user, other, uid]
1.678 +
1.679 + query, values = self.get_query(
1.680 + "delete from %(table)s :condition" % {
1.681 + "table" : table
1.682 + },
1.683 + columns, values)
1.684 +
1.685 + self.cursor.execute(query, values)
1.686 + return True
1.687 +
1.688 + # Event cancellation.
1.689 +
1.690 + def cancel_event(self, user, uid, recurrenceid=None):
1.691 +
1.692 + """
1.693 + Cancel an event for 'user' having the given 'uid'. If the optional
1.694 + 'recurrenceid' is specified, a specific instance or occurrence of an
1.695 + event is cancelled.
1.696 + """
1.697 +
1.698 + table = self.get_event_table(recurrenceid)
1.699 +
1.700 + if recurrenceid:
1.701 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.702 + values = [user, uid, recurrenceid]
1.703 + else:
1.704 + columns = ["store_user", "object_uid"]
1.705 + values = [user, uid]
1.706 +
1.707 + setcolumns = ["status"]
1.708 + setvalues = ["cancelled"]
1.709 +
1.710 + query, values = self.get_query(
1.711 + "update %(table)s :set :condition" % {
1.712 + "table" : table
1.713 + },
1.714 + columns, values, setcolumns, setvalues)
1.715 +
1.716 + self.cursor.execute(query, values)
1.717 + return True
1.718 +
1.719 + def uncancel_event(self, user, uid, recurrenceid=None):
1.720 +
1.721 + """
1.722 + Uncancel an event for 'user' having the given 'uid'. If the optional
1.723 + 'recurrenceid' is specified, a specific instance or occurrence of an
1.724 + event is uncancelled.
1.725 + """
1.726 +
1.727 + table = self.get_event_table(recurrenceid)
1.728 +
1.729 + if recurrenceid:
1.730 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.731 + values = [user, uid, recurrenceid]
1.732 + else:
1.733 + columns = ["store_user", "object_uid"]
1.734 + values = [user, uid]
1.735 +
1.736 + setcolumns = ["status"]
1.737 + setvalues = ["active"]
1.738 +
1.739 + query, values = self.get_query(
1.740 + "update %(table)s :set :condition" % {
1.741 + "table" : table
1.742 + },
1.743 + columns, values, setcolumns, setvalues)
1.744 +
1.745 + self.cursor.execute(query, values)
1.746 + return True
1.747 +
1.748 + def remove_cancellation(self, user, uid, recurrenceid=None):
1.749 +
1.750 + """
1.751 + Remove a cancellation for 'user' for the event having the given 'uid'.
1.752 + If the optional 'recurrenceid' is specified, a specific instance or
1.753 + occurrence of an event is affected.
1.754 + """
1.755 +
1.756 + table = self.get_event_table(recurrenceid)
1.757 +
1.758 + if recurrenceid:
1.759 + columns = ["store_user", "object_uid", "object_recurrenceid", "status"]
1.760 + values = [user, uid, recurrenceid, "cancelled"]
1.761 + else:
1.762 + columns = ["store_user", "object_uid", "status"]
1.763 + values = [user, uid, "cancelled"]
1.764 +
1.765 + query, values = self.get_query(
1.766 + "delete from %(table)s :condition" % {
1.767 + "table" : table
1.768 + },
1.769 + columns, values)
1.770 +
1.771 + self.cursor.execute(query, values)
1.772 + return True
1.773 +
1.774 +class DatabaseJournal(DatabaseStoreBase, JournalBase):
1.775 +
1.776 + "A journal system to support quotas."
1.777 +
1.778 + # Quota and user identity/group discovery.
1.779 +
1.780 + def get_quotas(self):
1.781 +
1.782 + "Return a list of quotas."
1.783 +
1.784 + query = "select distinct journal_quota from quota_freebusy"
1.785 + self.cursor.execute(query)
1.786 + return [r[0] for r in self.cursor.fetchall()]
1.787 +
1.788 + def get_quota_users(self, quota):
1.789 +
1.790 + "Return a list of quota users."
1.791 +
1.792 + columns = ["quota"]
1.793 + values = [quota]
1.794 +
1.795 + query, values = self.get_query(
1.796 + "select distinct user_group from quota_freebusy :condition",
1.797 + columns, values)
1.798 +
1.799 + self.cursor.execute(query)
1.800 + return [r[0] for r in self.cursor.fetchall()]
1.801 +
1.802 + # Groups of users sharing quotas.
1.803 +
1.804 + def get_groups(self, quota):
1.805 +
1.806 + "Return the identity mappings for the given 'quota' as a dictionary."
1.807 +
1.808 + columns = ["quota"]
1.809 + values = [quota]
1.810 +
1.811 + query, values = self.get_query(
1.812 + "select store_user, user_group from user_groups :condition",
1.813 + columns, values)
1.814 +
1.815 + self.cursor.execute(query)
1.816 + return dict(self.cursor.fetchall())
1.817 +
1.818 + def get_limits(self, quota):
1.819 +
1.820 + """
1.821 + Return the limits for the 'quota' as a dictionary mapping identities or
1.822 + groups to durations.
1.823 + """
1.824 +
1.825 + columns = ["quota"]
1.826 + values = [quota]
1.827 +
1.828 + query, values = self.get_query(
1.829 + "select user_group, quota_limit from quota_limits :condition",
1.830 + columns, values)
1.831 +
1.832 + self.cursor.execute(query)
1.833 + return dict(self.cursor.fetchall())
1.834 +
1.835 + # Free/busy period access for users within quota groups.
1.836 +
1.837 + def get_freebusy(self, quota, user, mutable=False):
1.838 +
1.839 + "Get free/busy details for the given 'quota' and 'user'."
1.840 +
1.841 + table = "user_freebusy"
1.842 + return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], mutable, self.paramstyle)
1.843 +
1.844 + def set_freebusy(self, quota, user, freebusy):
1.845 +
1.846 + "For the given 'quota' and 'user', set 'freebusy' details."
1.847 +
1.848 + table = "user_freebusy"
1.849 +
1.850 + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table:
1.851 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], True, self.paramstyle)
1.852 + fbc += freebusy
1.853 +
1.854 + return True
1.855 +
1.856 + # Journal entry methods.
1.857 +
1.858 + def get_entries(self, quota, group, mutable=False):
1.859 +
1.860 + """
1.861 + Return a list of journal entries for the given 'quota' for the indicated
1.862 + 'group'.
1.863 + """
1.864 +
1.865 + table = "quota_freebusy"
1.866 + return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], mutable, self.paramstyle)
1.867 +
1.868 + def set_entries(self, quota, group, entries):
1.869 +
1.870 + """
1.871 + For the given 'quota' and indicated 'group', set the list of journal
1.872 + 'entries'.
1.873 + """
1.874 +
1.875 + table = "quota_freebusy"
1.876 +
1.877 + if not isinstance(entries, FreeBusyDatabaseCollection) or entries.table_name != table:
1.878 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], True, self.paramstyle)
1.879 + fbc += entries
1.880 +
1.881 + return True
1.882 +
1.883 +# vim: tabstop=4 expandtab shiftwidth=4