1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/imiptools/stores/database.py Wed Mar 09 00:08:49 2016 +0100
1.3 @@ -0,0 +1,845 @@
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 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 FreeBusyPeriod, FreeBusyDatabaseCollection
1.31 +from imiptools.sql import DatabaseOperations
1.32 +
1.33 +class DatabaseStoreBase:
1.34 +
1.35 + "A database store supporting user-specific locking."
1.36 +
1.37 + def acquire_lock(self, user, timeout=None):
1.38 + FileBase.acquire_lock(self, timeout, user)
1.39 +
1.40 + def release_lock(self, user):
1.41 + FileBase.release_lock(self, user)
1.42 +
1.43 +class DatabaseStore(DatabaseStoreBase, StoreBase, DatabaseOperations):
1.44 +
1.45 + "A database store of tabular free/busy data and objects."
1.46 +
1.47 + def __init__(self, connection, paramstyle=None):
1.48 + DatabaseOperations.__init__(self, paramstyle=paramstyle)
1.49 + self.connection = connection
1.50 + self.cursor = connection.cursor()
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 [], []
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"
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"
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 + # Requests and counter-proposals.
1.471 +
1.472 + def get_requests(self, user):
1.473 +
1.474 + "Get requests for the given 'user'."
1.475 +
1.476 + columns = ["store_user"]
1.477 + values = [user]
1.478 +
1.479 + query, values = self.get_query(
1.480 + "select object_uid, object_recurrenceid from requests :condition",
1.481 + columns, values)
1.482 +
1.483 + self.cursor.execute(query, values)
1.484 + return self.cursor.fetchall()
1.485 +
1.486 + def set_requests(self, user, requests):
1.487 +
1.488 + "For the given 'user', set the list of queued 'requests'."
1.489 +
1.490 + # NOTE: Locking?
1.491 +
1.492 + columns = ["store_user"]
1.493 + values = [user]
1.494 +
1.495 + query, values = self.get_query(
1.496 + "delete from requests :condition",
1.497 + columns, values)
1.498 +
1.499 + self.cursor.execute(query, values)
1.500 +
1.501 + for uid, recurrenceid, type in requests:
1.502 + columns = ["store_user", "object_uid", "object_recurrenceid", "request_type"]
1.503 + values = [user, uid, recurrenceid, type]
1.504 +
1.505 + query, values = self.get_query(
1.506 + "insert into requests (:columns) values (:values)",
1.507 + columns, values)
1.508 +
1.509 + self.cursor.execute(query, values)
1.510 +
1.511 + return True
1.512 +
1.513 + def set_request(self, user, uid, recurrenceid=None, type=None):
1.514 +
1.515 + """
1.516 + For the given 'user', set the queued 'uid' and 'recurrenceid',
1.517 + indicating a request, along with any given 'type'.
1.518 + """
1.519 +
1.520 + columns = ["store_user", "object_uid", "object_recurrenceid", "request_type"]
1.521 + values = [user, uid, recurrenceid, type]
1.522 +
1.523 + query, values = self.get_query(
1.524 + "insert into requests (:columns) values (:values)",
1.525 + columns, values)
1.526 +
1.527 + self.cursor.execute(query, values)
1.528 + return True
1.529 +
1.530 + def get_counters(self, user, uid, recurrenceid=None):
1.531 +
1.532 + """
1.533 + For the given 'user', return a list of users from whom counter-proposals
1.534 + have been received for the given 'uid' and optional 'recurrenceid'.
1.535 + """
1.536 +
1.537 + table = self.get_event_table(recurrenceid, "counters")
1.538 +
1.539 + if recurrenceid:
1.540 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.541 + values = [user, uid, recurrenceid]
1.542 + else:
1.543 + columns = ["store_user", "object_uid"]
1.544 + values = [user, uid]
1.545 +
1.546 + query, values = self.get_query(
1.547 + "select other from %(table)s :condition" % {
1.548 + "table" : table
1.549 + },
1.550 + columns, values)
1.551 +
1.552 + self.cursor.execute(query, values)
1.553 + return self.cursor.fetchall()
1.554 +
1.555 + def get_counter(self, user, other, uid, recurrenceid=None):
1.556 +
1.557 + """
1.558 + For the given 'user', return the counter-proposal from 'other' for the
1.559 + given 'uid' and optional 'recurrenceid'.
1.560 + """
1.561 +
1.562 + table = self.get_event_table(recurrenceid, "counters")
1.563 +
1.564 + if recurrenceid:
1.565 + columns = ["store_user", "other", "object_uid", "object_recurrenceid"]
1.566 + values = [user, other, uid, recurrenceid]
1.567 + else:
1.568 + columns = ["store_user", "other", "object_uid"]
1.569 + values = [user, other, uid]
1.570 +
1.571 + query, values = self.get_query(
1.572 + "select object_text from %(table)s :condition" % {
1.573 + "table" : table
1.574 + },
1.575 + columns, values)
1.576 +
1.577 + self.cursor.execute(query, values)
1.578 + result = self.cursor.fetchall()
1.579 + return result and parse_string(result[0], "utf-8")
1.580 +
1.581 + def set_counter(self, user, other, node, uid, recurrenceid=None):
1.582 +
1.583 + """
1.584 + For the given 'user', store a counter-proposal received from 'other' the
1.585 + given 'node' representing that proposal for the given 'uid' and
1.586 + 'recurrenceid'.
1.587 + """
1.588 +
1.589 + table = self.get_event_table(recurrenceid, "counters")
1.590 +
1.591 + columns = ["store_user", "other", "object_uid", "object_recurrenceid", "object_text"]
1.592 + values = [user, other, uid, recurrenceid, to_string(node, "utf-8")]
1.593 +
1.594 + query, values = self.get_query(
1.595 + "insert into %(table)s (:columns) values (:values)" % {
1.596 + "table" : table
1.597 + },
1.598 + columns, values)
1.599 +
1.600 + self.cursor.execute(query, values)
1.601 + return True
1.602 +
1.603 + def remove_counters(self, user, uid, recurrenceid=None):
1.604 +
1.605 + """
1.606 + For the given 'user', remove all counter-proposals associated with the
1.607 + given 'uid' and 'recurrenceid'.
1.608 + """
1.609 +
1.610 + table = self.get_event_table(recurrenceid, "counters")
1.611 +
1.612 + if recurrenceid:
1.613 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.614 + values = [user, uid, recurrenceid]
1.615 + else:
1.616 + columns = ["store_user", "object_uid"]
1.617 + values = [user, uid]
1.618 +
1.619 + query, values = self.get_query(
1.620 + "delete from %(table)s :condition" % {
1.621 + "table" : table
1.622 + },
1.623 + columns, values)
1.624 +
1.625 + self.cursor.execute(query, values)
1.626 + return True
1.627 +
1.628 + def remove_counter(self, user, other, uid, recurrenceid=None):
1.629 +
1.630 + """
1.631 + For the given 'user', remove any counter-proposal from 'other'
1.632 + associated with the given 'uid' and 'recurrenceid'.
1.633 + """
1.634 +
1.635 + table = self.get_event_table(recurrenceid, "counters")
1.636 +
1.637 + if recurrenceid:
1.638 + columns = ["store_user", "other", "object_uid", "object_recurrenceid"]
1.639 + values = [user, other, uid, recurrenceid]
1.640 + else:
1.641 + columns = ["store_user", "other", "object_uid"]
1.642 + values = [user, other, uid]
1.643 +
1.644 + query, values = self.get_query(
1.645 + "delete from %(table)s :condition" % {
1.646 + "table" : table
1.647 + },
1.648 + columns, values)
1.649 +
1.650 + self.cursor.execute(query, values)
1.651 + return True
1.652 +
1.653 + # Event cancellation.
1.654 +
1.655 + def cancel_event(self, user, uid, recurrenceid=None):
1.656 +
1.657 + """
1.658 + Cancel an event for 'user' having the given 'uid'. If the optional
1.659 + 'recurrenceid' is specified, a specific instance or occurrence of an
1.660 + event is cancelled.
1.661 + """
1.662 +
1.663 + table = self.get_event_table(recurrenceid)
1.664 +
1.665 + if recurrenceid:
1.666 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.667 + values = [user, uid, recurrenceid]
1.668 + else:
1.669 + columns = ["store_user", "object_uid"]
1.670 + values = [user, uid]
1.671 +
1.672 + setcolumns = ["status"]
1.673 + setvalues = ["cancelled"]
1.674 +
1.675 + query, values = self.get_query(
1.676 + "update %(table)s :set :condition" % {
1.677 + "table" : table
1.678 + },
1.679 + columns, values, setcolumns, setvalues)
1.680 +
1.681 + self.cursor.execute(query, values)
1.682 + return True
1.683 +
1.684 + def uncancel_event(self, user, uid, recurrenceid=None):
1.685 +
1.686 + """
1.687 + Uncancel an event for 'user' having the given 'uid'. If the optional
1.688 + 'recurrenceid' is specified, a specific instance or occurrence of an
1.689 + event is uncancelled.
1.690 + """
1.691 +
1.692 + table = self.get_event_table(recurrenceid)
1.693 +
1.694 + if recurrenceid:
1.695 + columns = ["store_user", "object_uid", "object_recurrenceid"]
1.696 + values = [user, uid, recurrenceid]
1.697 + else:
1.698 + columns = ["store_user", "object_uid"]
1.699 + values = [user, uid]
1.700 +
1.701 + setcolumns = ["status"]
1.702 + setvalues = ["active"]
1.703 +
1.704 + query, values = self.get_query(
1.705 + "update %(table)s :set :condition" % {
1.706 + "table" : table
1.707 + },
1.708 + columns, values, setcolumns, setvalues)
1.709 +
1.710 + self.cursor.execute(query, values)
1.711 + return True
1.712 +
1.713 + def remove_cancellation(self, user, uid, recurrenceid=None):
1.714 +
1.715 + """
1.716 + Remove a cancellation for 'user' for the event having the given 'uid'.
1.717 + If the optional 'recurrenceid' is specified, a specific instance or
1.718 + occurrence of an event is affected.
1.719 + """
1.720 +
1.721 + table = self.get_event_table(recurrenceid)
1.722 +
1.723 + if recurrenceid:
1.724 + columns = ["store_user", "object_uid", "object_recurrenceid", "status"]
1.725 + values = [user, uid, recurrenceid, "cancelled"]
1.726 + else:
1.727 + columns = ["store_user", "object_uid", "status"]
1.728 + values = [user, uid, "cancelled"]
1.729 +
1.730 + query, values = self.get_query(
1.731 + "delete from %(table)s :condition" % {
1.732 + "table" : table
1.733 + },
1.734 + columns, values)
1.735 +
1.736 + self.cursor.execute(query, values)
1.737 + return True
1.738 +
1.739 +class DatabaseJournal(DatabaseStoreBase, JournalBase):
1.740 +
1.741 + "A journal system to support quotas."
1.742 +
1.743 + # Quota and user identity/group discovery.
1.744 +
1.745 + def get_quotas(self):
1.746 +
1.747 + "Return a list of quotas."
1.748 +
1.749 + query = "select distinct journal_quota from quota_freebusy"
1.750 + self.cursor.execute(query)
1.751 + return [r[0] for r in self.cursor.fetchall()]
1.752 +
1.753 + def get_quota_users(self, quota):
1.754 +
1.755 + "Return a list of quota users."
1.756 +
1.757 + columns = ["quota"]
1.758 + values = [quota]
1.759 +
1.760 + query, values = self.get_query(
1.761 + "select distinct user_group from quota_freebusy :condition",
1.762 + columns, values)
1.763 +
1.764 + self.cursor.execute(query)
1.765 + return [r[0] for r in self.cursor.fetchall()]
1.766 +
1.767 + # Groups of users sharing quotas.
1.768 +
1.769 + def get_groups(self, quota):
1.770 +
1.771 + "Return the identity mappings for the given 'quota' as a dictionary."
1.772 +
1.773 + columns = ["quota"]
1.774 + values = [quota]
1.775 +
1.776 + query, values = self.get_query(
1.777 + "select store_user, user_group from user_groups :condition",
1.778 + columns, values)
1.779 +
1.780 + self.cursor.execute(query)
1.781 + return dict(self.cursor.fetchall())
1.782 +
1.783 + def get_limits(self, quota):
1.784 +
1.785 + """
1.786 + Return the limits for the 'quota' as a dictionary mapping identities or
1.787 + groups to durations.
1.788 + """
1.789 +
1.790 + columns = ["quota"]
1.791 + values = [quota]
1.792 +
1.793 + query, values = self.get_query(
1.794 + "select user_group, quota_limit from quota_limits :condition",
1.795 + columns, values)
1.796 +
1.797 + self.cursor.execute(query)
1.798 + return dict(self.cursor.fetchall())
1.799 +
1.800 + # Free/busy period access for users within quota groups.
1.801 +
1.802 + def get_freebusy(self, quota, user, mutable=False):
1.803 +
1.804 + "Get free/busy details for the given 'quota' and 'user'."
1.805 +
1.806 + table = "user_freebusy"
1.807 + return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], mutable, self.paramstyle)
1.808 +
1.809 + def set_freebusy(self, quota, user, freebusy):
1.810 +
1.811 + "For the given 'quota' and 'user', set 'freebusy' details."
1.812 +
1.813 + table = "user_freebusy"
1.814 +
1.815 + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table:
1.816 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], True, self.paramstyle)
1.817 + fbc += freebusy
1.818 +
1.819 + return True
1.820 +
1.821 + # Journal entry methods.
1.822 +
1.823 + def get_entries(self, quota, group, mutable=False):
1.824 +
1.825 + """
1.826 + Return a list of journal entries for the given 'quota' for the indicated
1.827 + 'group'.
1.828 + """
1.829 +
1.830 + table = "quota_freebusy"
1.831 + return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], mutable, self.paramstyle)
1.832 +
1.833 + def set_entries(self, quota, group, entries):
1.834 +
1.835 + """
1.836 + For the given 'quota' and indicated 'group', set the list of journal
1.837 + 'entries'.
1.838 + """
1.839 +
1.840 + table = "quota_freebusy"
1.841 +
1.842 + if not isinstance(entries, FreeBusyDatabaseCollection) or entries.table_name != table:
1.843 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], True, self.paramstyle)
1.844 + fbc += entries
1.845 +
1.846 + return True
1.847 +
1.848 +# vim: tabstop=4 expandtab shiftwidth=4