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 obj = self.get_stored_object_version() 161 if obj: 162 stored_organiser = get_uri(obj.get_value("ORGANIZER")) 163 if stored_organiser != organiser: 164 return None 165 166 return organiser_item 167 168 def require_attendees(self, from_organiser=True): 169 170 """ 171 Return the attendees for the current object, filtered for the sender or 172 recipient of interest. Return None if no identities are eligible. 173 174 The attendee identities are normalized. 175 """ 176 177 attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) 178 179 # Only provide details for attendees who sent/receive the message. 180 181 attendee_filter_fn = from_organiser and self.filter_by_recipient or self.filter_by_senders 182 183 attendees = {} 184 for attendee in attendee_filter_fn(attendee_map): 185 if attendee: 186 attendees[attendee] = attendee_map[attendee] 187 188 return attendees 189 190 def require_organiser_and_attendees(self, from_organiser=True): 191 192 """ 193 Return the organiser and attendees for the current object, filtered for 194 the recipient of interest. Return None if no identities are eligible. 195 196 Organiser and attendee identities are normalized. 197 """ 198 199 organiser_item = self.require_organiser(from_organiser) 200 attendees = self.require_attendees(from_organiser) 201 202 if not attendees or not organiser_item: 203 return None 204 205 return organiser_item, attendees 206 207 # vim: tabstop=4 expandtab shiftwidth=4