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