imip-agent

Annotated imipweb/resource.py

686:b5bdf9dcad0f
2015-09-04 Paul Boddie Added docstrings.
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@622 24
from imiptools.data import get_uri, uri_values
paul@622 25
from imiptools.dates import get_recurrence_start_point
paul@678 26
from imiptools.period import remove_period, remove_affected_period
paul@446 27
from imipweb.env import CGIEnvironment
paul@446 28
import babel.dates
paul@446 29
import imip_store
paul@446 30
import markup
paul@446 31
paul@446 32
class Resource(Client):
paul@446 33
paul@446 34
    "A Web application resource and calendar client."
paul@446 35
paul@446 36
    def __init__(self, resource=None):
paul@446 37
        self.encoding = "utf-8"
paul@446 38
        self.env = CGIEnvironment(self.encoding)
paul@446 39
paul@446 40
        user = self.env.get_user()
paul@446 41
        Client.__init__(self, user and get_uri(user) or None)
paul@446 42
paul@446 43
        self.locale = None
paul@446 44
        self.requests = None
paul@446 45
paul@446 46
        self.out = resource and resource.out or self.env.get_output()
paul@446 47
        self.page = resource and resource.page or markup.page()
paul@446 48
        self.html_ids = None
paul@446 49
paul@446 50
        self.store = imip_store.FileStore()
paul@446 51
        self.objects = {}
paul@446 52
paul@446 53
        try:
paul@446 54
            self.publisher = imip_store.FilePublisher()
paul@446 55
        except OSError:
paul@446 56
            self.publisher = None
paul@446 57
paul@446 58
    # Presentation methods.
paul@446 59
paul@446 60
    def new_page(self, title):
paul@446 61
        self.page.init(title=title, charset=self.encoding, css=self.env.new_url("styles.css"))
paul@446 62
        self.html_ids = set()
paul@446 63
paul@446 64
    def status(self, code, message):
paul@446 65
        self.header("Status", "%s %s" % (code, message))
paul@446 66
paul@446 67
    def header(self, header, value):
paul@446 68
        print >>self.out, "%s: %s" % (header, value)
paul@446 69
paul@446 70
    def no_user(self):
paul@446 71
        self.status(403, "Forbidden")
paul@446 72
        self.new_page(title="Forbidden")
paul@446 73
        self.page.p("You are not logged in and thus cannot access scheduling requests.")
paul@446 74
paul@446 75
    def no_page(self):
paul@446 76
        self.status(404, "Not Found")
paul@446 77
        self.new_page(title="Not Found")
paul@446 78
        self.page.p("No page is provided at the given address.")
paul@446 79
paul@446 80
    def redirect(self, url):
paul@446 81
        self.status(302, "Redirect")
paul@446 82
        self.header("Location", url)
paul@446 83
        self.new_page(title="Redirect")
paul@446 84
        self.page.p("Redirecting to: %s" % url)
paul@446 85
paul@446 86
    def link_to(self, uid, recurrenceid=None):
paul@446 87
        if recurrenceid:
paul@446 88
            return self.env.new_url("/".join([uid, recurrenceid]))
paul@446 89
        else:
paul@446 90
            return self.env.new_url(uid)
paul@446 91
paul@623 92
    # Control naming helpers.
paul@446 93
paul@622 94
    def element_identifier(self, name, index=None):
paul@446 95
        return index is not None and "%s-%d" % (name, index) or name
paul@446 96
paul@622 97
    def element_name(self, name, suffix, index=None):
paul@446 98
        return index is not None and "%s-%s" % (name, suffix) or name
paul@446 99
paul@623 100
    def element_enable(self, index=None):
paul@623 101
        return index is not None and str(index) or "enable"
paul@623 102
paul@623 103
    # Access to objects.
paul@623 104
paul@446 105
    def _get_identifiers(self, path_info):
paul@446 106
        parts = path_info.lstrip("/").split("/")
paul@446 107
        if len(parts) == 1:
paul@446 108
            return parts[0], None
paul@446 109
        else:
paul@446 110
            return parts[:2]
paul@446 111
paul@446 112
    def _get_object(self, uid, recurrenceid=None):
paul@446 113
        if self.objects.has_key((uid, recurrenceid)):
paul@446 114
            return self.objects[(uid, recurrenceid)]
paul@446 115
paul@606 116
        obj = self.objects[(uid, recurrenceid)] = self.get_stored_object(uid, recurrenceid)
paul@446 117
        return obj
paul@446 118
paul@446 119
    def _get_recurrences(self, uid):
paul@446 120
        return self.store.get_recurrences(self.user, uid)
paul@446 121
paul@446 122
    def _get_requests(self):
paul@446 123
        if self.requests is None:
paul@446 124
            cancellations = self.store.get_cancellations(self.user)
paul@446 125
            requests = set(self.store.get_requests(self.user))
paul@446 126
            self.requests = requests.difference(cancellations)
paul@446 127
        return self.requests
paul@446 128
paul@446 129
    def _get_request_summary(self):
paul@630 130
paul@630 131
        "Return a list of periods comprising the request summary."
paul@630 132
paul@446 133
        summary = []
paul@630 134
paul@446 135
        for uid, recurrenceid in self._get_requests():
paul@606 136
            obj = self.get_stored_object(uid, recurrenceid)
paul@446 137
            if obj:
paul@446 138
                recurrenceids = self._get_recurrences(uid)
paul@446 139
paul@630 140
                # Obtain only active periods, not those replaced by redefined
paul@678 141
                # recurrences, converting to free/busy periods.
paul@446 142
paul@630 143
                for p in obj.get_active_periods(recurrenceids, self.get_tzid(), self.get_window_end()):
paul@678 144
                    summary.append(obj.get_freebusy_period(p))
paul@446 145
paul@446 146
        return summary
paul@446 147
paul@446 148
    # Preference methods.
paul@446 149
paul@446 150
    def get_user_locale(self):
paul@446 151
        if not self.locale:
paul@446 152
            self.locale = self.get_preferences().get("LANG", "en")
paul@446 153
        return self.locale
paul@446 154
paul@446 155
    # Prettyprinting of dates and times.
paul@446 156
paul@446 157
    def format_date(self, dt, format):
paul@446 158
        return self._format_datetime(babel.dates.format_date, dt, format)
paul@446 159
paul@446 160
    def format_time(self, dt, format):
paul@446 161
        return self._format_datetime(babel.dates.format_time, dt, format)
paul@446 162
paul@446 163
    def format_datetime(self, dt, format):
paul@446 164
        return self._format_datetime(
paul@446 165
            isinstance(dt, datetime) and babel.dates.format_datetime or babel.dates.format_date,
paul@446 166
            dt, format)
paul@446 167
paul@446 168
    def _format_datetime(self, fn, dt, format):
paul@446 169
        return fn(dt, format=format, locale=self.get_user_locale())
paul@446 170
paul@446 171
    # Data management methods.
paul@446 172
paul@446 173
    def remove_request(self, uid, recurrenceid=None):
paul@446 174
        return self.store.dequeue_request(self.user, uid, recurrenceid)
paul@446 175
paul@446 176
    def remove_event(self, uid, recurrenceid=None):
paul@446 177
        return self.store.remove_event(self.user, uid, recurrenceid)
paul@446 178
paul@446 179
    def update_freebusy(self, uid, recurrenceid, obj):
paul@446 180
paul@446 181
        """
paul@446 182
        Update stored free/busy details for the event with the given 'uid' and
paul@446 183
        'recurrenceid' having a representation of 'obj'.
paul@446 184
        """
paul@446 185
paul@446 186
        is_only_organiser = self.user not in uri_values(obj.get_values("ATTENDEE"))
paul@446 187
paul@446 188
        freebusy = self.store.get_freebusy(self.user)
paul@446 189
paul@606 190
        Client.update_freebusy(self, freebusy, self.get_periods(obj),
paul@446 191
            is_only_organiser and "ORG" or obj.get_value("TRANSP"),
paul@446 192
            uid, recurrenceid,
paul@446 193
            obj.get_value("SUMMARY"),
paul@446 194
            obj.get_value("ORGANIZER"))
paul@446 195
paul@446 196
        # Subtract any recurrences from the free/busy details of a parent
paul@446 197
        # object.
paul@446 198
paul@446 199
        for recurrenceid in self._get_recurrences(uid):
paul@627 200
            remove_affected_period(freebusy, uid, obj.get_recurrence_start_point(recurrenceid, self.get_tzid()))
paul@446 201
paul@446 202
        self.store.set_freebusy(self.user, freebusy)
paul@468 203
        self.publish_freebusy(freebusy)
paul@447 204
paul@672 205
        # Update free/busy provider information if the event may recur
paul@672 206
        # indefinitely.
paul@672 207
paul@672 208
        if obj.possibly_recurring_indefinitely():
paul@672 209
            self.store.append_freebusy_provider(self.user, obj)
paul@672 210
paul@446 211
    def remove_from_freebusy(self, uid, recurrenceid=None):
paul@446 212
        freebusy = self.store.get_freebusy(self.user)
paul@446 213
        remove_period(freebusy, uid, recurrenceid)
paul@446 214
        self.store.set_freebusy(self.user, freebusy)
paul@468 215
        self.publish_freebusy(freebusy)
paul@468 216
paul@672 217
        # Update free/busy provider information if the event may recur
paul@672 218
        # indefinitely.
paul@672 219
paul@672 220
        if obj.possibly_recurring_indefinitely():
paul@672 221
            self.store.remove_freebusy_provider(self.user, obj)
paul@672 222
paul@468 223
    def publish_freebusy(self, freebusy):
paul@468 224
paul@468 225
        "Publish the details if configured to share them."
paul@468 226
paul@468 227
        if self.publisher and self.is_sharing():
paul@468 228
            self.publisher.set_freebusy(self.user, freebusy)
paul@446 229
paul@446 230
# vim: tabstop=4 expandtab shiftwidth=4