2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/imiptools/stores/database.py Wed Mar 09 00:08:49 2016 +0100
2.3 @@ -0,0 +1,845 @@
2.4 +#!/usr/bin/env python
2.5 +
2.6 +"""
2.7 +A database store of calendar data.
2.8 +
2.9 +Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
2.10 +
2.11 +This program is free software; you can redistribute it and/or modify it under
2.12 +the terms of the GNU General Public License as published by the Free Software
2.13 +Foundation; either version 3 of the License, or (at your option) any later
2.14 +version.
2.15 +
2.16 +This program is distributed in the hope that it will be useful, but WITHOUT
2.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
2.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
2.19 +details.
2.20 +
2.21 +You should have received a copy of the GNU General Public License along with
2.22 +this program. If not, see <http://www.gnu.org/licenses/>.
2.23 +"""
2.24 +
2.25 +from imiptools.stores import StoreBase, JournalBase
2.26 +
2.27 +from datetime import datetime
2.28 +from imiptools.data import parse_string, to_string
2.29 +from imiptools.dates import format_datetime, get_datetime, to_timezone
2.30 +from imiptools.period import FreeBusyPeriod, FreeBusyDatabaseCollection
2.31 +from imiptools.sql import DatabaseOperations
2.32 +
2.33 +class DatabaseStoreBase:
2.34 +
2.35 + "A database store supporting user-specific locking."
2.36 +
2.37 + def acquire_lock(self, user, timeout=None):
2.38 + FileBase.acquire_lock(self, timeout, user)
2.39 +
2.40 + def release_lock(self, user):
2.41 + FileBase.release_lock(self, user)
2.42 +
2.43 +class DatabaseStore(DatabaseStoreBase, StoreBase, DatabaseOperations):
2.44 +
2.45 + "A database store of tabular free/busy data and objects."
2.46 +
2.47 + def __init__(self, connection, paramstyle=None):
2.48 + DatabaseOperations.__init__(self, paramstyle=paramstyle)
2.49 + self.connection = connection
2.50 + self.cursor = connection.cursor()
2.51 +
2.52 + # User discovery.
2.53 +
2.54 + def get_users(self):
2.55 +
2.56 + "Return a list of users."
2.57 +
2.58 + query = "select distinct store_user from freebusy"
2.59 + self.cursor.execute(query)
2.60 + return [r[0] for r in self.cursor.fetchall()]
2.61 +
2.62 + # Event and event metadata access.
2.63 +
2.64 + def get_events(self, user):
2.65 +
2.66 + "Return a list of event identifiers."
2.67 +
2.68 + columns = ["store_user", "status"]
2.69 + values = [user, "active"]
2.70 +
2.71 + query, values = self.get_query(
2.72 + "select object_uid from objects :condition",
2.73 + columns, values)
2.74 +
2.75 + self.cursor.execute(query, values)
2.76 + return [r[0] for r in self.cursor.fetchall()]
2.77 +
2.78 + def get_all_events(self, user):
2.79 +
2.80 + "Return a set of (uid, recurrenceid) tuples for all events."
2.81 +
2.82 + query, values = self.get_query(
2.83 + "select object_uid, null as object_recurrenceid from objects :condition "
2.84 + "union all "
2.85 + "select object_uid, object_recurrenceid from recurrences :condition",
2.86 + ["store_user"], [user])
2.87 +
2.88 + self.cursor.execute(query, values)
2.89 + return self.cursor.fetchall()
2.90 +
2.91 + def get_event_table(self, recurrenceid=None, dirname=None):
2.92 +
2.93 + "Get the table providing events for any specified 'dirname'."
2.94 +
2.95 + if recurrenceid:
2.96 + return self.get_recurrence_table(dirname)
2.97 + else:
2.98 + return self.get_complete_event_table(dirname)
2.99 +
2.100 + def get_event_table_filters(self, dirname=None):
2.101 +
2.102 + "Get filter details for any specified 'dirname'."
2.103 +
2.104 + if dirname == "cancellations":
2.105 + return ["status"], ["cancelled"]
2.106 + else:
2.107 + return [], []
2.108 +
2.109 + def get_event(self, user, uid, recurrenceid=None, dirname=None):
2.110 +
2.111 + """
2.112 + Get the event for the given 'user' with the given 'uid'. If
2.113 + the optional 'recurrenceid' is specified, a specific instance or
2.114 + occurrence of an event is returned.
2.115 + """
2.116 +
2.117 + table = self.get_event_table(recurrenceid, dirname)
2.118 + columns, values = self.get_event_table_filters(dirname)
2.119 +
2.120 + if recurrenceid:
2.121 + columns += ["store_user", "object_uid", "object_recurrenceid"]
2.122 + values += [user, uid, recurrenceid]
2.123 + else:
2.124 + columns += ["store_user", "object_uid"]
2.125 + values += [user, uid]
2.126 +
2.127 + query, values = self.get_query(
2.128 + "select object_text from %(table)s :condition" % {
2.129 + "table" : table
2.130 + },
2.131 + columns, values)
2.132 +
2.133 + self.cursor.execute(query, values)
2.134 + result = self.cursor.fetchone()
2.135 + return result and parse_string(result[0], "utf-8")
2.136 +
2.137 + def get_complete_event_table(self, dirname=None):
2.138 +
2.139 + "Get the table providing events for any specified 'dirname'."
2.140 +
2.141 + if dirname == "counters":
2.142 + return "countered_objects"
2.143 + else:
2.144 + return "objects"
2.145 +
2.146 + def get_complete_event(self, user, uid):
2.147 +
2.148 + "Get the event for the given 'user' with the given 'uid'."
2.149 +
2.150 + columns = ["store_user", "object_uid"]
2.151 + values = [user, uid]
2.152 +
2.153 + query, values = self.get_query(
2.154 + "select object_text from objects :condition",
2.155 + columns, values)
2.156 +
2.157 + self.cursor.execute(query, values)
2.158 + result = self.cursor.fetchone()
2.159 + return result and parse_string(result[0], "utf-8")
2.160 +
2.161 + def set_complete_event(self, user, uid, node):
2.162 +
2.163 + "Set an event for 'user' having the given 'uid' and 'node'."
2.164 +
2.165 + columns = ["store_user", "object_uid"]
2.166 + values = [user, uid]
2.167 + setcolumns = ["object_text", "status"]
2.168 + setvalues = [to_string(node, "utf-8"), "active"]
2.169 +
2.170 + query, values = self.get_query(
2.171 + "update objects :set :condition",
2.172 + columns, values, setcolumns, setvalues)
2.173 +
2.174 + self.cursor.execute(query, values)
2.175 +
2.176 + if self.cursor.rowcount > 0 or self.get_complete_event(user, uid):
2.177 + return True
2.178 +
2.179 + columns = ["store_user", "object_uid", "object_text", "status"]
2.180 + values = [user, uid, to_string(node, "utf-8"), "active"]
2.181 +
2.182 + query, values = self.get_query(
2.183 + "insert into objects (:columns) values (:values)",
2.184 + columns, values)
2.185 +
2.186 + self.cursor.execute(query, values)
2.187 + return True
2.188 +
2.189 + def remove_parent_event(self, user, uid):
2.190 +
2.191 + "Remove the parent event for 'user' having the given 'uid'."
2.192 +
2.193 + columns = ["store_user", "object_uid"]
2.194 + values = [user, uid]
2.195 +
2.196 + query, values = self.get_query(
2.197 + "delete from objects :condition",
2.198 + columns, values)
2.199 +
2.200 + self.cursor.execute(query, values)
2.201 + return self.cursor.rowcount > 0
2.202 +
2.203 + def get_active_recurrences(self, user, uid):
2.204 +
2.205 + """
2.206 + Get additional event instances for an event of the given 'user' with the
2.207 + indicated 'uid'. Cancelled recurrences are not returned.
2.208 + """
2.209 +
2.210 + columns = ["store_user", "object_uid", "status"]
2.211 + values = [user, uid, "active"]
2.212 +
2.213 + query, values = self.get_query(
2.214 + "select object_recurrenceid from recurrences :condition",
2.215 + columns, values)
2.216 +
2.217 + self.cursor.execute(query, values)
2.218 + return [t[0] for t in self.cursor.fetchall() or []]
2.219 +
2.220 + def get_cancelled_recurrences(self, user, uid):
2.221 +
2.222 + """
2.223 + Get additional event instances for an event of the given 'user' with the
2.224 + indicated 'uid'. Only cancelled recurrences are returned.
2.225 + """
2.226 +
2.227 + columns = ["store_user", "object_uid", "status"]
2.228 + values = [user, uid, "cancelled"]
2.229 +
2.230 + query, values = self.get_query(
2.231 + "select object_recurrenceid from recurrences :condition",
2.232 + columns, values)
2.233 +
2.234 + self.cursor.execute(query, values)
2.235 + return [t[0] for t in self.cursor.fetchall() or []]
2.236 +
2.237 + def get_recurrence_table(self, dirname=None):
2.238 +
2.239 + "Get the table providing recurrences for any specified 'dirname'."
2.240 +
2.241 + if dirname == "counters":
2.242 + return "countered_recurrences"
2.243 + else:
2.244 + return "recurrences"
2.245 +
2.246 + def get_recurrence(self, user, uid, recurrenceid):
2.247 +
2.248 + """
2.249 + For the event of the given 'user' with the given 'uid', return the
2.250 + specific recurrence indicated by the 'recurrenceid'.
2.251 + """
2.252 +
2.253 + columns = ["store_user", "object_uid", "object_recurrenceid"]
2.254 + values = [user, uid, recurrenceid]
2.255 +
2.256 + query, values = self.get_query(
2.257 + "select object_text from recurrences :condition",
2.258 + columns, values)
2.259 +
2.260 + self.cursor.execute(query, values)
2.261 + result = self.cursor.fetchone()
2.262 + return result and parse_string(result[0], "utf-8")
2.263 +
2.264 + def set_recurrence(self, user, uid, recurrenceid, node):
2.265 +
2.266 + "Set an event for 'user' having the given 'uid' and 'node'."
2.267 +
2.268 + columns = ["store_user", "object_uid", "object_recurrenceid"]
2.269 + values = [user, uid, recurrenceid]
2.270 + setcolumns = ["object_text", "status"]
2.271 + setvalues = [to_string(node, "utf-8"), "active"]
2.272 +
2.273 + query, values = self.get_query(
2.274 + "update recurrences :set :condition",
2.275 + columns, values, setcolumns, setvalues)
2.276 +
2.277 + self.cursor.execute(query, values)
2.278 +
2.279 + if self.cursor.rowcount > 0 or self.get_recurrence(user, uid, recurrenceid):
2.280 + return True
2.281 +
2.282 + columns = ["store_user", "object_uid", "object_recurrenceid", "object_text", "status"]
2.283 + values = [user, uid, recurrenceid, to_string(node, "utf-8"), "active"]
2.284 +
2.285 + query, values = self.get_query(
2.286 + "insert into recurrences (:columns) values (:values)",
2.287 + columns, values)
2.288 +
2.289 + self.cursor.execute(query, values)
2.290 + return True
2.291 +
2.292 + def remove_recurrence(self, user, uid, recurrenceid):
2.293 +
2.294 + """
2.295 + Remove a special recurrence from an event stored by 'user' having the
2.296 + given 'uid' and 'recurrenceid'.
2.297 + """
2.298 +
2.299 + columns = ["store_user", "object_uid", "object_recurrenceid"]
2.300 + values = [user, uid, recurrenceid]
2.301 +
2.302 + query, values = self.get_query(
2.303 + "delete from recurrences :condition",
2.304 + columns, values)
2.305 +
2.306 + self.cursor.execute(query, values)
2.307 + return True
2.308 +
2.309 + def remove_recurrences(self, user, uid):
2.310 +
2.311 + """
2.312 + Remove all recurrences for an event stored by 'user' having the given
2.313 + 'uid'.
2.314 + """
2.315 +
2.316 + columns = ["store_user", "object_uid"]
2.317 + values = [user, uid]
2.318 +
2.319 + query, values = self.get_query(
2.320 + "delete from recurrences :condition",
2.321 + columns, values)
2.322 +
2.323 + self.cursor.execute(query, values)
2.324 + return True
2.325 +
2.326 + # Free/busy period providers, upon extension of the free/busy records.
2.327 +
2.328 + def _get_freebusy_providers(self, user):
2.329 +
2.330 + """
2.331 + Return the free/busy providers for the given 'user'.
2.332 +
2.333 + This function returns any stored datetime and a list of providers as a
2.334 + 2-tuple. Each provider is itself a (uid, recurrenceid) tuple.
2.335 + """
2.336 +
2.337 + columns = ["store_user"]
2.338 + values = [user]
2.339 +
2.340 + query, values = self.get_query(
2.341 + "select object_uid, object_recurrenceid from freebusy_providers :condition",
2.342 + columns, values)
2.343 +
2.344 + self.cursor.execute(query, values)
2.345 + providers = self.cursor.fetchall()
2.346 +
2.347 + columns = ["store_user"]
2.348 + values = [user]
2.349 +
2.350 + query, values = self.get_query(
2.351 + "select start from freebusy_provider_datetimes :condition",
2.352 + columns, values)
2.353 +
2.354 + self.cursor.execute(query, values)
2.355 + result = self.cursor.fetchone()
2.356 + dt_string = result and result[0]
2.357 +
2.358 + return dt_string, providers
2.359 +
2.360 + def _set_freebusy_providers(self, user, dt_string, t):
2.361 +
2.362 + "Set the given provider timestamp 'dt_string' and table 't'."
2.363 +
2.364 + # NOTE: Locking?
2.365 +
2.366 + columns = ["store_user"]
2.367 + values = [user]
2.368 +
2.369 + query, values = self.get_query(
2.370 + "delete from freebusy_providers :condition",
2.371 + columns, values)
2.372 +
2.373 + self.cursor.execute(query, values)
2.374 +
2.375 + columns = ["store_user", "object_uid", "object_recurrenceid"]
2.376 +
2.377 + for uid, recurrenceid in t:
2.378 + values = [user, uid, recurrenceid]
2.379 +
2.380 + query, values = self.get_query(
2.381 + "insert into freebusy_providers (:columns) values (:values)",
2.382 + columns, values)
2.383 +
2.384 + self.cursor.execute(query, values)
2.385 +
2.386 + columns = ["store_user"]
2.387 + values = [user]
2.388 + setcolumns = ["start"]
2.389 + setvalues = [dt_string]
2.390 +
2.391 + query, values = self.get_query(
2.392 + "update freebusy_provider_datetimes :set :condition",
2.393 + columns, values, setcolumns, setvalues)
2.394 +
2.395 + self.cursor.execute(query, values)
2.396 +
2.397 + if self.cursor.rowcount > 0:
2.398 + return True
2.399 +
2.400 + columns = ["store_user", "start"]
2.401 + values = [user, dt_string]
2.402 +
2.403 + query, values = self.get_query(
2.404 + "insert into freebusy_provider_datetimes (:columns) values (:values)",
2.405 + columns, values)
2.406 +
2.407 + self.cursor.execute(query, values)
2.408 + return True
2.409 +
2.410 + # Free/busy period access.
2.411 +
2.412 + def get_freebusy(self, user, name=None, mutable=False):
2.413 +
2.414 + "Get free/busy details for the given 'user'."
2.415 +
2.416 + table = name or "freebusy"
2.417 + return FreeBusyDatabaseCollection(self.cursor, table, ["store_user"], [user], mutable, self.paramstyle)
2.418 +
2.419 + def get_freebusy_for_other(self, user, other, mutable=False):
2.420 +
2.421 + "For the given 'user', get free/busy details for the 'other' user."
2.422 +
2.423 + table = "freebusy"
2.424 + return FreeBusyDatabaseCollection(self.cursor, table, ["store_user", "other"], [user, other], mutable, self.paramstyle)
2.425 +
2.426 + def set_freebusy(self, user, freebusy, name=None):
2.427 +
2.428 + "For the given 'user', set 'freebusy' details."
2.429 +
2.430 + table = name or "freebusy"
2.431 +
2.432 + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table:
2.433 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["store_user"], [user], True, self.paramstyle)
2.434 + fbc += freebusy
2.435 +
2.436 + return True
2.437 +
2.438 + def set_freebusy_for_other(self, user, freebusy, other):
2.439 +
2.440 + "For the given 'user', set 'freebusy' details for the 'other' user."
2.441 +
2.442 + table = "freebusy"
2.443 +
2.444 + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table:
2.445 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["store_user", "other"], [user, other], True, self.paramstyle)
2.446 + fbc += freebusy
2.447 +
2.448 + return True
2.449 +
2.450 + # Tentative free/busy periods related to countering.
2.451 +
2.452 + def get_freebusy_offers(self, user, mutable=False):
2.453 +
2.454 + "Get free/busy offers for the given 'user'."
2.455 +
2.456 + # Expire old offers and save the collection if modified.
2.457 +
2.458 + now = format_datetime(to_timezone(datetime.utcnow(), "UTC"))
2.459 + columns = ["store_user", "expires"]
2.460 + values = [user, now]
2.461 +
2.462 + query, values = self.get_query(
2.463 + "delete from freebusy_offers :condition",
2.464 + columns, values)
2.465 +
2.466 + self.cursor.execute(query, values)
2.467 +
2.468 + return self.get_freebusy(user, "freebusy_offers", mutable)
2.469 +
2.470 + # Requests and counter-proposals.
2.471 +
2.472 + def get_requests(self, user):
2.473 +
2.474 + "Get requests for the given 'user'."
2.475 +
2.476 + columns = ["store_user"]
2.477 + values = [user]
2.478 +
2.479 + query, values = self.get_query(
2.480 + "select object_uid, object_recurrenceid from requests :condition",
2.481 + columns, values)
2.482 +
2.483 + self.cursor.execute(query, values)
2.484 + return self.cursor.fetchall()
2.485 +
2.486 + def set_requests(self, user, requests):
2.487 +
2.488 + "For the given 'user', set the list of queued 'requests'."
2.489 +
2.490 + # NOTE: Locking?
2.491 +
2.492 + columns = ["store_user"]
2.493 + values = [user]
2.494 +
2.495 + query, values = self.get_query(
2.496 + "delete from requests :condition",
2.497 + columns, values)
2.498 +
2.499 + self.cursor.execute(query, values)
2.500 +
2.501 + for uid, recurrenceid, type in requests:
2.502 + columns = ["store_user", "object_uid", "object_recurrenceid", "request_type"]
2.503 + values = [user, uid, recurrenceid, type]
2.504 +
2.505 + query, values = self.get_query(
2.506 + "insert into requests (:columns) values (:values)",
2.507 + columns, values)
2.508 +
2.509 + self.cursor.execute(query, values)
2.510 +
2.511 + return True
2.512 +
2.513 + def set_request(self, user, uid, recurrenceid=None, type=None):
2.514 +
2.515 + """
2.516 + For the given 'user', set the queued 'uid' and 'recurrenceid',
2.517 + indicating a request, along with any given 'type'.
2.518 + """
2.519 +
2.520 + columns = ["store_user", "object_uid", "object_recurrenceid", "request_type"]
2.521 + values = [user, uid, recurrenceid, type]
2.522 +
2.523 + query, values = self.get_query(
2.524 + "insert into requests (:columns) values (:values)",
2.525 + columns, values)
2.526 +
2.527 + self.cursor.execute(query, values)
2.528 + return True
2.529 +
2.530 + def get_counters(self, user, uid, recurrenceid=None):
2.531 +
2.532 + """
2.533 + For the given 'user', return a list of users from whom counter-proposals
2.534 + have been received for the given 'uid' and optional 'recurrenceid'.
2.535 + """
2.536 +
2.537 + table = self.get_event_table(recurrenceid, "counters")
2.538 +
2.539 + if recurrenceid:
2.540 + columns = ["store_user", "object_uid", "object_recurrenceid"]
2.541 + values = [user, uid, recurrenceid]
2.542 + else:
2.543 + columns = ["store_user", "object_uid"]
2.544 + values = [user, uid]
2.545 +
2.546 + query, values = self.get_query(
2.547 + "select other from %(table)s :condition" % {
2.548 + "table" : table
2.549 + },
2.550 + columns, values)
2.551 +
2.552 + self.cursor.execute(query, values)
2.553 + return self.cursor.fetchall()
2.554 +
2.555 + def get_counter(self, user, other, uid, recurrenceid=None):
2.556 +
2.557 + """
2.558 + For the given 'user', return the counter-proposal from 'other' for the
2.559 + given 'uid' and optional 'recurrenceid'.
2.560 + """
2.561 +
2.562 + table = self.get_event_table(recurrenceid, "counters")
2.563 +
2.564 + if recurrenceid:
2.565 + columns = ["store_user", "other", "object_uid", "object_recurrenceid"]
2.566 + values = [user, other, uid, recurrenceid]
2.567 + else:
2.568 + columns = ["store_user", "other", "object_uid"]
2.569 + values = [user, other, uid]
2.570 +
2.571 + query, values = self.get_query(
2.572 + "select object_text from %(table)s :condition" % {
2.573 + "table" : table
2.574 + },
2.575 + columns, values)
2.576 +
2.577 + self.cursor.execute(query, values)
2.578 + result = self.cursor.fetchall()
2.579 + return result and parse_string(result[0], "utf-8")
2.580 +
2.581 + def set_counter(self, user, other, node, uid, recurrenceid=None):
2.582 +
2.583 + """
2.584 + For the given 'user', store a counter-proposal received from 'other' the
2.585 + given 'node' representing that proposal for the given 'uid' and
2.586 + 'recurrenceid'.
2.587 + """
2.588 +
2.589 + table = self.get_event_table(recurrenceid, "counters")
2.590 +
2.591 + columns = ["store_user", "other", "object_uid", "object_recurrenceid", "object_text"]
2.592 + values = [user, other, uid, recurrenceid, to_string(node, "utf-8")]
2.593 +
2.594 + query, values = self.get_query(
2.595 + "insert into %(table)s (:columns) values (:values)" % {
2.596 + "table" : table
2.597 + },
2.598 + columns, values)
2.599 +
2.600 + self.cursor.execute(query, values)
2.601 + return True
2.602 +
2.603 + def remove_counters(self, user, uid, recurrenceid=None):
2.604 +
2.605 + """
2.606 + For the given 'user', remove all counter-proposals associated with the
2.607 + given 'uid' and 'recurrenceid'.
2.608 + """
2.609 +
2.610 + table = self.get_event_table(recurrenceid, "counters")
2.611 +
2.612 + if recurrenceid:
2.613 + columns = ["store_user", "object_uid", "object_recurrenceid"]
2.614 + values = [user, uid, recurrenceid]
2.615 + else:
2.616 + columns = ["store_user", "object_uid"]
2.617 + values = [user, uid]
2.618 +
2.619 + query, values = self.get_query(
2.620 + "delete from %(table)s :condition" % {
2.621 + "table" : table
2.622 + },
2.623 + columns, values)
2.624 +
2.625 + self.cursor.execute(query, values)
2.626 + return True
2.627 +
2.628 + def remove_counter(self, user, other, uid, recurrenceid=None):
2.629 +
2.630 + """
2.631 + For the given 'user', remove any counter-proposal from 'other'
2.632 + associated with the given 'uid' and 'recurrenceid'.
2.633 + """
2.634 +
2.635 + table = self.get_event_table(recurrenceid, "counters")
2.636 +
2.637 + if recurrenceid:
2.638 + columns = ["store_user", "other", "object_uid", "object_recurrenceid"]
2.639 + values = [user, other, uid, recurrenceid]
2.640 + else:
2.641 + columns = ["store_user", "other", "object_uid"]
2.642 + values = [user, other, uid]
2.643 +
2.644 + query, values = self.get_query(
2.645 + "delete from %(table)s :condition" % {
2.646 + "table" : table
2.647 + },
2.648 + columns, values)
2.649 +
2.650 + self.cursor.execute(query, values)
2.651 + return True
2.652 +
2.653 + # Event cancellation.
2.654 +
2.655 + def cancel_event(self, user, uid, recurrenceid=None):
2.656 +
2.657 + """
2.658 + Cancel an event for 'user' having the given 'uid'. If the optional
2.659 + 'recurrenceid' is specified, a specific instance or occurrence of an
2.660 + event is cancelled.
2.661 + """
2.662 +
2.663 + table = self.get_event_table(recurrenceid)
2.664 +
2.665 + if recurrenceid:
2.666 + columns = ["store_user", "object_uid", "object_recurrenceid"]
2.667 + values = [user, uid, recurrenceid]
2.668 + else:
2.669 + columns = ["store_user", "object_uid"]
2.670 + values = [user, uid]
2.671 +
2.672 + setcolumns = ["status"]
2.673 + setvalues = ["cancelled"]
2.674 +
2.675 + query, values = self.get_query(
2.676 + "update %(table)s :set :condition" % {
2.677 + "table" : table
2.678 + },
2.679 + columns, values, setcolumns, setvalues)
2.680 +
2.681 + self.cursor.execute(query, values)
2.682 + return True
2.683 +
2.684 + def uncancel_event(self, user, uid, recurrenceid=None):
2.685 +
2.686 + """
2.687 + Uncancel an event for 'user' having the given 'uid'. If the optional
2.688 + 'recurrenceid' is specified, a specific instance or occurrence of an
2.689 + event is uncancelled.
2.690 + """
2.691 +
2.692 + table = self.get_event_table(recurrenceid)
2.693 +
2.694 + if recurrenceid:
2.695 + columns = ["store_user", "object_uid", "object_recurrenceid"]
2.696 + values = [user, uid, recurrenceid]
2.697 + else:
2.698 + columns = ["store_user", "object_uid"]
2.699 + values = [user, uid]
2.700 +
2.701 + setcolumns = ["status"]
2.702 + setvalues = ["active"]
2.703 +
2.704 + query, values = self.get_query(
2.705 + "update %(table)s :set :condition" % {
2.706 + "table" : table
2.707 + },
2.708 + columns, values, setcolumns, setvalues)
2.709 +
2.710 + self.cursor.execute(query, values)
2.711 + return True
2.712 +
2.713 + def remove_cancellation(self, user, uid, recurrenceid=None):
2.714 +
2.715 + """
2.716 + Remove a cancellation for 'user' for the event having the given 'uid'.
2.717 + If the optional 'recurrenceid' is specified, a specific instance or
2.718 + occurrence of an event is affected.
2.719 + """
2.720 +
2.721 + table = self.get_event_table(recurrenceid)
2.722 +
2.723 + if recurrenceid:
2.724 + columns = ["store_user", "object_uid", "object_recurrenceid", "status"]
2.725 + values = [user, uid, recurrenceid, "cancelled"]
2.726 + else:
2.727 + columns = ["store_user", "object_uid", "status"]
2.728 + values = [user, uid, "cancelled"]
2.729 +
2.730 + query, values = self.get_query(
2.731 + "delete from %(table)s :condition" % {
2.732 + "table" : table
2.733 + },
2.734 + columns, values)
2.735 +
2.736 + self.cursor.execute(query, values)
2.737 + return True
2.738 +
2.739 +class DatabaseJournal(DatabaseStoreBase, JournalBase):
2.740 +
2.741 + "A journal system to support quotas."
2.742 +
2.743 + # Quota and user identity/group discovery.
2.744 +
2.745 + def get_quotas(self):
2.746 +
2.747 + "Return a list of quotas."
2.748 +
2.749 + query = "select distinct journal_quota from quota_freebusy"
2.750 + self.cursor.execute(query)
2.751 + return [r[0] for r in self.cursor.fetchall()]
2.752 +
2.753 + def get_quota_users(self, quota):
2.754 +
2.755 + "Return a list of quota users."
2.756 +
2.757 + columns = ["quota"]
2.758 + values = [quota]
2.759 +
2.760 + query, values = self.get_query(
2.761 + "select distinct user_group from quota_freebusy :condition",
2.762 + columns, values)
2.763 +
2.764 + self.cursor.execute(query)
2.765 + return [r[0] for r in self.cursor.fetchall()]
2.766 +
2.767 + # Groups of users sharing quotas.
2.768 +
2.769 + def get_groups(self, quota):
2.770 +
2.771 + "Return the identity mappings for the given 'quota' as a dictionary."
2.772 +
2.773 + columns = ["quota"]
2.774 + values = [quota]
2.775 +
2.776 + query, values = self.get_query(
2.777 + "select store_user, user_group from user_groups :condition",
2.778 + columns, values)
2.779 +
2.780 + self.cursor.execute(query)
2.781 + return dict(self.cursor.fetchall())
2.782 +
2.783 + def get_limits(self, quota):
2.784 +
2.785 + """
2.786 + Return the limits for the 'quota' as a dictionary mapping identities or
2.787 + groups to durations.
2.788 + """
2.789 +
2.790 + columns = ["quota"]
2.791 + values = [quota]
2.792 +
2.793 + query, values = self.get_query(
2.794 + "select user_group, quota_limit from quota_limits :condition",
2.795 + columns, values)
2.796 +
2.797 + self.cursor.execute(query)
2.798 + return dict(self.cursor.fetchall())
2.799 +
2.800 + # Free/busy period access for users within quota groups.
2.801 +
2.802 + def get_freebusy(self, quota, user, mutable=False):
2.803 +
2.804 + "Get free/busy details for the given 'quota' and 'user'."
2.805 +
2.806 + table = "user_freebusy"
2.807 + return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], mutable, self.paramstyle)
2.808 +
2.809 + def set_freebusy(self, quota, user, freebusy):
2.810 +
2.811 + "For the given 'quota' and 'user', set 'freebusy' details."
2.812 +
2.813 + table = "user_freebusy"
2.814 +
2.815 + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table:
2.816 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], True, self.paramstyle)
2.817 + fbc += freebusy
2.818 +
2.819 + return True
2.820 +
2.821 + # Journal entry methods.
2.822 +
2.823 + def get_entries(self, quota, group, mutable=False):
2.824 +
2.825 + """
2.826 + Return a list of journal entries for the given 'quota' for the indicated
2.827 + 'group'.
2.828 + """
2.829 +
2.830 + table = "quota_freebusy"
2.831 + return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], mutable, self.paramstyle)
2.832 +
2.833 + def set_entries(self, quota, group, entries):
2.834 +
2.835 + """
2.836 + For the given 'quota' and indicated 'group', set the list of journal
2.837 + 'entries'.
2.838 + """
2.839 +
2.840 + table = "quota_freebusy"
2.841 +
2.842 + if not isinstance(entries, FreeBusyDatabaseCollection) or entries.table_name != table:
2.843 + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], True, self.paramstyle)
2.844 + fbc += entries
2.845 +
2.846 + return True
2.847 +
2.848 +# vim: tabstop=4 expandtab shiftwidth=4