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): 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) 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: 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_item = uri_item(self.obj.get_item("ORGANIZER")) 147 148 # Only provide details for an organiser who sent/receives the message. 149 150 organiser_filter_fn = from_organiser and self.filter_by_senders or self.filter_by_recipient 151 152 if not organiser_filter_fn(dict([organiser_item])): 153 return None 154 155 return organiser_item 156 157 def require_attendees(self, from_organiser=True): 158 159 """ 160 Return the attendees for the current object, filtered for the sender or 161 recipient of interest. Return None if no identities are eligible. 162 163 The attendee identities are normalized. 164 """ 165 166 attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) 167 168 # Only provide details for attendees who sent/receive the message. 169 170 attendee_filter_fn = from_organiser and self.filter_by_recipient or self.filter_by_senders 171 172 attendees = {} 173 for attendee in attendee_filter_fn(attendee_map): 174 attendees[attendee] = attendee_map[attendee] 175 176 return attendees 177 178 def require_organiser_and_attendees(self, from_organiser=True): 179 180 """ 181 Return the organiser and attendees for the current object, filtered for 182 the recipient of interest. Return None if no identities are eligible. 183 184 Organiser and attendee identities are normalized. 185 """ 186 187 organiser_item = self.require_organiser(from_organiser) 188 attendees = self.require_attendees(from_organiser) 189 190 if not attendees or not organiser_item: 191 return None 192 193 return organiser_item, attendees 194 195 # vim: tabstop=4 expandtab shiftwidth=4