No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | ../../src/libtracker-miner/tracker-monitor.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None | |
clang-analyzer | no-output-found | ../../src/libtracker-miner/tracker-monitor.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
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 <stdlib.h>
23 #include <string.h>
24 #include <gio/gio.h>
25
26 #if defined (__OpenBSD__) || defined (__FreeBSD__) || defined (__NetBSD__) || defined (__APPLE__)
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/resource.h>
30 #define TRACKER_MONITOR_KQUEUE
31 #endif
32
33 #include "tracker-monitor.h"
34 #include "tracker-marshal.h"
35
36 #define TRACKER_MONITOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_MONITOR, TrackerMonitorPrivate))
37
38 /* If this is enabled, we are assuming that GIO is fixed so that after a CREATED
39 * event emitted after a move operation from a non-monitored path to a monitored
40 * one, a CHANGES_DONE_HINT is also emitted. This allows us to NOT send the
41 * CREATED event to upper layers until the file has been fully closed.
42 *
43 * See https://bugzilla.gnome.org/show_bug.cgi?id=640077 and
44 * https://projects.maemo.org/bugzilla/show_bug.cgi?id=219982
45 * When the upstream bugfix is integrated in GLib/GIO, we will be able to
46 * depend on an specific glib version. Until then, disable this and only
47 * enable in distros which have that patched glib.
48 **/
49 #ifdef GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED
50 #warning Assuming GLib/GIO always sends CHANGES_DONE_HINT after CREATED...
51 #endif /* GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED */
52
53 /* The life time of an item in the cache */
54 #define CACHE_LIFETIME_SECONDS 1
55
56 /* When we receive IO monitor events, we pause sending information to
57 * the indexer for a few seconds before continuing. We have to receive
58 * NO events for at least a few seconds before unpausing.
59 */
60 #define PAUSE_ON_IO_SECONDS 5
61
62 /* If this is defined, we pause the indexer when we get events. If it
63 * is not, we don't do any pausing.
64 */
65 #undef PAUSE_ON_IO
66
67 struct TrackerMonitorPrivate {
68 GHashTable *monitors;
69
70 gboolean enabled;
71
72 GType monitor_backend;
73
74 guint monitor_limit;
75 gboolean monitor_limit_warned;
76 guint monitors_ignored;
77
78 /* For FAM, the _CHANGES_DONE event is not signalled, so we
79 * have to just use the _CHANGED event instead.
80 */
81 gboolean use_changed_event;
82
83 #ifdef PAUSE_ON_IO
84 /* Timeout id for pausing when we get IO */
85 guint unpause_timeout_id;
86 #endif /* PAUSE_ON_IO */
87
88 GHashTable *pre_update;
89 GHashTable *pre_delete;
90 guint event_pairs_timeout_id;
91
92 TrackerIndexingTree *tree;
93 };
94
95 typedef struct {
96 GFile *file;
97 gchar *file_uri;
98 GFile *other_file;
99 gchar *other_file_uri;
100 gboolean is_directory;
101 GTimeVal start_time;
102 guint32 event_type;
103 gboolean expirable;
104 } EventData;
105
106 enum {
107 ITEM_CREATED,
108 ITEM_UPDATED,
109 ITEM_ATTRIBUTE_UPDATED,
110 ITEM_DELETED,
111 ITEM_MOVED,
112 LAST_SIGNAL
113 };
114
115 enum {
116 PROP_0,
117 PROP_ENABLED
118 };
119
120 static void tracker_monitor_finalize (GObject *object);
121 static void tracker_monitor_set_property (GObject *object,
122 guint prop_id,
123 const GValue *value,
124 GParamSpec *pspec);
125 static void tracker_monitor_get_property (GObject *object,
126 guint prop_id,
127 GValue *value,
128 GParamSpec *pspec);
129 static guint get_kqueue_limit (void);
130 static guint get_inotify_limit (void);
131 static GFileMonitor * directory_monitor_new (TrackerMonitor *monitor,
132 GFile *file);
133 static void directory_monitor_cancel (GFileMonitor *dir_monitor);
134
135
136 static void event_data_free (gpointer data);
137 static void emit_signal_for_event (TrackerMonitor *monitor,
138 EventData *event_data);
139 static gboolean monitor_cancel_recursively (TrackerMonitor *monitor,
140 GFile *file);
141
142 static guint signals[LAST_SIGNAL] = { 0, };
143
144 G_DEFINE_TYPE(TrackerMonitor, tracker_monitor, G_TYPE_OBJECT)
145
146 static void
147 tracker_monitor_class_init (TrackerMonitorClass *klass)
148 {
149 GObjectClass *object_class;
150
151 object_class = G_OBJECT_CLASS (klass);
152
153 object_class->finalize = tracker_monitor_finalize;
154 object_class->set_property = tracker_monitor_set_property;
155 object_class->get_property = tracker_monitor_get_property;
156
157 signals[ITEM_CREATED] =
158 g_signal_new ("item-created",
159 G_TYPE_FROM_CLASS (klass),
160 G_SIGNAL_RUN_LAST,
161 0,
162 NULL, NULL,
163 tracker_marshal_VOID__OBJECT_BOOLEAN,
164 G_TYPE_NONE,
165 2,
166 G_TYPE_OBJECT,
167 G_TYPE_BOOLEAN);
168 signals[ITEM_UPDATED] =
169 g_signal_new ("item-updated",
170 G_TYPE_FROM_CLASS (klass),
171 G_SIGNAL_RUN_LAST,
172 0,
173 NULL, NULL,
174 tracker_marshal_VOID__OBJECT_BOOLEAN,
175 G_TYPE_NONE,
176 2,
177 G_TYPE_OBJECT,
178 G_TYPE_BOOLEAN);
179 signals[ITEM_ATTRIBUTE_UPDATED] =
180 g_signal_new ("item-attribute-updated",
181 G_TYPE_FROM_CLASS (klass),
182 G_SIGNAL_RUN_LAST,
183 0,
184 NULL, NULL,
185 tracker_marshal_VOID__OBJECT_BOOLEAN,
186 G_TYPE_NONE,
187 2,
188 G_TYPE_OBJECT,
189 G_TYPE_BOOLEAN);
190 signals[ITEM_DELETED] =
191 g_signal_new ("item-deleted",
192 G_TYPE_FROM_CLASS (klass),
193 G_SIGNAL_RUN_LAST,
194 0,
195 NULL, NULL,
196 tracker_marshal_VOID__OBJECT_BOOLEAN,
197 G_TYPE_NONE,
198 2,
199 G_TYPE_OBJECT,
200 G_TYPE_BOOLEAN);
201 signals[ITEM_MOVED] =
202 g_signal_new ("item-moved",
203 G_TYPE_FROM_CLASS (klass),
204 G_SIGNAL_RUN_LAST,
205 0,
206 NULL, NULL,
207 tracker_marshal_VOID__OBJECT_OBJECT_BOOLEAN_BOOLEAN,
208 G_TYPE_NONE,
209 4,
210 G_TYPE_OBJECT,
211 G_TYPE_OBJECT,
212 G_TYPE_BOOLEAN,
213 G_TYPE_BOOLEAN);
214
215 g_object_class_install_property (object_class,
216 PROP_ENABLED,
217 g_param_spec_boolean ("enabled",
218 "Enabled",
219 "Enabled",
220 TRUE,
221 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
222
223 g_type_class_add_private (object_class, sizeof (TrackerMonitorPrivate));
224 }
225
226 static void
227 tracker_monitor_init (TrackerMonitor *object)
228 {
229 TrackerMonitorPrivate *priv;
230 GFile *file;
231 GFileMonitor *monitor;
232 const gchar *name;
233 GError *error = NULL;
234
235 object->priv = TRACKER_MONITOR_GET_PRIVATE (object);
236
237 priv = object->priv;
238
239 /* By default we enable monitoring */
240 priv->enabled = TRUE;
241
242 /* Create monitors table for this module */
243 priv->monitors =
244 g_hash_table_new_full (g_file_hash,
245 (GEqualFunc) g_file_equal,
246 (GDestroyNotify) g_object_unref,
247 (GDestroyNotify) directory_monitor_cancel);
248
249 priv->pre_update =
250 g_hash_table_new_full (g_file_hash,
251 (GEqualFunc) g_file_equal,
252 (GDestroyNotify) g_object_unref,
253 event_data_free);
254 priv->pre_delete =
255 g_hash_table_new_full (g_file_hash,
256 (GEqualFunc) g_file_equal,
257 (GDestroyNotify) g_object_unref,
258 event_data_free);
259
260 /* For the first monitor we get the type and find out if we
261 * are using inotify, FAM, polling, etc.
262 */
263 file = g_file_new_for_path (g_get_home_dir ());
264 monitor = g_file_monitor_directory (file,
265 G_FILE_MONITOR_WATCH_MOUNTS,
266 NULL,
267 &error);
268
269 if (error) {
270 g_critical ("Could not create sample directory monitor: %s", error->message);
271 g_error_free (error);
272
273 /* Guessing limit... */
274 priv->monitor_limit = 100;
275 } else {
276 priv->monitor_backend = G_OBJECT_TYPE (monitor);
277
278 /* We use the name because the type itself is actually
279 * private and not available publically. Note this is
280 * subject to change, but unlikely of course.
281 */
282 name = g_type_name (priv->monitor_backend);
283
284 /* Set limits based on backend... */
285 if (strcmp (name, "GInotifyDirectoryMonitor") == 0) {
286 /* Using inotify */
287 g_message ("Monitor backend is Inotify");
288
289 /* Setting limit based on kernel
290 * settings in /proc...
291 */
292 priv->monitor_limit = get_inotify_limit ();
293
294 /* We don't use 100% of the monitors, we allow other
295 * applications to have at least 500 or so to use
296 * between them selves. This only
297 * applies to inotify because it is a
298 * user shared resource.
299 */
300 priv->monitor_limit -= 500;
301
302 /* Make sure we don't end up with a
303 * negative maximum.
304 */
305 priv->monitor_limit = MAX (priv->monitor_limit, 0);
306 }
307 else if (strcmp (name, "GKqueueDirectoryMonitor") == 0) {
308 /* Using kqueue(2) */
309 g_message ("Monitor backend is kqueue");
310
311 priv->monitor_limit = get_kqueue_limit ();
312 }
313 else if (strcmp (name, "GFamDirectoryMonitor") == 0) {
314 /* Using Fam */
315 g_message ("Monitor backend is Fam");
316
317 /* Setting limit to an arbitary limit
318 * based on testing
319 */
320 priv->monitor_limit = 400;
321 priv->use_changed_event = TRUE;
322 }
323 else if (strcmp (name, "GFenDirectoryMonitor") == 0) {
324 /* Using Fen, what is this? */
325 g_message ("Monitor backend is Fen");
326
327 /* Guessing limit... */
328 priv->monitor_limit = 8192;
329 }
330 else if (strcmp (name, "GWin32DirectoryMonitor") == 0) {
331 /* Using Windows */
332 g_message ("Monitor backend is Windows");
333
334 /* Guessing limit... */
335 priv->monitor_limit = 8192;
336 }
337 else {
338 /* Unknown */
339 g_warning ("Monitor backend:'%s' is unknown, we have no limits "
340 "in place because we don't know what we are dealing with!",
341 name);
342
343 /* Guessing limit... */
344 priv->monitor_limit = 100;
345 }
346
347 g_file_monitor_cancel (monitor);
348 g_object_unref (monitor);
349 }
350
351 g_object_unref (file);
352 g_message ("Monitor limit is %d", priv->monitor_limit);
353 }
354
355 static void
356 tracker_monitor_finalize (GObject *object)
357 {
358 TrackerMonitorPrivate *priv;
359
360 priv = TRACKER_MONITOR_GET_PRIVATE (object);
361
362 #ifdef PAUSE_ON_IO
363 if (priv->unpause_timeout_id) {
364 g_source_remove (priv->unpause_timeout_id);
365 }
366 #endif /* PAUSE_ON_IO */
367
368 if (priv->event_pairs_timeout_id) {
369 g_source_remove (priv->event_pairs_timeout_id);
370 }
371
372 g_hash_table_unref (priv->pre_update);
373 g_hash_table_unref (priv->pre_delete);
374 g_hash_table_unref (priv->monitors);
375
376 G_OBJECT_CLASS (tracker_monitor_parent_class)->finalize (object);
377 }
378
379 static void
380 tracker_monitor_set_property (GObject *object,
381 guint prop_id,
382 const GValue *value,
383 GParamSpec *pspec)
384 {
385 switch (prop_id) {
386 case PROP_ENABLED:
387 tracker_monitor_set_enabled (TRACKER_MONITOR (object),
388 g_value_get_boolean (value));
389 break;
390
391 default:
392 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
393 }
394 }
395
396 static void
397 tracker_monitor_get_property (GObject *object,
398 guint prop_id,
399 GValue *value,
400 GParamSpec *pspec)
401 {
402 TrackerMonitorPrivate *priv;
403
404 priv = TRACKER_MONITOR_GET_PRIVATE (object);
405
406 switch (prop_id) {
407 case PROP_ENABLED:
408 g_value_set_boolean (value, priv->enabled);
409 break;
410
411 default:
412 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
413 }
414 }
415
416 static guint
417 get_kqueue_limit (void)
418 {
419 guint limit = 400;
420
421 #ifdef TRACKER_MONITOR_KQUEUE
422 struct rlimit rl;
423 if (getrlimit (RLIMIT_NOFILE, &rl) == 0)
424 limit = (rl.rlim_cur * 90) / 100;
425 #endif /* TRACKER_MONITOR_KQUEUE */
426
427 return limit;
428 }
429
430 static guint
431 get_inotify_limit (void)
432 {
433 GError *error = NULL;
434 const gchar *filename;
435 gchar *contents = NULL;
436 guint limit;
437
438 filename = "/proc/sys/fs/inotify/max_user_watches";
439
440 if (!g_file_get_contents (filename,
441 &contents,
442 NULL,
443 &error)) {
444 g_warning ("Couldn't get INotify monitor limit from:'%s', %s",
445 filename,
446 error ? error->message : "no error given");
447 g_clear_error (&error);
448
449 /* Setting limit to an arbitary limit */
450 limit = 8192;
451 } else {
452 limit = atoi (contents);
453 g_free (contents);
454 }
455
456 return limit;
457 }
458
459 #ifdef PAUSE_ON_IO
460
461 static gboolean
462 unpause_cb (gpointer data)
463 {
464 TrackerMonitor *monitor;
465
466 monitor = data;
467
468 g_message ("Resuming indexing now we have stopped "
469 "receiving monitor events for %d seconds",
470 PAUSE_ON_IO_SECONDS);
471
472 monitor->priv->unpause_timeout_id = 0;
473 tracker_status_set_is_paused_for_io (FALSE);
474
475 return FALSE;
476 }
477
478 #endif /* PAUSE_ON_IO */
479
480 static gboolean
481 check_is_directory (TrackerMonitor *monitor,
482 GFile *file)
483 {
484 GFileType file_type;
485
486 file_type = g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL);
487
488 if (file_type == G_FILE_TYPE_DIRECTORY)
489 return TRUE;
490
491 if (file_type == G_FILE_TYPE_UNKNOWN) {
492 /* Whatever it was, it's gone. Check the monitors
493 * hashtable to know whether it was a directory
494 * we knew about
495 */
496 if (g_hash_table_lookup (monitor->priv->monitors, file) != NULL)
497 return TRUE;
498 }
499
500 return FALSE;
501 }
502
503 static EventData *
504 event_data_new (GFile *file,
505 GFile *other_file,
506 gboolean is_directory,
507 guint32 event_type)
508 {
509 EventData *event;
510 GTimeVal now;
511
512 event = g_slice_new0 (EventData);
513 g_get_current_time (&now);
514
515 event->file = g_object_ref (file);
516 event->file_uri = g_file_get_uri (file);
517 if (other_file) {
518 event->other_file = g_object_ref (other_file);
519 event->other_file_uri = g_file_get_uri (other_file);
520 } else {
521 event->other_file = NULL;
522 event->other_file_uri = NULL;
523 }
524 event->is_directory = is_directory;
525 event->start_time = now;
526 event->event_type = event_type;
527 /* Always expirable when created */
528 event->expirable = TRUE;
529
530 return event;
531 }
532
533 static void
534 event_data_free (gpointer data)
535 {
536 EventData *event;
537
538 event = data;
539
540 g_object_unref (event->file);
541 g_free (event->file_uri);
542 if (event->other_file) {
543 g_object_unref (event->other_file);
544 g_free (event->other_file_uri);
545 }
546 g_slice_free (EventData, data);
547 }
548
549 gboolean
550 tracker_monitor_move (TrackerMonitor *monitor,
551 GFile *old_file,
552 GFile *new_file)
553 {
554 GHashTableIter iter;
555 GHashTable *new_monitors;
556 gchar *old_prefix;
557 gpointer iter_file, iter_file_monitor;
558 guint items_moved = 0;
559
560 /* So this is tricky. What we have to do is:
561 *
562 * 1) Add all monitors for the new_file directory hierarchy
563 * 2) Then remove the monitors for old_file
564 *
565 * This order is necessary because inotify can reuse watch
566 * descriptors, and libinotify will remove handles
567 * asynchronously on IN_IGNORE, so the opposite sequence
568 * may possibly remove valid, just added, monitors.
569 */
570 new_monitors = g_hash_table_new_full (g_file_hash,
571 (GEqualFunc) g_file_equal,
572 (GDestroyNotify) g_object_unref,
573 NULL);
574 old_prefix = g_file_get_path (old_file);
575
576 /* Find out which subdirectories should have a file monitor added */
577 g_hash_table_iter_init (&iter, monitor->priv->monitors);
578 while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
579 GFile *f;
580 gchar *old_path, *new_path;
581 gchar *new_prefix;
582 gchar *p;
583
584 if (!g_file_has_prefix (iter_file, old_file) &&
585 !g_file_equal (iter_file, old_file)) {
586 continue;
587 }
588
589 old_path = g_file_get_path (iter_file);
590 p = strstr (old_path, old_prefix);
591
592 if (!p || strcmp (p, old_prefix) == 0) {
593 g_free (old_path);
594 continue;
595 }
596
597 /* Move to end of prefix */
598 p += strlen (old_prefix) + 1;
599
600 /* Check this is not the end of the string */
601 if (*p == '\0') {
602 g_free (old_path);
603 continue;
604 }
605
606 new_prefix = g_file_get_path (new_file);
607 new_path = g_build_path (G_DIR_SEPARATOR_S, new_prefix, p, NULL);
608 g_free (new_prefix);
609
610 f = g_file_new_for_path (new_path);
611 g_free (new_path);
612
613 if (!g_hash_table_lookup (new_monitors, f)) {
614 g_hash_table_insert (new_monitors, f, GINT_TO_POINTER (1));
615 } else {
616 g_object_unref (f);
617 }
618
619 g_free (old_path);
620 items_moved++;
621 }
622
623 /* Add a new monitor for the top level directory */
624 tracker_monitor_add (monitor, new_file);
625
626 /* Add a new monitor for all subdirectories */
627 g_hash_table_iter_init (&iter, new_monitors);
628 while (g_hash_table_iter_next (&iter, &iter_file, NULL)) {
629 tracker_monitor_add (monitor, iter_file);
630 g_hash_table_iter_remove (&iter);
631 }
632
633 /* Remove the monitor for the old top level directory hierarchy */
634 tracker_monitor_remove_recursively (monitor, old_file);
635
636 g_hash_table_unref (new_monitors);
637 g_free (old_prefix);
638
639 return items_moved > 0;
640 }
641
642 static const gchar *
643 monitor_event_to_string (GFileMonitorEvent event_type)
644 {
645 switch (event_type) {
646 case G_FILE_MONITOR_EVENT_CHANGED:
647 return "G_FILE_MONITOR_EVENT_CHANGED";
648 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
649 return "G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT";
650 case G_FILE_MONITOR_EVENT_DELETED:
651 return "G_FILE_MONITOR_EVENT_DELETED";
652 case G_FILE_MONITOR_EVENT_CREATED:
653 return "G_FILE_MONITOR_EVENT_CREATED";
654 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
655 return "G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED";
656 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
657 return "G_FILE_MONITOR_EVENT_PRE_UNMOUNT";
658 case G_FILE_MONITOR_EVENT_UNMOUNTED:
659 return "G_FILE_MONITOR_EVENT_UNMOUNTED";
660 case G_FILE_MONITOR_EVENT_MOVED:
661 return "G_FILE_MONITOR_EVENT_MOVED";
662 }
663
664 return "unknown";
665 }
666
667 static void
668 emit_signal_for_event (TrackerMonitor *monitor,
669 EventData *event_data)
670 {
671 switch (event_data->event_type) {
672 case G_FILE_MONITOR_EVENT_CREATED:
673 g_debug ("Emitting ITEM_CREATED for (%s) '%s'",
674 event_data->is_directory ? "DIRECTORY" : "FILE",
675 event_data->file_uri);
676 g_signal_emit (monitor,
677 signals[ITEM_CREATED], 0,
678 event_data->file,
679 event_data->is_directory);
680 /* Note that if the created item is a Directory, we must NOT
681 * add automatically a new monitor. */
682 break;
683
684 case G_FILE_MONITOR_EVENT_CHANGED:
685 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
686 g_debug ("Emitting ITEM_UPDATED for (%s) '%s'",
687 event_data->is_directory ? "DIRECTORY" : "FILE",
688 event_data->file_uri);
689 g_signal_emit (monitor,
690 signals[ITEM_UPDATED], 0,
691 event_data->file,
692 event_data->is_directory);
693 break;
694
695 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
696 g_debug ("Emitting ITEM_ATTRIBUTE_UPDATED for (%s) '%s'",
697 event_data->is_directory ? "DIRECTORY" : "FILE",
698 event_data->file_uri);
699 g_signal_emit (monitor,
700 signals[ITEM_ATTRIBUTE_UPDATED], 0,
701 event_data->file,
702 event_data->is_directory);
703 break;
704
705 case G_FILE_MONITOR_EVENT_DELETED:
706 g_debug ("Emitting ITEM_DELETED for (%s) '%s'",
707 event_data->is_directory ? "DIRECTORY" : "FILE",
708 event_data->file_uri);
709 /* Remove monitors recursively */
710 if (event_data->is_directory) {
711 tracker_monitor_remove_recursively (monitor,
712 event_data->file);
713 }
714 g_signal_emit (monitor,
715 signals[ITEM_DELETED], 0,
716 event_data->file,
717 event_data->is_directory);
718 break;
719
720 case G_FILE_MONITOR_EVENT_MOVED:
721 /* Note that in any case we should be moving the monitors
722 * here to the new place, as the new place may be ignored.
723 * We should leave this to the upper layers. But one thing
724 * we must do is actually CANCEL all these monitors. */
725 if (event_data->is_directory) {
726 monitor_cancel_recursively (monitor,
727 event_data->file);
728 }
729
730 /* Try to avoid firing up events for ignored files */
731 if (monitor->priv->tree &&
732 !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
733 event_data->file,
734 (event_data->is_directory ?
735 G_FILE_TYPE_DIRECTORY :
736 G_FILE_TYPE_REGULAR))) {
737 g_debug ("Emitting ITEM_UPDATED for %s (%s) from "
738 "a move event, source is not indexable",
739 event_data->other_file_uri,
740 event_data->is_directory ? "DIRECTORY" : "FILE");
741 g_signal_emit (monitor,
742 signals[ITEM_UPDATED], 0,
743 event_data->other_file,
744 event_data->is_directory);
745 } else if (monitor->priv->tree &&
746 !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
747 event_data->other_file,
748 (event_data->is_directory ?
749 G_FILE_TYPE_DIRECTORY :
750 G_FILE_TYPE_REGULAR))) {
751 g_debug ("Emitting ITEM_DELETED for %s (%s) from "
752 "a move event, destination is not indexable",
753 event_data->file_uri,
754 event_data->is_directory ? "DIRECTORY" : "FILE");
755 g_signal_emit (monitor,
756 signals[ITEM_DELETED], 0,
757 event_data->file,
758 event_data->is_directory);
759 } else {
760 g_debug ("Emitting ITEM_MOVED for (%s) '%s'->'%s'",
761 event_data->is_directory ? "DIRECTORY" : "FILE",
762 event_data->file_uri,
763 event_data->other_file_uri);
764 g_signal_emit (monitor,
765 signals[ITEM_MOVED], 0,
766 event_data->file,
767 event_data->other_file,
768 event_data->is_directory,
769 TRUE);
770 }
771
772 break;
773
774 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
775 case G_FILE_MONITOR_EVENT_UNMOUNTED:
776 /* Do nothing */
777 break;
778 }
779 }
780
781 static void
782 event_pairs_process_in_ht (TrackerMonitor *monitor,
783 GHashTable *ht,
784 GTimeVal *now)
785 {
786 GHashTableIter iter;
787 gpointer key, value;
788 GList *expired_events = NULL;
789 GList *l;
790
791 /* Start iterating the HT of events, and see if any of them was expired.
792 * If so, STEAL the item from the HT, add it in an auxiliary list, and
793 * once the whole HT has been iterated, emit the signals for the stolen
794 * items. If the signal is emitted WHILE iterating the HT, we may end up
795 * with some upper layer action modifying the HT we are iterating, and
796 * that is not good. */
797 g_hash_table_iter_init (&iter, ht);
798 while (g_hash_table_iter_next (&iter, &key, &value)) {
799 EventData *event_data = value;
800 glong seconds;
801
802 /* If event is not yet expirable, keep it */
803 if (!event_data->expirable)
804 continue;
805
806 /* If event is expirable, but didn't expire yet, keep it */
807 seconds = now->tv_sec - event_data->start_time.tv_sec;
808 if (seconds < 2)
809 continue;
810
811 g_debug ("Event '%s' for URI '%s' has timed out (%ld seconds have elapsed)",
812 monitor_event_to_string (event_data->event_type),
813 event_data->file_uri,
814 seconds);
815 /* STEAL the item from the HT, so that disposal methods
816 * for key and value are not called. */
817 g_hash_table_iter_steal (&iter);
818 /* Unref the key, as no longer needed */
819 g_object_unref (key);
820 /* Add the expired event to our temp list */
821 expired_events = g_list_append (expired_events, event_data);
822 }
823
824 for (l = expired_events; l; l = g_list_next (l)) {
825 /* Emit signal for the expired event */
826 emit_signal_for_event (monitor, l->data);
827 /* And dispose the event data */
828 event_data_free (l->data);
829 }
830 g_list_free (expired_events);
831 }
832
833 static gboolean
834 event_pairs_timeout_cb (gpointer user_data)
835 {
836 TrackerMonitor *monitor;
837 GTimeVal now;
838
839 monitor = user_data;
840 g_get_current_time (&now);
841
842 /* Process PRE-UPDATE hash table */
843 event_pairs_process_in_ht (monitor, monitor->priv->pre_update, &now);
844
845 /* Process PRE-DELETE hash table */
846 event_pairs_process_in_ht (monitor, monitor->priv->pre_delete, &now);
847
848 if (g_hash_table_size (monitor->priv->pre_update) > 0 ||
849 g_hash_table_size (monitor->priv->pre_delete) > 0) {
850 return TRUE;
851 }
852
853 g_debug ("No more events to pair");
854 monitor->priv->event_pairs_timeout_id = 0;
855 return FALSE;
856 }
857
858 static void
859 monitor_event_file_created (TrackerMonitor *monitor,
860 GFile *file)
861 {
862 EventData *new_event;
863
864 /* - When a G_FILE_MONITOR_EVENT_CREATED(A) is received,
865 * -- Add it to the cache, replacing any previous element
866 * (shouldn't be any)
867 */
868 new_event = event_data_new (file,
869 NULL,
870 FALSE,
871 G_FILE_MONITOR_EVENT_CREATED);
872
873 /* If we know that there always must be a CHANGES_DONE_HINT
874 * emitted after a CREATED on a file event, set the event
875 * as non-expirable. Will be set expirable when CHANGES_DONE_HINT
876 * is actually received */
877 #ifdef GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED
878 new_event->expirable = FALSE;
879 #endif /* GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED */
880
881 g_hash_table_replace (monitor->priv->pre_update,
882 g_object_ref (file),
883 new_event);
884 }
885
886 static void
887 monitor_event_file_changed (TrackerMonitor *monitor,
888 GFile *file)
889 {
890 EventData *previous_update_event_data;
891
892 /* Get previous event data, if any */
893 previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, file);
894
895 /* If use_changed_event, treat as an ATTRIBUTE_CHANGED. Otherwise,
896 * assume there will be a CHANGES_DONE_HINT afterwards... */
897 if (!monitor->priv->use_changed_event) {
898 /* Process the CHANGED event knowing that there will be a CHANGES_DONE_HINT */
899 if (previous_update_event_data) {
900 if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) {
901 /* If there is a previous ATTRIBUTE_CHANGED still not notified,
902 * remove it, as we know there will be a CHANGES_DONE_HINT afterwards
903 */
904 g_hash_table_remove (monitor->priv->pre_update, file);
905 } else if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_CREATED) {
906 #ifdef GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED
907 /* If we got a CHANGED event before the CREATED was expired,
908 * set the CREATED as not expirable, as we expect a CHANGES_DONE_HINT
909 * afterwards. */
910 previous_update_event_data->expirable = FALSE;
911 #endif /* GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED */
912 }
913 }
914 return;
915 }
916
917 /* For FAM-based monitors, there won't be a CHANGES_DONE_HINT, so use the CHANGED
918 * events. */
919
920 if (!previous_update_event_data) {
921 /* If no previous one, insert it */
922 g_hash_table_insert (monitor->priv->pre_update,
923 g_object_ref (file),
924 event_data_new (file,
925 NULL,
926 FALSE,
927 G_FILE_MONITOR_EVENT_CHANGED));
928 return;
929 }
930
931 if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) {
932 /* Replace the previous ATTRIBUTE_CHANGED event with a CHANGED one. */
933 g_hash_table_replace (monitor->priv->pre_update,
934 g_object_ref (file),
935 event_data_new (file,
936 NULL,
937 FALSE,
938 G_FILE_MONITOR_EVENT_CHANGED));
939 } else {
940 /* Update the start_time of the previous one */
941 g_get_current_time (&(previous_update_event_data->start_time));
942 }
943 }
944
945 static void
946 monitor_event_file_attribute_changed (TrackerMonitor *monitor,
947 GFile *file)
948 {
949 EventData *previous_update_event_data;
950
951 /* Get previous event data, if any */
952 previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, file);
953 if (!previous_update_event_data) {
954 /* If no previous one, insert it */
955 g_hash_table_insert (monitor->priv->pre_update,
956 g_object_ref (file),
957 event_data_new (file,
958 NULL,
959 FALSE,
960 G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED));
961 return;
962 }
963
964 if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) {
965 /* Update the start_time of the previous one, if it is an ATTRIBUTE_CHANGED
966 * event. */
967 g_get_current_time (&(previous_update_event_data->start_time));
968
969 /* No need to update event time in CREATED, as these events
970 * only expire when there is a CHANGES_DONE_HINT.
971 */
972 }
973 }
974
975 static void
976 monitor_event_file_changes_done (TrackerMonitor *monitor,
977 GFile *file)
978 {
979 EventData *previous_update_event_data;
980
981 /* Get previous event data, if any */
982 previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, file);
983 if (!previous_update_event_data) {
984 /* Insert new update item in cache */
985 g_hash_table_insert (monitor->priv->pre_update,
986 g_object_ref (file),
987 event_data_new (file,
988 NULL,
989 FALSE,
990 G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT));
991 return;
992 }
993
994 /* Refresh event timer, and make sure the event is now set as expirable */
995 g_get_current_time (&(previous_update_event_data->start_time));
996 previous_update_event_data->expirable = TRUE;
997 }
998
999 static void
1000 monitor_event_file_deleted (TrackerMonitor *monitor,
1001 GFile *file)
1002 {
1003 EventData *new_event;
1004 EventData *previous_update_event_data;
1005
1006 /* Get previous event data, if any */
1007 previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, file);
1008
1009 /* Remove any previous pending event (if blacklisting enabled, there may be some) */
1010 if (previous_update_event_data) {
1011 GFileMonitorEvent previous_update_event_type;
1012
1013 previous_update_event_type = previous_update_event_data->event_type;
1014 g_hash_table_remove (monitor->priv->pre_update, file);
1015
1016 if (previous_update_event_type == G_FILE_MONITOR_EVENT_CREATED) {
1017 /* Oh, oh, oh, we got a previous CREATED event waiting in the event
1018 * cache... so we cancel it with the DELETED and don't notify anything */
1019 return;
1020 }
1021 /* else, keep on notifying the event */
1022 }
1023
1024 new_event = event_data_new (file,
1025 NULL,
1026 FALSE,
1027 G_FILE_MONITOR_EVENT_DELETED);
1028 emit_signal_for_event (monitor, new_event);
1029 event_data_free (new_event);
1030 }
1031
1032 static void
1033 monitor_event_file_moved (TrackerMonitor *monitor,
1034 GFile *src_file,
1035 GFile *dst_file)
1036 {
1037 EventData *new_event;
1038 EventData *previous_update_event_data;
1039
1040 /* Get previous event data, if any */
1041 previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, src_file);
1042
1043 /* Some event-merging that can also be enabled if doing blacklisting, as we are
1044 * queueing the CHANGES_DONE_HINT in the cache :
1045 * (a) CREATED(A) + MOVED(A->B) = CREATED (B)
1046 * (b) UPDATED(A) + MOVED(A->B) = MOVED(A->B) + UPDATED(B)
1047 * (c) ATTR_UPDATED(A) + MOVED(A->B) = MOVED(A->B) + UPDATED(B)
1048 * Notes:
1049 * - In case (a), the CREATED event is queued in the cache.
1050 * - In case (a), note that B may already exist before, so instead of a CREATED
1051 * we should be issuing an UPDATED instead... we don't do it as at the end we
1052 * don't really mind, and we save a call to g_file_query_exists().
1053 * - In case (b), the MOVED event is emitted right away, while the UPDATED
1054 * one is queued in the cache.
1055 * - In case (c), we issue an UPDATED instead of ATTR_UPDATED, because there may
1056 * already be another UPDATED or CREATED event for B, so we make sure in this
1057 * way that not only attributes get checked at the end.
1058 * */
1059 if (previous_update_event_data) {
1060 if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_CREATED) {
1061 /* (a) CREATED(A) + MOVED(A->B) = UPDATED (B)
1062 *
1063 * Oh, oh, oh, we got a previous created event
1064 * waiting in the event cache... so we remove it, and we
1065 * convert the MOVE event into a UPDATED(other_file),
1066 *
1067 * It is UPDATED instead of CREATED because the destination
1068 * file could probably exist, and mistakenly reporting
1069 * a CREATED event there can trick the monitor event
1070 * compression (for example, if we get a DELETED right after,
1071 * both CREATED/DELETED events turn into NOOP, so a no
1072 * longer existing file didn't trigger a DELETED.
1073 *
1074 * Instead, it is safer to just issue UPDATED, this event
1075 * wouldn't get compressed, and CREATED==UPDATED to the
1076 * miners' eyes.
1077 */
1078 g_hash_table_remove (monitor->priv->pre_update, src_file);
1079 g_hash_table_replace (monitor->priv->pre_update,
1080 g_object_ref (dst_file),
1081 event_data_new (dst_file,
1082 NULL,
1083 FALSE,
1084 G_FILE_MONITOR_EVENT_CHANGED));
1085
1086 /* Do not notify the moved event now */
1087 return;
1088 }
1089
1090 /* (b) UPDATED(A) + MOVED(A->B) = MOVED(A->B) + UPDATED(B)
1091 * (c) ATTR_UPDATED(A) + MOVED(A->B) = MOVED(A->B) + UPDATED(B)
1092 *
1093 * We setup here the UPDATED(B) event, added to the cache */
1094 g_hash_table_replace (monitor->priv->pre_update,
1095 g_object_ref (dst_file),
1096 event_data_new (dst_file,
1097 NULL,
1098 FALSE,
1099 G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT));
1100 /* Remove previous event */
1101 g_hash_table_remove (monitor->priv->pre_update, src_file);
1102
1103 /* And keep on notifying the MOVED event */
1104 }
1105
1106 new_event = event_data_new (src_file,
1107 dst_file,
1108 FALSE,
1109 G_FILE_MONITOR_EVENT_MOVED);
1110 emit_signal_for_event (monitor, new_event);
1111 event_data_free (new_event);
1112 }
1113
1114 static void
1115 monitor_event_directory_created_or_changed (TrackerMonitor *monitor,
1116 GFile *dir,
1117 GFileMonitorEvent event_type)
1118 {
1119 EventData *previous_delete_event_data;
1120
1121 /* If any previous event on this item, notify it
1122 * before creating it */
1123 previous_delete_event_data = g_hash_table_lookup (monitor->priv->pre_delete, dir);
1124 if (previous_delete_event_data) {
1125 emit_signal_for_event (monitor, previous_delete_event_data);
1126 g_hash_table_remove (monitor->priv->pre_delete, dir);
1127 }
1128
1129 if (!g_hash_table_lookup (monitor->priv->pre_update, dir)) {
1130 g_hash_table_insert (monitor->priv->pre_update,
1131 g_object_ref (dir),
1132 event_data_new (dir,
1133 NULL,
1134 TRUE,
1135 event_type));
1136 }
1137 }
1138
1139 static void
1140 monitor_event_directory_deleted (TrackerMonitor *monitor,
1141 GFile *dir)
1142 {
1143 EventData *previous_update_event_data;
1144 EventData *previous_delete_event_data;
1145
1146 /* If any previous update event on this item, notify it */
1147 previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, dir);
1148 if (previous_update_event_data) {
1149 emit_signal_for_event (monitor, previous_update_event_data);
1150 g_hash_table_remove (monitor->priv->pre_update, dir);
1151 }
1152
1153 /* Check if there is a previous delete event */
1154 previous_delete_event_data = g_hash_table_lookup (monitor->priv->pre_delete, dir);
1155 if (previous_delete_event_data &&
1156 previous_delete_event_data->event_type == G_FILE_MONITOR_EVENT_MOVED) {
1157 g_debug ("Processing MOVE(A->B) + DELETE(A) as MOVE(A->B) for directory '%s->%s'",
1158 previous_delete_event_data->file_uri,
1159 previous_delete_event_data->other_file_uri);
1160
1161 emit_signal_for_event (monitor, previous_delete_event_data);
1162 g_hash_table_remove (monitor->priv->pre_delete, dir);
1163 return;
1164 }
1165
1166 /* If no previous, add to HT */
1167 g_hash_table_replace (monitor->priv->pre_delete,
1168 g_object_ref (dir),
1169 event_data_new (dir,
1170 NULL,
1171 TRUE,
1172 G_FILE_MONITOR_EVENT_DELETED));
1173 }
1174
1175 static void
1176 monitor_event_directory_moved (TrackerMonitor *monitor,
1177 GFile *src_dir,
1178 GFile *dst_dir)
1179 {
1180 EventData *previous_update_event_data;
1181 EventData *previous_delete_event_data;
1182
1183 /* If any previous update event on this item, notify it */
1184 previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, src_dir);
1185 if (previous_update_event_data) {
1186 emit_signal_for_event (monitor, previous_update_event_data);
1187 g_hash_table_remove (monitor->priv->pre_update, src_dir);
1188 }
1189
1190 /* Check if there is a previous delete event */
1191 previous_delete_event_data = g_hash_table_lookup (monitor->priv->pre_delete, src_dir);
1192 if (previous_delete_event_data &&
1193 previous_delete_event_data->event_type == G_FILE_MONITOR_EVENT_DELETED) {
1194 EventData *new_event;
1195
1196 new_event = event_data_new (src_dir,
1197 dst_dir,
1198 TRUE,
1199 G_FILE_MONITOR_EVENT_MOVED);
1200 g_debug ("Processing DELETE(A) + MOVE(A->B) as MOVE(A->B) for directory '%s->%s'",
1201 new_event->file_uri,
1202 new_event->other_file_uri);
1203
1204 emit_signal_for_event (monitor, new_event);
1205 event_data_free (new_event);
1206
1207 /* And remove the previous DELETE */
1208 g_hash_table_remove (monitor->priv->pre_delete, src_dir);
1209 return;
1210 }
1211
1212 /* If no previous, add to HT */
1213 g_hash_table_replace (monitor->priv->pre_delete,
1214 g_object_ref (src_dir),
1215 event_data_new (src_dir,
1216 dst_dir,
1217 TRUE,
1218 G_FILE_MONITOR_EVENT_MOVED));
1219 }
1220
1221 static void
1222 monitor_event_cb (GFileMonitor *file_monitor,
1223 GFile *file,
1224 GFile *other_file,
1225 GFileMonitorEvent event_type,
1226 gpointer user_data)
1227 {
1228 TrackerMonitor *monitor;
1229 gchar *file_uri;
1230 gchar *other_file_uri;
1231 gboolean is_directory;
1232
1233 monitor = user_data;
1234
1235 if (G_UNLIKELY (!monitor->priv->enabled)) {
1236 g_debug ("Silently dropping monitor event, monitor disabled for now");
1237 return;
1238 }
1239
1240 /* Get URIs as paths may not be in UTF-8 */
1241 file_uri = g_file_get_uri (file);
1242
1243 if (!other_file) {
1244 is_directory = check_is_directory (monitor, file);
1245
1246 /* Avoid non-indexable-files */
1247 if (monitor->priv->tree &&
1248 !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
1249 file,
1250 (is_directory ?
1251 G_FILE_TYPE_DIRECTORY :
1252 G_FILE_TYPE_REGULAR))) {
1253 g_free (file_uri);
1254 return;
1255 }
1256
1257 other_file_uri = NULL;
1258 g_debug ("Received monitor event:%d (%s) for %s:'%s'",
1259 event_type,
1260 monitor_event_to_string (event_type),
1261 is_directory ? "directory" : "file",
1262 file_uri);
1263 } else {
1264 /* If we have other_file, it means an item was moved from file to other_file;
1265 * so, it makes sense to check if the other_file is directory instead of
1266 * the origin file, as this one will not exist any more */
1267 is_directory = check_is_directory (monitor, other_file);
1268
1269 /* Avoid doing anything of both
1270 * file/other_file are non-indexable
1271 */
1272 if (monitor->priv->tree &&
1273 !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
1274 file,
1275 (is_directory ?
1276 G_FILE_TYPE_DIRECTORY :
1277 G_FILE_TYPE_REGULAR)) &&
1278 !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
1279 other_file,
1280 (is_directory ?
1281 G_FILE_TYPE_DIRECTORY :
1282 G_FILE_TYPE_REGULAR))) {
1283 g_free (file_uri);
1284 return;
1285 }
1286
1287 other_file_uri = g_file_get_uri (other_file);
1288 g_debug ("Received monitor event:%d (%s) for files '%s'->'%s'",
1289 event_type,
1290 monitor_event_to_string (event_type),
1291 file_uri,
1292 other_file_uri);
1293 }
1294
1295 #ifdef PAUSE_ON_IO
1296 if (monitor->priv->unpause_timeout_id != 0) {
1297 g_source_remove (monitor->priv->unpause_timeout_id);
1298 } else {
1299 g_message ("Pausing indexing because we are "
1300 "receiving monitor events");
1301
1302 tracker_status_set_is_paused_for_io (TRUE);
1303 }
1304
1305 monitor->priv->unpause_timeout_id =
1306 g_timeout_add_seconds (PAUSE_ON_IO_SECONDS,
1307 unpause_cb,
1308 monitor);
1309 #endif /* PAUSE_ON_IO */
1310
1311 if (!is_directory) {
1312 /* FILE Events */
1313 switch (event_type) {
1314 case G_FILE_MONITOR_EVENT_CREATED:
1315 monitor_event_file_created (monitor, file);
1316 break;
1317 case G_FILE_MONITOR_EVENT_CHANGED:
1318 monitor_event_file_changed (monitor, file);
1319 break;
1320 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
1321 monitor_event_file_attribute_changed (monitor, file);
1322 break;
1323 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
1324 monitor_event_file_changes_done (monitor, file);
1325 break;
1326 case G_FILE_MONITOR_EVENT_DELETED:
1327 monitor_event_file_deleted (monitor, file);
1328 break;
1329 case G_FILE_MONITOR_EVENT_MOVED:
1330 monitor_event_file_moved (monitor, file, other_file);
1331 break;
1332 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
1333 case G_FILE_MONITOR_EVENT_UNMOUNTED:
1334 /* Do nothing */
1335 break;
1336 }
1337 } else {
1338 /* DIRECTORY Events */
1339
1340 switch (event_type) {
1341 case G_FILE_MONITOR_EVENT_CREATED:
1342 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
1343 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
1344 case G_FILE_MONITOR_EVENT_CHANGED:
1345 monitor_event_directory_created_or_changed (monitor, file, event_type);
1346 break;
1347 case G_FILE_MONITOR_EVENT_DELETED:
1348 monitor_event_directory_deleted (monitor, file);
1349 break;
1350 case G_FILE_MONITOR_EVENT_MOVED:
1351 monitor_event_directory_moved (monitor, file, other_file);
1352 break;
1353 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
1354 case G_FILE_MONITOR_EVENT_UNMOUNTED:
1355 /* Do nothing */
1356 break;
1357 }
1358 }
1359
1360 if (g_hash_table_size (monitor->priv->pre_update) > 0 ||
1361 g_hash_table_size (monitor->priv->pre_delete) > 0) {
1362 if (monitor->priv->event_pairs_timeout_id == 0) {
1363 g_debug ("Waiting for event pairs");
1364 monitor->priv->event_pairs_timeout_id =
1365 g_timeout_add_seconds (CACHE_LIFETIME_SECONDS,
1366 event_pairs_timeout_cb,
1367 monitor);
1368 }
1369 } else {
1370 if (monitor->priv->event_pairs_timeout_id != 0) {
1371 g_source_remove (monitor->priv->event_pairs_timeout_id);
1372 }
1373
1374 monitor->priv->event_pairs_timeout_id = 0;
1375 }
1376
1377 g_free (file_uri);
1378 g_free (other_file_uri);
1379 }
1380
1381 static GFileMonitor *
1382 directory_monitor_new (TrackerMonitor *monitor,
1383 GFile *file)
1384 {
1385 GFileMonitor *file_monitor;
1386 GError *error = NULL;
1387
1388 file_monitor = g_file_monitor_directory (file,
1389 G_FILE_MONITOR_SEND_MOVED | G_FILE_MONITOR_WATCH_MOUNTS,
1390 NULL,
1391 &error);
1392
1393 if (error) {
1394 gchar *uri;
1395
1396 uri = g_file_get_uri (file);
1397 g_warning ("Could not add monitor for path:'%s', %s",
1398 uri, error->message);
1399
1400 g_error_free (error);
1401 g_free (uri);
1402
1403 return NULL;
1404 }
1405
1406 g_signal_connect (file_monitor, "changed",
1407 G_CALLBACK (monitor_event_cb),
1408 monitor);
1409
1410 return file_monitor;
1411 }
1412
1413 static void
1414 directory_monitor_cancel (GFileMonitor *monitor)
1415 {
1416 if (monitor) {
1417 g_file_monitor_cancel (G_FILE_MONITOR (monitor));
1418 g_object_unref (monitor);
1419 }
1420 }
1421
1422 TrackerMonitor *
1423 tracker_monitor_new (void)
1424 {
1425 return g_object_new (TRACKER_TYPE_MONITOR, NULL);
1426 }
1427
1428 gboolean
1429 tracker_monitor_get_enabled (TrackerMonitor *monitor)
1430 {
1431 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
1432
1433 return monitor->priv->enabled;
1434 }
1435
1436 TrackerIndexingTree *
1437 tracker_monitor_get_indexing_tree (TrackerMonitor *monitor)
1438 {
1439 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), NULL);
1440
1441 return monitor->priv->tree;
1442 }
1443
1444 void
1445 tracker_monitor_set_indexing_tree (TrackerMonitor *monitor,
1446 TrackerIndexingTree *tree)
1447 {
1448 g_return_if_fail (TRACKER_IS_MONITOR (monitor));
1449 g_return_if_fail (!tree || TRACKER_IS_INDEXING_TREE (tree));
1450
1451 if (monitor->priv->tree) {
1452 g_object_unref (monitor->priv->tree);
1453 monitor->priv->tree = NULL;
1454 }
1455
1456 if (tree) {
1457 monitor->priv->tree = g_object_ref (tree);
1458 }
1459 }
1460
1461 void
1462 tracker_monitor_set_enabled (TrackerMonitor *monitor,
1463 gboolean enabled)
1464 {
1465 GList *keys, *k;
1466
1467 g_return_if_fail (TRACKER_IS_MONITOR (monitor));
1468
1469 /* Don't replace all monitors if we are already
1470 * enabled/disabled.
1471 */
1472 if (monitor->priv->enabled == enabled) {
1473 return;
1474 }
1475
1476 monitor->priv->enabled = enabled;
1477 g_object_notify (G_OBJECT (monitor), "enabled");
1478
1479 keys = g_hash_table_get_keys (monitor->priv->monitors);
1480
1481 /* Update state on all monitored dirs */
1482 for (k = keys; k != NULL; k = k->next) {
1483 GFile *file;
1484
1485 file = k->data;
1486
1487 if (enabled) {
1488 GFileMonitor *dir_monitor;
1489
1490 dir_monitor = directory_monitor_new (monitor, file);
1491 g_hash_table_replace (monitor->priv->monitors,
1492 g_object_ref (file), dir_monitor);
1493 } else {
1494 /* Remove monitor */
1495 g_hash_table_replace (monitor->priv->monitors,
1496 g_object_ref (file), NULL);
1497 }
1498 }
1499
1500 g_list_free (keys);
1501 }
1502
1503 gboolean
1504 tracker_monitor_add (TrackerMonitor *monitor,
1505 GFile *file)
1506 {
1507 GFileMonitor *dir_monitor = NULL;
1508 gchar *uri;
1509
1510 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
1511 g_return_val_if_fail (G_IS_FILE (file), FALSE);
1512
1513 if (g_hash_table_lookup (monitor->priv->monitors, file)) {
1514 return TRUE;
1515 }
1516
1517 /* Cap the number of monitors */
1518 if (g_hash_table_size (monitor->priv->monitors) >= monitor->priv->monitor_limit) {
1519 monitor->priv->monitors_ignored++;
1520
1521 if (!monitor->priv->monitor_limit_warned) {
1522 g_warning ("The maximum number of monitors to set (%d) "
1523 "has been reached, not adding any new ones",
1524 monitor->priv->monitor_limit);
1525 monitor->priv->monitor_limit_warned = TRUE;
1526 }
1527
1528 return FALSE;
1529 }
1530
1531 uri = g_file_get_uri (file);
1532
1533 if (monitor->priv->enabled) {
1534 /* We don't check if a file exists or not since we might want
1535 * to monitor locations which don't exist yet.
1536 *
1537 * Also, we assume ALL paths passed are directories.
1538 */
1539 dir_monitor = directory_monitor_new (monitor, file);
1540
1541 if (!dir_monitor) {
1542 g_warning ("Could not add monitor for path:'%s'",
1543 uri);
1544 g_free (uri);
1545 return FALSE;
1546 }
1547 }
1548
1549 /* NOTE: it is ok to add a NULL file_monitor, when our
1550 * enabled/disabled state changes, we iterate all keys and
1551 * add or remove monitors.
1552 */
1553 g_hash_table_insert (monitor->priv->monitors,
1554 g_object_ref (file),
1555 dir_monitor);
1556
1557 g_debug ("Added monitor for path:'%s', total monitors:%d",
1558 uri,
1559 g_hash_table_size (monitor->priv->monitors));
1560
1561 g_free (uri);
1562
1563 return TRUE;
1564 }
1565
1566 gboolean
1567 tracker_monitor_remove (TrackerMonitor *monitor,
1568 GFile *file)
1569 {
1570 gboolean removed;
1571
1572 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
1573 g_return_val_if_fail (G_IS_FILE (file), FALSE);
1574
1575 removed = g_hash_table_remove (monitor->priv->monitors, file);
1576
1577 if (removed) {
1578 gchar *uri;
1579
1580 uri = g_file_get_uri (file);
1581 g_debug ("Removed monitor for path:'%s', total monitors:%d",
1582 uri,
1583 g_hash_table_size (monitor->priv->monitors));
1584
1585 g_free (uri);
1586 }
1587
1588 return removed;
1589 }
1590
1591 gboolean
1592 tracker_monitor_remove_recursively (TrackerMonitor *monitor,
1593 GFile *file)
1594 {
1595 GHashTableIter iter;
1596 gpointer iter_file, iter_file_monitor;
1597 guint items_removed = 0;
1598 gchar *uri;
1599
1600 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
1601 g_return_val_if_fail (G_IS_FILE (file), FALSE);
1602
1603 g_hash_table_iter_init (&iter, monitor->priv->monitors);
1604 while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
1605 if (!g_file_has_prefix (iter_file, file) &&
1606 !g_file_equal (iter_file, file)) {
1607 continue;
1608 }
1609
1610 g_hash_table_iter_remove (&iter);
1611 items_removed++;
1612 }
1613
1614 uri = g_file_get_uri (file);
1615 g_debug ("Removed all monitors recursively for path:'%s', total monitors:%d",
1616 uri, g_hash_table_size (monitor->priv->monitors));
1617 g_free (uri);
1618
1619 if (items_removed > 0) {
1620 /* We reset this because now it is possible we have limit - 1 */
1621 monitor->priv->monitor_limit_warned = FALSE;
1622 return TRUE;
1623 }
1624
1625 return FALSE;
1626 }
1627
1628 static gboolean
1629 monitor_cancel_recursively (TrackerMonitor *monitor,
1630 GFile *file)
1631 {
1632 GHashTableIter iter;
1633 gpointer iter_file, iter_file_monitor;
1634 guint items_cancelled = 0;
1635
1636 g_hash_table_iter_init (&iter, monitor->priv->monitors);
1637 while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
1638 gchar *uri;
1639
1640 if (!g_file_has_prefix (iter_file, file) &&
1641 !g_file_equal (iter_file, file)) {
1642 continue;
1643 }
1644
1645 uri = g_file_get_uri (iter_file);
1646 g_file_monitor_cancel (G_FILE_MONITOR (iter_file_monitor));
1647 g_debug ("Cancelled monitor for path:'%s'", uri);
1648 g_free (uri);
1649
1650 items_cancelled++;
1651 }
1652
1653 return items_cancelled > 0;
1654 }
1655
1656 gboolean
1657 tracker_monitor_is_watched (TrackerMonitor *monitor,
1658 GFile *file)
1659 {
1660 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
1661 g_return_val_if_fail (G_IS_FILE (file), FALSE);
1662
1663 return g_hash_table_lookup (monitor->priv->monitors, file) != NULL;
1664 }
1665
1666 gboolean
1667 tracker_monitor_is_watched_by_string (TrackerMonitor *monitor,
1668 const gchar *path)
1669 {
1670 GFile *file;
1671 gboolean watched;
1672
1673 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
1674 g_return_val_if_fail (path != NULL, FALSE);
1675
1676 file = g_file_new_for_path (path);
1677 watched = g_hash_table_lookup (monitor->priv->monitors, file) != NULL;
1678 g_object_unref (file);
1679
1680 return watched;
1681 }
1682
1683 guint
1684 tracker_monitor_get_count (TrackerMonitor *monitor)
1685 {
1686 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), 0);
1687
1688 return g_hash_table_size (monitor->priv->monitors);
1689 }
1690
1691 guint
1692 tracker_monitor_get_ignored (TrackerMonitor *monitor)
1693 {
1694 g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), 0);
1695
1696 return monitor->priv->monitors_ignored;
1697 }