imip-agent

Annotated tools/make_freebusy.py

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