imip-agent

Annotated imip_store.py

147:ee2bd0ee8f55
2015-01-12 Paul Boddie Moved common filesystem functionality into a new module. Added preferences support so that the timezone and locale associated with individual users can be retrieved and used to configure the management interface. Made the default permissions of stored data a configuration setting.
paul@2 1
#!/usr/bin/env python
paul@2 2
paul@146 3
"""
paul@146 4
A simple filesystem-based store of calendar data.
paul@146 5
paul@146 6
Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
paul@146 7
paul@146 8
This program is free software; you can redistribute it and/or modify it under
paul@146 9
the terms of the GNU General Public License as published by the Free Software
paul@146 10
Foundation; either version 3 of the License, or (at your option) any later
paul@146 11
version.
paul@146 12
paul@146 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@146 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@146 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@146 16
details.
paul@146 17
paul@146 18
You should have received a copy of the GNU General Public License along with
paul@146 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@146 20
"""
paul@146 21
paul@30 22
from datetime import datetime
paul@68 23
from imiptools.config import STORE_DIR, PUBLISH_DIR
paul@147 24
from imiptools.filesys import fix_permissions, FileBase
paul@147 25
from os.path import exists, isfile, join
paul@147 26
from os import listdir
paul@15 27
from vCalendar import iterwrite
paul@2 28
paul@30 29
def make_calendar(fragment, method=None):
paul@105 30
paul@105 31
    """
paul@105 32
    Return a complete calendar item wrapping the given 'fragment' and employing
paul@105 33
    the given 'method', if indicated.
paul@105 34
    """
paul@105 35
paul@30 36
    return ("VCALENDAR", {},
paul@30 37
            (method and [("METHOD", {}, method)] or []) +
paul@30 38
            [("VERSION", {}, "2.0")] +
paul@30 39
            fragment
paul@30 40
           )
paul@30 41
paul@26 42
def to_stream(out, fragment, encoding="utf-8"):
paul@26 43
    iterwrite(out, encoding=encoding).append(fragment)
paul@15 44
paul@50 45
class FileStore(FileBase):
paul@50 46
paul@50 47
    "A file store of tabular free/busy data and objects."
paul@50 48
paul@147 49
    def __init__(self, store_dir=STORE_DIR):
paul@147 50
        FileBase.__init__(self, store_dir)
paul@147 51
paul@119 52
    def get_events(self, user):
paul@119 53
paul@119 54
        "Return a list of event identifiers."
paul@119 55
paul@138 56
        filename = self.get_object_in_store(user, "objects")
paul@119 57
        if not filename or not exists(filename):
paul@119 58
            return None
paul@119 59
paul@119 60
        return [name for name in listdir(filename) if isfile(join(filename, name))]
paul@119 61
paul@50 62
    def get_event(self, user, uid):
paul@50 63
paul@50 64
        "Get the event for the given 'user' with the given 'uid'."
paul@50 65
paul@138 66
        filename = self.get_object_in_store(user, "objects", uid)
paul@50 67
        if not filename or not exists(filename):
paul@50 68
            return None
paul@50 69
paul@119 70
        return open(filename) or None
paul@50 71
paul@50 72
    def set_event(self, user, uid, node):
paul@50 73
paul@50 74
        "Set an event for 'user' having the given 'uid' and 'node'."
paul@50 75
paul@138 76
        filename = self.get_object_in_store(user, "objects", uid)
paul@50 77
        if not filename:
paul@50 78
            return False
paul@50 79
paul@15 80
        f = open(filename, "w")
paul@15 81
        try:
paul@26 82
            to_stream(f, node)
paul@15 83
        finally:
paul@15 84
            f.close()
paul@103 85
            fix_permissions(filename)
paul@15 86
paul@15 87
        return True
paul@15 88
paul@15 89
    def get_freebusy(self, user):
paul@15 90
paul@15 91
        "Get free/busy details for the given 'user'."
paul@15 92
paul@52 93
        filename = self.get_object_in_store(user, "freebusy")
paul@15 94
        if not filename or not exists(filename):
paul@2 95
            return None
paul@112 96
        else:
paul@112 97
            return self._get_freebusy(filename)
paul@2 98
paul@112 99
    def get_freebusy_for_other(self, user, other):
paul@112 100
paul@112 101
        "For the given 'user', get free/busy details for the 'other' user."
paul@112 102
paul@112 103
        filename = self.get_object_in_store(user, "freebusy-other", other)
paul@112 104
        if not filename:
paul@112 105
            return None
paul@112 106
        else:
paul@112 107
            return self._get_freebusy(filename)
paul@112 108
paul@112 109
    def _get_freebusy(self, filename):
paul@2 110
        f = open(filename)
paul@2 111
        try:
paul@2 112
            l = []
paul@2 113
            for line in f.readlines():
paul@17 114
                l.append(tuple(line.strip().split("\t")))
paul@2 115
            return l
paul@2 116
        finally:
paul@2 117
            f.close()
paul@2 118
paul@15 119
    def set_freebusy(self, user, freebusy):
paul@15 120
paul@15 121
        "For the given 'user', set 'freebusy' details."
paul@15 122
paul@52 123
        filename = self.get_object_in_store(user, "freebusy")
paul@15 124
        if not filename:
paul@15 125
            return False
paul@15 126
paul@112 127
        self._set_freebusy(filename, freebusy)
paul@15 128
        return True
paul@15 129
paul@110 130
    def set_freebusy_for_other(self, user, freebusy, other):
paul@110 131
paul@110 132
        "For the given 'user', set 'freebusy' details for the 'other' user."
paul@110 133
paul@110 134
        filename = self.get_object_in_store(user, "freebusy-other", other)
paul@110 135
        if not filename:
paul@110 136
            return False
paul@110 137
paul@112 138
        self._set_freebusy(filename, freebusy)
paul@112 139
        return True
paul@112 140
paul@112 141
    def _set_freebusy(self, filename, freebusy):
paul@110 142
        f = open(filename, "w")
paul@110 143
        try:
paul@110 144
            for item in freebusy:
paul@119 145
                f.write("\t".join([(value or "OPAQUE") for value in item]) + "\n")
paul@110 146
        finally:
paul@110 147
            f.close()
paul@110 148
            fix_permissions(filename)
paul@110 149
paul@142 150
    def _get_requests(self, user, queue):
paul@66 151
paul@142 152
        "Get requests for the given 'user' from the given 'queue'."
paul@66 153
paul@142 154
        filename = self.get_object_in_store(user, queue)
paul@81 155
        if not filename or not exists(filename):
paul@66 156
            return None
paul@66 157
paul@66 158
        f = open(filename)
paul@66 159
        try:
paul@66 160
            return [line.strip() for line in f.readlines()]
paul@66 161
        finally:
paul@66 162
            f.close()
paul@66 163
paul@142 164
    def get_requests(self, user):
paul@142 165
paul@142 166
        "Get requests for the given 'user'."
paul@142 167
paul@142 168
        return self._get_requests(user, "requests")
paul@142 169
paul@142 170
    def get_cancellations(self, user):
paul@142 171
paul@142 172
        "Get cancellations for the given 'user'."
paul@55 173
paul@142 174
        return self._get_requests(user, "cancellations")
paul@142 175
paul@142 176
    def _set_requests(self, user, requests, queue):
paul@66 177
paul@142 178
        """
paul@142 179
        For the given 'user', set the list of queued 'requests' in the given
paul@142 180
        'queue'.
paul@142 181
        """
paul@142 182
paul@142 183
        filename = self.get_object_in_store(user, queue)
paul@66 184
        if not filename:
paul@66 185
            return False
paul@66 186
paul@66 187
        f = open(filename, "w")
paul@66 188
        try:
paul@66 189
            for request in requests:
paul@66 190
                print >>f, request
paul@66 191
        finally:
paul@66 192
            f.close()
paul@103 193
            fix_permissions(filename)
paul@66 194
paul@66 195
        return True
paul@66 196
paul@142 197
    def set_requests(self, user, requests):
paul@142 198
paul@142 199
        "For the given 'user', set the list of queued 'requests'."
paul@142 200
paul@142 201
        return self._set_requests(user, requests, "requests")
paul@142 202
paul@142 203
    def set_cancellations(self, user, cancellations):
paul@66 204
paul@142 205
        "For the given 'user', set the list of queued 'cancellations'."
paul@142 206
paul@142 207
        return self._set_requests(user, cancellations, "cancellations")
paul@55 208
paul@142 209
    def _set_request(self, user, request, queue):
paul@142 210
paul@142 211
        "For the given 'user', set the queued 'request' in the given 'queue'."
paul@142 212
paul@142 213
        filename = self.get_object_in_store(user, queue)
paul@55 214
        if not filename:
paul@55 215
            return False
paul@55 216
paul@55 217
        f = open(filename, "a")
paul@55 218
        try:
paul@66 219
            print >>f, request
paul@55 220
        finally:
paul@55 221
            f.close()
paul@103 222
            fix_permissions(filename)
paul@55 223
paul@55 224
        return True
paul@55 225
paul@142 226
    def set_request(self, user, request):
paul@142 227
paul@142 228
        "For the given 'user', set the queued 'request'."
paul@142 229
paul@142 230
        return self._set_request(user, request, "requests")
paul@142 231
paul@142 232
    def set_cancellation(self, user, cancellation):
paul@142 233
paul@142 234
        "For the given 'user', set the queued 'cancellation'."
paul@142 235
paul@142 236
        return self._set_request(user, cancellation, "cancellations")
paul@142 237
paul@66 238
    def queue_request(self, user, uid):
paul@66 239
paul@66 240
        "Queue a request for 'user' having the given 'uid'."
paul@66 241
paul@81 242
        requests = self.get_requests(user) or []
paul@66 243
paul@66 244
        if uid not in requests:
paul@66 245
            return self.set_request(user, uid)
paul@66 246
paul@66 247
        return False
paul@66 248
paul@105 249
    def dequeue_request(self, user, uid):
paul@105 250
paul@105 251
        "Dequeue a request for 'user' having the given 'uid'."
paul@105 252
paul@105 253
        requests = self.get_requests(user) or []
paul@105 254
paul@105 255
        try:
paul@105 256
            requests.remove(uid)
paul@105 257
            self.set_requests(user, requests)
paul@105 258
        except ValueError:
paul@105 259
            return False
paul@105 260
        else:
paul@105 261
            return True
paul@105 262
paul@142 263
    def cancel_event(self, user, uid):
paul@142 264
paul@142 265
        "Queue an event for cancellation for 'user' having the given 'uid'."
paul@142 266
paul@142 267
        cancellations = self.get_cancellations(user) or []
paul@142 268
paul@142 269
        if uid not in cancellations:
paul@142 270
            return self.set_cancellation(user, uid)
paul@142 271
paul@142 272
        return False
paul@142 273
paul@30 274
class FilePublisher(FileBase):
paul@30 275
paul@30 276
    "A publisher of objects."
paul@30 277
paul@30 278
    def __init__(self, store_dir=PUBLISH_DIR):
paul@30 279
        FileBase.__init__(self, store_dir)
paul@30 280
paul@30 281
    def set_freebusy(self, user, freebusy):
paul@30 282
paul@30 283
        "For the given 'user', set 'freebusy' details."
paul@30 284
paul@52 285
        filename = self.get_object_in_store(user, "freebusy")
paul@30 286
        if not filename:
paul@30 287
            return False
paul@30 288
paul@30 289
        record = []
paul@30 290
        rwrite = record.append
paul@30 291
paul@30 292
        rwrite(("ORGANIZER", {}, user))
paul@30 293
        rwrite(("UID", {}, user))
paul@30 294
        rwrite(("DTSTAMP", {}, datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")))
paul@30 295
paul@112 296
        for start, end, uid, transp in freebusy:
paul@119 297
            if not transp or transp == "OPAQUE":
paul@112 298
                rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join([start, end])))
paul@30 299
paul@30 300
        f = open(filename, "w")
paul@30 301
        try:
paul@30 302
            to_stream(f, make_calendar([("VFREEBUSY", {}, record)], "PUBLISH"))
paul@30 303
        finally:
paul@30 304
            f.close()
paul@103 305
            fix_permissions(filename)
paul@30 306
paul@30 307
        return True
paul@30 308
paul@2 309
# vim: tabstop=4 expandtab shiftwidth=4