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