Ticket #207: Ticket207.patch
| File Ticket207.patch, 23.2 KB (added by apm@…, 5 months ago) |
|---|
-
twistedcaldav/index.py
diff -x '*.svn' -x '*.pyc' -ru ./twistedcaldav/index.py ../../DCS1.2/CalendarServer-1.2/twistedcaldav/index.py
old new 26 26 __all__ = [ 27 27 "Index", 28 28 "IndexSchedule", 29 "IndexedSearchException", 29 30 ] 30 31 31 32 import datetime … … 83 84 which is not reserved. 84 85 """ 85 86 87 class IndexedSearchException(ValueError): 88 pass 89 90 86 91 class AbstractCalendarIndex(AbstractSQLDatabase): 87 92 """ 88 93 Calendar collection index abstract base class that defines the apis for the index. … … 246 251 statement += ")" 247 252 results = self._db_values_for_sql(statement, *names) 248 253 return results 249 250 def searchValid(self, filter):251 if isinstance(filter, caldavxml.Filter):252 qualifiers = calendarquery.sqlcalendarquery(filter)253 else:254 qualifiers = None255 256 return qualifiers is not None257 254 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): 259 263 """ 260 264 Finds resources matching the given qualifiers. 261 265 @param filter: the L{Filter} for the calendar-query to execute. … … 266 270 """ 267 271 # FIXME: Don't forget to use maximum_future_expansion_duration when we 268 272 # start caching... 269 273 270 274 # Make sure we have a proper Filter element and get the partial SQL statement to use. 271 275 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() 273 286 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]) 277 293 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 280 315 for row in rowiter: 281 316 name = row[0] 282 317 if self.resource.getChild(name.encode("utf-8")): … … 393 428 """ 394 429 ) 395 430 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): 397 452 """ 398 453 Records the given calendar resource in the index with the given name. 399 454 Resource names and UIDs must both be unique; only one resource name may … … 406 461 """ 407 462 uid = calendar.resourceUID() 408 463 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 410 474 411 instances = calendar.expandTimeRanges(expand _max)475 instances = calendar.expandTimeRanges(expand) 412 476 for key in instances: 413 477 instance = instances[key] 414 478 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 35 35 from twistedcaldav.caldavxml import caldav_namespace 36 36 from twistedcaldav.customxml import TwistedCalendarAccessProperty 37 37 from twistedcaldav.method import report_common 38 from twistedcaldav.index import IndexedSearchException 38 39 39 40 import urllib 40 41 … … 166 167 167 168 # Check for disabled access 168 169 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 174 180 if not names: 175 181 yield None 176 182 return 177 183 178 184 # Now determine which valid resources are readable and which are not 179 185 ok_resources = [] 180 186 d = calresource.findChildrenFaster( … … 189 195 x = waitForDeferred(d) 190 196 yield x 191 197 x.getResult() 192 198 193 199 for child, child_uri in ok_resources: 194 200 child_uri_name = child_uri[child_uri.rfind("/") + 1:] 195 201 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 47 47 from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap 48 48 from twistedcaldav.ical import Component, Property, iCalendarProductID 49 49 from twistedcaldav.instance import InstanceList 50 from twistedcaldav.index import IndexedSearchException 50 51 51 52 from vobject.icalendar import utc 52 53 … … 346 347 yield filteredaces 347 348 filteredaces = filteredaces.getResult() 348 349 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 350 356 351 357 # Check privileges - must have at least CalDAV:read-free-busy 352 358 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 24 24 __version__ = "0.0" 25 25 26 26 __all__ = [ 27 " calendarquery",27 "CalendarQueryExpressionGenerator", 28 28 "sqlcalendarquery", 29 29 ] 30 30 … … 42 42 #FIELD_DESCRIPTION = "RESOURCE.DESCRIPTION" 43 43 #FIELD_LOCATION = "RESOURCE.LOCATION" 44 44 45 def calendarquery(filter):45 class CalendarQueryExpressionGenerator(object): 46 46 """ 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 51 49 """ 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)]: 64 131 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 69 138 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 """ 82 157 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 136 161 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) 139 166 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] 166 187 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 189 199 200 # Utility function 190 201 def getTimerangeArguments(timerange): 191 202 """ 192 203 Get start/end and floating start/end (adjusted for timezone offset) values from the … … 209 220 210 221 return str(start), str(end), str(startfloat), str(endfloat) 211 222 223 # return SQL qualifiers and info to index. 212 224 def sqlcalendarquery(filter): 213 225 """ 214 Convert the supplied calendar-query into a oartial SQL statement.226 Convert the supplied calendar-query into a partial SQL statement. 215 227 216 @param filter: the L{Filter} for th wcalendar-query to convert.228 @param filter: the L{Filter} for the calendar-query to convert. 217 229 @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement, 218 230 and the C{list} is the list of argument substitutions to use with the SQL API execute method. 219 231 Or return C{None} if it is not possible to create an SQL query to fully match the calendar-query. 220 232 """ 221 233 try: 222 expression = calendarquery(filter) 234 expression_generator = CalendarQueryExpressionGenerator() 235 expression = expression_generator.calendarquery(filter) 223 236 sql = sqlgenerator.sqlgenerator(expression) 224 return sql.generate() 237 qualifiers = (sql.generate(),expression_generator.enddate) 238 return qualifiers 225 239 except ValueError: 240 # Indicate an unsupported filter 226 241 return None 227 242 228 243 -
twistedcaldav/static.py
diff -x '*.svn' -x '*.pyc' -ru ./twistedcaldav/static.py ../../DCS1.2/CalendarServer-1.2/twistedcaldav/static.py
old new 187 187 filteredaces = filteredaces.getResult() 188 188 189 189 # Must verify ACLs which means we need a request object at this point 190 for name, uid, type in self.index(). search(None): #@UnusedVariable190 for name, uid, type in self.index().bruteforceSearch(None): #@UnusedVariable 191 191 try: 192 192 child = waitForDeferred(request.locateChildResource(self, name)) 193 193 yield child
