1 #!/usr/bin/env python 2 3 """ 4 Construct free/busy records for a user, either recording that user's own 5 availability schedule or the schedule of another user (using details provided 6 when scheduling events with that user). 7 """ 8 9 from imiptools.data import get_window_end, Object 10 from imiptools.dates import format_datetime, get_default_timezone, to_recurrence_start 11 from imiptools.period import FreeBusyPeriod, is_replaced 12 from imiptools.profile import Preferences 13 from imip_store import FileStore, FilePublisher 14 import sys 15 16 def get_periods(fb, obj, tzid, window_end, only_organiser, recurrenceids): 17 18 """ 19 Update free/busy details 'fb' with the actual periods associated with the 20 event 'obj'. 21 """ 22 23 recurrenceid = obj.get_recurrenceid() 24 recurrenceids = [to_recurrence_start(r) for r in recurrenceids] 25 26 for p in obj.get_periods(tzid, window_end): 27 if recurrenceid or not is_replaced(p, recurrenceids, tzid): 28 fb.append(FreeBusyPeriod( 29 p.get_start(), 30 p.get_end(), 31 obj.get_value("UID"), 32 only_organiser and "ORG" or obj.get_value("TRANSP") or "OPAQUE", 33 recurrenceid, 34 obj.get_value("SUMMARY"), 35 obj.get_value("ORGANIZER"), 36 p.get_tzid() 37 )) 38 39 # Main program. 40 41 if __name__ == "__main__": 42 43 # Interpret the command line arguments. 44 45 try: 46 user = sys.argv[1] 47 args = sys.argv[2:] 48 participant = args and args[0] not in ("-n", "-s", "-v") and args[0] or user 49 store_and_publish = "-s" in args 50 include_needs_action = "-n" in args 51 verbose = "-v" in args 52 except IndexError: 53 print >>sys.stderr, """\ 54 Need a user and an optional participant (if different from the user), 55 along with the -s option if updating the store and the published details. 56 """ 57 sys.exit(1) 58 59 preferences = Preferences(user) 60 tzid = preferences.get("TZID") or get_default_timezone() 61 62 # Get the size of the free/busy window. 63 64 try: 65 window_size = int(preferences.get("window_size")) 66 except (TypeError, ValueError): 67 window_size = 100 68 window_end = get_window_end(tzid, window_size) 69 70 store = FileStore() 71 publisher = FilePublisher() 72 73 # Get all identifiers for events. 74 75 uids = store.get_events(user) 76 77 all_events = set() 78 for uid in uids: 79 all_events.add((uid, None)) 80 all_events.update([(uid, recurrenceid) for recurrenceid in store.get_recurrences(user, uid)]) 81 82 # Filter out cancelled events. 83 84 cancelled = store.get_cancellations(user) or [] 85 all_events.difference_update(cancelled) 86 87 # Obtain event objects. 88 89 objs = [] 90 for uid, recurrenceid in all_events: 91 if verbose: 92 print >>sys.stderr, uid, recurrenceid 93 event = store.get_event(user, uid, recurrenceid) 94 if event: 95 objs.append(Object(event)) 96 97 # Build a free/busy collection for the given user. 98 99 fb = [] 100 for obj in objs: 101 attendees = obj.get_value_map("ATTENDEE") 102 organiser = obj.get_value("ORGANIZER") 103 recurrenceids = store.get_recurrences(user, obj.get_value("UID")) 104 105 for attendee, attendee_attr in attendees.items(): 106 107 # Only consider events where the stated participant actually attends. 108 109 if attendee == participant: 110 partstat = attendee_attr.get("PARTSTAT", "NEEDS-ACTION") 111 112 if partstat not in ("DECLINED", "DELEGATED", "NEEDS-ACTION") or \ 113 include_needs_action and partstat == "NEEDS-ACTION": 114 115 get_periods(fb, obj, tzid, window_end, False, recurrenceids) 116 117 break 118 119 # Where not attending, retain the affected periods and mark them as 120 # organising periods. 121 122 else: 123 if organiser == participant: 124 get_periods(fb, obj, tzid, window_end, True, recurrenceids) 125 126 fb.sort() 127 128 # Store and publish the free/busy collection. 129 130 if store_and_publish: 131 if user == participant: 132 store.set_freebusy(user, fb) 133 publisher.set_freebusy(user, fb) 134 else: 135 store.set_freebusy_for_other(user, fb, participant) 136 else: 137 for item in fb: 138 print "\t".join(item.as_tuple(strings_only=True)) 139 140 # vim: tabstop=4 expandtab shiftwidth=4