1 #!/usr/bin/env python 2 3 """ 4 Common handler functionality for different entities. 5 6 Copyright (C) 2014, 2015, 2016 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 imiptools.data import get_address, get_uri, make_freebusy, to_part, \ 23 uri_dict 24 from imiptools.dates import format_datetime 25 from imiptools.period import FreeBusyPeriod, Period 26 27 class CommonFreebusy: 28 29 "Common free/busy mix-in." 30 31 def _record_freebusy(self, from_organiser=True): 32 33 """ 34 Record free/busy information for a message originating from an organiser 35 if 'from_organiser' is set to a true value. 36 """ 37 38 if from_organiser: 39 organiser_item = self.require_organiser(from_organiser) 40 if not organiser_item: 41 return 42 43 senders = [organiser_item] 44 else: 45 oa = self.require_organiser_and_attendees(from_organiser) 46 if not oa: 47 return 48 49 organiser_item, attendees = oa 50 senders = attendees.items() 51 52 if not senders: 53 return 54 55 freebusy = [FreeBusyPeriod(p.get_start_point(), p.get_end_point()) for p in self.obj.get_period_values("FREEBUSY")] 56 dtstart = self.obj.get_datetime("DTSTART") 57 dtend = self.obj.get_datetime("DTEND") 58 period = Period(dtstart, dtend, self.get_tzid()) 59 60 for sender, sender_attr in senders: 61 stored_freebusy = self.store.get_freebusy_for_other_for_update(self.user, sender) 62 stored_freebusy.replace_overlapping(period, freebusy) 63 self.store.set_freebusy_for_other(self.user, stored_freebusy, sender) 64 65 def request(self): 66 67 """ 68 Respond to a request by preparing a reply containing free/busy 69 information for each indicated attendee. 70 """ 71 72 oa = self.require_organiser_and_attendees() 73 if not oa: 74 return 75 76 (organiser, organiser_attr), attendees = oa 77 78 # Get the details for each attendee. 79 80 responses = [] 81 rwrite = responses.append 82 83 # For replies, the organiser and attendee are preserved. 84 85 for attendee, attendee_attr in attendees.items(): 86 freebusy = self.store.get_freebusy(attendee) 87 88 # Indicate the actual sender of the reply. 89 90 self.update_sender(attendee_attr) 91 92 dtstart = self.obj.get_datetime("DTSTART") 93 dtend = self.obj.get_datetime("DTEND") 94 period = dtstart and dtend and Period(dtstart, dtend, self.get_tzid()) or None 95 96 rwrite(make_freebusy(freebusy, self.uid, organiser, organiser_attr, attendee, attendee_attr, period)) 97 98 # Return the reply. 99 100 self.add_result("REPLY", [get_address(organiser)], to_part("REPLY", responses)) 101 102 class CommonEvent: 103 104 "Common outgoing message handling functionality mix-in." 105 106 def is_usable(self, method=None): 107 108 "Return whether the current object is usable with the given 'method'." 109 110 return self.obj and ( 111 method in ("CANCEL", "REFRESH") or 112 self.obj.get_datetime("DTSTART") and 113 (self.obj.get_datetime("DTEND") or self.obj.get_duration("DURATION"))) 114 115 def will_refresh(self): 116 117 """ 118 Indicate whether a REFRESH message should be used to respond to an ADD 119 message. 120 """ 121 122 return not self.get_stored_object_version() or self.get_add_method_response() == "refresh" 123 124 def make_refresh(self): 125 126 "Make a REFRESH message." 127 128 organiser = get_uri(self.obj.get_value("ORGANIZER")) 129 attendees = uri_dict(self.obj.get_value_map("ATTENDEE")) 130 131 # Indicate the actual sender of the message. 132 133 attendee_attr = attendees[self.user] 134 self.update_sender(attendee_attr) 135 136 # Make a new object with a minimal property selection. 137 138 obj = self.obj.copy() 139 obj.preserve(("ORGANIZER", "DTSTAMP", "UID", "RECURRENCE-ID")) 140 obj["ATTENDEE"] = [(self.user, attendee_attr)] 141 142 # Send a REFRESH message in response. 143 144 self.add_result("REFRESH", [get_address(organiser)], obj.to_part("REFRESH")) 145 146 def ensure_occurrence(self): 147 148 """ 149 Ensure that the object originating from an attendee corresponds to an 150 existing occurrence of an event, creating or reviving a specific 151 recurrence if necessary. 152 153 Return whether a valid occurrence was found. 154 """ 155 156 # Obtain any stored object. 157 158 obj = self.get_stored_object_version() 159 160 # Handle any newly-defined occurrence. 161 162 if not obj: 163 164 # Check for a valid occurrence. 165 166 if not self.is_recurrence(): 167 return False 168 169 # Set the complete event if not an additional occurrence. For any newly- 170 # indicated occurrence, use the received event details. 171 172 self.store.remove_cancellation(self.user, self.uid, self.recurrenceid) 173 self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node()) 174 175 return True 176 177 # vim: tabstop=4 expandtab shiftwidth=4