1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/imipweb/resource.py Thu Mar 26 16:11:46 2015 +0100
1.3 @@ -0,0 +1,212 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Common resource functionality for Web calendar clients.
1.8 +
1.9 +Copyright (C) 2014, 2015 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 datetime import datetime
1.26 +from imiptools.client import Client
1.27 +from imiptools.data import get_uri, get_window_end, Object, uri_values
1.28 +from imiptools.dates import format_datetime, format_time
1.29 +from imiptools.period import remove_period, remove_affected_period, update_freebusy
1.30 +from imipweb.env import CGIEnvironment
1.31 +import babel.dates
1.32 +import imip_store
1.33 +import markup
1.34 +
1.35 +class Resource(Client):
1.36 +
1.37 + "A Web application resource and calendar client."
1.38 +
1.39 + def __init__(self, resource=None):
1.40 + self.encoding = "utf-8"
1.41 + self.env = CGIEnvironment(self.encoding)
1.42 +
1.43 + user = self.env.get_user()
1.44 + Client.__init__(self, user and get_uri(user) or None)
1.45 +
1.46 + self.locale = None
1.47 + self.requests = None
1.48 +
1.49 + self.out = resource and resource.out or self.env.get_output()
1.50 + self.page = resource and resource.page or markup.page()
1.51 + self.html_ids = None
1.52 +
1.53 + self.store = imip_store.FileStore()
1.54 + self.objects = {}
1.55 +
1.56 + try:
1.57 + self.publisher = imip_store.FilePublisher()
1.58 + except OSError:
1.59 + self.publisher = None
1.60 +
1.61 + # Presentation methods.
1.62 +
1.63 + def new_page(self, title):
1.64 + self.page.init(title=title, charset=self.encoding, css=self.env.new_url("styles.css"))
1.65 + self.html_ids = set()
1.66 +
1.67 + def status(self, code, message):
1.68 + self.header("Status", "%s %s" % (code, message))
1.69 +
1.70 + def header(self, header, value):
1.71 + print >>self.out, "%s: %s" % (header, value)
1.72 +
1.73 + def no_user(self):
1.74 + self.status(403, "Forbidden")
1.75 + self.new_page(title="Forbidden")
1.76 + self.page.p("You are not logged in and thus cannot access scheduling requests.")
1.77 +
1.78 + def no_page(self):
1.79 + self.status(404, "Not Found")
1.80 + self.new_page(title="Not Found")
1.81 + self.page.p("No page is provided at the given address.")
1.82 +
1.83 + def redirect(self, url):
1.84 + self.status(302, "Redirect")
1.85 + self.header("Location", url)
1.86 + self.new_page(title="Redirect")
1.87 + self.page.p("Redirecting to: %s" % url)
1.88 +
1.89 + def link_to(self, uid, recurrenceid=None):
1.90 + if recurrenceid:
1.91 + return self.env.new_url("/".join([uid, recurrenceid]))
1.92 + else:
1.93 + return self.env.new_url(uid)
1.94 +
1.95 + # Access to objects.
1.96 +
1.97 + def _suffixed_name(self, name, index=None):
1.98 + return index is not None and "%s-%d" % (name, index) or name
1.99 +
1.100 + def _simple_suffixed_name(self, name, suffix, index=None):
1.101 + return index is not None and "%s-%s" % (name, suffix) or name
1.102 +
1.103 + def _get_identifiers(self, path_info):
1.104 + parts = path_info.lstrip("/").split("/")
1.105 + if len(parts) == 1:
1.106 + return parts[0], None
1.107 + else:
1.108 + return parts[:2]
1.109 +
1.110 + def _get_object(self, uid, recurrenceid=None):
1.111 + if self.objects.has_key((uid, recurrenceid)):
1.112 + return self.objects[(uid, recurrenceid)]
1.113 +
1.114 + fragment = uid and self.store.get_event(self.user, uid, recurrenceid) or None
1.115 + obj = self.objects[(uid, recurrenceid)] = fragment and Object(fragment)
1.116 + return obj
1.117 +
1.118 + def _get_recurrences(self, uid):
1.119 + return self.store.get_recurrences(self.user, uid)
1.120 +
1.121 + def _get_requests(self):
1.122 + if self.requests is None:
1.123 + cancellations = self.store.get_cancellations(self.user)
1.124 + requests = set(self.store.get_requests(self.user))
1.125 + self.requests = requests.difference(cancellations)
1.126 + return self.requests
1.127 +
1.128 + def _get_request_summary(self):
1.129 + summary = []
1.130 + for uid, recurrenceid in self._get_requests():
1.131 + obj = self._get_object(uid, recurrenceid)
1.132 + if obj:
1.133 + periods = obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end())
1.134 + recurrenceids = self._get_recurrences(uid)
1.135 +
1.136 + # Convert the periods to more substantial free/busy items.
1.137 +
1.138 + for start, end in periods:
1.139 +
1.140 + # Subtract any recurrences from the free/busy details of a
1.141 + # parent object.
1.142 +
1.143 + if recurrenceid or start not in recurrenceids:
1.144 + summary.append((
1.145 + start, end, uid,
1.146 + obj.get_value("TRANSP"),
1.147 + recurrenceid,
1.148 + obj.get_value("SUMMARY"),
1.149 + obj.get_value("ORGANIZER")
1.150 + ))
1.151 + return summary
1.152 +
1.153 + # Preference methods.
1.154 +
1.155 + def get_user_locale(self):
1.156 + if not self.locale:
1.157 + self.locale = self.get_preferences().get("LANG", "en")
1.158 + return self.locale
1.159 +
1.160 + # Prettyprinting of dates and times.
1.161 +
1.162 + def format_date(self, dt, format):
1.163 + return self._format_datetime(babel.dates.format_date, dt, format)
1.164 +
1.165 + def format_time(self, dt, format):
1.166 + return self._format_datetime(babel.dates.format_time, dt, format)
1.167 +
1.168 + def format_datetime(self, dt, format):
1.169 + return self._format_datetime(
1.170 + isinstance(dt, datetime) and babel.dates.format_datetime or babel.dates.format_date,
1.171 + dt, format)
1.172 +
1.173 + def _format_datetime(self, fn, dt, format):
1.174 + return fn(dt, format=format, locale=self.get_user_locale())
1.175 +
1.176 + # Data management methods.
1.177 +
1.178 + def remove_request(self, uid, recurrenceid=None):
1.179 + return self.store.dequeue_request(self.user, uid, recurrenceid)
1.180 +
1.181 + def remove_event(self, uid, recurrenceid=None):
1.182 + return self.store.remove_event(self.user, uid, recurrenceid)
1.183 +
1.184 + def update_freebusy(self, uid, recurrenceid, obj):
1.185 +
1.186 + """
1.187 + Update stored free/busy details for the event with the given 'uid' and
1.188 + 'recurrenceid' having a representation of 'obj'.
1.189 + """
1.190 +
1.191 + is_only_organiser = self.user not in uri_values(obj.get_values("ATTENDEE"))
1.192 +
1.193 + freebusy = self.store.get_freebusy(self.user)
1.194 +
1.195 + update_freebusy(freebusy,
1.196 + obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end()),
1.197 + is_only_organiser and "ORG" or obj.get_value("TRANSP"),
1.198 + uid, recurrenceid,
1.199 + obj.get_value("SUMMARY"),
1.200 + obj.get_value("ORGANIZER"))
1.201 +
1.202 + # Subtract any recurrences from the free/busy details of a parent
1.203 + # object.
1.204 +
1.205 + for recurrenceid in self._get_recurrences(uid):
1.206 + remove_affected_period(freebusy, uid, recurrenceid)
1.207 +
1.208 + self.store.set_freebusy(self.user, freebusy)
1.209 +
1.210 + def remove_from_freebusy(self, uid, recurrenceid=None):
1.211 + freebusy = self.store.get_freebusy(self.user)
1.212 + remove_period(freebusy, uid, recurrenceid)
1.213 + self.store.set_freebusy(self.user, freebusy)
1.214 +
1.215 +# vim: tabstop=4 expandtab shiftwidth=4