imip-agent

Annotated imiptools/data.py

213:7233b6cee1b0
2015-02-01 Paul Boddie Introduced an Object class for calendar objects and fragments, thus changing most of the code accessing such data. Added SENT-BY to manager-originating invitations.
paul@213 1
#!/usr/bin/env python
paul@213 2
paul@213 3
"""
paul@213 4
Interpretation of vCalendar content.
paul@213 5
paul@213 6
Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
paul@213 7
paul@213 8
This program is free software; you can redistribute it and/or modify it under
paul@213 9
the terms of the GNU General Public License as published by the Free Software
paul@213 10
Foundation; either version 3 of the License, or (at your option) any later
paul@213 11
version.
paul@213 12
paul@213 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@213 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@213 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@213 16
details.
paul@213 17
paul@213 18
You should have received a copy of the GNU General Public License along with
paul@213 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@213 20
"""
paul@213 21
paul@213 22
from email.mime.text import MIMEText
paul@213 23
from imiptools.dates import get_datetime, to_utc_datetime
paul@213 24
from vCalendar import iterwrite, parse, ParseError, to_dict, to_node
paul@213 25
import email.utils
paul@213 26
paul@213 27
try:
paul@213 28
    from cStringIO import StringIO
paul@213 29
except ImportError:
paul@213 30
    from StringIO import StringIO
paul@213 31
paul@213 32
class Object:
paul@213 33
paul@213 34
    "Access to calendar structures."
paul@213 35
paul@213 36
    def __init__(self, fragment):
paul@213 37
        self.objtype, (self.details, self.attr) = fragment.items()[0]
paul@213 38
paul@213 39
    def get_items(self, name, all=True):
paul@213 40
        return get_items(self.details, name, all)
paul@213 41
paul@213 42
    def get_item(self, name):
paul@213 43
        return get_item(self.details, name)
paul@213 44
paul@213 45
    def get_value_map(self, name):
paul@213 46
        return get_value_map(self.details, name)
paul@213 47
paul@213 48
    def get_values(self, name, all=True):
paul@213 49
        return get_values(self.details, name, all)
paul@213 50
paul@213 51
    def get_value(self, name):
paul@213 52
        return get_value(self.details, name)
paul@213 53
paul@213 54
    def get_utc_datetime(self, name):
paul@213 55
        return get_utc_datetime(self.details, name)
paul@213 56
paul@213 57
    def to_node(self):
paul@213 58
        return to_node({self.objtype : [(self.details, self.attr)]})
paul@213 59
paul@213 60
    def to_part(self, method):
paul@213 61
        return to_part(method, [self.to_node()])
paul@213 62
paul@213 63
    # Direct access to the structure.
paul@213 64
paul@213 65
    def __getitem__(self, name):
paul@213 66
        return self.details[name]
paul@213 67
paul@213 68
    def __setitem__(self, name, value):
paul@213 69
        self.details[name] = value
paul@213 70
paul@213 71
    def __delitem__(self, name):
paul@213 72
        del self.details[name]
paul@213 73
paul@213 74
# Construction and serialisation.
paul@213 75
paul@213 76
def make_calendar(nodes, method=None):
paul@213 77
paul@213 78
    """
paul@213 79
    Return a complete calendar node wrapping the given 'nodes' and employing the
paul@213 80
    given 'method', if indicated.
paul@213 81
    """
paul@213 82
paul@213 83
    return ("VCALENDAR", {},
paul@213 84
            (method and [("METHOD", {}, method)] or []) +
paul@213 85
            [("VERSION", {}, "2.0")] +
paul@213 86
            nodes
paul@213 87
           )
paul@213 88
paul@213 89
def parse_object(f, encoding, objtype=None):
paul@213 90
paul@213 91
    """
paul@213 92
    Parse the iTIP content from 'f' having the given 'encoding'. If 'objtype' is
paul@213 93
    given, only objects of that type will be returned. Otherwise, the root of
paul@213 94
    the content will be returned as a dictionary with a single key indicating
paul@213 95
    the object type.
paul@213 96
paul@213 97
    Return None if the content was not readable or suitable.
paul@213 98
    """
paul@213 99
paul@213 100
    try:
paul@213 101
        try:
paul@213 102
            doctype, attrs, elements = obj = parse(f, encoding=encoding)
paul@213 103
            if objtype and doctype == objtype:
paul@213 104
                return to_dict(obj)[objtype][0]
paul@213 105
            elif not objtype:
paul@213 106
                return to_dict(obj)
paul@213 107
        finally:
paul@213 108
            f.close()
paul@213 109
paul@213 110
    # NOTE: Handle parse errors properly.
paul@213 111
paul@213 112
    except (ParseError, ValueError):
paul@213 113
        pass
paul@213 114
paul@213 115
    return None
paul@213 116
paul@213 117
def to_part(method, calendar):
paul@213 118
paul@213 119
    """
paul@213 120
    Write using the given 'method', the 'calendar' details to a MIME
paul@213 121
    text/calendar part.
paul@213 122
    """
paul@213 123
paul@213 124
    encoding = "utf-8"
paul@213 125
    out = StringIO()
paul@213 126
    try:
paul@213 127
        to_stream(out, make_calendar(calendar, method), encoding)
paul@213 128
        part = MIMEText(out.getvalue(), "calendar", encoding)
paul@213 129
        part.set_param("method", method)
paul@213 130
        return part
paul@213 131
paul@213 132
    finally:
paul@213 133
        out.close()
paul@213 134
paul@213 135
def to_stream(out, fragment, encoding="utf-8"):
paul@213 136
    iterwrite(out, encoding=encoding).append(fragment)
paul@213 137
paul@213 138
# Structure access functions.
paul@213 139
paul@213 140
def get_fragments(d, name):
paul@213 141
paul@213 142
    """
paul@213 143
    Return all fragments from 'd' with the given 'name'. Each fragment is thus
paul@213 144
    suitable for using to initialise Object instances.
paul@213 145
    """
paul@213 146
paul@213 147
    fragments = []
paul@213 148
    if d.has_key(name):
paul@213 149
        for value in d[name]:
paul@213 150
            fragments.append(dict([(name, value)]))
paul@213 151
    return fragments
paul@213 152
paul@213 153
def get_items(d, name, all=True):
paul@213 154
paul@213 155
    """
paul@213 156
    Get all items from 'd' for the given 'name', returning single items if
paul@213 157
    'all' is specified and set to a false value and if only one value is
paul@213 158
    present for the name. Return None if no items are found for the name or if
paul@213 159
    many items are found but 'all' is set to a false value.
paul@213 160
    """
paul@213 161
paul@213 162
    if d.has_key(name):
paul@213 163
        values = d[name]
paul@213 164
        if all:
paul@213 165
            return values
paul@213 166
        elif len(values) == 1:
paul@213 167
            return values[0]
paul@213 168
        else:
paul@213 169
            return None
paul@213 170
    else:
paul@213 171
        return None
paul@213 172
paul@213 173
def get_item(d, name):
paul@213 174
    return get_items(d, name, False)
paul@213 175
paul@213 176
def get_value_map(d, name):
paul@213 177
paul@213 178
    """
paul@213 179
    Return a dictionary for all items in 'd' having the given 'name'. The
paul@213 180
    dictionary will map values for the name to any attributes or qualifiers
paul@213 181
    that may have been present.
paul@213 182
    """
paul@213 183
paul@213 184
    items = get_items(d, name)
paul@213 185
    if items:
paul@213 186
        return dict(items)
paul@213 187
    else:
paul@213 188
        return {}
paul@213 189
paul@213 190
def get_values(d, name, all=True):
paul@213 191
    if d.has_key(name):
paul@213 192
        values = d[name]
paul@213 193
        if not all and len(values) == 1:
paul@213 194
            return values[0][0]
paul@213 195
        else:
paul@213 196
            return map(lambda x: x[0], values)
paul@213 197
    else:
paul@213 198
        return None
paul@213 199
paul@213 200
def get_value(d, name):
paul@213 201
    return get_values(d, name, False)
paul@213 202
paul@213 203
def get_utc_datetime(d, name):
paul@213 204
    value, attr = get_item(d, name)
paul@213 205
    dt = get_datetime(value, attr)
paul@213 206
    return to_utc_datetime(dt)
paul@213 207
paul@213 208
def get_addresses(values):
paul@213 209
    return [address for name, address in email.utils.getaddresses(values)]
paul@213 210
paul@213 211
def get_address(value):
paul@213 212
    return value.lower().startswith("mailto:") and value.lower()[7:] or value
paul@213 213
paul@213 214
def get_uri(value):
paul@213 215
    return value.lower().startswith("mailto:") and value.lower() or ":" in value and value or "mailto:%s" % value.lower()
paul@213 216
paul@213 217
def uri_dict(d):
paul@213 218
    return dict([(get_uri(key), value) for key, value in d.items()])
paul@213 219
paul@213 220
def uri_item(item):
paul@213 221
    return get_uri(item[0]), item[1]
paul@213 222
paul@213 223
def uri_items(items):
paul@213 224
    return [(get_uri(value), attr) for value, attr in items]
paul@213 225
paul@213 226
# vim: tabstop=4 expandtab shiftwidth=4