imip-agent

Annotated imipweb/resource.py

606:d75510f99c06
2015-07-26 Paul Boddie Moved various handler methods to the client classes. Attempted to simplify various methods and to remove the need to obtain information from some methods only to pass it straight to other methods that could have obtained such information themselves.
paul@446 1
#!/usr/bin/env python
paul@446 2
paul@446 3
"""
paul@446 4
Common resource functionality for Web calendar clients.
paul@446 5
paul@446 6
Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
paul@446 7
paul@446 8
This program is free software; you can redistribute it and/or modify it under
paul@446 9
the terms of the GNU General Public License as published by the Free Software
paul@446 10
Foundation; either version 3 of the License, or (at your option) any later
paul@446 11
version.
paul@446 12
paul@446 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@446 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@446 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@446 16
details.
paul@446 17
paul@446 18
You should have received a copy of the GNU General Public License along with
paul@446 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@446 20
"""
paul@446 21
paul@446 22
from datetime import datetime
paul@446 23
from imiptools.client import Client
paul@446 24
from imiptools.data import get_uri, get_window_end, Object, uri_values
paul@563 25
from imiptools.dates import format_datetime, format_time, \
paul@563 26
                            get_recurrence_start_point
paul@563 27
from imiptools.period import FreeBusyPeriod, is_affected, is_replaced, \
paul@458 28
                             remove_period, remove_affected_period, update_freebusy
paul@446 29
from imipweb.env import CGIEnvironment
paul@446 30
import babel.dates
paul@446 31
import imip_store
paul@446 32
import markup
paul@446 33
paul@446 34
class Resource(Client):
paul@446 35
paul@446 36
    "A Web application resource and calendar client."
paul@446 37
paul@446 38
    def __init__(self, resource=None):
paul@446 39
        self.encoding = "utf-8"
paul@446 40
        self.env = CGIEnvironment(self.encoding)
paul@446 41
paul@446 42
        user = self.env.get_user()
paul@446 43
        Client.__init__(self, user and get_uri(user) or None)
paul@446 44
paul@446 45
        self.locale = None
paul@446 46
        self.requests = None
paul@446 47
paul@446 48
        self.out = resource and resource.out or self.env.get_output()
paul@446 49
        self.page = resource and resource.page or markup.page()
paul@446 50
        self.html_ids = None
paul@446 51
paul@446 52
        self.store = imip_store.FileStore()
paul@446 53
        self.objects = {}
paul@446 54
paul@446 55
        try:
paul@446 56
            self.publisher = imip_store.FilePublisher()
paul@446 57
        except OSError:
paul@446 58
            self.publisher = None
paul@446 59
paul@446 60
    # Presentation methods.
paul@446 61
paul@446 62
    def new_page(self, title):
paul@446 63
        self.page.init(title=title, charset=self.encoding, css=self.env.new_url("styles.css"))
paul@446 64
        self.html_ids = set()
paul@446 65
paul@446 66
    def status(self, code, message):
paul@446 67
        self.header("Status", "%s %s" % (code, message))
paul@446 68
paul@446 69
    def header(self, header, value):
paul@446 70
        print >>self.out, "%s: %s" % (header, value)
paul@446 71
paul@446 72
    def no_user(self):
paul@446 73
        self.status(403, "Forbidden")
paul@446 74
        self.new_page(title="Forbidden")
paul@446 75
        self.page.p("You are not logged in and thus cannot access scheduling requests.")
paul@446 76
paul@446 77
    def no_page(self):
paul@446 78
        self.status(404, "Not Found")
paul@446 79
        self.new_page(title="Not Found")
paul@446 80
        self.page.p("No page is provided at the given address.")
paul@446 81
paul@446 82
    def redirect(self, url):
paul@446 83
        self.status(302, "Redirect")
paul@446 84
        self.header("Location", url)
paul@446 85
        self.new_page(title="Redirect")
paul@446 86
        self.page.p("Redirecting to: %s" % url)
paul@446 87
paul@446 88
    def link_to(self, uid, recurrenceid=None):
paul@446 89
        if recurrenceid:
paul@446 90
            return self.env.new_url("/".join([uid, recurrenceid]))
paul@446 91
        else:
paul@446 92
            return self.env.new_url(uid)
paul@446 93
paul@446 94
    # Access to objects.
paul@446 95
paul@446 96
    def _suffixed_name(self, name, index=None):
paul@446 97
        return index is not None and "%s-%d" % (name, index) or name
paul@446 98
paul@446 99
    def _simple_suffixed_name(self, name, suffix, index=None):
paul@446 100
        return index is not None and "%s-%s" % (name, suffix) or name
paul@446 101
paul@446 102
    def _get_identifiers(self, path_info):
paul@446 103
        parts = path_info.lstrip("/").split("/")
paul@446 104
        if len(parts) == 1:
paul@446 105
            return parts[0], None
paul@446 106
        else:
paul@446 107
            return parts[:2]
paul@446 108
paul@446 109
    def _get_object(self, uid, recurrenceid=None):
paul@446 110
        if self.objects.has_key((uid, recurrenceid)):
paul@446 111
            return self.objects[(uid, recurrenceid)]
paul@446 112
paul@606 113
        obj = self.objects[(uid, recurrenceid)] = self.get_stored_object(uid, recurrenceid)
paul@446 114
        return obj
paul@446 115
paul@446 116
    def _get_recurrences(self, uid):
paul@446 117
        return self.store.get_recurrences(self.user, uid)
paul@446 118
paul@446 119
    def _get_requests(self):
paul@446 120
        if self.requests is None:
paul@446 121
            cancellations = self.store.get_cancellations(self.user)
paul@446 122
            requests = set(self.store.get_requests(self.user))
paul@446 123
            self.requests = requests.difference(cancellations)
paul@446 124
        return self.requests
paul@446 125
paul@446 126
    def _get_request_summary(self):
paul@446 127
        summary = []
paul@446 128
        for uid, recurrenceid in self._get_requests():
paul@606 129
            obj = self.get_stored_object(uid, recurrenceid)
paul@446 130
            if obj:
paul@557 131
                periods = obj.get_periods(self.get_tzid(), self.get_window_end())
paul@446 132
                recurrenceids = self._get_recurrences(uid)
paul@446 133
paul@446 134
                # Convert the periods to more substantial free/busy items.
paul@446 135
paul@458 136
                for p in periods:
paul@446 137
paul@446 138
                    # Subtract any recurrences from the free/busy details of a
paul@446 139
                    # parent object.
paul@446 140
paul@557 141
                    if recurrenceid or not self.is_replaced(p, recurrenceids):
paul@458 142
                        summary.append(
paul@458 143
                            FreeBusyPeriod(
paul@529 144
                                p.get_start(),
paul@529 145
                                p.get_end(),
paul@529 146
                                uid,
paul@458 147
                                obj.get_value("TRANSP"),
paul@458 148
                                recurrenceid,
paul@458 149
                                obj.get_value("SUMMARY"),
paul@541 150
                                obj.get_value("ORGANIZER"),
paul@561 151
                                p.get_tzid()
paul@458 152
                                ))
paul@446 153
        return summary
paul@446 154
paul@557 155
    # Period and recurrence testing.
paul@557 156
paul@557 157
    def is_replaced(self, period, recurrenceids):
paul@563 158
        return is_replaced(period, recurrenceids, self.get_tzid())
paul@557 159
paul@557 160
    def is_affected(self, period, recurrenceid):
paul@563 161
        return is_affected(period, recurrenceid, self.get_tzid())
paul@557 162
paul@446 163
    # Preference methods.
paul@446 164
paul@446 165
    def get_user_locale(self):
paul@446 166
        if not self.locale:
paul@446 167
            self.locale = self.get_preferences().get("LANG", "en")
paul@446 168
        return self.locale
paul@446 169
paul@446 170
    # Prettyprinting of dates and times.
paul@446 171
paul@446 172
    def format_date(self, dt, format):
paul@446 173
        return self._format_datetime(babel.dates.format_date, dt, format)
paul@446 174
paul@446 175
    def format_time(self, dt, format):
paul@446 176
        return self._format_datetime(babel.dates.format_time, dt, format)
paul@446 177
paul@446 178
    def format_datetime(self, dt, format):
paul@446 179
        return self._format_datetime(
paul@446 180
            isinstance(dt, datetime) and babel.dates.format_datetime or babel.dates.format_date,
paul@446 181
            dt, format)
paul@446 182
paul@446 183
    def _format_datetime(self, fn, dt, format):
paul@446 184
        return fn(dt, format=format, locale=self.get_user_locale())
paul@446 185
paul@446 186
    # Data management methods.
paul@446 187
paul@446 188
    def remove_request(self, uid, recurrenceid=None):
paul@446 189
        return self.store.dequeue_request(self.user, uid, recurrenceid)
paul@446 190
paul@446 191
    def remove_event(self, uid, recurrenceid=None):
paul@446 192
        return self.store.remove_event(self.user, uid, recurrenceid)
paul@446 193
paul@446 194
    def update_freebusy(self, uid, recurrenceid, obj):
paul@446 195
paul@446 196
        """
paul@446 197
        Update stored free/busy details for the event with the given 'uid' and
paul@446 198
        'recurrenceid' having a representation of 'obj'.
paul@446 199
        """
paul@446 200
paul@446 201
        is_only_organiser = self.user not in uri_values(obj.get_values("ATTENDEE"))
paul@446 202
paul@446 203
        freebusy = self.store.get_freebusy(self.user)
paul@446 204
paul@606 205
        Client.update_freebusy(self, freebusy, self.get_periods(obj),
paul@446 206
            is_only_organiser and "ORG" or obj.get_value("TRANSP"),
paul@446 207
            uid, recurrenceid,
paul@446 208
            obj.get_value("SUMMARY"),
paul@446 209
            obj.get_value("ORGANIZER"))
paul@446 210
paul@446 211
        # Subtract any recurrences from the free/busy details of a parent
paul@446 212
        # object.
paul@561 213
        # NOTE: The time zone may need obtaining using the object details.
paul@446 214
paul@446 215
        for recurrenceid in self._get_recurrences(uid):
paul@563 216
            remove_affected_period(freebusy, uid, get_recurrence_start_point(recurrenceid, self.get_tzid()))
paul@446 217
paul@446 218
        self.store.set_freebusy(self.user, freebusy)
paul@468 219
        self.publish_freebusy(freebusy)
paul@447 220
paul@446 221
    def remove_from_freebusy(self, uid, recurrenceid=None):
paul@446 222
        freebusy = self.store.get_freebusy(self.user)
paul@446 223
        remove_period(freebusy, uid, recurrenceid)
paul@446 224
        self.store.set_freebusy(self.user, freebusy)
paul@468 225
        self.publish_freebusy(freebusy)
paul@468 226
paul@468 227
    def publish_freebusy(self, freebusy):
paul@468 228
paul@468 229
        "Publish the details if configured to share them."
paul@468 230
paul@468 231
        if self.publisher and self.is_sharing():
paul@468 232
            self.publisher.set_freebusy(self.user, freebusy)
paul@446 233
paul@446 234
# vim: tabstop=4 expandtab shiftwidth=4