imip-agent

Annotated tools/make_freebusy.py

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