Code Repositories xandikos / 64bb135
Initial index work. Jelmer Vernooń≥ 2 years ago
3 changed file(s) with 150 addition(s) and 4 deletion(s). Raw diff Collapse all Expand all
0 Common filters:
1 ===============
2
3 <ns0:filter xmlns:ns0="urn:ietf:params:xml:ns:caldav"><ns0:comp-filter name="VCALENDAR"><ns0:comp-filter name="VEVENT"><ns0:prop-filter name="UID"><ns0:text-match collation="i;octet">7kukuqrfedlm2f9te062h01gedq36j0acgtcp5urq93605obov0j98d7uce73ekjd75g</ns0:text-match></ns0:prop-filter></ns0:comp-filter></ns0:comp-filter></ns0:filter>
4 <ns0:filter xmlns:ns0="urn:ietf:params:xml:ns:caldav"><ns0:comp-filter name="VCALENDAR"><ns0:comp-filter name="VEVENT"><ns0:time-range end="20170615T000000Z" start="20161025T000000Z" /></ns0:comp-filter></ns0:comp-filter></ns0:filter>'
5 <ns0:filter xmlns:ns0="urn:ietf:params:xml:ns:caldav"><ns0:comp-filter name="VCALENDAR"><ns0:comp-filter name="VTODO" /></ns0:comp-filter></ns0:filter>
6
7 Ideally, we'd like some way of *excluding* entries that don't match the filter early, without having to parse the file.
8
9 Indexes
10 =======
11
12 One way would be to keep indexes:
13
14 comp=VCALENDAR/comp=VEVENT
15 comp=VCALENDAR/comp=VTODO
16 comp=VCALENDAR/comp=VEVENT/prop=UID
17 comp=VCALENDAR/comp=VTODO/prop=UID
18 comp=VCALENDAR/comp=VJOURNAL/prop=UID
19 comp=VCALENDAR/comp=VEVENT/prop=DTSTART
20 comp=VCALENDAR/comp=VEVENT/prop=DTEND
21 comp=VCALENDAR/comp=VEVENT/prop=DURATION
22
23 These index should be sufficient to answer the queries listed above.
24
25 A stub of icalendar.cal.Calendar can be implemented on top of this, which uses
26 this index. If an item is requested that is not indexed, it raises an Exception
27 and the calling code can fall back to just scanning.
28
29 Ideally it should also log the filter that it wasn't able to apply through the
30 index.
31
32 Index values are boolean (presence), string or timestamp.
33
34 It should still be possible to modify the git repository externally. If this is done, the
35 index should automatically be updated.
36
37 Implementation
38 ==============
39
40 Use a TDB database (on disk or even in RAM). Store:
41
42 PRESENT/blobid -> ''
43 KEY/blobid/<key> -> <value>
111111 """
112112 raise NotImplementedError(self.get_max_attendees_per_instance)
113113
114 def check_filter(self, resource, check_filter):
115 """Check if a filter applies to a resource.
116
117 :param resource; Resource to check
118 :param check_filter: Function to check a calendar.
119 :return: boolean indicating whether resource matches
120 """
121 c = calendar_from_resource(resource)
122 if c is None:
123 return False
124 return check_filter(c)
125
114126
115127 class PrincipalExtensions:
116128 """CalDAV-specific extensions to DAVPrincipal."""
482494 if el is None:
483495 # Empty filter, let's not bother parsing
484496 return lambda x: True
485 c = calendar_from_resource(resource)
486 if c is None:
487 return False
488497 return apply_comp_filter(list(el)[0], c, tzify)
489498
490499
536545 tzify = lambda dt: as_tz_aware_ts(dt, tz)
537546 for (href, resource) in webdav.traverse_resource(
538547 base_resource, base_href, depth):
539 if not apply_filter(filter_el, resource, tzify):
548 if not base_resource.check_filter(resource, lambda cal: apply_filter(filter_el, cal, tzify)):
540549 continue
541550 propstat = davcommon.get_properties_with_data(
542551 self.data_property, href, resource, properties, environ,
0 # encoding: utf-8
1 #
2 # Xandikos
3 # Copyright (C) 2016 Jelmer Vernooń≥ <jelmer@jelmer.uk>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your option) any later version of
9 # the License.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
21 """Calendar Index.
22
23 See notes/filters.txt
24 """
25
26 import tdb
27
28 KEYS = [
29 'comp=VCALENDAR/comp=VEVENT',
30 'comp=VCALENDAR/comp=VTODO',
31 'comp=VCALENDAR/comp=VEVENT/prop=UID',
32 'comp=VCALENDAR/comp=VTODO/prop=UID',
33 'comp=VCALENDAR/comp=VJOURNAL/prop=UID',
34 'comp=VCALENDAR/comp=VEVENT/prop=DTSTART',
35 'comp=VCALENDAR/comp=VEVENT/prop=DTEND',
36 'comp=VCALENDAR/comp=VEVENT/prop=DURATION',
37 ]
38
39
40 def get_index_entry(comp, key):
41 (first, sep, rest) = key.partition('/')
42 if first.startswith('comp='):
43 if comp.name != first[5:]:
44 return
45 if not rest:
46 yield None
47 elif rest.startswith('comp='):
48 for subcomp in comp.subcomponents:
49 for value in get_index_entry(subcomp, rest):
50 yield value
51 elif rest.startswith('prop='):
52 for value in get_index_entry(comp, rest):
53 yield value
54 elif first.startswith('prop='):
55 try:
56 yield comp[first[5:]]
57 except KeyError:
58 pass
59 else:
60 raise AssertionError('invalid key name %s' % key)
61
62
63 def get_index_entries(calendar, keys):
64 for k in KEYS:
65 values = list(get_index_entry(calendar, k))
66 if values:
67 yield (k, values)
68
69
70 class CalendarIndex(object):
71
72 def insert_entry(self, etag, calendar):
73 self.db.transaction_start()
74 try:
75 for (k, vs) in get_index_entries(calendar, KEYS):
76 self.db[etag + '/' + k] = '\n'.join(vs)
77 self.db['PRESENT/' + etag] = ''
78 finally:
79 self.db.transaction_commit()
80
81 def get_indexed_calendar(self, etag):
82 raise NotImplementedError(self.get_indexed_calendar)
83
84
85 if __name__ == '__main__':
86 from icalendar.cal import Calendar
87 import sys
88 for arg in sys.argv[1:]:
89 with open(arg, 'rb') as f:
90 cal = Calendar.from_ical(f.read())
91 for (k, vs) in get_index_entries(cal, KEYS):
92 print("%s -> %r" % (k, vs))