Blob Blame History Raw
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-missing.h	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,34 @@
+/* inotify-helper.h - GNOME VFS Monitor using inotify
+
+   Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: John McCutchan <ttb@tentacle.dhs.org>
+*/
+
+
+#ifndef __INOTIFY_MISSING_H
+#define __INOTIFY_MISSING_H
+
+#include "inotify-sub.h"
+
+void im_startup (void (*missing_cb)(ih_sub_t *sub));
+void im_add (ih_sub_t *sub);
+void im_rm (ih_sub_t *sub);
+void im_diag_dump (GIOChannel *ioc);
+
+#endif /* __INOTIFY_MISSING_H */
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-kernel.h	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,40 @@
+/*
+	Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; version 2.
+
+	This program 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 General Public License version 2 for more details.
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software Foundation,
+	Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#ifndef __INOTIFY_KERNEL_H
+#define __INOTIFY_KERNEL_H
+
+typedef struct ik_event_s {
+	gint32 wd;
+	guint32 mask;
+	guint32 cookie;
+	guint32 len;
+	char *  name;
+	struct ik_event_s *pair;
+} ik_event_t;
+
+gboolean ik_startup (void (*cb)(ik_event_t *event));
+ik_event_t *ik_event_new_dummy (const char *name, gint32 wd, guint32 mask);
+void ik_event_free (ik_event_t *event);
+
+gint32 ik_watch(const char *path, guint32 mask, int *err);
+int ik_ignore(const char *path, gint32 wd);
+
+/* The miss count will probably be enflated */
+void ik_move_stats (guint32 *matches, guint32 *misses);
+const char *ik_mask_to_string (guint32 mask);
+
+#endif
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-diag.h	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,30 @@
+/* inotify-helper.h - GNOME VFS Monitor using inotify
+
+   Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: John McCutchan <ttb@tentacle.dhs.org>
+*/
+
+
+#ifndef __INOTIFY_DIAG_H
+#define __INOTIFY_DIAG_H
+
+void id_startup (void);
+gboolean id_dump (gpointer userdata);
+
+#endif /* __INOTIFY_DIAG_H */
--- gamin-0.1.7/server/Makefile.am.new-inotify-backend	2005-08-26 13:52:19.000000000 +0200
+++ gamin-0.1.7/server/Makefile.am	2006-09-05 11:01:21.000000000 +0200
@@ -51,6 +51,12 @@
 
 if ENABLE_INOTIFY
 gam_server_SOURCES += gam_inotify.c gam_inotify.h	\
+	inotify-helper.c inotify-helper.h \
+	inotify-kernel.c inotify-kernel.h \
+	inotify-missing.c inotify-missing.h \
+	inotify-path.c inotify-path.h \
+	inotify-sub.c inotify-sub.h \
+	inotify-diag.c inotify-diag.h \
 	local_inotify.h local_inotify_syscalls.h
 endif
 
--- gamin-0.1.7/server/local_inotify_syscalls.h.new-inotify-backend	2005-08-17 15:50:04.000000000 +0200
+++ gamin-0.1.7/server/local_inotify_syscalls.h	2006-09-05 11:01:21.000000000 +0200
@@ -1,7 +1,9 @@
 #ifndef _LINUX_INOTIFY_SYSCALLS_H
 #define _LINUX_INOTIFY_SYSCALLS_H
 
+#include <asm/types.h>
 #include <sys/syscall.h>
+#include <unistd.h>
 
 #if defined(__i386__)
 # define __NR_inotify_init	291
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-kernel.c	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,685 @@
+/*
+	Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; version 2.
+
+	This program 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 General Public License version 2 for more details.
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software Foundation,
+	Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <glib.h>
+#include "inotify-kernel.h"
+
+/* Just include the local headers to stop all the pain */
+#include "local_inotify.h"
+#include "local_inotify_syscalls.h"
+#if 0
+#ifdef HAVE_SYS_INOTIFY_H
+/* We don't actually include the libc header, because there has been
+ * problems with libc versions that was built without inotify support.
+ * Instead we use the local version.
+ */
+#include "local_inotify.h"
+#include "local_inotify_syscalls.h"
+#elif defined (HAVE_LINUX_INOTIFY_H)
+#include <linux/inotify.h>
+#include "local_inotify_syscalls.h"
+#endif
+#endif
+
+/* Timings for pairing MOVED_TO / MOVED_FROM events */
+#define PROCESS_EVENTS_TIME 1000 /* milliseconds (1 hz) */
+#define DEFAULT_HOLD_UNTIL_TIME 0 /* 0 millisecond */
+#define MOVE_HOLD_UNTIL_TIME 0 /* 0 milliseconds */
+
+static int inotify_instance_fd = -1;
+static GQueue *events_to_process = NULL;
+static GQueue *event_queue = NULL;
+static GHashTable * cookie_hash = NULL;
+static GIOChannel *inotify_read_ioc;
+static GPollFD ik_poll_fd;
+static gboolean ik_poll_fd_enabled = TRUE;
+static void (*user_cb)(ik_event_t *event);
+
+static gboolean ik_read_callback (gpointer user_data);
+static gboolean ik_process_eq_callback (gpointer user_data);
+
+static guint32 ik_move_matches = 0;
+static guint32 ik_move_misses = 0;
+
+static gboolean process_eq_running = FALSE;
+
+/* We use the lock from inotify-helper.c
+ *
+ * There are two places that we take this lock
+ *
+ * 1) In ik_read_callback
+ *
+ * 2) ik_process_eq_callback.
+ *
+ *
+ * The rest of locking is taken care of in inotify-helper.c
+ */
+G_LOCK_EXTERN (inotify_lock);
+
+typedef struct ik_event_internal {
+	ik_event_t *event;
+	gboolean seen;
+	gboolean sent;
+	GTimeVal hold_until;
+	struct ik_event_internal *pair;
+} ik_event_internal_t;
+
+/* In order to perform non-sleeping inotify event chunking we need
+ * a custom GSource
+ */
+static gboolean
+ik_source_prepare (GSource *source,
+				 gint *timeout)
+{
+	return FALSE;
+}
+
+static gboolean
+ik_source_timeout (gpointer data)
+{
+	GSource *source = (GSource *)data;
+
+	/* Re-active the PollFD */
+	g_source_add_poll (source, &ik_poll_fd);
+	g_source_unref (source);
+	ik_poll_fd_enabled = TRUE;
+
+	return FALSE;
+}
+
+#define MAX_PENDING_COUNT 2
+#define PENDING_THRESHOLD(qsize) ((qsize) >> 1)
+#define PENDING_MARGINAL_COST(p) ((unsigned int)(1 << (p)))
+#define MAX_QUEUED_EVENTS 2048
+#define AVERAGE_EVENT_SIZE sizeof (struct inotify_event) + 16
+#define TIMEOUT_MILLISECONDS 10
+static gboolean
+ik_source_check (GSource *source)
+{
+	static int prev_pending = 0, pending_count = 0;
+
+	/* We already disabled the PollFD or
+	 * nothing to be read from inotify */
+	if (!ik_poll_fd_enabled || !(ik_poll_fd.revents & G_IO_IN))
+	{
+		return FALSE;
+	}
+
+	if (pending_count < MAX_PENDING_COUNT) {
+		unsigned int pending;
+
+		if (ioctl (inotify_instance_fd, FIONREAD, &pending) == -1)
+			goto do_read;
+
+		pending /= AVERAGE_EVENT_SIZE;
+
+		/* Don't wait if the number of pending events is too close
+		* to the maximum queue size.
+		*/
+		if (pending > PENDING_THRESHOLD (MAX_QUEUED_EVENTS))
+			goto do_read;
+
+		/* With each successive iteration, the minimum rate for
+		* further sleep doubles. */
+		if (pending-prev_pending < PENDING_MARGINAL_COST(pending_count))
+			goto do_read;
+
+		prev_pending = pending;
+		pending_count++;
+
+		/* We are going to wait to read the events: */
+
+		/* Remove the PollFD from the source */
+		g_source_remove_poll (source, &ik_poll_fd);
+		/* To avoid threading issues we need to flag that we've done that */
+		ik_poll_fd_enabled = FALSE;
+		/* Set a timeout to re-add the PollFD to the source */
+		g_source_ref (source);
+		g_timeout_add (TIMEOUT_MILLISECONDS, ik_source_timeout, source);
+
+		return FALSE;
+	}
+
+do_read:
+	/* We are ready to read events from inotify */
+
+	prev_pending = 0;
+	pending_count = 0;
+
+	return TRUE;
+}
+
+static gboolean
+ik_source_dispatch (GSource *source,
+		    GSourceFunc callback,
+		    gpointer user_data)
+{
+	if (callback)
+	{
+		return callback(user_data);
+	}
+	return TRUE;
+}
+
+GSourceFuncs ik_source_funcs =
+{
+	ik_source_prepare,
+	ik_source_check,
+	ik_source_dispatch,
+	NULL
+};
+
+gboolean ik_startup (void (*cb)(ik_event_t *event))
+{
+	static gboolean initialized = FALSE;
+	GSource *source;
+
+	user_cb = cb;
+	/* Ignore multi-calls */
+	if (initialized) {
+		return inotify_instance_fd >= 0;
+	}
+
+	initialized = TRUE;
+	inotify_instance_fd = inotify_init ();
+
+	if (inotify_instance_fd < 0) {
+		return FALSE;
+	}
+
+	inotify_read_ioc = g_io_channel_unix_new(inotify_instance_fd);
+	ik_poll_fd.fd = inotify_instance_fd;
+	ik_poll_fd.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
+	g_io_channel_set_encoding(inotify_read_ioc, NULL, NULL);
+	g_io_channel_set_flags(inotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL);
+
+	source = g_source_new (&ik_source_funcs, sizeof(GSource));
+	g_source_add_poll (source, &ik_poll_fd);
+	g_source_set_callback(source, ik_read_callback, NULL, NULL);
+	g_source_attach(source, NULL);
+	g_source_unref (source);
+
+	cookie_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+	event_queue = g_queue_new ();
+	events_to_process = g_queue_new ();
+
+	return TRUE;
+}
+
+static ik_event_internal_t *ik_event_internal_new (ik_event_t *event)
+{
+	ik_event_internal_t *internal_event = g_new0(ik_event_internal_t, 1);
+	GTimeVal tv;
+
+	g_assert (event);
+	
+	g_get_current_time (&tv);
+	g_time_val_add (&tv, DEFAULT_HOLD_UNTIL_TIME);
+	internal_event->event = event;
+	internal_event->hold_until = tv;
+
+	return internal_event;
+}
+
+static ik_event_t *ik_event_new (char *buffer)
+{
+   struct inotify_event *kevent = (struct inotify_event *)buffer;
+   g_assert (buffer);
+   ik_event_t *event = g_new0(ik_event_t,1);
+   event->wd = kevent->wd;
+   event->mask = kevent->mask;
+   event->cookie = kevent->cookie;
+   event->len = kevent->len;
+   if (event->len)
+      event->name = g_strdup(kevent->name);
+   else
+      event->name = g_strdup("");
+
+   return event;
+}
+
+ik_event_t *ik_event_new_dummy (const char *name, gint32 wd, guint32 mask)
+{
+	ik_event_t *event = g_new0(ik_event_t,1);
+	event->wd = wd;
+	event->mask = mask;
+	event->cookie = 0;
+	if (name)
+		event->name = g_strdup(name);
+	else
+		event->name = g_strdup("");
+
+	event->len = strlen (event->name);
+
+	return event;
+}
+
+void ik_event_free (ik_event_t *event)
+{
+	if (event->pair)
+		ik_event_free (event->pair);
+	g_free(event->name);
+	g_free(event);
+}
+
+gint32 ik_watch (const char *path, guint32 mask, int *err)
+{
+   gint32 wd = -1;
+
+   g_assert (path != NULL);
+   g_assert (inotify_instance_fd >= 0);
+
+   wd = inotify_add_watch (inotify_instance_fd, path, mask);
+
+   if (wd < 0)
+   {
+      int e = errno;
+      // FIXME: debug msg failed to add watch
+      if (err)
+         *err = e;
+      return wd;
+   }
+
+   g_assert (wd >= 0);
+   return wd;
+}
+
+int ik_ignore(const char *path, gint32 wd)
+{
+	g_assert (wd >= 0);
+	g_assert (inotify_instance_fd >= 0);
+
+	if (inotify_rm_watch (inotify_instance_fd, wd) < 0)
+	{
+		//int e = errno;
+		// failed to rm watch
+		return -1;
+	}
+
+	return 0;
+}
+
+void ik_move_stats (guint32 *matches, guint32 *misses)
+{
+	if (matches)
+		*matches = ik_move_matches;
+
+	if (misses)
+		*misses = ik_move_misses;
+}
+
+const char *ik_mask_to_string (guint32 mask)
+{
+	gboolean is_dir = mask & IN_ISDIR;
+	mask &= ~IN_ISDIR;
+
+	if (is_dir)
+	{
+		switch (mask)
+		{
+			case IN_ACCESS:
+				return "ACCESS (dir)";
+			break;
+			case IN_MODIFY:
+				return "MODIFY (dir)";
+			break;
+			case IN_ATTRIB:
+				return "ATTRIB (dir)";
+			break;
+			case IN_CLOSE_WRITE:
+				return "CLOSE_WRITE (dir)";
+			break;
+			case IN_CLOSE_NOWRITE:
+				return "CLOSE_NOWRITE (dir)"; 
+			break;
+			case IN_OPEN:
+				return "OPEN (dir)";
+			break;
+			case IN_MOVED_FROM:
+				return "MOVED_FROM (dir)";
+			break;
+			case IN_MOVED_TO:
+				return "MOVED_TO (dir)";
+			break;
+			case IN_DELETE:
+				return "DELETE (dir)";
+			break;
+			case IN_CREATE:
+				return "CREATE (dir)";
+			break;
+			case IN_DELETE_SELF:
+				return "DELETE_SELF (dir)";
+			break;
+			case IN_UNMOUNT:
+				return "UNMOUNT (dir)";
+			break;
+			case IN_Q_OVERFLOW:
+				return "Q_OVERFLOW (dir)";
+			break;
+			case IN_IGNORED:
+				return "IGNORED (dir)";
+			break;
+			default:
+				return "UNKNOWN_EVENT (dir)";
+			break;
+
+		}
+	} else {
+		switch (mask)
+		{
+			case IN_ACCESS:
+				return "ACCESS";
+			break;
+			case IN_MODIFY:
+				return "MODIFY";
+			break;
+			case IN_ATTRIB:
+				return "ATTRIB";
+			break;
+			case IN_CLOSE_WRITE:
+				return "CLOSE_WRITE";
+			break;
+			case IN_CLOSE_NOWRITE:
+				return "CLOSE_NOWRITE";
+			break;
+			case IN_OPEN:
+				return "OPEN";
+			break;
+			case IN_MOVED_FROM:
+				return "MOVED_FROM";
+			break;
+			case IN_MOVED_TO:
+				return "MOVED_TO";
+			break;
+			case IN_DELETE:
+				return "DELETE";
+			break;
+			case IN_CREATE:
+				return "CREATE";
+			break;
+			case IN_DELETE_SELF:
+				return "DELETE_SELF";
+			break;
+			case IN_UNMOUNT:
+				return "UNMOUNT";
+			break;
+			case IN_Q_OVERFLOW:
+				return "Q_OVERFLOW";
+			break;
+			case IN_IGNORED:
+				return "IGNORED";
+			break;
+			default:
+				return "UNKNOWN_EVENT";
+			break;
+
+		}
+	}
+}
+
+
+static void ik_read_events (gsize *buffer_size_out, gchar **buffer_out)
+{
+	static gchar *buffer = NULL;
+	static gsize buffer_size;
+
+	/* Initialize the buffer on our first call */
+	if (buffer == NULL)
+	{
+		buffer_size = AVERAGE_EVENT_SIZE;
+		buffer_size *= MAX_QUEUED_EVENTS;
+		buffer = g_malloc (buffer_size);
+
+		if (!buffer) {
+			*buffer_size_out = 0;
+			*buffer_out = NULL;
+			return;
+		}
+	}
+
+	*buffer_size_out = 0;
+	*buffer_out = NULL;
+
+	memset(buffer, 0, buffer_size);
+
+	if (g_io_channel_read_chars (inotify_read_ioc, (char *)buffer, buffer_size, buffer_size_out, NULL) != G_IO_STATUS_NORMAL) {
+		// error reading
+	}
+	*buffer_out = buffer;
+}
+
+static gboolean ik_read_callback(gpointer user_data)
+{
+	gchar *buffer;
+	gsize buffer_size, buffer_i, events;
+
+	G_LOCK(inotify_lock);
+	ik_read_events (&buffer_size, &buffer);
+
+	buffer_i = 0;
+	events = 0;
+	while (buffer_i < buffer_size)
+	{
+		struct inotify_event *event;
+		gsize event_size;
+		event = (struct inotify_event *)&buffer[buffer_i];
+		event_size = sizeof(struct inotify_event) + event->len;
+		g_queue_push_tail (events_to_process, ik_event_internal_new (ik_event_new (&buffer[buffer_i])));
+		buffer_i += event_size;
+		events++;
+	}
+
+	/* If the event process callback is off, turn it back on */
+	if (!process_eq_running && events)
+	{
+		process_eq_running = TRUE;
+		g_timeout_add (PROCESS_EVENTS_TIME, ik_process_eq_callback, NULL);
+	}
+
+	G_UNLOCK(inotify_lock);
+	return TRUE;
+}
+
+static gboolean
+g_timeval_lt(GTimeVal *val1, GTimeVal *val2)
+{
+	if (val1->tv_sec < val2->tv_sec)
+		return TRUE;
+
+	if (val1->tv_sec > val2->tv_sec)
+		return FALSE;
+
+	/* val1->tv_sec == val2->tv_sec */
+	if (val1->tv_usec < val2->tv_usec)
+		return TRUE;
+
+	return FALSE;
+}
+
+static gboolean
+g_timeval_eq(GTimeVal *val1, GTimeVal *val2)
+{
+	return (val1->tv_sec == val2->tv_sec) && (val1->tv_usec == val2->tv_usec);
+}
+
+static void
+ik_pair_events (ik_event_internal_t *event1, ik_event_internal_t *event2)
+{
+	g_assert (event1 && event2);
+	/* We should only be pairing events that have the same cookie */
+	g_assert (event1->event->cookie == event2->event->cookie);
+	/* We shouldn't pair an event that already is paired */
+	g_assert (event1->pair == NULL && event2->pair == NULL);
+
+	/* Pair the internal structures and the ik_event_t structures */
+	event1->pair = event2;
+	event1->event->pair = event2->event;
+
+	if (g_timeval_lt (&event1->hold_until, &event2->hold_until))
+		event1->hold_until = event2->hold_until;
+
+	event2->hold_until = event1->hold_until;
+}
+
+static void
+ik_event_add_microseconds (ik_event_internal_t *event, glong ms)
+{
+	g_assert (event);
+	g_time_val_add (&event->hold_until, ms);
+}
+
+static gboolean
+ik_event_ready (ik_event_internal_t *event)
+{
+	GTimeVal tv;
+	g_assert (event);
+
+	g_get_current_time (&tv);
+
+	/* An event is ready if,
+	*
+	* it has no cookie -- there is nothing to be gained by holding it
+	* or, it is already paired -- we don't need to hold it anymore
+	* or, we have held it long enough
+	*/
+	return event->event->cookie == 0 ||
+	       event->pair != NULL ||
+	       g_timeval_lt(&event->hold_until, &tv) || g_timeval_eq(&event->hold_until, &tv);
+}
+
+static void
+ik_pair_moves (gpointer data, gpointer user_data)
+{
+	ik_event_internal_t *event = (ik_event_internal_t *)data;
+
+	if (event->seen == TRUE || event->sent == TRUE)
+	return;
+
+	if (event->event->cookie != 0)
+	{
+		/* When we get a MOVED_FROM event we delay sending the event by
+		* MOVE_HOLD_UNTIL_TIME microseconds. We need to do this because a
+		* MOVED_TO pair _might_ be coming in the near future */
+		if (event->event->mask & IN_MOVED_FROM) {
+			g_hash_table_insert (cookie_hash, GINT_TO_POINTER(event->event->cookie), event);
+			// because we don't deliver move events there is no point in waiting for the match right now.
+			ik_event_add_microseconds (event, MOVE_HOLD_UNTIL_TIME);
+		} else if (event->event->mask & IN_MOVED_TO) {
+			/* We need to check if we are waiting for this MOVED_TO events cookie to pair it with
+			* a MOVED_FROM */
+			ik_event_internal_t *match = NULL;
+			match = g_hash_table_lookup (cookie_hash, GINT_TO_POINTER(event->event->cookie));
+			if (match) {
+				g_hash_table_remove (cookie_hash, GINT_TO_POINTER(event->event->cookie));
+				ik_pair_events (match, event);
+			}
+		}
+	}
+	event->seen = TRUE;
+}
+
+static void
+ik_process_events ()
+{
+	g_queue_foreach (events_to_process, ik_pair_moves, NULL);
+
+	while (!g_queue_is_empty (events_to_process))
+	{
+		ik_event_internal_t *event = g_queue_peek_head (events_to_process);
+
+		/* This must have been sent as part of a MOVED_TO/MOVED_FROM */
+		if (event->sent)
+		{
+			/* Pop event */
+			g_queue_pop_head (events_to_process);
+			/* Free the internal event structure */
+			g_free (event);
+			continue;
+		}
+
+		/* The event isn't ready yet */
+		if (!ik_event_ready (event)) {
+			break;
+		}
+
+		/* Pop it */
+		event = g_queue_pop_head (events_to_process);
+
+		/* Check if this is a MOVED_FROM that is also sitting in the cookie_hash */
+		if (event->event->cookie && event->pair == NULL &&
+		    g_hash_table_lookup (cookie_hash, GINT_TO_POINTER(event->event->cookie)))
+		{
+			g_hash_table_remove (cookie_hash, GINT_TO_POINTER(event->event->cookie));
+		}
+
+		if (event->pair) {
+			/* We send out paired MOVED_FROM/MOVED_TO events in the same event buffer */
+			//g_assert (event->event->mask == IN_MOVED_FROM && event->pair->event->mask == IN_MOVED_TO);
+			/* Copy the paired data */
+			event->pair->sent = TRUE;
+			event->sent = TRUE;
+			ik_move_matches++;
+		} else if (event->event->cookie) {
+			/* If we couldn't pair a MOVED_FROM and MOVED_TO together, we change
+			* the event masks */
+			/* Changeing MOVED_FROM to DELETE and MOVED_TO to create lets us make
+			* the gaurantee that you will never see a non-matched MOVE event */
+
+			if (event->event->mask & IN_MOVED_FROM) {
+				event->event->mask = IN_DELETE|(event->event->mask & IN_ISDIR);
+				ik_move_misses++; // not super accurate, if we aren't watching the destination it still counts as a miss
+			}
+			if (event->event->mask & IN_MOVED_TO)
+				event->event->mask = IN_CREATE|(event->event->mask & IN_ISDIR);
+		}
+
+		/* Push the ik_event_t onto the event queue */
+		g_queue_push_tail (event_queue, event->event);
+		/* Free the internal event structure */
+		g_free (event);
+	}
+}
+
+gboolean ik_process_eq_callback (gpointer user_data)
+{
+    /* Try and move as many events to the event queue */
+	G_LOCK(inotify_lock);
+	ik_process_events ();
+
+	while (!g_queue_is_empty (event_queue))
+	{
+		ik_event_t *event = g_queue_pop_head (event_queue);
+
+		user_cb (event);
+	}
+
+	if (g_queue_get_length (events_to_process) == 0)
+	{
+		process_eq_running = FALSE;
+		G_UNLOCK(inotify_lock);
+		return FALSE;
+	} else {
+		G_UNLOCK(inotify_lock);
+		return TRUE;
+	}
+}
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-missing.c	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,158 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* inotify-helper.c - Gnome VFS Monitor based on inotify.
+
+   Copyright (C) 2005 John McCutchan
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Authors: 
+		 John McCutchan <john@johnmccutchan.com>
+*/
+
+#include "config.h"
+#include <glib.h>
+#include "inotify-missing.h"
+#include "inotify-path.h"
+
+#define SCAN_MISSING_TIME 4000 /* 1/4 Hz */
+
+static gboolean     im_debug_enabled = FALSE;
+#define IM_W if (im_debug_enabled) g_warning
+
+/* We put ih_sub_t's that are missing on this list */
+static GList *missing_sub_list = NULL;
+static gboolean im_scan_missing (gpointer user_data);
+static gboolean scan_missing_running = FALSE;
+static void (*missing_cb)(ih_sub_t *sub) = NULL;
+
+G_LOCK_EXTERN (inotify_lock);
+
+/* inotify_lock must be held before calling */
+void im_startup (void (*callback)(ih_sub_t *sub))
+{
+	static gboolean initialized = FALSE;
+
+	if (!initialized) {
+		initialized = TRUE;
+		missing_cb = callback;
+	}
+}
+
+/* inotify_lock must be held before calling */
+void im_add (ih_sub_t *sub)
+{
+	if (g_list_find (missing_sub_list, sub)) {
+		IM_W("asked to add %s to missing list but it's already on the list!\n", sub->pathname);
+		return;
+	}
+
+	IM_W("adding %s to missing list\n", sub->dirname);
+	missing_sub_list = g_list_prepend (missing_sub_list, sub);
+
+	/* If the timeout is turned off, we turn it back on */
+	if (!scan_missing_running)
+	{
+		scan_missing_running = TRUE;
+		g_timeout_add (SCAN_MISSING_TIME, im_scan_missing, NULL);
+	}
+}
+
+/* inotify_lock must be held before calling */
+void im_rm (ih_sub_t *sub)
+{
+	GList *link;
+
+	link = g_list_find (missing_sub_list, sub);
+
+	if (!link) {
+		IM_W("asked to remove %s from missing list but it isn't on the list!\n", sub->pathname);
+		return;
+	}
+
+	IM_W("removing %s from missing list\n", sub->dirname);
+
+	missing_sub_list = g_list_remove_link (missing_sub_list, link);
+	g_list_free_1 (link);
+}
+
+/* Scans the list of missing subscriptions checking if they
+ * are available yet.
+ */
+static gboolean im_scan_missing (gpointer user_data)
+{
+	GList *nolonger_missing = NULL;
+	GList *l;
+
+	G_LOCK(inotify_lock);
+
+	IM_W("scanning missing list with %d items\n", g_list_length (missing_sub_list));
+	for (l = missing_sub_list; l; l = l->next)
+	{
+		ih_sub_t *sub = l->data;
+		gboolean not_m = FALSE;
+
+		IM_W("checking %p\n", sub);
+		g_assert (sub);
+		g_assert (sub->dirname);
+		not_m = ip_start_watching (sub);
+
+		if (not_m)
+		{
+			missing_cb (sub);
+			IM_W("removed %s from missing list\n", sub->dirname);
+			/* We have to build a list of list nodes to remove from the
+			* missing_sub_list. We do the removal outside of this loop.
+			*/
+			nolonger_missing = g_list_prepend (nolonger_missing, l);
+		} 
+	}
+
+	for (l = nolonger_missing; l ; l = l->next)
+	{
+		GList *llink = l->data;
+		missing_sub_list = g_list_remove_link (missing_sub_list, llink);
+		g_list_free_1 (llink);
+	}
+
+	g_list_free (nolonger_missing);
+
+	/* If the missing list is now empty, we disable the timeout */
+	if (missing_sub_list == NULL)
+	{
+		scan_missing_running = FALSE;
+		G_UNLOCK(inotify_lock);
+		return FALSE;
+	} else {
+		G_UNLOCK(inotify_lock);
+		return TRUE;
+	}
+}
+
+
+/* inotify_lock must be held */
+void
+im_diag_dump (GIOChannel *ioc)
+{
+	GList *l;
+	g_io_channel_write_chars (ioc, "missing list:\n", -1, NULL, NULL);
+	for (l = missing_sub_list; l; l = l->next)
+	{
+		ih_sub_t *sub = l->data;
+		g_io_channel_write_chars (ioc, sub->pathname, -1, NULL, NULL);
+		g_io_channel_write_chars (ioc, "\n", -1, NULL, NULL);
+	}
+}
--- gamin-0.1.7/server/gam_inotify.c.new-inotify-backend	2005-10-25 16:16:28.000000000 +0200
+++ gamin-0.1.7/server/gam_inotify.c	2006-09-05 12:13:38.000000000 +0200
@@ -17,1582 +17,201 @@
  */
 
 #include "server_config.h"
-#define _GNU_SOURCE
-#include <errno.h>
-#include <stdio.h>
 #include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <asm/unistd.h>
-#include <time.h>
-#include <glib.h>
+/* Just include the local header to stop all the pain */
+#include "local_inotify.h"
+#if 0
+#ifdef HAVE_SYS_INOTIFY_H
+/* We don't actually include the libc header, because there has been
+ * problems with libc versions that was built without inotify support.
+ * Instead we use the local version.
+ */
+#include "local_inotify.h"
+#elif defined (HAVE_LINUX_INOTIFY_H)
+#include <linux/inotify.h>
+#endif
+#endif
+#include "inotify-sub.h"
+#include "inotify-helper.h"
+#include "inotify-diag.h"
 #ifdef GAMIN_DEBUG_API
 #include "gam_debugging.h"
 #endif
 #include "gam_error.h"
-#include "gam_poll_basic.h"
-#ifdef HAVE_LINUX_INOTIFY_H
-#include <linux/inotify.h>
-#else
-#include "local_inotify.h"
-#endif
-#include "local_inotify_syscalls.h"
-#include "gam_inotify.h"
-#include "gam_tree.h"
 #include "gam_event.h"
 #include "gam_server.h"
-#include "gam_event.h"
-#include "gam_fs.h"
-
-#define GAM_INOTIFY_SANITY
-#define GAM_INOTIFY_WD_MISSING -1
-#define GAM_INOTIFY_WD_PERM -2
-#define GAM_INOTIFY_WD_LINK -3
-
-/* Timings for pairing MOVED_TO / MOVED_FROM events */
-/* These numbers are in microseconds */
-#define DEFAULT_HOLD_UNTIL_TIME 1000 /* 1 ms */
-#define MOVE_HOLD_UNTIL_TIME 5000 /* 5 ms */
-
-/* Timings for main loop */
-/* These numbers are in milliseconds */
-#define SCAN_MISSING_TIME 1000 /* 1 Hz */
-#define SCAN_LINKS_TIME 1000 /* 1 Hz */
-#define PROCESS_EVENTS_TIME 33 /* 30 Hz */ 
-
-typedef struct {
-	/* The full pathname of this node */
-	char *path;
-	gboolean dir; /* Is this path a directory */
-
-	/* Inotify */
-	int wd;
-
-	/* State */
-	gboolean busy;
-	gboolean missing;
-	gboolean link;
-	gboolean permission; /* Exists, but don't have read access */
-	gboolean deactivated;
-	gboolean ignored;
-	int refcount;
-
-	/* Statistics */
-	int events;
-	int deactivated_events;
-	int ignored_events;
-
-	/* Gamin state */
-	GList *subs;
-} inotify_data_t;
-
-typedef struct _inotify_event_t {
-	gint wd;
-	gint mask;
-	gint cookie;
-	char *name;
-	gboolean seen;
-	gboolean sent;
-	GTimeVal hold_until;
-	struct _inotify_event_t *pair; 
-} inotify_event_t;
-
-typedef struct {
-	char *path;
-	GTime last_scan_time;
-	GTime scan_interval;
-	gboolean permission;
-} inotify_missing_t;
-
-typedef struct {
-	char *path;
-	struct stat sbuf;
-	GTime last_scan_time;
-	GTime scan_interval;
-} inotify_links_t;
-
-static GHashTable *	path_hash = NULL;
-static GHashTable *	wd_hash = NULL;
-static GList *		missing_list = NULL;
-static GList *		links_list = NULL;
-static GHashTable *	cookie_hash = NULL;
-static GQueue *		event_queue = NULL;
-static GQueue *		events_to_process = NULL;
-static GIOChannel *	inotify_read_ioc = NULL;
-static int		inotify_device_fd = -1;
-
-#define GAM_INOTIFY_MASK (IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE|IN_CREATE|IN_DELETE_SELF|IN_UNMOUNT|IN_MOVE_SELF)
-
-static int 	gam_inotify_add_watch 		(const char *path, __u32 mask, int *err);
-static int 	gam_inotify_rm_watch 		(const char *path, __u32 wd);
-static void 	gam_inotify_read_events 	(gsize *buffer_size_out, gchar **buffer_out);
-
-static gboolean gam_inotify_is_missing		(const char *path);
-static gboolean gam_inotify_nolonger_missing 	(const char *path);
-static void 	gam_inotify_add_missing 	(const char *path, gboolean perm);
-static void 	gam_inotify_rm_missing 		(const char *path);
-static gboolean gam_inotify_scan_missing 	(gpointer userdata);
-
-static gboolean	gam_inotify_is_link		(const char *path);
-static gboolean gam_inotify_nolonger_link	(const char *path);
-static void	gam_inotify_add_link		(const char *path);
-static void	gam_inotify_rm_link		(const char *path);
-static gboolean	gam_inotify_scan_links		(gpointer userdata);
-static void	gam_inotify_poll_link		(inotify_links_t *links);
-
-static void 	gam_inotify_sanity_check	(void);
-
-static gboolean	g_timeval_lt			(GTimeVal *val1, GTimeVal *val2);
-static gboolean	g_timeval_eq			(GTimeVal *val1, GTimeVal *val2);
-
-static void 
-gam_inotify_data_debug (gpointer key, gpointer value, gpointer user_data)
-{
-	int busy;
-	int deactivated;
-	int ignored;
-	int missing;
-	int permission;
-	inotify_data_t *data = (inotify_data_t *)value;
-
-	if (!data)
-		return;
-
-	busy = data->busy;
-	deactivated = data->deactivated;
-	ignored = data->ignored;
-	missing = data->missing;
-	permission = data->permission;
-
-	GAM_DEBUG(DEBUG_INFO, "isub wd %d refs %d permission %d missing %d busy %d deactivated %d ignored %d events (%d:%d:%d): %s\n", data->wd, data->refcount, permission, missing, busy, deactivated, ignored, data->events, data->deactivated_events, data->ignored_events, data->path);
-}
-
-gboolean
-gam_inotify_is_running(void)
-{
-	return inotify_device_fd >= 0;
-}
-
-void
-gam_inotify_debug(void)
-{
-	if (inotify_device_fd == -1)
-	{
-		return;
-	}
-
-	if (path_hash == NULL)
-		return;
-
-	GAM_DEBUG(DEBUG_INFO, "Inotify device fd = %d\n", inotify_device_fd);
-	GAM_DEBUG(DEBUG_INFO, "Dumping inotify subscriptions\n");
-	g_hash_table_foreach (path_hash, gam_inotify_data_debug, NULL);
-}
-
-static const char *
-mask_to_string (int mask)
-{
-	mask &= ~IN_ISDIR;
-	switch (mask)
-	{
-	case IN_ACCESS:
-		return "ACCESS";
-	break;
-	case IN_MODIFY:
-		return "MODIFY";
-	break;
-	case IN_ATTRIB:
-		return "ATTRIB";
-	break;
-	case IN_CLOSE_WRITE:
-		return "CLOSE_WRITE";
-	break;
-	case IN_CLOSE_NOWRITE:
-		return "CLOSE_NOWRITE";
-	break;
-	case IN_OPEN:
-		return "OPEN";
-	break;
-	case IN_MOVED_FROM:
-		return "MOVED_FROM";
-	break;
-	case IN_MOVED_TO:
-		return "MOVED_TO";
-	break;
-	case IN_DELETE:
-		return "DELETE";
-	break;
-	case IN_CREATE:
-		return "CREATE";
-	break;
-	case IN_DELETE_SELF:
-		return "DELETE_SELF";
-	break;
-	case IN_UNMOUNT:
-		return "UNMOUNT";
-	break;
-	case IN_Q_OVERFLOW:
-		return "Q_OVERFLOW";
-	break;
-	case IN_IGNORED:
-		return "IGNORED";
-	break;
-	default:
-		return "UNKNOWN_EVENT";
-	break;
-	}
-}
-
-static GaminEventType
-mask_to_gam_event (gint mask)
-{
-	mask &= ~IN_ISDIR;
-	switch (mask)
-	{
-	case IN_MODIFY:
-	case IN_ATTRIB:
-		return GAMIN_EVENT_CHANGED;
-	break;
-	case IN_MOVE_SELF:
-	case IN_MOVED_FROM:
-	case IN_DELETE:
-	case IN_DELETE_SELF:
-		return GAMIN_EVENT_DELETED;
-	break;
-	case IN_CREATE:
-	case IN_MOVED_TO:
-		return GAMIN_EVENT_CREATED;
-	break;
-	case IN_Q_OVERFLOW:
-	case IN_OPEN:
-	case IN_CLOSE_WRITE:
-	case IN_CLOSE_NOWRITE:
-	case IN_UNMOUNT:
-	case IN_ACCESS:
-	case IN_IGNORED:
-	default:
-		return GAMIN_EVENT_UNKNOWN;
-	break;
-	}
-}
-
-/* Called when a directory is being watched as a file */
-static GaminEventType
-gam_inotify_mask_to_gam_file_event (gint mask)
-{
-	mask &= ~IN_ISDIR;
-	switch (mask)
-	{
-	case IN_MOVED_FROM:
-	case IN_DELETE:
-	case IN_CREATE:
-	case IN_MOVED_TO:
-		return GAMIN_EVENT_CHANGED;
-	break;
-	case IN_MOVE_SELF:
-	case IN_DELETE_SELF:
-		return GAMIN_EVENT_DELETED;
-	break;
-	case IN_ATTRIB:
-	case IN_MODIFY:
-	case IN_Q_OVERFLOW:
-	case IN_OPEN:
-	case IN_CLOSE_WRITE:
-	case IN_CLOSE_NOWRITE:
-	case IN_UNMOUNT:
-	case IN_ACCESS:
-	case IN_IGNORED:
-	default:
-		return GAMIN_EVENT_UNKNOWN;
-	break;
-	}
-}
+#include "gam_subscription.h"
+#include "gam_inotify.h"
 
-/* Called when a file is watched as a directory */
+/* Transforms a inotify event to a gamin event. */
 static GaminEventType
-gam_inotify_mask_to_gam_dir_event (gint mask)
+ih_mask_to_EventType (guint32 mask)
 {
-	mask &= ~IN_ISDIR;
-	switch (mask)
-	{
-	case IN_MOVED_FROM:
-	case IN_DELETE:
-	case IN_CREATE:
-	case IN_MOVED_TO:
-	case IN_MOVE_SELF:
-	case IN_DELETE_SELF:
-	case IN_ATTRIB:
-	case IN_MODIFY:
-	case IN_Q_OVERFLOW:
-	case IN_OPEN:
-	case IN_CLOSE_WRITE:
-	case IN_CLOSE_NOWRITE:
-	case IN_UNMOUNT:
-	case IN_ACCESS:
-	case IN_IGNORED:
-	default:
-		return GAMIN_EVENT_UNKNOWN;
+        mask &= ~IN_ISDIR;
+        switch (mask)
+        {
+        case IN_MODIFY:
+                return GAMIN_EVENT_CHANGED;
+        break;
+        case IN_ATTRIB:
+                return GAMIN_EVENT_CHANGED;
+        break;
+        case IN_MOVE_SELF:
+        case IN_MOVED_FROM:
+        case IN_DELETE:
+        case IN_DELETE_SELF:
+                return GAMIN_EVENT_DELETED;
+        break;
+        case IN_CREATE:
+        case IN_MOVED_TO:
+                return GAMIN_EVENT_CREATED;
+        break;
+        case IN_Q_OVERFLOW:
+        case IN_OPEN:
+        case IN_CLOSE_WRITE:
+        case IN_CLOSE_NOWRITE:
+        case IN_UNMOUNT:
+        case IN_ACCESS:
+        case IN_IGNORED:
+        default:
+                return -1;
 	break;
 	}
 }
 
-static inotify_data_t *
-gam_inotify_data_new(const char *path, int wd, gboolean dir)
-{
-	inotify_data_t *data;
-
-	data = g_new0(inotify_data_t, 1);
-
-	data->path = g_strdup(path);
-	data->wd = wd;
-	data->busy = FALSE;
-	if (wd == GAM_INOTIFY_WD_MISSING)
-		data->missing = TRUE;
-	else
-		data->missing = FALSE;
-	if (wd == GAM_INOTIFY_WD_PERM)
-		data->permission = TRUE;
-	else
-		data->permission = FALSE;
-	if (wd == GAM_INOTIFY_WD_LINK)
-		data->link = TRUE;
-	else
-		data->link = FALSE;
-	data->deactivated = FALSE;
-	data->ignored = FALSE;
-	data->refcount = 1;
-	data->events = 0;
-	data->deactivated_events = 0;
-	data->ignored_events = 0;
-	data->dir = dir;
-	
-	return data;
-}
-
-static void
-gam_inotify_data_free(inotify_data_t * data)
-{
-	if (data->refcount != 0) 
-		GAM_DEBUG(DEBUG_INFO, "gam_inotify_data_free called with reffed data.\n");
-
-	g_free(data->path);
-	g_free(data);
-}
-
-static inotify_event_t *
-gam_inotify_event_new (struct inotify_event *event)
-{
-	inotify_event_t *gam_event;
-	GTimeVal tv;
-
-	gam_event = g_new0(inotify_event_t, 1);
-
-	gam_event->wd = event->wd;
-	gam_event->mask = event->mask;
-	gam_event->cookie = event->cookie;
-
-	if (event->len) 
-	{
-		gam_event->name = g_strdup (event->name);
-	} else {
-		gam_event->name = g_strdup ("");
-	}
-
-	g_get_current_time (&tv);
-	g_time_val_add (&tv, DEFAULT_HOLD_UNTIL_TIME);
-	gam_event->hold_until = tv;
-
-	return gam_event;
-}
-
-static void
-gam_inotify_event_free (inotify_event_t *event)
-{
-	g_free (event->name);
-	g_free (event);
-}
-
-static void
-gam_inotify_event_pair_with (inotify_event_t *event1, inotify_event_t *event2)
-{
-	g_assert (event1 && event2);
-	/* We should only be pairing events that have the same cookie */
-	g_assert (event1->cookie == event2->cookie);
-	/* We shouldn't pair an event that already is paired */
-	g_assert (event1->pair == NULL && event2->pair == NULL);
-	event1->pair = event2;
-	event2->pair = event1;
-	
-	GAM_DEBUG(DEBUG_INFO, "inotify: pairing a MOVE together\n");
-	if (g_timeval_lt (&event1->hold_until, &event2->hold_until))
-		event1->hold_until = event2->hold_until;
-
-	event2->hold_until = event1->hold_until;
-}
-
-static void
-gam_inotify_event_add_microseconds (inotify_event_t *event, glong ms)
-{
-	g_assert (event);
-	g_time_val_add (&event->hold_until, ms);
-}
-
-static gboolean
-gam_inotify_event_ready (inotify_event_t *event)
-{
-	GTimeVal tv;
-	g_assert (event);
-
-	g_get_current_time (&tv);
-
-	/* An event is ready if,
-	 *
-	 * it has no cookie -- there is nothing to be gained by holding it
-	 * or, it is already paired -- we don't need to hold it anymore
-	 * or, we have held it long enough
-	 */
-	return event->cookie == 0 || 
-	       event->pair != NULL ||
-	       g_timeval_lt(&event->hold_until, &tv) || g_timeval_eq(&event->hold_until, &tv);
-}
-
 static void
-gam_inotify_emit_one_event (inotify_data_t *data, inotify_event_t *event, GamSubscription *sub)
+gam_inotify_send_initial_events (const char *pathname, GamSubscription *sub, gboolean is_dir, gboolean was_missing)
 {
-	gint force = 1;
-	gint is_dir_node = 0;
 	GaminEventType gevent;
-	gchar *fullpath = NULL;
-	gboolean watching_dir_as_file;
-	gboolean watching_file_as_dir;
 
-	g_assert (data && event);
+	gevent = was_missing ? GAMIN_EVENT_CREATED : GAMIN_EVENT_EXISTS;
 
-	is_dir_node = event->mask & IN_ISDIR;
-	watching_dir_as_file = data->dir && !gam_subscription_is_dir (sub);
-	watching_file_as_dir = !data->dir && gam_subscription_is_dir (sub);
+	gam_server_emit_one_event (pathname, is_dir ? 1 : 0, gevent, sub, 1);
 
-	if (watching_dir_as_file)
+	if (is_dir) 
 	{
-		gevent = gam_inotify_mask_to_gam_file_event (event->mask);
-		fullpath = g_strdup (data->path);
-	} else if (watching_file_as_dir) {
-		gevent = gam_inotify_mask_to_gam_dir_event (event->mask);
-		fullpath = g_strdup (data->path);
-	} else {
-		gevent = mask_to_gam_event (event->mask);
-		if (strlen (event->name) == 0)
-			fullpath = g_strdup (data->path);
-		else
-			fullpath = g_strdup_printf ("%s/%s", data->path, event->name);
-	}
-
-	if (gevent == GAMIN_EVENT_UNKNOWN) {
-		GAM_DEBUG(DEBUG_INFO, "inotify: Not handling event %d\n", event->mask);
-		g_free (fullpath);
-		return;
-	}
-
-	GAM_DEBUG(DEBUG_INFO, "inotify: Emitting %s on %s\n", gam_event_to_string (gevent), fullpath);
-	gam_server_emit_one_event (fullpath, is_dir_node, gevent, sub, force);
-	g_free(fullpath);
-}
-
-static void
-gam_inotify_emit_events (inotify_data_t *data, inotify_event_t *event)
-{
-	GList *l;
-
-	if (!data||!event)
-		return;
-
-	for (l = data->subs; l; l = l->next) {
-		GamSubscription *sub = l->data;
-		gam_inotify_emit_one_event (data, event, sub);
-	}
-}
-
-static void
-gam_inotify_process_event (inotify_event_t *event)
-{
-	inotify_data_t *data = NULL;
-
-	data = g_hash_table_lookup (wd_hash, GINT_TO_POINTER(event->wd));
-
-	if (!data) 
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify: got %s event for unknown wd %d\n", mask_to_string (event->mask), event->wd);
-		return;
-	}
-
-	if (data->deactivated) 
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify: ignoring event on temporarily deactivated watch %s\n", data->path);
-		data->deactivated_events++;
-		return;
-	}
-
-	if (data->ignored) {
-		GAM_DEBUG (DEBUG_INFO, "inotify: got event on ignored watch %s\n", data->path);
-		data->ignored_events++;
-		return;
-	} 
-
-	if (event->mask & IN_IGNORED) 
-	{
-		data->ignored = TRUE;
-		data->ignored_events++;
-		return;
-	}
-
-	if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify: resource %s went away. Adding it to missing list\n", data->path);
-		/* Remove the wd from the hash table */
-		g_hash_table_remove (wd_hash, GINT_TO_POINTER(data->wd));
-#ifdef GAMIN_DEBUG_API
-		gam_debug_report(GAMDnotifyDelete, data->path, 0);
-#endif
-		/* Send delete event */
-		gam_inotify_emit_events (data, event);
-		data->events++;
-		/* Set state bits in struct */
-		data->wd = GAM_INOTIFY_WD_MISSING;
-		data->missing = TRUE;
-		data->permission = FALSE;
-		data->dir = FALSE;
-		/* Add path to missing list */
-		gam_inotify_add_missing (data->path, FALSE);
-		return;
-	}
-
-	if (event->mask & GAM_INOTIFY_MASK)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify: got %s on = %s/%s\n",  mask_to_string (event->mask), data->path, event->name);
-		gam_inotify_emit_events (data, event);
-		data->events++;
-		return;
-	}
-
-	if (event->mask & IN_Q_OVERFLOW) 
-	{
-		/* At this point we have missed some events, and no longer have a consistent
-		 * view of the filesystem.
-		 */
-		// XXX: Kill server and hope for the best?
-		// XXX: Or we could send_initial_events , does this work for FAM?
-		GAM_DEBUG (DEBUG_INFO, "inotify: DANGER, queue over flowed! Events have been missed.\n");
-		return;
-	}
-
-	GAM_DEBUG(DEBUG_INFO, "inotify: error event->mask = %d\n", event->mask);
-}
-
-static void
-gam_inotify_pair_moves (gpointer data, gpointer user_data)
-{
-	inotify_event_t *event = (inotify_event_t *)data;
-
-	if (event->seen == TRUE || event->sent == TRUE)
-		return;
+		GDir *dir;
+		GError *err = NULL;
+		dir = g_dir_open (pathname, 0, &err);
+		if (dir)
+		{
+			const char *filename;
 
-	if (event->cookie != 0)
-	{
-		if (event->mask & IN_MOVED_FROM) {
-			g_hash_table_insert (cookie_hash, GINT_TO_POINTER(event->cookie), event);
-			gam_inotify_event_add_microseconds (event, MOVE_HOLD_UNTIL_TIME);
-		} else if (event->mask & IN_MOVED_TO) {
-			inotify_event_t *match = NULL;
-			match = g_hash_table_lookup (cookie_hash, GINT_TO_POINTER(event->cookie));
-			if (match) {
-				g_hash_table_remove (cookie_hash, GINT_TO_POINTER(event->cookie));
-				gam_inotify_event_pair_with (match, event);
+			while ((filename = g_dir_read_name (dir)))
+			{
+				gchar *fullname = g_strdup_printf ("%s/%s", pathname, filename);
+				gboolean file_is_dir = FALSE;
+				struct stat fsb;
+				memset(&fsb, 0, sizeof (struct stat));
+				lstat(fullname, &fsb);
+				file_is_dir = (fsb.st_mode & S_IFDIR) != 0 ? TRUE : FALSE;
+				gam_server_emit_one_event (fullname, file_is_dir ? 1 : 0, gevent, sub, 1);
+				g_free (fullname);
 			}
-		}
-	}
-	event->seen = TRUE;
-}
-
-static void
-gam_inotify_process_internal ()
-{
-	int ecount = 0;
-	g_queue_foreach (events_to_process, gam_inotify_pair_moves, NULL);
 
-	while (!g_queue_is_empty (events_to_process)) 
-	{
-		inotify_event_t *event = g_queue_peek_head (events_to_process);
-
-		if (!gam_inotify_event_ready (event)) {
-			GAM_DEBUG(DEBUG_INFO, "inotify: event not ready\n");
-			break;
-		}
-
-		/* Pop it */
-		event = g_queue_pop_head (events_to_process);
-		/* This must have been sent as part of a MOVED_TO/MOVED_FROM */
-		if (event->sent)
-			continue;
-
-		/* Check if this is a MOVED_FROM that is also sitting in the cookie_hash */
-		if (event->cookie && event->pair == NULL &&
-			g_hash_table_lookup (cookie_hash, GINT_TO_POINTER(event->cookie)))
-		{
-			g_hash_table_remove (cookie_hash, GINT_TO_POINTER(event->cookie));
-			event->sent = TRUE;
-		}
-		
-		g_queue_push_tail (event_queue, event);
-		ecount++;
-		if (event->pair) {
-			// if this event has a pair
-			event->pair->sent = TRUE;
-			g_queue_push_tail (event_queue, event->pair);
-			ecount++;
+			g_dir_close (dir);
+		} else {
+			GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", pathname, err->message);
+			g_error_free (err);
 		}
 
 	}
-	if (ecount)
-		GAM_DEBUG(DEBUG_INFO, "inotify: moved %d events to event queue\n", ecount);
-}
 
-static gboolean
-gam_inotify_process_event_queue (gpointer data)
-{
-	/* Try and move as many events to the event queue */
-	gam_inotify_process_internal ();
-
-	/* Send the events on the event queue to gam clients */
-	while (!g_queue_is_empty (event_queue))
+	if (!was_missing) 
 	{
-		inotify_event_t *event = g_queue_pop_head (event_queue);
-		g_assert (event);
-		gam_inotify_process_event (event);
-		gam_inotify_event_free (event);
+		gam_server_emit_one_event (pathname, is_dir ? 1 : 0, GAMIN_EVENT_ENDEXISTS, sub, 1);
 	}
 
-	return TRUE;
-}
-
-static gboolean
-gam_inotify_read_handler(gpointer user_data)
-{
-	gchar *buffer;
-	gsize buffer_size, buffer_i, events;
-
-        gam_inotify_read_events (&buffer_size, &buffer);
-
-        buffer_i = 0;
-        events = 0;
-        while (buffer_i < buffer_size) 
-	{
-                struct inotify_event *event;
-                gsize event_size;
-                event = (struct inotify_event *)&buffer[buffer_i];
-                event_size = sizeof(struct inotify_event) + event->len;
-		g_queue_push_tail (events_to_process, gam_inotify_event_new (event));
-                buffer_i += event_size;
-                events++;
-        }
-
-	GAM_DEBUG(DEBUG_INFO, "inotify recieved %d events\n", events);
-        return TRUE;
 }
 
 static void
-gam_inotify_send_initial_events (inotify_data_t *data, GamSubscription *sub)
+gam_inotify_event_callback (const char *fullpath, guint32 mask, void *subdata)
 {
+	GamSubscription *sub = (GamSubscription *)subdata;
 	GaminEventType gevent;
-	gboolean is_dir = FALSE;
-	gboolean was_missing = data->missing;
-	gboolean was_permission = data->permission;
-	gboolean exists = FALSE;
-#if 0
-	gboolean watching_dir_as_file = data->dir && !gam_subscription_is_dir (sub);
-#endif
-	gboolean watching_file_as_dir = FALSE;
-	
-	struct stat sb;
-	memset(&sb, 0, sizeof (struct stat));
-	
-	exists = lstat (data->path, &sb) >= 0;
-	is_dir = (exists && (sb.st_mode & S_IFDIR) != 0) ? TRUE : FALSE;
-
-	if (was_missing) {
-		GAM_DEBUG (DEBUG_INFO, "inotify: Sending initial events for %s -- WAS_MISSING\n", data->path);
-	} else if (was_permission) {
-		GAM_DEBUG (DEBUG_INFO, "inotify: Sending initial events for %s -- WAS_PERMISSION\n", data->path);
-	} else {
-		GAM_DEBUG (DEBUG_INFO, "inotify: Sending initial events for %s\n", data->path);
-	}
-
-	if (data->wd >= 0)
-		watching_file_as_dir = !data->dir && gam_subscription_is_dir (sub);
-	else
-		watching_file_as_dir = FALSE;
-
-	if (!watching_file_as_dir && exists) 
-	{
-		gevent = was_permission ? GAMIN_EVENT_EXISTS : was_missing ? GAMIN_EVENT_CREATED : GAMIN_EVENT_EXISTS;
-
-		gam_server_emit_one_event (data->path, is_dir ? 1 : 0, gevent, sub, 1);
 
-		if (is_dir) 
-		{
-			GDir *dir;
-			GError *err = NULL;
-			dir = g_dir_open (data->path, 0, &err);
-			if (dir)
-			{
-				const char *filename;
-
-				while ((filename = g_dir_read_name (dir)))
-				{
-					gchar *fullname = g_strdup_printf ("%s/%s", data->path, filename);
-					gboolean file_is_dir = FALSE;
-					struct stat fsb;
-					memset(&fsb, 0, sizeof (struct stat));
-					lstat(fullname, &fsb);
-					file_is_dir = (fsb.st_mode & S_IFDIR) != 0 ? TRUE : FALSE;
-					gam_server_emit_one_event (fullname, file_is_dir ? 1 : 0, gevent, sub, 1);
-					g_free (fullname);
-				}
-
-				g_dir_close (dir);
-			} else {
-				GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", data->path, err->message);
-				g_error_free (err);
-			}
+	gevent = ih_mask_to_EventType (mask);
 
-		}
-
-		if (!was_missing) 
-		{
-			gam_server_emit_one_event (data->path, is_dir ? 1 : 0, GAMIN_EVENT_ENDEXISTS, sub, 1);
-		}
-
-	} else {
-		gam_server_emit_one_event (data->path, is_dir ? 1 : 0, GAMIN_EVENT_DELETED, sub, 1);
-		gam_server_emit_one_event (data->path, is_dir ? 1 : 0, GAMIN_EVENT_ENDEXISTS, sub, 1);
-	}
+	gam_server_emit_one_event (fullpath, gam_subscription_is_dir (sub), gevent, sub, 1);
 }
 
 static void
-gam_inotify_send_initial_events_all (inotify_data_t *data)
-{
-	GList *l;
-
-	if (!data)
-		return;
-
-	for (l = data->subs; l; l = l->next) {
-		GamSubscription *sub = l->data;
-		gam_inotify_send_initial_events (data, sub);
-	}
-
-}
-
-/**
- * Adds a subscription to be monitored.
- *
- * @param sub a #GamSubscription to be polled
- * @returns TRUE if adding the subscription succeeded, FALSE otherwise
- */
-gboolean
-gam_inotify_add_subscription(GamSubscription * sub)
-{
-	const char *path = gam_subscription_get_path (sub);
-	inotify_data_t *data = g_hash_table_lookup (path_hash, path);
-	int wd, err;
-
-
-	if (data) 
-	{
-		data->subs = g_list_prepend (data->subs, sub);
-		data->refcount++;
-		gam_inotify_send_initial_events (data, sub);
-#ifdef GAMIN_DEBUG_API
-		gam_debug_report(GAMDnotifyChange, path, data->refcount);
-#endif
-		gam_listener_add_subscription(gam_subscription_get_listener(sub), sub);
-		return TRUE;
-	}
-
-	wd = gam_inotify_add_watch (path, GAM_INOTIFY_MASK, &err);
-	if (wd < 0) {
-		GAM_DEBUG (DEBUG_INFO, "inotify: could not add watch for %s\n", path);
-		if (err == EACCES) {
-			GAM_DEBUG (DEBUG_INFO, "inotify: adding %s to missing list PERM\n", path);
-		} else {
-			GAM_DEBUG (DEBUG_INFO, "inotify: adding %s to missing list MISSING\n", path);
-		}
-		data = gam_inotify_data_new (path, err == EACCES ? GAM_INOTIFY_WD_PERM : GAM_INOTIFY_WD_MISSING, FALSE);
-		gam_inotify_add_missing (path, err == EACCES ? TRUE : FALSE);
-	} else if (gam_inotify_is_link (path)) {
-		/* The file turned out to be a link, cancel the watch, and add it to the links list */
-		gam_inotify_rm_watch (path, wd);
-		GAM_DEBUG (DEBUG_INFO, "inotify: could not add watch for %s\n", path);
-		GAM_DEBUG (DEBUG_INFO, "inotify: adding %s to links list\n", path);
-		data = gam_inotify_data_new (path, GAM_INOTIFY_WD_LINK, FALSE);
-		gam_inotify_add_link (path);
-	} else {
-		struct stat sbuf;
-		memset(&sbuf, 0, sizeof (struct stat));
-		lstat (path, &sbuf);
-		/* Just in case,
-		 * Clear this path off the missing list */
-		gam_inotify_rm_missing (path);
-		data = gam_inotify_data_new (path, wd, sbuf.st_mode & S_IFDIR);
-		g_hash_table_insert(wd_hash, GINT_TO_POINTER(data->wd), data);
-	}
-
-#ifdef GAMIN_DEBUG_API
-	gam_debug_report(GAMDnotifyCreate, path, 0);
-#endif
-	gam_listener_add_subscription(gam_subscription_get_listener(sub), sub);
-
-	g_hash_table_insert(path_hash, data->path, data);
-	data->subs = g_list_prepend (data->subs, sub);
-	gam_inotify_send_initial_events (data, sub);
-	return TRUE;
-}
-
-/**
- * Removes a subscription which was being monitored.
- *
- * @param sub a #GamSubscription to remove
- * @returns TRUE if removing the subscription succeeded, FALSE otherwise
- */
-gboolean
-gam_inotify_remove_subscription(GamSubscription * sub)
+gam_inotify_found_callback (const char *fullpath, void *subdata)
 {
-	const char *path = gam_subscription_get_path (sub);
-	inotify_data_t *data = g_hash_table_lookup (path_hash, path);
-
-	g_assert (g_list_find (data->subs, sub));
-
-	data->subs = g_list_remove_all (data->subs, sub);
-	data->refcount--;
-	/* No one is watching this path anymore */
-	if (!data->subs && data->refcount == 0)
-	{
-		if (data->link)
-		{
-			g_assert (data->wd == GAM_INOTIFY_WD_LINK);
-			g_assert (data->missing == FALSE && data->permission == FALSE);
-			g_hash_table_remove (path_hash, data->path);
-			gam_inotify_rm_link (data->path);
-		} else if (data->missing) {
-			g_assert (data->wd == GAM_INOTIFY_WD_MISSING);
-			g_assert (data->link == FALSE && data->permission == FALSE);
-			g_hash_table_remove (path_hash, data->path);
-			gam_inotify_rm_missing (data->path);
-		} else if (data->permission) {
-			g_assert (data->wd == GAM_INOTIFY_WD_PERM);
-			g_assert (data->link == FALSE && data->missing == FALSE);
-			g_hash_table_remove (path_hash, data->path);
-			gam_inotify_rm_missing (data->path);
-		} else {
-			g_hash_table_remove (wd_hash, GINT_TO_POINTER(data->wd));
-			g_hash_table_remove (path_hash, data->path);
-			gam_inotify_rm_watch (data->path, data->wd);
-		}
-#ifdef GAMIN_DEBUG_API
-		gam_debug_report(GAMDnotifyDelete, path, 0);
-#endif
-		gam_inotify_data_free (data);
-	} else {
-#ifdef GAMIN_DEBUG_API
-		gam_debug_report(GAMDnotifyChange, path, data->refcount);
-#endif
-	}
-
-	gam_subscription_cancel (sub);
+	GamSubscription *sub = (GamSubscription *)subdata;
 
-	return TRUE;
+	gam_inotify_send_initial_events (gam_subscription_get_path (sub), sub, gam_subscription_is_dir (sub), TRUE);
 }
 
-/**
- * Stop monitoring all subscriptions for a given listener.
- *
- * @param listener a #GamListener
- * @returns TRUE if removing the subscriptions succeeded, FALSE otherwise
- */
-gboolean
-gam_inotify_remove_all_for(GamListener * listener)
-{
-	GList *subs;
-	GList *l;
-	gboolean success = TRUE;
-
-	subs = gam_listener_get_subscriptions(listener);
-
-	for (l = subs; l != NULL; l = l->next)
-		if (!gam_inotify_remove_subscription(l->data))
-			success = FALSE;
 
-	g_list_free(subs);
-
-	return success;
-}
-
-/**
- * Initializes the inotify backend.  This must be called before
- * any other functions in this module.
- *
- * @returns TRUE if initialization succeeded, FALSE otherwise
- */
 gboolean
-gam_inotify_init(void)
+gam_inotify_init (void)
 {
-    GSource *source;
-
-    inotify_device_fd = inotify_init ();
-
-    if (inotify_device_fd < 0) {
-        GAM_DEBUG(DEBUG_INFO, "Could not initialize inotify\n");
-        return FALSE;
-    }
-
-    inotify_read_ioc = g_io_channel_unix_new(inotify_device_fd);
-
-    g_io_channel_set_encoding(inotify_read_ioc, NULL, NULL);
-    g_io_channel_set_flags(inotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL);
-
-    source = g_io_create_watch(inotify_read_ioc,
-			       G_IO_IN | G_IO_HUP | G_IO_ERR);
-    g_source_set_callback(source, gam_inotify_read_handler, NULL, NULL);
-    g_source_attach(source, NULL);
-    g_source_unref (source);
-    g_timeout_add (SCAN_MISSING_TIME, gam_inotify_scan_missing, NULL);
-    g_timeout_add (SCAN_LINKS_TIME, gam_inotify_scan_links, NULL);
-    g_timeout_add (PROCESS_EVENTS_TIME, gam_inotify_process_event_queue, NULL);
-
-    path_hash = g_hash_table_new(g_str_hash, g_str_equal);
-    wd_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
-    cookie_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
-    event_queue = g_queue_new ();
-    events_to_process = g_queue_new ();
-
-	gam_poll_basic_init ();
-	gam_server_install_kernel_hooks (GAMIN_K_INOTIFY2,
+	gam_server_install_kernel_hooks (GAMIN_K_INOTIFY2, 
 					 gam_inotify_add_subscription,
 					 gam_inotify_remove_subscription,
-					 gam_inotify_remove_all_for, NULL, NULL);
-
-	GAM_DEBUG(DEBUG_INFO, "inotify backend initialized\n");
-
-
-    return TRUE;
-}
-
-int gam_inotify_add_watch (const char *path, __u32 mask, int *err)
-{
-	int wd = -1;
-
-	g_assert (path != NULL);
-	g_assert (inotify_device_fd >= 0);
-
-	wd = inotify_add_watch (inotify_device_fd, path, mask);
-
-	if (wd < 0)
-	{
-		int e = errno;
-		GAM_DEBUG (DEBUG_INFO, "inotify: failed to add watch for %s\n", path);
-		GAM_DEBUG (DEBUG_INFO, "inotify: reason %d = %s\n", e, strerror (e));
-		if (err)
-			*err = e;
-		return wd;
-	} 
-	else 
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify: success adding watch for %s (wd = %d)\n", path, wd);
-	}
-
-	g_assert (wd >= 0);
-
-	return wd;
-}
-
-int gam_inotify_rm_watch (const char *path, __u32 wd)
-{
-	g_assert (wd >= 0);
-
-	if (inotify_rm_watch (inotify_device_fd, wd) < 0) 
-	{
-		int e = errno;
-		GAM_DEBUG (DEBUG_INFO, "inotify: failed to rm watch for %s (wd = %d)\n", path, wd);
-		GAM_DEBUG (DEBUG_INFO, "inotify: reason = %s\n", strerror (e));
-		return -1;
-	}
-	else
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify: success removing watch for %s (wd = %d)\n", path, wd);
-	}
-
-	return 0;
-}
-
-/* Code below based on beagle inotify glue code. I assume it was written by Robert Love */
-#define MAX_PENDING_COUNT 5
-#define PENDING_THRESHOLD(qsize) ((qsize) >> 1)
-#define PENDING_MARGINAL_COST(p) ((unsigned int)(1 << (p)))
-#define MAX_QUEUED_EVENTS 8192
-#define AVERAGE_EVENT_SIZE sizeof (struct inotify_event) + 16
-#define PENDING_PAUSE_MICROSECONDS 8000
-
-void gam_inotify_read_events (gsize *buffer_size_out, gchar **buffer_out)
-{
-        static int prev_pending = 0, pending_count = 0;
-        static gchar *buffer = NULL;
-        static gsize buffer_size;
-
-
-        /* Initialize the buffer on our first read() */
-        if (buffer == NULL)
-        {
-                buffer_size = AVERAGE_EVENT_SIZE;
-                buffer_size *= MAX_QUEUED_EVENTS;
-                buffer = g_malloc (buffer_size);
-
-                if (!buffer) {
-                        *buffer_size_out = 0;
-                        *buffer_out = NULL;
-                        GAM_DEBUG (DEBUG_INFO, "inotify: could not allocate read buffer\n");
-                        return;
-                }
-        }
-
-        *buffer_size_out = 0;
-        *buffer_out = NULL;
-
-        while (pending_count < MAX_PENDING_COUNT) {
-                unsigned int pending;
-
-                if (ioctl (inotify_device_fd, FIONREAD, &pending) == -1)
-                        break;
-
-                pending /= AVERAGE_EVENT_SIZE;
-
-                /* Don't wait if the number of pending events is too close
-                 * to the maximum queue size.
-                 */
-
-                if (pending > PENDING_THRESHOLD (MAX_QUEUED_EVENTS))
-                        break;
-
-                /* With each successive iteration, the minimum rate for
-                 * further sleep doubles. */
-
-                if (pending-prev_pending < PENDING_MARGINAL_COST(pending_count))
-                        break;
-
-		prev_pending = pending;
-                pending_count++;
-
-                /* We sleep for a bit and try again */
-                g_usleep (PENDING_PAUSE_MICROSECONDS);
-        }
-
-	memset(buffer, 0, buffer_size);
-
-        if (g_io_channel_read_chars (inotify_read_ioc, (char *)buffer, buffer_size, buffer_size_out, NULL) != G_IO_STATUS_NORMAL) {
-                GAM_DEBUG (DEBUG_INFO, "inotify: failed to read from buffer\n");
-        }
-        *buffer_out = buffer;
-
-        prev_pending = 0;
-        pending_count = 0;
-}
-
-gboolean gam_inotify_is_missing (const char *path)
-{
-	struct stat sbuf;
-
-	/* If the file doesn't exist, it is missing. */
-	if (lstat (path, &sbuf) < 0)
-		return TRUE;
-
-	/* If we can't read the file, it is missing. */
-	if (access (path, R_OK) < 0)
-		return TRUE;
-
-	return FALSE;
-}
-
-static gint missing_list_compare (gconstpointer a, gconstpointer b)
-{
-	const inotify_missing_t *missing = NULL;
+					 gam_inotify_remove_all_for,
+					 NULL, NULL);
 	
-	g_assert (a);
-	g_assert (b);
-	missing = a;
-	g_assert (missing->path);
-
-	return strcmp (missing->path, b);
-}
-
-static void gam_inotify_add_missing (const char *path, gboolean perm)
-{
-	inotify_missing_t *missing = NULL;
-
-	g_assert (path);
-
-	missing = g_new0 (inotify_missing_t, 1);
-
-	g_assert (missing);
-
-	missing->path = g_strdup (path);
-	missing->scan_interval = gam_fs_get_poll_timeout (path);
-	missing->last_scan_time = time (NULL);
-	missing->permission = perm;
-
-	GAM_DEBUG (DEBUG_INFO, "inotify-missing: add - %s\n", path);
-
-	missing_list = g_list_prepend (missing_list, missing);
-}
-
-static void gam_inotify_rm_missing (const char *path)
-{
-	GList *node = NULL;
-	inotify_missing_t *missing = NULL;
-
-	g_assert (path && *path);
-
-	node = g_list_find_custom (missing_list, path, missing_list_compare);
-
-	if (!node)
-		return;
-
-	GAM_DEBUG (DEBUG_INFO, "inotify-missing: rm - %s\n", path);
-	missing = node->data;
-	g_free (missing->path);
-	g_free (missing);
-
-	missing_list = g_list_remove_link (missing_list, node);
-}
-
-static gboolean gam_inotify_nolonger_missing (const char *path)
-{
-	int wd = -1, err;
-	inotify_data_t *data = NULL;
-	struct stat sbuf;
-	memset(&sbuf, 0, sizeof (struct stat));
-
-	data = g_hash_table_lookup (path_hash, path);
-	if (!data) {
-		GAM_DEBUG (DEBUG_INFO, "inotify: Could not find missing %s in hash table.\n", path);
-		return FALSE;
-	}
-
-	g_assert ((data->missing == TRUE || data->permission == TRUE) && data->link == FALSE);
-
-	wd = gam_inotify_add_watch (path, GAM_INOTIFY_MASK,&err);
-	if (wd < 0) {
-		/* Check if we don't have access to the new file */
-		if (err == EACCES)
-		{
-			data->wd = GAM_INOTIFY_WD_PERM;
-			data->permission = TRUE;
-			data->missing = FALSE;
-		} else {
-			data->wd = GAM_INOTIFY_WD_MISSING;
-			data->permission = FALSE;
-			data->missing = TRUE;
-		}
-		return FALSE;
-	} else if (gam_inotify_is_link (path)) {
-		GAM_DEBUG(DEBUG_INFO, "inotify: Missing resource %s exists now BUT IT IS A LINK\n", path);
-		/* XXX: See NOTE1 */
-		if (g_hash_table_lookup (wd_hash, GINT_TO_POINTER(wd)) == NULL)
-			gam_inotify_rm_watch (path, wd);
-		data->missing = FALSE;
-		data->permission = FALSE;
-		data->link = TRUE;
-		data->wd = GAM_INOTIFY_WD_LINK;
-		gam_inotify_add_link (path);
-		gam_inotify_send_initial_events_all (data);
-		return TRUE;
-	}
-
-
-	GAM_DEBUG(DEBUG_INFO, "inotify: Missing resource %s exists now\n", path);
-
-	lstat (path, &sbuf);
-	data->dir = (sbuf.st_mode & S_IFDIR);
-	data->wd = wd;
-	g_hash_table_insert(wd_hash, GINT_TO_POINTER(data->wd), data);
-	gam_inotify_send_initial_events_all (data);
-	data->missing = FALSE;
-	data->permission = FALSE;
-
-	return TRUE;
+	return ih_startup (gam_inotify_event_callback,
+			   gam_inotify_found_callback);
 }
 
-/* This function is called once per second in the main loop*/
-static gboolean gam_inotify_scan_missing (gpointer userdata)
+gboolean
+gam_inotify_add_subscription (GamSubscription *sub)
 {
-	guint i;
+	ih_sub_t *isub = NULL;
+	isub = ih_sub_new (gam_subscription_get_path (sub), gam_subscription_is_dir (sub), 0, sub);
 
-	gam_inotify_sanity_check ();
-	/* We have to walk the list like this because entries might be removed while we walk the list */
-	for (i = 0; ; i++)
+	if (!ih_sub_add (isub))
 	{
-		inotify_missing_t *missing = g_list_nth_data (missing_list, i);
-
-		if (!missing)
-			break;
-
-		/* Not enough time has passed since the last scan */
-		if (time(NULL) - missing->last_scan_time < missing->scan_interval)
-			continue;
-		
-		missing->last_scan_time = time(NULL);
-		if (!gam_inotify_is_missing (missing->path))
-		{
-			if (gam_inotify_nolonger_missing (missing->path))
-			{
-#ifdef GAMIN_DEBUG_API
-				gam_debug_report(GAMDnotifyCreate, missing->path, 0);
-#endif
-				gam_inotify_rm_missing (missing->path);
-			}
-		}
-	}
-
-	gam_inotify_sanity_check ();
-	return TRUE;
-}
-
-
-static gboolean	
-gam_inotify_is_link (const char *path)
-{
-	struct stat sbuf;
-
-	if (lstat(path, &sbuf) < 0)
-		return FALSE;
-
-	return S_ISLNK(sbuf.st_mode) != 0;
-}
-
-static gboolean 
-gam_inotify_nolonger_link (const char *path)
-{
-	int wd = -1, err;
-	inotify_data_t *data = NULL;
-	struct stat sbuf;
-	memset(&sbuf, 0, sizeof (struct stat));
-
-	GAM_DEBUG(DEBUG_INFO, "inotify: link resource %s no longer a link\n", path);
-	data = g_hash_table_lookup (path_hash, path);
-	if (!data) {
-		GAM_DEBUG (DEBUG_INFO, "inotify: Could not find link %s in hash table.\n", path);
+		ih_sub_free (isub);
 		return FALSE;
 	}
 
-	g_assert (data->link == TRUE && data->missing == FALSE && data->permission == FALSE);
-
-	wd = gam_inotify_add_watch (path, GAM_INOTIFY_MASK, &err);
-	if (wd < 0) {
-		/* The file must not exist anymore, so we add it to the missing list */
-		data->link = FALSE;
-		/* Check if we don't have access to the new file */
-		if (err == EACCES)
-		{
-			data->wd = GAM_INOTIFY_WD_PERM;
-			data->permission = TRUE;
-			data->missing = FALSE;
-		} else {
-			data->wd = GAM_INOTIFY_WD_MISSING;
-			data->permission = FALSE;
-			data->missing = TRUE;
-		}
-
-		gam_server_emit_event (path, data->dir, GAMIN_EVENT_DELETED, data->subs, 1);
-		gam_inotify_add_missing (path, data->permission);
-		return TRUE;
-	} else if (gam_inotify_is_link (path)) {
-		GAM_DEBUG(DEBUG_INFO, "inotify: Link resource %s re-appeared as a link...\n", path);
-		/* NOTE1: This is tricky, because inotify works on the inode level and
-		 * we are dealing with a link, we can be watching the same inode 
-		 * from two different paths (the wd's will be the same). So,
-		 * if the wd isn't in the hash table, we can remvoe the watch, 
-		 * otherwise we just leave the watch. This should probably be
-		 * handled by ref counting
-		 */
-		if (g_hash_table_lookup (wd_hash, GINT_TO_POINTER(wd)) == NULL)
-			gam_inotify_rm_watch (path, wd);
-		data->missing = FALSE;
-		data->permission = FALSE;
-		data->link = TRUE;
-		data->wd = GAM_INOTIFY_WD_LINK;
-		gam_inotify_send_initial_events_all (data);
-		return FALSE;
-	}
+	gam_inotify_send_initial_events (gam_subscription_get_path (sub), sub, gam_subscription_is_dir (sub), FALSE);
 
-	lstat (path, &sbuf);
-	data->dir = (sbuf.st_mode & S_IFDIR);
-	data->wd = wd;
-	g_hash_table_insert(wd_hash, GINT_TO_POINTER(data->wd), data);
-	gam_inotify_send_initial_events_all (data);
-	data->missing = FALSE;
-	data->permission = FALSE;
 	return TRUE;
 }
 
-static gint links_list_compare (gconstpointer a, gconstpointer b)
-{
-	const inotify_links_t *links = NULL;
-	
-	g_assert (a);
-	g_assert (b);
-	links = a;
-	g_assert (links->path);
-
-	return strcmp (links->path, b);
-}
-
-static void
-gam_inotify_add_link (const char *path)
-{
-	inotify_links_t *links = NULL;
-	struct stat sbuf;
-
-	g_assert (path);
-
-	links = g_new0 (inotify_links_t, 1);
-
-	g_assert (links);
-
-	GAM_DEBUG (DEBUG_INFO, "inotify-link: add - %s\n", path);
-	links->path = g_strdup (path);
-	links->scan_interval = gam_fs_get_poll_timeout (path);
-	links->last_scan_time = 0;
-	lstat(path, &sbuf);
-	links->sbuf = sbuf;
-	links_list = g_list_prepend (links_list, links);
-}
-
-static void
-gam_inotify_rm_link (const char *path)
+static gboolean
+gam_inotify_remove_sub_pred (ih_sub_t *sub, void *callerdata)
 {
-	GList *node = NULL;
-	inotify_links_t *links = NULL;
-
-	g_assert (path && *path);
-
-	node = g_list_find_custom (links_list, path, links_list_compare);
-
-	if (!node)
-		return;
-
-	GAM_DEBUG (DEBUG_INFO, "inotify-link: rm - %s\n", path);
-	links = node->data;
-	g_free (links->path);
-	g_free (links);
-
-	links_list = g_list_remove_link (links_list, node);
-
+	return sub->usersubdata == callerdata;
 }
 
-static gboolean 
-gam_inotify_scan_links (gpointer userdata)
+gboolean
+gam_inotify_remove_subscription (GamSubscription *sub)
 {
-	guint i;
-
-	gam_inotify_sanity_check ();
-	/* We have to walk the list like this because entries might be removed while we walk the list */
-	for (i = 0; ; i++)
-	{
-		inotify_links_t *links = g_list_nth_data (links_list, i);
-
-		if (!links)
-			break;
-
-		/* Not enough time has passed since the last scan */
-		if (time(NULL) - links->last_scan_time < links->scan_interval)
-			continue;
-		
-		links->last_scan_time = time(NULL);
-		if (!gam_inotify_is_link (links->path))
-		{
-			if (gam_inotify_nolonger_link (links->path))
-			{
-				gam_inotify_rm_link (links->path);
-			}
-		} else {
-			gam_inotify_poll_link (links);
-		}
-
-	}
+	ih_sub_foreach_free (sub, gam_inotify_remove_sub_pred);
 
-	gam_inotify_sanity_check ();
 	return TRUE;
 }
 
 static gboolean
-gam_inotify_stat_changed (struct stat sbuf1, struct stat sbuf2)
-{
-#ifdef ST_MTIM_NSEC
-	return ((sbuf1.st_mtim.tv_sec != sbuf2.st_mtim.tv_sec) ||
-		(sbuf1.st_mtim.tv_nsec != sbuf2.st_mtim.tv_nsec) ||
-		(sbuf1.st_size != sbuf2.st_size) ||
-		(sbuf1.st_ctim.tv_sec != sbuf2.st_ctim.tv_sec) ||
-		(sbuf1.st_ctim.tv_nsec != sbuf2.st_ctim.tv_nsec));
-#else
-	return ((sbuf1.st_mtime != sbuf2.st_mtime) ||
-		(sbuf1.st_size != sbuf2.st_size) ||
-		(sbuf1.st_ctime != sbuf2.st_ctime));
-#endif
-}
-
-static void
-gam_inotify_poll_link (inotify_links_t *links)
-{
-	struct stat sbuf;
-	g_assert (links);
-
-	/* Next time around, we will detect the deletion, and send the event */
-	if (lstat (links->path, &sbuf) < 0)
-		return;
-
-	if (gam_inotify_stat_changed (sbuf, links->sbuf))
-	{
-		inotify_data_t *data = g_hash_table_lookup (path_hash, links->path);
-		g_assert (data);
-		gam_server_emit_event (data->path, data->dir, GAMIN_EVENT_CHANGED, data->subs, 1);
-	}
-
-	links->sbuf = sbuf;
-}
-
-static void
-gam_inotify_wd_check (gpointer key, gpointer value, gpointer user_data)
-{
-	gint wd = GPOINTER_TO_INT(key);
-	inotify_data_t *data = (inotify_data_t *)value;
-	if (wd < 0)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: FAILURE wd hash for %s key < 0\n", data->path);
-	}
-	if (data->wd < 0)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: FAILURE wd hash for %s value < 0\n", data->path);
-	}
-	if (data->wd != wd) 
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: FAILURE wd hash value & key don't match\n");
-	}
-}
-
-static void
-gam_inotify_wd_hash_sanity_check (void)
-{
-	g_hash_table_foreach (wd_hash, gam_inotify_wd_check, NULL);
-}
-
-static void
-gam_inotify_missing_check (gpointer data, gpointer user_data)
+gam_inotify_remove_listener_pred (ih_sub_t *sub, void *callerdata)
 {
-	inotify_missing_t *missing = data;
-	inotify_data_t *idata = NULL;
-
-	if (!missing)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: Missing check called with NULL argument\n");
-		return;
-	}
-
-	if (!missing->path)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: Missing entry missing path name\n");
-		return;
-	}
-
-	idata = g_hash_table_lookup (path_hash, missing->path);
-
-	if (!idata) 
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: Could not find %s in path hash table\n", missing->path);
-		return;
-	}
-
-	if (idata->wd != GAM_INOTIFY_WD_MISSING && idata->wd != GAM_INOTIFY_WD_PERM) 
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->wd != GAM_INOTIFY_WD_(MISSING/PERM) for path in missing list\n");
-		return;
-	}
-
-	if (idata->missing != TRUE && idata->permission != TRUE)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->missing/permission != TRUE for path in missing list\n");
-		return;
-	}
-
-	if (idata->missing == TRUE && idata->wd != GAM_INOTIFY_WD_MISSING)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->missing == TRUE && idata->wd != GAM_INOTIFY_WD_MISSING\n");
-		return;
-	}
-
-	if (idata->permission == TRUE && idata->wd != GAM_INOTIFY_WD_PERM)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->permission == TRUE && idata->wd != GAM_INOTIFY_WD_PERM\n");
-		return;
-	}
+	GamSubscription *gsub = (GamSubscription *)sub->usersubdata;
 
-	if (idata->wd == GAM_INOTIFY_WD_MISSING && idata->missing != TRUE)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->missing == FALSE && idata->wd == GAM_INOTIFY_WD_MISSING\n");
-		return;
-	}
-
-	if (idata->wd == GAM_INOTIFY_WD_PERM && idata->permission != TRUE)
-	{
-		GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->permission != TRUE && idata->wd == GAM_INOTIFY_WD_PERM\n");
-		return;
-	}
+	return gam_subscription_get_listener (gsub) == callerdata;
 }
 
-static void
-gam_inotify_missing_list_sanity_check (void)
+gboolean
+gam_inotify_remove_all_for (GamListener *listener)
 {
-	g_list_foreach (missing_list, gam_inotify_missing_check, NULL);
-}
-
+	ih_sub_foreach_free (listener, gam_inotify_remove_listener_pred);
 
-static void
-gam_inotify_sanity_check (void)
-{
-#ifdef GAM_INOTIFY_SANITY
-	gam_inotify_wd_hash_sanity_check ();
-	gam_inotify_missing_list_sanity_check ();
-#endif
+	return TRUE;
 }
 
-static gboolean
-g_timeval_lt(GTimeVal *val1, GTimeVal *val2)
+void
+gam_inotify_debug (void)
 {
-	if (val1->tv_sec < val2->tv_sec)
-		return TRUE;
-
-	if (val1->tv_sec > val2->tv_sec)
-		return FALSE;
-
-	/* val1->tv_sec == val2->tv_sec */
-	if (val1->tv_usec < val2->tv_usec)
-		return TRUE;
-
-	return FALSE;
+	id_dump (NULL);
 }
 
-static gboolean
-g_timeval_eq(GTimeVal *val1, GTimeVal *val2)
+gboolean
+gam_inotify_is_running (void)
 {
-	return (val1->tv_sec == val2->tv_sec) && (val1->tv_usec == val2->tv_usec);
+	return ih_running ();
 }
-
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-sub.c	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,121 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* inotify-helper.c - Gnome VFS Monitor based on inotify.
+
+   Copyright (C) 2006 John McCutchan
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Authors: 
+		 John McCutchan <john@johnmccutchan.com>
+*/
+
+#include "config.h"
+#include <string.h>
+#include <glib.h>
+#include "gam_subscription.h"
+#include "inotify-sub.h"
+
+static gboolean     is_debug_enabled = FALSE;
+#define IS_W if (is_debug_enabled) g_warning
+
+static void ih_sub_setup (ih_sub_t *sub);
+
+ih_sub_t *
+ih_sub_new (const char *pathname, gboolean is_dir, guint32 flags, void *userdata)
+{
+	ih_sub_t *sub = NULL;
+
+	sub = g_new0 (ih_sub_t, 1);
+	sub->usersubdata = userdata;
+	sub->is_dir = is_dir;
+	sub->extra_flags = flags;
+	sub->pathname = g_strdup (pathname);
+
+	IS_W("new subscription for %s being setup\n", sub->pathname);
+
+	ih_sub_setup (sub);
+	return sub;
+}
+
+void
+ih_sub_free (ih_sub_t *sub)
+{
+	if (sub->filename)
+		g_free (sub->filename);
+	if (sub->dirname)
+	    g_free (sub->dirname);
+	g_free (sub->pathname);
+	g_free (sub);
+}
+
+static
+gchar *ih_sub_get_dirname (gchar *pathname)
+{
+	return g_path_get_dirname (pathname);
+}
+
+static
+gchar *ih_sub_get_filename (gchar *pathname)
+{
+	gchar *out;
+	// FIXME: return filename here
+	return out;
+}
+
+static 
+void ih_sub_fix_dirname (ih_sub_t *sub)
+{
+	size_t len = 0;
+	
+	g_assert (sub->dirname);
+
+	len = strlen (sub->dirname);
+
+	/* We need to strip a trailing slash
+	 * to get the correct behaviour
+	 * out of the kernel
+	 */
+	if (sub->dirname[len] == '/')
+		sub->dirname[len] = '\0';
+}
+
+/*
+ * XXX: Currently we just follow the gnome vfs monitor type flags when
+ * deciding how to treat the path. In the future we could try
+ * and determine whether the path points to a directory or a file but
+ * that is racey.
+ */
+static void
+ih_sub_setup (ih_sub_t *sub)
+{
+	if (sub->is_dir)
+	{
+		sub->dirname = g_strdup (sub->pathname);
+		sub->filename = NULL;
+	} else {
+		sub->dirname = ih_sub_get_dirname (sub->pathname);
+		sub->filename = ih_sub_get_filename (sub->pathname);
+	}
+
+	ih_sub_fix_dirname (sub);
+
+	IS_W("sub->dirname = %s\n", sub->dirname);
+	if (sub->filename)
+	{
+		IS_W("sub->filename = %s\n", sub->filename);
+	}
+}
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-helper.c	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,234 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* inotify-helper.c - Gnome VFS Monitor based on inotify.
+
+   Copyright (C) 2005 John McCutchan
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Authors: 
+		 John McCutchan <john@johnmccutchan.com>
+*/
+
+#include "config.h"
+#include <errno.h>
+#include <time.h>
+#include <string.h>
+#include <sys/ioctl.h>
+/* Just include the local header to stop all the pain */
+#include "local_inotify.h"
+#if 0
+#ifdef HAVE_SYS_INOTIFY_H
+/* We don't actually include the libc header, because there has been
+ * problems with libc versions that was built without inotify support.
+ * Instead we use the local version.
+ */
+#include "local_inotify.h"
+#elif defined (HAVE_LINUX_INOTIFY_H)
+#include <linux/inotify.h>
+#endif
+#endif
+#include "inotify-helper.h"
+#include "inotify-missing.h"
+#include "inotify-path.h"
+#include "inotify-diag.h"
+
+static gboolean		ih_debug_enabled = FALSE;
+#define IH_W if (ih_debug_enabled) g_warning 
+
+static void ih_event_callback (ik_event_t *event, ih_sub_t *sub);
+static void ih_found_callback (ih_sub_t *sub);
+
+/* We share this lock with inotify-kernel.c and inotify-missing.c
+ *
+ * inotify-kernel.c takes the lock when it reads events from
+ * the kernel and when it processes those events
+ *
+ * inotify-missing.c takes the lock when it is scanning the missing
+ * list.
+ * 
+ * We take the lock in all public functions 
+ */
+G_LOCK_DEFINE (inotify_lock);
+static GList *sub_list = NULL;
+static gboolean initialized = FALSE;
+static event_callback_t user_ecb = NULL;
+static found_callback_t user_fcb = NULL;
+
+/**
+ * Initializes the inotify backend.  This must be called before
+ * any other functions in this module.
+ *
+ * @returns TRUE if initialization succeeded, FALSE otherwise
+ */
+gboolean
+ih_startup (event_callback_t ecb,
+	    found_callback_t fcb)
+{
+	static gboolean result = FALSE;
+
+	G_LOCK(inotify_lock);
+	
+	if (initialized == TRUE) {
+		G_UNLOCK(inotify_lock);
+		return result;
+	}
+
+	initialized = TRUE;
+
+	result = ip_startup (ih_event_callback);
+	if (!result) {
+		g_warning( "Could not initialize inotify\n");
+		G_UNLOCK(inotify_lock);
+		return FALSE;
+	}
+	user_ecb = ecb;
+	user_fcb = fcb;
+	im_startup (ih_found_callback);
+	id_startup ();
+
+	IH_W ("started gnome-vfs inotify backend\n");
+
+	G_UNLOCK(inotify_lock);
+	return TRUE;
+}
+
+gboolean
+ih_running (void)
+{
+	return initialized;
+}
+
+/**
+ * Adds a subscription to be monitored.
+ */
+gboolean
+ih_sub_add (ih_sub_t * sub)
+{
+	G_LOCK(inotify_lock);
+	
+	g_assert (g_list_find (sub_list, sub) == NULL);
+
+	// make sure that sub isn't on sub_list first.
+	if (!ip_start_watching (sub))
+	{
+		im_add (sub);
+	}
+
+	sub_list = g_list_prepend (sub_list, sub);
+
+	G_UNLOCK(inotify_lock);
+	return TRUE;
+}
+
+/**
+ * Cancels a subscription which was being monitored.
+ */
+gboolean
+ih_sub_cancel (ih_sub_t * sub)
+{
+	G_LOCK(inotify_lock);
+
+
+	if (!sub->cancelled)
+	{
+		IH_W("cancelling %s\n", sub->pathname);
+		g_assert (g_list_find (sub_list, sub) != NULL);
+		sub->cancelled = TRUE;
+		im_rm (sub);
+		ip_stop_watching (sub);
+		sub_list = g_list_remove (sub_list, sub);
+	}
+
+	G_UNLOCK(inotify_lock);
+	return TRUE;
+}
+
+static void
+ih_sub_foreach_worker (void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata), gboolean free)
+{
+	GList *l = NULL;
+	GList *removed = NULL;
+
+	G_LOCK(inotify_lock);
+
+	for (l = sub_list; l; l = l->next)
+	{
+		ih_sub_t *sub = l->data;
+
+		if (f(sub, callerdata))
+		{
+			removed = g_list_prepend (removed, l);
+			ih_sub_cancel (sub);
+			if (free)
+				ih_sub_free (sub);
+		}
+	}
+
+	for (l = removed; l ; l = l->next)
+	{
+		GList *llink = l->data;
+		sub_list = g_list_remove_link (sub_list, llink);
+		g_list_free_1 (llink);
+	}
+
+	G_UNLOCK(inotify_lock);
+}
+
+void
+ih_sub_foreach (void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata))
+{
+	ih_sub_foreach_worker (callerdata, f, FALSE);
+}
+
+void
+ih_sub_foreach_free (void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata))
+{
+	ih_sub_foreach_worker (callerdata, f, TRUE);
+}
+
+static void ih_event_callback (ik_event_t *event, ih_sub_t *sub)
+{
+	gchar *fullpath;
+	if (event->name)
+	{
+		fullpath = g_strdup_printf ("%s/%s", sub->dirname, event->name);
+	} else {
+		fullpath = g_strdup_printf ("%s/", sub->dirname);
+	}
+
+	user_ecb (fullpath, event->mask, sub->usersubdata);
+	g_free(fullpath);
+}
+
+static void ih_found_callback (ih_sub_t *sub)
+{
+	gchar *fullpath;
+
+	if (sub->filename)
+	{
+		fullpath = g_strdup_printf ("%s/%s", sub->dirname, sub->filename);
+		if (!g_file_test (fullpath, G_FILE_TEST_EXISTS)) {
+			g_free (fullpath);
+			return;
+		}
+	} else {
+		fullpath = g_strdup_printf ("%s/", sub->dirname);
+	}
+
+	user_fcb (fullpath, sub->usersubdata);
+	g_free(fullpath);
+}
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-diag.c	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,67 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* inotify-helper.c - Gnome VFS Monitor based on inotify.
+
+   Copyright (C) 2005 John McCutchan
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Authors: 
+		 John McCutchan <john@johnmccutchan.com>
+*/
+
+#include "config.h"
+#include <glib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "inotify-missing.h"
+#include "inotify-path.h"
+#include "inotify-diag.h"
+
+#define DIAG_DUMP_TIME 20000 /* 20 seconds */
+G_LOCK_EXTERN (inotify_lock);
+
+gboolean id_dump (gpointer userdata)
+{
+	G_LOCK (inotify_lock);
+	GIOChannel *ioc = NULL;
+	pid_t pid = getpid();
+	char *fname = g_strdup_printf("/tmp/gvfsid.%d", pid);
+	ioc = g_io_channel_new_file (fname, "w", NULL);
+	g_free (fname);
+	if (!ioc)
+	{
+		G_UNLOCK (inotify_lock);
+		return TRUE;
+	}
+
+	im_diag_dump (ioc);
+
+	g_io_channel_shutdown (ioc, TRUE, NULL);
+	g_io_channel_unref (ioc);
+	G_UNLOCK (inotify_lock);
+	return TRUE;
+}
+
+void id_startup ()
+{
+	if (!g_getenv ("GNOME_VFS_INOTIFY_DIAG"))
+	{
+		return;
+	}
+	
+	g_timeout_add (DIAG_DUMP_TIME, id_dump, NULL);
+}
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-path.h	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,27 @@
+/*
+	Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; version 2.
+
+	This program 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 General Public License version 2 for more details.
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software Foundation,
+	Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#ifndef __INOTIFY_PATH_H
+#define __INOTIFY_PATH_H
+
+#include "inotify-kernel.h"
+#include "inotify-sub.h"
+
+gboolean ip_startup (void (*event_cb)(ik_event_t *event, ih_sub_t *sub));
+gboolean ip_start_watching (ih_sub_t *sub);
+gboolean ip_stop_watching  (ih_sub_t *sub);
+
+#endif
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-path.c	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,387 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* inotify-helper.c - Gnome VFS Monitor based on inotify.
+
+   Copyright (C) 2006 John McCutchan
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Authors: 
+		 John McCutchan <john@johnmccutchan.com>
+*/
+
+#include "config.h"
+
+/* Don't put conflicting kernel types in the global namespace: */
+#define __KERNEL_STRICT_NAMES
+
+#include "local_inotify.h"
+#if 0
+#ifdef HAVE_SYS_INOTIFY_H
+/* We don't actually include the libc header, because there has been
+ * problems with libc versions that was built without inotify support.
+ * Instead we use the local version.
+ */
+#include "local_inotify.h"
+#elif defined (HAVE_LINUX_INOTIFY_H)
+#include <linux/inotify.h>
+#endif
+#endif
+#include <string.h>
+#include <glib.h>
+#include "inotify-kernel.h"
+#include "inotify-path.h"
+#include "inotify-missing.h"
+
+#define IP_INOTIFY_MASK (IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE|IN_CREATE|IN_DELETE_SELF|IN_UNMOUNT|IN_MOVE_SELF)
+
+typedef struct ip_watched_dir_s {
+	char *path;
+	/* TODO: We need to maintain a tree of watched directories
+	 * so that we can deliver move/delete events to sub folders.
+	 * Or the application could do it...
+	 */
+	struct ip_watched_dir_s *parent;
+	GList *			 children;
+
+	/* Inotify state */
+	gint32 wd;
+
+	/* List of inotify subscriptions */
+	GList *subs;
+} ip_watched_dir_t;
+
+static gboolean     ip_debug_enabled = FALSE;
+#define IP_W if (ip_debug_enabled) g_warning
+
+/* path -> ip_watched_dir */
+static GHashTable * path_dir_hash = NULL;
+/* ih_sub_t * -> ip_watched_dir *
+ *
+ * Each subscription is attached to a watched directory or it is on
+ * the missing list
+ */
+static GHashTable * sub_dir_hash = NULL;
+/* This hash holds GLists of ip_watched_dir_t *'s
+ * We need to hold a list because symbolic links can share
+ * the same wd
+ */
+static GHashTable * wd_dir_hash = NULL;
+
+static ip_watched_dir_t *	ip_watched_dir_new (const char *path, int wd);
+static void 			ip_watched_dir_free (ip_watched_dir_t *dir);
+static void 			ip_event_callback (ik_event_t *event);
+
+static void (*event_callback)(ik_event_t *event, ih_sub_t *sub);
+
+gboolean ip_startup (void (*cb)(ik_event_t *event, ih_sub_t *sub))
+{
+	static gboolean initialized = FALSE;
+	static gboolean result = FALSE;
+
+	if (initialized == TRUE) {
+		return result;
+	}
+
+	initialized = TRUE;
+	event_callback = cb;
+	result = ik_startup (ip_event_callback);
+
+	if (!result) {
+		return FALSE;
+	}
+
+	path_dir_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	sub_dir_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+	wd_dir_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+	return TRUE;
+}
+
+static void
+ip_map_path_dir (const char *path, ip_watched_dir_t *dir)
+{
+	g_assert (path && dir);
+	g_hash_table_insert(path_dir_hash, dir->path, dir);
+}
+
+static void
+ip_map_sub_dir (ih_sub_t *sub, ip_watched_dir_t *dir)
+{
+	/* Associate subscription and directory */
+	g_assert (dir && sub);
+	g_hash_table_insert (sub_dir_hash, sub, dir);
+	dir->subs = g_list_prepend (dir->subs, sub);
+}
+
+static void
+ip_map_wd_dir (gint32 wd, ip_watched_dir_t *dir)
+{
+	g_assert (wd >= 0 && dir);
+	GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(wd));
+	dir_list = g_list_prepend (dir_list, dir);
+	g_hash_table_replace(wd_dir_hash, GINT_TO_POINTER(dir->wd), dir_list);
+}
+
+gboolean ip_start_watching (ih_sub_t *sub)
+{
+	gint32 wd;
+	int err;
+	ip_watched_dir_t *dir;
+
+	g_assert (sub);
+	g_assert (!sub->cancelled);
+	g_assert (sub->dirname);
+
+	IP_W("Starting to watch %s\n", sub->dirname);
+	dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
+	if (dir)
+	{
+		IP_W("Already watching\n");
+		goto out;
+	}
+	
+	IP_W("Trying to add inotify watch ");
+	wd = ik_watch (sub->dirname, IP_INOTIFY_MASK|IN_ONLYDIR|sub->extra_flags, &err);
+	if (wd < 0) 
+	{
+		IP_W("Failed\n");
+		return FALSE;
+	} else {
+		/* Create new watched directory and associate it with the 
+		 * wd hash and path hash
+		 */
+		IP_W("Success\n");
+		dir = ip_watched_dir_new (sub->dirname, wd);
+		ip_map_wd_dir (wd, dir);
+		ip_map_path_dir (sub->dirname, dir);
+	}
+
+out:
+	ip_map_sub_dir (sub, dir);
+
+	return TRUE;
+}
+
+static void
+ip_unmap_path_dir (const char *path, ip_watched_dir_t *dir)
+{
+	g_assert (path && dir);
+	g_hash_table_remove (path_dir_hash, dir->path);
+}
+
+static void
+ip_unmap_wd_dir (gint32 wd, ip_watched_dir_t *dir)
+{
+	GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(wd));
+	if (!dir_list)
+		return;
+	g_assert (wd >= 0 && dir);
+	dir_list = g_list_remove (dir_list, dir);
+	if (dir_list == NULL) {
+		g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER(dir->wd));
+	} else {
+		g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER(dir->wd), dir_list);
+	}
+}
+
+static void
+ip_unmap_wd (gint32 wd)
+{
+	GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(wd));
+	if (!dir_list)
+		return;
+	g_assert (wd >= 0);
+	g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER(wd));
+	g_list_free (dir_list);
+}
+
+static void
+ip_unmap_sub_dir (ih_sub_t *sub, ip_watched_dir_t *dir)
+{
+	g_assert (sub && dir);
+	g_hash_table_remove (sub_dir_hash, sub);
+	dir->subs = g_list_remove (dir->subs, sub);
+}
+
+static void
+ip_unmap_all_subs (ip_watched_dir_t *dir)
+{
+	GList *l = NULL;
+
+	for (l = dir->subs; l; l = l->next)
+	{
+		ih_sub_t *sub = l->data;
+		g_hash_table_remove (sub_dir_hash, sub);
+	}
+	g_list_free (dir->subs);
+	dir->subs = NULL;
+}
+
+gboolean ip_stop_watching  (ih_sub_t *sub)
+{
+	ip_watched_dir_t *dir = NULL;
+
+	dir = g_hash_table_lookup (sub_dir_hash, sub);
+	if (!dir) {
+		return TRUE;
+	}
+
+	ip_unmap_sub_dir (sub, dir);
+
+	/* No one is subscribing to this directory any more */
+	if (dir->subs == NULL) {
+        ik_ignore (dir->path, dir->wd);
+        ip_unmap_wd_dir (dir->wd, dir);
+		ip_unmap_path_dir (dir->path, dir);
+        ip_watched_dir_free (dir);
+	}
+
+	return TRUE;
+}
+
+
+static ip_watched_dir_t *
+ip_watched_dir_new (const char *path, gint32 wd)
+{
+	ip_watched_dir_t *dir = g_new0(ip_watched_dir_t, 1);
+
+	dir->path = g_strdup(path);
+	dir->wd = wd;
+
+	return dir;
+}
+
+static void
+ip_watched_dir_free (ip_watched_dir_t * dir)
+{
+	g_assert (dir->subs == 0);
+	g_free(dir->path);
+	g_free(dir);
+}
+
+static void ip_wd_delete (gpointer data, gpointer user_data)
+{
+	ip_watched_dir_t *dir = data;
+	GList *l = NULL;
+
+	for (l = dir->subs; l; l = l->next)
+	{
+		ih_sub_t *sub = l->data;
+
+		/* Add subscription to missing list */
+		im_add (sub);
+	}
+	ip_unmap_all_subs (dir);
+	/* Unassociate the path and the directory */
+	ip_unmap_path_dir (dir->path, dir);
+	ip_watched_dir_free (dir);
+}
+
+static void ip_event_dispatch (GList *dir_list, GList *pair_dir_list, ik_event_t *event)
+{
+	GList *dirl;
+
+	if (!event)
+		return;
+
+	/* TODO:
+	 *
+	 * Figure out how we will deliver move events
+	 */
+	for (dirl = dir_list; dirl; dirl = dirl->next)
+	{
+		GList *subl;
+		ip_watched_dir_t *dir = dirl->data;
+
+		for (subl = dir->subs; subl; subl = subl->next)
+		{
+			ih_sub_t *sub = subl->data;
+
+			/* If the event and the subscription have a filename
+			 * they need to match before the event could be delivered.
+			 */
+			if (event->name && sub->filename) {
+				if (strcmp (event->name, sub->filename))
+					continue;
+			/* If the event doesn't have a filename, but the subscription does
+			 * we shouldn't deliever the event */
+			} else if (sub->filename)
+				continue;
+
+			event_callback (event, sub);
+		}
+	}
+
+	if (!event->pair)
+		return;
+
+	for (dirl = pair_dir_list; dirl; dirl = dirl->next)
+	{
+		GList *subl;
+		ip_watched_dir_t *dir = dirl->data;
+
+		for (subl = dir->subs; subl; subl = subl->next)
+		{
+			ih_sub_t *sub = subl->data;
+
+			/* If the event and the subscription have a filename
+			 * they need to match before the event could be delivered.
+			 */
+			if (event->pair->name && sub->filename) {
+				if (strcmp (event->pair->name, sub->filename))
+					continue;
+			/* If the event doesn't have a filename, but the subscription does
+			 * we shouldn't deliever the event */
+			} else if (sub->filename)
+				continue;
+
+			event_callback (event->pair, sub);
+		}
+	}
+}
+
+static void
+ip_event_callback (ik_event_t *event)
+{
+	GList *dir_list = NULL;
+	GList *pair_dir_list = NULL;
+
+	dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(event->wd));
+
+	/* We can ignore IN_IGNORED events */
+	if (event->mask & IN_IGNORED) {
+		ik_event_free (event);
+		return;
+	}
+
+	if (event->pair)
+		pair_dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(event->pair->wd));
+
+	if (event->mask & IP_INOTIFY_MASK)
+		ip_event_dispatch (dir_list, pair_dir_list, event);
+
+	/* We have to manage the missing list when we get a DELETE event. */
+	if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF)
+	{
+		/* Add all subscriptions to missing list */
+		g_list_foreach (dir_list, ip_wd_delete, NULL);
+		/* Unmap all directories attached to this wd */
+		ip_unmap_wd (event->wd);
+	}
+
+	ik_event_free (event);
+}
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-helper.h	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,45 @@
+/* inotify-helper.h - GNOME VFS Monitor using inotify
+
+   Copyright (C) 2005 John McCutchan
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: John McCutchan <john@johnmccutchan.com>
+*/
+
+
+#ifndef __INOTIFY_HELPER_H
+#define __INOTIFY_HELPER_H
+
+#include "inotify-sub.h"
+#include "inotify-kernel.h"
+
+typedef void (*event_callback_t)(const char *fullpath, guint32 mask, void *subdata);
+typedef void (*found_callback_t)(const char *fullpath, void *subdata);
+
+gboolean	 ih_startup		(event_callback_t ecb,
+					 found_callback_t fcb);
+gboolean	 ih_running		(void);
+gboolean	 ih_sub_add		(ih_sub_t *sub);
+gboolean	 ih_sub_cancel		(ih_sub_t *sub);
+
+/* Return FALSE from 'f' if the subscription should be cancelled */
+void		 ih_sub_foreach		(void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata));
+
+/* Return FALSE from 'f' if the subscription should be cancelled and free'd */
+void		 ih_sub_foreach_free	(void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata));
+
+#endif /* __INOTIFY_HELPER_H */
--- /dev/null	2006-08-28 15:22:40.902752500 +0200
+++ gamin-0.1.7/server/inotify-sub.h	2006-09-05 11:01:21.000000000 +0200
@@ -0,0 +1,42 @@
+/* inotify-helper.h - GNOME VFS Monitor using inotify
+
+   Copyright (C) 2006 John McCutchan
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: John McCutchan <john@johnmccutchan.com>
+*/
+
+
+#ifndef __INOTIFY_SUB_H
+#define __INOTIFY_SUB_H
+
+#include "gam_subscription.h"
+
+typedef struct {
+	gboolean is_dir;
+	char *pathname;
+	char *dirname;
+	char *filename;
+	guint32 extra_flags;
+	gboolean cancelled;
+	void *usersubdata;
+} ih_sub_t;
+
+ih_sub_t	*ih_sub_new		(const char *pathname, gboolean is_dir, guint32 flags, void *userdata);
+void		 ih_sub_free 	 	(ih_sub_t *sub);
+
+#endif /* __INOTIFY_SUB_H */
--- gamin-0.1.7/server/local_inotify.h.new-inotify-backend	2005-08-17 15:50:04.000000000 +0200
+++ gamin-0.1.7/server/local_inotify.h	2006-09-05 11:01:21.000000000 +0200
@@ -47,6 +47,9 @@
 #define IN_MOVE			(IN_MOVED_FROM | IN_MOVED_TO) /* moves */
 
 /* special flags */
+#define IN_ONLYDIR		0x01000000	/* only watch the path if it is a directory */
+#define IN_DONT_FOLLOW		0x02000000	/* don't follow a sym link */
+#define IN_MASK_ADD		0x20000000	/* add to the mask of an already existing watch */
 #define IN_ISDIR		0x40000000	/* event occurred against dir */
 #define IN_ONESHOT		0x80000000	/* only send event once */