1.1 --- a/imip_manager.py Sat Jan 31 22:34:12 2015 +0100
1.2 +++ b/imip_manager.py Sun Feb 01 01:59:37 2015 +0100
1.3 @@ -30,10 +30,8 @@
1.4
1.5 sys.path.append(LIBRARY_PATH)
1.6
1.7 -from imiptools.content import Handler, get_address, \
1.8 - get_item, get_items, get_uri, get_utc_datetime, \
1.9 - get_value, get_value_map, get_values, \
1.10 - parse_object, to_part
1.11 +from imiptools.content import Handler
1.12 +from imiptools.data import get_address, get_uri, parse_object, Object
1.13 from imiptools.dates import format_datetime, get_datetime, get_start_of_day, \
1.14 get_end_of_day, ends_on_same_day, to_timezone
1.15 from imiptools.mail import Messenger
1.16 @@ -42,9 +40,8 @@
1.17 get_scale, have_conflict, get_slots, get_spans, \
1.18 partition_by_day
1.19 from imiptools.profile import Preferences
1.20 -from vCalendar import to_node
1.21 +import imip_store
1.22 import markup
1.23 -import imip_store
1.24
1.25 getenv = os.environ.get
1.26 setenv = os.environ.__setitem__
1.27 @@ -116,14 +113,11 @@
1.28 """
1.29
1.30 def __init__(self, obj, user, messenger):
1.31 - details, details_attr = obj.values()[0]
1.32 - Handler.__init__(self, details)
1.33 - self.obj = obj
1.34 + Handler.__init__(self, obj, messenger=messenger)
1.35 self.user = user
1.36 - self.messenger = messenger
1.37
1.38 - self.organiser = self.get_value("ORGANIZER")
1.39 - self.attendees = self.get_values("ATTENDEE")
1.40 + self.organiser = self.obj.get_value("ORGANIZER")
1.41 + self.attendees = self.obj.get_values("ATTENDEE")
1.42
1.43 # Communication methods.
1.44
1.45 @@ -134,8 +128,7 @@
1.46 to the appropriate recipients, also sending a copy to the 'sender'.
1.47 """
1.48
1.49 - node = to_node(self.obj)
1.50 - part = to_part(method, [node])
1.51 + part = self.obj.to_part(method)
1.52
1.53 if self.user == self.organiser:
1.54 recipients = map(get_address, self.attendees)
1.55 @@ -161,16 +154,16 @@
1.56 # When accepting or declining, do so only on behalf of this user,
1.57 # preserving any other attributes set as an attendee.
1.58
1.59 - for attendee, attendee_attr in self.get_items("ATTENDEE"):
1.60 + for attendee, attendee_attr in self.obj.get_items("ATTENDEE"):
1.61
1.62 if attendee == self.user:
1.63 attendee_attr["PARTSTAT"] = accept and "ACCEPTED" or "DECLINED"
1.64 if self.messenger and self.messenger.sender != get_address(attendee):
1.65 attendee_attr["SENT-BY"] = get_uri(self.messenger.sender)
1.66 - self.details["ATTENDEE"] = [(attendee, attendee_attr)]
1.67 + self.obj["ATTENDEE"] = [(attendee, attendee_attr)]
1.68 if update:
1.69 - sequence = self.get_value("SEQUENCE") or "0"
1.70 - self.details["SEQUENCE"] = [(str(int(sequence) + 1), {})]
1.71 + sequence = self.obj.get_value("SEQUENCE") or "0"
1.72 + self.obj["SEQUENCE"] = [(str(int(sequence) + 1), {})]
1.73 self.update_dtstamp()
1.74
1.75 self.send_message("REPLY", get_address(attendee))
1.76 @@ -189,9 +182,13 @@
1.77 to override any previous message.
1.78 """
1.79
1.80 + organiser, organiser_attr = self.obj.get_item("ORGANIZER")
1.81 +
1.82 + if self.messenger and self.messenger.sender != get_address(organiser):
1.83 + organiser_attr["SENT-BY"] = get_uri(self.messenger.sender)
1.84 if update:
1.85 - sequence = self.get_value("SEQUENCE") or "0"
1.86 - self.details["SEQUENCE"] = [(str(int(sequence) + 1), {})]
1.87 + sequence = self.obj.get_value("SEQUENCE") or "0"
1.88 + self.obj["SEQUENCE"] = [(str(int(sequence) + 1), {})]
1.89 self.update_dtstamp()
1.90
1.91 self.send_message("REQUEST", get_address(self.organiser))
1.92 @@ -237,17 +234,11 @@
1.93 if not f:
1.94 return None
1.95
1.96 - self.objects[uid] = obj = parse_object(f, "utf-8")
1.97 -
1.98 - if not obj:
1.99 - return None
1.100 + fragment = parse_object(f, "utf-8")
1.101 + obj = self.objects[uid] = fragment and Object(fragment)
1.102
1.103 return obj
1.104
1.105 - def _get_details(self, obj):
1.106 - details, details_attr = obj.values()[0]
1.107 - return details
1.108 -
1.109 def _get_requests(self):
1.110 if self.requests is None:
1.111 self.requests = self.store.get_requests(self.user)
1.112 @@ -258,10 +249,9 @@
1.113 for uid in self._get_requests():
1.114 obj = self._get_object(uid)
1.115 if obj:
1.116 - details = self._get_details(obj)
1.117 summary.append((
1.118 - get_value(details, "DTSTART"),
1.119 - get_value(details, "DTEND"),
1.120 + obj.get_value("DTSTART"),
1.121 + obj.get_value("DTEND"),
1.122 uid
1.123 ))
1.124 return summary
1.125 @@ -406,8 +396,7 @@
1.126 # Update the object.
1.127
1.128 if args.has_key("summary"):
1.129 - details = self._get_details(obj)
1.130 - details["SUMMARY"] = [(args["summary"][0], {})]
1.131 + obj["SUMMARY"] = [(args["summary"][0], {})]
1.132
1.133 # Process any action.
1.134
1.135 @@ -452,11 +441,10 @@
1.136
1.137 page = self.page
1.138
1.139 - details = self._get_details(obj)
1.140 - is_organiser = get_value(details, "ORGANIZER") == self.user
1.141 + is_organiser = obj.get_value("ORGANIZER") == self.user
1.142
1.143 if not is_organiser:
1.144 - attendees = get_value_map(details, "ATTENDEE")
1.145 + attendees = obj.get_value_map("ATTENDEE")
1.146 attendee_attr = attendees.get(self.user)
1.147
1.148 if attendee_attr:
1.149 @@ -519,8 +507,6 @@
1.150
1.151 # Provide a summary of the object.
1.152
1.153 - details = self._get_details(obj)
1.154 -
1.155 page.table(id="object", cellspacing=5, cellpadding=5)
1.156 page.thead()
1.157 page.tr()
1.158 @@ -537,7 +523,7 @@
1.159 # Handle datetimes specially.
1.160
1.161 if name in ["DTSTART", "DTEND"]:
1.162 - value, attr = get_item(details, name)
1.163 + value, attr = obj.get_item(name)
1.164 tzid = attr.get("TZID", tzid)
1.165 value = self.format_datetime(to_timezone(get_datetime(value), tzid), "full")
1.166 page.th(label, class_="objectheading")
1.167 @@ -547,7 +533,7 @@
1.168 # Handle the summary specially.
1.169
1.170 elif name == "SUMMARY":
1.171 - value = get_value(details, name)
1.172 + value = obj.get_value(name)
1.173 page.th(label, class_="objectheading")
1.174 page.td()
1.175 page.input(name="summary", type="text", value=value, size=80)
1.176 @@ -557,7 +543,7 @@
1.177 # Handle potentially many values.
1.178
1.179 else:
1.180 - items = get_items(details, name)
1.181 + items = obj.get_items(name)
1.182 page.th(label, class_="objectheading", rowspan=len(items))
1.183
1.184 first = True
1.185 @@ -582,8 +568,8 @@
1.186 page.tbody.close()
1.187 page.table.close()
1.188
1.189 - dtstart = format_datetime(get_utc_datetime(details, "DTSTART"))
1.190 - dtend = format_datetime(get_utc_datetime(details, "DTEND"))
1.191 + dtstart = format_datetime(obj.get_utc_datetime("DTSTART"))
1.192 + dtend = format_datetime(obj.get_utc_datetime("DTEND"))
1.193
1.194 # Indicate whether there are conflicting events.
1.195
1.196 @@ -593,7 +579,7 @@
1.197
1.198 # Obtain any time zone details from the suggested event.
1.199
1.200 - _dtstart, attr = get_item(details, "DTSTART")
1.201 + _dtstart, attr = obj.get_item("DTSTART")
1.202 tzid = attr.get("TZID", tzid)
1.203
1.204 # Show any conflicts.
1.205 @@ -612,8 +598,7 @@
1.206
1.207 found_obj = self._get_object(found_uid)
1.208 if found_obj:
1.209 - found_details = self._get_details(found_obj)
1.210 - page.a(get_value(found_details, "SUMMARY"), href=self.env.new_url(found_uid))
1.211 + page.a(found_obj.get_value("SUMMARY"), href=self.env.new_url(found_uid))
1.212
1.213 self.show_request_controls(obj, needs_action)
1.214 page.form.close()
1.215 @@ -637,9 +622,8 @@
1.216 for request in requests:
1.217 obj = self._get_object(request)
1.218 if obj:
1.219 - details = self._get_details(obj)
1.220 self.page.li()
1.221 - self.page.a(get_value(details, "SUMMARY"), href="#request-%s" % request)
1.222 + self.page.a(obj.get_value("SUMMARY"), href="#request-%s" % request)
1.223 self.page.li.close()
1.224
1.225 self.page.ul.close()
1.226 @@ -1020,8 +1004,7 @@
1.227 if not obj:
1.228 page.span("")
1.229 else:
1.230 - details = self._get_details(obj)
1.231 - summary = get_value(details, "SUMMARY")
1.232 + summary = obj.get_value("SUMMARY")
1.233
1.234 # Only link to events if they are not being
1.235 # updated by requests.
2.1 --- a/imip_store.py Sat Jan 31 22:34:12 2015 +0100
2.2 +++ b/imip_store.py Sun Feb 01 01:59:37 2015 +0100
2.3 @@ -21,26 +21,10 @@
2.4
2.5 from datetime import datetime
2.6 from imiptools.config import STORE_DIR, PUBLISH_DIR
2.7 +from imiptools.data import make_calendar, to_stream
2.8 from imiptools.filesys import fix_permissions, FileBase
2.9 from os.path import exists, isfile, join
2.10 from os import listdir
2.11 -from vCalendar import iterwrite
2.12 -
2.13 -def make_calendar(fragment, method=None):
2.14 -
2.15 - """
2.16 - Return a complete calendar item wrapping the given 'fragment' and employing
2.17 - the given 'method', if indicated.
2.18 - """
2.19 -
2.20 - return ("VCALENDAR", {},
2.21 - (method and [("METHOD", {}, method)] or []) +
2.22 - [("VERSION", {}, "2.0")] +
2.23 - fragment
2.24 - )
2.25 -
2.26 -def to_stream(out, fragment, encoding="utf-8"):
2.27 - iterwrite(out, encoding=encoding).append(fragment)
2.28
2.29 class FileStore(FileBase):
2.30
3.1 --- a/imiptools/__init__.py Sat Jan 31 22:34:12 2015 +0100
3.2 +++ b/imiptools/__init__.py Sun Feb 01 01:59:37 2015 +0100
3.3 @@ -20,7 +20,8 @@
3.4 """
3.5
3.6 from email import message_from_file
3.7 -from imiptools.content import get_addresses, get_uri, handle_itip_part
3.8 +from imiptools.content import handle_itip_part
3.9 +from imiptools.data import get_addresses, get_uri
3.10 from imiptools.mail import Messenger
3.11 from imiptools.profile import Preferences
3.12 import sys
4.1 --- a/imiptools/content.py Sat Jan 31 22:34:12 2015 +0100
4.2 +++ b/imiptools/content.py Sun Feb 01 01:59:37 2015 +0100
4.3 @@ -23,13 +23,14 @@
4.4 from datetime import datetime, timedelta
4.5 from email.mime.text import MIMEText
4.6 from imiptools.config import MANAGER_PATH, MANAGER_URL
4.7 +from imiptools.data import Object, parse_object, \
4.8 + get_address, get_fragments, \
4.9 + get_uri, get_value, uri_dict, uri_item
4.10 from imiptools.dates import *
4.11 from imiptools.period import have_conflict, insert_period, remove_period
4.12 from pytz import timezone
4.13 from socket import gethostname
4.14 -from vCalendar import parse, ParseError, to_dict
4.15 from vRecurrence import get_parameters, get_rule
4.16 -import email.utils
4.17 import imip_store
4.18
4.19 try:
4.20 @@ -37,81 +38,6 @@
4.21 except ImportError:
4.22 from StringIO import StringIO
4.23
4.24 -# Content interpretation.
4.25 -
4.26 -def get_items(d, name, all=True):
4.27 -
4.28 - """
4.29 - Get all items from 'd' with the given 'name', returning single items if
4.30 - 'all' is specified and set to a false value and if only one value is
4.31 - present for the name. Return None if no items are found for the name or if
4.32 - many items are found but 'all' is set to a false value.
4.33 - """
4.34 -
4.35 - if d.has_key(name):
4.36 - values = d[name]
4.37 - if all:
4.38 - return values
4.39 - elif len(values) == 1:
4.40 - return values[0]
4.41 - else:
4.42 - return None
4.43 - else:
4.44 - return None
4.45 -
4.46 -def get_item(d, name):
4.47 - return get_items(d, name, False)
4.48 -
4.49 -def get_value_map(d, name):
4.50 -
4.51 - """
4.52 - Return a dictionary for all items in 'd' having the given 'name'. The
4.53 - dictionary will map values for the name to any attributes or qualifiers
4.54 - that may have been present.
4.55 - """
4.56 -
4.57 - items = get_items(d, name)
4.58 - if items:
4.59 - return dict(items)
4.60 - else:
4.61 - return {}
4.62 -
4.63 -def get_values(d, name, all=True):
4.64 - if d.has_key(name):
4.65 - values = d[name]
4.66 - if not all and len(values) == 1:
4.67 - return values[0][0]
4.68 - else:
4.69 - return map(lambda x: x[0], values)
4.70 - else:
4.71 - return None
4.72 -
4.73 -def get_value(d, name):
4.74 - return get_values(d, name, False)
4.75 -
4.76 -def get_utc_datetime(d, name):
4.77 - value, attr = get_item(d, name)
4.78 - dt = get_datetime(value, attr)
4.79 - return to_utc_datetime(dt)
4.80 -
4.81 -def get_addresses(values):
4.82 - return [address for name, address in email.utils.getaddresses(values)]
4.83 -
4.84 -def get_address(value):
4.85 - return value.lower().startswith("mailto:") and value.lower()[7:] or value
4.86 -
4.87 -def get_uri(value):
4.88 - return value.lower().startswith("mailto:") and value.lower() or ":" in value and value or "mailto:%s" % value.lower()
4.89 -
4.90 -def uri_dict(d):
4.91 - return dict([(get_uri(key), value) for key, value in d.items()])
4.92 -
4.93 -def uri_item(item):
4.94 - return get_uri(item[0]), item[1]
4.95 -
4.96 -def uri_items(items):
4.97 - return [(get_uri(value), attr) for value, attr in items]
4.98 -
4.99 def is_new_object(old_sequence, new_sequence, old_dtstamp, new_dtstamp, partstat_set):
4.100
4.101 """
4.102 @@ -144,8 +70,8 @@
4.103 to the given 'window_size' in days starting from the present moment.
4.104 """
4.105
4.106 - dtstart = get_utc_datetime(obj, "DTSTART")
4.107 - dtend = get_utc_datetime(obj, "DTEND")
4.108 + dtstart = obj.get_utc_datetime("DTSTART")
4.109 + dtend = obj.get_utc_datetime("DTEND")
4.110
4.111 # NOTE: Need also DURATION support.
4.112
4.113 @@ -160,7 +86,7 @@
4.114
4.115 # NOTE: Need also RDATE and EXDATE support.
4.116
4.117 - rrule = get_value(obj, "RRULE")
4.118 + rrule = obj.get_value("RRULE")
4.119
4.120 if rrule:
4.121 selector = get_rule(dtstart, rrule)
4.122 @@ -274,11 +200,11 @@
4.123 all_results = []
4.124
4.125 for name, cls in handlers:
4.126 - for details in get_values(itip, name) or []:
4.127 + for fragment in get_fragments(itip, name):
4.128
4.129 # Dispatch to a handler and obtain any response.
4.130
4.131 - handler = cls(details, senders, recipient, messenger)
4.132 + handler = cls(Object(fragment), senders, recipient, messenger)
4.133 results = methods[method](handler)()
4.134
4.135 # Aggregate responses for a single message.
4.136 @@ -292,52 +218,6 @@
4.137
4.138 return []
4.139
4.140 -def parse_object(f, encoding, objtype=None):
4.141 -
4.142 - """
4.143 - Parse the iTIP content from 'f' having the given 'encoding'. If 'objtype' is
4.144 - given, only objects of that type will be returned. Otherwise, the root of
4.145 - the content will be returned as a dictionary with a single key indicating
4.146 - the object type.
4.147 -
4.148 - Return None if the content was not readable or suitable.
4.149 - """
4.150 -
4.151 - try:
4.152 - try:
4.153 - doctype, attrs, elements = obj = parse(f, encoding=encoding)
4.154 - if objtype and doctype == objtype:
4.155 - return to_dict(obj)[objtype][0]
4.156 - elif not objtype:
4.157 - return to_dict(obj)
4.158 - finally:
4.159 - f.close()
4.160 -
4.161 - # NOTE: Handle parse errors properly.
4.162 -
4.163 - except (ParseError, ValueError):
4.164 - pass
4.165 -
4.166 - return None
4.167 -
4.168 -def to_part(method, calendar):
4.169 -
4.170 - """
4.171 - Write using the given 'method', the 'calendar' details to a MIME
4.172 - text/calendar part.
4.173 - """
4.174 -
4.175 - encoding = "utf-8"
4.176 - out = StringIO()
4.177 - try:
4.178 - imip_store.to_stream(out, imip_store.make_calendar(calendar, method), encoding)
4.179 - part = MIMEText(out.getvalue(), "calendar", encoding)
4.180 - part.set_param("method", method)
4.181 - return part
4.182 -
4.183 - finally:
4.184 - out.close()
4.185 -
4.186 # References to the Web interface.
4.187
4.188 def get_manager_url():
4.189 @@ -351,21 +231,21 @@
4.190
4.191 "General handler support."
4.192
4.193 - def __init__(self, details, senders=None, recipient=None, messenger=None):
4.194 + def __init__(self, obj, senders=None, recipient=None, messenger=None):
4.195
4.196 """
4.197 - Initialise the handler with the 'details' of a calendar object and the
4.198 - 'senders' and 'recipient' of the object (if specifically indicated).
4.199 + Initialise the handler with the calendar 'obj' and the 'senders' and
4.200 + 'recipient' of the object (if specifically indicated).
4.201 """
4.202
4.203 - self.details = details
4.204 + self.obj = obj
4.205 self.senders = senders and set(map(get_address, senders))
4.206 self.recipient = recipient and get_address(recipient)
4.207 self.messenger = messenger
4.208
4.209 - self.uid = self.get_value("UID")
4.210 - self.sequence = self.get_value("SEQUENCE")
4.211 - self.dtstamp = self.get_value("DTSTAMP")
4.212 + self.uid = self.obj.get_value("UID")
4.213 + self.sequence = self.obj.get_value("SEQUENCE")
4.214 + self.dtstamp = self.obj.get_value("DTSTAMP")
4.215
4.216 self.store = imip_store.FileStore()
4.217
4.218 @@ -389,26 +269,8 @@
4.219
4.220 # Access to calendar structures and other data.
4.221
4.222 - def get_items(self, name, all=True):
4.223 - return get_items(self.details, name, all)
4.224 -
4.225 - def get_item(self, name):
4.226 - return get_item(self.details, name)
4.227 -
4.228 - def get_value_map(self, name):
4.229 - return get_value_map(self.details, name)
4.230 -
4.231 - def get_values(self, name, all=True):
4.232 - return get_values(self.details, name, all)
4.233 -
4.234 - def get_value(self, name):
4.235 - return get_value(self.details, name)
4.236 -
4.237 - def get_utc_datetime(self, name):
4.238 - return get_utc_datetime(self.details, name)
4.239 -
4.240 def get_periods(self):
4.241 - return get_periods(self.details)
4.242 + return get_periods(self.obj)
4.243
4.244 def remove_from_freebusy(self, freebusy, attendee):
4.245 remove_from_freebusy(freebusy, attendee, self.uid, self.store)
4.246 @@ -417,10 +279,10 @@
4.247 remove_from_freebusy_for_other(freebusy, user, other, self.uid, self.store)
4.248
4.249 def update_freebusy(self, freebusy, attendee, periods):
4.250 - return update_freebusy(freebusy, attendee, periods, self.get_value("TRANSP"), self.uid, self.store)
4.251 + return update_freebusy(freebusy, attendee, periods, self.obj.get_value("TRANSP"), self.uid, self.store)
4.252
4.253 def update_freebusy_for_other(self, freebusy, user, other, periods):
4.254 - return update_freebusy_for_other(freebusy, user, other, periods, self.get_value("TRANSP"), self.uid, self.store)
4.255 + return update_freebusy_for_other(freebusy, user, other, periods, self.obj.get_value("TRANSP"), self.uid, self.store)
4.256
4.257 def can_schedule(self, freebusy, periods):
4.258 return can_schedule(freebusy, periods, self.uid)
4.259 @@ -470,8 +332,8 @@
4.260 Organiser and attendee identities are provided as lower case values.
4.261 """
4.262
4.263 - attendee_map = uri_dict(self.get_value_map("ATTENDEE"))
4.264 - organiser_item = uri_item(self.get_item("ORGANIZER"))
4.265 + attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))
4.266 + organiser_item = uri_item(self.obj.get_item("ORGANIZER"))
4.267
4.268 # Only provide details for attendees who sent/receive the message.
4.269
4.270 @@ -512,7 +374,7 @@
4.271
4.272 return senders
4.273
4.274 - def get_object(self, user, objtype):
4.275 + def get_object(self, user):
4.276
4.277 """
4.278 Return the stored object to which the current object refers for the
4.279 @@ -520,23 +382,23 @@
4.280 """
4.281
4.282 f = self.store.get_event(user, self.uid)
4.283 - obj = f and parse_object(f, "utf-8", objtype)
4.284 - return obj
4.285 + fragment = f and parse_object(f, "utf-8")
4.286 + return fragment and Object(fragment)
4.287
4.288 - def have_new_object(self, attendee, objtype, obj=None):
4.289 + def have_new_object(self, attendee, obj=None):
4.290
4.291 """
4.292 - Return whether the current object is new to the 'attendee' for the
4.293 - given 'objtype'.
4.294 + Return whether the current object is new to the 'attendee' (or if the
4.295 + given 'obj' is new).
4.296 """
4.297
4.298 - obj = obj or self.get_object(attendee, objtype)
4.299 + obj = obj or self.get_object(attendee)
4.300
4.301 # If found, compare SEQUENCE and potentially DTSTAMP.
4.302
4.303 if obj:
4.304 - sequence = get_value(obj, "SEQUENCE")
4.305 - dtstamp = get_value(obj, "DTSTAMP")
4.306 + sequence = obj.get_value("SEQUENCE")
4.307 + dtstamp = obj.get_value("DTSTAMP")
4.308
4.309 # If the request refers to an older version of the object, ignore
4.310 # it.
4.311 @@ -557,8 +419,8 @@
4.312 NOTE: incorporated into any new object assessment.
4.313 """
4.314
4.315 - old_attendees = get_value_map(obj, "ATTENDEE")
4.316 - new_attendees = self.get_value_map("ATTENDEE")
4.317 + old_attendees = obj.get_value_map("ATTENDEE")
4.318 + new_attendees = self.obj.get_value_map("ATTENDEE")
4.319
4.320 for attendee, attr in old_attendees.items():
4.321 old_partstat = attr.get("PARTSTAT")
4.322 @@ -576,9 +438,9 @@
4.323
4.324 "Update the DTSTAMP in the current object."
4.325
4.326 - dtstamp = self.get_utc_datetime("DTSTAMP")
4.327 + dtstamp = self.obj.get_utc_datetime("DTSTAMP")
4.328 utcnow = to_timezone(datetime.utcnow(), "UTC")
4.329 - self.details["DTSTAMP"] = [(format_datetime(dtstamp > utcnow and dtstamp or utcnow), {})]
4.330 + self.obj["DTSTAMP"] = [(format_datetime(dtstamp > utcnow and dtstamp or utcnow), {})]
4.331
4.332 # Handler registry.
4.333
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/imiptools/data.py Sun Feb 01 01:59:37 2015 +0100
5.3 @@ -0,0 +1,226 @@
5.4 +#!/usr/bin/env python
5.5 +
5.6 +"""
5.7 +Interpretation of vCalendar content.
5.8 +
5.9 +Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
5.10 +
5.11 +This program is free software; you can redistribute it and/or modify it under
5.12 +the terms of the GNU General Public License as published by the Free Software
5.13 +Foundation; either version 3 of the License, or (at your option) any later
5.14 +version.
5.15 +
5.16 +This program is distributed in the hope that it will be useful, but WITHOUT
5.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
5.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
5.19 +details.
5.20 +
5.21 +You should have received a copy of the GNU General Public License along with
5.22 +this program. If not, see <http://www.gnu.org/licenses/>.
5.23 +"""
5.24 +
5.25 +from email.mime.text import MIMEText
5.26 +from imiptools.dates import get_datetime, to_utc_datetime
5.27 +from vCalendar import iterwrite, parse, ParseError, to_dict, to_node
5.28 +import email.utils
5.29 +
5.30 +try:
5.31 + from cStringIO import StringIO
5.32 +except ImportError:
5.33 + from StringIO import StringIO
5.34 +
5.35 +class Object:
5.36 +
5.37 + "Access to calendar structures."
5.38 +
5.39 + def __init__(self, fragment):
5.40 + self.objtype, (self.details, self.attr) = fragment.items()[0]
5.41 +
5.42 + def get_items(self, name, all=True):
5.43 + return get_items(self.details, name, all)
5.44 +
5.45 + def get_item(self, name):
5.46 + return get_item(self.details, name)
5.47 +
5.48 + def get_value_map(self, name):
5.49 + return get_value_map(self.details, name)
5.50 +
5.51 + def get_values(self, name, all=True):
5.52 + return get_values(self.details, name, all)
5.53 +
5.54 + def get_value(self, name):
5.55 + return get_value(self.details, name)
5.56 +
5.57 + def get_utc_datetime(self, name):
5.58 + return get_utc_datetime(self.details, name)
5.59 +
5.60 + def to_node(self):
5.61 + return to_node({self.objtype : [(self.details, self.attr)]})
5.62 +
5.63 + def to_part(self, method):
5.64 + return to_part(method, [self.to_node()])
5.65 +
5.66 + # Direct access to the structure.
5.67 +
5.68 + def __getitem__(self, name):
5.69 + return self.details[name]
5.70 +
5.71 + def __setitem__(self, name, value):
5.72 + self.details[name] = value
5.73 +
5.74 + def __delitem__(self, name):
5.75 + del self.details[name]
5.76 +
5.77 +# Construction and serialisation.
5.78 +
5.79 +def make_calendar(nodes, method=None):
5.80 +
5.81 + """
5.82 + Return a complete calendar node wrapping the given 'nodes' and employing the
5.83 + given 'method', if indicated.
5.84 + """
5.85 +
5.86 + return ("VCALENDAR", {},
5.87 + (method and [("METHOD", {}, method)] or []) +
5.88 + [("VERSION", {}, "2.0")] +
5.89 + nodes
5.90 + )
5.91 +
5.92 +def parse_object(f, encoding, objtype=None):
5.93 +
5.94 + """
5.95 + Parse the iTIP content from 'f' having the given 'encoding'. If 'objtype' is
5.96 + given, only objects of that type will be returned. Otherwise, the root of
5.97 + the content will be returned as a dictionary with a single key indicating
5.98 + the object type.
5.99 +
5.100 + Return None if the content was not readable or suitable.
5.101 + """
5.102 +
5.103 + try:
5.104 + try:
5.105 + doctype, attrs, elements = obj = parse(f, encoding=encoding)
5.106 + if objtype and doctype == objtype:
5.107 + return to_dict(obj)[objtype][0]
5.108 + elif not objtype:
5.109 + return to_dict(obj)
5.110 + finally:
5.111 + f.close()
5.112 +
5.113 + # NOTE: Handle parse errors properly.
5.114 +
5.115 + except (ParseError, ValueError):
5.116 + pass
5.117 +
5.118 + return None
5.119 +
5.120 +def to_part(method, calendar):
5.121 +
5.122 + """
5.123 + Write using the given 'method', the 'calendar' details to a MIME
5.124 + text/calendar part.
5.125 + """
5.126 +
5.127 + encoding = "utf-8"
5.128 + out = StringIO()
5.129 + try:
5.130 + to_stream(out, make_calendar(calendar, method), encoding)
5.131 + part = MIMEText(out.getvalue(), "calendar", encoding)
5.132 + part.set_param("method", method)
5.133 + return part
5.134 +
5.135 + finally:
5.136 + out.close()
5.137 +
5.138 +def to_stream(out, fragment, encoding="utf-8"):
5.139 + iterwrite(out, encoding=encoding).append(fragment)
5.140 +
5.141 +# Structure access functions.
5.142 +
5.143 +def get_fragments(d, name):
5.144 +
5.145 + """
5.146 + Return all fragments from 'd' with the given 'name'. Each fragment is thus
5.147 + suitable for using to initialise Object instances.
5.148 + """
5.149 +
5.150 + fragments = []
5.151 + if d.has_key(name):
5.152 + for value in d[name]:
5.153 + fragments.append(dict([(name, value)]))
5.154 + return fragments
5.155 +
5.156 +def get_items(d, name, all=True):
5.157 +
5.158 + """
5.159 + Get all items from 'd' for the given 'name', returning single items if
5.160 + 'all' is specified and set to a false value and if only one value is
5.161 + present for the name. Return None if no items are found for the name or if
5.162 + many items are found but 'all' is set to a false value.
5.163 + """
5.164 +
5.165 + if d.has_key(name):
5.166 + values = d[name]
5.167 + if all:
5.168 + return values
5.169 + elif len(values) == 1:
5.170 + return values[0]
5.171 + else:
5.172 + return None
5.173 + else:
5.174 + return None
5.175 +
5.176 +def get_item(d, name):
5.177 + return get_items(d, name, False)
5.178 +
5.179 +def get_value_map(d, name):
5.180 +
5.181 + """
5.182 + Return a dictionary for all items in 'd' having the given 'name'. The
5.183 + dictionary will map values for the name to any attributes or qualifiers
5.184 + that may have been present.
5.185 + """
5.186 +
5.187 + items = get_items(d, name)
5.188 + if items:
5.189 + return dict(items)
5.190 + else:
5.191 + return {}
5.192 +
5.193 +def get_values(d, name, all=True):
5.194 + if d.has_key(name):
5.195 + values = d[name]
5.196 + if not all and len(values) == 1:
5.197 + return values[0][0]
5.198 + else:
5.199 + return map(lambda x: x[0], values)
5.200 + else:
5.201 + return None
5.202 +
5.203 +def get_value(d, name):
5.204 + return get_values(d, name, False)
5.205 +
5.206 +def get_utc_datetime(d, name):
5.207 + value, attr = get_item(d, name)
5.208 + dt = get_datetime(value, attr)
5.209 + return to_utc_datetime(dt)
5.210 +
5.211 +def get_addresses(values):
5.212 + return [address for name, address in email.utils.getaddresses(values)]
5.213 +
5.214 +def get_address(value):
5.215 + return value.lower().startswith("mailto:") and value.lower()[7:] or value
5.216 +
5.217 +def get_uri(value):
5.218 + return value.lower().startswith("mailto:") and value.lower() or ":" in value and value or "mailto:%s" % value.lower()
5.219 +
5.220 +def uri_dict(d):
5.221 + return dict([(get_uri(key), value) for key, value in d.items()])
5.222 +
5.223 +def uri_item(item):
5.224 + return get_uri(item[0]), item[1]
5.225 +
5.226 +def uri_items(items):
5.227 + return [(get_uri(value), attr) for value, attr in items]
5.228 +
5.229 +# vim: tabstop=4 expandtab shiftwidth=4
6.1 --- a/imiptools/handlers/common.py Sat Jan 31 22:34:12 2015 +0100
6.2 +++ b/imiptools/handlers/common.py Sun Feb 01 01:59:37 2015 +0100
6.3 @@ -19,7 +19,7 @@
6.4 this program. If not, see <http://www.gnu.org/licenses/>.
6.5 """
6.6
6.7 -from imiptools.content import to_part
6.8 +from imiptools.data import to_part
6.9
6.10 class SupportFreebusy:
6.11
7.1 --- a/imiptools/handlers/person.py Sat Jan 31 22:34:12 2015 +0100
7.2 +++ b/imiptools/handlers/person.py Sun Feb 01 01:59:37 2015 +0100
7.3 @@ -19,16 +19,16 @@
7.4 this program. If not, see <http://www.gnu.org/licenses/>.
7.5 """
7.6
7.7 -from imiptools.content import Handler, get_address, get_uri, to_part, uri_dict, uri_items
7.8 +from imiptools.content import Handler
7.9 +from imiptools.data import get_address, get_uri, uri_dict, uri_items
7.10 from imiptools.handlers.common import CommonFreebusy, SupportFreebusy
7.11 from imiptools.profile import Preferences
7.12 -from vCalendar import to_node
7.13
7.14 class PersonHandler(Handler):
7.15
7.16 "Handling mechanisms specific to people."
7.17
7.18 - def _record_and_deliver(self, objtype, from_organiser=True, queue=False, cancel=False):
7.19 + def _record_and_deliver(self, from_organiser=True, queue=False, cancel=False):
7.20
7.21 oa = self.require_organiser_and_attendees(from_organiser)
7.22 if not oa:
7.23 @@ -44,7 +44,7 @@
7.24
7.25 for attendee, attendee_attr in attendees.items():
7.26
7.27 - if not self.have_new_object(attendee, objtype):
7.28 + if not self.have_new_object(attendee):
7.29 continue
7.30
7.31 # Record other party free/busy information.
7.32 @@ -59,9 +59,7 @@
7.33
7.34 # Store the object and queue any request.
7.35
7.36 - self.store.set_event(attendee, self.uid, to_node(
7.37 - {objtype : [(self.details, {})]}
7.38 - ))
7.39 + self.store.set_event(attendee, self.uid, self.obj.to_node())
7.40
7.41 if queue:
7.42 self.store.queue_request(attendee, self.uid)
7.43 @@ -71,13 +69,13 @@
7.44 # As organiser, update attendance.
7.45
7.46 else:
7.47 - obj = self.get_object(organiser, objtype)
7.48 + obj = self.get_object(organiser)
7.49
7.50 - if obj and self.have_new_object(organiser, objtype, obj):
7.51 + if obj and self.have_new_object(organiser, obj=obj):
7.52
7.53 # Get attendee details in a usable form.
7.54
7.55 - attendee_map = uri_dict(self.get_value_map("ATTENDEE"))
7.56 + attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))
7.57
7.58 for attendee, attendee_attr in attendees.items():
7.59
7.60 @@ -99,9 +97,7 @@
7.61
7.62 obj["ATTENDEE"] = attendee_map.items()
7.63
7.64 - self.store.set_event(organiser, self.uid, to_node(
7.65 - {objtype : [(obj, {})]}
7.66 - ))
7.67 + self.store.set_event(organiser, self.uid, obj.to_node())
7.68
7.69 return True
7.70
7.71 @@ -111,7 +107,7 @@
7.72
7.73 freebusy = []
7.74
7.75 - for value in self.get_values("FREEBUSY") or []:
7.76 + for value in self.obj.get_values("FREEBUSY") or []:
7.77 if not isinstance(value, list):
7.78 value = [value]
7.79 for v in value:
7.80 @@ -121,7 +117,7 @@
7.81 except ValueError:
7.82 pass
7.83
7.84 - for sender, sender_attr in uri_items(self.get_items(from_organiser and "ORGANIZER" or "ATTENDEE")):
7.85 + for sender, sender_attr in uri_items(self.obj.get_items(from_organiser and "ORGANIZER" or "ATTENDEE")):
7.86 self.store.set_freebusy_for_other(get_uri(self.recipient), freebusy, sender)
7.87
7.88 class Event(PersonHandler, SupportFreebusy):
7.89 @@ -138,7 +134,7 @@
7.90
7.91 "Queue a cancellation of any active event."
7.92
7.93 - self._record_and_deliver("VEVENT", from_organiser=True, queue=False, cancel=True)
7.94 + self._record_and_deliver(from_organiser=True, queue=False, cancel=True)
7.95 return self.wrap("A cancellation has been received.", link=False)
7.96
7.97 def counter(self):
7.98 @@ -157,28 +153,28 @@
7.99
7.100 "Register details of any relevant event."
7.101
7.102 - self._record_and_deliver("VEVENT", from_organiser=True, queue=False)
7.103 + self._record_and_deliver(from_organiser=True, queue=False)
7.104 return self.wrap("Details of an event have been received.")
7.105
7.106 def refresh(self):
7.107
7.108 "Update details of any active event."
7.109
7.110 - self._record_and_deliver("VEVENT", from_organiser=True, queue=False)
7.111 + self._record_and_deliver(from_organiser=True, queue=False)
7.112 return self.wrap("An event update has been received.")
7.113
7.114 def reply(self):
7.115
7.116 "Record replies and notify the recipient."
7.117
7.118 - self._record_and_deliver("VEVENT", from_organiser=False, queue=False)
7.119 + self._record_and_deliver(from_organiser=False, queue=False)
7.120 return self.wrap("A reply has been received.")
7.121
7.122 def request(self):
7.123
7.124 "Hold requests and notify the recipient."
7.125
7.126 - self._record_and_deliver("VEVENT", from_organiser=True, queue=True)
7.127 + self._record_and_deliver(from_organiser=True, queue=True)
7.128
7.129 # Produce free/busy information if configured to do so.
7.130
7.131 @@ -253,7 +249,7 @@
7.132
7.133 # NOTE: Register details of any relevant entry.
7.134
7.135 - self._record_and_deliver("VJOURNAL", from_organiser=True, queue=False)
7.136 + self._record_and_deliver(from_organiser=True, queue=False)
7.137 return self.wrap("Details of a journal entry have been received.")
7.138
7.139 class Todo(PersonHandler):
7.140 @@ -288,28 +284,28 @@
7.141
7.142 "Register details of any relevant item."
7.143
7.144 - self._record_and_deliver("VTODO", from_organiser=True, queue=False)
7.145 + self._record_and_deliver(from_organiser=True, queue=False)
7.146 return self.wrap("Details of an item have been received.")
7.147
7.148 def refresh(self):
7.149
7.150 "Update details of any active item."
7.151
7.152 - self._record_and_deliver("VTODO", from_organiser=True, queue=False)
7.153 + self._record_and_deliver(from_organiser=True, queue=False)
7.154 return self.wrap("An item update has been received.")
7.155
7.156 def reply(self):
7.157
7.158 "Record replies and notify the recipient."
7.159
7.160 - self._record_and_deliver("VTODO", from_organiser=False, queue=False)
7.161 + self._record_and_deliver(from_organiser=False, queue=False)
7.162 return self.wrap("A reply has been received.")
7.163
7.164 def request(self):
7.165
7.166 "Hold requests and notify the recipient."
7.167
7.168 - self._record_and_deliver("VTODO", from_organiser=True, queue=True)
7.169 + self._record_and_deliver(from_organiser=True, queue=True)
7.170 return self.wrap("A request has been received.")
7.171
7.172 # Handler registry.
8.1 --- a/imiptools/handlers/person_outgoing.py Sat Jan 31 22:34:12 2015 +0100
8.2 +++ b/imiptools/handlers/person_outgoing.py Sun Feb 01 01:59:37 2015 +0100
8.3 @@ -20,34 +20,34 @@
8.4 this program. If not, see <http://www.gnu.org/licenses/>.
8.5 """
8.6
8.7 -from imiptools.content import Handler, uri_item
8.8 -from vCalendar import to_node
8.9 +from imiptools.content import Handler
8.10 +from imiptools.data import uri_item
8.11
8.12 class PersonHandler(Handler):
8.13
8.14 "Handling mechanisms specific to people."
8.15
8.16 - def _get_identity(self, objtype, from_organiser=True):
8.17 + def _get_identity(self, from_organiser=True):
8.18
8.19 """
8.20 Get the identity of interest in a usable form for any unprocessed
8.21 object.
8.22 """
8.23
8.24 - identity, attr = item = uri_item(self.get_item(from_organiser and "ORGANIZER" or "ATTENDEE"))
8.25 + identity, attr = item = uri_item(self.obj.get_item(from_organiser and "ORGANIZER" or "ATTENDEE"))
8.26
8.27 # Check for event using UID.
8.28
8.29 - if not self.have_new_object(identity, objtype):
8.30 + if not self.have_new_object(identity):
8.31 return None
8.32
8.33 return item
8.34
8.35 - def _record(self, objtype, from_organiser=True, update_freebusy=False):
8.36 + def _record(self, from_organiser=True, update_freebusy=False):
8.37
8.38 "Record free/busy and object information."
8.39
8.40 - item = self._get_identity(objtype, from_organiser)
8.41 + item = self._get_identity(from_organiser)
8.42 if not item:
8.43 return False
8.44
8.45 @@ -55,9 +55,7 @@
8.46
8.47 # Store the object.
8.48
8.49 - self.store.set_event(identity, self.uid, to_node(
8.50 - {objtype : [(self.details, {})]}
8.51 - ))
8.52 + self.store.set_event(identity, self.uid, self.obj.to_node())
8.53
8.54 # Remove any associated request.
8.55
8.56 @@ -83,11 +81,11 @@
8.57
8.58 return True
8.59
8.60 - def _remove(self, objtype, from_organiser=True):
8.61 + def _remove(self, from_organiser=True):
8.62
8.63 "Remove free/busy information for any unprocessed object."
8.64
8.65 - item = self._get_identity(objtype, from_organiser)
8.66 + item = self._get_identity(from_organiser)
8.67 if not item:
8.68 return False
8.69
8.70 @@ -106,7 +104,7 @@
8.71 pass
8.72
8.73 def cancel(self):
8.74 - self._remove("VEVENT", True)
8.75 + self._remove(True)
8.76
8.77 def counter(self):
8.78 pass
8.79 @@ -115,16 +113,16 @@
8.80 pass
8.81
8.82 def publish(self):
8.83 - self._record("VEVENT", True, True)
8.84 + self._record(True, True)
8.85
8.86 def refresh(self):
8.87 - self._record("VEVENT", True, True)
8.88 + self._record(True, True)
8.89
8.90 def reply(self):
8.91 - self._record("VEVENT", False, True)
8.92 + self._record(False, True)
8.93
8.94 def request(self):
8.95 - self._record("VEVENT", True, True)
8.96 + self._record(True, True)
8.97
8.98 class Freebusy(PersonHandler):
8.99
8.100 @@ -147,10 +145,10 @@
8.101 pass
8.102
8.103 def cancel(self):
8.104 - self._remove("VJOURNAL", True)
8.105 + self._remove(True)
8.106
8.107 def publish(self):
8.108 - self._record("VJOURNAL", True)
8.109 + self._record(True)
8.110
8.111 class Todo(PersonHandler):
8.112
8.113 @@ -160,7 +158,7 @@
8.114 pass
8.115
8.116 def cancel(self):
8.117 - self._remove("VTODO", True)
8.118 + self._remove(True)
8.119
8.120 def counter(self):
8.121 pass
8.122 @@ -169,16 +167,16 @@
8.123 pass
8.124
8.125 def publish(self):
8.126 - self._record("VTODO", True)
8.127 + self._record(True)
8.128
8.129 def refresh(self):
8.130 - self._record("VTODO", True)
8.131 + self._record(True)
8.132
8.133 def reply(self):
8.134 - self._record("VTODO", False)
8.135 + self._record(False)
8.136
8.137 def request(self):
8.138 - self._record("VTODO", True)
8.139 + self._record(True)
8.140
8.141 # Handler registry.
8.142
9.1 --- a/imiptools/handlers/resource.py Sat Jan 31 22:34:12 2015 +0100
9.2 +++ b/imiptools/handlers/resource.py Sun Feb 01 01:59:37 2015 +0100
9.3 @@ -19,10 +19,9 @@
9.4 this program. If not, see <http://www.gnu.org/licenses/>.
9.5 """
9.6
9.7 -from imiptools.content import Handler, format_datetime, get_address, get_uri, \
9.8 - to_part
9.9 +from imiptools.content import Handler
9.10 +from imiptools.data import get_address, get_uri, to_part
9.11 from imiptools.handlers.common import CommonFreebusy
9.12 -from vCalendar import to_node
9.13
9.14 class ResourceHandler(Handler):
9.15
9.16 @@ -44,7 +43,7 @@
9.17
9.18 # Check for event using UID.
9.19
9.20 - if not self.have_new_object(attendee, "VEVENT"):
9.21 + if not self.have_new_object(attendee):
9.22 continue
9.23
9.24 # Collect response objects produced when handling the request.
9.25 @@ -70,13 +69,13 @@
9.26
9.27 # Make a version of the request with just this attendee.
9.28
9.29 - self.details["ATTENDEE"] = [(attendee, attendee_attr)]
9.30 + self.obj["ATTENDEE"] = [(attendee, attendee_attr)]
9.31
9.32 # Update the DTSTAMP.
9.33
9.34 self.update_dtstamp()
9.35
9.36 - event = to_node({"VEVENT" : [(self.details, {})]})
9.37 + event = self.obj.to_node()
9.38 self.store.set_event(attendee, self.uid, event)
9.39
9.40 # Only update free/busy details if the event is scheduled.