1 #!/usr/bin/env python 2 3 """ 4 A handler to help with testing. 5 6 Copyright (C) 2014, 2015, 2016, 2017 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() 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 self.update_sender_attr(attendee_attr) 96 self.obj["ATTENDEE"] = [(self.user, attendee_attr)] 97 self.update_dtstamp() 98 self.update_sequence() 99 100 parts = [self.obj.to_part(method)] 101 recipients = self.get_recipients() 102 103 message = self.make_message(parts, recipients, bcc_sender=True) 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 len(sys.argv) >= 10: 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 or 129 new recurrence datetimes (see below), plus the appropriate event UID and 130 RECURRENCE-ID (if a 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. The event details must be a fragment of a calendar file, not an entire 139 calendar file, nor a mail message containing calendar data. 140 141 If 'counter' has been indicated, alternative start and end datetimes are also 142 required. If a specific recurrence is being separated from an event, such 143 datetimes are also required in order to set the main period of the recurrence. 144 """ 145 sys.exit(1) 146 147 store = get_store(store_type, store_dir) 148 journal = get_journal(store_type, journal_dir) 149 150 if uid is not None: 151 obj = store.get_event(user, uid, recurrenceid) 152 153 # Permit new recurrences by getting the parent object. 154 155 if not obj: 156 obj = store.get_event(user, uid) 157 158 if not obj: 159 print >>sys.stderr, "No such event:", uid, recurrenceid 160 sys.exit(1) 161 else: 162 obj = Object(parse_object(sys.stdin, "utf-8")) 163 164 handler = TestClient(obj, user, Messenger(), store, None, journal, preferences_dir) 165 response = handler.handle_request(action, start, end, recurrenceid) 166 167 if response: 168 if uid is not None: 169 store.dequeue_request(user, uid, recurrenceid) 170 print response 171 else: 172 sys.exit(1) 173 174 # vim: tabstop=4 expandtab shiftwidth=4