imip-agent

imiptools/handlers/scheduling/freebusy.py

1034:365c2fab060e
2016-01-31 Paul Boddie Fixed unmodified invocations after function signature changes.
     1 #!/usr/bin/env python     2      3 """     4 Free/busy-related scheduling functionality.     5      6 Copyright (C) 2015, 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.data import uri_values    23 from imiptools.dates import ValidityError, to_timezone    24 from imiptools.period import coalesce_freebusy, invert_freebusy, \    25                              periods_from, remove_event_periods, \    26                              remove_periods    27     28 def schedule_in_freebusy(handler, args, freebusy=None):    29     30     """    31     Attempt to schedule the current object of the given 'handler' in the    32     free/busy schedule of a resource, returning an indication of the kind of    33     response to be returned.    34     35     If 'freebusy' is specified, the given collection of busy periods will be    36     used to determine whether any conflicts occur. Otherwise, the current user's    37     free/busy records will be used.    38     """    39     40     # If newer than any old version, discard old details from the    41     # free/busy record and check for suitability.    42     43     periods = handler.get_periods(handler.obj)    44     45     freebusy = freebusy or handler.store.get_freebusy(handler.user)    46     offers = handler.store.get_freebusy_offers(handler.user)    47     48     # Check the periods against any scheduled events and against    49     # any outstanding offers.    50     51     scheduled = handler.can_schedule(freebusy, periods)    52     scheduled = scheduled and handler.can_schedule(offers, periods)    53     54     return scheduled and "ACCEPTED" or "DECLINED"    55     56 def schedule_corrected_in_freebusy(handler, args):    57     58     """    59     Attempt to schedule the current object of the given 'handler', correcting    60     specified datetimes according to the configuration of a resource,    61     returning an indication of the kind of response to be returned.    62     """    63     64     obj = handler.obj.copy()    65     66     # Check any constraints on the request.    67     68     try:    69         corrected = handler.correct_object()    70     71     # Refuse to schedule obviously invalid requests.    72     73     except ValidityError:    74         return None    75     76     # With a valid request, determine whether the event can be scheduled.    77     78     scheduled = schedule_in_freebusy(handler, args)    79     80     # Restore the original object if it was corrected but could not be    81     # scheduled.    82     83     if scheduled == "DECLINED" and corrected:    84         handler.set_object(obj)    85         86     # Where the corrected object can be scheduled, issue a counter    87     # request.    88     89     return scheduled == "ACCEPTED" and (corrected and "COUNTER" or "ACCEPTED") or "DECLINED"    90     91 def schedule_next_available_in_freebusy(handler, args):    92     93     """    94     Attempt to schedule the current object of the given 'handler', correcting    95     specified datetimes according to the configuration of a resource, then    96     suggesting the next available period in the free/busy records if scheduling    97     cannot occur for the requested period, returning an indication of the kind    98     of response to be returned.    99     """   100    101     scheduled = schedule_corrected_in_freebusy(handler, args)   102    103     if scheduled in ("ACCEPTED", "COUNTER"):   104         return scheduled   105    106     # There should already be free/busy information for the user.   107    108     user_freebusy = handler.store.get_freebusy(handler.user)   109     busy = user_freebusy   110    111     # Subtract any periods from this event from the free/busy collections.   112    113     event_periods = remove_event_periods(user_freebusy, handler.uid, handler.recurrenceid)   114    115     # Find busy periods for the other attendees.   116    117     for attendee in uri_values(handler.obj.get_values("ATTENDEE")):   118         if attendee != handler.user:   119             freebusy = handler.store.get_freebusy_for_other(handler.user, attendee)   120             if freebusy:   121                 remove_periods(freebusy, event_periods)   122                 busy += freebusy   123    124     # Obtain the combined busy periods.   125    126     busy.sort()   127     busy = coalesce_freebusy(busy)   128    129     # Obtain free periods.   130    131     free = invert_freebusy(busy)   132     permitted_values = handler.get_permitted_values()   133     periods = []   134    135     # Do not attempt to redefine rule-based periods.   136    137     last = None   138    139     for period in handler.get_periods(handler.obj, explicit_only=True):   140         duration = period.get_duration()   141    142         # Try and schedule periods normally since some of them may be   143         # compatible with the schedule.   144    145         if permitted_values:   146             period = period.get_corrected(permitted_values)   147    148         scheduled = handler.can_schedule(freebusy, [period])   149    150         if scheduled == "ACCEPTED":   151             periods.append(period)   152             last = period.get_end()   153             continue   154    155         # Get free periods from the time of each period.   156    157         for found in periods_from(free, period):   158    159             # Skip any periods before the last period.   160    161             if last:   162                 if last > found.get_end():   163                     continue   164    165                 # Adjust the start of the free period to exclude the last period.   166    167                 found = found.make_corrected(max(found.get_start(), last), found.get_end())   168    169             # Only test free periods long enough to hold the requested period.   170    171             if found.get_duration() >= duration:   172    173                 # Obtain a possible period, starting at the found point and   174                 # with the requested duration. Then, correct the period if   175                 # necessary.   176    177                 start = to_timezone(found.get_start(), period.get_tzid())   178                 possible = period.make_corrected(start, start + period.get_duration())   179                 if permitted_values:   180                     possible = possible.get_corrected(permitted_values)   181    182                 # Only if the possible period is still within the free period   183                 # can it be used.   184    185                 if possible.within(found):   186                     periods.append(possible)   187                     break   188    189         # Where no period can be found, decline the invitation.   190    191         else:   192             return "DECLINED"   193    194         # Use the found period to set the start of the next window to search.   195    196         last = periods[-1].get_end()   197    198     # Replace the periods in the object.   199    200     obj = handler.obj.copy()   201     changed = handler.obj.set_periods(periods)   202    203     # Check one last time, reverting the change if not scheduled.   204    205     scheduled = schedule_in_freebusy(handler, args, busy)   206    207     if scheduled == "DECLINED":   208         handler.set_object(obj)   209    210     return scheduled == "ACCEPTED" and (changed and "COUNTER" or "ACCEPTED") or "DECLINED"   211    212 # Registry of scheduling functions.   213    214 scheduling_functions = {   215     "schedule_in_freebusy" : schedule_in_freebusy,   216     "schedule_corrected_in_freebusy" : schedule_corrected_in_freebusy,   217     "schedule_next_available_in_freebusy" : schedule_next_available_in_freebusy,   218     }   219    220 # vim: tabstop=4 expandtab shiftwidth=4