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 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, 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 require_organiser(self, from_organiser=True): 154 155 """ 156 Return the organiser for the current object, filtered for the sender or 157 recipient of interest. Return None if no identities are eligible. 158 159 The organiser identity is normalized. 160 """ 161 162 organiser, organiser_attr = organiser_item = uri_item(self.obj.get_item("ORGANIZER")) 163 164 if not organiser: 165 return None 166 167 # Only provide details for an organiser who sent/receives the message. 168 169 organiser_filter_fn = from_organiser and self.filter_by_senders or self.filter_by_recipient 170 171 if not organiser_filter_fn(dict([organiser_item])): 172 return None 173 174 # Test against any previously-received organiser details. 175 176 if not self.is_recognised_organiser(organiser): 177 replacement = self.get_organiser_replacement() 178 179 # Allow any organiser as a replacement where indicated. 180 181 if replacement == "any": 182 pass 183 184 # Allow any recognised attendee as a replacement where indicated. 185 186 elif replacement != "attendee" or not self.is_recognised_attendee(organiser): 187 return None 188 189 return organiser_item 190 191 def require_attendees(self, from_organiser=True): 192 193 """ 194 Return the attendees for the current object, filtered for the sender or 195 recipient of interest. Return None if no identities are eligible. 196 197 The attendee identities are normalized. 198 """ 199 200 attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) 201 202 # Only provide details for attendees who sent/receive the message. 203 204 attendee_filter_fn = from_organiser and self.filter_by_recipient or self.filter_by_senders 205 206 attendees = {} 207 for attendee in attendee_filter_fn(attendee_map): 208 if attendee: 209 attendees[attendee] = attendee_map[attendee] 210 211 return attendees 212 213 def require_organiser_and_attendees(self, from_organiser=True): 214 215 """ 216 Return the organiser and attendees for the current object, filtered for 217 the recipient of interest. Return None if no identities are eligible. 218 219 Organiser and attendee identities are normalized. 220 """ 221 222 organiser_item = self.require_organiser(from_organiser) 223 attendees = self.require_attendees(from_organiser) 224 225 if not attendees or not organiser_item: 226 return None 227 228 return organiser_item, attendees 229 230 # vim: tabstop=4 expandtab shiftwidth=4