imip-agent

tools/make_freebusy.py

1030:6e8f43c29e71
2016-01-30 Paul Boddie Added the domain-related scheduling function to the documentation.
     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 Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>     9     10 This program is free software; you can redistribute it and/or modify it under    11 the terms of the GNU General Public License as published by the Free Software    12 Foundation; either version 3 of the License, or (at your option) any later    13 version.    14     15 This program is distributed in the hope that it will be useful, but WITHOUT    16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    17 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    18 details.    19     20 You should have received a copy of the GNU General Public License along with    21 this program.  If not, see <http://www.gnu.org/licenses/>.    22 """    23     24 from os.path import split    25 import sys    26     27 # Find the modules.    28     29 try:    30     import imiptools    31 except ImportError:    32     parent = split(split(__file__)[0])[0]    33     if split(parent)[1] == "imip-agent":    34         sys.path.append(parent)    35     36 from codecs import getwriter    37 from imiptools.client import Client    38 from imiptools.data import get_window_end, Object    39 from imiptools.dates import get_default_timezone, to_utc_datetime    40 from imiptools.period import insert_period    41 from imip_store import FileStore, FilePublisher    42     43 def make_freebusy(client, participant, store_and_publish, include_needs_action,    44     reset_updated_list, verbose):    45     46     """    47     Using the given 'client' representing a user, make free/busy details for the    48     records of the user, generating details for 'participant' if not indicated    49     as None; otherwise, generating free/busy details concerning the given user.    50     51     If 'store_and_publish' is set, the stored details will be updated;    52     otherwise, the details will be written to standard output.    53     54     If 'include_needs_action' is set, details of objects whose participation    55     status is set to "NEEDS-ACTION" for the participant will be included in the    56     details.    57     58     If 'reset_updated_list' is set, all objects will be inspected for periods;    59     otherwise, only those in the stored free/busy providers file will be    60     inspected.    61     62     If 'verbose' is set, messages will be written to standard error.    63     """    64     65     user = client.user    66     store = client.get_store()    67     publisher = client.get_publisher()    68     preferences = client.get_preferences()    69     70     participant = participant or user    71     tzid = preferences.get("TZID") or get_default_timezone()    72     73     # Get the size of the free/busy window.    74     75     try:    76         window_size = int(preferences.get("window_size"))    77     except (TypeError, ValueError):    78         window_size = 100    79     window_end = get_window_end(tzid, window_size)    80     81     # Get identifiers for uncancelled events either from a list of events    82     # providing free/busy periods at the end of the given time window, or from    83     # a list of all events.    84     85     all_events = not reset_updated_list and store.get_freebusy_providers(user, window_end)    86     87     if not all_events:    88         all_events = store.get_all_events(user)    89         fb = []    90     91     # With providers of additional periods, append to the existing collection.    92     93     else:    94         if user == participant:    95             fb = store.get_freebusy(user)    96         else:    97             fb = store.get_freebusy_for_other(user, participant)    98     99     # Obtain event objects.   100    101     objs = []   102     for uid, recurrenceid in all_events:   103         if verbose:   104             print >>sys.stderr, uid, recurrenceid   105         event = store.get_event(user, uid, recurrenceid)   106         if event:   107             objs.append(Object(event))   108    109     # Build a free/busy collection for the given user.   110    111     for obj in objs:   112         partstat = obj.get_participation_status(participant)   113         recurrenceids = not obj.get_recurrenceid() and store.get_recurrences(user, obj.get_uid())   114    115         if obj.get_participation(partstat, include_needs_action):   116             for p in obj.get_active_periods(recurrenceids, tzid, window_end):   117                 fbp = obj.get_freebusy_period(p, partstat == "ORG")   118                 insert_period(fb, fbp)   119    120     # Store and publish the free/busy collection.   121    122     if store_and_publish:   123         if user == participant:   124             store.set_freebusy(user, fb)   125    126             if client.is_sharing() and client.is_publishing():   127                 publisher.set_freebusy(user, fb)   128    129             # Update the list of objects providing periods on future occasions.   130    131             store.set_freebusy_providers(user, to_utc_datetime(window_end, tzid),   132                 [obj for obj in objs if obj.possibly_active_from(window_end, tzid)])   133         else:   134             store.set_freebusy_for_other(user, fb, participant)   135    136     # Alternatively, just write the collection to standard output.   137    138     else:   139         f = getwriter("utf-8")(sys.stdout)   140         for item in fb:   141             print >>f, "\t".join(item.as_tuple(strings_only=True))   142    143 # Main program.   144    145 if __name__ == "__main__":   146    147     # Interpret the command line arguments.   148    149     participants = []   150     args = []   151     store_dir = []   152     publishing_dir = []   153     preferences_dir = []   154     ignored = []   155    156     # Collect user details first, switching to other arguments when encountering   157     # switches.   158    159     l = participants   160    161     for arg in sys.argv[1:]:   162         if arg in ("-n", "-s", "-v", "-r"):   163             args.append(arg)   164             l = ignored   165         elif arg == "-S":   166             l = store_dir   167         elif arg == "-P":   168             l = publishing_dir   169         elif arg == "-p":   170             l = preferences_dir   171         else:   172             l.append(arg)   173    174     try:   175         user = participants[0]   176     except IndexError:   177         print >>sys.stderr, """\   178 Usage: %s <user> [ <other user> ] <options>   179    180 Need a user and an optional participant (if different from the user),   181 along with the -s option if updating the store and the published details.   182 Specify -n to include objects with PARTSTAT of NEEDS-ACTION.   183 Specify -r to inspect all objects, not just those expected to provide details.   184 Specify -v for additional messages on standard error.   185    186 General options:   187    188 -S  indicate the store directory location   189 -P  indicate the publishing directory location   190 -p  indicate the preferences directory location   191 """ % split(sys.argv[0])[1]   192         sys.exit(1)   193    194     # Define any other participant of interest plus options.   195    196     participant = participants[1:] and participants[1] or None   197     store_and_publish = "-s" in args   198     include_needs_action = "-n" in args   199     reset_updated_list = "-r" in args   200     verbose = "-v" in args   201    202     # Override defaults if indicated.   203    204     store_dir = store_dir and store_dir[0] or None   205     publishing_dir = publishing_dir and publishing_dir[0] or None   206     preferences_dir = preferences_dir and preferences_dir[0] or None   207    208     # Obtain store-related objects.   209    210     store = FileStore(store_dir)   211     publisher = FilePublisher(publishing_dir)   212    213     # Obtain a list of users for processing.   214    215     if user in ("*", "all"):   216         users = store.get_users()   217     else:   218         users = [user]   219    220     # Process the given users.   221    222     for user in users:   223         if verbose:   224             print >>sys.stderr, user   225         make_freebusy(   226             Client(user, None, store, publisher, preferences_dir), participant,   227             store_and_publish, include_needs_action, reset_updated_list, verbose)   228    229 # vim: tabstop=4 expandtab shiftwidth=4