[glib/wip/desrt/gfilemonitor: 3/11] substantially rework file monitors



commit 8db91d31edd88cceaad8107765359881675238f2
Author: Ryan Lortie <desrt desrt ca>
Date:   Mon Jan 12 14:59:35 2015 -0500

    substantially rework file monitors
    
    Remove all event merging and dispatch logic from GFileMonitor.  The only
    implementation of GFileMonitor outside of glib is in gvfs and it already
    does these things properly.
    
    Get rid of GLocalDirectoryMonitor.  We will use a single class,
    GLocalFileMonitor, for both directory and file monitoring.  This will
    prevent every single backend from having to create two objects
    separately (eg: ginotifydirectorymonitor.c and ginotifyfilemonitor.c).
    
    Introduce GFileMonitorSource as a thread-safe cross-context dispatch
    mechanism.  Put it in GLocalFileMonitor.  All backends will be expected
    to dispatch via the source and not touch the GFileMonitor object at all
    from the worker thread.
    
    Remove all construct properties from GLocalFileMonitor and remove the
    "context" construct property from GFileMonitor.  All backends must now
    get the information about what file to monitor from the ->start() call
    which is mandatory to implement.
    
    Remove the implementation of rate limiting in GFileMonitor and add an
    implementation in GLocalFileMonitor.  gvfs never did anything with this
    anyway, but if it wanted to, it would have to implement it for itself.
    This was done in order to get the rate_limit field into the
    GFileMonitorSource so that it could be safely accessed from the worker
    thread.
    
    Add a couple of extra utility functions to GLocalFile so that we can
    skip some memory copies (and skip the gvfs detection logic when choosing
    which backend to use).  Expose the pre-existing "is_remote"
    functionality.
    
    With the "is_remote" functionality exposed, we can now move all
    functions for creating local file monitors to a proper location in
    glocalfilemonitor.c
    
    Port the inotify backend to adjust to the changes above.  None of the
    other backends are ported yet.
    
    This mega-commit is a work in progress -- it should probably be broken
    up.

 gio/Makefile.am                        |    2 -
 gio/gdesktopappinfo.c                  |   14 +-
 gio/gfilemonitor.c                     |  632 +++--------------------
 gio/gioenums.h                         |   27 +-
 gio/giomodule.c                        |   34 +-
 gio/glocaldirectorymonitor.c           |  245 ---------
 gio/glocaldirectorymonitor.h           |   84 ---
 gio/glocalfile.c                       |   38 +--
 gio/glocalfile.h                       |    2 +
 gio/glocalfilemonitor.c                |  861 +++++++++++++++++++++++++++++---
 gio/glocalfilemonitor.h                |   58 ++-
 gio/inotify/Makefile.am                |    2 -
 gio/inotify/ginotifydirectorymonitor.c |    2 +-
 gio/inotify/ginotifyfilemonitor.c      |  133 ++----
 gio/inotify/ginotifyfilemonitor.h      |    4 +-
 gio/inotify/inotify-helper.c           |  149 ++----
 gio/inotify/inotify-kernel.c           |    6 +-
 gio/inotify/inotify-path.c             |   97 +----
 gio/inotify/inotify-sub.c              |    2 -
 gio/inotify/inotify-sub.h              |    1 -
 20 files changed, 1080 insertions(+), 1313 deletions(-)
---
diff --git a/gio/Makefile.am b/gio/Makefile.am
index d0b8d10..df4260c 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -183,8 +183,6 @@ application_sources = \
        $(NULL)
 
 local_sources = \
-       glocaldirectorymonitor.c        \
-       glocaldirectorymonitor.h        \
        glocalfile.c                    \
        glocalfile.h                    \
        glocalfileenumerator.c          \
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index 786ce0f..0b34235 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -46,8 +46,7 @@
 #include "giomodule-priv.h"
 #include "gappinfo.h"
 #include "gappinfoprivate.h"
-#include "glocaldirectorymonitor.h"
-
+#include "glocalfilemonitor.h"
 
 /**
  * SECTION:gdesktopappinfo
@@ -140,7 +139,7 @@ typedef struct
   gchar                      *alternatively_watching;
   gboolean                    is_config;
   gboolean                    is_setup;
-  GLocalDirectoryMonitor     *monitor;
+  GFileMonitor               *monitor;
   GHashTable                 *app_names;
   GHashTable                 *mime_tweaks;
   GHashTable                 *memory_index;
@@ -1335,13 +1334,8 @@ desktop_file_dir_init (DesktopFileDir *dir)
    * does (and we catch the unlikely race), the only degradation is that
    * we will fall back to polling.
    */
-  dir->monitor = g_local_directory_monitor_new_in_worker (watch_dir, G_FILE_MONITOR_NONE, NULL);
-
-  if (dir->monitor)
-    {
-      g_signal_connect (dir->monitor, "changed", G_CALLBACK (desktop_file_dir_changed), dir);
-      g_local_directory_monitor_start (dir->monitor);
-    }
+  dir->monitor = g_local_file_monitor_new_in_worker (watch_dir, TRUE, G_FILE_MONITOR_NONE,
+                                                     desktop_file_dir_changed, dir, NULL);
 
   desktop_file_dir_unindexed_init (dir);
 
diff --git a/gio/gfilemonitor.c b/gio/gfilemonitor.c
index c81f6fc..71d5f55 100644
--- a/gio/gfilemonitor.c
+++ b/gio/gfilemonitor.c
@@ -27,11 +27,6 @@
 #include "gvfs.h"
 #include "glibintl.h"
 
-
-struct _FileChange;
-typedef struct _FileChange FileChange;
-static void file_change_free (FileChange *change);
-
 /**
  * SECTION:gfilemonitor
  * @short_description: File Monitor
@@ -53,48 +48,23 @@ static void file_change_free (FileChange *change);
  * context is still running).
  **/
 
-G_LOCK_DEFINE_STATIC(cancelled);
-
-enum {
-  CHANGED,
-  LAST_SIGNAL
-};
-
-typedef struct {
-  GFile *file;
-  guint32 last_sent_change_time; /* 0 == not sent */
-  guint32 send_delayed_change_at; /* 0 == never */
-  guint32 send_virtual_changes_done_at; /* 0 == never */
-} RateLimiter;
+#define DEFAULT_RATE_LIMIT_MSECS 800
 
-struct _GFileMonitorPrivate {
+struct _GFileMonitorPrivate
+{
   gboolean cancelled;
-  int rate_limit_msec;
-
-  /* Rate limiting change events */
-  GHashTable *rate_limiter;
-
-  GMutex mutex;
-  GSource *pending_file_change_source;
-  GSList *pending_file_changes; /* FileChange */
-
-  GSource *timeout;
-  guint32 timeout_fires_at;
-
-  GMainContext *context;
 };
 
-enum {
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GFileMonitor, g_file_monitor, G_TYPE_OBJECT)
+
+enum
+{
   PROP_0,
   PROP_RATE_LIMIT,
-  PROP_CANCELLED,
-  PROP_CONTEXT
+  PROP_CANCELLED
 };
 
-/* work around a limitation of the aliasing foo */
-#undef g_file_monitor
-
-G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GFileMonitor, g_file_monitor, G_TYPE_OBJECT)
+static guint g_file_monitor_changed_signal;
 
 static void
 g_file_monitor_set_property (GObject      *object,
@@ -102,20 +72,14 @@ g_file_monitor_set_property (GObject      *object,
                              const GValue *value,
                              GParamSpec   *pspec)
 {
-  GFileMonitor *monitor;
+  //GFileMonitor *monitor;
 
-  monitor = G_FILE_MONITOR (object);
+  //monitor = G_FILE_MONITOR (object);
 
   switch (prop_id)
     {
     case PROP_RATE_LIMIT:
-      g_file_monitor_set_rate_limit (monitor, g_value_get_int (value));
-      break;
-
-    case PROP_CONTEXT:
-      monitor->priv->context = g_value_dup_boxed (value);
-      if (monitor->priv->context == NULL)
-        monitor->priv->context = g_main_context_ref_thread_default ();
+      /* not supported by default */
       break;
 
     default:
@@ -130,22 +94,17 @@ g_file_monitor_get_property (GObject    *object,
                              GValue     *value,
                              GParamSpec *pspec)
 {
-  GFileMonitor *monitor;
-  GFileMonitorPrivate *priv;
-
-  monitor = G_FILE_MONITOR (object);
-  priv = monitor->priv;
-
   switch (prop_id)
     {
     case PROP_RATE_LIMIT:
-      g_value_set_int (value, priv->rate_limit_msec);
+      /* we expect this to be overridden... */
+      g_value_set_int (value, DEFAULT_RATE_LIMIT_MSECS);
       break;
 
     case PROP_CANCELLED:
-      G_LOCK (cancelled);
-      g_value_set_boolean (value, priv->cancelled);
-      G_UNLOCK (cancelled);
+      //g_mutex_lock (&fms->lock);
+      g_value_set_boolean (value, FALSE);//fms->cancelled);
+      //g_mutex_unlock (&fms->lock);
       break;
 
     default:
@@ -154,56 +113,10 @@ g_file_monitor_get_property (GObject    *object,
     }
 }
 
-#define DEFAULT_RATE_LIMIT_MSECS 800
-#define DEFAULT_VIRTUAL_CHANGES_DONE_DELAY_SECS 2
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-static void
-rate_limiter_free (RateLimiter *limiter)
-{
-  g_object_unref (limiter->file);
-  g_slice_free (RateLimiter, limiter);
-}
-
-static void
-g_file_monitor_finalize (GObject *object)
-{
-  GFileMonitor *monitor;
-
-  monitor = G_FILE_MONITOR (object);
-
-  if (monitor->priv->timeout)
-    {
-      g_source_destroy (monitor->priv->timeout);
-      g_source_unref (monitor->priv->timeout);
-    }
-
-  g_hash_table_destroy (monitor->priv->rate_limiter);
-
-  g_main_context_unref (monitor->priv->context);
-  g_mutex_clear (&monitor->priv->mutex);
-
-  G_OBJECT_CLASS (g_file_monitor_parent_class)->finalize (object);
-}
-
 static void
 g_file_monitor_dispose (GObject *object)
 {
-  GFileMonitor *monitor;
-  GFileMonitorPrivate *priv;
-  
-  monitor = G_FILE_MONITOR (object);
-  priv = monitor->priv;
-
-  if (priv->pending_file_change_source)
-    {
-      g_source_destroy (priv->pending_file_change_source);
-      g_source_unref (priv->pending_file_change_source);
-      priv->pending_file_change_source = NULL;
-    }
-  g_slist_free_full (priv->pending_file_changes, (GDestroyNotify) file_change_free);
-  priv->pending_file_changes = NULL;
+  GFileMonitor *monitor = G_FILE_MONITOR (object);
 
   /* Make sure we cancel on last unref */
   g_file_monitor_cancel (monitor);
@@ -212,12 +125,17 @@ g_file_monitor_dispose (GObject *object)
 }
 
 static void
+g_file_monitor_init (GFileMonitor *monitor)
+{
+  monitor->priv = g_file_monitor_get_instance_private (monitor);
+}
+
+static void
 g_file_monitor_class_init (GFileMonitorClass *klass)
 {
   GObjectClass *object_class;
 
   object_class = G_OBJECT_CLASS (klass);
-  object_class->finalize = g_file_monitor_finalize;
   object_class->dispose = g_file_monitor_dispose;
   object_class->get_property = g_file_monitor_get_property;
   object_class->set_property = g_file_monitor_set_property;
@@ -231,58 +149,54 @@ g_file_monitor_class_init (GFileMonitorClass *klass)
    *
    * Emitted when @file has been changed.
    *
-   * If using #G_FILE_MONITOR_SEND_MOVED flag and @event_type is
+   * If using %G_FILE_MONITOR_WATCH_RENAMES on a directory monitor, and
+   * the information is available (and if supported by the backend),
+   * @event_type may be %G_FILE_MONITOR_EVENT_RENAMED,
+   * %G_FILE_MONITOR_EVENT_MOVED_IN or %G_FILE_MONITOR_EVENT_MOVED_OUT.
+   *
+   * In all cases @file will be a child of the monitored directory.  For
+   * renames, @file will be the old name and @other_file is the new
+   * name.  For "moved in" events, @file is the name of the file that
+   * appeared and @other_file is the old name that it was moved from (in
+   * another directory).  For "moved out" events, @file is the name of
+   * the file that used to be in this directory and @other_file is the
+   * name of the file at its new location.
+   *
+   * It makes sense to treat %G_FILE_MONITOR_EVENT_MOVED_IN as
+   * equivalent to %G_FILE_MONITOR_EVENT_CREATED and
+   * %G_FILE_MONITOR_EVENT_MOVED_OUT as equivalent to
+   * %G_FILE_MONITOR_EVENT_DELETED, with extra information.
+   * %G_FILE_MONITOR_EVENT_RENAMED is equivalent to a delete/create
+   * pair.  This is exactly how the events will be reported in the case
+   * that the %G_FILE_MONITOR_WATCH_RENAMES flag is not in use.
+   *
+   * If using the deprecated flag %G_FILE_MONITOR_SEND_MOVED flag and @event_type is
    * #G_FILE_MONITOR_EVENT_MOVED, @file will be set to a #GFile containing the
    * old path, and @other_file will be set to a #GFile containing the new path.
    *
    * In all the other cases, @other_file will be set to #NULL.
    **/
-  signals[CHANGED] =
-    g_signal_new (I_("changed"),
-                 G_TYPE_FILE_MONITOR,
-                 G_SIGNAL_RUN_LAST,
-                 G_STRUCT_OFFSET (GFileMonitorClass, changed),
-                 NULL, NULL,
-                 NULL,
-                 G_TYPE_NONE, 3,
-                 G_TYPE_FILE, G_TYPE_FILE, G_TYPE_FILE_MONITOR_EVENT);
-
-  g_object_class_install_property (object_class,
-                                   PROP_RATE_LIMIT,
+  g_file_monitor_changed_signal = g_signal_new (I_("changed"),
+                                                G_TYPE_FILE_MONITOR,
+                                                G_SIGNAL_RUN_LAST,
+                                                G_STRUCT_OFFSET (GFileMonitorClass, changed),
+                                                NULL, NULL,
+                                                NULL,
+                                                G_TYPE_NONE, 3,
+                                                G_TYPE_FILE, G_TYPE_FILE, G_TYPE_FILE_MONITOR_EVENT);
+
+  g_object_class_install_property (object_class, PROP_RATE_LIMIT,
                                    g_param_spec_int ("rate-limit",
                                                      P_("Rate limit"),
                                                      P_("The limit of the monitor to watch for changes, in 
milliseconds"),
-                                                     0, G_MAXINT,
-                                                     DEFAULT_RATE_LIMIT_MSECS,
-                                                     G_PARAM_READWRITE|
-                                                     
G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB));
+                                                     0, G_MAXINT, DEFAULT_RATE_LIMIT_MSECS, 
G_PARAM_READWRITE |
+                                                     G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
-  g_object_class_install_property (object_class,
-                                   PROP_CANCELLED,
+  g_object_class_install_property (object_class, PROP_CANCELLED,
                                    g_param_spec_boolean ("cancelled",
                                                          P_("Cancelled"),
                                                          P_("Whether the monitor has been cancelled"),
-                                                         FALSE,
-                                                         G_PARAM_READABLE|
-                                                         
G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB));
-
-  g_object_class_install_property (object_class,
-                                   PROP_CONTEXT,
-                                   g_param_spec_boxed ("context",
-                                                       P_("Context"),
-                                                       P_("The main context to dispatch from"),
-                                                       G_TYPE_MAIN_CONTEXT, G_PARAM_WRITABLE |
-                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-}
-
-static void
-g_file_monitor_init (GFileMonitor *monitor)
-{
-  monitor->priv = g_file_monitor_get_instance_private (monitor);
-  monitor->priv->rate_limit_msec = DEFAULT_RATE_LIMIT_MSECS;
-  monitor->priv->rate_limiter = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal,
-                                                      NULL, (GDestroyNotify) rate_limiter_free);
-  g_mutex_init (&monitor->priv->mutex);
+                                                         FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 }
 
 /**
@@ -300,42 +214,33 @@ g_file_monitor_is_cancelled (GFileMonitor *monitor)
 
   g_return_val_if_fail (G_IS_FILE_MONITOR (monitor), FALSE);
 
-  G_LOCK (cancelled);
   res = monitor->priv->cancelled;
-  G_UNLOCK (cancelled);
-  
+
   return res;
 }
 
 /**
  * g_file_monitor_cancel:
  * @monitor: a #GFileMonitor.
- * 
+ *
  * Cancels a file monitor.
- * 
- * Returns: %TRUE if monitor was cancelled.
+ *
+ * Returns: always %TRUE
  **/
 gboolean
-g_file_monitor_cancel (GFileMonitor* monitor)
+g_file_monitor_cancel (GFileMonitor *monitor)
 {
-  GFileMonitorClass *klass;
-  
   g_return_val_if_fail (G_IS_FILE_MONITOR (monitor), FALSE);
-  
-  G_LOCK (cancelled);
-  if (monitor->priv->cancelled)
+
+  if (!monitor->priv->cancelled)
     {
-      G_UNLOCK (cancelled);
-      return TRUE;
+      G_FILE_MONITOR_GET_CLASS (monitor)->cancel (monitor);
+
+      monitor->priv->cancelled = TRUE;
+      g_object_notify (G_OBJECT (monitor), "cancelled");
     }
-  
-  monitor->priv->cancelled = TRUE;
-  G_UNLOCK (cancelled);
-  
-  g_object_notify (G_OBJECT (monitor), "cancelled");
-
-  klass = G_FILE_MONITOR_GET_CLASS (monitor);
-  return (* klass->cancel) (monitor);
+
+  return TRUE;
 }
 
 /**
@@ -351,396 +256,21 @@ void
 g_file_monitor_set_rate_limit (GFileMonitor *monitor,
                                gint          limit_msecs)
 {
-  GFileMonitorPrivate *priv;
-
-  g_return_if_fail (G_IS_FILE_MONITOR (monitor));
-  g_return_if_fail (limit_msecs >= 0);
-
-  priv = monitor->priv;
-  if (priv->rate_limit_msec != limit_msecs)
-    {
-      monitor->priv->rate_limit_msec = limit_msecs;
-      g_object_notify (G_OBJECT (monitor), "rate-limit");
-    }
-}
-
-struct _FileChange {
-  GFile             *child;
-  GFile             *other_file;
-  GFileMonitorEvent  event_type;
-};
-
-static void
-file_change_free (FileChange *change)
-{
-  g_object_unref (change->child);
-  if (change->other_file)
-    g_object_unref (change->other_file);
-  
-  g_slice_free (FileChange, change);
-}
-
-static gboolean
-emit_cb (gpointer data)
-{
-  GFileMonitor *monitor = G_FILE_MONITOR (data);
-  GSList *pending, *iter;
-
-  g_mutex_lock (&monitor->priv->mutex);
-  pending = g_slist_reverse (monitor->priv->pending_file_changes);
-  monitor->priv->pending_file_changes = NULL;
-  if (monitor->priv->pending_file_change_source)
-    {
-      g_source_unref (monitor->priv->pending_file_change_source);
-      monitor->priv->pending_file_change_source = NULL;
-    }
-  g_mutex_unlock (&monitor->priv->mutex);
-
-  g_object_ref (monitor);
-  for (iter = pending; iter; iter = iter->next)
-    {
-       FileChange *change = iter->data;
-
-       g_signal_emit (monitor, signals[CHANGED], 0,
-                     change->child, change->other_file, change->event_type);
-       file_change_free (change);
-    }
-  g_slist_free (pending);
-  g_object_unref (monitor);
-
-  return FALSE;
-}
-
-static void
-emit_in_idle (GFileMonitor      *monitor,
-             GFile             *child,
-             GFile             *other_file,
-             GFileMonitorEvent  event_type)
-{
-  GSource *source;
-  FileChange *change;
-  GFileMonitorPrivate *priv;
-
-  priv = monitor->priv;
-
-  change = g_slice_new (FileChange);
-
-  change->child = g_object_ref (child);
-  if (other_file)
-    change->other_file = g_object_ref (other_file);
-  else
-    change->other_file = NULL;
-  change->event_type = event_type;
-
-  g_mutex_lock (&monitor->priv->mutex);
-  if (!priv->pending_file_change_source)
-    {
-      source = g_idle_source_new ();
-      priv->pending_file_change_source = source;
-      g_source_set_priority (source, 0);
-
-      /* We don't ref monitor here - instead dispose will free any
-       * pending idles.
-       */
-      g_source_set_callback (source, emit_cb, monitor, NULL);
-      g_source_set_name (source, "[gio] emit_cb");
-      g_source_attach (source, monitor->priv->context);
-    }
-  /* We reverse this in the processor */
-  priv->pending_file_changes = g_slist_prepend (priv->pending_file_changes, change);
-  g_mutex_unlock (&monitor->priv->mutex);
-}
-
-static guint32
-get_time_msecs (void)
-{
-  return g_get_monotonic_time () / G_TIME_SPAN_MILLISECOND;
-}
-
-static guint32
-time_difference (guint32 from, guint32 to)
-{
-  if (from > to)
-    return 0;
-  return to - from;
-}
-
-/* Change event rate limiting support: */
-
-static RateLimiter *
-new_limiter (GFileMonitor *monitor,
-            GFile             *file)
-{
-  RateLimiter *limiter;
-
-  limiter = g_slice_new0 (RateLimiter);
-  limiter->file = g_object_ref (file);
-  g_hash_table_insert (monitor->priv->rate_limiter, file, limiter);
-  
-  return limiter;
-}
-
-static void
-rate_limiter_send_virtual_changes_done_now (GFileMonitor *monitor, 
-                                            RateLimiter  *limiter)
-{
-  if (limiter->send_virtual_changes_done_at != 0)
-    {
-      emit_in_idle (monitor, limiter->file, NULL,
-                   G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT);
-      limiter->send_virtual_changes_done_at = 0;
-    }
-}
-
-static void
-rate_limiter_send_delayed_change_now (GFileMonitor *monitor, 
-                                      RateLimiter *limiter, 
-                                      guint32 time_now)
-{
-  if (limiter->send_delayed_change_at != 0)
-    {
-      emit_in_idle (monitor, 
-                   limiter->file, NULL,
-                   G_FILE_MONITOR_EVENT_CHANGED);
-      limiter->send_delayed_change_at = 0;
-      limiter->last_sent_change_time = time_now;
-    }
-}
-
-typedef struct {
-  guint32 min_time;
-  guint32 time_now;
-  GFileMonitor *monitor;
-} ForEachData;
-
-static gboolean
-calc_min_time (GFileMonitor *monitor, 
-               RateLimiter *limiter, 
-               guint32 time_now, 
-               guint32 *min_time)
-{
-  gboolean delete_me;
-  guint32 expire_at;
-
-  delete_me = TRUE;
-
-  if (limiter->last_sent_change_time != 0)
-    {
-      /* Set a timeout at 2*rate limit so that we can clear out the change from the hash eventually */
-      expire_at = limiter->last_sent_change_time + 2 * monitor->priv->rate_limit_msec;
-
-      if (time_difference (time_now, expire_at) > 0)
-       {
-         delete_me = FALSE;
-         *min_time = MIN (*min_time,
-                          time_difference (time_now, expire_at));
-       }
-    }
-
-  if (limiter->send_delayed_change_at != 0)
-    {
-      delete_me = FALSE;
-      *min_time = MIN (*min_time,
-                      time_difference (time_now, limiter->send_delayed_change_at));
-    }
-
-  if (limiter->send_virtual_changes_done_at != 0)
-    {
-      delete_me = FALSE;
-      *min_time = MIN (*min_time,
-                      time_difference (time_now, limiter->send_virtual_changes_done_at));
-    }
-
-  return delete_me;
-}
-
-static gboolean
-foreach_rate_limiter_fire (gpointer key,
-                          gpointer value,
-                          gpointer user_data)
-{
-  RateLimiter *limiter = value;
-  ForEachData *data = user_data;
-
-  if (limiter->send_delayed_change_at != 0 &&
-      time_difference (data->time_now, limiter->send_delayed_change_at) == 0)
-    rate_limiter_send_delayed_change_now (data->monitor, limiter, data->time_now);
-  
-  if (limiter->send_virtual_changes_done_at != 0 &&
-      time_difference (data->time_now, limiter->send_virtual_changes_done_at) == 0)
-    rate_limiter_send_virtual_changes_done_now (data->monitor, limiter);
-  
-  return calc_min_time (data->monitor, limiter, data->time_now, &data->min_time);
-}
-
-static gboolean 
-rate_limiter_timeout (gpointer timeout_data)
-{
-  GFileMonitor *monitor = timeout_data;
-  ForEachData data;
-  GSource *source;
-  
-  data.min_time = G_MAXUINT32;
-  data.monitor = monitor;
-  data.time_now = get_time_msecs ();
-  g_hash_table_foreach_remove (monitor->priv->rate_limiter,
-                              foreach_rate_limiter_fire,
-                              &data);
-  
-  /* Remove old timeout */
-  if (monitor->priv->timeout)
-    {
-      g_source_destroy (monitor->priv->timeout);
-      g_source_unref (monitor->priv->timeout);
-      monitor->priv->timeout = NULL;
-      monitor->priv->timeout_fires_at = 0;
-    }
-  
-  /* Set up new timeout */
-  if (data.min_time != G_MAXUINT32)
-    {
-      source = g_timeout_source_new (data.min_time + 1); /* + 1 to make sure we've really passed the time */
-      g_source_set_callback (source, rate_limiter_timeout, monitor, NULL);
-      g_source_attach (source, monitor->priv->context);
-      
-      monitor->priv->timeout = source;
-      monitor->priv->timeout_fires_at = data.time_now + data.min_time; 
-    }
-  
-  return FALSE;
-}
-
-static gboolean
-foreach_rate_limiter_update (gpointer key,
-                            gpointer value,
-                            gpointer user_data)
-{
-  RateLimiter *limiter = value;
-  ForEachData *data = user_data;
-
-  return calc_min_time (data->monitor, limiter, data->time_now, &data->min_time);
+  g_object_set (monitor, "rate-limit", limit_msecs, NULL);
 }
 
-static void
-update_rate_limiter_timeout (GFileMonitor *monitor, 
-                             guint new_time)
-{
-  ForEachData data;
-  GSource *source;
-  
-  if (monitor->priv->timeout_fires_at != 0 && new_time != 0 &&
-      time_difference (new_time, monitor->priv->timeout_fires_at) == 0)
-    return; /* Nothing to do, we already fire earlier than that */
-
-  data.min_time = G_MAXUINT32;
-  data.monitor = monitor;
-  data.time_now = get_time_msecs ();
-  g_hash_table_foreach_remove (monitor->priv->rate_limiter,
-                              foreach_rate_limiter_update,
-                              &data);
-
-  /* Remove old timeout */
-  if (monitor->priv->timeout)
-    {
-      g_source_destroy (monitor->priv->timeout);
-      g_source_unref (monitor->priv->timeout);
-      monitor->priv->timeout_fires_at = 0;
-      monitor->priv->timeout = NULL;
-    }
-
-  /* Set up new timeout */
-  if (data.min_time != G_MAXUINT32)
-    {
-      source = g_timeout_source_new (data.min_time + 1);  /* + 1 to make sure we've really passed the time */
-      g_source_set_callback (source, rate_limiter_timeout, monitor, NULL);
-      g_source_attach (source, monitor->priv->context);
-      
-      monitor->priv->timeout = source;
-      monitor->priv->timeout_fires_at = data.time_now + data.min_time; 
-    }
-}
-
-/**
- * g_file_monitor_emit_event:
- * @monitor: a #GFileMonitor.
- * @child: a #GFile.
- * @other_file: a #GFile.
- * @event_type: a set of #GFileMonitorEvent flags.
- * 
- * Emits the #GFileMonitor::changed signal if a change
- * has taken place. Should be called from file monitor 
- * implementations only.
- *
- * The signal will be emitted from an idle handler (in the
- * [thread-default main context][g-main-context-push-thread-default]).
- **/
 void
 g_file_monitor_emit_event (GFileMonitor      *monitor,
-                          GFile             *child,
-                          GFile             *other_file,
-                          GFileMonitorEvent  event_type)
+                           GFile             *child,
+                           GFile             *other_file,
+                           GFileMonitorEvent  event_type)
 {
-  guint32 time_now, since_last;
-  gboolean emit_now;
-  RateLimiter *limiter;
-
   g_return_if_fail (G_IS_FILE_MONITOR (monitor));
   g_return_if_fail (G_IS_FILE (child));
+  g_return_if_fail (!other_file || G_IS_FILE (other_file));
 
-  limiter = g_hash_table_lookup (monitor->priv->rate_limiter, child);
+  if (monitor->priv->cancelled)
+    return;
 
-  if (event_type != G_FILE_MONITOR_EVENT_CHANGED)
-    {
-      if (limiter)
-       {
-         rate_limiter_send_delayed_change_now (monitor, limiter, get_time_msecs ());
-         if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
-           limiter->send_virtual_changes_done_at = 0;
-         else
-           rate_limiter_send_virtual_changes_done_now (monitor, limiter);
-         update_rate_limiter_timeout (monitor, 0);
-       }
-      emit_in_idle (monitor, child, other_file, event_type);
-    }
-  else
-    {
-      /* Changed event, rate limit */
-      time_now = get_time_msecs ();
-      emit_now = TRUE;
-      
-      if (limiter)
-       {
-         since_last = time_difference (limiter->last_sent_change_time, time_now);
-         if (since_last < monitor->priv->rate_limit_msec)
-           {
-             /* We ignore this change, but arm a timer so that we can fire it later if we
-                don't get any other events (that kill this timeout) */
-             emit_now = FALSE;
-             if (limiter->send_delayed_change_at == 0)
-               {
-                 limiter->send_delayed_change_at = time_now + monitor->priv->rate_limit_msec;
-                 update_rate_limiter_timeout (monitor, limiter->send_delayed_change_at);
-               }
-           }
-       }
-      
-      if (limiter == NULL)
-       limiter = new_limiter (monitor, child);
-      
-      if (emit_now)
-       {
-         emit_in_idle (monitor, child, other_file, event_type);
-         
-         limiter->last_sent_change_time = time_now;
-         limiter->send_delayed_change_at = 0;
-         /* Set a timeout of 2*rate limit so that we can clear out the change from the hash eventually */
-         update_rate_limiter_timeout (monitor, time_now + 2 * monitor->priv->rate_limit_msec);
-       }
-      
-      /* Schedule a virtual change done. This is removed if we get a real one, and
-        postponed if we get more change events. */
-      
-      limiter->send_virtual_changes_done_at = time_now + DEFAULT_VIRTUAL_CHANGES_DONE_DELAY_SECS * 1000;
-      update_rate_limiter_timeout (monitor, limiter->send_virtual_changes_done_at);
-    }
+  g_signal_emit (monitor, g_file_monitor_changed_signal, 0, child, other_file, event_type);
 }
diff --git a/gio/gioenums.h b/gio/gioenums.h
index 6b8ab72..9fd563d 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -328,9 +328,14 @@ typedef enum {
  *   by file renames (moves) and send a single G_FILE_MONITOR_EVENT_MOVED
  *   event instead (NB: not supported on all backends; the default
  *   behaviour -without specifying this flag- is to send single DELETED
- *   and CREATED events).
+ *   and CREATED events).  Deprecated since 2.44: use
+ *   %G_FILE_MONITOR_WATCH_MOVES instead.
  * @G_FILE_MONITOR_WATCH_HARD_LINKS: Watch for changes to the file made
  *   via another hard link. Since 2.36.
+ * @G_FILE_MONITOR_WATCH_MOVES: Watch for rename operations on a
+ *   monitored directory.  This causes %G_FILE_MONITOR_EVENT_RENAMED,
+ *   %G_FILE_MONITOR_EVENT_MOVED_IN and %G_FILE_MONITOR_EVENT_MOVED_OUT
+ *   events to be emitted when possible.  Since: 2.44.
  *
  * Flags used to set what a #GFileMonitor will watch for.
  */
@@ -338,7 +343,8 @@ typedef enum {
   G_FILE_MONITOR_NONE             = 0,
   G_FILE_MONITOR_WATCH_MOUNTS     = (1 << 0),
   G_FILE_MONITOR_SEND_MOVED       = (1 << 1),
-  G_FILE_MONITOR_WATCH_HARD_LINKS = (1 << 2)
+  G_FILE_MONITOR_WATCH_HARD_LINKS = (1 << 2),
+  G_FILE_MONITOR_WATCH_MOVES      = (1 << 3)
 } GFileMonitorFlags;
 
 
@@ -393,7 +399,17 @@ typedef enum {
  * @G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: a file attribute was changed.
  * @G_FILE_MONITOR_EVENT_PRE_UNMOUNT: the file location will soon be unmounted.
  * @G_FILE_MONITOR_EVENT_UNMOUNTED: the file location was unmounted.
- * @G_FILE_MONITOR_EVENT_MOVED: the file was moved.
+ * @G_FILE_MONITOR_EVENT_MOVED: the file was moved -- only sent if the
+ *   (deprecated) %G_FILE_MONITOR_SEND_MOVED flag is set
+ * @G_FILE_MONITOR_EVENT_RENAMED: the file was renamed within the
+ *   current directory -- only sent if the %G_FILE_MONITOR_WATCH_MOVES
+ *   flag is set.  Since: 2.44.
+ * @G_FILE_MONITOR_EVENT_MOVED_IN: the file was moved into the
+ *   monitored directory from another location -- only sent if the
+ *   %G_FILE_MONITOR_WATCH_MOVES flag is set.  Since: 2.44.
+ * @G_FILE_MONITOR_EVENT_MOVED_OUT: the file was moved out of the
+ *   monitored directory to another location -- only sent if the
+ *   %G_FILE_MONITOR_WATCH_MOVES flag is set.  Since: 2.44
  *
  * Specifies what type of event a monitor event is.
  **/
@@ -405,7 +421,10 @@ typedef enum {
   G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
   G_FILE_MONITOR_EVENT_PRE_UNMOUNT,
   G_FILE_MONITOR_EVENT_UNMOUNTED,
-  G_FILE_MONITOR_EVENT_MOVED
+  G_FILE_MONITOR_EVENT_MOVED,
+  G_FILE_MONITOR_EVENT_RENAMED,
+  G_FILE_MONITOR_EVENT_MOVED_IN,
+  G_FILE_MONITOR_EVENT_MOVED_OUT
 } GFileMonitorEvent;
 
 
diff --git a/gio/giomodule.c b/gio/giomodule.c
index b22ea49..eaf6b53 100644
--- a/gio/giomodule.c
+++ b/gio/giomodule.c
@@ -25,7 +25,6 @@
 #include "giomodule.h"
 #include "giomodule-priv.h"
 #include "glocalfilemonitor.h"
-#include "glocaldirectorymonitor.h"
 #include "gnativevolumemonitor.h"
 #include "gproxyresolver.h"
 #include "gproxy.h"
@@ -36,6 +35,8 @@
 #include "gtlsbackend.h"
 #include "gvfs.h"
 #include "gnotificationbackend.h"
+#include "ginitable.h"
+#include "gnetworkmonitor.h"
 #ifdef G_OS_WIN32
 #include "gregistrysettingsbackend.h"
 #endif
@@ -885,17 +886,15 @@ _g_io_module_get_default (const gchar         *extension_point,
 G_LOCK_DEFINE_STATIC (registered_extensions);
 G_LOCK_DEFINE_STATIC (loaded_dirs);
 
-extern GType _g_fen_directory_monitor_get_type (void);
-extern GType _g_fen_file_monitor_get_type (void);
-extern GType _g_inotify_directory_monitor_get_type (void);
-extern GType _g_inotify_file_monitor_get_type (void);
-extern GType _g_kqueue_directory_monitor_get_type (void);
-extern GType _g_kqueue_file_monitor_get_type (void);
+extern GType g_fen_file_monitor_get_type (void);
+extern GType g_inotify_file_monitor_get_type (void);
+extern GType g_kqueue_file_monitor_get_type (void);
+extern GType g_win32_file_monitor_get_type (void);
+
 extern GType _g_unix_volume_monitor_get_type (void);
 extern GType _g_local_vfs_get_type (void);
 
 extern GType _g_win32_volume_monitor_get_type (void);
-extern GType g_win32_directory_monitor_get_type (void);
 extern GType _g_winhttp_vfs_get_type (void);
 
 extern GType _g_dummy_proxy_resolver_get_type (void);
@@ -978,15 +977,9 @@ _g_io_modules_ensure_extension_points_registered (void)
       G_GNUC_END_IGNORE_DEPRECATIONS
 #endif
 #endif
-      
-      ep = g_io_extension_point_register (G_LOCAL_DIRECTORY_MONITOR_EXTENSION_POINT_NAME);
-      g_io_extension_point_set_required_type (ep, G_TYPE_LOCAL_DIRECTORY_MONITOR);
-      
+
       ep = g_io_extension_point_register (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME);
       g_io_extension_point_set_required_type (ep, G_TYPE_LOCAL_FILE_MONITOR);
-      
-      ep = g_io_extension_point_register (G_NFS_DIRECTORY_MONITOR_EXTENSION_POINT_NAME);
-      g_io_extension_point_set_required_type (ep, G_TYPE_LOCAL_DIRECTORY_MONITOR);
 
       ep = g_io_extension_point_register (G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME);
       g_io_extension_point_set_required_type (ep, G_TYPE_LOCAL_FILE_MONITOR);
@@ -1069,20 +1062,17 @@ _g_io_modules_ensure_loaded (void)
       g_type_ensure (g_null_settings_backend_get_type ());
       g_type_ensure (g_memory_settings_backend_get_type ());
 #if defined(HAVE_INOTIFY_INIT1)
-      g_type_ensure (_g_inotify_directory_monitor_get_type ());
-      g_type_ensure (_g_inotify_file_monitor_get_type ());
+      g_type_ensure (g_inotify_file_monitor_get_type ());
 #endif
 #if defined(HAVE_KQUEUE)
-      g_type_ensure (_g_kqueue_directory_monitor_get_type ());
-      g_type_ensure (_g_kqueue_file_monitor_get_type ());
+      g_type_ensure (g_kqueue_file_monitor_get_type ());
 #endif
 #if defined(HAVE_FEN)
-      g_type_ensure (_g_fen_directory_monitor_get_type ());
-      g_type_ensure (_g_fen_file_monitor_get_type ());
+      g_type_ensure (g_fen_file_monitor_get_type ());
 #endif
 #ifdef G_OS_WIN32
       g_type_ensure (_g_win32_volume_monitor_get_type ());
-      g_type_ensure (g_win32_directory_monitor_get_type ());
+      g_type_ensure (g_win32_file_monitor_get_type ());
       g_type_ensure (g_registry_backend_get_type ());
 #endif
 #ifdef HAVE_COCOA
diff --git a/gio/glocalfile.c b/gio/glocalfile.c
index 20e45ab..eed3a99 100644
--- a/gio/glocalfile.c
+++ b/gio/glocalfile.c
@@ -56,7 +56,6 @@
 #include "glocalfileinputstream.h"
 #include "glocalfileoutputstream.h"
 #include "glocalfileiostream.h"
-#include "glocaldirectorymonitor.h"
 #include "glocalfilemonitor.h"
 #include "gmountprivate.h"
 #include "gunixmounts.h"
@@ -2451,8 +2450,8 @@ g_local_file_move (GFile                  *source,
 
 #ifdef G_OS_WIN32
 
-static gboolean
-is_remote (const gchar *filename)
+gboolean
+g_local_file_is_remote (const gchar *filename)
 {
   return FALSE;
 }
@@ -2508,8 +2507,8 @@ is_remote_fs (const gchar *filename)
   return FALSE;
 }
 
-static gboolean
-is_remote (const gchar *filename)
+gboolean
+g_local_file_is_remote (const gchar *filename)
 {
   static gboolean remote_home;
   static gsize initialized;
@@ -2536,8 +2535,9 @@ g_local_file_monitor_dir (GFile             *file,
                          GCancellable      *cancellable,
                          GError           **error)
 {
-  GLocalFile* local_file = G_LOCAL_FILE(file);
-  return _g_local_directory_monitor_new (local_file->filename, flags, NULL, is_remote 
(local_file->filename), TRUE, error);
+  GLocalFile *local_file = G_LOCAL_FILE (file);
+
+  return g_local_file_monitor_new_for_path (local_file->filename, TRUE, flags, error);
 }
 
 static GFileMonitor*
@@ -2546,31 +2546,11 @@ g_local_file_monitor_file (GFile             *file,
                           GCancellable      *cancellable,
                           GError           **error)
 {
-  GLocalFile* local_file = G_LOCAL_FILE(file);
-  return _g_local_file_monitor_new (local_file->filename, flags, NULL, is_remote (local_file->filename), 
TRUE, error);
-}
-
-GLocalDirectoryMonitor *
-g_local_directory_monitor_new_in_worker (const char         *pathname,
-                                         GFileMonitorFlags   flags,
-                                         GError            **error)
-{
-  return (gpointer) _g_local_directory_monitor_new (pathname, flags,
-                                                    GLIB_PRIVATE_CALL (g_get_worker_context) (),
-                                                    is_remote (pathname), FALSE, error);
-}
+  GLocalFile *local_file = G_LOCAL_FILE (file);
 
-GLocalFileMonitor *
-g_local_file_monitor_new_in_worker (const char         *pathname,
-                                    GFileMonitorFlags   flags,
-                                    GError            **error)
-{
-  return (gpointer) _g_local_file_monitor_new (pathname, flags,
-                                               GLIB_PRIVATE_CALL (g_get_worker_context) (),
-                                               is_remote (pathname), FALSE, error);
+  return g_local_file_monitor_new_for_path (local_file->filename, FALSE, flags, error);
 }
 
-
 /* Here is the GLocalFile implementation of g_file_measure_disk_usage().
  *
  * If available, we use fopenat() in preference to filenames for
diff --git a/gio/glocalfile.h b/gio/glocalfile.h
index e6cf4bf..6b14222 100644
--- a/gio/glocalfile.h
+++ b/gio/glocalfile.h
@@ -46,6 +46,8 @@ GFile * _g_local_file_new      (const char *filename);
 
 const char * _g_local_file_get_filename (GLocalFile *file);
 
+gboolean g_local_file_is_remote (const gchar *filename);
+
 GFile * g_local_file_new_from_dirname_and_basename (const char *dirname,
                                                     const char *basename);
 
diff --git a/gio/glocalfilemonitor.c b/gio/glocalfilemonitor.c
index 818440b..1d5d9a9 100644
--- a/gio/glocalfilemonitor.c
+++ b/gio/glocalfilemonitor.c
@@ -1,5 +1,5 @@
 /* GIO - GLib Input, Output and Streaming Library
- * 
+ *
  * Copyright (C) 2006-2007 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
@@ -25,57 +25,756 @@
 #include "giomodule-priv.h"
 #include "gioerror.h"
 #include "glibintl.h"
+#include "glocalfile.h"
+#include "glib-private.h"
 
 #include <string.h>
 
+#define DEFAULT_RATE_LIMIT                           800 * G_TIME_SPAN_MILLISECOND
+#define VIRTUAL_CHANGES_DONE_DELAY                     2 * G_TIME_SPAN_SECOND
 
-enum
-{
-  PROP_0,
-  PROP_FILENAME,
-  PROP_FLAGS
+/* GFileMonitorSource is a GSource responsible for emitting the changed
+ * signals in the owner-context of the GFileMonitor.
+ *
+ * It contains functionality for cross-thread queuing of events.  It
+ * also handles merging of CHANGED events and emission of CHANGES_DONE
+ * events.
+ *
+ * We use the "priv" pointer in the external struct to store it.
+ */
+struct _GFileMonitorSource {
+  GSource       source;
+
+  GMutex        lock;
+  gpointer      instance;
+  GFileMonitorFlags flags;
+  gchar        *dirname;
+  gchar        *basename;
+  gchar        *filename;
+  GSequence    *pending_changes; /* sorted by ready time */
+  GHashTable   *pending_changes_table;
+  GQueue        event_queue;
+  gint64        rate_limit;
 };
 
-G_DEFINE_ABSTRACT_TYPE (GLocalFileMonitor, g_local_file_monitor, G_TYPE_FILE_MONITOR)
+/* PendingChange is a struct to keep track of a file that needs to have
+ * (at least) a CHANGES_DONE_HINT event sent for it in the near future.
+ *
+ * If 'dirty' is TRUE then a CHANGED event also needs to be sent.
+ *
+ * last_emission is the last time a CHANGED event was emitted.  It is
+ * used to calculate the time to send the next event.
+ */
+typedef struct {
+  gchar    *child;
+  guint64   last_emission : 63;
+  guint64   dirty         :  1;
+} PendingChange;
+
+/* QueuedEvent is a signal that will be sent immediately, as soon as the
+ * source gets a chance to dispatch.  The existence of any queued event
+ * implies that the source is ready now.
+ */
+typedef struct
+{
+  GFileMonitorEvent event_type;
+  GFile *child;
+  GFile *other;
+} QueuedEvent;
+
+static gint64
+pending_change_get_ready_time (const PendingChange *change,
+                               GFileMonitorSource  *fms)
+{
+  if (change->dirty)
+    return change->last_emission + fms->rate_limit;
+  else
+    return change->last_emission + VIRTUAL_CHANGES_DONE_DELAY;
+}
+
+static int
+pending_change_compare_ready_time (gconstpointer a_p,
+                                   gconstpointer b_p,
+                                   gpointer      user_data)
+{
+  GFileMonitorSource *fms = user_data;
+  const PendingChange *a = a_p;
+  const PendingChange *b = b_p;
+  gint64 ready_time_a;
+  gint64 ready_time_b;
+
+  ready_time_a = pending_change_get_ready_time (a, fms);
+  ready_time_b = pending_change_get_ready_time (b, fms);
+
+  if (ready_time_a < ready_time_b)
+    return -1;
+  else
+    return ready_time_a > ready_time_b;
+}
 
 static void
-g_local_file_monitor_init (GLocalFileMonitor* local_monitor)
+pending_change_free (gpointer data)
 {
+  PendingChange *change = data;
+
+  g_free (change->child);
+
+  g_slice_free (PendingChange, change);
 }
 
 static void
-g_local_file_monitor_set_property (GObject      *object,
-                                   guint         property_id,
-                                   const GValue *value,
-                                   GParamSpec   *pspec)
+queued_event_free (QueuedEvent *event)
 {
-  GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
+  g_object_unref (event->child);
+  if (event->other)
+    g_object_unref (event->other);
+
+  g_slice_free (QueuedEvent, event);
+}
+
+static gint64
+g_file_monitor_source_get_ready_time (GFileMonitorSource *fms)
+{
+  GSequenceIter *iter;
+
+  if (fms->event_queue.length)
+    return 0;
+
+  iter = g_sequence_get_begin_iter (fms->pending_changes);
+  if (g_sequence_iter_is_end (iter))
+    return -1;
+
+  return pending_change_get_ready_time (g_sequence_get (iter), fms);
+}
 
-  switch (property_id)
+static void
+g_file_monitor_source_update_ready_time (GFileMonitorSource *fms)
+{
+  g_source_set_ready_time ((GSource *) fms, g_file_monitor_source_get_ready_time (fms));
+}
+
+static GSequenceIter *
+g_file_monitor_source_find_pending_change (GFileMonitorSource *fms,
+                                           const gchar        *child)
+{
+  return g_hash_table_lookup (fms->pending_changes_table, child);
+}
+
+static void
+g_file_monitor_source_add_pending_change (GFileMonitorSource *fms,
+                                          const gchar        *child,
+                                          gint64              now)
+{
+  PendingChange *change;
+  GSequenceIter *iter;
+
+  change = g_slice_new (PendingChange);
+  change->child = g_strdup (child);
+  change->last_emission = now;
+  change->dirty = FALSE;
+
+  iter = g_sequence_insert_sorted (fms->pending_changes, change, pending_change_compare_ready_time, fms);
+  g_hash_table_insert (fms->pending_changes_table, change->child, iter);
+}
+
+static void
+g_file_monitor_source_set_pending_change_dirty (GFileMonitorSource *fms,
+                                                GSequenceIter      *iter)
+{
+  PendingChange *change;
+
+  change = g_sequence_get (iter);
+
+  if (!change->dirty)
     {
-    case PROP_FILENAME:
-      local_monitor->filename = g_value_dup_string (value);
+      change->dirty = TRUE;
+
+      g_sequence_sort_changed (iter, pending_change_compare_ready_time, fms);
+    }
+}
+
+static gboolean
+g_file_monitor_source_get_pending_change_dirty (GFileMonitorSource *fms,
+                                                GSequenceIter      *iter)
+{
+  PendingChange *change;
+
+  change = g_sequence_get (iter);
+
+  return change->dirty;
+}
+
+static void
+g_file_monitor_source_remove_pending_change (GFileMonitorSource *fms,
+                                             GSequenceIter      *iter,
+                                             const gchar        *child)
+{
+  /* must remove the hash entry first -- its key is owned by the data
+   * which will be freed when removing the sequence iter
+   */
+  g_hash_table_remove (fms->pending_changes_table, child);
+  g_sequence_remove (iter);
+}
+
+static void
+g_file_monitor_source_queue_event (GFileMonitorSource *fms,
+                                   GFileMonitorEvent   event_type,
+                                   const gchar        *child,
+                                   GFile              *other)
+{
+  QueuedEvent *event;
+
+  event = g_slice_new (QueuedEvent);
+  event->event_type = event_type;
+  if (child)
+    event->child = g_local_file_new_from_dirname_and_basename (fms->dirname, child);
+  else if (fms->dirname)
+    event->child = _g_local_file_new (fms->dirname);
+  else if (fms->filename)
+    event->child = _g_local_file_new (fms->filename);
+  event->other = other;
+  if (other)
+    g_object_ref (other);
+
+  g_queue_push_tail (&fms->event_queue, event);
+}
+
+static void
+g_file_monitor_source_file_changed (GFileMonitorSource *fms,
+                                    const gchar        *child,
+                                    gint64              now)
+{
+  GSequenceIter *pending;
+
+  pending = g_file_monitor_source_find_pending_change (fms, child);
+
+  /* If there is no pending change, emit one and create a record,
+   * else: just mark the existing record as dirty.
+   */
+  if (!pending)
+    {
+      g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, child, NULL);
+      g_file_monitor_source_add_pending_change (fms, child, now);
+    }
+  else
+    g_file_monitor_source_set_pending_change_dirty (fms, pending);
+
+  g_file_monitor_source_update_ready_time (fms);
+}
+
+static void
+g_file_monitor_source_file_changes_done (GFileMonitorSource *fms,
+                                         const gchar        *child)
+{
+  GSequenceIter *pending;
+
+  pending = g_file_monitor_source_find_pending_change (fms, child);
+  if (pending)
+    {
+      /* If it is dirty, make sure we push out the last CHANGED event */
+      if (g_file_monitor_source_get_pending_change_dirty (fms, pending))
+        g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, child, NULL);
+
+      g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, child, NULL);
+      g_file_monitor_source_remove_pending_change (fms, pending, child);
+    }
+}
+
+static void
+g_file_monitor_source_file_created (GFileMonitorSource *fms,
+                                    const gchar        *child,
+                                    gint64              event_time)
+{
+  /* Unlikely, but if we have pending changes for this filename, make
+   * sure we flush those out first, before creating the new ones.
+   */
+  g_file_monitor_source_file_changes_done (fms, child);
+
+  /* Emit CREATE and add a pending changes record */
+  g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CREATED, child, NULL);
+  g_file_monitor_source_add_pending_change (fms, child, event_time);
+}
+
+static void
+g_file_monitor_source_send_event (GFileMonitorSource *fms,
+                                  GFileMonitorEvent   event_type,
+                                  const gchar        *child,
+                                  GFile              *other)
+{
+  /* always flush any pending changes before we queue a new event */
+  g_file_monitor_source_file_changes_done (fms, child);
+  g_file_monitor_source_queue_event (fms, event_type, child, other);
+}
+
+static void
+g_file_monitor_source_send_synthetic_created (GFileMonitorSource *fms,
+                                              const gchar        *child)
+{
+  g_file_monitor_source_file_changes_done (fms, child);
+  g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CREATED, child, NULL);
+  g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, child, NULL);
+}
+
+static gboolean
+is_basename (const gchar *name)
+{
+  if (name[0] == '.' && ((name[1] == '.' && name[2] == '\0') || name[1] == '\0'))
+    return FALSE;
+
+  return !strchr (name, '/');
+}
+
+void
+g_file_monitor_source_handle_event (GFileMonitorSource *fms,
+                                    GFileMonitorEvent   event_type,
+                                    const gchar        *child,
+                                    const gchar        *rename_to,
+                                    GFile              *other,
+                                    gint64              event_time)
+{
+  g_assert (!child || is_basename (child));
+  g_assert (!rename_to || is_basename (rename_to));
+
+  if (fms->basename && (!child || !g_str_equal (child, fms->basename)))
+    return;
+
+  g_mutex_lock (&fms->lock);
+
+  /* monitor is already gone -- don't bother */
+  if (!fms->instance)
+    {
+      g_mutex_unlock (&fms->lock);
+      return;
+    }
+
+  switch (event_type)
+    {
+    case G_FILE_MONITOR_EVENT_CREATED:
+      g_assert (!other && !rename_to);
+      g_file_monitor_source_file_created (fms, child, event_time);
       break;
 
-    case PROP_FLAGS:
-      local_monitor->flags = g_value_get_flags (value);
+    case G_FILE_MONITOR_EVENT_CHANGED:
+      g_assert (!other && !rename_to);
+      g_file_monitor_source_file_changed (fms, child, event_time);
       break;
 
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+      g_assert (!other && !rename_to);
+      g_file_monitor_source_file_changes_done (fms, child);
       break;
+
+    case G_FILE_MONITOR_EVENT_MOVED_IN:
+      g_assert (!rename_to);
+      if (fms->flags & G_FILE_MONITOR_WATCH_MOVES)
+        g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED_IN, child, other);
+      else
+        g_file_monitor_source_send_synthetic_created (fms, child);
+      break;
+
+    case G_FILE_MONITOR_EVENT_MOVED_OUT:
+      g_assert (!rename_to);
+      if (fms->flags & G_FILE_MONITOR_WATCH_MOVES)
+        g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED_OUT, child, other);
+      else if (fms->flags & G_FILE_MONITOR_SEND_MOVED)
+        g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED, child, other);
+      else
+        g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_DELETED, child, NULL);
+      break;
+
+    case G_FILE_MONITOR_EVENT_RENAMED:
+      g_assert (!other && rename_to);
+      if (fms->flags & G_FILE_MONITOR_WATCH_MOVES)
+        {
+          GFile *other;
+
+          other = g_local_file_new_from_dirname_and_basename (fms->dirname, rename_to);
+          g_file_monitor_source_file_changes_done (fms, rename_to);
+          g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_RENAMED, child, other);
+          g_object_unref (other);
+        }
+      else if (fms->flags & G_FILE_MONITOR_SEND_MOVED)
+        {
+          GFile *other;
+
+          other = g_local_file_new_from_dirname_and_basename (fms->dirname, rename_to);
+          g_file_monitor_source_file_changes_done (fms, rename_to);
+          g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED, child, other);
+          g_object_unref (other);
+        }
+      else
+        {
+          g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_DELETED, child, NULL);
+          g_file_monitor_source_send_synthetic_created (fms, child);
+        }
+      break;
+
+    case G_FILE_MONITOR_EVENT_DELETED:
+    case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+    case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+    case G_FILE_MONITOR_EVENT_UNMOUNTED:
+      g_assert (!other && !rename_to);
+      g_file_monitor_source_send_event (fms, event_type, child, NULL);
+      break;
+
+    case G_FILE_MONITOR_EVENT_MOVED:
+      /* was never available in this API */
+    default:
+      g_assert_not_reached ();
     }
+
+  g_file_monitor_source_update_ready_time (fms);
+
+  g_mutex_unlock (&fms->lock);
 }
 
-void
-g_local_file_monitor_start (GLocalFileMonitor *local_monitor)
+static gint64
+g_file_monitor_source_get_rate_limit (GFileMonitorSource *fms)
 {
-  GLocalFileMonitorClass *class;
+  gint64 rate_limit;
+
+  g_mutex_lock (&fms->lock);
+  rate_limit = fms->rate_limit;
+  g_mutex_unlock (&fms->lock);
+
+  return rate_limit;
+}
+
+static gboolean
+g_file_monitor_source_set_rate_limit (GFileMonitorSource *fms,
+                                      gint64              rate_limit)
+{
+  gboolean changed;
+
+  g_mutex_lock (&fms->lock);
+
+  if (rate_limit != fms->rate_limit)
+    {
+      fms->rate_limit = rate_limit;
+
+      g_sequence_sort (fms->pending_changes, pending_change_compare_ready_time, fms);
+      g_file_monitor_source_update_ready_time (fms);
+    }
+  else
+    changed = FALSE;
 
-  class = G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor);
+  g_mutex_unlock (&fms->lock);
 
-  if (class->start)
-    class->start (local_monitor);
+  return changed;
+}
+
+static gboolean
+g_file_monitor_source_dispatch (GSource     *source,
+                                GSourceFunc  callback,
+                                gpointer     user_data)
+{
+  GFileMonitorSource *fms = (GFileMonitorSource *) source;
+  QueuedEvent *event;
+  GQueue event_queue;
+  gint64 now;
+
+  /* make sure the monitor still exists */
+  if (!fms->instance)
+    return FALSE;
+
+  now = g_source_get_time (source);
+
+  /* Acquire the lock once and grab all events in one go, handling the
+   * queued events first.  This avoids strange possibilities in cases of
+   * long delays, such as CHANGED events coming before CREATED events.
+   *
+   * We do this by converting the applicable pending changes into queued
+   * events (after the ones already queued) and then stealing the entire
+   * event queue in one go.
+   */
+  g_mutex_lock (&fms->lock);
+
+  /* Create events for any pending changes that are due to fire */
+  while (g_sequence_get_length (fms->pending_changes))
+    {
+      GSequenceIter *iter = g_sequence_get_begin_iter (fms->pending_changes);
+      PendingChange *pending = g_sequence_get (iter);
+
+      /* We've gotten to a pending change that's not ready.  Stop. */
+      if (pending_change_get_ready_time (pending, fms) > now)
+        break;
+
+      if (pending->dirty)
+        {
+          /* It's time to send another CHANGED and update the record */
+          g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, pending->child, NULL);
+          pending->last_emission = now;
+          pending->dirty = FALSE;
+
+          g_sequence_sort_changed (iter, pending_change_compare_ready_time, fms);
+        }
+      else
+        {
+          /* It's time to send CHANGES_DONE and remove the pending record */
+          g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, pending->child, 
NULL);
+          g_file_monitor_source_remove_pending_change (fms, iter, pending->child);
+        }
+    }
+
+  /* Steal the queue */
+  memcpy (&event_queue, &fms->event_queue, sizeof event_queue);
+  memset (&fms->event_queue, 0, sizeof fms->event_queue);
+
+  g_file_monitor_source_update_ready_time (fms);
+
+  g_mutex_unlock (&fms->lock);
+
+  /* We now have our list of events to deliver */
+  while ((event = g_queue_pop_head (&event_queue)))
+    {
+      /* an event handler could destroy 'instance', so check each time */
+      if (fms->instance)
+        g_file_monitor_emit_event (fms->instance, event->child, event->other, event->event_type);
+
+      queued_event_free (event);
+    }
+
+  return TRUE;
+}
+
+static void
+g_file_monitor_source_dispose (GFileMonitorSource *fms)
+{
+  g_mutex_lock (&fms->lock);
+
+  if (fms->instance)
+    {
+      GHashTableIter iter;
+      gpointer seqiter;
+      QueuedEvent *event;
+
+      g_hash_table_iter_init (&iter, fms->pending_changes_table);
+      while (g_hash_table_iter_next (&iter, NULL, &seqiter))
+        {
+          g_hash_table_iter_remove (&iter);
+          g_sequence_remove (seqiter);
+        }
+
+      while ((event = g_queue_pop_head (&fms->event_queue)))
+        queued_event_free (event);
+
+      g_assert (g_sequence_get_length (fms->pending_changes) == 0);
+      g_assert (g_hash_table_size (fms->pending_changes_table) == 0);
+      g_assert (fms->event_queue.length == 0);
+      fms->instance = NULL;
+
+      g_file_monitor_source_update_ready_time (fms);
+    }
+
+  g_mutex_unlock (&fms->lock);
+
+  g_source_destroy ((GSource *) fms);
+}
+
+static void
+g_file_monitor_source_finalize (GSource *source)
+{
+  GFileMonitorSource *fms = (GFileMonitorSource *) source;
+
+  /* should already have been cleared in dispose of the monitor */
+  g_assert (fms->instance == NULL);
+  g_assert (g_sequence_get_length (fms->pending_changes) == 0);
+  g_assert (g_hash_table_size (fms->pending_changes_table) == 0);
+  g_assert (fms->event_queue.length == 0);
+
+  g_hash_table_unref (fms->pending_changes_table);
+  g_sequence_free (fms->pending_changes);
+
+  g_free (fms->dirname);
+  g_free (fms->basename);
+  g_free (fms->filename);
+
+  g_mutex_clear (&fms->lock);
+}
+
+static guint
+str_hash0 (gconstpointer str)
+{
+  return str ? g_str_hash (str) : 0;
+}
+
+static gboolean
+str_equal0 (gconstpointer a,
+            gconstpointer b)
+{
+  return g_strcmp0 (a, b) == 0;
+}
+
+static GFileMonitorSource *
+g_file_monitor_source_new (gpointer           instance,
+                           const gchar       *filename,
+                           gboolean           is_directory,
+                           GFileMonitorFlags  flags)
+{
+  static GSourceFuncs source_funcs = {
+    NULL, NULL,
+    g_file_monitor_source_dispatch,
+    g_file_monitor_source_finalize
+  };
+  GFileMonitorSource *fms;
+  GSource *source;
+
+  source = g_source_new (&source_funcs, sizeof (GFileMonitorSource));
+  fms = (GFileMonitorSource *) source;
+
+  g_mutex_init (&fms->lock);
+  fms->instance = instance;
+  fms->pending_changes = g_sequence_new (pending_change_free);
+  fms->pending_changes_table = g_hash_table_new (str_hash0, str_equal0);
+  fms->rate_limit = DEFAULT_RATE_LIMIT;
+  fms->flags = flags;
+
+  if (is_directory)
+    {
+      fms->dirname = g_strdup (filename);
+      fms->basename = NULL;
+      fms->filename = NULL;
+    }
+  else if (flags & G_FILE_MONITOR_WATCH_HARD_LINKS)
+    {
+      fms->dirname = NULL;
+      fms->basename = NULL;
+      fms->filename = g_strdup (filename);
+    }
+  else
+    {
+      fms->dirname = g_path_get_dirname (filename);
+      fms->basename = g_path_get_basename (filename);
+      fms->filename = NULL;
+    }
+
+  return fms;
+}
+
+G_DEFINE_ABSTRACT_TYPE (GLocalFileMonitor, g_local_file_monitor, G_TYPE_FILE_MONITOR)
+
+enum {
+  PROP_0,
+  PROP_RATE_LIMIT,
+};
+
+static void
+g_local_file_monitor_get_property (GObject *object, guint prop_id,
+                                   GValue *value, GParamSpec *pspec)
+{
+  GLocalFileMonitor *monitor = G_LOCAL_FILE_MONITOR (object);
+  gint64 rate_limit;
+
+  g_assert (prop_id == PROP_RATE_LIMIT);
+
+  rate_limit = g_file_monitor_source_get_rate_limit (monitor->source);
+  rate_limit /= G_TIME_SPAN_MILLISECOND;
+
+  g_value_set_int (value, rate_limit);
+}
+
+static void
+g_local_file_monitor_set_property (GObject *object, guint prop_id,
+                                   const GValue *value, GParamSpec *pspec)
+{
+  GLocalFileMonitor *monitor = G_LOCAL_FILE_MONITOR (object);
+  gint64 rate_limit;
+
+  g_assert (prop_id == PROP_RATE_LIMIT);
+
+  rate_limit = g_value_get_int (value);
+  rate_limit *= G_TIME_SPAN_MILLISECOND;
+
+  if (g_file_monitor_source_set_rate_limit (monitor->source, rate_limit))
+    g_object_notify (object, "rate-limit");
+}
+
+#ifndef G_OS_WIN32
+static void
+g_local_file_monitor_mounts_changed (GUnixMountMonitor *mount_monitor,
+                                     gpointer           user_data)
+{
+  GLocalFileMonitor *local_monitor = user_data;
+  GUnixMountEntry *mount;
+  gboolean is_mounted;
+  GFile *file;
+
+  /* Emulate unmount detection */
+  mount = g_unix_mount_at (local_monitor->source->dirname, NULL);
+
+  is_mounted = mount != NULL;
+
+  if (mount)
+    g_unix_mount_free (mount);
+
+  if (local_monitor->was_mounted != is_mounted)
+    {
+      if (local_monitor->was_mounted && !is_mounted)
+        {
+          file = g_file_new_for_path (local_monitor->source->dirname);
+          g_file_monitor_emit_event (G_FILE_MONITOR (local_monitor), file, NULL, 
G_FILE_MONITOR_EVENT_UNMOUNTED);
+          g_object_unref (file);
+        }
+      local_monitor->was_mounted = is_mounted;
+    }
+}
+#endif
+
+static void
+g_local_file_monitor_start (GLocalFileMonitor *local_monitor,
+                            const gchar       *filename,
+                            gboolean           is_directory,
+                            GFileMonitorFlags  flags,
+                            GMainContext      *context)
+{
+  GLocalFileMonitorClass *class = G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor);
+  GFileMonitorSource *source;
+
+  g_return_if_fail (G_IS_LOCAL_FILE_MONITOR (local_monitor));
+
+  g_assert (!local_monitor->source);
+
+  if (is_directory && !class->mount_notify && (flags & G_FILE_MONITOR_WATCH_MOUNTS))
+    {
+#ifdef G_OS_WIN32
+      /*claim everything was mounted */
+      local_monitor->was_mounted = TRUE;
+#else
+      GUnixMountEntry *mount;
+
+      /* Emulate unmount detection */
+
+      mount = g_unix_mount_at (local_monitor->source->dirname, NULL);
+
+      local_monitor->was_mounted = mount != NULL;
+
+      if (mount)
+        g_unix_mount_free (mount);
+
+      local_monitor->mount_monitor = g_unix_mount_monitor_get ();
+      g_signal_connect_object (local_monitor->mount_monitor, "mounts-changed",
+                               G_CALLBACK (g_local_file_monitor_mounts_changed), local_monitor, 0);
+#endif
+    }
+
+  source = g_file_monitor_source_new (local_monitor, filename, is_directory, flags);
+  local_monitor->source = source; /* owns the ref */
+
+  G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor)->start (local_monitor,
+                                                         source->dirname, source->basename, source->filename,
+                                                         source);
+
+  g_source_attach ((GSource *) source, context);
+}
+
+static void
+g_local_file_monitor_dispose (GObject *object)
+{
+  GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
+
+  g_file_monitor_source_dispose (local_monitor->source);
+
+  G_OBJECT_CLASS (g_local_file_monitor_parent_class)->dispose (object);
 }
 
 static void
@@ -83,49 +782,32 @@ g_local_file_monitor_finalize (GObject *object)
 {
   GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
 
-  g_free (local_monitor->filename);
+  g_source_unref ((GSource *) local_monitor->source);
 
   G_OBJECT_CLASS (g_local_file_monitor_parent_class)->finalize (object);
 }
 
-static void g_local_file_monitor_class_init (GLocalFileMonitorClass *klass)
+static void
+g_local_file_monitor_init (GLocalFileMonitor* local_monitor)
+{
+}
+
+static void g_local_file_monitor_class_init (GLocalFileMonitorClass *class)
 {
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
 
+  gobject_class->get_property = g_local_file_monitor_get_property;
   gobject_class->set_property = g_local_file_monitor_set_property;
+  gobject_class->dispose = g_local_file_monitor_dispose;
   gobject_class->finalize = g_local_file_monitor_finalize;
 
-  g_object_class_install_property (gobject_class, 
-                                   PROP_FILENAME,
-                                   g_param_spec_string ("filename", 
-                                                        P_("File name"), 
-                                                        P_("File name to monitor"),
-                                                        NULL, 
-                                                        G_PARAM_CONSTRUCT_ONLY|
-                                                        G_PARAM_WRITABLE|
-                                                        
G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB));
-
-  g_object_class_install_property (gobject_class,
-                                   PROP_FLAGS,
-                                   g_param_spec_flags ("flags",
-                                                      P_("Monitor flags"),
-                                                      P_("Monitor flags"),
-                                                      G_TYPE_FILE_MONITOR_FLAGS,
-                                                      0,
-                                                      G_PARAM_CONSTRUCT_ONLY|
-                                                      G_PARAM_WRITABLE|
-                                                      
G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB));
-}
-
-GFileMonitor*
-_g_local_file_monitor_new (const char         *pathname,
-                           GFileMonitorFlags   flags,
-                           GMainContext       *context,
-                           gboolean            is_remote_fs,
-                           gboolean            do_start,
-                           GError            **error)
-{
-  GFileMonitor *monitor = NULL;
+  g_object_class_override_property (gobject_class, PROP_RATE_LIMIT, "rate-limit");
+}
+
+static GLocalFileMonitor *
+g_local_file_monitor_new (gboolean   is_remote_fs,
+                          GError   **error)
+{
   GType type = G_TYPE_INVALID;
 
   if (is_remote_fs)
@@ -138,14 +820,57 @@ _g_local_file_monitor_new (const char         *pathname,
                                           "GIO_USE_FILE_MONITOR",
                                           G_STRUCT_OFFSET (GLocalFileMonitorClass, is_supported));
 
-  if (type != G_TYPE_INVALID)
-    monitor = G_FILE_MONITOR (g_object_new (type, "filename", pathname, "flags", flags, "context", context, 
NULL));
-  else
-    g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                         _("Unable to find default local file monitor type"));
+  if (type == G_TYPE_INVALID)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           _("Unable to find default local file monitor type"));
+      return NULL;
+    }
+
+  return g_object_new (type, NULL);
+}
+
+GFileMonitor *
+g_local_file_monitor_new_for_path (const gchar        *pathname,
+                                   gboolean            is_directory,
+                                   GFileMonitorFlags   flags,
+                                   GError            **error)
+{
+  GLocalFileMonitor *monitor;
+  gboolean is_remote_fs;
 
-  if (monitor && do_start)
-    g_local_file_monitor_start (G_LOCAL_FILE_MONITOR (monitor));
+  is_remote_fs = g_local_file_is_remote (pathname);
+
+  monitor = g_local_file_monitor_new (is_remote_fs, error);
+
+  if (monitor)
+    g_local_file_monitor_start (monitor, pathname, is_directory, flags, g_main_context_get_thread_default 
());
+
+  return G_FILE_MONITOR (monitor);
+}
+
+GFileMonitor *
+g_local_file_monitor_new_in_worker (const gchar           *pathname,
+                                    gboolean               is_directory,
+                                    GFileMonitorFlags      flags,
+                                    GFileMonitorCallback   callback,
+                                    gpointer               user_data,
+                                    GError               **error)
+{
+  GLocalFileMonitor *monitor;
+  gboolean is_remote_fs;
+
+  is_remote_fs = g_local_file_is_remote (pathname);
+
+  monitor = g_local_file_monitor_new (is_remote_fs, error);
+
+  if (monitor)
+    {
+      if (callback)
+        g_signal_connect (monitor, "changed", G_CALLBACK (callback), user_data);
+
+      g_local_file_monitor_start (monitor, pathname, is_directory, flags, 
GLIB_PRIVATE_CALL(g_get_worker_context) ());
+    }
 
-  return monitor;
+  return G_FILE_MONITOR (monitor);
 }
diff --git a/gio/glocalfilemonitor.h b/gio/glocalfilemonitor.h
index aed2e9c..ced880f 100644
--- a/gio/glocalfilemonitor.h
+++ b/gio/glocalfilemonitor.h
@@ -22,6 +22,7 @@
 #define __G_LOCAL_FILE_MONITOR_H__
 
 #include <gio/gfilemonitor.h>
+#include "gunixmounts.h"
 
 G_BEGIN_DECLS
 
@@ -37,13 +38,15 @@ G_BEGIN_DECLS
 
 typedef struct _GLocalFileMonitor      GLocalFileMonitor;
 typedef struct _GLocalFileMonitorClass GLocalFileMonitorClass;
+typedef struct _GFileMonitorSource     GFileMonitorSource;
 
 struct _GLocalFileMonitor
 {
   GFileMonitor parent_instance;
 
-  gchar *filename;
-  GFileMonitorFlags flags;
+  GFileMonitorSource *source;
+  GUnixMountMonitor  *mount_monitor;
+  gboolean            was_mounted;
 };
 
 struct _GLocalFileMonitorClass
@@ -51,7 +54,13 @@ struct _GLocalFileMonitorClass
   GFileMonitorClass parent_class;
 
   gboolean (* is_supported) (void);
-  void     (* start)        (GLocalFileMonitor *local_monitor);
+  void     (* start)        (GLocalFileMonitor *local_monitor,
+                             const gchar *dirname,
+                             const gchar *basename,
+                             const gchar *filename,
+                             GFileMonitorSource *source);
+
+  gboolean mount_notify;
 };
 
 #ifdef G_OS_UNIX
@@ -59,18 +68,37 @@ GLIB_AVAILABLE_IN_ALL
 #endif
 GType           g_local_file_monitor_get_type (void) G_GNUC_CONST;
 
-GFileMonitor * _g_local_file_monitor_new      (const char         *pathname,
-                                               GFileMonitorFlags   flags,
-                                               GMainContext       *context,
-                                               gboolean            is_remote_fs,
-                                               gboolean            do_start,
-                                               GError            **error);
-void            g_local_file_monitor_start    (GLocalFileMonitor  *local_monitor);
-
-/* Actually in glocalfile.c */
-GLocalFileMonitor *  g_local_file_monitor_new_in_worker (const char         *pathname,
-                                                         GFileMonitorFlags   flags,
-                                                         GError            **error);
+/* for glocalfile.c */
+GFileMonitor *
+g_local_file_monitor_new_for_path (const gchar        *pathname,
+                                   gboolean            is_directory,
+                                   GFileMonitorFlags   flags,
+                                   GError            **error);
+
+/* for various users in glib */
+typedef void (* GFileMonitorCallback) (GFileMonitor      *monitor,
+                                       GFile             *child,
+                                       GFile             *other,
+                                       GFileMonitorEvent  event,
+                                       gpointer           user_data);
+GFileMonitor *
+g_local_file_monitor_new_in_worker (const gchar           *pathname,
+                                    gboolean               is_directory,
+                                    GFileMonitorFlags      flags,
+                                    GFileMonitorCallback   callback,
+                                    gpointer               user_data,
+                                    GError               **error);
+
+/* for implementations of GLocalFileMonitor */
+GLIB_AVAILABLE_IN_2_44
+void
+g_file_monitor_source_handle_event (GFileMonitorSource *fms,
+                                    GFileMonitorEvent   event_type,
+                                    const gchar        *child,
+                                    const gchar        *rename_to,
+                                    GFile              *other,
+                                    gint64              event_time);
+
 
 G_END_DECLS
 
diff --git a/gio/inotify/Makefile.am b/gio/inotify/Makefile.am
index 2838685..8b4b3ae 100644
--- a/gio/inotify/Makefile.am
+++ b/gio/inotify/Makefile.am
@@ -15,8 +15,6 @@ libinotify_la_SOURCES =               \
        inotify-helper.h                \
        ginotifyfilemonitor.c           \
        ginotifyfilemonitor.h           \
-       ginotifydirectorymonitor.c      \
-       ginotifydirectorymonitor.h      \
        $(NULL)
 
 libinotify_la_CFLAGS = \
diff --git a/gio/inotify/ginotifydirectorymonitor.c b/gio/inotify/ginotifydirectorymonitor.c
index cd4a610..80a2d6f 100644
--- a/gio/inotify/ginotifydirectorymonitor.c
+++ b/gio/inotify/ginotifydirectorymonitor.c
@@ -31,7 +31,7 @@
 
 struct _GInotifyDirectoryMonitor
 {
-  GLocalDirectoryMonitor parent_instance;
+  GLocalFileMonitor parent_instance;
   inotify_sub *sub;
 };
 
diff --git a/gio/inotify/ginotifyfilemonitor.c b/gio/inotify/ginotifyfilemonitor.c
index e122d67..15a1131 100644
--- a/gio/inotify/ginotifyfilemonitor.c
+++ b/gio/inotify/ginotifyfilemonitor.c
@@ -19,6 +19,7 @@
  * Authors: Alexander Larsson <alexl redhat com>
  *          John McCutchan <john johnmccutchan com> 
  *          Sebastian Dröge <slomo circular-chaos org>
+ *          Ryan Lortie <desrt desrt ca>
  */
 
 #include "config.h"
@@ -32,100 +33,62 @@
 struct _GInotifyFileMonitor
 {
   GLocalFileMonitor parent_instance;
-  gchar *filename;
-  gchar *dirname;
+
   inotify_sub *sub;
-  gboolean pair_moves;
 };
 
-static gboolean g_inotify_file_monitor_cancel (GFileMonitor* monitor);
-
-#define g_inotify_file_monitor_get_type _g_inotify_file_monitor_get_type
 G_DEFINE_TYPE_WITH_CODE (GInotifyFileMonitor, g_inotify_file_monitor, G_TYPE_LOCAL_FILE_MONITOR,
-                        g_io_extension_point_implement (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
-                                                        g_define_type_id,
-                                                        "inotify",
-                                                        20))
+                         g_io_extension_point_implement (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
+                                                         g_define_type_id, "inotify", 20))
 
-static void
-g_inotify_file_monitor_finalize (GObject *object)
+static gboolean
+g_inotify_file_monitor_is_supported (void)
 {
-  GInotifyFileMonitor *inotify_monitor = G_INOTIFY_FILE_MONITOR (object);
-  inotify_sub *sub = inotify_monitor->sub;
-
-  if (sub)
-    {
-      _ih_sub_cancel (sub);
-      _ih_sub_free (sub);
-      inotify_monitor->sub = NULL;
-    }
-
-  g_free (inotify_monitor->filename);
-  g_free (inotify_monitor->dirname);
-
-  G_OBJECT_CLASS (g_inotify_file_monitor_parent_class)->finalize (object);
+  return _ih_startup ();
 }
 
 static void
-g_inotify_file_monitor_start (GLocalFileMonitor *local_monitor)
+g_inotify_file_monitor_start (GLocalFileMonitor  *local_monitor,
+                              const gchar        *dirname,
+                              const gchar        *basename,
+                              const gchar        *filename,
+                              GFileMonitorSource *source)
 {
   GInotifyFileMonitor *inotify_monitor = G_INOTIFY_FILE_MONITOR (local_monitor);
-  const gchar *filename = NULL;
-  gboolean watch_hardlinks;
-  inotify_sub *sub = NULL;
-  gboolean pair_moves;
-  gboolean ret_ih_startup; /* return value of _ih_startup, for asserting */
-
-  filename = local_monitor->filename;
-  g_assert (filename != NULL);
-
-  inotify_monitor->filename = g_path_get_basename (filename);
-  inotify_monitor->dirname = g_path_get_dirname (filename);
-
-  /* Will never fail as is_supported() should be called before instantiating
-   * anyway */
-  /* assert on return value */
-  ret_ih_startup = _ih_startup();
-  g_assert (ret_ih_startup);
-
-  pair_moves = local_monitor->flags & G_FILE_MONITOR_SEND_MOVED;
-  watch_hardlinks = local_monitor->flags & G_FILE_MONITOR_WATCH_HARD_LINKS;
-
-  sub = _ih_sub_new (inotify_monitor->dirname,
-                     inotify_monitor->filename,
-                     pair_moves,
-                     watch_hardlinks,
-                     inotify_monitor);
-
-  /* FIXME: what to do about errors here? we can't return NULL or another
-   * kind of error and an assertion is probably too hard */
-  g_assert (sub != NULL);
-
-  /* _ih_sub_add allways returns TRUE, see gio/inotify/inotify-helper.c line 109
-   * g_assert (_ih_sub_add (sub)); */
-  _ih_sub_add (sub);
-
-  inotify_monitor->sub = sub;
+  gboolean success;
+
+  /* should already have been called, from is_supported() */
+  success = _ih_startup ();
+  g_assert (success);
+
+  inotify_monitor->sub = _ih_sub_new (dirname, basename, filename != NULL, source);
+  _ih_sub_add (inotify_monitor->sub);
 }
 
 static gboolean
-g_inotify_file_monitor_is_supported (void)
+g_inotify_file_monitor_cancel (GFileMonitor *monitor)
 {
-  return _ih_startup ();
+  GInotifyFileMonitor *inotify_monitor = G_INOTIFY_FILE_MONITOR (monitor);
+
+  if (inotify_monitor->sub)
+    {
+      _ih_sub_cancel (inotify_monitor->sub);
+      _ih_sub_free (inotify_monitor->sub);
+      inotify_monitor->sub = NULL;
+    }
+
+  return TRUE;
 }
 
 static void
-g_inotify_file_monitor_class_init (GInotifyFileMonitorClass* klass)
+g_inotify_file_monitor_finalize (GObject *object)
 {
-  GObjectClass* gobject_class = G_OBJECT_CLASS (klass);
-  GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass);
-  GLocalFileMonitorClass *local_file_monitor_class = G_LOCAL_FILE_MONITOR_CLASS (klass);
+  GInotifyFileMonitor *inotify_monitor = G_INOTIFY_FILE_MONITOR (object);
 
-  gobject_class->finalize = g_inotify_file_monitor_finalize;
-  file_monitor_class->cancel = g_inotify_file_monitor_cancel;
+  /* must surely have been cancelled already */
+  g_assert (!inotify_monitor->sub);
 
-  local_file_monitor_class->is_supported = g_inotify_file_monitor_is_supported;
-  local_file_monitor_class->start = g_inotify_file_monitor_start;
+  G_OBJECT_CLASS (g_inotify_file_monitor_parent_class)->finalize (object);
 }
 
 static void
@@ -133,21 +96,17 @@ g_inotify_file_monitor_init (GInotifyFileMonitor* monitor)
 {
 }
 
-static gboolean
-g_inotify_file_monitor_cancel (GFileMonitor* monitor)
+static void
+g_inotify_file_monitor_class_init (GInotifyFileMonitorClass* klass)
 {
-  GInotifyFileMonitor *inotify_monitor = G_INOTIFY_FILE_MONITOR (monitor);
-  inotify_sub *sub = inotify_monitor->sub;
-
-  if (sub) 
-    {
-      _ih_sub_cancel (sub);
-      _ih_sub_free (sub);
-      inotify_monitor->sub = NULL;
-    }
+  GObjectClass* gobject_class = G_OBJECT_CLASS (klass);
+  GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass);
+  GLocalFileMonitorClass *local_file_monitor_class = G_LOCAL_FILE_MONITOR_CLASS (klass);
 
-  if (G_FILE_MONITOR_CLASS (g_inotify_file_monitor_parent_class)->cancel)
-    (*G_FILE_MONITOR_CLASS (g_inotify_file_monitor_parent_class)->cancel) (monitor);
+  local_file_monitor_class->is_supported = g_inotify_file_monitor_is_supported;
+  local_file_monitor_class->start = g_inotify_file_monitor_start;
+  local_file_monitor_class->mount_notify = TRUE;
+  file_monitor_class->cancel = g_inotify_file_monitor_cancel;
 
-  return TRUE;
+  gobject_class->finalize = g_inotify_file_monitor_finalize;
 }
diff --git a/gio/inotify/ginotifyfilemonitor.h b/gio/inotify/ginotifyfilemonitor.h
index 05837b9..2ff8de8 100644
--- a/gio/inotify/ginotifyfilemonitor.h
+++ b/gio/inotify/ginotifyfilemonitor.h
@@ -32,7 +32,7 @@
 
 G_BEGIN_DECLS
 
-#define G_TYPE_INOTIFY_FILE_MONITOR            (_g_inotify_file_monitor_get_type ())
+#define G_TYPE_INOTIFY_FILE_MONITOR            (g_inotify_file_monitor_get_type ())
 #define G_INOTIFY_FILE_MONITOR(o)              (G_TYPE_CHECK_INSTANCE_CAST ((o), 
G_TYPE_INOTIFY_FILE_MONITOR, GInotifyFileMonitor))
 #define G_INOTIFY_FILE_MONITOR_CLASS(k)                (G_TYPE_CHECK_CLASS_CAST ((k), 
G_TYPE_INOTIFY_FILE_MONITOR, GInotifyFileMonitorClass))
 #define G_IS_INOTIFY_FILE_MONITOR(o)           (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
G_TYPE_INOTIFY_FILE_MONITOR))
@@ -45,7 +45,7 @@ struct _GInotifyFileMonitorClass {
   GLocalFileMonitorClass parent_class;
 };
 
-GType _g_inotify_file_monitor_get_type (void);
+GType g_inotify_file_monitor_get_type (void);
 
 G_END_DECLS
 
diff --git a/gio/inotify/inotify-helper.c b/gio/inotify/inotify-helper.c
index abf66c8..843db26 100644
--- a/gio/inotify/inotify-helper.c
+++ b/gio/inotify/inotify-helper.c
@@ -29,8 +29,7 @@
 #include <sys/ioctl.h>
 /* Just include the local header to stop all the pain */
 #include <sys/inotify.h>
-#include <gio/glocalfile.h>
-#include <gio/gfilemonitor.h>
+#include <gio/glocalfilemonitor.h>
 #include <gio/gfile.h>
 #include "inotify-helper.h"
 #include "inotify-missing.h"
@@ -109,6 +108,7 @@ _ih_sub_add (inotify_sub *sub)
     _im_add (sub);
   
   G_UNLOCK (inotify_lock);
+
   return TRUE;
 }
 
@@ -129,7 +129,7 @@ _ih_sub_cancel (inotify_sub *sub)
     }
   
   G_UNLOCK (inotify_lock);
-  
+
   return TRUE;
 }
 
@@ -150,113 +150,60 @@ _ih_fullpath_from_event (ik_event_t *event,
    return fullpath;
 }
 
-
-static gboolean
-ih_event_is_paired_move (ik_event_t *event)
-{
-  if (event->pair)
-    {
-      ik_event_t *paired = event->pair;
-      /* intofiy(7): IN_MOVE == IN_MOVED_FROM | IN_MOVED_TO */
-      return (event->mask | paired->mask) & IN_MOVE;
-    }
-
-    return FALSE;
-}
-
 static void
-ih_event_callback (ik_event_t  *event, 
+ih_event_callback (ik_event_t  *event,
                    inotify_sub *sub,
-                  gboolean     file_event)
+                   gboolean     file_event)
 {
-  gchar *fullpath;
-  GFileMonitorEvent eflags;
-  GFile* child;
-  GFile* other;
+  g_assert (!file_event); /* XXX hardlink support */
 
-  eflags = ih_mask_to_EventFlags (event->mask);
-  fullpath = _ih_fullpath_from_event (event, sub->dirname,
-                                     file_event ? sub->filename : NULL);
-  child = g_file_new_for_path (fullpath);
-  g_free (fullpath);
-
-  if (ih_event_is_paired_move (event) && sub->pair_moves)
+  if (event->mask & IN_MOVE)
     {
-      const char *parent_dir = (char *) _ip_get_path_for_wd (event->pair->wd);
-      fullpath = _ih_fullpath_from_event (event->pair, parent_dir, NULL);
-      other = g_file_new_for_path (fullpath);
-      g_free (fullpath);
-      eflags = G_FILE_MONITOR_EVENT_MOVED;
-      event->pair = NULL; /* prevents the paired event to be emitted as well */
+      /* We either have a rename (in the same directory) or a move
+       * (between different directories).
+       */
+      if (event->pair && event->pair->wd == event->wd)
+        {
+          /* this is a rename */
+          g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_RENAMED,
+                                              event->name, event->pair->name, NULL, event->timestamp);
+        }
+      else
+        {
+          GFile *other;
+
+          if (event->pair)
+            {
+              const char *parent_dir;
+              gchar *fullpath;
+
+              parent_dir = _ip_get_path_for_wd (event->pair->wd);
+              fullpath = _ih_fullpath_from_event (event->pair, parent_dir, NULL);
+              other = g_file_new_for_path (fullpath);
+              g_free (fullpath);
+            }
+          else
+            other = NULL;
+
+          /* this is either an incoming or outgoing move */
+          g_file_monitor_source_handle_event (sub->user_data, ih_mask_to_EventFlags (event->mask),
+                                              event->name, NULL, other, event->timestamp);
+
+          if (other)
+            g_object_unref (other);
+        }
     }
   else
-    other = NULL;
-
-  g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
-                            child, other, eflags);
-
-  /* For paired moves or moves whose mask has been changed from IN_MOVED_TO to
-   * IN_CREATE, notify also that it's probably the last change to the file,
-   * emitting CHANGES_DONE_HINT.
-   * The first (first part of the if's guard below) is the case of a normal
-   * move within the monitored tree and in the same mounted volume.
-   * The latter (second part of the guard) is the case of a move within the
-   * same mounted volume, but from a not monitored directory.
-   *
-   * It's not needed in cases like moves across mounted volumes as the IN_CREATE
-   * will be followed by a IN_MODIFY and IN_CLOSE_WRITE events.
-   * Also not needed if sub->pair_moves is set as EVENT_MOVED will be emitted
-   * instead of EVENT_CREATED which implies no further modification will be
-   * applied to the file
-   * See: https://bugzilla.gnome.org/show_bug.cgi?id=640077
-   */
-  if ((!sub->pair_moves &&
-        event->is_second_in_pair && (event->mask & IN_MOVED_TO)) ||
-      (!ih_event_is_paired_move (event) &&
-       (event->original_mask & IN_MOVED_TO) && (event->mask & IN_CREATE)))
-    {
-      g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
-          child, NULL, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT);
-    }
-
-  g_object_unref (child);
-  if (other)
-    g_object_unref (other);
+    /* unpaired event -- no 'other' field */
+    g_file_monitor_source_handle_event (sub->user_data, ih_mask_to_EventFlags (event->mask),
+                                        event->name, NULL, NULL, event->timestamp);
 }
 
 static void
 ih_not_missing_callback (inotify_sub *sub)
 {
-  gchar *fullpath;
-  GFileMonitorEvent eflags;
-  guint32 mask;
-  GFile* child;
-
-  if (sub->filename)
-    {
-      fullpath = g_strdup_printf ("%s/%s", sub->dirname, sub->filename);
-      g_warning ("Missing callback called fullpath = %s\n", fullpath);
-      if (!g_file_test (fullpath, G_FILE_TEST_EXISTS))
-       {
-         g_free (fullpath);
-         return;
-       }
-      mask = IN_CREATE;
-    }
-  else
-    {
-      fullpath = g_strdup_printf ("%s", sub->dirname);
-      mask = IN_CREATE|IN_ISDIR;
-    }
-
-  eflags = ih_mask_to_EventFlags (mask);
-  child = g_file_new_for_path (fullpath);
-  g_free (fullpath);
-
-  g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
-                            child, NULL, eflags);
-
-  g_object_unref (child);
+  g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CREATED,
+                                      sub->filename, NULL, NULL, g_get_monotonic_time ());
 }
 
 /* Transforms a inotify event to a GVFS event. */
@@ -273,13 +220,15 @@ ih_mask_to_EventFlags (guint32 mask)
     case IN_ATTRIB:
       return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
     case IN_MOVE_SELF:
-    case IN_MOVED_FROM:
     case IN_DELETE:
     case IN_DELETE_SELF:
       return G_FILE_MONITOR_EVENT_DELETED;
     case IN_CREATE:
-    case IN_MOVED_TO:
       return G_FILE_MONITOR_EVENT_CREATED;
+    case IN_MOVED_FROM:
+      return G_FILE_MONITOR_EVENT_MOVED_OUT;
+    case IN_MOVED_TO:
+      return G_FILE_MONITOR_EVENT_MOVED_IN;
     case IN_UNMOUNT:
       return G_FILE_MONITOR_EVENT_UNMOUNTED;
     case IN_Q_OVERFLOW:
diff --git a/gio/inotify/inotify-kernel.c b/gio/inotify/inotify-kernel.c
index 1d20ecf..1c1daf5 100644
--- a/gio/inotify/inotify-kernel.c
+++ b/gio/inotify/inotify-kernel.c
@@ -72,7 +72,10 @@ void
 _ik_event_free (ik_event_t *event)
 {
   if (event->pair)
-    _ik_event_free (event->pair);
+    {
+      event->pair->pair = NULL;
+      _ik_event_free (event->pair);
+    }
 
   g_free (event->name);
   g_free (event);
@@ -148,6 +151,7 @@ ik_source_try_to_pair_head (InotifyKernelSource *iks)
           g_queue_remove (&iks->queue, candidate);
           candidate->is_second_in_pair = TRUE;
           head->pair = candidate;
+          candidate->pair = head;
           return;
         }
     }
diff --git a/gio/inotify/inotify-path.c b/gio/inotify/inotify-path.c
index 9d6c00e..830f514 100644
--- a/gio/inotify/inotify-path.c
+++ b/gio/inotify/inotify-path.c
@@ -438,9 +438,7 @@ ip_wd_delete (gpointer data,
 
 static void
 ip_event_dispatch (GList      *dir_list, 
-                   GList      *pair_dir_list, 
                    GList      *file_list,
-                   GList      *pair_file_list,
                    ik_event_t *event)
 {
   GList *l;
@@ -521,92 +519,13 @@ ip_event_dispatch (GList      *dir_list,
          event_callback (event, sub, TRUE);
         }
     }
-  
-  if (!event->pair)
-    return;
-  
-  for (l = pair_dir_list; l; l = l->next)
-    {
-      GList *subl;
-      ip_watched_dir_t *dir = l->data;
-      
-      for (subl = dir->subs; subl; subl = subl->next)
-       {
-         inotify_sub *sub = subl->data;
-         
-         /* If the subscription and the event
-          * contain a filename and they don't
-          * match, we don't deliver this event.
-          */
-         if (sub->filename &&
-             event->pair->name &&
-             strcmp (sub->filename, event->pair->name))
-           continue;
-         
-         /* If the subscription has a filename
-          * but this event doesn't, we don't
-          * deliver this event.
-          */
-         if (sub->filename && !event->pair->name)
-           continue;
-         
-         /* If we're also watching the file directly
-          * don't report events that will also be
-          * reported on the file itself.
-          */
-         if (sub->hardlinks)
-           {
-             event->mask &= ~IP_INOTIFY_FILE_MASK;
-             if (!event->mask)
-               continue;
-           }
-         
-         /* FIXME: We might need to synthesize
-          * DELETE/UNMOUNT events when
-          * the filename doesn't match
-          */
-         
-         event_callback (event->pair, sub, FALSE);
-
-          if (sub->hardlinks)
-            {
-              ip_watched_file_t *file;
-
-              file = g_hash_table_lookup (dir->files_hash, sub->filename);
-
-              if (file != NULL)
-                {
-                  if (event->pair->mask & (IN_MOVED_FROM | IN_DELETE))
-                    ip_watched_file_stop (file);
-
-                  if (event->pair->mask & (IN_MOVED_TO | IN_CREATE))
-                    ip_watched_file_start (file);
-                }
-            }
-       }
-    }
-
-  for (l = pair_file_list; l; l = l->next)
-    {
-      ip_watched_file_t *file = l->data;
-      GList *subl;
-
-      for (subl = file->subs; subl; subl = subl->next)
-        {
-         inotify_sub *sub = subl->data;
-
-         event_callback (event->pair, sub, TRUE);
-        }
-    }
 }
 
 static void
 ip_event_callback (ik_event_t *event)
 {
   GList* dir_list = NULL;
-  GList* pair_dir_list = NULL;
   GList *file_list = NULL;
-  GList *pair_file_list = NULL;
 
   /* We can ignore the IGNORED events */
   if (event->mask & IN_IGNORED)
@@ -618,15 +537,19 @@ ip_event_callback (ik_event_t *event)
   dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
   file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
 
-  if (event->pair)
+  if (event->mask & IP_INOTIFY_DIR_MASK)
+    ip_event_dispatch (dir_list, file_list, event);
+
+  /* Only deliver paired events if the wds are separate */
+  if (event->pair && event->pair->wd != event->wd)
     {
-      pair_dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
-      pair_file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
+      dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
+      file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
+
+      if (event->pair->mask & IP_INOTIFY_DIR_MASK)
+        ip_event_dispatch (dir_list, file_list, event->pair);
     }
 
-  if (event->mask & IP_INOTIFY_DIR_MASK)
-    ip_event_dispatch (dir_list, pair_dir_list, file_list, pair_file_list, event);
-  
   /* We have to manage the missing list
    * when we get an event that means the
    * file has been deleted/moved/unmounted.
diff --git a/gio/inotify/inotify-sub.c b/gio/inotify/inotify-sub.c
index 85b3411..a00a890 100644
--- a/gio/inotify/inotify-sub.c
+++ b/gio/inotify/inotify-sub.c
@@ -46,7 +46,6 @@ dup_dirname (const gchar *dirname)
 inotify_sub*
 _ih_sub_new (const gchar *dirname, 
              const gchar *filename,
-             gboolean     pair_moves,
              gboolean     watch_hardlinks,
              gpointer     user_data)
 {
@@ -55,7 +54,6 @@ _ih_sub_new (const gchar *dirname,
   sub = g_new0 (inotify_sub, 1);
   sub->dirname = dup_dirname (dirname);
   sub->filename = g_strdup (filename);
-  sub->pair_moves = pair_moves;
   sub->hardlinks = watch_hardlinks;
   sub->user_data = user_data;
 
diff --git a/gio/inotify/inotify-sub.h b/gio/inotify/inotify-sub.h
index ff37567..ab24dd8 100644
--- a/gio/inotify/inotify-sub.h
+++ b/gio/inotify/inotify-sub.h
@@ -35,7 +35,6 @@ typedef struct
 
 inotify_sub *_ih_sub_new (const gchar  *dirname,
                          const gchar  *filename,
-                         gboolean      pair_moves,
                          gboolean      watch_hardlinks,
                          gpointer      user_data);
 void         _ih_sub_free (inotify_sub *sub);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]