tracker-monitor.c 30.9 KB
Newer Older
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 10 11
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * 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 16 17 18 19 20 21 22
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#include "config.h"

#include <stdlib.h>
23
#include <string.h>
24
#include <gio/gio.h>
25

26 27 28 29 30 31 32
#if defined (__OpenBSD__) || defined (__FreeBSD__) || defined (__NetBSD__) || defined (__APPLE__)
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#define TRACKER_MONITOR_KQUEUE
#endif

33 34 35 36 37
#include "tracker-monitor.h"

/* The life time of an item in the cache */
#define CACHE_LIFETIME_SECONDS 1

38 39
typedef struct TrackerMonitorPrivate  TrackerMonitorPrivate;

40
struct TrackerMonitorPrivate {
41
	GHashTable    *monitors;
42

43 44
	gboolean       enabled;

45
	guint          monitor_limit;
46
	gboolean       monitor_limit_warned;
47
	guint          monitors_ignored;
48 49 50 51 52 53

	/* For FAM, the _CHANGES_DONE event is not signalled, so we
	 * have to just use the _CHANGED event instead.
	 */
	gboolean       use_changed_event;

54
	GHashTable    *cached_events;
55 56

	TrackerIndexingTree *tree;
57 58 59 60
};

typedef struct {
	GFile    *file;
61 62 63 64
	gchar    *file_uri;
	GFile    *other_file;
	gchar    *other_file_uri;
	gboolean  is_directory;
65 66
	GTimeVal  start_time;
	guint32   event_type;
67
	gboolean  expirable;
68 69 70 71 72
} EventData;

enum {
	ITEM_CREATED,
	ITEM_UPDATED,
73
	ITEM_ATTRIBUTE_UPDATED,
74 75 76 77 78 79 80
	ITEM_DELETED,
	ITEM_MOVED,
	LAST_SIGNAL
};

enum {
	PROP_0,
81
	PROP_ENABLED
82 83 84 85
};

static void           tracker_monitor_finalize     (GObject        *object);
static void           tracker_monitor_set_property (GObject        *object,
86 87 88
                                                    guint           prop_id,
                                                    const GValue   *value,
                                                    GParamSpec     *pspec);
89
static void           tracker_monitor_get_property (GObject        *object,
90 91 92
                                                    guint           prop_id,
                                                    GValue         *value,
                                                    GParamSpec     *pspec);
93
static guint          get_kqueue_limit             (void);
94
static guint          get_inotify_limit            (void);
95
static GFileMonitor * directory_monitor_new        (TrackerMonitor *monitor,
96
                                                    GFile          *file);
97
static void           directory_monitor_cancel     (GFileMonitor     *dir_monitor);
98

99

100 101 102 103 104
static void           emit_signal_for_event        (TrackerMonitor    *monitor,
                                                    GFileMonitorEvent  type,
                                                    gboolean           is_directory,
                                                    GFile             *file,
                                                    GFile             *other_file);
105 106
static gboolean       monitor_cancel_recursively   (TrackerMonitor *monitor,
                                                    GFile          *file);
107

108 109
static guint signals[LAST_SIGNAL] = { 0, };

110
G_DEFINE_TYPE_WITH_PRIVATE (TrackerMonitor, tracker_monitor, G_TYPE_OBJECT)
111 112 113 114 115 116 117 118 119 120 121 122 123 124

static void
tracker_monitor_class_init (TrackerMonitorClass *klass)
{
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS (klass);

	object_class->finalize = tracker_monitor_finalize;
	object_class->set_property = tracker_monitor_set_property;
	object_class->get_property = tracker_monitor_get_property;

	signals[ITEM_CREATED] =
		g_signal_new ("item-created",
125 126 127 128
		              G_TYPE_FROM_CLASS (klass),
		              G_SIGNAL_RUN_LAST,
		              0,
		              NULL, NULL,
Xavier Claessens's avatar
Xavier Claessens committed
129
		              NULL,
130 131 132 133
		              G_TYPE_NONE,
		              2,
		              G_TYPE_OBJECT,
		              G_TYPE_BOOLEAN);
134 135
	signals[ITEM_UPDATED] =
		g_signal_new ("item-updated",
136 137 138 139
		              G_TYPE_FROM_CLASS (klass),
		              G_SIGNAL_RUN_LAST,
		              0,
		              NULL, NULL,
Xavier Claessens's avatar
Xavier Claessens committed
140
		              NULL,
141 142 143 144
		              G_TYPE_NONE,
		              2,
		              G_TYPE_OBJECT,
		              G_TYPE_BOOLEAN);
145 146 147 148 149 150
	signals[ITEM_ATTRIBUTE_UPDATED] =
		g_signal_new ("item-attribute-updated",
		              G_TYPE_FROM_CLASS (klass),
		              G_SIGNAL_RUN_LAST,
		              0,
		              NULL, NULL,
Xavier Claessens's avatar
Xavier Claessens committed
151
		              NULL,
152 153 154 155
		              G_TYPE_NONE,
		              2,
		              G_TYPE_OBJECT,
		              G_TYPE_BOOLEAN);
156 157
	signals[ITEM_DELETED] =
		g_signal_new ("item-deleted",
158 159 160 161
		              G_TYPE_FROM_CLASS (klass),
		              G_SIGNAL_RUN_LAST,
		              0,
		              NULL, NULL,
Xavier Claessens's avatar
Xavier Claessens committed
162
		              NULL,
163 164 165 166
		              G_TYPE_NONE,
		              2,
		              G_TYPE_OBJECT,
		              G_TYPE_BOOLEAN);
167 168
	signals[ITEM_MOVED] =
		g_signal_new ("item-moved",
169 170 171 172
		              G_TYPE_FROM_CLASS (klass),
		              G_SIGNAL_RUN_LAST,
		              0,
		              NULL, NULL,
Xavier Claessens's avatar
Xavier Claessens committed
173
		              NULL,
174 175 176 177 178 179
		              G_TYPE_NONE,
		              4,
		              G_TYPE_OBJECT,
		              G_TYPE_OBJECT,
		              G_TYPE_BOOLEAN,
		              G_TYPE_BOOLEAN);
180 181

	g_object_class_install_property (object_class,
182 183 184 185 186 187
	                                 PROP_ENABLED,
	                                 g_param_spec_boolean ("enabled",
	                                                       "Enabled",
	                                                       "Enabled",
	                                                       TRUE,
	                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
188 189 190 191 192 193
}

static void
tracker_monitor_init (TrackerMonitor *object)
{
	TrackerMonitorPrivate *priv;
194 195
	GFile                 *file;
	GFileMonitor          *monitor;
196
	GError                *error = NULL;
197

198
	priv = tracker_monitor_get_instance_private (object);
199 200 201 202

	/* By default we enable monitoring */
	priv->enabled = TRUE;

203 204 205 206 207 208 209
	/* Create monitors table for this module */
	priv->monitors =
		g_hash_table_new_full (g_file_hash,
		                       (GEqualFunc) g_file_equal,
		                       (GDestroyNotify) g_object_unref,
		                       (GDestroyNotify) directory_monitor_cancel);

210
	priv->cached_events =
211
		g_hash_table_new_full (g_file_hash,
212
		                       (GEqualFunc) g_file_equal,
213 214
		                       g_object_unref,
		                       NULL);
215 216 217 218 219 220

	/* For the first monitor we get the type and find out if we
	 * are using inotify, FAM, polling, etc.
	 */
	file = g_file_new_for_path (g_get_home_dir ());
	monitor = g_file_monitor_directory (file,
221
	                                    G_FILE_MONITOR_WATCH_MOVES,
222
	                                    NULL,
223
	                                    &error);
224

225 226 227 228 229 230 231
	if (error) {
		g_critical ("Could not create sample directory monitor: %s", error->message);
		g_error_free (error);

		/* Guessing limit... */
		priv->monitor_limit = 100;
	} else {
232 233 234 235
		GType monitor_backend;
		const gchar *name;

		monitor_backend = G_OBJECT_TYPE (monitor);
236 237 238 239 240

		/* We use the name because the type itself is actually
		 * private and not available publically. Note this is
		 * subject to change, but unlikely of course.
		 */
241
		name = g_type_name (monitor_backend);
242 243

		/* Set limits based on backend... */
244 245
		if (strcmp (name, "GInotifyDirectoryMonitor") == 0 ||
		    strcmp (name, "GInotifyFileMonitor") == 0) {
246
			/* Using inotify */
247
			g_debug ("Monitor backend is Inotify");
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266

			/* Setting limit based on kernel
			 * settings in /proc...
			 */
			priv->monitor_limit = get_inotify_limit ();

			/* We don't use 100% of the monitors, we allow other
			 * applications to have at least 500 or so to use
			 * between them selves. This only
			 * applies to inotify because it is a
			 * user shared resource.
			 */
			priv->monitor_limit -= 500;

			/* Make sure we don't end up with a
			 * negative maximum.
			 */
			priv->monitor_limit = MAX (priv->monitor_limit, 0);
		}
267 268
		else if (strcmp (name, "GKqueueDirectoryMonitor") == 0 ||
		         strcmp (name, "GKqueueFileMonitor") == 0) {
269
			/* Using kqueue(2) */
270
			g_debug ("Monitor backend is kqueue");
271 272 273

			priv->monitor_limit = get_kqueue_limit ();
		}
274 275
		else if (strcmp (name, "GFamDirectoryMonitor") == 0) {
			/* Using Fam */
276
			g_debug ("Monitor backend is Fam");
277 278 279 280 281 282 283 284 285

			/* Setting limit to an arbitary limit
			 * based on testing
			 */
			priv->monitor_limit = 400;
			priv->use_changed_event = TRUE;
		}
		else if (strcmp (name, "GWin32DirectoryMonitor") == 0) {
			/* Using Windows */
286
			g_debug ("Monitor backend is Windows");
287 288 289 290 291 292

			/* Guessing limit... */
			priv->monitor_limit = 8192;
		}
		else {
			/* Unknown */
293
			g_warning ("Monitor backend:'%s' is unhandled. Monitoring will be disabled",
294
			           name);
295
			priv->enabled = FALSE;
296
		}
297 298 299

		g_file_monitor_cancel (monitor);
		g_object_unref (monitor);
300 301 302
	}

	g_object_unref (file);
303 304 305

	if (priv->enabled)
		g_debug ("Monitor limit is %d", priv->monitor_limit);
306 307 308 309 310 311 312
}

static void
tracker_monitor_finalize (GObject *object)
{
	TrackerMonitorPrivate *priv;

313
	priv = tracker_monitor_get_instance_private (TRACKER_MONITOR (object));
314

315
	g_hash_table_unref (priv->cached_events);
Carlos Garnacho's avatar
Carlos Garnacho committed
316
	g_hash_table_unref (priv->monitors);
317 318 319 320 321

	G_OBJECT_CLASS (tracker_monitor_parent_class)->finalize (object);
}

static void
322 323 324 325
tracker_monitor_set_property (GObject      *object,
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
326 327 328 329
{
	switch (prop_id) {
	case PROP_ENABLED:
		tracker_monitor_set_enabled (TRACKER_MONITOR (object),
330
		                             g_value_get_boolean (value));
331 332 333 334 335 336 337 338
		break;

	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void
339 340 341 342
tracker_monitor_get_property (GObject      *object,
                              guint         prop_id,
                              GValue       *value,
                              GParamSpec   *pspec)
343 344 345
{
	TrackerMonitorPrivate *priv;

346
	priv = tracker_monitor_get_instance_private (TRACKER_MONITOR (object));
347 348 349 350 351 352 353 354 355 356 357

	switch (prop_id) {
	case PROP_ENABLED:
		g_value_set_boolean (value, priv->enabled);
		break;

	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

358 359 360 361 362 363 364
static guint
get_kqueue_limit (void)
{
	guint limit = 400;

#ifdef TRACKER_MONITOR_KQUEUE
	struct rlimit rl;
365 366 367 368 369 370 371
	if (getrlimit (RLIMIT_NOFILE, &rl) == 0) {
		rl.rlim_cur = rl.rlim_max;
	} else {
		return limit;
	}

	if (setrlimit(RLIMIT_NOFILE, &rl) == 0)
372 373 374 375 376 377
		limit = (rl.rlim_cur * 90) / 100;
#endif /* TRACKER_MONITOR_KQUEUE */

	return limit;
}

378 379 380
static guint
get_inotify_limit (void)
{
381
	GError      *error = NULL;
382
	const gchar *filename;
383 384
	gchar       *contents = NULL;
	guint        limit;
385 386 387 388

	filename = "/proc/sys/fs/inotify/max_user_watches";

	if (!g_file_get_contents (filename,
389 390 391
	                          &contents,
	                          NULL,
	                          &error)) {
392
		g_warning ("Couldn't get INotify monitor limit from:'%s', %s",
393 394
		           filename,
		           error ? error->message : "no error given");
395 396 397 398 399 400 401 402 403 404 405 406
		g_clear_error (&error);

		/* Setting limit to an arbitary limit */
		limit = 8192;
	} else {
		limit = atoi (contents);
		g_free (contents);
	}

	return limit;
}

407 408
static gboolean
check_is_directory (TrackerMonitor *monitor,
409
                    GFile          *file)
410 411 412 413 414 415 416 417 418
{
	GFileType file_type;

	file_type = g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL);

	if (file_type == G_FILE_TYPE_DIRECTORY)
		return TRUE;

	if (file_type == G_FILE_TYPE_UNKNOWN) {
419 420 421 422
		TrackerMonitorPrivate *priv;

		priv = tracker_monitor_get_instance_private (monitor);

423 424 425 426
		/* Whatever it was, it's gone. Check the monitors
		 * hashtable to know whether it was a directory
		 * we knew about
		 */
427
		if (g_hash_table_lookup (priv->monitors, file) != NULL)
428 429 430 431 432 433
			return TRUE;
	}

	return FALSE;
}

434
gboolean
435 436 437
tracker_monitor_move (TrackerMonitor *monitor,
                      GFile          *old_file,
                      GFile          *new_file)
438
{
439
	TrackerMonitorPrivate *priv;
440 441 442 443 444 445
	GHashTableIter iter;
	GHashTable *new_monitors;
	gchar *old_prefix;
	gpointer iter_file, iter_file_monitor;
	guint items_moved = 0;

446 447
	priv = tracker_monitor_get_instance_private (monitor);

448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
	/* So this is tricky. What we have to do is:
	 *
	 * 1) Add all monitors for the new_file directory hierarchy
	 * 2) Then remove the monitors for old_file
	 *
	 * This order is necessary because inotify can reuse watch
	 * descriptors, and libinotify will remove handles
	 * asynchronously on IN_IGNORE, so the opposite sequence
	 * may possibly remove valid, just added, monitors.
	 */
	new_monitors = g_hash_table_new_full (g_file_hash,
	                                      (GEqualFunc) g_file_equal,
	                                      (GDestroyNotify) g_object_unref,
	                                      NULL);
	old_prefix = g_file_get_path (old_file);

	/* Find out which subdirectories should have a file monitor added */
465
	g_hash_table_iter_init (&iter, priv->monitors);
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
	while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
		GFile *f;
		gchar *old_path, *new_path;
		gchar *new_prefix;
		gchar *p;

		if (!g_file_has_prefix (iter_file, old_file) &&
		    !g_file_equal (iter_file, old_file)) {
			continue;
		}

		old_path = g_file_get_path (iter_file);
		p = strstr (old_path, old_prefix);

		if (!p || strcmp (p, old_prefix) == 0) {
			g_free (old_path);
			continue;
		}

		/* Move to end of prefix */
		p += strlen (old_prefix) + 1;

		/* Check this is not the end of the string */
		if (*p == '\0') {
			g_free (old_path);
			continue;
		}

		new_prefix = g_file_get_path (new_file);
		new_path = g_build_path (G_DIR_SEPARATOR_S, new_prefix, p, NULL);
		g_free (new_prefix);

		f = g_file_new_for_path (new_path);
		g_free (new_path);

		if (!g_hash_table_lookup (new_monitors, f)) {
			g_hash_table_insert (new_monitors, f, GINT_TO_POINTER (1));
		} else {
			g_object_unref (f);
		}

		g_free (old_path);
		items_moved++;
	}

	/* Add a new monitor for the top level directory */
	tracker_monitor_add (monitor, new_file);

	/* Add a new monitor for all subdirectories */
	g_hash_table_iter_init (&iter, new_monitors);
	while (g_hash_table_iter_next (&iter, &iter_file, NULL)) {
		tracker_monitor_add (monitor, iter_file);
		g_hash_table_iter_remove (&iter);
	}

	/* Remove the monitor for the old top level directory hierarchy */
	tracker_monitor_remove_recursively (monitor, old_file);

	g_hash_table_unref (new_monitors);
	g_free (old_prefix);

	return items_moved > 0;
}

530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
static const gchar *
monitor_event_to_string (GFileMonitorEvent event_type)
{
	switch (event_type) {
	case G_FILE_MONITOR_EVENT_CHANGED:
		return "G_FILE_MONITOR_EVENT_CHANGED";
	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
		return "G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT";
	case G_FILE_MONITOR_EVENT_DELETED:
		return "G_FILE_MONITOR_EVENT_DELETED";
	case G_FILE_MONITOR_EVENT_CREATED:
		return "G_FILE_MONITOR_EVENT_CREATED";
	case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
		return "G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED";
	case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
		return "G_FILE_MONITOR_EVENT_PRE_UNMOUNT";
	case G_FILE_MONITOR_EVENT_UNMOUNTED:
		return "G_FILE_MONITOR_EVENT_UNMOUNTED";
	case G_FILE_MONITOR_EVENT_MOVED:
		return "G_FILE_MONITOR_EVENT_MOVED";
550
	case G_FILE_MONITOR_EVENT_RENAMED:
551
		return "G_FILE_MONITOR_EVENT_RENAMED";
552
	case G_FILE_MONITOR_EVENT_MOVED_IN:
553
		return "G_FILE_MONITOR_EVENT_MOVED_IN";
554
	case G_FILE_MONITOR_EVENT_MOVED_OUT:
555
		return "G_FILE_MONITOR_EVENT_MOVED_OUT";
556
		break;
557 558 559 560 561 562
	}

	return "unknown";
}

static void
563 564 565 566 567
emit_signal_for_event (TrackerMonitor    *monitor,
                       GFileMonitorEvent  type,
                       gboolean           is_directory,
                       GFile             *file,
                       GFile             *other_file)
568
{
569 570 571 572 573 574 575 576 577 578 579
	/* Note that in any case we should be moving the monitors
	 * here to the new place, as the new place may be ignored.
	 * We should leave this to the upper layers. But one thing
	 * we must do is actually CANCEL all these monitors. */
	if (is_directory &&
	    (type == G_FILE_MONITOR_EVENT_MOVED ||
	     type == G_FILE_MONITOR_EVENT_DELETED)) {
		monitor_cancel_recursively (monitor, file);
	}

	switch (type) {
580
	case G_FILE_MONITOR_EVENT_CREATED:
581
		g_signal_emit (monitor,
582
		               signals[ITEM_CREATED], 0,
583
		               file, is_directory);
584 585
		break;
	case G_FILE_MONITOR_EVENT_CHANGED:
586
		g_signal_emit (monitor,
587
		               signals[ITEM_UPDATED], 0,
588
		               file, is_directory);
589
		break;
590 591
	case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
		g_signal_emit (monitor,
592
		               signals[ITEM_ATTRIBUTE_UPDATED], 0,
593
		               file, is_directory);
594
		break;
595
	case G_FILE_MONITOR_EVENT_DELETED:
596 597
		g_signal_emit (monitor,
		               signals[ITEM_DELETED], 0,
598
		               file, is_directory);
599 600
		break;
	case G_FILE_MONITOR_EVENT_MOVED:
601 602 603
		g_signal_emit (monitor,
		               signals[ITEM_MOVED], 0,
		               file, other_file, is_directory, TRUE);
604
		break;
605 606 607
	default:
		g_warning ("Trying to emit monitor signal with unhandled event %d",
		           type);
608
		break;
609 610 611
	}
}

612
static void
613 614 615
flush_cached_event (TrackerMonitor *monitor,
                    GFile          *file,
                    gboolean        is_directory)
616
{
617
	gpointer value = NULL;
618
	TrackerMonitorPrivate *priv;
619

620 621 622
	priv = tracker_monitor_get_instance_private (monitor);

	if (g_hash_table_lookup_extended (priv->cached_events,
623 624 625
	                                  file, NULL, &value)) {
		GFileMonitorEvent prev_event_type = GPOINTER_TO_UINT (value);

626
		g_hash_table_remove (priv->cached_events, file);
627 628
		emit_signal_for_event (monitor, prev_event_type,
		                       is_directory, file, NULL);
629 630 631 632
	}
}

static void
633 634 635
cache_event (TrackerMonitor    *monitor,
             GFile             *file,
             GFileMonitorEvent  event_type)
636
{
637 638 639
	TrackerMonitorPrivate *priv;

	priv = tracker_monitor_get_instance_private (monitor);
640 641 642 643 644

	if (g_hash_table_lookup_extended (priv->cached_events, file,
	                                  NULL, NULL))
		return;

645
	g_hash_table_insert (priv->cached_events,
646 647
	                     g_object_ref (file),
	                     GUINT_TO_POINTER (event_type));
648 649
}

650
static void
651 652 653
monitor_event_cb (GFileMonitor      *file_monitor,
                  GFile             *file,
                  GFile             *other_file,
654
                  GFileMonitorEvent  event_type,
655
                  gpointer           user_data)
656 657
{
	TrackerMonitor *monitor;
658 659
	gchar *file_uri;
	gchar *other_file_uri;
660
	gboolean is_directory = FALSE;
661
	TrackerMonitorPrivate *priv;
662
	gpointer value;
663 664

	monitor = user_data;
665
	priv = tracker_monitor_get_instance_private (monitor);
666

667
	if (G_UNLIKELY (!priv->enabled)) {
668 669 670 671
		g_debug ("Silently dropping monitor event, monitor disabled for now");
		return;
	}

672 673
	/* Get URIs as paths may not be in UTF-8 */
	file_uri = g_file_get_uri (file);
674

675
	if (!other_file) {
676 677
		is_directory = check_is_directory (monitor, file);

678
		other_file_uri = NULL;
679
		g_debug ("Received monitor event:%d (%s) for %s:'%s'",
680 681
		         event_type,
		         monitor_event_to_string (event_type),
682
		         is_directory ? "directory" : "file",
683 684
		         file_uri);
	} else {
685 686 687 688 689 690
		if (event_type == G_FILE_MONITOR_EVENT_RENAMED ||
		    event_type == G_FILE_MONITOR_EVENT_MOVED_OUT) {
			is_directory = check_is_directory (monitor, other_file);
		} else if (event_type == G_FILE_MONITOR_EVENT_MOVED_IN) {
			is_directory = check_is_directory (monitor, file);
		}
691

692 693 694 695 696 697 698
		other_file_uri = g_file_get_uri (other_file);
		g_debug ("Received monitor event:%d (%s) for files '%s'->'%s'",
		         event_type,
		         monitor_event_to_string (event_type),
		         file_uri,
		         other_file_uri);
	}
699

700 701 702
	switch (event_type) {
	case G_FILE_MONITOR_EVENT_CREATED:
	case G_FILE_MONITOR_EVENT_CHANGED:
703
		if (!priv->use_changed_event) {
704 705 706 707
			cache_event (monitor, file, event_type);
		} else {
			emit_signal_for_event (monitor, event_type,
			                       is_directory, file, NULL);
708
		}
709 710
		break;
	case G_FILE_MONITOR_EVENT_DELETED:
711 712 713 714 715 716 717 718 719 720 721 722
		if (g_hash_table_lookup_extended (priv->cached_events,
		                                  file, NULL, &value) &&
		    GPOINTER_TO_UINT (value) == G_FILE_MONITOR_EVENT_CREATED) {
			/* Consume both the cached CREATED event and this one */
			g_hash_table_remove (priv->cached_events, file);
			break;
		}

		/* In any case, cached events are stale */
		g_hash_table_remove (priv->cached_events, file);

		/* Fall through */
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
	case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
		emit_signal_for_event (monitor, event_type,
		                       is_directory, file, NULL);
		break;
	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
		flush_cached_event (monitor, file, is_directory);
		break;
	case G_FILE_MONITOR_EVENT_MOVED_IN:
		if (other_file) {
			/* Both MOVED_IN and MOVE_OUT are fine points to emit
			 * ::item-moved when source/dest are known. We choose
			 * to emit it here, and ignore the MOVE_OUT.
			 */
			emit_signal_for_event (monitor,
			                       G_FILE_MONITOR_EVENT_MOVED,
			                       is_directory,
			                       other_file, file);
		} else {
			/* No known origin, treat as a new file */
			emit_signal_for_event (monitor,
			                       G_FILE_MONITOR_EVENT_CREATED,
			                       is_directory,
			                       file, NULL);
746
		}
747 748 749 750 751 752 753 754
		break;
	case G_FILE_MONITOR_EVENT_MOVED_OUT:
		if (!other_file) {
			/* No known destination. Treat as remove */
			emit_signal_for_event (monitor,
			                       G_FILE_MONITOR_EVENT_DELETED,
			                       is_directory,
			                       file, NULL);
755
		}
756 757 758 759 760 761 762 763 764
		break;
	case G_FILE_MONITOR_EVENT_RENAMED:
		emit_signal_for_event (monitor,
		                       G_FILE_MONITOR_EVENT_MOVED,
		                       is_directory, file, other_file);
		break;
	case G_FILE_MONITOR_EVENT_MOVED:
		g_warn_if_reached ();
		break;
765 766 767
	case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
	case G_FILE_MONITOR_EVENT_UNMOUNTED:
		break;
768 769
	}

770 771
	g_free (file_uri);
	g_free (other_file_uri);
772 773
}

774
static GFileMonitor *
775
directory_monitor_new (TrackerMonitor *monitor,
776
                       GFile          *file)
777 778 779 780 781
{
	GFileMonitor *file_monitor;
	GError *error = NULL;

	file_monitor = g_file_monitor_directory (file,
782
	                                         G_FILE_MONITOR_WATCH_MOVES,
783 784
	                                         NULL,
	                                         &error);
785 786

	if (error) {
787
		gchar *uri;
788

789
		uri = g_file_get_uri (file);
790
		g_warning ("Could not add monitor for path:'%s', %s",
791
		           uri, error->message);
792 793

		g_error_free (error);
794
		g_free (uri);
795 796 797 798 799

		return NULL;
	}

	g_signal_connect (file_monitor, "changed",
800 801
	                  G_CALLBACK (monitor_event_cb),
	                  monitor);
802 803 804 805 806

	return file_monitor;
}

static void
807
directory_monitor_cancel (GFileMonitor *monitor)
808 809 810 811 812 813 814
{
	if (monitor) {
		g_file_monitor_cancel (G_FILE_MONITOR (monitor));
		g_object_unref (monitor);
	}
}

815
TrackerMonitor *
816
tracker_monitor_new (void)
817
{
818
	return g_object_new (TRACKER_TYPE_MONITOR, NULL);
819 820 821 822 823
}

gboolean
tracker_monitor_get_enabled (TrackerMonitor *monitor)
{
824 825
	TrackerMonitorPrivate *priv;

826 827
	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);

828 829 830
	priv = tracker_monitor_get_instance_private (monitor);

	return priv->enabled;
831 832 833 834
}

void
tracker_monitor_set_enabled (TrackerMonitor *monitor,
835
                             gboolean        enabled)
836
{
837
	TrackerMonitorPrivate *priv;
838 839
	GList *keys, *k;

840 841
	g_return_if_fail (TRACKER_IS_MONITOR (monitor));

842 843
	priv = tracker_monitor_get_instance_private (monitor);

844
	/* Don't replace all monitors if we are already
845
	 * enabled/disabled.
846
	 */
847
	if (priv->enabled == enabled) {
848 849 850
		return;
	}

851
	priv->enabled = enabled;
852
	g_object_notify (G_OBJECT (monitor), "enabled");
853

854
	keys = g_hash_table_get_keys (priv->monitors);
855 856 857 858 859 860 861 862

	/* Update state on all monitored dirs */
	for (k = keys; k != NULL; k = k->next) {
		GFile *file;

		file = k->data;

		if (enabled) {
863
			GFileMonitor *dir_monitor;
864

865
			dir_monitor = directory_monitor_new (monitor, file);
866
			g_hash_table_replace (priv->monitors,
867
			                      g_object_ref (file), dir_monitor);
868 869
		} else {
			/* Remove monitor */
870
			g_hash_table_replace (priv->monitors,
871
			                      g_object_ref (file), NULL);
872 873 874 875
		}
	}

	g_list_free (keys);
876 877 878 879
}

gboolean
tracker_monitor_add (TrackerMonitor *monitor,
880
                     GFile          *file)
881
{
882
	TrackerMonitorPrivate *priv;
883
	GFileMonitor *dir_monitor = NULL;
884
	gchar *uri;
885 886 887 888

	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
	g_return_val_if_fail (G_IS_FILE (file), FALSE);

889 890 891
	priv = tracker_monitor_get_instance_private (monitor);

	if (g_hash_table_lookup (priv->monitors, file)) {
892 893 894 895
		return TRUE;
	}

	/* Cap the number of monitors */
896 897
	if (g_hash_table_size (priv->monitors) >= priv->monitor_limit) {
		priv->monitors_ignored++;
898

899
		if (!priv->monitor_limit_warned) {
900
			g_warning ("The maximum number of monitors to set (%d) "
901
			           "has been reached, not adding any new ones",
902 903
			           priv->monitor_limit);
			priv->monitor_limit_warned = TRUE;
904 905 906 907 908
		}

		return FALSE;
	}

909
	uri = g_file_get_uri (file);
910

911
	if (priv->enabled) {
912 913 914 915 916
		/* We don't check if a file exists or not since we might want
		 * to monitor locations which don't exist yet.
		 *
		 * Also, we assume ALL paths passed are directories.
		 */
917
		dir_monitor = directory_monitor_new (monitor, file);
918

919
		if (!dir_monitor) {
920
			g_warning ("Could not add monitor for path:'%s'",
921 922
			           uri);
			g_free (uri);
923 924
			return FALSE;
		}
925 926
	}

927 928 929 930
	/* NOTE: it is ok to add a NULL file_monitor, when our
	 * enabled/disabled state changes, we iterate all keys and
	 * add or remove monitors.
	 */
931
	g_hash_table_insert (priv->monitors,
932
	                     g_object_ref (file),
933
	                     dir_monitor);
934

935
	g_debug ("Added monitor for path:'%s', total monitors:%d",
936
	         uri,
937
	         g_hash_table_size (priv->monitors));
938

939
	g_free (uri);
940 941 942 943 944 945

	return TRUE;
}

gboolean
tracker_monitor_remove (TrackerMonitor *monitor,
946
                        GFile          *file)
947
{
948
	TrackerMonitorPrivate *priv;
949
	gboolean removed;
950 951 952

	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
	g_return_val_if_fail (G_IS_FILE (file), FALSE);
953

954 955
	priv = tracker_monitor_get_instance_private (monitor);
	removed = g_hash_table_remove (priv->monitors, file);
956

957
	if (removed) {
958
		gchar *uri;
959

960
		uri = g_file_get_uri (file);
961
		g_debug ("Removed monitor for path:'%s', total monitors:%d",
962
		         uri,
963
		         g_hash_table_size (priv->monitors));
964

965
		g_free (uri);
966
	}
967 968 969

	return removed;
}
970

971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
/* If @is_strict is %TRUE, return %TRUE iff @file is a child of @prefix.
 * If @is_strict is %FALSE, additionally return %TRUE if @file equals @prefix.
 */
static gboolean
file_has_maybe_strict_prefix (GFile    *file,
                              GFile    *prefix,
                              gboolean  is_strict)
{
	return (g_file_has_prefix (file, prefix) ||
	        (!is_strict && g_file_equal (file, prefix)));
}

static gboolean
remove_recursively (TrackerMonitor *monitor,
                    GFile          *file,
                    gboolean        remove_top_level)
987
{
988
	TrackerMonitorPrivate *priv;
989
	GHashTableIter iter;
990 991
	gpointer iter_file, iter_file_monitor;
	guint items_removed = 0;
992
	gchar *uri;
993 994 995 996

	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
	g_return_val_if_fail (G_IS_FILE (file), FALSE);

997 998 999
	priv = tracker_monitor_get_instance_private (monitor);

	g_hash_table_iter_init (&iter, priv->monitors);
1000
	while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
1001 1002
		if (!file_has_maybe_strict_prefix (iter_file, file,
		                                   !remove_top_level)) {
1003
			continue;
1004
		}
1005

1006
		g_hash_table_iter_remove (&iter);
1007 1008
		items_removed++;
	}
1009

1010
	uri = g_file_get_uri (file);
1011 1012 1013
	g_debug ("Removed all monitors %srecursively for path:'%s', "
	         "total monitors:%d",
	         !remove_top_level ? "(except top level) " : "",
1014
	         uri, g_hash_table_size (priv->monitors));
1015
	g_free (uri);
1016

1017
	if (items_removed > 0) {
1018
		/* We reset this because now it is possible we have limit - 1 */
1019
		priv->monitor_limit_warned = FALSE;
1020
		return TRUE;
1021 1022
	}

1023
	return FALSE;
1024 1025
}

1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
gboolean
tracker_monitor_remove_recursively (TrackerMonitor *monitor,
                                    GFile          *file)
{
	return remove_recursively (monitor, file, TRUE);
}

gboolean
tracker_monitor_remove_children_recursively (TrackerMonitor *monitor,
                                             GFile          *file)
{
	return remove_recursively (monitor, file, FALSE);
}

1040 1041 1042 1043
static gboolean
monitor_cancel_recursively (TrackerMonitor *monitor,
                            GFile          *file)
{
1044
	TrackerMonitorPrivate *priv;
1045 1046 1047 1048
	GHashTableIter iter;
	gpointer iter_file, iter_file_monitor;
	guint items_cancelled = 0;

1049 1050 1051
	priv = tracker_monitor_get_instance_private (monitor);

	g_hash_table_iter_init (&iter, priv->monitors);
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
	while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
		gchar *uri;

		if (!g_file_has_prefix (iter_file, file) &&
		    !g_file_equal (iter_file, file)) {
			continue;
		}

		uri = g_file_get_uri (iter_file);
		g_file_monitor_cancel (G_FILE_MONITOR (iter_file_monitor));
		g_debug ("Cancelled monitor for path:'%s'", uri);
		g_free (uri);

		items_cancelled++;
	}

	return items_cancelled > 0;
}

1071 1072
gboolean
tracker_monitor_is_watched (TrackerMonitor *monitor,
1073
                            GFile          *file)
1074
{
1075 1076
	TrackerMonitorPrivate *priv;

1077 1078 1079
	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
	g_return_val_if_fail (G_IS_FILE (file), FALSE);

1080 1081 1082
	priv = tracker_monitor_get_instance_private (monitor);

	return g_hash_table_lookup (priv->monitors, file) != NULL;
1083 1084 1085 1086
}

gboolean
tracker_monitor_is_watched_by_string (TrackerMonitor *monitor,
1087
                                      const gchar    *path)
1088
{
1089
	TrackerMonitorPrivate *priv;
1090
	GFile      *file;
1091 1092 1093 1094 1095
	gboolean    watched;

	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
	g_return_val_if_fail (path != NULL, FALSE);

1096 1097
	priv = tracker_monitor_get_instance_private (monitor);

1098
	file = g_file_new_for_path (path);
1099
	watched = g_hash_table_lookup (priv->monitors, file) != NULL;
1100 1101 1102 1103 1104 1105
	g_object_unref (file);

	return watched;
}

guint
1106
tracker_monitor_get_count (TrackerMonitor *monitor)
1107
{
1108 1109
	TrackerMonitorPrivate *priv;

1110 1111
	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), 0);

1112 1113 1114
	priv = tracker_monitor_get_instance_private (monitor);

	return g_hash_table_size (priv->monitors);
1115 1116 1117 1118 1119
}

guint
tracker_monitor_get_ignored (TrackerMonitor *monitor)
{
1120 1121
	TrackerMonitorPrivate *priv;

1122 1123
	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), 0);

1124 1125 1126
	priv = tracker_monitor_get_instance_private (monitor);

	return priv->monitors_ignored;
1127
}
1128 1129 1130 1131

guint
tracker_monitor_get_limit (TrackerMonitor *monitor)
{
1132 1133
	TrackerMonitorPrivate *priv;

1134 1135
	g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), 0);

1136 1137 1138
	priv = tracker_monitor_get_instance_private (monitor);

	return priv->monitor_limit;
1139
}