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 ".". 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