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 58 # Reply only on behalf of this user. 59 60 if action in ("accept", "decline"): 61 attendee_attr = self.update_participation(action == "accept" and "ACCEPTED" or "DECLINED") 62 method = "REPLY" 63 64 # For counter-proposals, set a new main period for the event. 65 66 elif action == "counter": 67 attendee_attr = self.obj.get_value_map("ATTENDEE").get(self.user) 68 period = self.obj.get_main_period(self.get_tzid()) 69 70 # Use the existing or configured time zone for the specified 71 # datetimes. 72 73 start = to_timezone(get_datetime(start), period.tzid) 74 end = to_timezone(get_datetime(end), period.tzid) 75 period = RecurringPeriod(start, end, period.tzid, period.origin, period.get_start_attr(), period.get_end_attr()) 76 self.obj.set_period(period) 77 method = "COUNTER" 78 79 # Nothing else is supported. 80 81 else: 82 return None 83 84 # Where no attendees remain, no message is generated. 85 86 if not attendee_attr: 87 return None 88 89 # NOTE: This is a simpler form of the code in imipweb.client. 90 91 organiser = get_address(self.obj.get_value("ORGANIZER")) 92 93 self.update_sender(attendee_attr) 94 self.obj["ATTENDEE"] = [(self.user, attendee_attr)] 95 self.update_dtstamp() 96 self.update_sequence(False) 97 98 message = self.messenger.make_outgoing_message( 99 [self.obj.to_part(method)], 100 [organiser], 101 outgoing_bcc=get_address(self.user) 102 ) 103 104 return message.as_string() 105 106 # A simple main program that attempts to handle a stored request, writing the 107 # response message to standard output. 108 109 if __name__ == "__main__": 110 progname = split(sys.argv[0])[-1] 111 112 try: 113 action, store_type, store_dir, journal_dir, preferences_dir, user = sys.argv[1:7] 114 if action == "counter": 115 start, end = sys.argv[7:9] 116 i = 9 117 else: 118 start, end = None, None 119 i = 7 120 uid, recurrenceid = (sys.argv[i:i+2] + [None] * 2)[:2] 121 except ValueError: 122 print >>sys.stderr, """\ 123 Usage: %s <action> <store type> <store directory> <journal directory> 124 <preferences directory> <user URI> [ <start> <end> ] 125 <uid> <recurrence-id> 126 127 Need 'accept', 'counter' or 'decline', a store type, a store directory, a 128 journal directory, a preferences directory, user URI, any counter-proposal 129 datetimes (see below), plus the appropriate event UID and RECURRENCE-ID (if a 130 recurrence is involved). 131 132 The RECURRENCE-ID must be in exactly the form employed by the store, not a 133 different but equivalent representation, if the identifier is to refer to an 134 existing recurrence. 135 136 Alternatively, omit the UID and RECURRENCE-ID and provide event-only details on 137 standard input to force the script to handle an event not already present in the 138 store. 139 140 If 'counter' has been indicated, alternative start and end datetimes are also 141 required. 142 """ 143 sys.exit(1) 144 145 store = get_store(store_type, store_dir) 146 journal = get_journal(store_type, journal_dir) 147 148 if uid is not None: 149 fragment = store.get_event(user, uid, recurrenceid) 150 151 # Permit new recurrences by getting the parent object. 152 153 if not fragment: 154 fragment = store.get_event(user, uid) 155 156 if not fragment: 157 print >>sys.stderr, "No such event:", uid, recurrenceid 158 sys.exit(1) 159 else: 160 fragment = parse_object(sys.stdin, "utf-8") 161 162 obj = Object(fragment) 163 handler = TestClient(obj, user, Messenger(), store, None, journal, preferences_dir) 164 response = handler.handle_request(action, start, end, recurrenceid) 165 166 if response: 167 if uid is not None: 168 store.dequeue_request(user, uid, recurrenceid) 169 print response 170 else: 171 sys.exit(1) 172 173 # vim: tabstop=4 expandtab shiftwidth=4