Projects
Wiki     Timeline     Roadmap     Browse Source     View Tickets     New Ticket     Search

Ticket #207: Ticket207.patch

File Ticket207.patch, 23.2 KB (added by apm@…, 5 months ago)

Proposal for patch against CalendarServer 1.2 sources

  • twistedcaldav/index.py

    diff -x '*.svn' -x '*.pyc' -ru ./twistedcaldav/index.py ../../DCS1.2/CalendarServer-1.2/twistedcaldav/index.py
    old new  
    2626__all__ = [ 
    2727    "Index", 
    2828    "IndexSchedule", 
     29    "IndexedSearchException", 
    2930] 
    3031 
    3132import datetime 
     
    8384    which is not reserved. 
    8485    """ 
    8586 
     87class IndexedSearchException(ValueError): 
     88    pass 
     89 
     90 
    8691class AbstractCalendarIndex(AbstractSQLDatabase): 
    8792    """ 
    8893    Calendar collection index abstract base class that defines the apis for the index. 
     
    246251        statement += ")" 
    247252        results = self._db_values_for_sql(statement, *names) 
    248253        return results 
    249      
    250     def searchValid(self, filter): 
    251         if isinstance(filter, caldavxml.Filter): 
    252             qualifiers = calendarquery.sqlcalendarquery(filter) 
    253         else: 
    254             qualifiers = None 
    255              
    256         return qualifiers is not None 
    257254 
    258     def search(self, filter): 
     255    def testAndUpdateIndex(self, mindate): 
     256        # Find out if the index is expanded far enough. 
     257        names = self.notExpandedBeyond(mindate) 
     258        # Actually expand recurrance max 
     259        for name in names: 
     260            self.reExpandResource(name,mindate) 
     261 
     262    def indexedSearch(self, filter): 
    259263        """ 
    260264        Finds resources matching the given qualifiers. 
    261265        @param filter: the L{Filter} for the calendar-query to execute. 
     
    266270        """ 
    267271        # FIXME: Don't forget to use maximum_future_expansion_duration when we 
    268272        # start caching... 
    269          
     273 
    270274        # Make sure we have a proper Filter element and get the partial SQL statement to use. 
    271275        if isinstance(filter, caldavxml.Filter): 
    272             qualifiers = calendarquery.sqlcalendarquery(filter) 
     276            # Get a tuple of (SQL-qualifier, hint-to-update-index:date) 
     277            qualifier = calendarquery.sqlcalendarquery(filter) 
     278            if qualifier is not None: 
     279                sqlqualifiers = qualifier[0] 
     280                if qualifier[1] is not None: 
     281                    # bring index up to a a given date if it's not already 
     282                    self.testAndUpdateIndex(qualifier[1]) 
     283            else: 
     284                # We cannot handle this filter in an indexed search 
     285                raise IndexedSearchException() 
    273286        else: 
    274             qualifiers = None 
    275         if qualifiers is not None: 
    276             rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE" + qualifiers[0], *qualifiers[1]) 
     287            sqlqualifiers = None 
     288 
     289        # Perform the search 
     290        if sqlqualifiers is not None: 
     291            rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE" + sqlqualifiers[0], 
     292                                       *sqlqualifiers[1]) 
    277293        else: 
    278             rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE") 
    279              
     294            raise IndexedSearchException() 
     295 
     296        # Check result for missing resources 
     297        for row in rowiter: 
     298            name = row[0] 
     299            if self.resource.getChild(name.encode("utf-8")): 
     300                yield row 
     301            else: 
     302                log.err("Calendar resource %s is missing from %s. Removing from index." 
     303                        % (name, self.resource)) 
     304                self.deleteResource(name) 
     305 
     306    def bruteforceSearch(self): 
     307        """ 
     308        List the whole index and tests for existance, updating the index. 
     309        @return: all resources in the index. 
     310        """ 
     311        # List all resources 
     312        rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE") 
     313 
     314        # Check result for missing resources 
    280315        for row in rowiter: 
    281316            name = row[0] 
    282317            if self.resource.getChild(name.encode("utf-8")): 
     
    393428                """ 
    394429            ) 
    395430 
    396     def _add_to_db(self, name, calendar, cursor = None): 
     431    def notExpandedBeyond(self, mindate): 
     432        """ 
     433        Gives all resources which has not been expanded beyond a given date in the index 
     434        """ 
     435        return self._db_values_for_sql("select NAME from RESOURCE where RECURRANCE_MAX <= :1", mindate) 
     436 
     437    def reExpandResource(self,name,until_date): 
     438        """ 
     439        Given a resource name, remove it from the database and re-add it 
     440        with a longer expansion. 
     441        """ 
     442        calendar = self.resource.getChild(name).iCalendar() 
     443 
     444        oldUID = self.resourceUIDForName(name) 
     445        if oldUID is not None: 
     446            self._delete_from_db(name, oldUID) 
     447        self._add_to_db(name, calendar,until_date) 
     448        self._db_commit() 
     449 
     450 
     451    def _add_to_db(self, name, calendar, expand_wish = None, cursor = None): 
    397452        """ 
    398453        Records the given calendar resource in the index with the given name. 
    399454        Resource names and UIDs must both be unique; only one resource name may 
     
    406461        """ 
    407462        uid = calendar.resourceUID() 
    408463 
    409         expand_max = datetime.date.today() + default_future_expansion_duration 
     464        expand_default = datetime.date.today() + default_future_expansion_duration 
     465        expand_max     = datetime.date.today() + maximum_future_expansion_duration 
     466 
     467        expand = expand_default 
     468        if expand_wish is not None: 
     469            if expand_wish > expand_default: 
     470                expand = expand_wish 
     471            if expand > expand_max: 
     472                # Will not handle 
     473                raise IndexedSearchException 
    410474 
    411         instances = calendar.expandTimeRanges(expand_max) 
     475        instances = calendar.expandTimeRanges(expand) 
    412476        for key in instances: 
    413477            instance = instances[key] 
    414478            start = instance.start.replace(tzinfo=utc) 
  • twistedcaldav/method/report_calquery.py

    diff -x '*.svn' -x '*.pyc' -ru ./twistedcaldav/method/report_calquery.py ../../DCS1.2/CalendarServer-1.2/twistedcaldav/method/report_calquery.py
    old new  
    3535from twistedcaldav.caldavxml import caldav_namespace 
    3636from twistedcaldav.customxml import TwistedCalendarAccessProperty 
    3737from twistedcaldav.method import report_common 
     38from twistedcaldav.index import IndexedSearchException 
    3839 
    3940import urllib 
    4041 
     
    166167 
    167168            # Check for disabled access 
    168169            if filteredaces is not None: 
    169                 # See whether the filter is valid for an index only query 
    170                 index_query_ok = calresource.index().searchValid(filter) 
    171              
    172                 # Get list of children that match the search and have read access 
    173                 names = [name for name, ignore_uid, ignore_type in calresource.index().search(filter)] 
     170                # Variable to remember whether the filter is valid for an index only query 
     171                index_query_ok = None 
     172                try: 
     173                    # Get list of children that match the search and have read access 
     174                    names = [name for name, ignore_uid, ignore_type in calresource.index().indexedSearch(filter)] 
     175                    index_query_ok = True 
     176                except IndexedSearchException: 
     177                    names = [name for name, ignore_uid, ignore_type in calresource.index().bruteforceSearch()] 
     178                    index_query_ok = False 
     179 
    174180                if not names: 
    175181                    yield None 
    176182                    return 
    177                    
     183 
    178184                # Now determine which valid resources are readable and which are not 
    179185                ok_resources = [] 
    180186                d = calresource.findChildrenFaster( 
     
    189195                x = waitForDeferred(d) 
    190196                yield x 
    191197                x.getResult() 
    192                  
     198 
    193199                for child, child_uri in ok_resources: 
    194200                    child_uri_name = child_uri[child_uri.rfind("/") + 1:] 
    195201                    child_path_name = urllib.unquote(child_uri_name) 
  • twistedcaldav/method/report_common.py

    diff -x '*.svn' -x '*.pyc' -ru ./twistedcaldav/method/report_common.py ../../DCS1.2/CalendarServer-1.2/twistedcaldav/method/report_common.py
    old new  
    4747from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap 
    4848from twistedcaldav.ical import Component, Property, iCalendarProductID 
    4949from twistedcaldav.instance import InstanceList 
     50from twistedcaldav.index import IndexedSearchException 
    5051 
    5152from vobject.icalendar import utc 
    5253 
     
    346347    yield filteredaces 
    347348    filteredaces = filteredaces.getResult() 
    348349 
    349     for name, uid, type in calresource.index().search(filter): #@UnusedVariable 
     350    try: 
     351        resources = calresource.index().indexedSearch(filter) 
     352    except IndexedSearchException: 
     353        resources = calresource.index().bruteforceSearch() 
     354 
     355    for name, uid, type in resources: #@UnusedVariable 
    350356         
    351357        # Check privileges - must have at least CalDAV:read-free-busy 
    352358        child = waitForDeferred(request.locateChildResource(calresource, name)) 
  • twistedcaldav/query/calendarquery.py

    diff -x '*.svn' -x '*.pyc' -ru ./twistedcaldav/query/calendarquery.py ../../DCS1.2/CalendarServer-1.2/twistedcaldav/query/calendarquery.py
    old new  
    2424__version__ = "0.0" 
    2525 
    2626__all__ = [ 
    27     "calendarquery", 
     27    "CalendarQueryExpressionGenerator", 
    2828    "sqlcalendarquery", 
    2929] 
    3030 
     
    4242#FIELD_DESCRIPTION = "RESOURCE.DESCRIPTION" 
    4343#FIELD_LOCATION = "RESOURCE.LOCATION" 
    4444 
    45 def calendarquery(filter): 
     45class CalendarQueryExpressionGenerator(object): 
    4646    """ 
    47     Convert the supplied calendar-query into an expression tree. 
    48  
    49     @param filter: the L{Filter} for thw calendar-query to convert. 
    50     @return: a L{baseExpression} for the expression tree. 
     47    Can parse CalDAV XML filters to expression structures and 
     48    Hold information about the expression useful for clients 
    5149    """ 
    52      
    53     # Lets assume we have a valid filter from the outset. 
    54      
    55     # Top-level filter contains exactly one comp-filter element 
    56     assert len(filter.children) == 1 
    57     vcalfilter = filter.children[0] 
    58     assert isinstance(vcalfilter, caldavxml.ComponentFilter) 
    59     assert vcalfilter.filter_name == "VCALENDAR" 
    60      
    61     if len(vcalfilter.children) > 0: 
    62         # Only comp-filters are handled 
    63         for _ignore in [x for x in vcalfilter.children if not isinstance(x, caldavxml.ComponentFilter)]: 
     50 
     51    def __init__(self): 
     52        self.enddate = None 
     53 
     54    def calendarquery(self, filter): 
     55        """ 
     56        Convert the supplied calendar-query into an expression tree. 
     57 
     58        @param filter: the L{Filter} for thw calendar-query to convert. 
     59        @return: a L{baseExpression} for the expression tree. 
     60        """ 
     61 
     62        # Lets assume we have a valid filter from the outset. 
     63 
     64        # Top-level filter contains exactly one comp-filter element 
     65        assert len(filter.children) == 1 
     66        vcalfilter = filter.children[0] 
     67        assert isinstance(vcalfilter, caldavxml.ComponentFilter) 
     68        assert vcalfilter.filter_name == "VCALENDAR" 
     69 
     70        if len(vcalfilter.children) > 0: 
     71            # Only comp-filters are handled 
     72            for _ignore in [x for x in vcalfilter.children if not isinstance(x, caldavxml.ComponentFilter)]: 
     73                raise ValueError 
     74 
     75            return self.compfilterListExpression(vcalfilter.children) 
     76        else: 
     77            return expression.allExpression() 
     78 
     79    def compfilterListExpression(self, compfilters): 
     80        """ 
     81        Create an expression for a list of comp-filter elements. 
     82 
     83        @param compfilters: the C{list} of L{ComponentFilter} elements. 
     84        @return: a L{baseExpression} for the expression tree. 
     85        """ 
     86 
     87        if len(compfilters) == 1: 
     88            return self.compfilterExpression(compfilters[0]) 
     89        else: 
     90            return expression.orExpression([self.compfilterExpression(c) for c in compfilters]) 
     91 
     92    def compfilterExpression(self, compfilter): 
     93        """ 
     94        Create an expression for a single comp-filter element. 
     95 
     96        @param compfilter: the L{ComponentFilter} element. 
     97        @return: a L{baseExpression} for the expression tree. 
     98        """ 
     99 
     100        # Handle is-not-defined case 
     101        if not compfilter.defined: 
     102            # Test for TYPE != <<component-type name>> 
     103            return expression.isnotExpression(FIELD_TYPE, compfilter.filter_name, True) 
     104 
     105        expressions = [] 
     106        if isinstance(compfilter.filter_name, str): 
     107            expressions.append(expression.isExpression(FIELD_TYPE, compfilter.filter_name, True)) 
     108        else: 
     109            expressions.append(expression.inExpression(FIELD_TYPE, compfilter.filter_name, True)) 
     110 
     111        # Handle time-range 
     112        if compfilter.qualifier and isinstance(compfilter.qualifier, caldavxml.TimeRange): 
     113            start, end, startfloat, endfloat = getTimerangeArguments(compfilter.qualifier) 
     114            expressions.append(expression.timerangeExpression(start, end, startfloat, endfloat)) 
     115            self.enddate = compfilter.qualifier.end.date() 
     116 
     117        # Handle properties - we can only do UID right now 
     118        props = [] 
     119        for p in [x for x in compfilter.filters if isinstance(x, caldavxml.PropertyFilter)]: 
     120            props.append(self.propfilterExpression(p)) 
     121        if len(props) > 1: 
     122            propsExpression = expression.orExpression[props] 
     123        elif len(props) == 1: 
     124            propsExpression = props[0] 
     125        else: 
     126            propsExpression = None 
     127 
     128        # Handle embedded components - we do not right now as our Index does not handle them 
     129        comps = [] 
     130        for _ignore in [x for x in compfilter.filters if isinstance(x, caldavxml.ComponentFilter)]: 
    64131            raise ValueError 
    65          
    66         return compfilterListExpression(vcalfilter.children) 
    67     else: 
    68         return expression.allExpression() 
     132        if len(comps) > 1: 
     133            compsExpression = expression.orExpression[comps] 
     134        elif len(comps) == 1: 
     135            compsExpression = comps[0] 
     136        else: 
     137            compsExpression = None 
    69138 
    70 def compfilterListExpression(compfilters): 
    71     """ 
    72     Create an expression for a list of comp-filter elements. 
    73      
    74     @param compfilters: the C{list} of L{ComponentFilter} elements. 
    75     @return: a L{baseExpression} for the expression tree. 
    76     """ 
    77      
    78     if len(compfilters) == 1: 
    79         return compfilterExpression(compfilters[0]) 
    80     else: 
    81         return expression.orExpression([compfilterExpression(c) for c in compfilters]) 
     139        # Now build compound expression 
     140        if ((propsExpression is not None) and (compsExpression is not None)): 
     141            expressions.append(expression.orExpression([propsExpression, compsExpression])) 
     142        elif propsExpression is not None: 
     143            expressions.append(propsExpression) 
     144        elif compsExpression is not None: 
     145            expressions.append(compsExpression) 
     146 
     147        # Now build return expression 
     148        return expression.andExpression(expressions) 
     149 
     150    def propfilterExpression(self, propfilter): 
     151        """ 
     152        Create an expression for a single prop-filter element. 
     153 
     154        @param propfilter: the L{PropertyFilter} element. 
     155        @return: a L{baseExpression} for the expression tree. 
     156        """ 
    82157 
    83 def compfilterExpression(compfilter): 
    84     """ 
    85     Create an expression for a single comp-filter element. 
    86      
    87     @param compfilter: the L{ComponentFilter} element. 
    88     @return: a L{baseExpression} for the expression tree. 
    89     """ 
    90      
    91     # Handle is-not-defined case 
    92     if not compfilter.defined: 
    93         # Test for TYPE != <<component-type name>> 
    94         return expression.isnotExpression(FIELD_TYPE, compfilter.filter_name, True) 
    95          
    96     expressions = [] 
    97     if isinstance(compfilter.filter_name, str): 
    98         expressions.append(expression.isExpression(FIELD_TYPE, compfilter.filter_name, True)) 
    99     else: 
    100         expressions.append(expression.inExpression(FIELD_TYPE, compfilter.filter_name, True)) 
    101      
    102     # Handle time-range     
    103     if compfilter.qualifier and isinstance(compfilter.qualifier, caldavxml.TimeRange): 
    104         start, end, startfloat, endfloat = getTimerangeArguments(compfilter.qualifier) 
    105         expressions.append(expression.timerangeExpression(start, end, startfloat, endfloat)) 
    106          
    107     # Handle properties - we can only do UID right now 
    108     props = [] 
    109     for p in [x for x in compfilter.filters if isinstance(x, caldavxml.PropertyFilter)]: 
    110         props.append(propfilterExpression(p)) 
    111     if len(props) > 1: 
    112         propsExpression = expression.orExpression[props] 
    113     elif len(props) == 1: 
    114         propsExpression = props[0] 
    115     else: 
    116         propsExpression = None 
    117          
    118     # Handle embedded components - we do not right now as our Index does not handle them 
    119     comps = [] 
    120     for _ignore in [x for x in compfilter.filters if isinstance(x, caldavxml.ComponentFilter)]: 
    121         raise ValueError 
    122     if len(comps) > 1: 
    123         compsExpression = expression.orExpression[comps] 
    124     elif len(comps) == 1: 
    125         compsExpression = comps[0] 
    126     else: 
    127         compsExpression = None 
    128  
    129     # Now build compound expression 
    130     if ((propsExpression is not None) and (compsExpression is not None)): 
    131         expressions.append(expression.orExpression([propsExpression, compsExpression])) 
    132     elif propsExpression is not None: 
    133         expressions.append(propsExpression) 
    134     elif compsExpression is not None: 
    135         expressions.append(compsExpression) 
     158        # Only handle UID right now 
     159        if propfilter.filter_name != "UID": 
     160            raise ValueError 
    136161 
    137     # Now build return expression 
    138     return expression.andExpression(expressions) 
     162        # Handle is-not-defined case 
     163        if not propfilter.defined: 
     164            # Test for <<field>> != "*" 
     165            return expression.isExpression(FIELD_UID, "", True) 
    139166 
    140 def propfilterExpression(propfilter): 
    141     """ 
    142     Create an expression for a single prop-filter element. 
    143      
    144     @param propfilter: the L{PropertyFilter} element. 
    145     @return: a L{baseExpression} for the expression tree. 
    146     """ 
    147      
    148     # Only handle UID right now 
    149     if propfilter.filter_name != "UID": 
    150         raise ValueError 
    151  
    152     # Handle is-not-defined case 
    153     if not propfilter.defined: 
    154         # Test for <<field>> != "*" 
    155         return expression.isExpression(FIELD_UID, "", True) 
    156      
    157     # Handle time-range - we cannot do this with our Index right now 
    158     if propfilter.qualifier and isinstance(propfilter.qualifier, caldavxml.TimeRange): 
    159         raise ValueError 
    160      
    161     # Handle text-match 
    162     tm = None 
    163     if propfilter.qualifier and isinstance(propfilter.qualifier, caldavxml.TextMatch): 
    164         if propfilter.qualifier.negate: 
    165             tm = expression.notcontainsExpression(propfilter.filter_name, str(propfilter.qualifier), propfilter.qualifier) 
     167        # Handle time-range - we cannot do this with our Index right now 
     168        if propfilter.qualifier and isinstance(propfilter.qualifier, caldavxml.TimeRange): 
     169            raise ValueError 
     170 
     171        # Handle text-match 
     172        tm = None 
     173        if propfilter.qualifier and isinstance(propfilter.qualifier, caldavxml.TextMatch): 
     174            if propfilter.qualifier.negate: 
     175                tm = expression.notcontainsExpression(propfilter.filter_name, str(propfilter.qualifier), propfilter.qualifier) 
     176            else: 
     177                tm = expression.containsExpression(propfilter.filter_name, str(propfilter.qualifier), propfilter.qualifier) 
     178 
     179        # Handle embedded parameters - we do not right now as our Index does not handle them 
     180        params = [] 
     181        for _ignore in propfilter.filters: 
     182            raise ValueError 
     183        if len(params) > 1: 
     184            paramsExpression = expression.orExpression[params] 
     185        elif len(params) == 1: 
     186            paramsExpression = params[0] 
    166187        else: 
    167             tm = expression.containsExpression(propfilter.filter_name, str(propfilter.qualifier), propfilter.qualifier) 
    168      
    169     # Handle embedded parameters - we do not right now as our Index does not handle them 
    170     params = [] 
    171     for _ignore in propfilter.filters: 
    172         raise ValueError 
    173     if len(params) > 1: 
    174         paramsExpression = expression.orExpression[params] 
    175     elif len(params) == 1: 
    176         paramsExpression = params[0] 
    177     else: 
    178         paramsExpression = None 
    179  
    180     # Now build return expression 
    181     if (tm is not None) and (paramsExpression is not None): 
    182         return expression.andExpression([tm, paramsExpression]) 
    183     elif tm is not None: 
    184         return tm 
    185     elif paramsExpression is not None: 
    186         return paramsExpression 
    187     else: 
    188         return None 
     188            paramsExpression = None 
     189 
     190        # Now build return expression 
     191        if (tm is not None) and (paramsExpression is not None): 
     192            return expression.andExpression([tm, paramsExpression]) 
     193        elif tm is not None: 
     194            return tm 
     195        elif paramsExpression is not None: 
     196            return paramsExpression 
     197        else: 
     198            return None 
    189199 
     200# Utility function 
    190201def getTimerangeArguments(timerange): 
    191202    """ 
    192203    Get start/end and floating start/end (adjusted for timezone offset) values from the 
     
    209220 
    210221    return str(start), str(end), str(startfloat), str(endfloat) 
    211222 
     223# return SQL qualifiers and info to index. 
    212224def sqlcalendarquery(filter): 
    213225    """ 
    214     Convert the supplied calendar-query into a oartial SQL statement. 
     226    Convert the supplied calendar-query into a partial SQL statement. 
    215227 
    216     @param filter: the L{Filter} for thw calendar-query to convert. 
     228    @param filter: the L{Filter} for the calendar-query to convert. 
    217229    @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement, 
    218230            and the C{list} is the list of argument substitutions to use with the SQL API execute method. 
    219231            Or return C{None} if it is not possible to create an SQL query to fully match the calendar-query. 
    220232    """ 
    221233    try: 
    222         expression = calendarquery(filter) 
     234        expression_generator = CalendarQueryExpressionGenerator() 
     235        expression = expression_generator.calendarquery(filter) 
    223236        sql = sqlgenerator.sqlgenerator(expression) 
    224         return sql.generate() 
     237        qualifiers = (sql.generate(),expression_generator.enddate) 
     238        return qualifiers 
    225239    except ValueError: 
     240        # Indicate an unsupported filter 
    226241        return None 
    227242 
    228243 
  • twistedcaldav/static.py

    diff -x '*.svn' -x '*.pyc' -ru ./twistedcaldav/static.py ../../DCS1.2/CalendarServer-1.2/twistedcaldav/static.py
    old new  
    187187            filteredaces = filteredaces.getResult() 
    188188 
    189189            # Must verify ACLs which means we need a request object at this point 
    190             for name, uid, type in self.index().search(None): #@UnusedVariable 
     190            for name, uid, type in self.index().bruteforceSearch(None): #@UnusedVariable 
    191191                try: 
    192192                    child = waitForDeferred(request.locateChildResource(self, name)) 
    193193                    yield child