No issues found
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-log.h>
23 #include <libtracker-common/tracker-date-time.h>
24 #include <libtracker-sparql/tracker-sparql.h>
25
26 #include "tracker-miner-common.h"
27 #include "tracker-file-notifier.h"
28 #include "tracker-file-system.h"
29 #include "tracker-crawler.h"
30 #include "tracker-monitor.h"
31 #include "tracker-marshal.h"
32
33 static GQuark quark_property_crawled = 0;
34 static GQuark quark_property_queried = 0;
35 static GQuark quark_property_iri = 0;
36 static GQuark quark_property_store_mtime = 0;
37 static GQuark quark_property_filesystem_mtime = 0;
38
39 enum {
40 PROP_0,
41 PROP_INDEXING_TREE
42 };
43
44 enum {
45 FILE_CREATED,
46 FILE_UPDATED,
47 FILE_DELETED,
48 FILE_MOVED,
49 DIRECTORY_STARTED,
50 DIRECTORY_FINISHED,
51 FINISHED,
52 LAST_SIGNAL
53 };
54
55 static guint signals[LAST_SIGNAL] = { 0 };
56
57 typedef struct {
58 TrackerIndexingTree *indexing_tree;
59 TrackerFileSystem *file_system;
60
61 TrackerSparqlConnection *connection;
62 GCancellable *cancellable;
63
64 TrackerCrawler *crawler;
65 TrackerMonitor *monitor;
66
67 GTimer *timer;
68
69 /* List of pending directory
70 * trees to get data from
71 */
72 GList *pending_index_roots;
73
74 guint stopped : 1;
75 } TrackerFileNotifierPrivate;
76
77 typedef struct {
78 TrackerFileNotifier *notifier;
79 GNode *cur_parent_node;
80
81 /* Canonical copy from priv->file_system */
82 GFile *cur_parent;
83 } DirectoryCrawledData;
84
85 static gboolean crawl_directories_start (TrackerFileNotifier *notifier);
86
87
88 G_DEFINE_TYPE (TrackerFileNotifier, tracker_file_notifier, G_TYPE_OBJECT)
89
90 static void
91 tracker_file_notifier_set_property (GObject *object,
92 guint prop_id,
93 const GValue *value,
94 GParamSpec *pspec)
95 {
96 TrackerFileNotifierPrivate *priv;
97
98 priv = TRACKER_FILE_NOTIFIER (object)->priv;
99
100 switch (prop_id) {
101 case PROP_INDEXING_TREE:
102 priv->indexing_tree = g_value_dup_object (value);
103 tracker_monitor_set_indexing_tree (priv->monitor,
104 priv->indexing_tree);
105 break;
106 default:
107 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
108 break;
109 }
110 }
111
112 static void
113 tracker_file_notifier_get_property (GObject *object,
114 guint prop_id,
115 GValue *value,
116 GParamSpec *pspec)
117 {
118 TrackerFileNotifierPrivate *priv;
119
120 priv = TRACKER_FILE_NOTIFIER (object)->priv;
121
122 switch (prop_id) {
123 case PROP_INDEXING_TREE:
124 g_value_set_object (value, priv->indexing_tree);
125 break;
126 default:
127 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
128 break;
129 }
130 }
131
132 /* Crawler signal handlers */
133 static gboolean
134 crawler_check_file_cb (TrackerCrawler *crawler,
135 GFile *file,
136 gpointer user_data)
137 {
138 TrackerFileNotifierPrivate *priv;
139
140 priv = TRACKER_FILE_NOTIFIER (user_data)->priv;
141
142 return tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
143 file,
144 G_FILE_TYPE_REGULAR);
145 }
146
147 static gboolean
148 crawler_check_directory_cb (TrackerCrawler *crawler,
149 GFile *directory,
150 gpointer user_data)
151 {
152 TrackerFileNotifierPrivate *priv;
153 GFile *root, *canonical;
154
155 priv = TRACKER_FILE_NOTIFIER (user_data)->priv;
156
157 canonical = tracker_file_system_peek_file (priv->file_system, directory);
158 root = tracker_indexing_tree_get_root (priv->indexing_tree, directory, NULL);
159
160 /* If it's a config root itself, other than the one
161 * currently processed, bypass it, it will be processed
162 * when the time arrives.
163 */
164 if (canonical &&
165 root == canonical &&
166 root != priv->pending_index_roots->data) {
167 return FALSE;
168 }
169
170 return tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
171 directory,
172 G_FILE_TYPE_DIRECTORY);
173 }
174
175 static gboolean
176 crawler_check_directory_contents_cb (TrackerCrawler *crawler,
177 GFile *parent,
178 GList *children,
179 gpointer user_data)
180 {
181 TrackerFileNotifierPrivate *priv;
182 gboolean process;
183
184 priv = TRACKER_FILE_NOTIFIER (user_data)->priv;
185 process = tracker_indexing_tree_parent_is_indexable (priv->indexing_tree,
186 parent, children);
187 if (process) {
188 TrackerDirectoryFlags parent_flags;
189 GFile *canonical;
190 gboolean add_monitor;
191
192 canonical = tracker_file_system_get_file (priv->file_system,
193 parent,
194 G_FILE_TYPE_DIRECTORY,
195 NULL);
196 tracker_indexing_tree_get_root (priv->indexing_tree,
197 canonical, &parent_flags);
198
199 add_monitor = (parent_flags & TRACKER_DIRECTORY_FLAG_MONITOR) != 0;
200
201 if (add_monitor) {
202 tracker_monitor_add (priv->monitor, canonical);
203 } else {
204 tracker_monitor_remove (priv->monitor, canonical);
205 }
206 }
207
208 return process;
209 }
210
211 static gboolean
212 file_notifier_traverse_tree_foreach (GFile *file,
213 gpointer user_data)
214 {
215 TrackerFileNotifier *notifier;
216 TrackerFileNotifierPrivate *priv;
217 guint64 *store_mtime, *disk_mtime;
218
219 notifier = user_data;
220 priv = notifier->priv;
221
222 store_mtime = tracker_file_system_get_property (priv->file_system, file,
223 quark_property_store_mtime);
224 disk_mtime = tracker_file_system_get_property (priv->file_system, file,
225 quark_property_filesystem_mtime);
226
227 if (store_mtime && !disk_mtime) {
228 /* In store but not in disk, delete */
229 g_signal_emit (notifier, signals[FILE_DELETED], 0, file);
230
231 return TRUE;
232 } else if (disk_mtime && !store_mtime) {
233 /* In disk but not in store, create */
234 g_signal_emit (notifier, signals[FILE_CREATED], 0, file);
235 } else if (store_mtime && disk_mtime &&
236 abs (*disk_mtime - *store_mtime) > 2) {
237 /* Mtime changed, update */
238 g_signal_emit (notifier, signals[FILE_UPDATED], 0, file, FALSE);
239 } else if (!store_mtime && !disk_mtime) {
240 /* what are we doing with such file? should happen rarely,
241 * only with files that we've queried, but we decided not
242 * to crawl (i.e. embedded root directories, that would
243 * be processed when that root is being crawled).
244 */
245 if (!tracker_indexing_tree_file_is_root (priv->indexing_tree, file)) {
246 gchar *uri;
247
248 uri = g_file_get_uri (file);
249 g_critical ("File '%s' has no disk nor store mtime",
250 uri);
251 g_free (uri);
252 }
253 }
254
255 return FALSE;
256 }
257
258 static void
259 file_notifier_traverse_tree (TrackerFileNotifier *notifier)
260 {
261 TrackerFileNotifierPrivate *priv;
262 GFile *current_root, *config_root;
263 TrackerDirectoryFlags flags;
264
265 priv = notifier->priv;
266 current_root = priv->pending_index_roots->data;
267 config_root = tracker_indexing_tree_get_root (priv->indexing_tree,
268 current_root, &flags);
269
270 /* Check mtime for 1) directories with the check_mtime flag
271 * and 2) directories gotten from monitor events.
272 */
273 if (config_root != current_root ||
274 flags & TRACKER_DIRECTORY_FLAG_CHECK_MTIME) {
275 tracker_file_system_traverse (priv->file_system,
276 current_root,
277 G_LEVEL_ORDER,
278 file_notifier_traverse_tree_foreach,
279 notifier);
280 }
281
282 /* We dispose regular files here, only directories are cached once crawling
283 * has completed.
284 */
285 tracker_file_system_forget_files (priv->file_system,
286 current_root,
287 G_FILE_TYPE_REGULAR);
288
289 tracker_info (" Notified files after %2.2f seconds",
290 g_timer_elapsed (priv->timer, NULL));
291
292 /* We've finished crawling/querying on the first element
293 * of the pending list, continue onto the next */
294 priv->pending_index_roots = g_list_delete_link (priv->pending_index_roots,
295 priv->pending_index_roots);
296
297 if (priv->pending_index_roots) {
298 crawl_directories_start (notifier);
299 } else {
300 g_signal_emit (notifier, signals[FINISHED], 0);
301 }
302 }
303
304 static gboolean
305 file_notifier_add_node_foreach (GNode *node,
306 gpointer user_data)
307 {
308 DirectoryCrawledData *data = user_data;
309 TrackerFileNotifierPrivate *priv;
310 GFileInfo *file_info;
311 GFile *canonical, *file;
312
313 priv = data->notifier->priv;
314 file = node->data;
315
316 if (node->parent &&
317 node->parent != data->cur_parent_node) {
318 data->cur_parent_node = node->parent;
319 data->cur_parent = tracker_file_system_peek_file (priv->file_system,
320 node->parent->data);
321 } else {
322 data->cur_parent_node = NULL;
323 data->cur_parent = NULL;
324 }
325
326 file_info = tracker_crawler_get_file_info (priv->crawler, file);
327
328 if (file_info) {
329 GFileType file_type;
330 guint64 time, *time_ptr;
331
332 file_type = g_file_info_get_file_type (file_info);
333
334 /* Intern file in filesystem */
335 canonical = tracker_file_system_get_file (priv->file_system,
336 file, file_type,
337 data->cur_parent);
338
339 time = g_file_info_get_attribute_uint64 (file_info,
340 G_FILE_ATTRIBUTE_TIME_MODIFIED);
341
342 time_ptr = g_new (guint64, 1);
343 *time_ptr = time;
344
345 tracker_file_system_set_property (priv->file_system, canonical,
346 quark_property_filesystem_mtime,
347 time_ptr);
348 }
349
350 return FALSE;
351 }
352
353 static void
354 crawler_directory_crawled_cb (TrackerCrawler *crawler,
355 GFile *directory,
356 GNode *tree,
357 guint directories_found,
358 guint directories_ignored,
359 guint files_found,
360 guint files_ignored,
361 gpointer user_data)
362 {
363 TrackerFileNotifier *notifier;
364 DirectoryCrawledData data = { 0 };
365
366 notifier = data.notifier = user_data;
367 g_node_traverse (tree,
368 G_PRE_ORDER,
369 G_TRAVERSE_ALL,
370 -1,
371 file_notifier_add_node_foreach,
372 &data);
373
374 g_signal_emit (notifier, signals[DIRECTORY_FINISHED], 0,
375 directory,
376 directories_found, directories_ignored,
377 files_found, files_ignored);
378
379 tracker_info (" Found %d directories, ignored %d directories",
380 directories_found,
381 directories_ignored);
382 tracker_info (" Found %d files, ignored %d files",
383 files_found,
384 files_ignored);
385 }
386
387 static void
388 sparql_file_query_populate (TrackerFileNotifier *notifier,
389 TrackerSparqlCursor *cursor,
390 gboolean check_root)
391 {
392 TrackerFileNotifierPrivate *priv;
393
394 priv = notifier->priv;
395
396 while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
397 GFile *file, *canonical, *root;
398 const gchar *mtime, *iri;
399 guint64 *time_ptr;
400 GError *error = NULL;
401
402 file = g_file_new_for_uri (tracker_sparql_cursor_get_string (cursor, 0, NULL));
403
404 if (check_root) {
405 /* If it's a config root itself, other than the one
406 * currently processed, bypass it, it will be processed
407 * when the time arrives.
408 */
409 canonical = tracker_file_system_peek_file (priv->file_system, file);
410 root = tracker_indexing_tree_get_root (priv->indexing_tree, file, NULL);
411
412 if (canonical &&
413 root == file &&
414 root != priv->pending_index_roots->data) {
415 g_object_unref (file);
416 continue;
417 }
418 }
419
420 canonical = tracker_file_system_get_file (priv->file_system,
421 file,
422 G_FILE_TYPE_UNKNOWN,
423 NULL);
424
425 iri = tracker_sparql_cursor_get_string (cursor, 1, NULL);
426 tracker_file_system_set_property (priv->file_system, canonical,
427 quark_property_iri,
428 g_strdup (iri));
429
430 mtime = tracker_sparql_cursor_get_string (cursor, 2, NULL);
431 time_ptr = g_new (guint64, 1);
432 *time_ptr = (guint64) tracker_string_to_date (mtime, NULL, &error);
433
434 if (error) {
435 /* This should never happen. Assume that file was modified. */
436 g_critical ("Getting store mtime: %s", error->message);
437 g_clear_error (&error);
438 *time_ptr = 0;
439 }
440
441 tracker_file_system_set_property (priv->file_system, canonical,
442 quark_property_store_mtime,
443 time_ptr);
444 g_object_unref (file);
445 }
446 }
447
448 static void
449 sparql_query_cb (GObject *object,
450 GAsyncResult *result,
451 gpointer user_data)
452 {
453 TrackerFileNotifierPrivate *priv;
454 TrackerFileNotifier *notifier;
455 TrackerSparqlCursor *cursor;
456 GError *error = NULL;
457
458 notifier = user_data;
459 priv = notifier->priv;
460 cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (object),
461 result, &error);
462
463 if (!cursor || error) {
464 g_warning ("Could not query directory elements: %s\n", error->message);
465 g_error_free (error);
466 return;
467 }
468
469 sparql_file_query_populate (notifier, cursor, TRUE);
470
471 /* Mark the directory root as queried */
472 tracker_file_system_set_property (priv->file_system,
473 priv->pending_index_roots->data,
474 quark_property_queried,
475 GUINT_TO_POINTER (TRUE));
476
477 tracker_info (" Queried files after %2.2f seconds",
478 g_timer_elapsed (priv->timer, NULL));
479
480 /* If it's also been crawled, finish operation */
481 if (tracker_file_system_get_property (priv->file_system,
482 priv->pending_index_roots->data,
483 quark_property_crawled)) {
484 file_notifier_traverse_tree (notifier);
485 }
486
487 g_object_unref (cursor);
488 }
489
490 static void
491 sparql_file_query_start (TrackerFileNotifier *notifier,
492 GFile *file,
493 GFileType file_type,
494 gboolean recursive,
495 gboolean sync)
496 {
497 TrackerFileNotifierPrivate *priv;
498 gchar *uri, *sparql;
499
500 priv = notifier->priv;
501 uri = g_file_get_uri (file);
502
503 if (file_type == G_FILE_TYPE_DIRECTORY) {
504 if (recursive) {
505 sparql = g_strdup_printf ("select ?url ?u nfo:fileLastModified(?u) "
506 "where {"
507 " ?u a nie:DataObject ; "
508 " nie:url ?url . "
509 " FILTER (?url = \"%s\" || "
510 " fn:starts-with (?url, \"%s/\")) "
511 "}", uri, uri);
512 } else {
513 sparql = g_strdup_printf ("select ?url ?u nfo:fileLastModified(?u) "
514 "where { "
515 " ?u a nie:DataObject ; "
516 " nie:url ?url . "
517 " OPTIONAL { ?u nfo:belongsToContainer ?p } . "
518 " FILTER (?url = \"%s\" || "
519 " nie:url(?p) = \"%s\") "
520 "}", uri, uri);
521 }
522 } else {
523 /* If it's a regular file, only query this item */
524 sparql = g_strdup_printf ("select ?url ?u nfo:fileLastModified(?u) "
525 "where { "
526 " ?u a nie:DataObject ; "
527 " nie:url ?url ; "
528 " nie:url \"%s\" . "
529 "}", uri);
530 }
531
532 if (sync) {
533 TrackerSparqlCursor *cursor;
534
535 cursor = tracker_sparql_connection_query (priv->connection,
536 sparql, NULL, NULL);
537 if (cursor) {
538 sparql_file_query_populate (notifier, cursor, FALSE);
539 g_object_unref (cursor);
540 }
541 } else {
542 tracker_sparql_connection_query_async (priv->connection,
543 sparql,
544 priv->cancellable,
545 sparql_query_cb,
546 notifier);
547 }
548
549 g_free (sparql);
550 g_free (uri);
551 }
552
553 static gboolean
554 crawl_directories_start (TrackerFileNotifier *notifier)
555 {
556 TrackerFileNotifierPrivate *priv = notifier->priv;
557 TrackerDirectoryFlags flags;
558 GFile *directory;
559
560 if (!priv->pending_index_roots) {
561 return FALSE;
562 }
563
564 if (priv->stopped) {
565 return FALSE;
566 }
567
568 while (priv->pending_index_roots) {
569 directory = priv->pending_index_roots->data;
570
571 tracker_indexing_tree_get_root (priv->indexing_tree,
572 directory,
573 &flags);
574
575 /* Unset crawled/queried checks on the
576 * directory, we might have requested a
577 * reindex.
578 */
579 tracker_file_system_unset_property (priv->file_system,
580 directory,
581 quark_property_crawled);
582 tracker_file_system_unset_property (priv->file_system,
583 directory,
584 quark_property_queried);
585
586 g_cancellable_reset (priv->cancellable);
587
588 if ((flags & TRACKER_DIRECTORY_FLAG_IGNORE) == 0 &&
589 tracker_crawler_start (priv->crawler,
590 directory,
591 (flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0)) {
592 gchar *uri;
593
594 sparql_file_query_start (notifier, directory,
595 G_FILE_TYPE_DIRECTORY,
596 (flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0,
597 FALSE);
598
599 g_timer_reset (priv->timer);
600 g_signal_emit (notifier, signals[DIRECTORY_STARTED], 0, directory);
601
602 uri = g_file_get_uri (directory);
603 tracker_info ("Started inspecting '%s'", uri);
604 g_free (uri);
605
606 return TRUE;
607 } else {
608 /* Emit both signals for consistency */
609 g_signal_emit (notifier, signals[DIRECTORY_STARTED], 0, directory);
610
611 if ((flags & TRACKER_DIRECTORY_FLAG_PRESERVE) == 0) {
612 g_signal_emit (notifier, signals[FILE_DELETED], 0, directory);
613 }
614
615 g_signal_emit (notifier, signals[DIRECTORY_FINISHED], 0,
616 directory, 0, 0, 0, 0);
617 }
618
619 /* Remove index root and try the next one */
620 priv->pending_index_roots = g_list_delete_link (priv->pending_index_roots,
621 priv->pending_index_roots);
622 }
623
624 g_signal_emit (notifier, signals[FINISHED], 0);
625
626 return FALSE;
627 }
628
629 static void
630 crawler_finished_cb (TrackerCrawler *crawler,
631 gboolean was_interrupted,
632 gpointer user_data)
633 {
634 TrackerFileNotifier *notifier = user_data;
635 TrackerFileNotifierPrivate *priv = notifier->priv;
636
637 tracker_info (" %s crawling files after %2.2f seconds",
638 was_interrupted ? "Stopped" : "Finished",
639 g_timer_elapsed (priv->timer, NULL));
640
641 if (!was_interrupted) {
642 GFile *directory;
643
644 directory = priv->pending_index_roots->data;
645
646 /* Mark the directory root as crawled */
647 tracker_file_system_set_property (priv->file_system, directory,
648 quark_property_crawled,
649 GUINT_TO_POINTER (TRUE));
650
651 /* If it's also been queried, finish operation */
652 if (tracker_file_system_get_property (priv->file_system,
653 directory,
654 quark_property_queried)) {
655 file_notifier_traverse_tree (notifier);
656 }
657 }
658 }
659
660 /* Monitor signal handlers */
661 static void
662 monitor_item_created_cb (TrackerMonitor *monitor,
663 GFile *file,
664 gboolean is_directory,
665 gpointer user_data)
666 {
667 TrackerFileNotifier *notifier = user_data;
668 TrackerFileNotifierPrivate *priv = notifier->priv;
669 GFileType file_type;
670 GFile *canonical;
671
672 file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
673
674 if (!tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
675 file, file_type)) {
676 /* File should not be indexed */
677 return ;
678 }
679
680 if (!is_directory) {
681 gboolean indexable;
682 GList *children;
683 GFile *parent;
684
685 parent = g_file_get_parent (file);
686
687 if (parent) {
688 children = g_list_prepend (NULL, file);
689 indexable = tracker_indexing_tree_parent_is_indexable (priv->indexing_tree,
690 parent,
691 children);
692 g_list_free (children);
693
694 if (!indexable) {
695 /* New file triggered a directory content
696 * filter, remove parent directory altogether
697 */
698 g_signal_emit (notifier, signals[FILE_DELETED], 0, parent);
699 g_object_unref (parent);
700 return;
701 }
702
703 g_object_unref (parent);
704 }
705 } else {
706 TrackerDirectoryFlags flags;
707
708 /* If config for the directory is recursive,
709 * Crawl new entire directory and add monitors
710 */
711 tracker_indexing_tree_get_root (priv->indexing_tree,
712 file, &flags);
713
714 if (flags & TRACKER_DIRECTORY_FLAG_RECURSE) {
715 canonical = tracker_file_system_get_file (priv->file_system,
716 file,
717 file_type,
718 NULL);
719 priv->pending_index_roots =
720 g_list_append (priv->pending_index_roots,
721 canonical);
722
723 crawl_directories_start (notifier);
724 return;
725 }
726 }
727
728 /* Fetch the interned copy */
729 canonical = tracker_file_system_get_file (priv->file_system,
730 file, file_type, NULL);
731
732 g_signal_emit (notifier, signals[FILE_CREATED], 0, canonical);
733
734 if (!is_directory) {
735 tracker_file_system_forget_files (priv->file_system, canonical,
736 G_FILE_TYPE_REGULAR);
737 }
738 }
739
740 static void
741 monitor_item_updated_cb (TrackerMonitor *monitor,
742 GFile *file,
743 gboolean is_directory,
744 gpointer user_data)
745 {
746 TrackerFileNotifier *notifier = user_data;
747 TrackerFileNotifierPrivate *priv = notifier->priv;
748 GFileType file_type;
749 GFile *canonical;
750
751 file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
752
753 if (!tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
754 file, file_type)) {
755 /* File should not be indexed */
756 return;
757 }
758
759 /* Fetch the interned copy */
760 canonical = tracker_file_system_get_file (priv->file_system,
761 file, file_type, NULL);
762 g_signal_emit (notifier, signals[FILE_UPDATED], 0, canonical, FALSE);
763
764 if (!is_directory) {
765 tracker_file_system_forget_files (priv->file_system, canonical,
766 G_FILE_TYPE_REGULAR);
767 }
768 }
769
770 static void
771 monitor_item_attribute_updated_cb (TrackerMonitor *monitor,
772 GFile *file,
773 gboolean is_directory,
774 gpointer user_data)
775 {
776 TrackerFileNotifier *notifier = user_data;
777 TrackerFileNotifierPrivate *priv = notifier->priv;
778 GFile *canonical;
779 GFileType file_type;
780
781 file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
782
783 if (!tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
784 file, file_type)) {
785 /* File should not be indexed */
786 return;
787 }
788
789 /* Fetch the interned copy */
790 canonical = tracker_file_system_get_file (priv->file_system,
791 file, file_type, NULL);
792 g_signal_emit (notifier, signals[FILE_UPDATED], 0, canonical, TRUE);
793
794 if (!is_directory) {
795 tracker_file_system_forget_files (priv->file_system, canonical,
796 G_FILE_TYPE_REGULAR);
797 }
798 }
799
800 static void
801 monitor_item_deleted_cb (TrackerMonitor *monitor,
802 GFile *file,
803 gboolean is_directory,
804 gpointer user_data)
805 {
806 TrackerFileNotifier *notifier = user_data;
807 TrackerFileNotifierPrivate *priv = notifier->priv;
808 GFile *canonical;
809 GFileType file_type;
810
811 file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
812
813 if (!tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
814 file, file_type)) {
815 /* File was not indexed */
816 return ;
817 }
818
819 if (!is_directory) {
820 gboolean indexable;
821 GList *children;
822 GFile *parent;
823
824 children = g_list_prepend (NULL, file);
825 parent = g_file_get_parent (file);
826
827 indexable = tracker_indexing_tree_parent_is_indexable (priv->indexing_tree,
828 parent, children);
829 g_object_unref (parent);
830 g_list_free (children);
831
832 /* note: This supposedly works, but in practice
833 * won't ever happen as we don't get monitor events
834 * from directories triggering a filter of type
835 * TRACKER_FILTER_PARENT_DIRECTORY.
836 */
837 if (!indexable) {
838 /* New file was triggering a directory content
839 * filter, reindex parent directory altogether
840 */
841 file = tracker_file_system_get_file (priv->file_system,
842 file,
843 G_FILE_TYPE_DIRECTORY,
844 NULL);
845 priv->pending_index_roots =
846 g_list_append (priv->pending_index_roots, file);
847
848 crawl_directories_start (notifier);
849 return;
850 }
851 }
852
853 /* Fetch the interned copy */
854 canonical = tracker_file_system_get_file (priv->file_system,
855 file, file_type, NULL);
856 g_signal_emit (notifier, signals[FILE_DELETED], 0, canonical);
857
858 /* Remove the file from the cache (works recursively for directories) */
859 tracker_file_system_forget_files (priv->file_system,
860 file,
861 G_FILE_TYPE_UNKNOWN);
862 }
863
864 static void
865 monitor_item_moved_cb (TrackerMonitor *monitor,
866 GFile *file,
867 GFile *other_file,
868 gboolean is_directory,
869 gboolean is_source_monitored,
870 gpointer user_data)
871 {
872 TrackerFileNotifier *notifier;
873 TrackerFileNotifierPrivate *priv;
874
875 notifier = user_data;
876 priv = notifier->priv;
877
878 if (!is_source_monitored) {
879 if (is_directory) {
880 /* Remove monitors if any */
881 tracker_monitor_remove_recursively (priv->monitor, file);
882
883 /* If should recurse, crawl other_file, as content is "new" */
884 file = tracker_file_system_get_file (priv->file_system,
885 other_file,
886 G_FILE_TYPE_DIRECTORY,
887 NULL);
888 priv->pending_index_roots =
889 g_list_append (priv->pending_index_roots, file);
890
891 crawl_directories_start (notifier);
892 }
893 /* else, file, do nothing */
894 } else {
895 gboolean source_stored, should_process_other;
896 GFileType file_type;
897 GFile *check_file;
898
899 if (is_directory) {
900 check_file = g_object_ref (file);
901 } else {
902 check_file = g_file_get_parent (file);
903 }
904
905 file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
906
907 /* If the (parent) directory is in
908 * the filesystem, file is stored
909 */
910 source_stored = (tracker_file_system_peek_file (priv->file_system,
911 check_file) != NULL);
912 should_process_other = tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
913 other_file,
914 file_type);
915 g_object_unref (check_file);
916
917 if (!source_stored) {
918 /* Destination location should be indexed as if new */
919 /* Remove monitors if any */
920 if (is_directory) {
921 tracker_monitor_remove_recursively (priv->monitor,
922 file);
923 }
924
925 if (should_process_other) {
926 gboolean dest_is_recursive;
927 TrackerDirectoryFlags flags;
928
929 tracker_indexing_tree_get_root (priv->indexing_tree, other_file, &flags);
930 dest_is_recursive = (flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0;
931
932 /* Source file was not stored, check dest file as new */
933 if (!is_directory || !dest_is_recursive) {
934 g_signal_emit (notifier, signals[FILE_CREATED], 0, other_file);
935 } else if (is_directory) {
936 /* Crawl dest directory */
937 other_file = tracker_file_system_get_file (priv->file_system,
938 other_file,
939 G_FILE_TYPE_DIRECTORY,
940 NULL);
941 priv->pending_index_roots =
942 g_list_append (priv->pending_index_roots,
943 other_file);
944
945 crawl_directories_start (notifier);
946 }
947 }
948 /* Else, do nothing else */
949 } else if (!should_process_other) {
950 /* Delete original location as it moves to be non indexable */
951 if (is_directory) {
952 tracker_monitor_remove_recursively (priv->monitor,
953 file);
954 }
955
956 g_signal_emit (notifier, signals[FILE_DELETED], 0, file);
957 } else {
958 /* Handle move */
959 if (is_directory) {
960 gboolean dest_is_recursive, source_is_recursive;
961 TrackerDirectoryFlags flags;
962
963 tracker_monitor_move (priv->monitor,
964 file, other_file);
965
966 tracker_indexing_tree_get_root (priv->indexing_tree,
967 other_file,
968 &flags);
969 dest_is_recursive = (flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0;
970
971 tracker_indexing_tree_get_root (priv->indexing_tree,
972 file, &flags);
973 source_is_recursive = (flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0;
974
975 if (source_is_recursive && !dest_is_recursive) {
976 /* A directory is being moved from a
977 * recursive location to a non-recursive
978 * one, don't do anything here, and let
979 * TrackerMinerFS handle it, see item_move().
980 */
981 } else if (!source_is_recursive && dest_is_recursive) {
982 /* crawl the folder */
983 file = tracker_file_system_get_file (priv->file_system,
984 other_file,
985 G_FILE_TYPE_DIRECTORY,
986 NULL);
987 priv->pending_index_roots =
988 g_list_append (priv->pending_index_roots,
989 file);
990
991 crawl_directories_start (notifier);
992 }
993 }
994
995 g_signal_emit (notifier, signals[FILE_MOVED], 0, file, other_file);
996 }
997 }
998 }
999
1000 /* Indexing tree signal handlers */
1001 static void
1002 indexing_tree_directory_added (TrackerIndexingTree *indexing_tree,
1003 GFile *directory,
1004 gpointer user_data)
1005 {
1006 TrackerFileNotifier *notifier = user_data;
1007 TrackerFileNotifierPrivate *priv = notifier->priv;
1008 gboolean start_crawler = FALSE;
1009 TrackerDirectoryFlags flags;
1010
1011 tracker_indexing_tree_get_root (indexing_tree, directory, &flags);
1012
1013 directory = tracker_file_system_get_file (priv->file_system, directory,
1014 G_FILE_TYPE_DIRECTORY, NULL);
1015 if (!priv->stopped &&
1016 !priv->pending_index_roots) {
1017 start_crawler = TRUE;
1018 }
1019
1020 if (!g_list_find (priv->pending_index_roots, directory)) {
1021 priv->pending_index_roots = g_list_append (priv->pending_index_roots,
1022 directory);
1023 if (start_crawler) {
1024 crawl_directories_start (notifier);
1025 }
1026 }
1027 }
1028
1029 static void
1030 indexing_tree_directory_removed (TrackerIndexingTree *indexing_tree,
1031 GFile *directory,
1032 gpointer user_data)
1033 {
1034 TrackerFileNotifier *notifier = user_data;
1035 TrackerFileNotifierPrivate *priv = notifier->priv;
1036 TrackerDirectoryFlags flags;
1037
1038 /* Flags are still valid at the moment of deletion */
1039 tracker_indexing_tree_get_root (indexing_tree, directory, &flags);
1040 directory = tracker_file_system_peek_file (priv->file_system, directory);
1041
1042 if (!directory) {
1043 /* If the dir has no canonical copy,
1044 * it wasn't even told to be indexed.
1045 */
1046 return;
1047 }
1048
1049 /* If the folder was being ignored, index/crawl it from scratch */
1050 if (flags & TRACKER_DIRECTORY_FLAG_IGNORE) {
1051 GFile *parent;
1052
1053 parent = g_file_get_parent (directory);
1054
1055 if (parent) {
1056 TrackerDirectoryFlags parent_flags;
1057
1058 tracker_indexing_tree_get_root (indexing_tree,
1059 parent,
1060 &parent_flags);
1061
1062 if (parent_flags & TRACKER_DIRECTORY_FLAG_RECURSE) {
1063 priv->pending_index_roots =
1064 g_list_append (priv->pending_index_roots,
1065 g_object_ref (directory));
1066
1067 crawl_directories_start (notifier);
1068 } else if (tracker_indexing_tree_file_is_root (indexing_tree,
1069 parent)) {
1070 g_signal_emit (notifier, signals[FILE_CREATED],
1071 0, directory);
1072 }
1073
1074 g_object_unref (parent);
1075 }
1076 return;
1077 }
1078
1079 if ((flags & TRACKER_DIRECTORY_FLAG_PRESERVE) == 0) {
1080 /* Directory needs to be deleted from the store too */
1081 g_signal_emit (notifier, signals[FILE_DELETED], 0, directory);
1082 }
1083
1084 if (priv->pending_index_roots) {
1085 gboolean start_crawler = FALSE;
1086
1087 if (directory == priv->pending_index_roots->data) {
1088 /* Directory being currently processed */
1089 tracker_crawler_stop (priv->crawler);
1090 g_cancellable_cancel (priv->cancellable);
1091 start_crawler = TRUE;
1092 }
1093
1094 priv->pending_index_roots = g_list_remove_all (priv->pending_index_roots,
1095 directory);
1096
1097 if (start_crawler && priv->pending_index_roots != NULL) {
1098 crawl_directories_start (notifier);
1099 }
1100 }
1101
1102 /* Remove monitors if any */
1103 tracker_monitor_remove_recursively (priv->monitor, directory);
1104
1105 /* Remove all files from cache */
1106 tracker_file_system_forget_files (priv->file_system, directory,
1107 G_FILE_TYPE_UNKNOWN);
1108 }
1109
1110 static void
1111 tracker_file_notifier_finalize (GObject *object)
1112 {
1113 TrackerFileNotifierPrivate *priv;
1114
1115 priv = TRACKER_FILE_NOTIFIER (object)->priv;
1116
1117 if (priv->indexing_tree) {
1118 g_object_unref (priv->indexing_tree);
1119 }
1120
1121 g_object_unref (priv->crawler);
1122 g_object_unref (priv->monitor);
1123 g_object_unref (priv->file_system);
1124 g_object_unref (priv->cancellable);
1125
1126 g_list_free (priv->pending_index_roots);
1127 g_timer_destroy (priv->timer);
1128
1129 G_OBJECT_CLASS (tracker_file_notifier_parent_class)->finalize (object);
1130 }
1131
1132 static void
1133 tracker_file_notifier_constructed (GObject *object)
1134 {
1135 TrackerFileNotifierPrivate *priv;
1136
1137 priv = TRACKER_FILE_NOTIFIER (object)->priv;
1138 g_assert (priv->indexing_tree);
1139
1140 g_signal_connect (priv->indexing_tree, "directory-added",
1141 G_CALLBACK (indexing_tree_directory_added), object);
1142 g_signal_connect (priv->indexing_tree, "directory-updated",
1143 G_CALLBACK (indexing_tree_directory_added), object);
1144 g_signal_connect (priv->indexing_tree, "directory-removed",
1145 G_CALLBACK (indexing_tree_directory_removed), object);
1146 }
1147
1148 static void
1149 tracker_file_notifier_class_init (TrackerFileNotifierClass *klass)
1150 {
1151 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1152
1153 object_class->finalize = tracker_file_notifier_finalize;
1154 object_class->set_property = tracker_file_notifier_set_property;
1155 object_class->get_property = tracker_file_notifier_get_property;
1156 object_class->constructed = tracker_file_notifier_constructed;
1157
1158 signals[FILE_CREATED] =
1159 g_signal_new ("file-created",
1160 G_TYPE_FROM_CLASS (klass),
1161 G_SIGNAL_RUN_LAST,
1162 G_STRUCT_OFFSET (TrackerFileNotifierClass,
1163 file_created),
1164 NULL, NULL,
1165 g_cclosure_marshal_VOID__OBJECT,
1166 G_TYPE_NONE,
1167 1, G_TYPE_FILE);
1168 signals[FILE_UPDATED] =
1169 g_signal_new ("file-updated",
1170 G_TYPE_FROM_CLASS (klass),
1171 G_SIGNAL_RUN_LAST,
1172 G_STRUCT_OFFSET (TrackerFileNotifierClass,
1173 file_updated),
1174 NULL, NULL,
1175 tracker_marshal_VOID__OBJECT_BOOLEAN,
1176 G_TYPE_NONE,
1177 2, G_TYPE_FILE, G_TYPE_BOOLEAN);
1178 signals[FILE_DELETED] =
1179 g_signal_new ("file-deleted",
1180 G_TYPE_FROM_CLASS (klass),
1181 G_SIGNAL_RUN_LAST,
1182 G_STRUCT_OFFSET (TrackerFileNotifierClass,
1183 file_deleted),
1184 NULL, NULL,
1185 g_cclosure_marshal_VOID__OBJECT,
1186 G_TYPE_NONE,
1187 1, G_TYPE_FILE);
1188 signals[FILE_MOVED] =
1189 g_signal_new ("file-moved",
1190 G_TYPE_FROM_CLASS (klass),
1191 G_SIGNAL_RUN_LAST,
1192 G_STRUCT_OFFSET (TrackerFileNotifierClass,
1193 file_moved),
1194 NULL, NULL,
1195 tracker_marshal_VOID__OBJECT_OBJECT,
1196 G_TYPE_NONE,
1197 2, G_TYPE_FILE, G_TYPE_FILE);
1198 signals[DIRECTORY_STARTED] =
1199 g_signal_new ("directory-started",
1200 G_TYPE_FROM_CLASS (klass),
1201 G_SIGNAL_RUN_LAST,
1202 G_STRUCT_OFFSET (TrackerFileNotifierClass,
1203 directory_started),
1204 NULL, NULL,
1205 g_cclosure_marshal_VOID__OBJECT,
1206 G_TYPE_NONE,
1207 1, G_TYPE_FILE);
1208 signals[DIRECTORY_FINISHED] =
1209 g_signal_new ("directory-finished",
1210 G_TYPE_FROM_CLASS (klass),
1211 G_SIGNAL_RUN_LAST,
1212 G_STRUCT_OFFSET (TrackerFileNotifierClass,
1213 directory_finished),
1214 NULL, NULL,
1215 tracker_marshal_VOID__OBJECT_UINT_UINT_UINT_UINT,
1216 G_TYPE_NONE,
1217 5, G_TYPE_FILE, G_TYPE_UINT,
1218 G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
1219 signals[FINISHED] =
1220 g_signal_new ("finished",
1221 G_TYPE_FROM_CLASS (klass),
1222 G_SIGNAL_RUN_LAST,
1223 G_STRUCT_OFFSET (TrackerFileNotifierClass,
1224 finished),
1225 NULL, NULL,
1226 g_cclosure_marshal_VOID__VOID,
1227 G_TYPE_NONE, 0, G_TYPE_NONE);
1228
1229 g_object_class_install_property (object_class,
1230 PROP_INDEXING_TREE,
1231 g_param_spec_object ("indexing-tree",
1232 "Indexing tree",
1233 "Indexing tree",
1234 TRACKER_TYPE_INDEXING_TREE,
1235 G_PARAM_READWRITE |
1236 G_PARAM_CONSTRUCT_ONLY));
1237 g_type_class_add_private (object_class,
1238 sizeof (TrackerFileNotifierClass));
1239
1240 /* Initialize property quarks */
1241 quark_property_crawled = g_quark_from_static_string ("tracker-property-crawled");
1242 tracker_file_system_register_property (quark_property_crawled, NULL);
1243
1244 quark_property_queried = g_quark_from_static_string ("tracker-property-queried");
1245 tracker_file_system_register_property (quark_property_queried, NULL);
1246
1247 quark_property_iri = g_quark_from_static_string ("tracker-property-iri");
1248 tracker_file_system_register_property (quark_property_iri, g_free);
1249
1250 quark_property_store_mtime = g_quark_from_static_string ("tracker-property-store-mtime");
1251 tracker_file_system_register_property (quark_property_store_mtime,
1252 g_free);
1253
1254 quark_property_filesystem_mtime = g_quark_from_static_string ("tracker-property-filesystem-mtime");
1255 tracker_file_system_register_property (quark_property_filesystem_mtime,
1256 g_free);
1257 }
1258
1259 static void
1260 tracker_file_notifier_init (TrackerFileNotifier *notifier)
1261 {
1262 TrackerFileNotifierPrivate *priv;
1263 GError *error = NULL;
1264
1265 priv = notifier->priv =
1266 G_TYPE_INSTANCE_GET_PRIVATE (notifier,
1267 TRACKER_TYPE_FILE_NOTIFIER,
1268 TrackerFileNotifierPrivate);
1269
1270 priv->connection = tracker_sparql_connection_get (NULL, &error);
1271 priv->cancellable = g_cancellable_new ();
1272
1273 if (error) {
1274 g_critical ("Could not get SPARQL connection: %s\n",
1275 error->message);
1276 g_error_free (error);
1277
1278 g_assert_not_reached ();
1279 }
1280
1281 /* Initialize filesystem and register properties */
1282 priv->file_system = tracker_file_system_new ();
1283
1284 priv->timer = g_timer_new ();
1285 priv->stopped = TRUE;
1286
1287 /* Set up crawler */
1288 priv->crawler = tracker_crawler_new ();
1289 tracker_crawler_set_file_attributes (priv->crawler,
1290 G_FILE_ATTRIBUTE_TIME_MODIFIED ","
1291 G_FILE_ATTRIBUTE_STANDARD_TYPE);
1292
1293 g_signal_connect (priv->crawler, "check-file",
1294 G_CALLBACK (crawler_check_file_cb),
1295 notifier);
1296 g_signal_connect (priv->crawler, "check-directory",
1297 G_CALLBACK (crawler_check_directory_cb),
1298 notifier);
1299 g_signal_connect (priv->crawler, "check-directory-contents",
1300 G_CALLBACK (crawler_check_directory_contents_cb),
1301 notifier);
1302 g_signal_connect (priv->crawler, "directory-crawled",
1303 G_CALLBACK (crawler_directory_crawled_cb),
1304 notifier);
1305 g_signal_connect (priv->crawler, "finished",
1306 G_CALLBACK (crawler_finished_cb),
1307 notifier);
1308
1309 /* Set up monitor */
1310 priv->monitor = tracker_monitor_new ();
1311
1312 g_signal_connect (priv->monitor, "item-created",
1313 G_CALLBACK (monitor_item_created_cb),
1314 notifier);
1315 g_signal_connect (priv->monitor, "item-updated",
1316 G_CALLBACK (monitor_item_updated_cb),
1317 notifier);
1318 g_signal_connect (priv->monitor, "item-attribute-updated",
1319 G_CALLBACK (monitor_item_attribute_updated_cb),
1320 notifier);
1321 g_signal_connect (priv->monitor, "item-deleted",
1322 G_CALLBACK (monitor_item_deleted_cb),
1323 notifier);
1324 g_signal_connect (priv->monitor, "item-moved",
1325 G_CALLBACK (monitor_item_moved_cb),
1326 notifier);
1327 }
1328
1329 TrackerFileNotifier *
1330 tracker_file_notifier_new (TrackerIndexingTree *indexing_tree)
1331 {
1332 g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (indexing_tree), NULL);
1333
1334 return g_object_new (TRACKER_TYPE_FILE_NOTIFIER,
1335 "indexing-tree", indexing_tree,
1336 NULL);
1337 }
1338
1339 gboolean
1340 tracker_file_notifier_start (TrackerFileNotifier *notifier)
1341 {
1342 TrackerFileNotifierPrivate *priv;
1343
1344 g_return_val_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier), FALSE);
1345
1346 priv = notifier->priv;
1347
1348 if (priv->stopped) {
1349 priv->stopped = FALSE;
1350
1351 if (priv->pending_index_roots) {
1352 crawl_directories_start (notifier);
1353 }
1354 }
1355
1356 return TRUE;
1357 }
1358
1359 void
1360 tracker_file_notifier_stop (TrackerFileNotifier *notifier)
1361 {
1362 TrackerFileNotifierPrivate *priv;
1363
1364 g_return_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier));
1365
1366 priv = notifier->priv;
1367
1368 if (!priv->stopped) {
1369 tracker_crawler_stop (priv->crawler);
1370 g_cancellable_cancel (priv->cancellable);
1371 priv->stopped = TRUE;
1372 }
1373 }
1374
1375 gboolean
1376 tracker_file_notifier_is_active (TrackerFileNotifier *notifier)
1377 {
1378 TrackerFileNotifierPrivate *priv;
1379
1380 g_return_val_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier), FALSE);
1381
1382 priv = notifier->priv;
1383 return priv->pending_index_roots != NULL;
1384 }
1385
1386 const gchar *
1387 tracker_file_notifier_get_file_iri (TrackerFileNotifier *notifier,
1388 GFile *file)
1389 {
1390 TrackerFileNotifierPrivate *priv;
1391 GFile *canonical;
1392 gchar *iri = NULL;
1393
1394 g_return_val_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier), NULL);
1395 g_return_val_if_fail (G_IS_FILE (file), NULL);
1396
1397 priv = notifier->priv;
1398 canonical = tracker_file_system_get_file (priv->file_system,
1399 file,
1400 G_FILE_TYPE_REGULAR,
1401 NULL);
1402 if (!canonical) {
1403 return NULL;
1404 }
1405
1406 iri = tracker_file_system_get_property (priv->file_system,
1407 canonical,
1408 quark_property_iri);
1409
1410 if (!iri) {
1411 /* Fetch data for this file synchronously */
1412 sparql_file_query_start (notifier, canonical,
1413 G_FILE_TYPE_REGULAR,
1414 FALSE, TRUE);
1415
1416 iri = tracker_file_system_get_property (priv->file_system,
1417 canonical,
1418 quark_property_iri);
1419 }
1420
1421 return iri;
1422 }