1 /*
2  * Copyright (C) 2011, Nokia <ivan.frade@nokia.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA  02110-1301, USA.
18  *
19  * Author: Carlos Garnacho  <carlos@lanedo.com>
20  */
21 
22 #include <libtracker-common/tracker-file-utils.h>
23 #include "tracker-indexing-tree.h"
24 
25 /**
26  * SECTION:tracker-indexing-tree
27  * @short_description: Indexing tree handling
28  *
29  * #TrackerIndexingTree handles the tree of directories configured to be indexed
30  * by the #TrackerMinerFS.
31  **/
32 
33 typedef struct _TrackerIndexingTreePrivate TrackerIndexingTreePrivate;
34 typedef struct _NodeData NodeData;
35 typedef struct _PatternData PatternData;
36 typedef struct _FindNodeData FindNodeData;
37 
38 struct _NodeData
39 {
40 	GFile *file;
41 	guint flags;
42 	guint shallow : 1;
43 	guint removing : 1;
44 };
45 
46 struct _PatternData
47 {
48 	GPatternSpec *pattern;
49 	TrackerFilterType type;
50 	GFile *file; /* Only filled in in absolute paths */
51 };
52 
53 struct _FindNodeData
54 {
55 	GEqualFunc func;
56 	GNode *node;
57 	GFile *file;
58 };
59 
60 struct _TrackerIndexingTreePrivate
61 {
62 	GNode *config_tree;
63 	GList *filter_patterns;
64 	TrackerFilterPolicy policies[TRACKER_FILTER_PARENT_DIRECTORY + 1];
65 
66 	GFile *root;
67 	guint filter_hidden : 1;
68 };
69 
70 G_DEFINE_TYPE_WITH_PRIVATE (TrackerIndexingTree, tracker_indexing_tree, G_TYPE_OBJECT)
71 
72 enum {
73 	PROP_0,
74 	PROP_ROOT,
75 	PROP_FILTER_HIDDEN
76 };
77 
78 enum {
79 	DIRECTORY_ADDED,
80 	DIRECTORY_REMOVED,
81 	DIRECTORY_UPDATED,
82 	CHILD_UPDATED,
83 	LAST_SIGNAL
84 };
85 
86 static guint signals[LAST_SIGNAL] = { 0 };
87 
88 static NodeData *
node_data_new(GFile * file,guint flags)89 node_data_new (GFile *file,
90                guint  flags)
91 {
92 	NodeData *data;
93 
94 	data = g_slice_new0 (NodeData);
95 	data->file = g_object_ref (file);
96 	data->flags = flags;
97 
98 	return data;
99 }
100 
101 static void
node_data_free(NodeData * data)102 node_data_free (NodeData *data)
103 {
104 	g_object_unref (data->file);
105 	g_slice_free (NodeData, data);
106 }
107 
108 static gboolean
node_free(GNode * node,gpointer user_data)109 node_free (GNode    *node,
110            gpointer  user_data)
111 {
112 	node_data_free (node->data);
113 	return FALSE;
114 }
115 
116 static PatternData *
pattern_data_new(const gchar * glob_string,guint type)117 pattern_data_new (const gchar *glob_string,
118                   guint        type)
119 {
120 	PatternData *data;
121 
122 	data = g_slice_new0 (PatternData);
123 	data->pattern = g_pattern_spec_new (glob_string);
124 	data->type = type;
125 
126 	if (g_path_is_absolute (glob_string)) {
127 		data->file = g_file_new_for_path (glob_string);
128 	}
129 
130 	return data;
131 }
132 
133 static void
pattern_data_free(PatternData * data)134 pattern_data_free (PatternData *data)
135 {
136 	if (data->file) {
137 		g_object_unref (data->file);
138 	}
139 
140 	g_pattern_spec_free (data->pattern);
141 	g_slice_free (PatternData, data);
142 }
143 
144 static void
tracker_indexing_tree_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)145 tracker_indexing_tree_get_property (GObject    *object,
146                                     guint       prop_id,
147                                     GValue     *value,
148                                     GParamSpec *pspec)
149 {
150 	TrackerIndexingTreePrivate *priv;
151 
152 	priv = TRACKER_INDEXING_TREE (object)->priv;
153 
154 	switch (prop_id) {
155 	case PROP_ROOT:
156 		g_value_set_object (value, priv->root);
157 		break;
158 	case PROP_FILTER_HIDDEN:
159 		g_value_set_boolean (value, priv->filter_hidden);
160 		break;
161 	default:
162 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
163 		break;
164 	}
165 }
166 
167 static void
tracker_indexing_tree_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)168 tracker_indexing_tree_set_property (GObject      *object,
169                                     guint         prop_id,
170                                     const GValue *value,
171                                     GParamSpec   *pspec)
172 {
173 	TrackerIndexingTree *tree;
174 	TrackerIndexingTreePrivate *priv;
175 
176 	tree = TRACKER_INDEXING_TREE (object);
177 	priv = tree->priv;
178 
179 	switch (prop_id) {
180 	case PROP_ROOT:
181 		priv->root = g_value_dup_object (value);
182 		break;
183 	case PROP_FILTER_HIDDEN:
184 		tracker_indexing_tree_set_filter_hidden (tree,
185 		                                         g_value_get_boolean (value));
186 		break;
187 	default:
188 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
189 		break;
190 	}
191 }
192 
193 static void
tracker_indexing_tree_constructed(GObject * object)194 tracker_indexing_tree_constructed (GObject *object)
195 {
196 	TrackerIndexingTree *tree;
197 	TrackerIndexingTreePrivate *priv;
198 	NodeData *data;
199 
200 	G_OBJECT_CLASS (tracker_indexing_tree_parent_class)->constructed (object);
201 
202 	tree = TRACKER_INDEXING_TREE (object);
203 	priv = tree->priv;
204 
205 	/* Add a shallow root node */
206 	if (priv->root == NULL) {
207 		priv->root = g_file_new_for_uri ("file:///");
208 	}
209 
210 	data = node_data_new (priv->root, 0);
211 	data->shallow = TRUE;
212 
213 	priv->config_tree = g_node_new (data);
214 }
215 
216 static void
tracker_indexing_tree_finalize(GObject * object)217 tracker_indexing_tree_finalize (GObject *object)
218 {
219 	TrackerIndexingTreePrivate *priv;
220 	TrackerIndexingTree *tree;
221 
222 	tree = TRACKER_INDEXING_TREE (object);
223 	priv = tree->priv;
224 
225 	g_list_foreach (priv->filter_patterns, (GFunc) pattern_data_free, NULL);
226 	g_list_free (priv->filter_patterns);
227 
228 	g_node_traverse (priv->config_tree,
229 	                 G_POST_ORDER,
230 	                 G_TRAVERSE_ALL,
231 	                 -1,
232 	                 (GNodeTraverseFunc) node_free,
233 	                 NULL);
234 	g_node_destroy (priv->config_tree);
235 
236 	if (priv->root) {
237 		g_object_unref (priv->root);
238 	}
239 
240 	G_OBJECT_CLASS (tracker_indexing_tree_parent_class)->finalize (object);
241 }
242 
243 static void
tracker_indexing_tree_class_init(TrackerIndexingTreeClass * klass)244 tracker_indexing_tree_class_init (TrackerIndexingTreeClass *klass)
245 {
246 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
247 
248 	object_class->finalize = tracker_indexing_tree_finalize;
249 	object_class->constructed = tracker_indexing_tree_constructed;
250 	object_class->set_property = tracker_indexing_tree_set_property;
251 	object_class->get_property = tracker_indexing_tree_get_property;
252 
253 	g_object_class_install_property (object_class,
254 	                                 PROP_ROOT,
255 	                                 g_param_spec_object ("root",
256 	                                                      "Root URL",
257 	                                                      "The root GFile for the indexing tree",
258 	                                                      G_TYPE_FILE,
259 	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
260 
261 	g_object_class_install_property (object_class,
262 	                                 PROP_FILTER_HIDDEN,
263 	                                 g_param_spec_boolean ("filter-hidden",
264 	                                                       "Filter hidden",
265 	                                                       "Whether hidden resources are filtered",
266 	                                                       FALSE,
267 	                                                       G_PARAM_READWRITE));
268 	/**
269 	 * TrackerIndexingTree::directory-added:
270 	 * @indexing_tree: a #TrackerIndexingTree
271 	 * @directory: a #GFile
272 	 *
273 	 * the ::directory-added signal is emitted when a new
274 	 * directory is added to the list of other directories which
275 	 * are to be considered for indexing. Typically this is
276 	 * signalled when the tracker_indexing_tree_add() API is
277 	 * called.
278 	 *
279 	 * Since: 0.14
280 	 **/
281 	signals[DIRECTORY_ADDED] =
282 		g_signal_new ("directory-added",
283 		              G_OBJECT_CLASS_TYPE (object_class),
284 		              G_SIGNAL_RUN_LAST,
285 		              G_STRUCT_OFFSET (TrackerIndexingTreeClass,
286 		                               directory_added),
287 		              NULL, NULL,
288 		              NULL,
289 		              G_TYPE_NONE, 1, G_TYPE_FILE);
290 
291 	/**
292 	 * TrackerIndexingTree::directory-removed:
293 	 * @indexing_tree: a #TrackerIndexingTree
294 	 * @directory: a #GFile
295 	 *
296 	 * the ::directory-removed signal is emitted when a
297 	 * directory is removed from the list of other directories
298 	 * which are to be considered for indexing. Typically this is
299 	 * signalled when the tracker_indexing_tree_remove() API is
300 	 * called.
301 	 *
302 	 * Since: 0.14
303 	 **/
304 	signals[DIRECTORY_REMOVED] =
305 		g_signal_new ("directory-removed",
306 		              G_OBJECT_CLASS_TYPE (object_class),
307 		              G_SIGNAL_RUN_LAST,
308 		              G_STRUCT_OFFSET (TrackerIndexingTreeClass,
309 		                               directory_removed),
310 		              NULL, NULL,
311 		              NULL,
312 		              G_TYPE_NONE, 1, G_TYPE_FILE);
313 
314 	/**
315 	 * TrackerIndexingTree::directory-updated:
316 	 * @indexing_tree: a #TrackerIndexingTree
317 	 * @directory: a #GFile
318 	 *
319 	 * The ::directory-updated signal is emitted on a root
320 	 * when either its indexing flags change (e.g. due to consecutive
321 	 * calls to tracker_indexing_tree_add()), or anytime an update is
322 	 * requested through tracker_indexing_tree_notify_update().
323 	 *
324 	 * Since: 0.14
325 	 **/
326 	signals[DIRECTORY_UPDATED] =
327 		g_signal_new ("directory-updated",
328 		              G_OBJECT_CLASS_TYPE (object_class),
329 		              G_SIGNAL_RUN_LAST,
330 		              G_STRUCT_OFFSET (TrackerIndexingTreeClass,
331 		                               directory_updated),
332 		              NULL, NULL,
333 		              NULL,
334 		              G_TYPE_NONE, 1, G_TYPE_FILE);
335 
336 	/**
337 	 * TrackerIndexingTree::child-updated:
338 	 * @indexing_tree: a #TrackerIndexingTree
339 	 * @root: the root of this child
340 	 * @child: the updated child
341 	 *
342 	 * The ::child-updated signal may be emitted to notify
343 	 * about possible changes on children of a root.
344 	 *
345 	 * #TrackerIndexingTree does not emit those by itself,
346 	 * those may be triggered through tracker_indexing_tree_notify_update().
347 	 *
348 	 * Since: 1.10
349 	 **/
350 	signals[CHILD_UPDATED] =
351 		g_signal_new ("child-updated",
352 		              G_OBJECT_CLASS_TYPE (object_class),
353 		              G_SIGNAL_RUN_LAST,
354 		              G_STRUCT_OFFSET (TrackerIndexingTreeClass,
355 		                               child_updated),
356 		              NULL, NULL,
357 		              NULL,
358 		              G_TYPE_NONE, 2, G_TYPE_FILE, G_TYPE_FILE);
359 }
360 
361 static void
tracker_indexing_tree_init(TrackerIndexingTree * tree)362 tracker_indexing_tree_init (TrackerIndexingTree *tree)
363 {
364 	TrackerIndexingTreePrivate *priv;
365 	gint i;
366 
367 	priv = tree->priv = tracker_indexing_tree_get_instance_private (tree);
368 
369 	for (i = TRACKER_FILTER_FILE; i <= TRACKER_FILTER_PARENT_DIRECTORY; i++) {
370 		priv->policies[i] = TRACKER_FILTER_POLICY_ACCEPT;
371 	}
372 }
373 
374 /**
375  * tracker_indexing_tree_new:
376  *
377  * Returns a newly created #TrackerIndexingTree
378  *
379  * Returns: a newly allocated #TrackerIndexingTree
380  *
381  * Since: 0.14
382  **/
383 TrackerIndexingTree *
tracker_indexing_tree_new(void)384 tracker_indexing_tree_new (void)
385 {
386 	return g_object_new (TRACKER_TYPE_INDEXING_TREE, NULL);
387 }
388 
389 /**
390  * tracker_indexing_tree_new_with_root:
391  * @root: The top level URL
392  *
393  * If @root is %NULL, the default value is 'file:///'. Using %NULL
394  * here is the equivalent to calling tracker_indexing_tree_new() which
395  * takes no @root argument.
396  *
397  * Returns: a newly allocated #TrackerIndexingTree
398  *
399  * Since: 1.2.2
400  **/
401 TrackerIndexingTree *
tracker_indexing_tree_new_with_root(GFile * root)402 tracker_indexing_tree_new_with_root (GFile *root)
403 {
404 	return g_object_new (TRACKER_TYPE_INDEXING_TREE,
405 	                     "root", root,
406 	                     NULL);
407 }
408 
409 #ifdef PRINT_INDEXING_TREE
410 static gboolean
print_node_foreach(GNode * node,gpointer user_data)411 print_node_foreach (GNode    *node,
412                     gpointer  user_data)
413 {
414 	NodeData *node_data = node->data;
415 	gchar *uri;
416 
417 	uri = g_file_get_uri (node_data->file);
418 	g_debug ("%*s %s", g_node_depth (node), "-", uri);
419 	g_free (uri);
420 
421 	return FALSE;
422 }
423 
424 static void
print_tree(GNode * node)425 print_tree (GNode *node)
426 {
427 	g_debug ("Printing modified tree...");
428 	g_node_traverse (node,
429 	                 G_PRE_ORDER,
430 	                 G_TRAVERSE_ALL,
431 	                 -1,
432 	                 print_node_foreach,
433 	                 NULL);
434 }
435 
436 #endif /* PRINT_INDEXING_TREE */
437 
438 static gboolean
find_node_foreach(GNode * node,gpointer user_data)439 find_node_foreach (GNode    *node,
440                    gpointer  user_data)
441 {
442 	FindNodeData *data = user_data;
443 	NodeData *node_data = node->data;
444 
445 	if ((data->func) (data->file, node_data->file)) {
446 		data->node = node;
447 		return TRUE;
448 	}
449 
450 	return FALSE;
451 }
452 
453 static GNode *
find_directory_node(GNode * node,GFile * file,GEqualFunc func)454 find_directory_node (GNode      *node,
455                      GFile      *file,
456                      GEqualFunc  func)
457 {
458 	FindNodeData data;
459 
460 	data.file = file;
461 	data.node = NULL;
462 	data.func = func;
463 
464 	g_node_traverse (node,
465 	                 G_POST_ORDER,
466 	                 G_TRAVERSE_ALL,
467 	                 -1,
468 	                 find_node_foreach,
469 	                 &data);
470 
471 	return data.node;
472 }
473 
474 static void
check_reparent_node(GNode * node,gpointer user_data)475 check_reparent_node (GNode    *node,
476                      gpointer  user_data)
477 {
478 	GNode *new_node = user_data;
479 	NodeData *new_node_data, *node_data;
480 
481 	new_node_data = new_node->data;
482 	node_data = node->data;
483 
484 	if (g_file_has_prefix (node_data->file,
485 	                       new_node_data->file)) {
486 		g_node_unlink (node);
487 		g_node_append (new_node, node);
488 	}
489 }
490 
491 /**
492  * tracker_indexing_tree_add:
493  * @tree: a #TrackerIndexingTree
494  * @directory: #GFile pointing to a directory
495  * @flags: Configuration flags for the directory
496  *
497  * Adds a directory to the indexing tree with the
498  * given configuration flags.
499  **/
500 void
tracker_indexing_tree_add(TrackerIndexingTree * tree,GFile * directory,TrackerDirectoryFlags flags)501 tracker_indexing_tree_add (TrackerIndexingTree   *tree,
502                            GFile                 *directory,
503                            TrackerDirectoryFlags  flags)
504 {
505 	TrackerIndexingTreePrivate *priv;
506 	GNode *parent, *node;
507 	NodeData *data;
508 
509 	g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
510 	g_return_if_fail (G_IS_FILE (directory));
511 
512 	priv = tree->priv;
513 	node = find_directory_node (priv->config_tree, directory,
514 	                            (GEqualFunc) g_file_equal);
515 
516 	if (node) {
517 		/* Node already existed */
518 		data = node->data;
519 		data->shallow = FALSE;
520 
521 		/* Overwrite flags if they are different */
522 		if (data->flags != flags) {
523 			gchar *uri;
524 
525 			uri = g_file_get_uri (directory);
526 			g_message ("Overwriting flags for directory '%s'", uri);
527 			g_free (uri);
528 
529 			data->flags = flags;
530 			g_signal_emit (tree, signals[DIRECTORY_UPDATED], 0,
531 			               data->file);
532 		}
533 		return;
534 	}
535 
536 	/* Find out the parent */
537 	parent = find_directory_node (priv->config_tree, directory,
538 	                              (GEqualFunc) g_file_has_prefix);
539 
540 	/* Create node, move children of parent that
541 	 * could be children of this new node now.
542 	 */
543 	data = node_data_new (directory, flags);
544 	node = g_node_new (data);
545 
546 	g_node_children_foreach (parent, G_TRAVERSE_ALL,
547 	                         check_reparent_node, node);
548 
549 	/* Add the new node underneath the parent */
550 	g_node_append (parent, node);
551 
552 	g_signal_emit (tree, signals[DIRECTORY_ADDED], 0, directory);
553 
554 #ifdef PRINT_INDEXING_TREE
555 	/* Print tree */
556 	print_tree (priv->config_tree);
557 #endif /* PRINT_INDEXING_TREE */
558 }
559 
560 /**
561  * tracker_indexing_tree_remove:
562  * @tree: a #TrackerIndexingTree
563  * @directory: #GFile pointing to a directory
564  *
565  * Removes @directory from the indexing tree, note that
566  * only directories previously added with tracker_indexing_tree_add()
567  * can be effectively removed.
568  **/
569 void
tracker_indexing_tree_remove(TrackerIndexingTree * tree,GFile * directory)570 tracker_indexing_tree_remove (TrackerIndexingTree *tree,
571                               GFile               *directory)
572 {
573 	TrackerIndexingTreePrivate *priv;
574 	GNode *node, *parent;
575 	NodeData *data;
576 
577 	g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
578 	g_return_if_fail (G_IS_FILE (directory));
579 
580 	priv = tree->priv;
581 	node = find_directory_node (priv->config_tree, directory,
582 	                            (GEqualFunc) g_file_equal);
583 	if (!node) {
584 		return;
585 	}
586 
587 	data = node->data;
588 
589 	if (data->removing) {
590 		return;
591 	}
592 
593 	data->removing = TRUE;
594 
595 	if (!node->parent) {
596 		/* Node is the config tree
597 		 * root, mark as shallow again
598 		 */
599 		data->shallow = TRUE;
600 		return;
601 	}
602 
603 	g_signal_emit (tree, signals[DIRECTORY_REMOVED], 0, data->file);
604 
605 	parent = node->parent;
606 	g_node_unlink (node);
607 
608 	/* Move children to parent */
609 	g_node_children_foreach (node, G_TRAVERSE_ALL,
610 	                         check_reparent_node, parent);
611 
612 	node_data_free (node->data);
613 	g_node_destroy (node);
614 }
615 
616 /**
617  * tracker_indexing_tree_notify_update:
618  * @tree: a #TrackerIndexingTree
619  * @file: a #GFile
620  * @recursive: Whether contained indexing roots are affected by the update
621  *
622  * Signals either #TrackerIndexingTree::directory-updated or
623  * #TrackerIndexingTree::child-updated on the given file and
624  * returns #TRUE. If @file is not indexed according to the
625  * #TrackerIndexingTree, #FALSE is returned.
626  *
627  * If @recursive is #TRUE, #TrackerIndexingTree::directory-updated
628  * will be emitted on the indexing roots that are contained in @file.
629  *
630  * Returns: #TRUE if a signal is emitted.
631  *
632  * Since: 1.10
633  **/
634 gboolean
tracker_indexing_tree_notify_update(TrackerIndexingTree * tree,GFile * file,gboolean recursive)635 tracker_indexing_tree_notify_update (TrackerIndexingTree *tree,
636                                      GFile               *file,
637                                      gboolean             recursive)
638 {
639 	TrackerDirectoryFlags flags;
640 	gboolean emitted = FALSE;
641 	GFile *root;
642 
643 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
644 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
645 
646 	root = tracker_indexing_tree_get_root (tree, file, &flags);
647 
648 	if (tracker_indexing_tree_file_is_root (tree, file)) {
649 		g_signal_emit (tree, signals[DIRECTORY_UPDATED], 0, root);
650 		emitted = TRUE;
651 	} else if (root &&
652 	           ((flags & TRACKER_DIRECTORY_FLAG_RECURSE) ||
653 	            g_file_has_parent (file, root))) {
654 		g_signal_emit (tree, signals[CHILD_UPDATED], 0, root, file);
655 		emitted = TRUE;
656 	}
657 
658 	if (recursive) {
659 		GList *roots, *l;
660 
661 		roots = tracker_indexing_tree_list_roots (tree);
662 
663 		for (l = roots; l; l = l->next) {
664 			if (!g_file_has_prefix (l->data, file))
665 				continue;
666 
667 			g_signal_emit (tree, signals[DIRECTORY_UPDATED], 0, l->data);
668 			emitted = TRUE;
669 		}
670 
671 		g_list_free (roots);
672 	}
673 
674 	return emitted;
675 }
676 
677 /**
678  * tracker_indexing_tree_add_filter:
679  * @tree: a #TrackerIndexingTree
680  * @filter: filter type
681  * @glob_string: glob-style string for the filter
682  *
683  * Adds a new filter for basenames.
684  **/
685 void
tracker_indexing_tree_add_filter(TrackerIndexingTree * tree,TrackerFilterType filter,const gchar * glob_string)686 tracker_indexing_tree_add_filter (TrackerIndexingTree *tree,
687                                   TrackerFilterType    filter,
688                                   const gchar         *glob_string)
689 {
690 	TrackerIndexingTreePrivate *priv;
691 	PatternData *data;
692 
693 	g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
694 	g_return_if_fail (glob_string != NULL);
695 
696 	priv = tree->priv;
697 
698 	data = pattern_data_new (glob_string, filter);
699 	priv->filter_patterns = g_list_prepend (priv->filter_patterns, data);
700 }
701 
702 /**
703  * tracker_indexing_tree_clear_filters:
704  * @tree: a #TrackerIndexingTree
705  * @type: filter type to clear
706  *
707  * Clears all filters of a given type.
708  **/
709 void
tracker_indexing_tree_clear_filters(TrackerIndexingTree * tree,TrackerFilterType type)710 tracker_indexing_tree_clear_filters (TrackerIndexingTree *tree,
711                                      TrackerFilterType    type)
712 {
713 	TrackerIndexingTreePrivate *priv;
714 	GList *l;
715 
716 	g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
717 
718 	priv = tree->priv;
719 
720 	for (l = priv->filter_patterns; l; l = l->next) {
721 		PatternData *data = l->data;
722 
723 		if (data->type == type) {
724 			/* When we delete the link 'l', we point back
725 			 * to the beginning of the list to make sure
726 			 * we don't miss anything.
727 			 */
728 			l = priv->filter_patterns = g_list_delete_link (priv->filter_patterns, l);
729 			pattern_data_free (data);
730 		}
731 	}
732 }
733 
734 /**
735  * tracker_indexing_tree_file_matches_filter:
736  * @tree: a #TrackerIndexingTree
737  * @type: filter type
738  * @file: a #GFile
739  *
740  * Returns %TRUE if @file matches any filter of the given filter type.
741  *
742  * Returns: %TRUE if @file is filtered.
743  **/
744 gboolean
tracker_indexing_tree_file_matches_filter(TrackerIndexingTree * tree,TrackerFilterType type,GFile * file)745 tracker_indexing_tree_file_matches_filter (TrackerIndexingTree *tree,
746                                            TrackerFilterType    type,
747                                            GFile               *file)
748 {
749 	TrackerIndexingTreePrivate *priv;
750 	GList *filters;
751 	gchar *basename, *str, *reverse;
752 	gboolean match = FALSE;
753 	gint len;
754 
755 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
756 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
757 
758 	priv = tree->priv;
759 	filters = priv->filter_patterns;
760 	basename = g_file_get_basename (file);
761 
762 	str = g_utf8_make_valid (basename, -1);
763 	len = strlen (str);
764 	reverse = g_utf8_strreverse (str, len);
765 
766 	while (filters) {
767 		PatternData *data = filters->data;
768 
769 		filters = filters->next;
770 
771 		if (data->type != type)
772 			continue;
773 
774 		if (data->file &&
775 		    (g_file_equal (file, data->file) ||
776 		     g_file_has_prefix (file, data->file))) {
777 			match = TRUE;
778 			break;
779 		}
780 
781 		if (g_pattern_match (data->pattern, len, str, reverse)) {
782 			match = TRUE;
783 			break;
784 		}
785 	}
786 
787 	g_free (basename);
788 	g_free (str);
789 	g_free (reverse);
790 
791 	return match;
792 }
793 
794 static gboolean
parent_or_equals(GFile * file1,GFile * file2)795 parent_or_equals (GFile *file1,
796                   GFile *file2)
797 {
798 	return (file1 == file2 ||
799 	        g_file_equal (file1, file2) ||
800 	        g_file_has_prefix (file1, file2));
801 }
802 
803 static gboolean
indexing_tree_file_is_filtered(TrackerIndexingTree * tree,TrackerFilterType filter,GFile * file)804 indexing_tree_file_is_filtered (TrackerIndexingTree *tree,
805                                 TrackerFilterType    filter,
806                                 GFile               *file)
807 {
808 	TrackerIndexingTreePrivate *priv;
809 
810 	priv = tree->priv;
811 
812 	if (tracker_indexing_tree_file_matches_filter (tree, filter, file)) {
813 		if (priv->policies[filter] == TRACKER_FILTER_POLICY_ACCEPT) {
814 			/* Filter blocks otherwise accepted
815 			 * (by the default policy) file
816 			 */
817 			return TRUE;
818 		}
819 	} else {
820 		if (priv->policies[filter] == TRACKER_FILTER_POLICY_DENY) {
821 			/* No match, and the default policy denies it */
822 			return TRUE;
823 		}
824 	}
825 
826 	return FALSE;
827 }
828 
829 /**
830  * tracker_indexing_tree_file_is_indexable:
831  * @tree: a #TrackerIndexingTree
832  * @file: a #GFile
833  * @file_type: a #GFileType
834  *
835  * returns %TRUE if @file should be indexed according to the
836  * parameters given through tracker_indexing_tree_add() and
837  * tracker_indexing_tree_add_filter().
838  *
839  * If @file_type is #G_FILE_TYPE_UNKNOWN, file type will be queried to the
840  * file system.
841  *
842  * Returns: %TRUE if @file should be indexed.
843  **/
844 gboolean
tracker_indexing_tree_file_is_indexable(TrackerIndexingTree * tree,GFile * file,GFileType file_type)845 tracker_indexing_tree_file_is_indexable (TrackerIndexingTree *tree,
846                                          GFile               *file,
847                                          GFileType            file_type)
848 {
849 	TrackerFilterType filter;
850 	TrackerDirectoryFlags config_flags;
851 	GFile *config_file;
852 
853 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
854 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
855 
856 	config_file = tracker_indexing_tree_get_root (tree, file, &config_flags);
857 	if (!config_file) {
858 		/* Not under an added dir */
859 		return FALSE;
860 	}
861 
862 	/* Don't check file type if _NO_STAT is given in flags */
863 	if (file_type == G_FILE_TYPE_UNKNOWN &&
864 	    (config_flags & TRACKER_DIRECTORY_FLAG_NO_STAT) != 0) {
865 		GFileQueryInfoFlags file_flags;
866 
867 		file_flags = G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS;
868 
869 		file_type = g_file_query_file_type (file, file_flags, NULL);
870 
871 		filter = (file_type == G_FILE_TYPE_DIRECTORY) ?
872 			TRACKER_FILTER_DIRECTORY : TRACKER_FILTER_FILE;
873 
874 		if (indexing_tree_file_is_filtered (tree, filter, file)) {
875 			return FALSE;
876 		}
877 	} else if (file_type != G_FILE_TYPE_UNKNOWN) {
878 		filter = (file_type == G_FILE_TYPE_DIRECTORY) ?
879 			TRACKER_FILTER_DIRECTORY : TRACKER_FILTER_FILE;
880 
881 		if (indexing_tree_file_is_filtered (tree, filter, file)) {
882 			return FALSE;
883 		}
884 	}
885 
886 	/* FIXME: Shouldn't we only do this for file_type == G_FILE_TYPE_DIRECTORY ? */
887 	if (config_flags & TRACKER_DIRECTORY_FLAG_IGNORE) {
888 		return FALSE;
889 	}
890 
891 	if (g_file_equal (file, config_file)) {
892 		return TRUE;
893 	} else {
894 		if ((config_flags & TRACKER_DIRECTORY_FLAG_RECURSE) == 0 &&
895 		    !g_file_has_parent (file, config_file)) {
896 			/* Non direct child in a non-recursive dir, ignore */
897 			return FALSE;
898 		}
899 
900 		if (tracker_indexing_tree_get_filter_hidden (tree) &&
901 		    tracker_file_is_hidden (file)) {
902 			return FALSE;
903 		}
904 
905 		return TRUE;
906 	}
907 }
908 
909 /**
910  * tracker_indexing_tree_parent_is_indexable:
911  * @tree: a #TrackerIndexingTree
912  * @parent: parent directory
913  * @children: (element-type GFile): children within @parent
914  *
915  * returns %TRUE if @parent should be indexed based on its contents.
916  *
917  * Returns: %TRUE if @parent should be indexed.
918  **/
919 gboolean
tracker_indexing_tree_parent_is_indexable(TrackerIndexingTree * tree,GFile * parent,GList * children)920 tracker_indexing_tree_parent_is_indexable (TrackerIndexingTree *tree,
921                                            GFile               *parent,
922                                            GList               *children)
923 {
924 	TrackerIndexingTreePrivate *priv;
925 	gboolean has_match = FALSE;
926 
927 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
928 	g_return_val_if_fail (G_IS_FILE (parent), FALSE);
929 
930 	priv = tree->priv;
931 
932 	if (!tracker_indexing_tree_file_is_indexable (tree,
933 	                                              parent,
934 	                                              G_FILE_TYPE_DIRECTORY)) {
935 		return FALSE;
936 	}
937 
938 	while (children && !has_match) {
939 		has_match = tracker_indexing_tree_file_matches_filter (tree,
940 		                                                       TRACKER_FILTER_PARENT_DIRECTORY,
941 		                                                       children->data);
942 		children = children->next;
943 	}
944 
945 	if (priv->policies[TRACKER_FILTER_PARENT_DIRECTORY] == TRACKER_FILTER_POLICY_ACCEPT)
946 		return !has_match;
947 	else
948 		return has_match;
949 }
950 
951 /**
952  * tracker_indexing_tree_get_filter_hidden:
953  * @tree: a #TrackerIndexingTree
954  *
955  * Describes if the @tree should index hidden content. To change this
956  * setting, see tracker_indexing_tree_set_filter_hidden().
957  *
958  * Returns: %FALSE if hidden files are indexed, otherwise %TRUE.
959  *
960  * Since: 0.18
961  **/
962 gboolean
tracker_indexing_tree_get_filter_hidden(TrackerIndexingTree * tree)963 tracker_indexing_tree_get_filter_hidden (TrackerIndexingTree *tree)
964 {
965 	TrackerIndexingTreePrivate *priv;
966 
967 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
968 
969 	priv = tree->priv;
970 	return priv->filter_hidden;
971 }
972 
973 /**
974  * tracker_indexing_tree_set_filter_hidden:
975  * @tree: a #TrackerIndexingTree
976  * @filter_hidden: a boolean
977  *
978  * When indexing content, sometimes it is preferable to ignore hidden
979  * content, for example, files prefixed with &quot;.&quot;. This is
980  * common for files in a home directory which are usually config
981  * files.
982  *
983  * Sets the indexing policy for @tree with hidden files and content.
984  * To ignore hidden files, @filter_hidden should be %TRUE, otherwise
985  * %FALSE.
986  *
987  * Since: 0.18
988  **/
989 void
tracker_indexing_tree_set_filter_hidden(TrackerIndexingTree * tree,gboolean filter_hidden)990 tracker_indexing_tree_set_filter_hidden (TrackerIndexingTree *tree,
991                                          gboolean             filter_hidden)
992 {
993 	TrackerIndexingTreePrivate *priv;
994 
995 	g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
996 
997 	priv = tree->priv;
998 	priv->filter_hidden = filter_hidden;
999 
1000 	g_object_notify (G_OBJECT (tree), "filter-hidden");
1001 }
1002 
1003 /**
1004  * tracker_indexing_tree_set_default_policy:
1005  * @tree: a #TrackerIndexingTree
1006  * @filter: a #TrackerFilterType
1007  * @policy: a #TrackerFilterPolicy
1008  *
1009  * Set the default @policy (to allow or deny) for content in @tree
1010  * based on the type - in this case @filter. Here, @filter is a file
1011  * or directory and there are some other options too.
1012  *
1013  * For example, you can (by default), disable indexing all directories
1014  * using this function.
1015  *
1016  * Since: 0.18
1017  **/
1018 void
tracker_indexing_tree_set_default_policy(TrackerIndexingTree * tree,TrackerFilterType filter,TrackerFilterPolicy policy)1019 tracker_indexing_tree_set_default_policy (TrackerIndexingTree *tree,
1020                                           TrackerFilterType    filter,
1021                                           TrackerFilterPolicy  policy)
1022 {
1023 	TrackerIndexingTreePrivate *priv;
1024 
1025 	g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
1026 	g_return_if_fail (filter >= TRACKER_FILTER_FILE && filter <= TRACKER_FILTER_PARENT_DIRECTORY);
1027 
1028 	priv = tree->priv;
1029 	priv->policies[filter] = policy;
1030 }
1031 
1032 /**
1033  * tracker_indexing_tree_get_default_policy:
1034  * @tree: a #TrackerIndexingTree
1035  * @filter: a #TrackerFilterType
1036  *
1037  * Get the default filtering policies for @tree when indexing content.
1038  * Some content is black listed or white listed and the default policy
1039  * for that is returned here. The @filter allows specific type of
1040  * policies to be returned, for example, the default policy for files
1041  * (#TRACKER_FILTER_FILE).
1042  *
1043  * Returns: Either #TRACKER_FILTER_POLICY_DENY or
1044  * #TRACKER_FILTER_POLICY_ACCEPT.
1045  *
1046  * Since: 0.18
1047  **/
1048 TrackerFilterPolicy
tracker_indexing_tree_get_default_policy(TrackerIndexingTree * tree,TrackerFilterType filter)1049 tracker_indexing_tree_get_default_policy (TrackerIndexingTree *tree,
1050                                           TrackerFilterType    filter)
1051 {
1052 	TrackerIndexingTreePrivate *priv;
1053 
1054 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree),
1055 	                      TRACKER_FILTER_POLICY_DENY);
1056 	g_return_val_if_fail (filter >= TRACKER_FILTER_FILE &&
1057 	                      filter <= TRACKER_FILTER_PARENT_DIRECTORY,
1058 	                      TRACKER_FILTER_POLICY_DENY);
1059 
1060 	priv = tree->priv;
1061 	return priv->policies[filter];
1062 }
1063 
1064 /**
1065  * tracker_indexing_tree_get_root:
1066  * @tree: a #TrackerIndexingTree
1067  * @file: a #GFile
1068  * @directory_flags: (out): return location for the applying #TrackerDirectoryFlags
1069  *
1070  * Returns the #GFile that was previously added through tracker_indexing_tree_add()
1071  * and would equal or contain @file, or %NULL if none applies.
1072  *
1073  * If the return value is non-%NULL, @directory_flags would contain the
1074  * #TrackerDirectoryFlags applying to @file.
1075  *
1076  * Returns: (transfer none): the effective parent in @tree, or %NULL
1077  **/
1078 GFile *
tracker_indexing_tree_get_root(TrackerIndexingTree * tree,GFile * file,TrackerDirectoryFlags * directory_flags)1079 tracker_indexing_tree_get_root (TrackerIndexingTree   *tree,
1080                                 GFile                 *file,
1081                                 TrackerDirectoryFlags *directory_flags)
1082 {
1083 	TrackerIndexingTreePrivate *priv;
1084 	NodeData *data;
1085 	GNode *parent;
1086 
1087 	if (directory_flags) {
1088 		*directory_flags = TRACKER_DIRECTORY_FLAG_NONE;
1089 	}
1090 
1091 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), NULL);
1092 	g_return_val_if_fail (G_IS_FILE (file), NULL);
1093 
1094 	priv = tree->priv;
1095 	parent = find_directory_node (priv->config_tree, file,
1096 	                              (GEqualFunc) parent_or_equals);
1097 	if (!parent) {
1098 		return NULL;
1099 	}
1100 
1101 	data = parent->data;
1102 
1103 	if (!data->shallow &&
1104 	    (file == data->file ||
1105 	     g_file_equal (file, data->file) ||
1106 	     g_file_has_prefix (file, data->file))) {
1107 		if (directory_flags) {
1108 			*directory_flags = data->flags;
1109 		}
1110 
1111 		return data->file;
1112 	}
1113 
1114 	return NULL;
1115 }
1116 
1117 /**
1118  * tracker_indexing_tree_get_master_root:
1119  * @tree: a #TrackerIndexingTree
1120  *
1121  * Returns the #GFile that represents the master root location for all
1122  * indexing locations. For example, if
1123  * <filename>file:///etc</filename> is an indexed path and so was
1124  * <filename>file:///home/user</filename>, the master root is
1125  * <filename>file:///</filename>. Only one scheme per @tree can be
1126  * used, so you can not mix <filename>http</filename> and
1127  * <filename>file</filename> roots in @tree.
1128  *
1129  * The return value should <emphasis>NEVER</emphasis> be %NULL. In
1130  * cases where no root is given, we fallback to
1131  * <filename>file:///</filename>.
1132  *
1133  * Roots explained:
1134  *
1135  * - master root = top most level root node,
1136  *   e.g. file:///
1137  *
1138  * - config root = a root node from GSettings,
1139  *   e.g. file:///home/martyn/Documents
1140  *
1141  * - root = ANY root, normally config root, but it can also apply to
1142  *   roots added for devices, which technically are not a config root or a
1143  *   master root.
1144  *
1145  * Returns: (transfer none): the effective root for all locations, or
1146  * %NULL on error. The root is owned by @tree and should not be freed.
1147  * It can be referenced using g_object_ref().
1148  *
1149  * Since: 1.2
1150  **/
1151 GFile *
tracker_indexing_tree_get_master_root(TrackerIndexingTree * tree)1152 tracker_indexing_tree_get_master_root (TrackerIndexingTree *tree)
1153 {
1154 	TrackerIndexingTreePrivate *priv;
1155 
1156 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), NULL);
1157 
1158 	priv = tree->priv;
1159 
1160 	return priv->root;
1161 }
1162 
1163 /**
1164  * tracker_indexing_tree_file_is_root:
1165  * @tree: a #TrackerIndexingTree
1166  * @file: a #GFile to compare
1167  *
1168  * Evaluates if the URL represented by @file is the same of that for
1169  * the root of the @tree.
1170  *
1171  * Returns: %TRUE if @file matches the URL canonically, otherwise %FALSE.
1172  *
1173  * Since: 1.2
1174  **/
1175 gboolean
tracker_indexing_tree_file_is_root(TrackerIndexingTree * tree,GFile * file)1176 tracker_indexing_tree_file_is_root (TrackerIndexingTree *tree,
1177                                     GFile               *file)
1178 {
1179 	TrackerIndexingTreePrivate *priv;
1180 	GNode *node;
1181 
1182 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
1183 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
1184 
1185 	priv = tree->priv;
1186 	node = find_directory_node (priv->config_tree, file,
1187 	                            (GEqualFunc) g_file_equal);
1188 	return node != NULL;
1189 }
1190 
1191 static gboolean
prepend_config_root(GNode * node,gpointer user_data)1192 prepend_config_root (GNode    *node,
1193                      gpointer  user_data)
1194 {
1195 	GList **list = user_data;
1196 	NodeData *data = node->data;
1197 
1198 	if (!data->shallow && !data->removing)
1199 		*list = g_list_prepend (*list, data->file);
1200 	return FALSE;
1201 }
1202 
1203 /**
1204  * tracker_indexing_tree_list_roots:
1205  * @tree: a #TrackerIndexingTree
1206  *
1207  * Returns the list of indexing roots in @tree
1208  *
1209  * Returns: (transfer container) (element-type GFile): The list
1210  *          of roots, the list itself must be freed with g_list_free(),
1211  *          the list elements are owned by @tree and should not be
1212  *          freed.
1213  **/
1214 GList *
tracker_indexing_tree_list_roots(TrackerIndexingTree * tree)1215 tracker_indexing_tree_list_roots (TrackerIndexingTree *tree)
1216 {
1217 	TrackerIndexingTreePrivate *priv;
1218 	GList *nodes = NULL;
1219 
1220 	g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), NULL);
1221 
1222 	priv = tree->priv;
1223 	g_node_traverse (priv->config_tree,
1224 	                 G_POST_ORDER,
1225 	                 G_TRAVERSE_ALL,
1226 	                 -1,
1227 	                 prepend_config_root,
1228 	                 &nodes);
1229 	return nodes;
1230 }
1231