1
2
3
4
5
6
7
8
9
10
11 import time
12 import logging
13 LOG = logging.getLogger('ZenUtils.MultiPathIndex')
14
15 from Globals import DTMLFile
16
17 from ExtendedPathIndex import ExtendedPathIndex
18 from Products.PluginIndexes.common import safe_callable
19 from BTrees.OOBTree import OOSet
20 from BTrees.IIBTree import IISet, intersection, union, multiunion
21
23 if not seq:
24 return False
25 return (isinstance(seq, (tuple, list)) and
26 all(isinstance(item, (tuple, list)) for item in seq))
27
29 if isinstance(seq, (tuple, list)):
30 return map(_recursivePathSplit, seq)
31 if '/' in seq:
32 return seq.split('/')
33 else:
34 return seq
35
36
38 """
39 A path index that is capable of indexing multiple paths per object.
40 """
41 meta_type = "MultiPathIndex"
42
43
44 - def search(self, path, default_level=0, depth=-1, navtree=0,
45 navtree_start=0):
46 """
47 path is either a string representing a
48 relative URL or a part of a relative URL or
49 a tuple (path,level).
50
51 level >= 0 starts searching at the given level
52 level < 0 not implemented yet
53 """
54
55 if isinstance(path, basestring):
56 startlevel = default_level
57 else:
58 startlevel = int(path[1])
59 path = path[0]
60
61 absolute_path = isinstance(path, basestring) and path.startswith('/')
62 comps = filter(None, path.split('/'))
63
64 orig_comps = [''] + comps[:]
65
66 if depth > 0:
67 raise ValueError, "Can't do depth searches anymore"
68
69 if not comps:
70 comps = ['dmd']
71 startlevel = 1
72 elif comps[0] == 'zport':
73 comps = comps[1:]
74 elif comps[0] != 'dmd':
75 raise ValueError, "Depth searches must start with 'dmd'"
76 startlevel = len(comps)
77
78
79 if len(comps) == 0:
80 if depth == -1 and not navtree:
81 return IISet(self._unindex.keys())
82
83
84
85
86 orig_depth = depth
87 if depth == -1:
88 depth = 0 or navtree
89
90
91 if absolute_path and navtree and depth == 1 and default_level==0:
92 set_list = []
93
94 if navtree_start >= len(orig_comps):
95 navtree_start = 0
96
97 for i in range(len(orig_comps), navtree_start, -1):
98 parent_path = '/'.join(orig_comps[:i])
99 parent_path = parent_path and parent_path or '/'
100 try:
101 set_list.append(self._index_parents[parent_path])
102 except KeyError:
103 pass
104 return multiunion(set_list)
105
106 elif absolute_path and navtree and depth == 0 and default_level==0:
107 item_list = IISet()
108
109 if navtree_start >= len(orig_comps):
110 navtree_start = 0
111
112 for i in range(len(orig_comps), navtree_start, -1):
113 parent_path = '/'.join(orig_comps[:i])
114 parent_path = parent_path and parent_path or '/'
115 try:
116 item_list.insert(self._index_items[parent_path])
117 except KeyError:
118 pass
119 return item_list
120
121 elif absolute_path and orig_depth == 0 and default_level == 0:
122 try:
123 return IISet([self._index_items[path]])
124 except KeyError:
125 return IISet()
126
127 elif absolute_path and orig_depth == 1 and default_level == 0:
128
129 try:
130 return self._index_parents[path]
131 except KeyError:
132 return IISet()
133
134 elif startlevel >= 0:
135
136 pathset = None
137 navset = None
138 depthset = None
139
140 if navtree and depth and \
141 self._index.has_key(None) and \
142 self._index[None].has_key(startlevel):
143 navset = self._index[None][startlevel]
144 for level in range(startlevel, startlevel+len(comps)):
145 if level <= len(comps):
146 comp = "/".join(comps[:level])
147 if (not self._index.has_key(comp)
148 or not self._index[comp].has_key(level)):
149
150
151 if navtree:
152 pathset = IISet()
153 else:
154 return IISet()
155 else:
156 return self._index[comp][level]
157 if navtree and depth and \
158 self._index.has_key(None) and \
159 self._index[None].has_key(level+depth):
160 navset = union(navset, intersection(pathset,
161 self._index[None][level+depth]))
162 if level-startlevel >= len(comps) or navtree:
163 if (self._index.has_key(None)
164 and self._index[None].has_key(level)):
165 depthset = union(depthset, intersection(pathset,
166 self._index[None][level]))
167
168 if navtree:
169 return union(depthset, navset) or IISet()
170 elif depth:
171 return depthset or IISet()
172 else:
173 return pathset or IISet()
174
175 else:
176 results = IISet()
177 for level in range(0,self._depth + 1):
178 ids = None
179 error = 0
180 for cn in range(0,len(comps)):
181 comp = comps[cn]
182 try:
183 ids = intersection(ids,self._index[comp][level+cn])
184 except KeyError:
185 error = 1
186 if error==0:
187 results = union(results,ids)
188 return results
189
190
192 """ return names of indexed attributes """
193 return (self.id, )
194
196 """ hook for (Z)Catalog """
197
198 f = getattr(obj, self.id, None)
199 if f is not None:
200 if safe_callable(f):
201 try:
202 paths = f()
203 except AttributeError:
204 return 0
205 else:
206 paths = f
207 else:
208 try:
209 paths = obj.getPhysicalPath()
210 except AttributeError:
211 return 0
212
213 if not paths: return 0
214 paths = _recursivePathSplit(paths)
215 if not _isSequenceOfSequences(paths):
216 paths = [paths]
217
218 if docid in self._unindex:
219 unin = self._unindex[docid]
220
221 if isinstance(unin, set):
222 unin = self._unindex[docid] = OOSet(unin)
223 for oldpath in list(unin):
224 if list(oldpath.split('/')) not in paths:
225 self.unindex_paths(docid, (oldpath,))
226 else:
227 self._unindex[docid] = OOSet()
228 self._length.change(1)
229
230 self.index_paths(docid, paths)
231
232 return 1
233
234
236 for path in paths:
237 if isinstance(path, (list, tuple)):
238 path = '/'+ '/'.join(path[1:])
239 comps = filter(None, path.split('/'))
240 parent_path = '/' + '/'.join(comps[:-1])
241
242 for i in range(len(comps)):
243 comp = "/".join(comps[1:i+1])
244 if comp:
245 self.insertEntry(comp, docid, i)
246
247
248 self.insertEntry(None, docid, len(comps)-1, parent_path, path)
249
250 self._unindex.setdefault(docid, OOSet()).insert(path)
251
252
254
255 if not self._unindex.has_key(docid):
256 return
257
258 def unindex(comp, level, docid=docid, parent_path=None,
259 object_path=None):
260 try:
261 self._index[comp][level].remove(docid)
262
263 if not self._index[comp][level]:
264 del self._index[comp][level]
265
266 if not self._index[comp]:
267 del self._index[comp]
268
269 if parent_path is not None:
270 self._index_parents[parent_path].remove(docid)
271 if not self._index_parents[parent_path]:
272 del self._index_parents[parent_path]
273 if object_path is not None:
274 del self._index_items[object_path]
275 except KeyError:
276
277 pass
278
279 old = set(self._unindex.get(docid, ()))
280 mkstr = lambda path:'/'.join(path) if isinstance(path, tuple) else path
281 paths = map(mkstr, paths)
282 toremove = set(paths) & old
283 tokeep = old - toremove
284 for path in toremove:
285 if not path.startswith('/'):
286 path = '/'+path
287 comps = path.split('/')
288 parent_path = '/'.join(comps[:-1])
289
290 for level in range(1, len(comps[2:])+1):
291 comp = "/".join(comps[2:level+2])
292 unindex(comp, level, docid, parent_path, path)
293
294 level = len(comps[1:])
295 comp = None
296 unindex(comp, level-1, parent_path=parent_path, object_path=path)
297
298 self._unindex[docid].remove(path)
299
300 if tokeep:
301 self.index_paths(docid, tokeep)
302 else:
303
304 self._length.change(-1)
305 del self._unindex[docid]
306
307
309 """ hook for (Z)Catalog """
310 if not self._unindex.has_key(docid):
311 return
312 self.unindex_paths(docid, self._unindex[docid])
313
314 manage = manage_main = DTMLFile('dtml/manageMultiPathIndex', globals())
315 manage_main._setName('manage_main')
316
317
318 manage_addMultiPathIndexForm = DTMLFile('dtml/addMultiPathIndex', globals())
319
322 """
323 Add a MultiPathIndex.
324 """
325 return self.manage_addIndex(id, 'MultiPathIndex', extra=None,
326 REQUEST=REQUEST, RESPONSE=RESPONSE,
327 URL1=URL3)
328