1.1 --- a/imip_text_client.py Wed Nov 01 23:25:17 2017 +0100
1.2 +++ b/imip_text_client.py Wed Nov 01 23:26:58 2017 +0100
1.3 @@ -1,17 +1,19 @@
1.4 #!/usr/bin/env python
1.5
1.6 from email import message_from_file
1.7 -from imiptools import parse_args
1.8 -from imiptools.client import ClientForObject
1.9 +from imiptools import get_handlers, parse_args
1.10 from imiptools.config import settings
1.11 -from imiptools.content import get_objects_from_itip, have_itip_part, parse_itip_part
1.12 -from imiptools.data import get_address, get_main_period, get_recurrence_periods, get_value
1.13 +from imiptools.content import get_objects_from_itip, handle_calendar_data, \
1.14 + handle_calendar_object, have_itip_part, \
1.15 + parse_itip_part
1.16 +from imiptools.data import get_address, get_main_period, get_recurrence_periods, get_value, parse_object
1.17 from imiptools.dates import get_datetime_item, get_time, to_timezone
1.18 from imiptools.editing import EditingClient, PeriodError
1.19 +from imiptools.handlers import person, person_outgoing
1.20 from imiptools.mail import Messenger
1.21 from imiptools.stores import get_journal, get_store
1.22 from imiptools.utils import decode_part, message_as_string
1.23 -import sys
1.24 +import sys, os
1.25
1.26 # User interface functions.
1.27
1.28 @@ -40,9 +42,9 @@
1.29
1.30 # Interpret an input file containing a calendar resource.
1.31
1.32 -def get_objects(filename):
1.33 +def get_itip_from_message(filename):
1.34
1.35 - "Return objects provided by 'filename'."
1.36 + "Return iTIP details provided by 'filename'."
1.37
1.38 f = open(filename)
1.39 try:
1.40 @@ -50,18 +52,25 @@
1.41 finally:
1.42 f.close()
1.43
1.44 - all_objects = []
1.45 + all_itip = []
1.46
1.47 for part in msg.walk():
1.48 - itip = parse_itip_part(part)
1.49 - method = itip and get_value(itip, "METHOD")
1.50 + if have_itip_part(part):
1.51 + all_itip.append(parse_itip_part(part))
1.52
1.53 - # Ignore cancelled objects since only active objects are of interest.
1.54 + return all_itip
1.55 +
1.56 +def get_itip_from_data(filename, charset):
1.57
1.58 - if method != "CANCEL":
1.59 - all_objects += get_objects_from_itip(itip, ["VEVENT"])
1.60 + "Return objects provided by 'filename'."
1.61
1.62 - return all_objects
1.63 + f = open(filename)
1.64 + try:
1.65 + itip = parse_object(f, charset, "VCALENDAR")
1.66 + finally:
1.67 + f.close()
1.68 +
1.69 + return [itip]
1.70
1.71 def show_objects(objects, user, store):
1.72
1.73 @@ -70,11 +79,34 @@
1.74 'store'.
1.75 """
1.76
1.77 + print
1.78 + print_title("Objects")
1.79 + print
1.80 +
1.81 for index, obj in enumerate(objects):
1.82 recurrenceid = obj.get_recurrenceid()
1.83 recurrence_label = recurrenceid and " %s" % recurrenceid or ""
1.84 print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label)
1.85
1.86 +def show_requests(user, store):
1.87 +
1.88 + "Show requests available to the given 'user' in the given 'store'."
1.89 +
1.90 + requests = store.get_requests(user)
1.91 +
1.92 + print
1.93 + print_title("Requests")
1.94 + print
1.95 +
1.96 + if not requests:
1.97 + print "No requests are pending."
1.98 + return
1.99 +
1.100 + for index, (uid, recurrenceid) in enumerate(requests):
1.101 + obj = store.get_event(user, uid, recurrenceid)
1.102 + recurrence_label = recurrenceid and " %s" % recurrenceid or ""
1.103 + print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label)
1.104 +
1.105 def show_attendee(attendee_item, index):
1.106
1.107 "Show the 'attendee_item' (value and attributes) at 'index'."
1.108 @@ -374,6 +406,8 @@
1.109
1.110 def show_object(self):
1.111 print
1.112 + print_title("Object details")
1.113 + print
1.114 print "Summary:", self.state.get("summary")
1.115 print
1.116 print "Organiser:", self.state.get("organiser")
1.117 @@ -468,9 +502,15 @@
1.118
1.119 def select_object(cl, objects):
1.120 print
1.121 +
1.122 + if objects:
1.123 + label = "Select object number or (n)ew object or (q)uit> "
1.124 + else:
1.125 + label = "Select (n)ew object or (q)uit> "
1.126 +
1.127 while True:
1.128 try:
1.129 - cmd = read_input("Object or (n)ew object or (q)uit> ")
1.130 + cmd = read_input(label)
1.131 except EOFError:
1.132 return None
1.133
1.134 @@ -479,12 +519,13 @@
1.135 if 0 <= index < len(objects):
1.136 obj = objects[index]
1.137 return cl.load_object(obj.get_uid(), obj.get_recurrenceid())
1.138 +
1.139 elif cmd in ("n", "new"):
1.140 return cl.new_object()
1.141 elif cmd in ("q", "quit", "exit"):
1.142 return None
1.143
1.144 -def show_help():
1.145 +def show_commands():
1.146 print
1.147 print_title("Editing commands")
1.148 print
1.149 @@ -532,6 +573,13 @@
1.150 Set event summary
1.151 """
1.152
1.153 + print_title("Messaging commands")
1.154 + print
1.155 + print """\
1.156 +S, send
1.157 + Send messages to recipients and to self, if appropriate
1.158 +"""
1.159 +
1.160 print_title("Diagnostic commands")
1.161 print
1.162 print """\
1.163 @@ -551,7 +599,7 @@
1.164 Show event unique identifier, writing to <filename> if specified
1.165 """
1.166
1.167 - print_title("Messaging commands")
1.168 + print_title("Message inspection commands")
1.169 print
1.170 print """\
1.171 P [ <filename> ]
1.172 @@ -569,7 +617,7 @@
1.173 Show update message, writing to <filename> if specified
1.174 """
1.175
1.176 -def edit_object(cl, obj):
1.177 +def edit_object(cl, obj, handle_outgoing=False):
1.178 cl.show_object()
1.179 print
1.180
1.181 @@ -603,7 +651,7 @@
1.182 # Help.
1.183
1.184 elif cmd in ("h", "?", "help"):
1.185 - show_help()
1.186 + show_commands()
1.187
1.188 # Show object details.
1.189
1.190 @@ -660,6 +708,61 @@
1.191 filename = get_filename_arg(cmd)
1.192 cl.show_update_message(plain=not filename, filename=filename)
1.193
1.194 + # Definitive finishing action.
1.195 +
1.196 + elif args[0] in ("S", "send"):
1.197 +
1.198 + # Send update and cancellation messages.
1.199 +
1.200 + did_send = False
1.201 +
1.202 + message = cl.prepare_update_message()
1.203 + if message:
1.204 + cl.send_message(message, cl.get_recipients())
1.205 + did_send = True
1.206 +
1.207 + to_cancel = cl.state.get("attendees_to_cancel")
1.208 + if to_cancel:
1.209 + message = cl.prepare_cancel_message()
1.210 + if message:
1.211 + cl.send_message(message, to_cancel)
1.212 + did_send = True
1.213 +
1.214 + # Process the object using the person outgoing handler.
1.215 +
1.216 + if handle_outgoing:
1.217 +
1.218 + # Handle the parent object plus any rescheduled periods.
1.219 +
1.220 + unscheduled_objects, rescheduled_objects, added_objects = \
1.221 + cl.get_publish_objects()
1.222 +
1.223 + handlers = get_handlers(cl, person_outgoing.handlers,
1.224 + [get_address(cl.user)])
1.225 +
1.226 + handle_calendar_object(cl.obj, handlers, "PUBLISH")
1.227 + for o in unscheduled_objects:
1.228 + handle_calendar_object(o, handlers, "CANCEL")
1.229 + for o in rescheduled_objects:
1.230 + handle_calendar_object(o, handlers, "PUBLISH")
1.231 + for o in added_objects:
1.232 + handle_calendar_object(o, handlers, "ADD")
1.233 +
1.234 + # Otherwise, send a message to self with the event details.
1.235 +
1.236 + else:
1.237 + message = cl.prepare_publish_message()
1.238 + if message:
1.239 + cl.send_message_to_self(message)
1.240 + did_send = True
1.241 +
1.242 + # Exit if sending occurred.
1.243 +
1.244 + if did_send:
1.245 + break
1.246 + else:
1.247 + print "No messages sent. Try making edits or exit manually."
1.248 +
1.249 # Editing operations.
1.250
1.251 elif not cl.state.get("finished"):
1.252 @@ -821,7 +924,8 @@
1.253 # Set the summary.
1.254
1.255 elif args[0] in ("s", "summary"):
1.256 - cl.edit_summary(cmd.split(None, 1)[1])
1.257 + t = cmd.split(None, 1)
1.258 + cl.edit_summary(len(t) > 1 and t[1] or None)
1.259 cl.show_object()
1.260 print
1.261
1.262 @@ -831,18 +935,28 @@
1.263 def main(args):
1.264 global echo
1.265
1.266 + if "--help" in args:
1.267 + show_help(os.path.split(sys.argv[0])[-1])
1.268 + return
1.269 +
1.270 # Parse command line arguments using the standard options plus some extra
1.271 # options.
1.272
1.273 args = parse_args(args, {
1.274 + "--calendar-data" : ("calendar_data", False),
1.275 + "--charset" : ("charset", "utf-8"),
1.276 "--echo" : ("echo", False),
1.277 "-f" : ("filename", None),
1.278 + "--handle-data" : ("handle_data", False),
1.279 "--suppress-bcc" : ("suppress_bcc", False),
1.280 "-u" : ("user", None),
1.281 })
1.282
1.283 + charset = args["charset"]
1.284 + calendar_data = args["calendar_data"]
1.285 echo = args["echo"]
1.286 filename = args["filename"]
1.287 + handle_data = args["handle_data"]
1.288 sender = (args["senders"] or [None])[0]
1.289 suppress_bcc = args["suppress_bcc"]
1.290 user = args["user"]
1.291 @@ -877,7 +991,24 @@
1.292 # Read any input resource.
1.293
1.294 if filename:
1.295 - objects = get_objects(filename)
1.296 + if calendar_data:
1.297 + all_itip = get_itip_from_data(filename, charset)
1.298 + else:
1.299 + all_itip = get_itip_from_message(filename)
1.300 +
1.301 + objects = []
1.302 +
1.303 + # Process the objects using the person handler.
1.304 +
1.305 + if handle_data:
1.306 + for itip in all_itip:
1.307 + objects += handle_calendar_data(itip, get_handlers(cl, person.handlers, None))
1.308 +
1.309 + # Or just obtain objects from the data.
1.310 +
1.311 + else:
1.312 + for itip in all_itip:
1.313 + objects += get_objects_from_itip(itip, ["VEVENT"])
1.314
1.315 # Choose an object to edit.
1.316
1.317 @@ -887,7 +1018,7 @@
1.318 # Exit without any object.
1.319
1.320 if not obj:
1.321 - print >>sys.stderr, "Object not loaded."
1.322 + print >>sys.stderr, "No object loaded."
1.323 sys.exit(1)
1.324
1.325 # Or create a new object.
1.326 @@ -897,7 +1028,52 @@
1.327
1.328 # Edit the object.
1.329
1.330 - edit_object(cl, obj)
1.331 + edit_object(cl, obj, handle_outgoing=handle_data)
1.332 +
1.333 +def show_help(progname):
1.334 + print >>sys.stderr, help_text % progname
1.335 +
1.336 +help_text = """\
1.337 +Usage: %s -s <sender> | -u <user> \\
1.338 + [ -f <filename> ] \\
1.339 + [ --calendar-data --charset ] \\
1.340 + [ --handle-data ] \\
1.341 + [ -T <store type ] [ -S <store directory> ] \\
1.342 + [ -p <preferences directory> ] \\
1.343 + [ --echo ]
1.344 +
1.345 +Identity options:
1.346 +
1.347 +-s Indicate the user by specifying a sender address
1.348 +-u Indicate the user by specifying their URI
1.349 +
1.350 +Input options:
1.351 +
1.352 +-f Indicates a filename containing a MIME-encoded message or calendar object
1.353 +
1.354 +--calendar-data Indicates that the specified file contains a calendar object
1.355 + as opposed to a mail message
1.356 +--charset Specifies the character encoding used by a calendar object
1.357 + description
1.358 +
1.359 +Processing options:
1.360 +
1.361 +--handle-data Cause the input to be handled and stored in the configured
1.362 + data store
1.363 +
1.364 +Configuration options (overriding configured defaults):
1.365 +
1.366 +-p Indicates the location of user preference directories
1.367 +-S Indicates the location of the calendar data store containing user storage
1.368 + directories
1.369 +-T Indicates the store type (the configured value if omitted)
1.370 +
1.371 +Output options:
1.372 +
1.373 +--echo Echo received input, useful if consuming input from the
1.374 + standard input stream and producing a log of the program's
1.375 + activity
1.376 +"""
1.377
1.378 if __name__ == "__main__":
1.379 main(sys.argv[1:])