No issues found
1 /*
2 * Copyright (C) 2009, 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
20 #include "config.h"
21
22 #include "tracker-crawler.h"
23 #include "tracker-marshal.h"
24 #include "tracker-utils.h"
25
26 #define TRACKER_CRAWLER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_CRAWLER, TrackerCrawlerPrivate))
27
28 #define FILE_ATTRIBUTES \
29 G_FILE_ATTRIBUTE_STANDARD_NAME "," \
30 G_FILE_ATTRIBUTE_STANDARD_TYPE
31
32 #define FILES_QUEUE_PROCESS_INTERVAL 2000
33 #define FILES_QUEUE_PROCESS_MAX 5000
34
35 /* This is the number of files to be called back with from GIO at a
36 * time so we don't get called back for every file.
37 */
38 #define FILES_GROUP_SIZE 100
39
40 typedef struct DirectoryChildData DirectoryChildData;
41 typedef struct DirectoryProcessingData DirectoryProcessingData;
42 typedef struct DirectoryRootInfo DirectoryRootInfo;
43
44 struct DirectoryChildData {
45 GFile *child;
46 gboolean is_dir;
47 };
48
49 struct DirectoryProcessingData {
50 GNode *node;
51 GSList *children;
52 guint was_inspected : 1;
53 guint ignored_by_content : 1;
54 };
55
56 struct DirectoryRootInfo {
57 GFile *directory;
58 GNode *tree;
59 guint recurse : 1;
60
61 GQueue *directory_processing_queue;
62
63 /* Directory stats */
64 guint directories_found;
65 guint directories_ignored;
66 guint files_found;
67 guint files_ignored;
68 };
69
70 struct TrackerCrawlerPrivate {
71 /* Directories to crawl */
72 GQueue *directories;
73
74 GList *cancellables;
75
76 /* Idle handler for processing found data */
77 guint idle_id;
78
79 gdouble throttle;
80
81 gchar *file_attributes;
82
83 gboolean recurse;
84
85 /* Statistics */
86 GTimer *timer;
87
88 /* Status */
89 gboolean is_running;
90 gboolean is_finished;
91 gboolean is_paused;
92 gboolean was_started;
93 };
94
95 enum {
96 CHECK_DIRECTORY,
97 CHECK_FILE,
98 CHECK_DIRECTORY_CONTENTS,
99 DIRECTORY_CRAWLED,
100 FINISHED,
101 LAST_SIGNAL
102 };
103
104 typedef struct {
105 TrackerCrawler *crawler;
106 DirectoryRootInfo *root_info;
107 DirectoryProcessingData *dir_info;
108 GFile *dir_file;
109 GCancellable *cancellable;
110 } EnumeratorData;
111
112 static void crawler_finalize (GObject *object);
113 static gboolean check_defaults (TrackerCrawler *crawler,
114 GFile *file);
115 static gboolean check_contents_defaults (TrackerCrawler *crawler,
116 GFile *file,
117 GList *contents);
118 static void file_enumerate_next (GFileEnumerator *enumerator,
119 EnumeratorData *ed);
120 static void file_enumerate_children (TrackerCrawler *crawler,
121 DirectoryRootInfo *info,
122 DirectoryProcessingData *dir_data);
123
124 static void directory_root_info_free (DirectoryRootInfo *info);
125
126
127 static guint signals[LAST_SIGNAL] = { 0, };
128 static GQuark file_info_quark = 0;
129
130 G_DEFINE_TYPE (TrackerCrawler, tracker_crawler, G_TYPE_OBJECT)
131
132 static void
133 tracker_crawler_class_init (TrackerCrawlerClass *klass)
134 {
135 GObjectClass *object_class = G_OBJECT_CLASS (klass);
136 TrackerCrawlerClass *crawler_class = TRACKER_CRAWLER_CLASS (klass);
137
138 object_class->finalize = crawler_finalize;
139
140 crawler_class->check_directory = check_defaults;
141 crawler_class->check_file = check_defaults;
142 crawler_class->check_directory_contents = check_contents_defaults;
143
144 signals[CHECK_DIRECTORY] =
145 g_signal_new ("check-directory",
146 G_TYPE_FROM_CLASS (klass),
147 G_SIGNAL_RUN_LAST,
148 G_STRUCT_OFFSET (TrackerCrawlerClass, check_directory),
149 tracker_accumulator_check_file,
150 NULL,
151 tracker_marshal_BOOLEAN__OBJECT,
152 G_TYPE_BOOLEAN,
153 1,
154 G_TYPE_FILE);
155 signals[CHECK_FILE] =
156 g_signal_new ("check-file",
157 G_TYPE_FROM_CLASS (klass),
158 G_SIGNAL_RUN_LAST,
159 G_STRUCT_OFFSET (TrackerCrawlerClass, check_file),
160 tracker_accumulator_check_file,
161 NULL,
162 tracker_marshal_BOOLEAN__OBJECT,
163 G_TYPE_BOOLEAN,
164 1,
165 G_TYPE_FILE);
166 signals[CHECK_DIRECTORY_CONTENTS] =
167 g_signal_new ("check-directory-contents",
168 G_TYPE_FROM_CLASS (klass),
169 G_SIGNAL_RUN_LAST,
170 G_STRUCT_OFFSET (TrackerCrawlerClass, check_directory_contents),
171 tracker_accumulator_check_file,
172 NULL,
173 tracker_marshal_BOOLEAN__OBJECT_POINTER,
174 G_TYPE_BOOLEAN,
175 2, G_TYPE_FILE, G_TYPE_POINTER);
176 signals[DIRECTORY_CRAWLED] =
177 g_signal_new ("directory-crawled",
178 G_TYPE_FROM_CLASS (klass),
179 G_SIGNAL_RUN_LAST,
180 G_STRUCT_OFFSET (TrackerCrawlerClass, directory_crawled),
181 NULL, NULL,
182 tracker_marshal_VOID__OBJECT_POINTER_UINT_UINT_UINT_UINT,
183 G_TYPE_NONE,
184 6,
185 G_TYPE_FILE,
186 G_TYPE_POINTER,
187 G_TYPE_UINT,
188 G_TYPE_UINT,
189 G_TYPE_UINT,
190 G_TYPE_UINT);
191 signals[FINISHED] =
192 g_signal_new ("finished",
193 G_TYPE_FROM_CLASS (klass),
194 G_SIGNAL_RUN_LAST,
195 G_STRUCT_OFFSET (TrackerCrawlerClass, finished),
196 NULL, NULL,
197 g_cclosure_marshal_VOID__BOOLEAN,
198 G_TYPE_NONE,
199 1, G_TYPE_BOOLEAN);
200
201 g_type_class_add_private (object_class, sizeof (TrackerCrawlerPrivate));
202
203 file_info_quark = g_quark_from_static_string ("tracker-crawler-file-info");
204 }
205
206 static void
207 tracker_crawler_init (TrackerCrawler *object)
208 {
209 TrackerCrawlerPrivate *priv;
210
211 object->priv = TRACKER_CRAWLER_GET_PRIVATE (object);
212
213 priv = object->priv;
214
215 priv->directories = g_queue_new ();
216 }
217
218 static void
219 crawler_finalize (GObject *object)
220 {
221 TrackerCrawlerPrivate *priv;
222
223 priv = TRACKER_CRAWLER_GET_PRIVATE (object);
224
225 if (priv->timer) {
226 g_timer_destroy (priv->timer);
227 }
228
229 if (priv->idle_id) {
230 g_source_remove (priv->idle_id);
231 }
232
233 g_list_free (priv->cancellables);
234
235 g_queue_foreach (priv->directories, (GFunc) directory_root_info_free, NULL);
236 g_queue_free (priv->directories);
237
238 g_free (priv->file_attributes);
239
240 G_OBJECT_CLASS (tracker_crawler_parent_class)->finalize (object);
241 }
242
243 static gboolean
244 check_defaults (TrackerCrawler *crawler,
245 GFile *file)
246 {
247 return TRUE;
248 }
249
250 static gboolean
251 check_contents_defaults (TrackerCrawler *crawler,
252 GFile *file,
253 GList *contents)
254 {
255 return TRUE;
256 }
257
258 TrackerCrawler *
259 tracker_crawler_new (void)
260 {
261 TrackerCrawler *crawler;
262
263 crawler = g_object_new (TRACKER_TYPE_CRAWLER, NULL);
264
265 return crawler;
266 }
267
268 static gboolean
269 check_file (TrackerCrawler *crawler,
270 DirectoryRootInfo *info,
271 GFile *file)
272 {
273 gboolean use = FALSE;
274 TrackerCrawlerPrivate *priv;
275
276 priv = TRACKER_CRAWLER_GET_PRIVATE (crawler);
277
278 g_signal_emit (crawler, signals[CHECK_FILE], 0, file, &use);
279
280 /* Crawler may have been stopped while waiting for the 'use' value,
281 * and the DirectoryRootInfo already disposed... */
282 if (!priv->is_running) {
283 return FALSE;
284 }
285
286 info->files_found++;
287
288 if (!use) {
289 info->files_ignored++;
290 }
291
292 return use;
293 }
294
295 static gboolean
296 check_directory (TrackerCrawler *crawler,
297 DirectoryRootInfo *info,
298 GFile *file)
299 {
300 gboolean use = FALSE;
301 TrackerCrawlerPrivate *priv;
302
303 priv = TRACKER_CRAWLER_GET_PRIVATE (crawler);
304
305 g_signal_emit (crawler, signals[CHECK_DIRECTORY], 0, file, &use);
306
307 /* Crawler may have been stopped while waiting for the 'use' value,
308 * and the DirectoryRootInfo already disposed... */
309 if (!priv->is_running) {
310 return FALSE;
311 }
312
313 info->directories_found++;
314
315 if (!use) {
316 info->directories_ignored++;
317 }
318
319 return use;
320 }
321
322 static DirectoryChildData *
323 directory_child_data_new (GFile *child,
324 gboolean is_dir)
325 {
326 DirectoryChildData *child_data;
327
328 child_data = g_slice_new (DirectoryChildData);
329 child_data->child = g_object_ref (child);
330 child_data->is_dir = is_dir;
331
332 return child_data;
333 }
334
335 static void
336 directory_child_data_free (DirectoryChildData *child_data)
337 {
338 g_object_unref (child_data->child);
339 g_slice_free (DirectoryChildData, child_data);
340 }
341
342 static DirectoryProcessingData *
343 directory_processing_data_new (GNode *node)
344 {
345 DirectoryProcessingData *data;
346
347 data = g_slice_new0 (DirectoryProcessingData);
348 data->node = node;
349
350 return data;
351 }
352
353 static void
354 directory_processing_data_free (DirectoryProcessingData *data)
355 {
356 g_slist_foreach (data->children, (GFunc) directory_child_data_free, NULL);
357 g_slist_free (data->children);
358
359 g_slice_free (DirectoryProcessingData, data);
360 }
361
362 static void
363 directory_processing_data_add_child (DirectoryProcessingData *data,
364 GFile *child,
365 gboolean is_dir)
366 {
367 DirectoryChildData *child_data;
368
369 child_data = directory_child_data_new (child, is_dir);
370 data->children = g_slist_prepend (data->children, child_data);
371 }
372
373 static DirectoryRootInfo *
374 directory_root_info_new (GFile *file,
375 gboolean recurse,
376 gchar *file_attributes)
377 {
378 DirectoryRootInfo *info;
379 DirectoryProcessingData *dir_info;
380
381 info = g_slice_new0 (DirectoryRootInfo);
382
383 info->directory = g_object_ref (file);
384 info->recurse = recurse;
385 info->directory_processing_queue = g_queue_new ();
386
387 info->tree = g_node_new (g_object_ref (file));
388
389 if (file_attributes) {
390 GFileInfo *file_info;
391
392 file_info = g_file_query_info (file,
393 file_attributes,
394 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
395 NULL,
396 NULL);
397 g_object_set_qdata_full (G_OBJECT (file),
398 file_info_quark,
399 file_info,
400 (GDestroyNotify) g_object_unref);
401 }
402
403 /* Fill in the processing info for the root node */
404 dir_info = directory_processing_data_new (info->tree);
405 g_queue_push_tail (info->directory_processing_queue, dir_info);
406
407 return info;
408 }
409
410 static gboolean
411 directory_tree_free_foreach (GNode *node,
412 gpointer user_data)
413 {
414 g_object_unref (node->data);
415 return FALSE;
416 }
417
418 static void
419 directory_root_info_free (DirectoryRootInfo *info)
420 {
421 g_object_unref (info->directory);
422
423 g_node_traverse (info->tree,
424 G_PRE_ORDER,
425 G_TRAVERSE_ALL,
426 -1,
427 directory_tree_free_foreach,
428 NULL);
429 g_node_destroy (info->tree);
430
431 g_queue_foreach (info->directory_processing_queue,
432 (GFunc) directory_processing_data_free,
433 NULL);
434 g_queue_free (info->directory_processing_queue);
435
436 g_slice_free (DirectoryRootInfo, info);
437 }
438
439 static gboolean
440 process_func (gpointer data)
441 {
442 TrackerCrawler *crawler;
443 TrackerCrawlerPrivate *priv;
444 DirectoryRootInfo *info;
445 DirectoryProcessingData *dir_data = NULL;
446 gboolean stop_idle = FALSE;
447
448 crawler = TRACKER_CRAWLER (data);
449 priv = crawler->priv;
450
451 if (priv->is_paused) {
452 /* Stop the idle func for now until we are unpaused */
453 priv->idle_id = 0;
454
455 return FALSE;
456 }
457
458 info = g_queue_peek_head (priv->directories);
459
460 if (info) {
461 dir_data = g_queue_peek_head (info->directory_processing_queue);
462 }
463
464 if (dir_data) {
465 /* One directory inside the tree hierarchy is being inspected */
466 if (!dir_data->was_inspected) {
467 gboolean iterate;
468
469 if (G_NODE_IS_ROOT (dir_data->node)) {
470 iterate = check_directory (crawler, info, dir_data->node->data);
471 } else {
472 /* Directory has been already checked in the block below, so
473 * so obey the settings for the current directory root.
474 */
475 iterate = info->recurse;
476 }
477
478 dir_data->was_inspected = TRUE;
479
480 /* Crawler may have been already stopped while we were waiting for the
481 * check_directory return value, and thus we should check if it's
482 * running before going on with the iteration */
483 if (priv->is_running && iterate) {
484 /* Directory contents haven't been inspected yet,
485 * stop this idle function while it's being iterated
486 */
487 file_enumerate_children (crawler, info, dir_data);
488 stop_idle = TRUE;
489 }
490 } else if (dir_data->was_inspected &&
491 !dir_data->ignored_by_content &&
492 dir_data->children != NULL) {
493 DirectoryChildData *child_data;
494 GNode *child_node = NULL;
495
496 /* Directory has been already inspected, take children
497 * one by one and check whether they should be incorporated
498 * to the tree.
499 */
500 child_data = dir_data->children->data;
501 dir_data->children = g_slist_remove (dir_data->children, child_data);
502
503 if (((child_data->is_dir &&
504 check_directory (crawler, info, child_data->child)) ||
505 (!child_data->is_dir &&
506 check_file (crawler, info, child_data->child))) &&
507 /* Crawler may have been already stopped while we were waiting for the
508 * check_directory or check_file return value, and thus we should
509 * check if it's running before going on */
510 priv->is_running) {
511 child_node = g_node_prepend_data (dir_data->node,
512 g_object_ref (child_data->child));
513 }
514
515 if (info->recurse && priv->is_running &&
516 child_node && child_data->is_dir) {
517 DirectoryProcessingData *child_dir_data;
518
519 child_dir_data = directory_processing_data_new (child_node);
520 g_queue_push_tail (info->directory_processing_queue, child_dir_data);
521 }
522
523 directory_child_data_free (child_data);
524 } else {
525 /* No (more) children, or directory ignored. stop processing. */
526 g_queue_pop_head (info->directory_processing_queue);
527 directory_processing_data_free (dir_data);
528 }
529 } else if (!dir_data && info) {
530 /* Current directory being crawled doesn't have anything else
531 * to process, emit ::directory-crawled and free data.
532 */
533 g_signal_emit (crawler, signals[DIRECTORY_CRAWLED], 0,
534 info->directory,
535 info->tree,
536 info->directories_found,
537 info->directories_ignored,
538 info->files_found,
539 info->files_ignored);
540
541 g_queue_pop_head (priv->directories);
542 directory_root_info_free (info);
543 }
544
545 if (!g_queue_peek_head (priv->directories)) {
546 /* There's nothing else to process */
547 priv->is_finished = TRUE;
548 tracker_crawler_stop (crawler);
549 stop_idle = TRUE;
550 }
551
552 if (stop_idle) {
553 priv->idle_id = 0;
554 return FALSE;
555 }
556
557 return TRUE;
558 }
559
560 static gboolean
561 process_func_start (TrackerCrawler *crawler)
562 {
563 if (crawler->priv->is_paused) {
564 return FALSE;
565 }
566
567 if (crawler->priv->is_finished) {
568 return FALSE;
569 }
570
571 if (crawler->priv->idle_id == 0) {
572 crawler->priv->idle_id = g_idle_add (process_func, crawler);
573 }
574
575 return TRUE;
576 }
577
578 static void
579 process_func_stop (TrackerCrawler *crawler)
580 {
581 if (crawler->priv->idle_id != 0) {
582 g_source_remove (crawler->priv->idle_id);
583 crawler->priv->idle_id = 0;
584 }
585 }
586
587 static EnumeratorData *
588 enumerator_data_new (TrackerCrawler *crawler,
589 DirectoryRootInfo *root_info,
590 DirectoryProcessingData *dir_info)
591 {
592 EnumeratorData *ed;
593
594 ed = g_slice_new (EnumeratorData);
595
596 ed->crawler = g_object_ref (crawler);
597 ed->root_info = root_info;
598 ed->dir_info = dir_info;
599 /* Make sure there's always a ref of the GFile while we're
600 * iterating it */
601 ed->dir_file = g_object_ref (G_FILE (dir_info->node->data));
602 ed->cancellable = g_cancellable_new ();
603
604 crawler->priv->cancellables = g_list_prepend (crawler->priv->cancellables,
605 ed->cancellable);
606 return ed;
607 }
608
609 static void
610 enumerator_data_process (EnumeratorData *ed)
611 {
612 TrackerCrawler *crawler;
613 GSList *l;
614 GList *children = NULL;
615 gboolean use;
616
617 crawler = ed->crawler;
618
619 for (l = ed->dir_info->children; l; l = l->next) {
620 DirectoryChildData *child_data;
621
622 child_data = l->data;
623 children = g_list_prepend (children, child_data->child);
624 }
625
626 g_signal_emit (crawler, signals[CHECK_DIRECTORY_CONTENTS], 0, ed->dir_info->node->data, children, &use);
627 g_list_free (children);
628
629 if (!use) {
630 ed->dir_info->ignored_by_content = TRUE;
631 /* FIXME: Update stats */
632 return;
633 }
634 }
635
636 static void
637 enumerator_data_free (EnumeratorData *ed)
638 {
639 ed->crawler->priv->cancellables =
640 g_list_remove (ed->crawler->priv->cancellables,
641 ed->cancellable);
642
643 g_object_unref (ed->dir_file);
644 g_object_unref (ed->crawler);
645 g_object_unref (ed->cancellable);
646 g_slice_free (EnumeratorData, ed);
647 }
648
649 static void
650 file_enumerator_close_cb (GObject *enumerator,
651 GAsyncResult *result,
652 gpointer user_data)
653 {
654 TrackerCrawler *crawler;
655 GError *error = NULL;
656
657 crawler = TRACKER_CRAWLER (user_data);
658
659 if (!g_file_enumerator_close_finish (G_FILE_ENUMERATOR (enumerator),
660 result,
661 &error)) {
662 g_warning ("Couldn't close GFileEnumerator (%p): %s", enumerator,
663 (error) ? error->message : "No reason");
664
665 g_clear_error (&error);
666 }
667
668 /* Processing of directory is now finished,
669 * continue with queued files/directories.
670 */
671 process_func_start (crawler);
672 }
673
674 static void
675 file_enumerate_next_cb (GObject *object,
676 GAsyncResult *result,
677 gpointer user_data)
678 {
679 TrackerCrawler *crawler;
680 EnumeratorData *ed;
681 GFileEnumerator *enumerator;
682 GFile *parent, *child;
683 GFileInfo *info;
684 GList *files, *l;
685 GError *error = NULL;
686 gboolean cancelled;
687
688 enumerator = G_FILE_ENUMERATOR (object);
689
690 ed = user_data;
691 crawler = ed->crawler;
692 cancelled = g_cancellable_is_cancelled (ed->cancellable);
693
694 files = g_file_enumerator_next_files_finish (enumerator,
695 result,
696 &error);
697
698 if (error || !files || !crawler->priv->is_running) {
699 if (error && !cancelled) {
700 g_critical ("Could not crawl through directory: %s", error->message);
701 g_error_free (error);
702 }
703
704 /* No more files or we are stopping anyway, so clean
705 * up and close all file enumerators.
706 */
707 if (files) {
708 g_list_foreach (files, (GFunc) g_object_unref, NULL);
709 g_list_free (files);
710 }
711
712 if (!cancelled) {
713 enumerator_data_process (ed);
714 }
715
716 enumerator_data_free (ed);
717 g_file_enumerator_close_async (enumerator,
718 G_PRIORITY_DEFAULT,
719 NULL,
720 file_enumerator_close_cb,
721 crawler);
722 g_object_unref (enumerator);
723
724 return;
725 }
726
727 parent = ed->dir_info->node->data;
728
729 for (l = files; l; l = l->next) {
730 const gchar *child_name;
731 gboolean is_dir;
732
733 info = l->data;
734
735 child_name = g_file_info_get_name (info);
736 child = g_file_get_child (parent, child_name);
737 is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
738
739 if (crawler->priv->file_attributes) {
740 /* Store the file info for future retrieval */
741 g_object_set_qdata_full (G_OBJECT (child),
742 file_info_quark,
743 g_object_ref (info),
744 (GDestroyNotify) g_object_unref);
745 }
746
747 directory_processing_data_add_child (ed->dir_info, child, is_dir);
748
749 g_object_unref (child);
750 g_object_unref (info);
751 }
752
753 g_list_free (files);
754
755 /* Get next files */
756 file_enumerate_next (enumerator, ed);
757 }
758
759 static void
760 file_enumerate_next (GFileEnumerator *enumerator,
761 EnumeratorData *ed)
762 {
763 g_file_enumerator_next_files_async (enumerator,
764 FILES_GROUP_SIZE,
765 G_PRIORITY_DEFAULT,
766 ed->cancellable,
767 file_enumerate_next_cb,
768 ed);
769 }
770
771 static void
772 file_enumerate_children_cb (GObject *file,
773 GAsyncResult *result,
774 gpointer user_data)
775 {
776 TrackerCrawler *crawler;
777 EnumeratorData *ed;
778 GFileEnumerator *enumerator;
779 GFile *parent;
780 GError *error = NULL;
781 gboolean cancelled;
782
783 parent = G_FILE (file);
784 ed = (EnumeratorData*) user_data;
785 crawler = ed->crawler;
786 cancelled = g_cancellable_is_cancelled (ed->cancellable);
787 enumerator = g_file_enumerate_children_finish (parent, result, &error);
788
789 if (!enumerator) {
790 if (error && !cancelled) {
791 gchar *path;
792
793 path = g_file_get_path (parent);
794
795 g_warning ("Could not open directory '%s': %s",
796 path, error->message);
797
798 g_error_free (error);
799 g_free (path);
800 }
801
802 enumerator_data_free (ed);
803 process_func_start (crawler);
804 return;
805 }
806
807 /* Start traversing the directory's files */
808 file_enumerate_next (enumerator, ed);
809 }
810
811 static void
812 file_enumerate_children (TrackerCrawler *crawler,
813 DirectoryRootInfo *info,
814 DirectoryProcessingData *dir_data)
815 {
816 EnumeratorData *ed;
817 gchar *attrs;
818
819 ed = enumerator_data_new (crawler, info, dir_data);
820
821 if (crawler->priv->file_attributes) {
822 attrs = g_strconcat (FILE_ATTRIBUTES ",",
823 crawler->priv->file_attributes,
824 NULL);
825 } else {
826 attrs = g_strdup (FILE_ATTRIBUTES);
827 }
828
829 g_file_enumerate_children_async (ed->dir_file,
830 attrs,
831 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
832 G_PRIORITY_LOW,
833 ed->cancellable,
834 file_enumerate_children_cb,
835 ed);
836
837 g_free (attrs);
838 }
839
840 gboolean
841 tracker_crawler_start (TrackerCrawler *crawler,
842 GFile *file,
843 gboolean recurse)
844 {
845 TrackerCrawlerPrivate *priv;
846 DirectoryRootInfo *info;
847
848 g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), FALSE);
849 g_return_val_if_fail (G_IS_FILE (file), FALSE);
850
851 priv = crawler->priv;
852
853 if (!g_file_query_exists (file, NULL)) {
854 /* This shouldn't happen, unless the removal/unmount notification
855 * didn't yet reach the TrackerFileNotifier.
856 */
857 return FALSE;
858 }
859
860 priv->was_started = TRUE;
861 priv->recurse = recurse;
862
863 /* Time the event */
864 if (priv->timer) {
865 g_timer_destroy (priv->timer);
866 }
867
868 priv->timer = g_timer_new ();
869
870 if (priv->is_paused) {
871 g_timer_stop (priv->timer);
872 }
873
874 /* Set as running now */
875 priv->is_running = TRUE;
876 priv->is_finished = FALSE;
877
878 info = directory_root_info_new (file, recurse, priv->file_attributes);
879 g_queue_push_tail (priv->directories, info);
880
881 process_func_start (crawler);
882
883 return TRUE;
884 }
885
886 void
887 tracker_crawler_stop (TrackerCrawler *crawler)
888 {
889 TrackerCrawlerPrivate *priv;
890
891 g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
892
893 priv = crawler->priv;
894
895 /* If already not running, just ignore */
896 if (!priv->is_running) {
897 return;
898 }
899
900 priv->is_running = FALSE;
901 g_list_foreach (priv->cancellables, (GFunc) g_cancellable_cancel, NULL);
902
903 process_func_stop (crawler);
904
905 if (priv->timer) {
906 g_timer_destroy (priv->timer);
907 priv->timer = NULL;
908 }
909
910 /* Clean up queue */
911 g_queue_foreach (priv->directories, (GFunc) directory_root_info_free, NULL);
912 g_queue_clear (priv->directories);
913
914 g_signal_emit (crawler, signals[FINISHED], 0,
915 !priv->is_finished);
916
917 /* We don't free the queue in case the crawler is reused, it
918 * is only freed in finalize.
919 */
920 }
921
922 void
923 tracker_crawler_pause (TrackerCrawler *crawler)
924 {
925 g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
926
927 crawler->priv->is_paused = TRUE;
928
929 if (crawler->priv->is_running) {
930 g_timer_stop (crawler->priv->timer);
931 process_func_stop (crawler);
932 }
933
934 g_message ("Crawler is paused, %s",
935 crawler->priv->is_running ? "currently running" : "not running");
936 }
937
938 void
939 tracker_crawler_resume (TrackerCrawler *crawler)
940 {
941 g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
942
943 crawler->priv->is_paused = FALSE;
944
945 if (crawler->priv->is_running) {
946 g_timer_continue (crawler->priv->timer);
947 process_func_start (crawler);
948 }
949
950 g_message ("Crawler is resuming, %s",
951 crawler->priv->is_running ? "currently running" : "not running");
952 }
953
954 void
955 tracker_crawler_set_throttle (TrackerCrawler *crawler,
956 gdouble throttle)
957 {
958 g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
959
960 throttle = CLAMP (throttle, 0, 1);
961 crawler->priv->throttle = throttle;
962
963 /* Update timeouts */
964 if (crawler->priv->idle_id != 0) {
965 guint interval, idle_id;
966
967 interval = TRACKER_MAX_TIMEOUT_INTERVAL * crawler->priv->throttle;
968
969 g_source_remove (crawler->priv->idle_id);
970
971 if (interval == 0) {
972 idle_id = g_idle_add (process_func, crawler);
973 } else {
974 idle_id = g_timeout_add (interval, process_func, crawler);
975 }
976
977 crawler->priv->idle_id = idle_id;
978 }
979 }
980
981 /**
982 * tracker_crawler_set_file_attributes:
983 * @crawler: a #TrackerCrawler
984 * @file_attributes: file attributes to extract
985 *
986 * Sets the file attributes that @crawler will fetch for every
987 * file it gets, this info may be requested through
988 * tracker_crawler_get_file_info() in any #TrackerCrawler callback
989 **/
990 void
991 tracker_crawler_set_file_attributes (TrackerCrawler *crawler,
992 const gchar *file_attributes)
993 {
994 g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
995
996 g_free (crawler->priv->file_attributes);
997 crawler->priv->file_attributes = g_strdup (file_attributes);
998 }
999
1000 /**
1001 * tracker_crawler_get_file_attributes:
1002 * @crawler: a #TrackerCrawler
1003 *
1004 * Returns the file attributes that @crawler will fetch
1005 *
1006 * Returns: the file attributes as a string.
1007 **/
1008 const gchar *
1009 tracker_crawler_get_file_attributes (TrackerCrawler *crawler)
1010 {
1011 g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), NULL);
1012
1013 return crawler->priv->file_attributes;
1014 }
1015
1016 /**
1017 * tracker_crawler_get_file_info:
1018 * @crawler: a #TrackerCrawler
1019 * @file: a #GFile returned by @crawler
1020 *
1021 * Returns a #GFileInfo with the file attributes requested through
1022 * tracker_crawler_set_file_attributes().
1023 *
1024 * Returns: (transfer none): a #GFileInfo with the file information
1025 **/
1026 GFileInfo *
1027 tracker_crawler_get_file_info (TrackerCrawler *crawler,
1028 GFile *file)
1029 {
1030 GFileInfo *info;
1031
1032 g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), NULL);
1033 g_return_val_if_fail (G_IS_FILE (file), NULL);
1034
1035 info = g_object_get_qdata (G_OBJECT (file), file_info_quark);
1036 return info;
1037 }