imip-agent

tools/make_freebusy.py

1071:9cb80c0c685c
2016-03-06 Paul Boddie Introduced mutation constraints and "for update" methods when handling free/busy data, in order to potentially support storage mechanisms where the stored data is manipulated live. freebusy-collections
     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, 2016 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 FreeBusyCollection    41 from imiptools.stores.file import FileStore, FilePublisher, FileJournal    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 = FreeBusyCollection()    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                 fb.insert_period(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     journal_dir = []   154     preferences_dir = []   155     ignored = []   156    157     # Collect user details first, switching to other arguments when encountering   158     # switches.   159    160     l = participants   161    162     for arg in sys.argv[1:]:   163         if arg in ("-n", "-s", "-v", "-r"):   164             args.append(arg)   165             l = ignored   166         elif arg == "-S":   167             l = store_dir   168         elif arg == "-P":   169             l = publishing_dir   170         elif arg == "-j":   171             l = journal_dir   172         elif arg == "-p":   173             l = preferences_dir   174         else:   175             l.append(arg)   176    177     try:   178         user = participants[0]   179     except IndexError:   180         print >>sys.stderr, """\   181 Usage: %s <user> [ <other user> ] <options>   182    183 Need a user and an optional participant (if different from the user),   184 along with the -s option if updating the store and the published details.   185 Specify -n to include objects with PARTSTAT of NEEDS-ACTION.   186 Specify -r to inspect all objects, not just those expected to provide details.   187 Specify -v for additional messages on standard error.   188    189 General options:   190    191 -j  indicate the journal directory location   192 -p  indicate the preferences directory location   193 -P  indicate the publishing directory location   194 -S  indicate the store directory location   195 """ % split(sys.argv[0])[1]   196         sys.exit(1)   197    198     # Define any other participant of interest plus options.   199    200     participant = participants[1:] and participants[1] or None   201     store_and_publish = "-s" in args   202     include_needs_action = "-n" in args   203     reset_updated_list = "-r" in args   204     verbose = "-v" in args   205    206     # Override defaults if indicated.   207    208     store_dir = store_dir and store_dir[0] or None   209     publishing_dir = publishing_dir and publishing_dir[0] or None   210     journal_dir = journal_dir and journal_dir[0] or None   211     preferences_dir = preferences_dir and preferences_dir[0] or None   212    213     # Obtain store-related objects.   214    215     store = FileStore(store_dir)   216     publisher = FilePublisher(publishing_dir)   217     journal = FileJournal(journal_dir)   218    219     # Obtain a list of users for processing.   220    221     if user in ("*", "all"):   222         users = store.get_users()   223     else:   224         users = [user]   225    226     # Process the given users.   227    228     for user in users:   229         if verbose:   230             print >>sys.stderr, user   231         make_freebusy(   232             Client(user, None, store, publisher, journal, preferences_dir), participant,   233             store_and_publish, include_needs_action, reset_updated_list, verbose)   234    235 # vim: tabstop=4 expandtab shiftwidth=4