1.1 --- a/imiptools/content.py Sat Jan 31 22:34:12 2015 +0100
1.2 +++ b/imiptools/content.py Sun Feb 01 01:59:37 2015 +0100
1.3 @@ -23,13 +23,14 @@
1.4 from datetime import datetime, timedelta
1.5 from email.mime.text import MIMEText
1.6 from imiptools.config import MANAGER_PATH, MANAGER_URL
1.7 +from imiptools.data import Object, parse_object, \
1.8 + get_address, get_fragments, \
1.9 + get_uri, get_value, uri_dict, uri_item
1.10 from imiptools.dates import *
1.11 from imiptools.period import have_conflict, insert_period, remove_period
1.12 from pytz import timezone
1.13 from socket import gethostname
1.14 -from vCalendar import parse, ParseError, to_dict
1.15 from vRecurrence import get_parameters, get_rule
1.16 -import email.utils
1.17 import imip_store
1.18
1.19 try:
1.20 @@ -37,81 +38,6 @@
1.21 except ImportError:
1.22 from StringIO import StringIO
1.23
1.24 -# Content interpretation.
1.25 -
1.26 -def get_items(d, name, all=True):
1.27 -
1.28 - """
1.29 - Get all items from 'd' with the given 'name', returning single items if
1.30 - 'all' is specified and set to a false value and if only one value is
1.31 - present for the name. Return None if no items are found for the name or if
1.32 - many items are found but 'all' is set to a false value.
1.33 - """
1.34 -
1.35 - if d.has_key(name):
1.36 - values = d[name]
1.37 - if all:
1.38 - return values
1.39 - elif len(values) == 1:
1.40 - return values[0]
1.41 - else:
1.42 - return None
1.43 - else:
1.44 - return None
1.45 -
1.46 -def get_item(d, name):
1.47 - return get_items(d, name, False)
1.48 -
1.49 -def get_value_map(d, name):
1.50 -
1.51 - """
1.52 - Return a dictionary for all items in 'd' having the given 'name'. The
1.53 - dictionary will map values for the name to any attributes or qualifiers
1.54 - that may have been present.
1.55 - """
1.56 -
1.57 - items = get_items(d, name)
1.58 - if items:
1.59 - return dict(items)
1.60 - else:
1.61 - return {}
1.62 -
1.63 -def get_values(d, name, all=True):
1.64 - if d.has_key(name):
1.65 - values = d[name]
1.66 - if not all and len(values) == 1:
1.67 - return values[0][0]
1.68 - else:
1.69 - return map(lambda x: x[0], values)
1.70 - else:
1.71 - return None
1.72 -
1.73 -def get_value(d, name):
1.74 - return get_values(d, name, False)
1.75 -
1.76 -def get_utc_datetime(d, name):
1.77 - value, attr = get_item(d, name)
1.78 - dt = get_datetime(value, attr)
1.79 - return to_utc_datetime(dt)
1.80 -
1.81 -def get_addresses(values):
1.82 - return [address for name, address in email.utils.getaddresses(values)]
1.83 -
1.84 -def get_address(value):
1.85 - return value.lower().startswith("mailto:") and value.lower()[7:] or value
1.86 -
1.87 -def get_uri(value):
1.88 - return value.lower().startswith("mailto:") and value.lower() or ":" in value and value or "mailto:%s" % value.lower()
1.89 -
1.90 -def uri_dict(d):
1.91 - return dict([(get_uri(key), value) for key, value in d.items()])
1.92 -
1.93 -def uri_item(item):
1.94 - return get_uri(item[0]), item[1]
1.95 -
1.96 -def uri_items(items):
1.97 - return [(get_uri(value), attr) for value, attr in items]
1.98 -
1.99 def is_new_object(old_sequence, new_sequence, old_dtstamp, new_dtstamp, partstat_set):
1.100
1.101 """
1.102 @@ -144,8 +70,8 @@
1.103 to the given 'window_size' in days starting from the present moment.
1.104 """
1.105
1.106 - dtstart = get_utc_datetime(obj, "DTSTART")
1.107 - dtend = get_utc_datetime(obj, "DTEND")
1.108 + dtstart = obj.get_utc_datetime("DTSTART")
1.109 + dtend = obj.get_utc_datetime("DTEND")
1.110
1.111 # NOTE: Need also DURATION support.
1.112
1.113 @@ -160,7 +86,7 @@
1.114
1.115 # NOTE: Need also RDATE and EXDATE support.
1.116
1.117 - rrule = get_value(obj, "RRULE")
1.118 + rrule = obj.get_value("RRULE")
1.119
1.120 if rrule:
1.121 selector = get_rule(dtstart, rrule)
1.122 @@ -274,11 +200,11 @@
1.123 all_results = []
1.124
1.125 for name, cls in handlers:
1.126 - for details in get_values(itip, name) or []:
1.127 + for fragment in get_fragments(itip, name):
1.128
1.129 # Dispatch to a handler and obtain any response.
1.130
1.131 - handler = cls(details, senders, recipient, messenger)
1.132 + handler = cls(Object(fragment), senders, recipient, messenger)
1.133 results = methods[method](handler)()
1.134
1.135 # Aggregate responses for a single message.
1.136 @@ -292,52 +218,6 @@
1.137
1.138 return []
1.139
1.140 -def parse_object(f, encoding, objtype=None):
1.141 -
1.142 - """
1.143 - Parse the iTIP content from 'f' having the given 'encoding'. If 'objtype' is
1.144 - given, only objects of that type will be returned. Otherwise, the root of
1.145 - the content will be returned as a dictionary with a single key indicating
1.146 - the object type.
1.147 -
1.148 - Return None if the content was not readable or suitable.
1.149 - """
1.150 -
1.151 - try:
1.152 - try:
1.153 - doctype, attrs, elements = obj = parse(f, encoding=encoding)
1.154 - if objtype and doctype == objtype:
1.155 - return to_dict(obj)[objtype][0]
1.156 - elif not objtype:
1.157 - return to_dict(obj)
1.158 - finally:
1.159 - f.close()
1.160 -
1.161 - # NOTE: Handle parse errors properly.
1.162 -
1.163 - except (ParseError, ValueError):
1.164 - pass
1.165 -
1.166 - return None
1.167 -
1.168 -def to_part(method, calendar):
1.169 -
1.170 - """
1.171 - Write using the given 'method', the 'calendar' details to a MIME
1.172 - text/calendar part.
1.173 - """
1.174 -
1.175 - encoding = "utf-8"
1.176 - out = StringIO()
1.177 - try:
1.178 - imip_store.to_stream(out, imip_store.make_calendar(calendar, method), encoding)
1.179 - part = MIMEText(out.getvalue(), "calendar", encoding)
1.180 - part.set_param("method", method)
1.181 - return part
1.182 -
1.183 - finally:
1.184 - out.close()
1.185 -
1.186 # References to the Web interface.
1.187
1.188 def get_manager_url():
1.189 @@ -351,21 +231,21 @@
1.190
1.191 "General handler support."
1.192
1.193 - def __init__(self, details, senders=None, recipient=None, messenger=None):
1.194 + def __init__(self, obj, senders=None, recipient=None, messenger=None):
1.195
1.196 """
1.197 - Initialise the handler with the 'details' of a calendar object and the
1.198 - 'senders' and 'recipient' of the object (if specifically indicated).
1.199 + Initialise the handler with the calendar 'obj' and the 'senders' and
1.200 + 'recipient' of the object (if specifically indicated).
1.201 """
1.202
1.203 - self.details = details
1.204 + self.obj = obj
1.205 self.senders = senders and set(map(get_address, senders))
1.206 self.recipient = recipient and get_address(recipient)
1.207 self.messenger = messenger
1.208
1.209 - self.uid = self.get_value("UID")
1.210 - self.sequence = self.get_value("SEQUENCE")
1.211 - self.dtstamp = self.get_value("DTSTAMP")
1.212 + self.uid = self.obj.get_value("UID")
1.213 + self.sequence = self.obj.get_value("SEQUENCE")
1.214 + self.dtstamp = self.obj.get_value("DTSTAMP")
1.215
1.216 self.store = imip_store.FileStore()
1.217
1.218 @@ -389,26 +269,8 @@
1.219
1.220 # Access to calendar structures and other data.
1.221
1.222 - def get_items(self, name, all=True):
1.223 - return get_items(self.details, name, all)
1.224 -
1.225 - def get_item(self, name):
1.226 - return get_item(self.details, name)
1.227 -
1.228 - def get_value_map(self, name):
1.229 - return get_value_map(self.details, name)
1.230 -
1.231 - def get_values(self, name, all=True):
1.232 - return get_values(self.details, name, all)
1.233 -
1.234 - def get_value(self, name):
1.235 - return get_value(self.details, name)
1.236 -
1.237 - def get_utc_datetime(self, name):
1.238 - return get_utc_datetime(self.details, name)
1.239 -
1.240 def get_periods(self):
1.241 - return get_periods(self.details)
1.242 + return get_periods(self.obj)
1.243
1.244 def remove_from_freebusy(self, freebusy, attendee):
1.245 remove_from_freebusy(freebusy, attendee, self.uid, self.store)
1.246 @@ -417,10 +279,10 @@
1.247 remove_from_freebusy_for_other(freebusy, user, other, self.uid, self.store)
1.248
1.249 def update_freebusy(self, freebusy, attendee, periods):
1.250 - return update_freebusy(freebusy, attendee, periods, self.get_value("TRANSP"), self.uid, self.store)
1.251 + return update_freebusy(freebusy, attendee, periods, self.obj.get_value("TRANSP"), self.uid, self.store)
1.252
1.253 def update_freebusy_for_other(self, freebusy, user, other, periods):
1.254 - return update_freebusy_for_other(freebusy, user, other, periods, self.get_value("TRANSP"), self.uid, self.store)
1.255 + return update_freebusy_for_other(freebusy, user, other, periods, self.obj.get_value("TRANSP"), self.uid, self.store)
1.256
1.257 def can_schedule(self, freebusy, periods):
1.258 return can_schedule(freebusy, periods, self.uid)
1.259 @@ -470,8 +332,8 @@
1.260 Organiser and attendee identities are provided as lower case values.
1.261 """
1.262
1.263 - attendee_map = uri_dict(self.get_value_map("ATTENDEE"))
1.264 - organiser_item = uri_item(self.get_item("ORGANIZER"))
1.265 + attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))
1.266 + organiser_item = uri_item(self.obj.get_item("ORGANIZER"))
1.267
1.268 # Only provide details for attendees who sent/receive the message.
1.269
1.270 @@ -512,7 +374,7 @@
1.271
1.272 return senders
1.273
1.274 - def get_object(self, user, objtype):
1.275 + def get_object(self, user):
1.276
1.277 """
1.278 Return the stored object to which the current object refers for the
1.279 @@ -520,23 +382,23 @@
1.280 """
1.281
1.282 f = self.store.get_event(user, self.uid)
1.283 - obj = f and parse_object(f, "utf-8", objtype)
1.284 - return obj
1.285 + fragment = f and parse_object(f, "utf-8")
1.286 + return fragment and Object(fragment)
1.287
1.288 - def have_new_object(self, attendee, objtype, obj=None):
1.289 + def have_new_object(self, attendee, obj=None):
1.290
1.291 """
1.292 - Return whether the current object is new to the 'attendee' for the
1.293 - given 'objtype'.
1.294 + Return whether the current object is new to the 'attendee' (or if the
1.295 + given 'obj' is new).
1.296 """
1.297
1.298 - obj = obj or self.get_object(attendee, objtype)
1.299 + obj = obj or self.get_object(attendee)
1.300
1.301 # If found, compare SEQUENCE and potentially DTSTAMP.
1.302
1.303 if obj:
1.304 - sequence = get_value(obj, "SEQUENCE")
1.305 - dtstamp = get_value(obj, "DTSTAMP")
1.306 + sequence = obj.get_value("SEQUENCE")
1.307 + dtstamp = obj.get_value("DTSTAMP")
1.308
1.309 # If the request refers to an older version of the object, ignore
1.310 # it.
1.311 @@ -557,8 +419,8 @@
1.312 NOTE: incorporated into any new object assessment.
1.313 """
1.314
1.315 - old_attendees = get_value_map(obj, "ATTENDEE")
1.316 - new_attendees = self.get_value_map("ATTENDEE")
1.317 + old_attendees = obj.get_value_map("ATTENDEE")
1.318 + new_attendees = self.obj.get_value_map("ATTENDEE")
1.319
1.320 for attendee, attr in old_attendees.items():
1.321 old_partstat = attr.get("PARTSTAT")
1.322 @@ -576,9 +438,9 @@
1.323
1.324 "Update the DTSTAMP in the current object."
1.325
1.326 - dtstamp = self.get_utc_datetime("DTSTAMP")
1.327 + dtstamp = self.obj.get_utc_datetime("DTSTAMP")
1.328 utcnow = to_timezone(datetime.utcnow(), "UTC")
1.329 - self.details["DTSTAMP"] = [(format_datetime(dtstamp > utcnow and dtstamp or utcnow), {})]
1.330 + self.obj["DTSTAMP"] = [(format_datetime(dtstamp > utcnow and dtstamp or utcnow), {})]
1.331
1.332 # Handler registry.
1.333