imip-agent

Annotated imiptools/handlers/scheduling/quota.py

1059:861e86e687af
2016-02-09 Paul Boddie Changed the recording of reservations for quotas to use free/busy records.
paul@1039 1
#!/usr/bin/env python
paul@1039 2
paul@1039 3
"""
paul@1039 4
Quota-related scheduling functionality.
paul@1039 5
paul@1039 6
Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk>
paul@1039 7
paul@1039 8
This program is free software; you can redistribute it and/or modify it under
paul@1039 9
the terms of the GNU General Public License as published by the Free Software
paul@1039 10
Foundation; either version 3 of the License, or (at your option) any later
paul@1039 11
version.
paul@1039 12
paul@1039 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@1039 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@1039 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@1039 16
details.
paul@1039 17
paul@1039 18
You should have received a copy of the GNU General Public License along with
paul@1039 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@1039 20
"""
paul@1039 21
paul@1059 22
from imiptools.dates import get_duration, to_utc_datetime
paul@1039 23
from imiptools.data import get_uri
paul@1039 24
from imiptools.period import Endless
paul@1039 25
from datetime import timedelta
paul@1039 26
paul@1039 27
# Quota maintenance.
paul@1039 28
paul@1039 29
def check_quota(handler, args):
paul@1039 30
paul@1039 31
    """
paul@1039 32
    Check the current object of the given 'handler' against the applicable
paul@1039 33
    quota.
paul@1039 34
    """
paul@1039 35
paul@1039 36
    quota, group = _get_quota_and_group(handler, args)
paul@1039 37
paul@1039 38
    # Obtain the journal entries and check the balance.
paul@1039 39
paul@1039 40
    journal = handler.get_journal()
paul@1039 41
    entries = journal.get_entries(quota, group)
paul@1039 42
    limits = journal.get_limits(quota)
paul@1039 43
paul@1039 44
    # Obtain a limit for the group or any general limit.
paul@1039 45
    # Decline invitations if no limit has been set.
paul@1039 46
paul@1039 47
    limit = limits.get(group) or limits.get("*")
paul@1039 48
    if not limit:
paul@1039 49
        return "DECLINED"
paul@1039 50
paul@1039 51
    # Decline events whose durations exceed the balance.
paul@1039 52
paul@1039 53
    total = _get_duration(handler)
paul@1039 54
paul@1039 55
    if total == Endless():
paul@1039 56
        return "DECLINED"
paul@1039 57
paul@1039 58
    balance = get_duration(limit) - _get_usage(entries)
paul@1039 59
paul@1039 60
    if total > balance:
paul@1039 61
        return "DECLINED"
paul@1039 62
    else:
paul@1039 63
        return "ACCEPTED"
paul@1039 64
paul@1039 65
def add_to_quota(handler, args):
paul@1039 66
paul@1039 67
    """
paul@1039 68
    Record details of the current object of the given 'handler' in the
paul@1039 69
    applicable quota.
paul@1039 70
    """
paul@1039 71
paul@1039 72
    quota, group = _get_quota_and_group(handler, args)
paul@1039 73
paul@1039 74
    total = _get_duration(handler)
paul@1048 75
    expiry = _get_expiry_time(handler)
paul@1039 76
paul@1050 77
    # Reject indefinitely recurring events.
paul@1050 78
paul@1050 79
    if total == Endless() or not expiry:
paul@1050 80
        return
paul@1050 81
paul@1059 82
    # Update the journal entries.
paul@1039 83
paul@1039 84
    journal = handler.get_journal()
paul@1040 85
    entries = journal.get_entries(quota, group)
paul@1059 86
    handler.update_freebusy(entries, group, False)
paul@1059 87
    journal.set_entries(quota, group, entries)
paul@1039 88
paul@1039 89
def remove_from_quota(handler, args):
paul@1039 90
paul@1039 91
    """
paul@1039 92
    Remove details of the current object of the given 'handler' from the
paul@1039 93
    applicable quota.
paul@1039 94
    """
paul@1039 95
paul@1039 96
    quota, group = _get_quota_and_group(handler, args)
paul@1039 97
paul@1039 98
    total = _get_duration(handler)
paul@1039 99
paul@1052 100
    # Allow indefinitely recurring events.
paul@1050 101
paul@1050 102
    if total == Endless():
paul@1052 103
        total = None
paul@1050 104
paul@1059 105
    # Update the journal entries.
paul@1039 106
paul@1039 107
    journal = handler.get_journal()
paul@1040 108
    entries = journal.get_entries(quota, group)
paul@1059 109
    handler.remove_from_freebusy(entries)
paul@1059 110
    journal.set_entries(quota, group, entries)
paul@1039 111
paul@1039 112
def _get_quota_and_group(handler, args):
paul@1039 113
paul@1039 114
    """
paul@1039 115
    Combine information about the current object from the 'handler' with the
paul@1039 116
    given 'args' to return a tuple containing the quota group and the user
paul@1039 117
    identity or group involved.
paul@1039 118
    """
paul@1039 119
paul@1039 120
    quota = args and args[0] or handler.user
paul@1039 121
paul@1039 122
    # Obtain the identity to whom the quota will apply.
paul@1039 123
paul@1039 124
    organiser = get_uri(handler.obj.get_value("ORGANIZER"))
paul@1039 125
paul@1039 126
    # Obtain any user group to which the quota will apply instead.
paul@1039 127
paul@1039 128
    journal = handler.get_journal()
paul@1039 129
    groups = journal.get_groups(quota)
paul@1039 130
paul@1039 131
    return quota, groups.get(organiser) or organiser
paul@1039 132
paul@1039 133
def _get_duration(handler):
paul@1039 134
paul@1039 135
    "Return the duration of the current object provided by the 'handler'."
paul@1039 136
paul@1050 137
    # Reject indefinitely recurring events.
paul@1050 138
paul@1050 139
    if handler.obj.possibly_recurring_indefinitely():
paul@1050 140
        return Endless()
paul@1050 141
paul@1050 142
    # Otherwise, return a sum of the period durations.
paul@1039 143
paul@1039 144
    total = timedelta(0)
paul@1039 145
paul@1050 146
    for period in handler.get_periods(handler.obj):
paul@1039 147
        duration = period.get_duration()
paul@1039 148
paul@1039 149
        # Decline events whose period durations are endless.
paul@1039 150
paul@1039 151
        if duration == Endless():
paul@1039 152
            return duration
paul@1039 153
        else:
paul@1039 154
            total += duration
paul@1039 155
paul@1039 156
    return total
paul@1039 157
paul@1048 158
def _get_expiry_time(handler):
paul@1048 159
paul@1048 160
    """
paul@1048 161
    Return the expiry time for quota purposes of the current object provided by
paul@1048 162
    the 'handler'.
paul@1048 163
    """
paul@1048 164
paul@1050 165
    # Reject indefinitely recurring events.
paul@1048 166
paul@1050 167
    if handler.obj.possibly_recurring_indefinitely():
paul@1050 168
        return None
paul@1050 169
paul@1050 170
    periods = handler.get_periods(handler.obj)
paul@1048 171
    return periods and to_utc_datetime(periods[-1].get_end_point()) or None
paul@1048 172
paul@1039 173
def _get_usage(entries):
paul@1039 174
paul@1039 175
    "Return the usage total according to the given 'entries'."
paul@1039 176
paul@1039 177
    total = timedelta(0)
paul@1059 178
    for period in entries:
paul@1059 179
        total += period.get_duration()
paul@1039 180
    return total
paul@1039 181
paul@1039 182
# Collective free/busy maintenance.
paul@1039 183
paul@1039 184
def schedule_across_quota(handler, args):
paul@1039 185
paul@1039 186
    """
paul@1039 187
    Check the current object of the given 'handler' against the schedules
paul@1039 188
    managed by the quota.
paul@1039 189
    """
paul@1039 190
paul@1039 191
    quota, organiser = _get_quota_and_identity(handler, args)
paul@1039 192
paul@1039 193
    # If newer than any old version, discard old details from the
paul@1039 194
    # free/busy record and check for suitability.
paul@1039 195
paul@1039 196
    periods = handler.get_periods(handler.obj)
paul@1039 197
    freebusy = handler.get_journal().get_freebusy(quota, organiser)
paul@1039 198
    scheduled = handler.can_schedule(freebusy, periods)
paul@1039 199
paul@1039 200
    return scheduled and "ACCEPTED" or "DECLINED"
paul@1039 201
paul@1039 202
def add_to_quota_freebusy(handler, args):
paul@1039 203
paul@1039 204
    """
paul@1039 205
    Record details of the current object of the 'handler' in the applicable
paul@1039 206
    free/busy resource.
paul@1039 207
    """
paul@1039 208
paul@1039 209
    quota, organiser = _get_quota_and_identity(handler, args)
paul@1039 210
paul@1039 211
    journal = handler.get_journal()
paul@1040 212
    freebusy = journal.get_freebusy(quota, organiser)
paul@1040 213
    handler.update_freebusy(freebusy, organiser, True)
paul@1040 214
    journal.set_freebusy(quota, organiser, freebusy)
paul@1039 215
paul@1039 216
def remove_from_quota_freebusy(handler, args):
paul@1039 217
paul@1039 218
    """
paul@1039 219
    Remove details of the current object of the 'handler' from the applicable
paul@1039 220
    free/busy resource.
paul@1039 221
    """
paul@1039 222
paul@1039 223
    quota, organiser = _get_quota_and_identity(handler, args)
paul@1039 224
paul@1039 225
    journal = handler.get_journal()
paul@1040 226
    freebusy = journal.get_freebusy(quota, organiser)
paul@1040 227
    handler.remove_from_freebusy(freebusy)
paul@1040 228
    journal.set_freebusy(quota, organiser, freebusy)
paul@1039 229
paul@1039 230
def _get_quota_and_identity(handler, args):
paul@1039 231
paul@1039 232
    """
paul@1039 233
    Combine information about the current object from the 'handler' with the
paul@1039 234
    given 'args' to return a tuple containing the quota group and the user
paul@1039 235
    identity involved.
paul@1039 236
    """
paul@1039 237
paul@1039 238
    quota = args and args[0] or handler.user
paul@1039 239
paul@1039 240
    # Obtain the identity for whom the scheduling will apply.
paul@1039 241
paul@1039 242
    organiser = get_uri(handler.obj.get_value("ORGANIZER"))
paul@1039 243
paul@1039 244
    return quota, organiser
paul@1039 245
paul@1040 246
# Locking and unlocking.
paul@1040 247
paul@1040 248
def lock_journal(handler, args):
paul@1040 249
paul@1040 250
    "Using the 'handler' and 'args', lock the journal for the quota."
paul@1040 251
paul@1040 252
    handler.get_journal().acquire_lock(_get_quota(handler, args))
paul@1040 253
paul@1040 254
def unlock_journal(handler, args):
paul@1040 255
paul@1040 256
    "Using the 'handler' and 'args', unlock the journal for the quota."
paul@1040 257
paul@1040 258
    handler.get_journal().release_lock(_get_quota(handler, args))
paul@1040 259
paul@1040 260
def _get_quota(handler, args):
paul@1040 261
paul@1040 262
    "Return the quota using the 'handler' and 'args'."
paul@1040 263
paul@1040 264
    return args and args[0] or handler.user
paul@1040 265
paul@1039 266
# Registry of scheduling functions.
paul@1039 267
paul@1039 268
scheduling_functions = {
paul@1039 269
    "check_quota" : check_quota,
paul@1039 270
    "schedule_across_quota" : schedule_across_quota,
paul@1039 271
    }
paul@1039 272
paul@1040 273
# Registries of locking and unlocking functions.
paul@1040 274
paul@1040 275
locking_functions = {
paul@1040 276
    "check_quota" : lock_journal,
paul@1040 277
    "schedule_across_quota" : lock_journal,
paul@1040 278
    }
paul@1040 279
paul@1040 280
unlocking_functions = {
paul@1040 281
    "check_quota" : unlock_journal,
paul@1040 282
    "schedule_across_quota" : unlock_journal,
paul@1040 283
    }
paul@1040 284
paul@1039 285
# Registries of listener functions.
paul@1039 286
paul@1039 287
confirmation_functions = {
paul@1058 288
    "check_quota" : add_to_quota,
paul@1058 289
    "schedule_across_quota" : add_to_quota_freebusy,
paul@1039 290
    }
paul@1039 291
paul@1039 292
retraction_functions = {
paul@1058 293
    "check_quota" : remove_from_quota,
paul@1058 294
    "schedule_across_quota" : remove_from_quota_freebusy,
paul@1039 295
    }
paul@1039 296
paul@1039 297
# vim: tabstop=4 expandtab shiftwidth=4