1 #!/usr/bin/env python 2 3 """ 4 General handler support for incoming calendar objects. 5 6 Copyright (C) 2014, 2015 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 get_address, get_uri, get_sender_identities, \ 26 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, 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' and 'publisher' can be specified to override the 57 default store and publisher objects. 58 """ 59 60 ClientForObject.__init__(self, None, recipient and get_uri(recipient), messenger, store, publisher, preferences_dir) 61 62 self.senders = senders and set(map(get_address, senders)) 63 self.recipient = recipient and get_address(recipient) 64 65 self.results = [] 66 self.outgoing_methods = set() 67 68 def wrap(self, text, link=True): 69 70 "Wrap any valid message for passing to the recipient." 71 72 texts = [] 73 texts.append(text) 74 if link and self.have_manager(): 75 texts.append("If your mail program cannot handle this " 76 "message, you may view the details here:\n\n%s" % 77 get_object_url(self.uid, self.recurrenceid)) 78 79 return self.add_result(None, None, MIMEText("\n".join(texts))) 80 81 # Result registration. 82 83 def add_result(self, method, outgoing_recipients, part): 84 85 """ 86 Record a result having the given 'method', 'outgoing_recipients' and 87 message 'part'. 88 """ 89 90 if outgoing_recipients: 91 self.outgoing_methods.add(method) 92 self.results.append((outgoing_recipients, part)) 93 94 def add_results(self, methods, outgoing_recipients, parts): 95 96 """ 97 Record results having the given 'methods', 'outgoing_recipients' and 98 message 'parts'. 99 """ 100 101 if outgoing_recipients: 102 self.outgoing_methods.update(methods) 103 for part in parts: 104 self.results.append((outgoing_recipients, part)) 105 106 def get_results(self): 107 return self.results 108 109 def get_outgoing_methods(self): 110 return self.outgoing_methods 111 112 # Logic, filtering and access to calendar structures and other data. 113 114 def filter_by_senders(self, mapping): 115 116 """ 117 Return a list of items from 'mapping' filtered using sender information. 118 """ 119 120 if self.senders: 121 122 # Get a mapping from senders to identities. 123 124 identities = get_sender_identities(mapping) 125 126 # Find the senders that are valid. 127 128 senders = map(get_address, identities) 129 valid = self.senders.intersection(senders) 130 131 # Return the true identities. 132 133 return reduce(lambda a, b: a + b, [identities[get_uri(address)] for address in valid], []) 134 else: 135 return mapping 136 137 def filter_by_recipient(self, mapping): 138 139 """ 140 Return a list of items from 'mapping' filtered using recipient 141 information. 142 """ 143 144 if self.recipient: 145 addresses = set(map(get_address, mapping)) 146 return map(get_uri, addresses.intersection([self.recipient])) 147 else: 148 return mapping 149 150 def require_organiser(self, from_organiser=True): 151 152 """ 153 Return the organiser for the current object, filtered for the sender or 154 recipient of interest. Return None if no identities are eligible. 155 156 The organiser identity is normalized. 157 """ 158 159 organiser, organiser_attr = organiser_item = uri_item(self.obj.get_item("ORGANIZER")) 160 161 if not organiser: 162 return None 163 164 # Only provide details for an organiser who sent/receives the message. 165 166 organiser_filter_fn = from_organiser and self.filter_by_senders or self.filter_by_recipient 167 168 if not organiser_filter_fn(dict([organiser_item])): 169 return None 170 171 # Test against any previously-received organiser details. 172 173 if not self.is_recognised_organiser(organiser): 174 replacement = self.get_organiser_replacement() 175 176 # Allow any organiser as a replacement where indicated. 177 178 if replacement == "any": 179 pass 180 181 # Allow any recognised attendee as a replacement where indicated. 182 183 elif replacement != "attendee" or not self.is_recognised_attendee(organiser): 184 return None 185 186 return organiser_item 187 188 def require_attendees(self, from_organiser=True): 189 190 """ 191 Return the attendees for the current object, filtered for the sender or 192 recipient of interest. Return None if no identities are eligible. 193 194 The attendee identities are normalized. 195 """ 196 197 attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) 198 199 # Only provide details for attendees who sent/receive the message. 200 201 attendee_filter_fn = from_organiser and self.filter_by_recipient or self.filter_by_senders 202 203 attendees = {} 204 for attendee in attendee_filter_fn(attendee_map): 205 if attendee: 206 attendees[attendee] = attendee_map[attendee] 207 208 return attendees 209 210 def require_organiser_and_attendees(self, from_organiser=True): 211 212 """ 213 Return the organiser and attendees for the current object, filtered for 214 the recipient of interest. Return None if no identities are eligible. 215 216 Organiser and attendee identities are normalized. 217 """ 218 219 organiser_item = self.require_organiser(from_organiser) 220 attendees = self.require_attendees(from_organiser) 221 222 if not attendees or not organiser_item: 223 return None 224 225 return organiser_item, attendees 226 227 # vim: tabstop=4 expandtab shiftwidth=4