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 get_results(self): 94 return self.results 95 96 def get_outgoing_methods(self): 97 return self.outgoing_methods 98 99 # Logic, filtering and access to calendar structures and other data. 100 101 def filter_by_senders(self, mapping): 102 103 """ 104 Return a list of items from 'mapping' filtered using sender information. 105 """ 106 107 if self.senders: 108 109 # Get a mapping from senders to identities. 110 111 identities = get_sender_identities(mapping) 112 113 # Find the senders that are valid. 114 115 senders = map(get_address, identities) 116 valid = self.senders.intersection(senders) 117 118 # Return the true identities. 119 120 return reduce(lambda a, b: a + b, [identities[get_uri(address)] for address in valid], []) 121 else: 122 return mapping 123 124 def filter_by_recipient(self, mapping): 125 126 """ 127 Return a list of items from 'mapping' filtered using recipient 128 information. 129 """ 130 131 if self.recipient: 132 addresses = set(map(get_address, mapping)) 133 return map(get_uri, addresses.intersection([self.recipient])) 134 else: 135 return mapping 136 137 def require_organiser(self, from_organiser=True): 138 139 """ 140 Return the organiser for the current object, filtered for the sender or 141 recipient of interest. Return None if no identities are eligible. 142 143 The organiser identity is normalized. 144 """ 145 146 organiser, organiser_attr = organiser_item = uri_item(self.obj.get_item("ORGANIZER")) 147 148 if not organiser: 149 return None 150 151 # Only provide details for an organiser who sent/receives the message. 152 153 organiser_filter_fn = from_organiser and self.filter_by_senders or self.filter_by_recipient 154 155 if not organiser_filter_fn(dict([organiser_item])): 156 return None 157 158 # Test against any previously-received organiser details. 159 160 if not self.is_recognised_organiser(organiser): 161 replacement = self.get_organiser_replacement() 162 163 # Allow any organiser as a replacement where indicated. 164 165 if replacement == "any": 166 pass 167 168 # Allow any recognised attendee as a replacement where indicated. 169 170 elif replacement != "attendee" or not self.is_recognised_attendee(organiser): 171 return None 172 173 return organiser_item 174 175 def require_attendees(self, from_organiser=True): 176 177 """ 178 Return the attendees for the current object, filtered for the sender or 179 recipient of interest. Return None if no identities are eligible. 180 181 The attendee identities are normalized. 182 """ 183 184 attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) 185 186 # Only provide details for attendees who sent/receive the message. 187 188 attendee_filter_fn = from_organiser and self.filter_by_recipient or self.filter_by_senders 189 190 attendees = {} 191 for attendee in attendee_filter_fn(attendee_map): 192 if attendee: 193 attendees[attendee] = attendee_map[attendee] 194 195 return attendees 196 197 def require_organiser_and_attendees(self, from_organiser=True): 198 199 """ 200 Return the organiser and attendees for the current object, filtered for 201 the recipient of interest. Return None if no identities are eligible. 202 203 Organiser and attendee identities are normalized. 204 """ 205 206 organiser_item = self.require_organiser(from_organiser) 207 attendees = self.require_attendees(from_organiser) 208 209 if not attendees or not organiser_item: 210 return None 211 212 return organiser_item, attendees 213 214 # vim: tabstop=4 expandtab shiftwidth=4