24
24
Package providing filtering framework for Gramps.
25
25
"""
26
26
27
+ import logging
28
+ import time
29
+
27
30
# ------------------------------------------------------------------------
28
31
#
29
32
# Gramps imports
40
43
from ..lib .note import Note
41
44
from ..lib .tag import Tag
42
45
from ..const import GRAMPS_LOCALE as glocale
46
+ from .rules import Rule
47
+ from .optimizer import Optimizer
43
48
44
49
_ = glocale .translation .gettext
50
+ LOG = logging .getLogger (".filter.results" )
45
51
46
52
47
53
# -------------------------------------------------------------------------
52
58
class GenericFilter :
53
59
"""Filter class that consists of several rules."""
54
60
55
- logical_functions = ["or" , " and" , "xor " , "one" ]
61
+ logical_functions = ["and" , "or " , "one" ]
56
62
57
63
def __init__ (self , source = None ):
58
64
if source :
@@ -74,10 +80,8 @@ def match(self, handle, db):
74
80
"""
75
81
Return True or False depending on whether the handle matches the filter.
76
82
"""
77
- if self .apply (db , [handle ]):
78
- return True
79
- else :
80
- return False
83
+ obj = self .get_object (handle )
84
+ return self .apply_to_one (db , obj )
81
85
82
86
def is_empty (self ):
83
87
return (len (self .flist ) == 0 ) or (
@@ -88,10 +92,7 @@ def set_logical_op(self, val):
88
92
if val in GenericFilter .logical_functions :
89
93
self .logical_op = val
90
94
else :
91
- self .logical_op = "and"
92
-
93
- def get_logical_op (self ):
94
- return self .logical_op
95
+ raise Exception ("invalid operator: %r" % val )
95
96
96
97
def set_invert (self , val ):
97
98
self .invert = bool (val )
@@ -138,99 +139,116 @@ def find_from_handle(self, db, handle):
138
139
def get_number (self , db ):
139
140
return db .get_number_of_people ()
140
141
141
- def check_func (self , db , id_list , task , user = None , tupleind = None , tree = False ):
142
+ def apply_logical_op_to_all (
143
+ self , db , id_list , apply_logical_op , user = None , tupleind = None , tree = False
144
+ ):
142
145
final_list = []
143
- if user :
144
- user .begin_progress (_ ("Filter" ), _ ("Applying ..." ), self .get_number (db ))
146
+
147
+ optimizer = Optimizer (self )
148
+ handles_in , handles_out = optimizer .get_handles ()
149
+
150
+ LOG .debug (
151
+ "Optimizer handles_in: %s" ,
152
+ len (handles_in ) if handles_in is not None else None ,
153
+ )
154
+ LOG .debug ("Optimizer handles_out: %s" , len (handles_out ))
145
155
if id_list is None :
146
- with self .get_tree_cursor (db ) if tree else self .get_cursor (db ) as cursor :
147
- for handle , data in cursor :
148
- person = db .serializer .data_to_object (data )
156
+ if handles_in is not None :
157
+ if user :
158
+ user .begin_progress (_ ("Filter" ), _ ("Applying ..." ), len (handles_in ))
159
+
160
+ # Use these rather than going through entire database
161
+ for handle in handles_in :
149
162
if user :
150
163
user .step_progress ()
151
- if task (db , person ) != self .invert :
152
- final_list .append (handle )
164
+
165
+ if handle is None :
166
+ continue
167
+
168
+ obj = self .get_object (db , handle )
169
+
170
+ if apply_logical_op (db , obj , self .flist ) != self .invert :
171
+ final_list .append (obj .handle )
172
+
173
+ else :
174
+ with (
175
+ self .get_tree_cursor (db ) if tree else self .get_cursor (db )
176
+ ) as cursor :
177
+ if user :
178
+ user .begin_progress (
179
+ _ ("Filter" ), _ ("Applying ..." ), self .get_number (db )
180
+ )
181
+
182
+ for handle , obj in cursor :
183
+ if user :
184
+ user .step_progress ()
185
+
186
+ if handle in handles_out :
187
+ continue
188
+
189
+ if apply_logical_op (db , obj , self .flist ) != self .invert :
190
+ final_list .append (handle )
191
+
153
192
else :
154
- for data in id_list :
155
- if tupleind is None :
156
- handle = data
157
- else :
158
- handle = data [tupleind ]
159
- person = self .find_from_handle (db , handle )
193
+ if user :
194
+ id_list = list (id_list )
195
+ user .begin_progress (_ ("Filter" ), _ ("Applying ..." ), len (id_list ))
196
+ for handle_data in id_list :
160
197
if user :
161
198
user .step_progress ()
162
- if task (db , person ) != self .invert :
163
- final_list .append (data )
164
- if user :
165
- user .end_progress ()
166
- return final_list
167
199
168
- def check_and (self , db , id_list , user = None , tupleind = None , tree = False ):
169
- final_list = []
170
- flist = self .flist
171
- if user :
172
- user .begin_progress (_ ("Filter" ), _ ("Applying ..." ), self .get_number (db ))
173
- if id_list is None :
174
- with self .get_tree_cursor (db ) if tree else self .get_cursor (db ) as cursor :
175
- for handle , data in cursor :
176
- person = db .serializer .data_to_object (data )
177
- if user :
178
- user .step_progress ()
179
- val = all (rule .apply (db , person ) for rule in flist )
180
- if val != self .invert :
181
- final_list .append (handle )
182
- else :
183
- for data in id_list :
184
200
if tupleind is None :
185
- handle = data
201
+ handle = handle_data
186
202
else :
187
- handle = data [tupleind ]
188
- person = self .find_from_handle (db , handle )
189
- if user :
190
- user .step_progress ()
191
- val = all (rule .apply (db , person ) for rule in flist if person )
192
- if val != self .invert :
193
- final_list .append (data )
194
- if user :
195
- user .end_progress ()
196
- return final_list
203
+ handle = handle_data [tupleind ]
197
204
198
- def check_or (self , db , id_list , user = None , tupleind = None , tree = False ):
199
- return self .check_func (db , id_list , self .or_test , user , tupleind , tree = False )
205
+ if handles_in is not None :
206
+ if handle not in handles_in :
207
+ continue
208
+ elif handle in handles_out :
209
+ continue
200
210
201
- def check_one (self , db , id_list , user = None , tupleind = None , tree = False ):
202
- return self .check_func (db , id_list , self .one_test , user , tupleind , tree = False )
211
+ obj = self .get_object (db , handle )
203
212
204
- def check_xor ( self , db , id_list , user = None , tupleind = None , tree = False ) :
205
- return self . check_func ( db , id_list , self . xor_test , user , tupleind , tree = False )
213
+ if apply_logical_op ( db , obj , self . flist ) != self . invert :
214
+ final_list . append ( handle_data )
206
215
207
- def xor_test (self , db , person ):
208
- test = False
209
- for rule in self .flist :
210
- test = test ^ rule .apply (db , person )
211
- return test
216
+ if user :
217
+ user .end_progress ()
218
+
219
+ return final_list
212
220
213
- def one_test (self , db , person ):
221
+ def and_test (self , db , data : dict , flist ):
222
+ return all (rule .apply_to_one (db , data ) for rule in flist )
223
+
224
+ def one_test (self , db , data : dict , flist ):
214
225
found_one = False
215
- for rule in self . flist :
216
- if rule .apply (db , person ):
226
+ for rule in flist :
227
+ if rule .apply_to_one (db , data ):
217
228
if found_one :
218
229
return False # There can be only one!
219
230
found_one = True
220
231
return found_one
221
232
222
- def or_test (self , db , person ):
223
- return any (rule .apply (db , person ) for rule in self . flist )
233
+ def or_test (self , db , data : dict , flist ):
234
+ return any (rule .apply_to_one (db , data ) for rule in flist )
224
235
225
- def get_check_func (self ):
226
- try :
227
- m = getattr (self , "check_" + self .logical_op )
228
- except AttributeError :
229
- m = self .check_and
230
- return m
236
+ def get_logical_op (self ):
237
+ return self .logical_op
231
238
232
- def check (self , db , handle ):
233
- return self .get_check_func ()(db , [handle ])
239
+ def apply_to_one (self , db , data : dict ) -> bool :
240
+ """
241
+ Filter-level apply rules to single data item.
242
+ """
243
+ if self .logical_op == "and" :
244
+ res = self .and_test (db , data , self .flist )
245
+ elif self .logical_op == "or" :
246
+ res = self .or_test (db , data , self .flist )
247
+ elif self .logical_op == "one" :
248
+ res = self .one_test (db , data , self .flist )
249
+ else :
250
+ raise Exception ("invalid operator: %r" % self .logical_op )
251
+ return res != self .invert
234
252
235
253
def apply (self , db , id_list = None , tupleind = None , user = None , tree = False ):
236
254
"""
@@ -249,14 +267,44 @@ def apply(self, db, id_list=None, tupleind=None, user=None, tree=False):
249
267
if id_list not given, all items in the database that
250
268
match the filter are returned as a list of handles
251
269
"""
252
- m = self .get_check_func ()
270
+ if user :
271
+ user .begin_progress (_ ("Filter" ), _ ("Preparing ..." ), len (self .flist ) + 1 )
272
+ # FIXME: this dialog doesn't show often. Adding a time.sleep(0.1) here
273
+ # can help on my machine
274
+
275
+ start_time = time .time ()
253
276
for rule in self .flist :
277
+ if user :
278
+ user .step_progress ()
254
279
rule .requestprepare (db , user )
255
- res = m (db , id_list , user , tupleind , tree )
280
+ LOG .debug ("Prepare time: %s seconds" , time .time () - start_time )
281
+
282
+ if user :
283
+ user .end_progress ()
284
+
285
+ if self .logical_op == "and" :
286
+ apply_logical_op = self .and_test
287
+ elif self .logical_op == "or" :
288
+ apply_logical_op = self .or_test
289
+ elif self .logical_op == "one" :
290
+ apply_logical_op = self .one_test
291
+ else :
292
+ raise Exception ("invalid operator: %r" % self .logical_op )
293
+
294
+ start_time = time .time ()
295
+ res = self .apply_logical_op_to_all (
296
+ db , id_list , apply_logical_op , user , tupleind , tree
297
+ )
298
+ LOG .debug ("Apply time: %s seconds" , time .time () - start_time )
299
+
256
300
for rule in self .flist :
257
301
rule .requestreset ()
302
+
258
303
return res
259
304
305
+ def get_object (self , db , handle ):
306
+ return db .get_person_from_handle (handle )
307
+
260
308
261
309
class GenericFamilyFilter (GenericFilter ):
262
310
def __init__ (self , source = None ):
@@ -274,6 +322,9 @@ def find_from_handle(self, db, handle):
274
322
def get_number (self , db ):
275
323
return db .get_number_of_families ()
276
324
325
+ def get_object (self , db , handle ):
326
+ return db .get_family_from_handle (handle )
327
+
277
328
278
329
class GenericEventFilter (GenericFilter ):
279
330
def __init__ (self , source = None ):
@@ -291,6 +342,9 @@ def find_from_handle(self, db, handle):
291
342
def get_number (self , db ):
292
343
return db .get_number_of_events ()
293
344
345
+ def get_object (self , db , handle ):
346
+ return db .get_event_from_handle (handle )
347
+
294
348
295
349
class GenericSourceFilter (GenericFilter ):
296
350
def __init__ (self , source = None ):
@@ -308,6 +362,9 @@ def find_from_handle(self, db, handle):
308
362
def get_number (self , db ):
309
363
return db .get_number_of_sources ()
310
364
365
+ def get_object (self , db , handle ):
366
+ return db .get_source_from_handle (handle )
367
+
311
368
312
369
class GenericCitationFilter (GenericFilter ):
313
370
def __init__ (self , source = None ):
@@ -328,6 +385,9 @@ def find_from_handle(self, db, handle):
328
385
def get_number (self , db ):
329
386
return db .get_number_of_citations ()
330
387
388
+ def get_object (self , db , handle ):
389
+ return db .get_citation_from_handle (handle )
390
+
331
391
332
392
class GenericPlaceFilter (GenericFilter ):
333
393
def __init__ (self , source = None ):
@@ -348,6 +408,9 @@ def find_from_handle(self, db, handle):
348
408
def get_number (self , db ):
349
409
return db .get_number_of_places ()
350
410
411
+ def get_object (self , db , handle ):
412
+ return db .get_place_from_handle (handle )
413
+
351
414
352
415
class GenericMediaFilter (GenericFilter ):
353
416
def __init__ (self , source = None ):
@@ -365,6 +428,9 @@ def find_from_handle(self, db, handle):
365
428
def get_number (self , db ):
366
429
return db .get_number_of_media ()
367
430
431
+ def get_object (self , db , handle ):
432
+ return db .get_media_from_handle (handle )
433
+
368
434
369
435
class GenericRepoFilter (GenericFilter ):
370
436
def __init__ (self , source = None ):
@@ -382,6 +448,9 @@ def find_from_handle(self, db, handle):
382
448
def get_number (self , db ):
383
449
return db .get_number_of_repositories ()
384
450
451
+ def get_object (self , db , handle ):
452
+ return db .get_repository_from_handle (handle )
453
+
385
454
386
455
class GenericNoteFilter (GenericFilter ):
387
456
def __init__ (self , source = None ):
@@ -399,6 +468,9 @@ def find_from_handle(self, db, handle):
399
468
def get_number (self , db ):
400
469
return db .get_number_of_notes ()
401
470
471
+ def get_object (self , db , handle ):
472
+ return db .get_note_from_handle (handle )
473
+
402
474
403
475
def GenericFilterFactory (namespace ):
404
476
if namespace == "Person" :
0 commit comments