imip-agent

imiptools/handlers/scheduling/quota.py

1169:2121da71f66d
2016-05-11 Paul Boddie Use the recipient identity, not a group, when populating free/busy records.
     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     _ = handler.get_translator()    37     38     quota, group = _get_quota_and_group(handler, args)    39     40     # Obtain the journal entries and check the balance.    41     42     journal = handler.get_journal()    43     entries = journal.get_entries(quota, group)    44     limits = journal.get_limits(quota)    45     46     # Obtain a limit for the group or any general limit.    47     # Decline invitations if no limit has been set.    48     49     limit = limits.get(group) or limits.get("*")    50     if not limit:    51         return "DECLINED", _("You have no quota allocation for the recipient.")    52     53     # Decline events whose durations exceed the balance.    54     55     total = _get_duration(handler)    56     57     if total == Endless():    58         return "DECLINED", _("The event period exceeds your quota allocation for the recipient.")    59     60     balance = get_duration(limit) - _get_usage(entries)    61     62     if total > balance:    63         return "DECLINED", _("The event period exceeds your quota allocation for the recipient.")    64     else:    65         return "ACCEPTED", _("The recipient has scheduled the requested period.")    66     67 def add_to_quota(handler, args):    68     69     """    70     Record details of the current object of the given 'handler' in the    71     applicable quota.    72     """    73     74     quota, group = _get_quota_and_group(handler, args)    75     76     total = _get_duration(handler)    77     expiry = _get_expiry_time(handler)    78     79     # Reject indefinitely recurring events.    80     81     if total == Endless() or not expiry:    82         return    83     84     # Update the journal entries.    85     86     journal = handler.get_journal()    87     entries = journal.get_entries_for_update(quota, group)    88     handler.update_freebusy(entries, handler.user, False)    89     journal.set_entries(quota, group, entries)    90     91 def remove_from_quota(handler, args):    92     93     """    94     Remove details of the current object of the given 'handler' from the    95     applicable quota.    96     """    97     98     quota, group = _get_quota_and_group(handler, args)    99    100     total = _get_duration(handler)   101    102     # Allow indefinitely recurring events.   103    104     if total == Endless():   105         total = None   106    107     # Update the journal entries.   108    109     journal = handler.get_journal()   110     entries = journal.get_entries_for_update(quota, group)   111     handler.remove_from_freebusy(entries)   112     journal.set_entries(quota, group, entries)   113    114 def _get_quota_and_group(handler, args):   115    116     """   117     Combine information about the current object from the 'handler' with the   118     given 'args' to return a tuple containing the quota group and the user   119     identity or group involved.   120     """   121    122     quota = args and args[0] or handler.user   123    124     # Obtain the identity to whom the quota will apply.   125    126     organiser = get_uri(handler.obj.get_value("ORGANIZER"))   127    128     # Obtain any user group to which the quota will apply instead.   129    130     journal = handler.get_journal()   131     groups = journal.get_groups(quota)   132    133     return quota, groups.get(organiser) or organiser   134    135 def _get_duration(handler):   136    137     "Return the duration of the current object provided by the 'handler'."   138    139     # Reject indefinitely recurring events.   140    141     if handler.obj.possibly_recurring_indefinitely():   142         return Endless()   143    144     # Otherwise, return a sum of the period durations.   145    146     total = timedelta(0)   147    148     for period in handler.get_periods(handler.obj):   149         duration = period.get_duration()   150    151         # Decline events whose period durations are endless.   152    153         if duration == Endless():   154             return duration   155         else:   156             total += duration   157    158     return total   159    160 def _get_expiry_time(handler):   161    162     """   163     Return the expiry time for quota purposes of the current object provided by   164     the 'handler'.   165     """   166    167     # Reject indefinitely recurring events.   168    169     if handler.obj.possibly_recurring_indefinitely():   170         return None   171    172     periods = handler.get_periods(handler.obj)   173     return periods and to_utc_datetime(periods[-1].get_end_point()) or None   174    175 def _get_usage(entries):   176    177     "Return the usage total according to the given 'entries'."   178    179     total = timedelta(0)   180     for period in entries:   181         total += period.get_duration()   182     return total   183    184 # Collective free/busy maintenance.   185    186 def schedule_across_quota(handler, args):   187    188     """   189     Check the current object of the given 'handler' against the schedules   190     managed by the quota.   191     """   192    193     _ = handler.get_translator()   194    195     quota, organiser = _get_quota_and_identity(handler, args)   196    197     # Check the event periods against the quota's consolidated record of the   198     # organiser's reservations.   199    200     periods = handler.get_periods(handler.obj)   201     freebusy = handler.get_journal().get_freebusy(quota, organiser)   202     scheduled = handler.can_schedule(freebusy, periods)   203    204     if scheduled:   205         return "ACCEPTED", _("The recipient has scheduled the requested period.")   206     else:   207         return "DECLINED", _("The requested period cannot be scheduled.")   208    209 def add_to_quota_freebusy(handler, args):   210    211     """   212     Record details of the current object of the 'handler' in the applicable   213     free/busy resource.   214     """   215    216     quota, organiser = _get_quota_and_identity(handler, args)   217    218     journal = handler.get_journal()   219     freebusy = journal.get_freebusy_for_update(quota, organiser)   220     handler.update_freebusy(freebusy, organiser, True)   221     journal.set_freebusy(quota, organiser, freebusy)   222    223 def remove_from_quota_freebusy(handler, args):   224    225     """   226     Remove details of the current object of the 'handler' from the applicable   227     free/busy resource.   228     """   229    230     quota, organiser = _get_quota_and_identity(handler, args)   231    232     journal = handler.get_journal()   233     freebusy = journal.get_freebusy_for_update(quota, organiser)   234     handler.remove_from_freebusy(freebusy)   235     journal.set_freebusy(quota, organiser, freebusy)   236    237 def _get_quota_and_identity(handler, args):   238    239     """   240     Combine information about the current object from the 'handler' with the   241     given 'args' to return a tuple containing the quota group and the user   242     identity involved.   243     """   244    245     quota = args and args[0] or handler.user   246    247     # Obtain the identity for whom the scheduling will apply.   248    249     organiser = get_uri(handler.obj.get_value("ORGANIZER"))   250    251     return quota, organiser   252    253 # Locking and unlocking.   254    255 def lock_journal(handler, args):   256    257     "Using the 'handler' and 'args', lock the journal for the quota."   258    259     handler.get_journal().acquire_lock(_get_quota(handler, args))   260    261 def unlock_journal(handler, args):   262    263     "Using the 'handler' and 'args', unlock the journal for the quota."   264    265     handler.get_journal().release_lock(_get_quota(handler, args))   266    267 def _get_quota(handler, args):   268    269     "Return the quota using the 'handler' and 'args'."   270    271     return args and args[0] or handler.user   272    273 # Registry of scheduling functions.   274    275 scheduling_functions = {   276     "check_quota" : check_quota,   277     "schedule_across_quota" : schedule_across_quota,   278     }   279    280 # Registries of locking and unlocking functions.   281    282 locking_functions = {   283     "check_quota" : lock_journal,   284     "schedule_across_quota" : lock_journal,   285     }   286    287 unlocking_functions = {   288     "check_quota" : unlock_journal,   289     "schedule_across_quota" : unlock_journal,   290     }   291    292 # Registries of listener functions.   293    294 confirmation_functions = {   295     "check_quota" : add_to_quota,   296     "schedule_across_quota" : add_to_quota_freebusy,   297     }   298    299 retraction_functions = {   300     "check_quota" : remove_from_quota,   301     "schedule_across_quota" : remove_from_quota_freebusy,   302     }   303    304 # vim: tabstop=4 expandtab shiftwidth=4