imip-agent

Annotated imip_store.py

142:caf2415fb036
2014-12-19 Paul Boddie Added support for queuing cancellation requests for approval (or review) in the manager.
paul@2 1
#!/usr/bin/env python
paul@2 2
paul@30 3
from datetime import datetime
paul@68 4
from imiptools.config import STORE_DIR, PUBLISH_DIR
paul@119 5
from os.path import abspath, commonprefix, exists, isfile, join, split
paul@119 6
from os import chmod, listdir, makedirs
paul@15 7
from vCalendar import iterwrite
paul@2 8
paul@2 9
def check_dir(base, dir):
paul@2 10
    return commonprefix([base, abspath(dir)]) == base
paul@2 11
paul@103 12
def fix_permissions(filename):
paul@103 13
    try:
paul@103 14
        chmod(filename, 0660)
paul@103 15
    except OSError:
paul@103 16
        pass
paul@103 17
paul@30 18
def make_calendar(fragment, method=None):
paul@105 19
paul@105 20
    """
paul@105 21
    Return a complete calendar item wrapping the given 'fragment' and employing
paul@105 22
    the given 'method', if indicated.
paul@105 23
    """
paul@105 24
paul@30 25
    return ("VCALENDAR", {},
paul@30 26
            (method and [("METHOD", {}, method)] or []) +
paul@30 27
            [("VERSION", {}, "2.0")] +
paul@30 28
            fragment
paul@30 29
           )
paul@30 30
paul@26 31
def to_stream(out, fragment, encoding="utf-8"):
paul@26 32
    iterwrite(out, encoding=encoding).append(fragment)
paul@15 33
paul@30 34
class FileBase:
paul@2 35
paul@30 36
    "Basic filesystem operations."
paul@2 37
paul@30 38
    def __init__(self, store_dir=STORE_DIR):
paul@30 39
        self.store_dir = store_dir
paul@2 40
        if not exists(self.store_dir):
paul@2 41
            makedirs(self.store_dir)
paul@2 42
paul@15 43
    def get_file_object(self, base, *parts):
paul@15 44
        pathname = join(base, *parts)
paul@15 45
        return check_dir(base, pathname) and pathname or None
paul@2 46
paul@52 47
    def get_object_in_store(self, *parts):
paul@52 48
paul@52 49
        """
paul@52 50
        Return the name of any valid object stored within a hierarchy specified
paul@52 51
        by the given 'parts'.
paul@52 52
        """
paul@52 53
paul@52 54
        parent = expected = self.store_dir
paul@15 55
paul@52 56
        for part in parts:
paul@52 57
            filename = self.get_file_object(expected, part)
paul@52 58
            if not filename:
paul@52 59
                return False
paul@52 60
            parent = expected
paul@52 61
            expected = filename
paul@15 62
paul@52 63
        if not exists(parent):
paul@52 64
            makedirs(parent)
paul@15 65
paul@50 66
        return filename
paul@50 67
paul@50 68
class FileStore(FileBase):
paul@50 69
paul@50 70
    "A file store of tabular free/busy data and objects."
paul@50 71
paul@119 72
    def get_events(self, user):
paul@119 73
paul@119 74
        "Return a list of event identifiers."
paul@119 75
paul@138 76
        filename = self.get_object_in_store(user, "objects")
paul@119 77
        if not filename or not exists(filename):
paul@119 78
            return None
paul@119 79
paul@119 80
        return [name for name in listdir(filename) if isfile(join(filename, name))]
paul@119 81
paul@50 82
    def get_event(self, user, uid):
paul@50 83
paul@50 84
        "Get the event for the given 'user' with the given 'uid'."
paul@50 85
paul@138 86
        filename = self.get_object_in_store(user, "objects", uid)
paul@50 87
        if not filename or not exists(filename):
paul@50 88
            return None
paul@50 89
paul@119 90
        return open(filename) or None
paul@50 91
paul@50 92
    def set_event(self, user, uid, node):
paul@50 93
paul@50 94
        "Set an event for 'user' having the given 'uid' and 'node'."
paul@50 95
paul@138 96
        filename = self.get_object_in_store(user, "objects", uid)
paul@50 97
        if not filename:
paul@50 98
            return False
paul@50 99
paul@15 100
        f = open(filename, "w")
paul@15 101
        try:
paul@26 102
            to_stream(f, node)
paul@15 103
        finally:
paul@15 104
            f.close()
paul@103 105
            fix_permissions(filename)
paul@15 106
paul@15 107
        return True
paul@15 108
paul@15 109
    def get_freebusy(self, user):
paul@15 110
paul@15 111
        "Get free/busy details for the given 'user'."
paul@15 112
paul@52 113
        filename = self.get_object_in_store(user, "freebusy")
paul@15 114
        if not filename or not exists(filename):
paul@2 115
            return None
paul@112 116
        else:
paul@112 117
            return self._get_freebusy(filename)
paul@2 118
paul@112 119
    def get_freebusy_for_other(self, user, other):
paul@112 120
paul@112 121
        "For the given 'user', get free/busy details for the 'other' user."
paul@112 122
paul@112 123
        filename = self.get_object_in_store(user, "freebusy-other", other)
paul@112 124
        if not filename:
paul@112 125
            return None
paul@112 126
        else:
paul@112 127
            return self._get_freebusy(filename)
paul@112 128
paul@112 129
    def _get_freebusy(self, filename):
paul@2 130
        f = open(filename)
paul@2 131
        try:
paul@2 132
            l = []
paul@2 133
            for line in f.readlines():
paul@17 134
                l.append(tuple(line.strip().split("\t")))
paul@2 135
            return l
paul@2 136
        finally:
paul@2 137
            f.close()
paul@2 138
paul@15 139
    def set_freebusy(self, user, freebusy):
paul@15 140
paul@15 141
        "For the given 'user', set 'freebusy' details."
paul@15 142
paul@52 143
        filename = self.get_object_in_store(user, "freebusy")
paul@15 144
        if not filename:
paul@15 145
            return False
paul@15 146
paul@112 147
        self._set_freebusy(filename, freebusy)
paul@15 148
        return True
paul@15 149
paul@110 150
    def set_freebusy_for_other(self, user, freebusy, other):
paul@110 151
paul@110 152
        "For the given 'user', set 'freebusy' details for the 'other' user."
paul@110 153
paul@110 154
        filename = self.get_object_in_store(user, "freebusy-other", other)
paul@110 155
        if not filename:
paul@110 156
            return False
paul@110 157
paul@112 158
        self._set_freebusy(filename, freebusy)
paul@112 159
        return True
paul@112 160
paul@112 161
    def _set_freebusy(self, filename, freebusy):
paul@110 162
        f = open(filename, "w")
paul@110 163
        try:
paul@110 164
            for item in freebusy:
paul@119 165
                f.write("\t".join([(value or "OPAQUE") for value in item]) + "\n")
paul@110 166
        finally:
paul@110 167
            f.close()
paul@110 168
            fix_permissions(filename)
paul@110 169
paul@142 170
    def _get_requests(self, user, queue):
paul@66 171
paul@142 172
        "Get requests for the given 'user' from the given 'queue'."
paul@66 173
paul@142 174
        filename = self.get_object_in_store(user, queue)
paul@81 175
        if not filename or not exists(filename):
paul@66 176
            return None
paul@66 177
paul@66 178
        f = open(filename)
paul@66 179
        try:
paul@66 180
            return [line.strip() for line in f.readlines()]
paul@66 181
        finally:
paul@66 182
            f.close()
paul@66 183
paul@142 184
    def get_requests(self, user):
paul@142 185
paul@142 186
        "Get requests for the given 'user'."
paul@142 187
paul@142 188
        return self._get_requests(user, "requests")
paul@142 189
paul@142 190
    def get_cancellations(self, user):
paul@142 191
paul@142 192
        "Get cancellations for the given 'user'."
paul@55 193
paul@142 194
        return self._get_requests(user, "cancellations")
paul@142 195
paul@142 196
    def _set_requests(self, user, requests, queue):
paul@66 197
paul@142 198
        """
paul@142 199
        For the given 'user', set the list of queued 'requests' in the given
paul@142 200
        'queue'.
paul@142 201
        """
paul@142 202
paul@142 203
        filename = self.get_object_in_store(user, queue)
paul@66 204
        if not filename:
paul@66 205
            return False
paul@66 206
paul@66 207
        f = open(filename, "w")
paul@66 208
        try:
paul@66 209
            for request in requests:
paul@66 210
                print >>f, request
paul@66 211
        finally:
paul@66 212
            f.close()
paul@103 213
            fix_permissions(filename)
paul@66 214
paul@66 215
        return True
paul@66 216
paul@142 217
    def set_requests(self, user, requests):
paul@142 218
paul@142 219
        "For the given 'user', set the list of queued 'requests'."
paul@142 220
paul@142 221
        return self._set_requests(user, requests, "requests")
paul@142 222
paul@142 223
    def set_cancellations(self, user, cancellations):
paul@66 224
paul@142 225
        "For the given 'user', set the list of queued 'cancellations'."
paul@142 226
paul@142 227
        return self._set_requests(user, cancellations, "cancellations")
paul@55 228
paul@142 229
    def _set_request(self, user, request, queue):
paul@142 230
paul@142 231
        "For the given 'user', set the queued 'request' in the given 'queue'."
paul@142 232
paul@142 233
        filename = self.get_object_in_store(user, queue)
paul@55 234
        if not filename:
paul@55 235
            return False
paul@55 236
paul@55 237
        f = open(filename, "a")
paul@55 238
        try:
paul@66 239
            print >>f, request
paul@55 240
        finally:
paul@55 241
            f.close()
paul@103 242
            fix_permissions(filename)
paul@55 243
paul@55 244
        return True
paul@55 245
paul@142 246
    def set_request(self, user, request):
paul@142 247
paul@142 248
        "For the given 'user', set the queued 'request'."
paul@142 249
paul@142 250
        return self._set_request(user, request, "requests")
paul@142 251
paul@142 252
    def set_cancellation(self, user, cancellation):
paul@142 253
paul@142 254
        "For the given 'user', set the queued 'cancellation'."
paul@142 255
paul@142 256
        return self._set_request(user, cancellation, "cancellations")
paul@142 257
paul@66 258
    def queue_request(self, user, uid):
paul@66 259
paul@66 260
        "Queue a request for 'user' having the given 'uid'."
paul@66 261
paul@81 262
        requests = self.get_requests(user) or []
paul@66 263
paul@66 264
        if uid not in requests:
paul@66 265
            return self.set_request(user, uid)
paul@66 266
paul@66 267
        return False
paul@66 268
paul@105 269
    def dequeue_request(self, user, uid):
paul@105 270
paul@105 271
        "Dequeue a request for 'user' having the given 'uid'."
paul@105 272
paul@105 273
        requests = self.get_requests(user) or []
paul@105 274
paul@105 275
        try:
paul@105 276
            requests.remove(uid)
paul@105 277
            self.set_requests(user, requests)
paul@105 278
        except ValueError:
paul@105 279
            return False
paul@105 280
        else:
paul@105 281
            return True
paul@105 282
paul@142 283
    def cancel_event(self, user, uid):
paul@142 284
paul@142 285
        "Queue an event for cancellation for 'user' having the given 'uid'."
paul@142 286
paul@142 287
        cancellations = self.get_cancellations(user) or []
paul@142 288
paul@142 289
        if uid not in cancellations:
paul@142 290
            return self.set_cancellation(user, uid)
paul@142 291
paul@142 292
        return False
paul@142 293
paul@30 294
class FilePublisher(FileBase):
paul@30 295
paul@30 296
    "A publisher of objects."
paul@30 297
paul@30 298
    def __init__(self, store_dir=PUBLISH_DIR):
paul@30 299
        FileBase.__init__(self, store_dir)
paul@30 300
paul@30 301
    def set_freebusy(self, user, freebusy):
paul@30 302
paul@30 303
        "For the given 'user', set 'freebusy' details."
paul@30 304
paul@52 305
        filename = self.get_object_in_store(user, "freebusy")
paul@30 306
        if not filename:
paul@30 307
            return False
paul@30 308
paul@30 309
        record = []
paul@30 310
        rwrite = record.append
paul@30 311
paul@30 312
        rwrite(("ORGANIZER", {}, user))
paul@30 313
        rwrite(("UID", {}, user))
paul@30 314
        rwrite(("DTSTAMP", {}, datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")))
paul@30 315
paul@112 316
        for start, end, uid, transp in freebusy:
paul@119 317
            if not transp or transp == "OPAQUE":
paul@112 318
                rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join([start, end])))
paul@30 319
paul@30 320
        f = open(filename, "w")
paul@30 321
        try:
paul@30 322
            to_stream(f, make_calendar([("VFREEBUSY", {}, record)], "PUBLISH"))
paul@30 323
        finally:
paul@30 324
            f.close()
paul@103 325
            fix_permissions(filename)
paul@30 326
paul@30 327
        return True
paul@30 328
paul@2 329
# vim: tabstop=4 expandtab shiftwidth=4