# HG changeset patch # User Paul Boddie # Date 1422752377 -3600 # Node ID 7233b6cee1b0b0377230735b9214b648e1e1ea35 # Parent 7dc75bb19e9111a3289fa07dbc430c3aceaca65c 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. diff -r 7dc75bb19e91 -r 7233b6cee1b0 imip_manager.py --- a/imip_manager.py Sat Jan 31 22:34:12 2015 +0100 +++ b/imip_manager.py Sun Feb 01 01:59:37 2015 +0100 @@ -30,10 +30,8 @@ sys.path.append(LIBRARY_PATH) -from imiptools.content import Handler, get_address, \ - get_item, get_items, get_uri, get_utc_datetime, \ - get_value, get_value_map, get_values, \ - parse_object, to_part +from imiptools.content import Handler +from imiptools.data import get_address, get_uri, parse_object, Object from imiptools.dates import format_datetime, get_datetime, get_start_of_day, \ get_end_of_day, ends_on_same_day, to_timezone from imiptools.mail import Messenger @@ -42,9 +40,8 @@ get_scale, have_conflict, get_slots, get_spans, \ partition_by_day from imiptools.profile import Preferences -from vCalendar import to_node +import imip_store import markup -import imip_store getenv = os.environ.get setenv = os.environ.__setitem__ @@ -116,14 +113,11 @@ """ def __init__(self, obj, user, messenger): - details, details_attr = obj.values()[0] - Handler.__init__(self, details) - self.obj = obj + Handler.__init__(self, obj, messenger=messenger) self.user = user - self.messenger = messenger - self.organiser = self.get_value("ORGANIZER") - self.attendees = self.get_values("ATTENDEE") + self.organiser = self.obj.get_value("ORGANIZER") + self.attendees = self.obj.get_values("ATTENDEE") # Communication methods. @@ -134,8 +128,7 @@ to the appropriate recipients, also sending a copy to the 'sender'. """ - node = to_node(self.obj) - part = to_part(method, [node]) + part = self.obj.to_part(method) if self.user == self.organiser: recipients = map(get_address, self.attendees) @@ -161,16 +154,16 @@ # When accepting or declining, do so only on behalf of this user, # preserving any other attributes set as an attendee. - for attendee, attendee_attr in self.get_items("ATTENDEE"): + for attendee, attendee_attr in self.obj.get_items("ATTENDEE"): if attendee == self.user: attendee_attr["PARTSTAT"] = accept and "ACCEPTED" or "DECLINED" if self.messenger and self.messenger.sender != get_address(attendee): attendee_attr["SENT-BY"] = get_uri(self.messenger.sender) - self.details["ATTENDEE"] = [(attendee, attendee_attr)] + self.obj["ATTENDEE"] = [(attendee, attendee_attr)] if update: - sequence = self.get_value("SEQUENCE") or "0" - self.details["SEQUENCE"] = [(str(int(sequence) + 1), {})] + sequence = self.obj.get_value("SEQUENCE") or "0" + self.obj["SEQUENCE"] = [(str(int(sequence) + 1), {})] self.update_dtstamp() self.send_message("REPLY", get_address(attendee)) @@ -189,9 +182,13 @@ to override any previous message. """ + organiser, organiser_attr = self.obj.get_item("ORGANIZER") + + if self.messenger and self.messenger.sender != get_address(organiser): + organiser_attr["SENT-BY"] = get_uri(self.messenger.sender) if update: - sequence = self.get_value("SEQUENCE") or "0" - self.details["SEQUENCE"] = [(str(int(sequence) + 1), {})] + sequence = self.obj.get_value("SEQUENCE") or "0" + self.obj["SEQUENCE"] = [(str(int(sequence) + 1), {})] self.update_dtstamp() self.send_message("REQUEST", get_address(self.organiser)) @@ -237,17 +234,11 @@ if not f: return None - self.objects[uid] = obj = parse_object(f, "utf-8") - - if not obj: - return None + fragment = parse_object(f, "utf-8") + obj = self.objects[uid] = fragment and Object(fragment) return obj - def _get_details(self, obj): - details, details_attr = obj.values()[0] - return details - def _get_requests(self): if self.requests is None: self.requests = self.store.get_requests(self.user) @@ -258,10 +249,9 @@ for uid in self._get_requests(): obj = self._get_object(uid) if obj: - details = self._get_details(obj) summary.append(( - get_value(details, "DTSTART"), - get_value(details, "DTEND"), + obj.get_value("DTSTART"), + obj.get_value("DTEND"), uid )) return summary @@ -406,8 +396,7 @@ # Update the object. if args.has_key("summary"): - details = self._get_details(obj) - details["SUMMARY"] = [(args["summary"][0], {})] + obj["SUMMARY"] = [(args["summary"][0], {})] # Process any action. @@ -452,11 +441,10 @@ page = self.page - details = self._get_details(obj) - is_organiser = get_value(details, "ORGANIZER") == self.user + is_organiser = obj.get_value("ORGANIZER") == self.user if not is_organiser: - attendees = get_value_map(details, "ATTENDEE") + attendees = obj.get_value_map("ATTENDEE") attendee_attr = attendees.get(self.user) if attendee_attr: @@ -519,8 +507,6 @@ # Provide a summary of the object. - details = self._get_details(obj) - page.table(id="object", cellspacing=5, cellpadding=5) page.thead() page.tr() @@ -537,7 +523,7 @@ # Handle datetimes specially. if name in ["DTSTART", "DTEND"]: - value, attr = get_item(details, name) + value, attr = obj.get_item(name) tzid = attr.get("TZID", tzid) value = self.format_datetime(to_timezone(get_datetime(value), tzid), "full") page.th(label, class_="objectheading") @@ -547,7 +533,7 @@ # Handle the summary specially. elif name == "SUMMARY": - value = get_value(details, name) + value = obj.get_value(name) page.th(label, class_="objectheading") page.td() page.input(name="summary", type="text", value=value, size=80) @@ -557,7 +543,7 @@ # Handle potentially many values. else: - items = get_items(details, name) + items = obj.get_items(name) page.th(label, class_="objectheading", rowspan=len(items)) first = True @@ -582,8 +568,8 @@ page.tbody.close() page.table.close() - dtstart = format_datetime(get_utc_datetime(details, "DTSTART")) - dtend = format_datetime(get_utc_datetime(details, "DTEND")) + dtstart = format_datetime(obj.get_utc_datetime("DTSTART")) + dtend = format_datetime(obj.get_utc_datetime("DTEND")) # Indicate whether there are conflicting events. @@ -593,7 +579,7 @@ # Obtain any time zone details from the suggested event. - _dtstart, attr = get_item(details, "DTSTART") + _dtstart, attr = obj.get_item("DTSTART") tzid = attr.get("TZID", tzid) # Show any conflicts. @@ -612,8 +598,7 @@ found_obj = self._get_object(found_uid) if found_obj: - found_details = self._get_details(found_obj) - page.a(get_value(found_details, "SUMMARY"), href=self.env.new_url(found_uid)) + page.a(found_obj.get_value("SUMMARY"), href=self.env.new_url(found_uid)) self.show_request_controls(obj, needs_action) page.form.close() @@ -637,9 +622,8 @@ for request in requests: obj = self._get_object(request) if obj: - details = self._get_details(obj) self.page.li() - self.page.a(get_value(details, "SUMMARY"), href="#request-%s" % request) + self.page.a(obj.get_value("SUMMARY"), href="#request-%s" % request) self.page.li.close() self.page.ul.close() @@ -1020,8 +1004,7 @@ if not obj: page.span("") else: - details = self._get_details(obj) - summary = get_value(details, "SUMMARY") + summary = obj.get_value("SUMMARY") # Only link to events if they are not being # updated by requests. diff -r 7dc75bb19e91 -r 7233b6cee1b0 imip_store.py --- a/imip_store.py Sat Jan 31 22:34:12 2015 +0100 +++ b/imip_store.py Sun Feb 01 01:59:37 2015 +0100 @@ -21,26 +21,10 @@ from datetime import datetime from imiptools.config import STORE_DIR, PUBLISH_DIR +from imiptools.data import make_calendar, to_stream from imiptools.filesys import fix_permissions, FileBase from os.path import exists, isfile, join from os import listdir -from vCalendar import iterwrite - -def make_calendar(fragment, method=None): - - """ - Return a complete calendar item wrapping the given 'fragment' and employing - the given 'method', if indicated. - """ - - return ("VCALENDAR", {}, - (method and [("METHOD", {}, method)] or []) + - [("VERSION", {}, "2.0")] + - fragment - ) - -def to_stream(out, fragment, encoding="utf-8"): - iterwrite(out, encoding=encoding).append(fragment) class FileStore(FileBase): diff -r 7dc75bb19e91 -r 7233b6cee1b0 imiptools/__init__.py --- a/imiptools/__init__.py Sat Jan 31 22:34:12 2015 +0100 +++ b/imiptools/__init__.py Sun Feb 01 01:59:37 2015 +0100 @@ -20,7 +20,8 @@ """ from email import message_from_file -from imiptools.content import get_addresses, get_uri, handle_itip_part +from imiptools.content import handle_itip_part +from imiptools.data import get_addresses, get_uri from imiptools.mail import Messenger from imiptools.profile import Preferences import sys diff -r 7dc75bb19e91 -r 7233b6cee1b0 imiptools/content.py --- a/imiptools/content.py Sat Jan 31 22:34:12 2015 +0100 +++ b/imiptools/content.py Sun Feb 01 01:59:37 2015 +0100 @@ -23,13 +23,14 @@ from datetime import datetime, timedelta from email.mime.text import MIMEText from imiptools.config import MANAGER_PATH, MANAGER_URL +from imiptools.data import Object, parse_object, \ + get_address, get_fragments, \ + get_uri, get_value, uri_dict, uri_item from imiptools.dates import * from imiptools.period import have_conflict, insert_period, remove_period from pytz import timezone from socket import gethostname -from vCalendar import parse, ParseError, to_dict from vRecurrence import get_parameters, get_rule -import email.utils import imip_store try: @@ -37,81 +38,6 @@ except ImportError: from StringIO import StringIO -# Content interpretation. - -def get_items(d, name, all=True): - - """ - Get all items from 'd' with the given 'name', returning single items if - 'all' is specified and set to a false value and if only one value is - present for the name. Return None if no items are found for the name or if - many items are found but 'all' is set to a false value. - """ - - if d.has_key(name): - values = d[name] - if all: - return values - elif len(values) == 1: - return values[0] - else: - return None - else: - return None - -def get_item(d, name): - return get_items(d, name, False) - -def get_value_map(d, name): - - """ - Return a dictionary for all items in 'd' having the given 'name'. The - dictionary will map values for the name to any attributes or qualifiers - that may have been present. - """ - - items = get_items(d, name) - if items: - return dict(items) - else: - return {} - -def get_values(d, name, all=True): - if d.has_key(name): - values = d[name] - if not all and len(values) == 1: - return values[0][0] - else: - return map(lambda x: x[0], values) - else: - return None - -def get_value(d, name): - return get_values(d, name, False) - -def get_utc_datetime(d, name): - value, attr = get_item(d, name) - dt = get_datetime(value, attr) - return to_utc_datetime(dt) - -def get_addresses(values): - return [address for name, address in email.utils.getaddresses(values)] - -def get_address(value): - return value.lower().startswith("mailto:") and value.lower()[7:] or value - -def get_uri(value): - return value.lower().startswith("mailto:") and value.lower() or ":" in value and value or "mailto:%s" % value.lower() - -def uri_dict(d): - return dict([(get_uri(key), value) for key, value in d.items()]) - -def uri_item(item): - return get_uri(item[0]), item[1] - -def uri_items(items): - return [(get_uri(value), attr) for value, attr in items] - def is_new_object(old_sequence, new_sequence, old_dtstamp, new_dtstamp, partstat_set): """ @@ -144,8 +70,8 @@ to the given 'window_size' in days starting from the present moment. """ - dtstart = get_utc_datetime(obj, "DTSTART") - dtend = get_utc_datetime(obj, "DTEND") + dtstart = obj.get_utc_datetime("DTSTART") + dtend = obj.get_utc_datetime("DTEND") # NOTE: Need also DURATION support. @@ -160,7 +86,7 @@ # NOTE: Need also RDATE and EXDATE support. - rrule = get_value(obj, "RRULE") + rrule = obj.get_value("RRULE") if rrule: selector = get_rule(dtstart, rrule) @@ -274,11 +200,11 @@ all_results = [] for name, cls in handlers: - for details in get_values(itip, name) or []: + for fragment in get_fragments(itip, name): # Dispatch to a handler and obtain any response. - handler = cls(details, senders, recipient, messenger) + handler = cls(Object(fragment), senders, recipient, messenger) results = methods[method](handler)() # Aggregate responses for a single message. @@ -292,52 +218,6 @@ return [] -def parse_object(f, encoding, objtype=None): - - """ - Parse the iTIP content from 'f' having the given 'encoding'. If 'objtype' is - given, only objects of that type will be returned. Otherwise, the root of - the content will be returned as a dictionary with a single key indicating - the object type. - - Return None if the content was not readable or suitable. - """ - - try: - try: - doctype, attrs, elements = obj = parse(f, encoding=encoding) - if objtype and doctype == objtype: - return to_dict(obj)[objtype][0] - elif not objtype: - return to_dict(obj) - finally: - f.close() - - # NOTE: Handle parse errors properly. - - except (ParseError, ValueError): - pass - - return None - -def to_part(method, calendar): - - """ - Write using the given 'method', the 'calendar' details to a MIME - text/calendar part. - """ - - encoding = "utf-8" - out = StringIO() - try: - imip_store.to_stream(out, imip_store.make_calendar(calendar, method), encoding) - part = MIMEText(out.getvalue(), "calendar", encoding) - part.set_param("method", method) - return part - - finally: - out.close() - # References to the Web interface. def get_manager_url(): @@ -351,21 +231,21 @@ "General handler support." - def __init__(self, details, senders=None, recipient=None, messenger=None): + def __init__(self, obj, senders=None, recipient=None, messenger=None): """ - Initialise the handler with the 'details' of a calendar object and the - 'senders' and 'recipient' of the object (if specifically indicated). + Initialise the handler with the calendar 'obj' and the 'senders' and + 'recipient' of the object (if specifically indicated). """ - self.details = details + self.obj = obj self.senders = senders and set(map(get_address, senders)) self.recipient = recipient and get_address(recipient) self.messenger = messenger - self.uid = self.get_value("UID") - self.sequence = self.get_value("SEQUENCE") - self.dtstamp = self.get_value("DTSTAMP") + self.uid = self.obj.get_value("UID") + self.sequence = self.obj.get_value("SEQUENCE") + self.dtstamp = self.obj.get_value("DTSTAMP") self.store = imip_store.FileStore() @@ -389,26 +269,8 @@ # Access to calendar structures and other data. - def get_items(self, name, all=True): - return get_items(self.details, name, all) - - def get_item(self, name): - return get_item(self.details, name) - - def get_value_map(self, name): - return get_value_map(self.details, name) - - def get_values(self, name, all=True): - return get_values(self.details, name, all) - - def get_value(self, name): - return get_value(self.details, name) - - def get_utc_datetime(self, name): - return get_utc_datetime(self.details, name) - def get_periods(self): - return get_periods(self.details) + return get_periods(self.obj) def remove_from_freebusy(self, freebusy, attendee): remove_from_freebusy(freebusy, attendee, self.uid, self.store) @@ -417,10 +279,10 @@ remove_from_freebusy_for_other(freebusy, user, other, self.uid, self.store) def update_freebusy(self, freebusy, attendee, periods): - return update_freebusy(freebusy, attendee, periods, self.get_value("TRANSP"), self.uid, self.store) + return update_freebusy(freebusy, attendee, periods, self.obj.get_value("TRANSP"), self.uid, self.store) def update_freebusy_for_other(self, freebusy, user, other, periods): - return update_freebusy_for_other(freebusy, user, other, periods, self.get_value("TRANSP"), self.uid, self.store) + return update_freebusy_for_other(freebusy, user, other, periods, self.obj.get_value("TRANSP"), self.uid, self.store) def can_schedule(self, freebusy, periods): return can_schedule(freebusy, periods, self.uid) @@ -470,8 +332,8 @@ Organiser and attendee identities are provided as lower case values. """ - attendee_map = uri_dict(self.get_value_map("ATTENDEE")) - organiser_item = uri_item(self.get_item("ORGANIZER")) + attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) + organiser_item = uri_item(self.obj.get_item("ORGANIZER")) # Only provide details for attendees who sent/receive the message. @@ -512,7 +374,7 @@ return senders - def get_object(self, user, objtype): + def get_object(self, user): """ Return the stored object to which the current object refers for the @@ -520,23 +382,23 @@ """ f = self.store.get_event(user, self.uid) - obj = f and parse_object(f, "utf-8", objtype) - return obj + fragment = f and parse_object(f, "utf-8") + return fragment and Object(fragment) - def have_new_object(self, attendee, objtype, obj=None): + def have_new_object(self, attendee, obj=None): """ - Return whether the current object is new to the 'attendee' for the - given 'objtype'. + Return whether the current object is new to the 'attendee' (or if the + given 'obj' is new). """ - obj = obj or self.get_object(attendee, objtype) + obj = obj or self.get_object(attendee) # If found, compare SEQUENCE and potentially DTSTAMP. if obj: - sequence = get_value(obj, "SEQUENCE") - dtstamp = get_value(obj, "DTSTAMP") + sequence = obj.get_value("SEQUENCE") + dtstamp = obj.get_value("DTSTAMP") # If the request refers to an older version of the object, ignore # it. @@ -557,8 +419,8 @@ NOTE: incorporated into any new object assessment. """ - old_attendees = get_value_map(obj, "ATTENDEE") - new_attendees = self.get_value_map("ATTENDEE") + old_attendees = obj.get_value_map("ATTENDEE") + new_attendees = self.obj.get_value_map("ATTENDEE") for attendee, attr in old_attendees.items(): old_partstat = attr.get("PARTSTAT") @@ -576,9 +438,9 @@ "Update the DTSTAMP in the current object." - dtstamp = self.get_utc_datetime("DTSTAMP") + dtstamp = self.obj.get_utc_datetime("DTSTAMP") utcnow = to_timezone(datetime.utcnow(), "UTC") - self.details["DTSTAMP"] = [(format_datetime(dtstamp > utcnow and dtstamp or utcnow), {})] + self.obj["DTSTAMP"] = [(format_datetime(dtstamp > utcnow and dtstamp or utcnow), {})] # Handler registry. diff -r 7dc75bb19e91 -r 7233b6cee1b0 imiptools/data.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/data.py Sun Feb 01 01:59:37 2015 +0100 @@ -0,0 +1,226 @@ +#!/usr/bin/env python + +""" +Interpretation of vCalendar content. + +Copyright (C) 2014, 2015 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from email.mime.text import MIMEText +from imiptools.dates import get_datetime, to_utc_datetime +from vCalendar import iterwrite, parse, ParseError, to_dict, to_node +import email.utils + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +class Object: + + "Access to calendar structures." + + def __init__(self, fragment): + self.objtype, (self.details, self.attr) = fragment.items()[0] + + def get_items(self, name, all=True): + return get_items(self.details, name, all) + + def get_item(self, name): + return get_item(self.details, name) + + def get_value_map(self, name): + return get_value_map(self.details, name) + + def get_values(self, name, all=True): + return get_values(self.details, name, all) + + def get_value(self, name): + return get_value(self.details, name) + + def get_utc_datetime(self, name): + return get_utc_datetime(self.details, name) + + def to_node(self): + return to_node({self.objtype : [(self.details, self.attr)]}) + + def to_part(self, method): + return to_part(method, [self.to_node()]) + + # Direct access to the structure. + + def __getitem__(self, name): + return self.details[name] + + def __setitem__(self, name, value): + self.details[name] = value + + def __delitem__(self, name): + del self.details[name] + +# Construction and serialisation. + +def make_calendar(nodes, method=None): + + """ + Return a complete calendar node wrapping the given 'nodes' and employing the + given 'method', if indicated. + """ + + return ("VCALENDAR", {}, + (method and [("METHOD", {}, method)] or []) + + [("VERSION", {}, "2.0")] + + nodes + ) + +def parse_object(f, encoding, objtype=None): + + """ + Parse the iTIP content from 'f' having the given 'encoding'. If 'objtype' is + given, only objects of that type will be returned. Otherwise, the root of + the content will be returned as a dictionary with a single key indicating + the object type. + + Return None if the content was not readable or suitable. + """ + + try: + try: + doctype, attrs, elements = obj = parse(f, encoding=encoding) + if objtype and doctype == objtype: + return to_dict(obj)[objtype][0] + elif not objtype: + return to_dict(obj) + finally: + f.close() + + # NOTE: Handle parse errors properly. + + except (ParseError, ValueError): + pass + + return None + +def to_part(method, calendar): + + """ + Write using the given 'method', the 'calendar' details to a MIME + text/calendar part. + """ + + encoding = "utf-8" + out = StringIO() + try: + to_stream(out, make_calendar(calendar, method), encoding) + part = MIMEText(out.getvalue(), "calendar", encoding) + part.set_param("method", method) + return part + + finally: + out.close() + +def to_stream(out, fragment, encoding="utf-8"): + iterwrite(out, encoding=encoding).append(fragment) + +# Structure access functions. + +def get_fragments(d, name): + + """ + Return all fragments from 'd' with the given 'name'. Each fragment is thus + suitable for using to initialise Object instances. + """ + + fragments = [] + if d.has_key(name): + for value in d[name]: + fragments.append(dict([(name, value)])) + return fragments + +def get_items(d, name, all=True): + + """ + Get all items from 'd' for the given 'name', returning single items if + 'all' is specified and set to a false value and if only one value is + present for the name. Return None if no items are found for the name or if + many items are found but 'all' is set to a false value. + """ + + if d.has_key(name): + values = d[name] + if all: + return values + elif len(values) == 1: + return values[0] + else: + return None + else: + return None + +def get_item(d, name): + return get_items(d, name, False) + +def get_value_map(d, name): + + """ + Return a dictionary for all items in 'd' having the given 'name'. The + dictionary will map values for the name to any attributes or qualifiers + that may have been present. + """ + + items = get_items(d, name) + if items: + return dict(items) + else: + return {} + +def get_values(d, name, all=True): + if d.has_key(name): + values = d[name] + if not all and len(values) == 1: + return values[0][0] + else: + return map(lambda x: x[0], values) + else: + return None + +def get_value(d, name): + return get_values(d, name, False) + +def get_utc_datetime(d, name): + value, attr = get_item(d, name) + dt = get_datetime(value, attr) + return to_utc_datetime(dt) + +def get_addresses(values): + return [address for name, address in email.utils.getaddresses(values)] + +def get_address(value): + return value.lower().startswith("mailto:") and value.lower()[7:] or value + +def get_uri(value): + return value.lower().startswith("mailto:") and value.lower() or ":" in value and value or "mailto:%s" % value.lower() + +def uri_dict(d): + return dict([(get_uri(key), value) for key, value in d.items()]) + +def uri_item(item): + return get_uri(item[0]), item[1] + +def uri_items(items): + return [(get_uri(value), attr) for value, attr in items] + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 7dc75bb19e91 -r 7233b6cee1b0 imiptools/handlers/common.py --- a/imiptools/handlers/common.py Sat Jan 31 22:34:12 2015 +0100 +++ b/imiptools/handlers/common.py Sun Feb 01 01:59:37 2015 +0100 @@ -19,7 +19,7 @@ this program. If not, see . """ -from imiptools.content import to_part +from imiptools.data import to_part class SupportFreebusy: diff -r 7dc75bb19e91 -r 7233b6cee1b0 imiptools/handlers/person.py --- a/imiptools/handlers/person.py Sat Jan 31 22:34:12 2015 +0100 +++ b/imiptools/handlers/person.py Sun Feb 01 01:59:37 2015 +0100 @@ -19,16 +19,16 @@ this program. If not, see . """ -from imiptools.content import Handler, get_address, get_uri, to_part, uri_dict, uri_items +from imiptools.content import Handler +from imiptools.data import get_address, get_uri, uri_dict, uri_items from imiptools.handlers.common import CommonFreebusy, SupportFreebusy from imiptools.profile import Preferences -from vCalendar import to_node class PersonHandler(Handler): "Handling mechanisms specific to people." - def _record_and_deliver(self, objtype, from_organiser=True, queue=False, cancel=False): + def _record_and_deliver(self, from_organiser=True, queue=False, cancel=False): oa = self.require_organiser_and_attendees(from_organiser) if not oa: @@ -44,7 +44,7 @@ for attendee, attendee_attr in attendees.items(): - if not self.have_new_object(attendee, objtype): + if not self.have_new_object(attendee): continue # Record other party free/busy information. @@ -59,9 +59,7 @@ # Store the object and queue any request. - self.store.set_event(attendee, self.uid, to_node( - {objtype : [(self.details, {})]} - )) + self.store.set_event(attendee, self.uid, self.obj.to_node()) if queue: self.store.queue_request(attendee, self.uid) @@ -71,13 +69,13 @@ # As organiser, update attendance. else: - obj = self.get_object(organiser, objtype) + obj = self.get_object(organiser) - if obj and self.have_new_object(organiser, objtype, obj): + if obj and self.have_new_object(organiser, obj=obj): # Get attendee details in a usable form. - attendee_map = uri_dict(self.get_value_map("ATTENDEE")) + attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) for attendee, attendee_attr in attendees.items(): @@ -99,9 +97,7 @@ obj["ATTENDEE"] = attendee_map.items() - self.store.set_event(organiser, self.uid, to_node( - {objtype : [(obj, {})]} - )) + self.store.set_event(organiser, self.uid, obj.to_node()) return True @@ -111,7 +107,7 @@ freebusy = [] - for value in self.get_values("FREEBUSY") or []: + for value in self.obj.get_values("FREEBUSY") or []: if not isinstance(value, list): value = [value] for v in value: @@ -121,7 +117,7 @@ except ValueError: pass - for sender, sender_attr in uri_items(self.get_items(from_organiser and "ORGANIZER" or "ATTENDEE")): + for sender, sender_attr in uri_items(self.obj.get_items(from_organiser and "ORGANIZER" or "ATTENDEE")): self.store.set_freebusy_for_other(get_uri(self.recipient), freebusy, sender) class Event(PersonHandler, SupportFreebusy): @@ -138,7 +134,7 @@ "Queue a cancellation of any active event." - self._record_and_deliver("VEVENT", from_organiser=True, queue=False, cancel=True) + self._record_and_deliver(from_organiser=True, queue=False, cancel=True) return self.wrap("A cancellation has been received.", link=False) def counter(self): @@ -157,28 +153,28 @@ "Register details of any relevant event." - self._record_and_deliver("VEVENT", from_organiser=True, queue=False) + self._record_and_deliver(from_organiser=True, queue=False) return self.wrap("Details of an event have been received.") def refresh(self): "Update details of any active event." - self._record_and_deliver("VEVENT", from_organiser=True, queue=False) + self._record_and_deliver(from_organiser=True, queue=False) return self.wrap("An event update has been received.") def reply(self): "Record replies and notify the recipient." - self._record_and_deliver("VEVENT", from_organiser=False, queue=False) + self._record_and_deliver(from_organiser=False, queue=False) return self.wrap("A reply has been received.") def request(self): "Hold requests and notify the recipient." - self._record_and_deliver("VEVENT", from_organiser=True, queue=True) + self._record_and_deliver(from_organiser=True, queue=True) # Produce free/busy information if configured to do so. @@ -253,7 +249,7 @@ # NOTE: Register details of any relevant entry. - self._record_and_deliver("VJOURNAL", from_organiser=True, queue=False) + self._record_and_deliver(from_organiser=True, queue=False) return self.wrap("Details of a journal entry have been received.") class Todo(PersonHandler): @@ -288,28 +284,28 @@ "Register details of any relevant item." - self._record_and_deliver("VTODO", from_organiser=True, queue=False) + self._record_and_deliver(from_organiser=True, queue=False) return self.wrap("Details of an item have been received.") def refresh(self): "Update details of any active item." - self._record_and_deliver("VTODO", from_organiser=True, queue=False) + self._record_and_deliver(from_organiser=True, queue=False) return self.wrap("An item update has been received.") def reply(self): "Record replies and notify the recipient." - self._record_and_deliver("VTODO", from_organiser=False, queue=False) + self._record_and_deliver(from_organiser=False, queue=False) return self.wrap("A reply has been received.") def request(self): "Hold requests and notify the recipient." - self._record_and_deliver("VTODO", from_organiser=True, queue=True) + self._record_and_deliver(from_organiser=True, queue=True) return self.wrap("A request has been received.") # Handler registry. diff -r 7dc75bb19e91 -r 7233b6cee1b0 imiptools/handlers/person_outgoing.py --- a/imiptools/handlers/person_outgoing.py Sat Jan 31 22:34:12 2015 +0100 +++ b/imiptools/handlers/person_outgoing.py Sun Feb 01 01:59:37 2015 +0100 @@ -20,34 +20,34 @@ this program. If not, see . """ -from imiptools.content import Handler, uri_item -from vCalendar import to_node +from imiptools.content import Handler +from imiptools.data import uri_item class PersonHandler(Handler): "Handling mechanisms specific to people." - def _get_identity(self, objtype, from_organiser=True): + def _get_identity(self, from_organiser=True): """ Get the identity of interest in a usable form for any unprocessed object. """ - identity, attr = item = uri_item(self.get_item(from_organiser and "ORGANIZER" or "ATTENDEE")) + identity, attr = item = uri_item(self.obj.get_item(from_organiser and "ORGANIZER" or "ATTENDEE")) # Check for event using UID. - if not self.have_new_object(identity, objtype): + if not self.have_new_object(identity): return None return item - def _record(self, objtype, from_organiser=True, update_freebusy=False): + def _record(self, from_organiser=True, update_freebusy=False): "Record free/busy and object information." - item = self._get_identity(objtype, from_organiser) + item = self._get_identity(from_organiser) if not item: return False @@ -55,9 +55,7 @@ # Store the object. - self.store.set_event(identity, self.uid, to_node( - {objtype : [(self.details, {})]} - )) + self.store.set_event(identity, self.uid, self.obj.to_node()) # Remove any associated request. @@ -83,11 +81,11 @@ return True - def _remove(self, objtype, from_organiser=True): + def _remove(self, from_organiser=True): "Remove free/busy information for any unprocessed object." - item = self._get_identity(objtype, from_organiser) + item = self._get_identity(from_organiser) if not item: return False @@ -106,7 +104,7 @@ pass def cancel(self): - self._remove("VEVENT", True) + self._remove(True) def counter(self): pass @@ -115,16 +113,16 @@ pass def publish(self): - self._record("VEVENT", True, True) + self._record(True, True) def refresh(self): - self._record("VEVENT", True, True) + self._record(True, True) def reply(self): - self._record("VEVENT", False, True) + self._record(False, True) def request(self): - self._record("VEVENT", True, True) + self._record(True, True) class Freebusy(PersonHandler): @@ -147,10 +145,10 @@ pass def cancel(self): - self._remove("VJOURNAL", True) + self._remove(True) def publish(self): - self._record("VJOURNAL", True) + self._record(True) class Todo(PersonHandler): @@ -160,7 +158,7 @@ pass def cancel(self): - self._remove("VTODO", True) + self._remove(True) def counter(self): pass @@ -169,16 +167,16 @@ pass def publish(self): - self._record("VTODO", True) + self._record(True) def refresh(self): - self._record("VTODO", True) + self._record(True) def reply(self): - self._record("VTODO", False) + self._record(False) def request(self): - self._record("VTODO", True) + self._record(True) # Handler registry. diff -r 7dc75bb19e91 -r 7233b6cee1b0 imiptools/handlers/resource.py --- a/imiptools/handlers/resource.py Sat Jan 31 22:34:12 2015 +0100 +++ b/imiptools/handlers/resource.py Sun Feb 01 01:59:37 2015 +0100 @@ -19,10 +19,9 @@ this program. If not, see . """ -from imiptools.content import Handler, format_datetime, get_address, get_uri, \ - to_part +from imiptools.content import Handler +from imiptools.data import get_address, get_uri, to_part from imiptools.handlers.common import CommonFreebusy -from vCalendar import to_node class ResourceHandler(Handler): @@ -44,7 +43,7 @@ # Check for event using UID. - if not self.have_new_object(attendee, "VEVENT"): + if not self.have_new_object(attendee): continue # Collect response objects produced when handling the request. @@ -70,13 +69,13 @@ # Make a version of the request with just this attendee. - self.details["ATTENDEE"] = [(attendee, attendee_attr)] + self.obj["ATTENDEE"] = [(attendee, attendee_attr)] # Update the DTSTAMP. self.update_dtstamp() - event = to_node({"VEVENT" : [(self.details, {})]}) + event = self.obj.to_node() self.store.set_event(attendee, self.uid, event) # Only update free/busy details if the event is scheduled.