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