imip-agent

imiptools/handlers/__init__.py

1176:6bc9f39224a9
2016-05-12 Paul Boddie Added initial support for delegating attendance, introducing a quota scheduling function that appoints delegates within a quota group when a recipient cannot itself attend. Changed the journal methods setting user-group mappings and quota limits to operate using entire collections rather than with individual items.
     1 #!/usr/bin/env python     2      3 """     4 General handler support for incoming calendar objects.     5      6 Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from email.mime.text import MIMEText    23 from imiptools.client import ClientForObject    24 from imiptools.config import MANAGER_PATH, MANAGER_URL, MANAGER_URL_SCHEME    25 from imiptools.data import check_delegation, get_address, get_uri, \    26                            get_sender_identities, uri_dict, uri_item    27 from socket import gethostname    28     29 # References to the Web interface.    30     31 def get_manager_url():    32     url_base = MANAGER_URL or \    33                "%s%s/" % (MANAGER_URL_SCHEME or "https://", gethostname())    34     return "%s/%s" % (url_base.rstrip("/"), MANAGER_PATH.lstrip("/"))    35     36 def get_object_url(uid, recurrenceid=None):    37     return "%s/%s%s" % (    38         get_manager_url().rstrip("/"), uid,    39         recurrenceid and "/%s" % recurrenceid or ""    40         )    41     42 class Handler(ClientForObject):    43     44     "General handler support."    45     46     def __init__(self, senders=None, recipient=None, messenger=None, store=None,    47                  publisher=None, journal=None, preferences_dir=None):    48     49         """    50         Initialise the handler with any specifically indicated 'senders' and    51         'recipient' of a calendar object. The object is initially undefined.    52     53         The optional 'messenger' provides a means of interacting with the mail    54         system.    55     56         The optional 'store', 'publisher' and 'journal' can be specified to    57         override the default store and publisher objects.    58         """    59     60         ClientForObject.__init__(self, None, recipient and get_uri(recipient),    61             messenger, store, publisher, journal, preferences_dir)    62     63         self.senders = senders and set(map(get_address, senders))    64         self.recipient = recipient and get_address(recipient)    65     66         self.results = []    67         self.outgoing_methods = set()    68     69     def wrap(self, text, link=True):    70     71         "Wrap any valid message for passing to the recipient."    72     73         _ = self.get_translator()    74     75         texts = []    76         texts.append(text)    77         if link and self.have_manager():    78             texts.append(_("If your mail program cannot handle this "    79                            "message, you may view the details here:\n\n%s") %    80                          get_object_url(self.uid, self.recurrenceid))    81     82         return self.add_result(None, None, MIMEText("\n".join(texts)))    83     84     # Result registration.    85     86     def add_result(self, method, outgoing_recipients, part):    87     88         """    89         Record a result having the given 'method', 'outgoing_recipients' and    90         message 'part'.    91         """    92     93         if outgoing_recipients:    94             self.outgoing_methods.add(method)    95         self.results.append((outgoing_recipients, part))    96     97     def add_results(self, methods, outgoing_recipients, parts):    98     99         """   100         Record results having the given 'methods', 'outgoing_recipients' and   101         message 'parts'.   102         """   103    104         if outgoing_recipients:   105             self.outgoing_methods.update(methods)   106         for part in parts:   107             self.results.append((outgoing_recipients, part))   108    109     def get_results(self):   110         return self.results   111    112     def get_outgoing_methods(self):   113         return self.outgoing_methods   114    115     # Logic, filtering and access to calendar structures and other data.   116    117     def filter_by_senders(self, mapping):   118    119         """   120         Return a list of items from 'mapping' filtered using sender information.   121         """   122    123         if self.senders:   124    125             # Get a mapping from senders to identities.   126    127             identities = get_sender_identities(mapping)   128    129             # Find the senders that are valid.   130    131             senders = map(get_address, identities)   132             valid = self.senders.intersection(senders)   133    134             # Return the true identities.   135    136             return reduce(lambda a, b: a + b, [identities[get_uri(address)] for address in valid], [])   137         else:   138             return mapping   139    140     def filter_by_recipient(self, mapping):   141    142         """   143         Return a list of items from 'mapping' filtered using recipient   144         information.   145         """   146    147         if self.recipient:   148             addresses = set(map(get_address, mapping))   149             return map(get_uri, addresses.intersection([self.recipient]))   150         else:   151             return mapping   152    153     def is_delegation(self):   154    155         """   156         Return whether delegation is occurring by returning any delegator.   157         """   158    159         attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))   160         attendee_attr = attendee_map.get(self.user)   161         return check_delegation(attendee_map, self.user, attendee_attr)   162    163     def require_organiser(self, from_organiser=True):   164    165         """   166         Return the normalised organiser for the current object, filtered for the   167         sender or recipient of interest. Return None if no identities are   168         eligible.   169    170         If the sender is not the organiser but is delegating to the recipient,   171         the actual organiser is returned.   172         """   173    174         organiser, organiser_attr = organiser_item = uri_item(self.obj.get_item("ORGANIZER"))   175    176         if not organiser:   177             return None   178    179         # Check the delegate status of the recipient.   180    181         delegated = from_organiser and self.is_delegation()   182    183         # Only provide details for an organiser who sent/receives the message or   184         # is presiding over a delegation.   185    186         organiser_filter_fn = from_organiser and self.filter_by_senders or self.filter_by_recipient   187    188         if not delegated and not organiser_filter_fn(dict([organiser_item])):   189             return None   190    191         # Test against any previously-received organiser details.   192    193         if not self.is_recognised_organiser(organiser):   194             replacement = self.get_organiser_replacement()   195    196             # Allow any organiser as a replacement where indicated.   197    198             if replacement == "any":   199                 pass   200    201             # Allow any recognised attendee as a replacement where indicated.   202    203             elif replacement != "attendee" or not self.is_recognised_attendee(organiser):   204                 return None   205    206         return organiser_item   207    208     def require_attendees(self, from_organiser=True):   209    210         """   211         Return the attendees for the current object, filtered for the sender or   212         recipient of interest. Return None if no identities are eligible.   213    214         The attendee identities are normalized.   215         """   216    217         attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))   218    219         # Only provide details for attendees who sent/receive the message.   220    221         attendee_filter_fn = from_organiser and self.filter_by_recipient or self.filter_by_senders   222    223         attendees = {}   224         for attendee in attendee_filter_fn(attendee_map):   225             if attendee:   226                 attendees[attendee] = attendee_map[attendee]   227    228         return attendees   229    230     def require_organiser_and_attendees(self, from_organiser=True):   231    232         """   233         Return the organiser and attendees for the current object, filtered for   234         the recipient of interest. Return None if no identities are eligible.   235    236         Organiser and attendee identities are normalized.   237         """   238    239         organiser_item = self.require_organiser(from_organiser)   240         attendees = self.require_attendees(from_organiser)   241    242         if not attendees or not organiser_item:   243             return None   244    245         return organiser_item, attendees   246    247 # vim: tabstop=4 expandtab shiftwidth=4