1 #!/usr/bin/env python 2 3 """ 4 A handler to help with testing. 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.client import ClientForObject 23 from imiptools.data import Object, get_address, parse_object 24 from imiptools.dates import get_datetime, to_timezone 25 from imiptools.mail import Messenger 26 from imiptools.period import RecurringPeriod 27 from imiptools.stores import get_store, get_journal 28 from os.path import split 29 import sys 30 31 class TestClient(ClientForObject): 32 33 """ 34 A content handler for use in testing, as opposed to operating within the 35 mail processing pipeline. 36 """ 37 38 # Action methods. 39 40 def handle_request(self, action, start=None, end=None, recurrenceid=None): 41 42 """ 43 Process the current request for the current user. Return whether the 44 given 'action' was taken. 45 46 If 'start' and 'end' are specified, they will be used in any 47 counter-proposal. 48 49 Where 'recurrenceid' is specified and refers to a new recurrence, the 50 action will apply only to this new recurrence. 51 """ 52 53 have_new_recurrence = self.obj.get_recurrenceid() != recurrenceid 54 55 if have_new_recurrence: 56 self.obj["RECURRENCE-ID"] = [(recurrenceid, {})] 57 self.obj.remove_all(["RDATE", "RRULE"]) 58 59 # Reply only on behalf of this user. 60 61 if action in ("accept", "decline"): 62 attendee_attr = self.update_participation(action == "accept" and "ACCEPTED" or "DECLINED") 63 method = "REPLY" 64 65 elif action == "counter": 66 attendee_attr = self.obj.get_value_map("ATTENDEE").get(self.user) 67 method = "COUNTER" 68 69 # Nothing else is supported. 70 71 else: 72 return None 73 74 # For counter-proposals or new recurrences, set a new main period for 75 # the event. 76 77 if action == "counter" or have_new_recurrence: 78 period = self.obj.get_main_period(self.get_tzid()) 79 80 # Use the existing or configured time zone for the specified 81 # datetimes. 82 83 start = to_timezone(get_datetime(start), period.tzid) 84 end = to_timezone(get_datetime(end), period.tzid) 85 period = RecurringPeriod(start, end, period.tzid, period.origin, period.get_start_attr(), period.get_end_attr()) 86 self.obj.set_period(period) 87 88 # Where no attendees remain, no message is generated. 89 90 if not attendee_attr: 91 return None 92 93 # NOTE: This is a simpler form of the code in imipweb.client. 94 95 organiser = get_address(self.obj.get_value("ORGANIZER")) 96 97 self.update_sender(attendee_attr) 98 self.obj["ATTENDEE"] = [(self.user, attendee_attr)] 99 self.update_dtstamp() 100 self.update_sequence(False) 101 102 message = self.messenger.make_outgoing_message( 103 [self.obj.to_part(method)], 104 [organiser], 105 outgoing_bcc=get_address(self.user) 106 ) 107 108 return message.as_string() 109 110 # A simple main program that attempts to handle a stored request, writing the 111 # response message to standard output. 112 113 if __name__ == "__main__": 114 progname = split(sys.argv[0])[-1] 115 116 try: 117 action, store_type, store_dir, journal_dir, preferences_dir, user = sys.argv[1:7] 118 if len(sys.argv) >= 10: 119 start, end = sys.argv[7:9] 120 i = 9 121 else: 122 start, end = None, None 123 i = 7 124 uid, recurrenceid = (sys.argv[i:i+2] + [None] * 2)[:2] 125 except ValueError: 126 print >>sys.stderr, """\ 127 Usage: %s <action> <store type> <store directory> <journal directory> 128 <preferences directory> <user URI> [ <start> <end> ] 129 <uid> <recurrence-id> 130 131 Need 'accept', 'counter' or 'decline', a store type, a store directory, a 132 journal directory, a preferences directory, user URI, any counter-proposal or 133 new recurrence datetimes (see below), plus the appropriate event UID and 134 RECURRENCE-ID (if a recurrence is involved). 135 136 The RECURRENCE-ID must be in exactly the form employed by the store, not a 137 different but equivalent representation, if the identifier is to refer to an 138 existing recurrence. 139 140 Alternatively, omit the UID and RECURRENCE-ID and provide event-only details on 141 standard input to force the script to handle an event not already present in the 142 store. 143 144 If 'counter' has been indicated, alternative start and end datetimes are also 145 required. If a specific recurrence is being separated from an event, such 146 datetimes are also required in order to set the main period of the recurrence. 147 """ 148 sys.exit(1) 149 150 store = get_store(store_type, store_dir) 151 journal = get_journal(store_type, journal_dir) 152 153 if uid is not None: 154 fragment = store.get_event(user, uid, recurrenceid) 155 156 # Permit new recurrences by getting the parent object. 157 158 if not fragment: 159 fragment = store.get_event(user, uid) 160 161 if not fragment: 162 print >>sys.stderr, "No such event:", uid, recurrenceid 163 sys.exit(1) 164 else: 165 fragment = parse_object(sys.stdin, "utf-8") 166 167 obj = Object(fragment) 168 handler = TestClient(obj, user, Messenger(), store, None, journal, preferences_dir) 169 response = handler.handle_request(action, start, end, recurrenceid) 170 171 if response: 172 if uid is not None: 173 store.dequeue_request(user, uid, recurrenceid) 174 print response 175 else: 176 sys.exit(1) 177 178 # vim: tabstop=4 expandtab shiftwidth=4