tracker-0.16.2/src/libtracker-miner/tracker-monitor.c

No issues found

Incomplete coverage

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
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
   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 }