mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 10:08:56 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			919 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			919 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* 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
 | 
						|
 * modify it under the terms of the GNU Lesser General Public
 | 
						|
 * License as published by the Free Software Foundation; either
 | 
						|
 * version 2.1 of the License, or (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This library is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
 * Lesser General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU Lesser General
 | 
						|
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
 | 
						|
 *
 | 
						|
 * Author: Alexander Larsson <alexl@redhat.com>
 | 
						|
 */
 | 
						|
 | 
						|
#include "config.h"
 | 
						|
 | 
						|
#include "gioenumtypes.h"
 | 
						|
#include "glocalfilemonitor.h"
 | 
						|
#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
 | 
						|
 | 
						|
/* 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;
 | 
						|
  GWeakRef      instance_ref;
 | 
						|
  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;
 | 
						|
};
 | 
						|
 | 
						|
/* 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
 | 
						|
pending_change_free (gpointer data)
 | 
						|
{
 | 
						|
  PendingChange *change = data;
 | 
						|
 | 
						|
  g_free (change->child);
 | 
						|
 | 
						|
  g_slice_free (PendingChange, change);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
queued_event_free (QueuedEvent *event)
 | 
						|
{
 | 
						|
  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);
 | 
						|
}
 | 
						|
 | 
						|
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 gboolean
 | 
						|
g_file_monitor_source_set_pending_change_dirty (GFileMonitorSource *fms,
 | 
						|
                                                GSequenceIter      *iter)
 | 
						|
{
 | 
						|
  PendingChange *change;
 | 
						|
 | 
						|
  change = g_sequence_get (iter);
 | 
						|
 | 
						|
  /* if it was already dirty then this change is 'uninteresting' */
 | 
						|
  if (change->dirty)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  change->dirty = TRUE;
 | 
						|
 | 
						|
  g_sequence_sort_changed (iter, pending_change_compare_ready_time, fms);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
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 != NULL && fms->dirname != NULL)
 | 
						|
    event->child = g_local_file_new_from_dirname_and_basename (fms->dirname, child);
 | 
						|
  else if (child != NULL)
 | 
						|
    {
 | 
						|
      gchar *dirname = g_path_get_dirname (fms->filename);
 | 
						|
      event->child = g_local_file_new_from_dirname_and_basename (dirname, child);
 | 
						|
      g_free (dirname);
 | 
						|
    }
 | 
						|
  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 gboolean
 | 
						|
g_file_monitor_source_file_changed (GFileMonitorSource *fms,
 | 
						|
                                    const gchar        *child,
 | 
						|
                                    gint64              now)
 | 
						|
{
 | 
						|
  GSequenceIter *pending;
 | 
						|
  gboolean interesting;
 | 
						|
 | 
						|
  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);
 | 
						|
      interesting = TRUE;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    interesting = g_file_monitor_source_set_pending_change_dirty (fms, pending);
 | 
						|
 | 
						|
  g_file_monitor_source_update_ready_time (fms);
 | 
						|
 | 
						|
  return interesting;
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
}
 | 
						|
 | 
						|
#ifndef G_DISABLE_ASSERT
 | 
						|
static gboolean
 | 
						|
is_basename (const gchar *name)
 | 
						|
{
 | 
						|
  if (name[0] == '.' && ((name[1] == '.' && name[2] == '\0') || name[1] == '\0'))
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  return !strchr (name, '/');
 | 
						|
}
 | 
						|
#endif  /* !G_DISABLE_ASSERT */
 | 
						|
 | 
						|
gboolean
 | 
						|
g_file_monitor_source_handle_event (GFileMonitorSource *fms,
 | 
						|
                                    GFileMonitorEvent   event_type,
 | 
						|
                                    const gchar        *child,
 | 
						|
                                    const gchar        *rename_to,
 | 
						|
                                    GFile              *other,
 | 
						|
                                    gint64              event_time)
 | 
						|
{
 | 
						|
  gboolean interesting = TRUE;
 | 
						|
  GFileMonitor *instance = NULL;
 | 
						|
 | 
						|
  g_assert (!child || is_basename (child));
 | 
						|
  g_assert (!rename_to || is_basename (rename_to));
 | 
						|
 | 
						|
  if (fms->basename && (!child || !g_str_equal (child, fms->basename))
 | 
						|
                    && (!rename_to || !g_str_equal (rename_to, fms->basename)))
 | 
						|
    return TRUE;
 | 
						|
 | 
						|
  g_mutex_lock (&fms->lock);
 | 
						|
 | 
						|
  /* monitor is already gone -- don't bother */
 | 
						|
  instance = g_weak_ref_get (&fms->instance_ref);
 | 
						|
  if (instance == NULL)
 | 
						|
    {
 | 
						|
      g_mutex_unlock (&fms->lock);
 | 
						|
      return TRUE;
 | 
						|
    }
 | 
						|
 | 
						|
  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 G_FILE_MONITOR_EVENT_CHANGED:
 | 
						|
      g_assert (!other && !rename_to);
 | 
						|
      interesting = g_file_monitor_source_file_changed (fms, child, event_time);
 | 
						|
      break;
 | 
						|
 | 
						|
    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 (other && (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 | G_FILE_MONITOR_SEND_MOVED))
 | 
						|
        {
 | 
						|
          GFile *other;
 | 
						|
          const gchar *dirname;
 | 
						|
          gchar *allocated_dirname = NULL;
 | 
						|
          GFileMonitorEvent event;
 | 
						|
 | 
						|
          event = (fms->flags & G_FILE_MONITOR_WATCH_MOVES) ? G_FILE_MONITOR_EVENT_RENAMED : G_FILE_MONITOR_EVENT_MOVED;
 | 
						|
 | 
						|
          if (fms->dirname != NULL)
 | 
						|
            dirname = fms->dirname;
 | 
						|
          else
 | 
						|
            {
 | 
						|
              allocated_dirname = g_path_get_dirname (fms->filename);
 | 
						|
              dirname = allocated_dirname;
 | 
						|
            }
 | 
						|
 | 
						|
          other = g_local_file_new_from_dirname_and_basename (dirname, rename_to);
 | 
						|
          g_file_monitor_source_file_changes_done (fms, rename_to);
 | 
						|
          g_file_monitor_source_send_event (fms, event, child, other);
 | 
						|
 | 
						|
          g_object_unref (other);
 | 
						|
          g_free (allocated_dirname);
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_DELETED, child, NULL);
 | 
						|
          g_file_monitor_source_send_synthetic_created (fms, rename_to);
 | 
						|
        }
 | 
						|
      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);
 | 
						|
  g_clear_object (&instance);
 | 
						|
 | 
						|
  return interesting;
 | 
						|
}
 | 
						|
 | 
						|
static gint64
 | 
						|
g_file_monitor_source_get_rate_limit (GFileMonitorSource *fms)
 | 
						|
{
 | 
						|
  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);
 | 
						|
 | 
						|
      changed = TRUE;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    changed = FALSE;
 | 
						|
 | 
						|
  g_mutex_unlock (&fms->lock);
 | 
						|
 | 
						|
  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;
 | 
						|
  GFileMonitor *instance = NULL;
 | 
						|
 | 
						|
  /* make sure the monitor still exists */
 | 
						|
  instance = g_weak_ref_get (&fms->instance_ref);
 | 
						|
  if (instance == NULL)
 | 
						|
    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_is_empty (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);
 | 
						|
  g_clear_object (&instance);
 | 
						|
 | 
						|
  /* 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 */
 | 
						|
      instance = g_weak_ref_get (&fms->instance_ref);
 | 
						|
      if (instance != NULL)
 | 
						|
        g_file_monitor_emit_event (instance, event->child, event->other, event->event_type);
 | 
						|
 | 
						|
      g_clear_object (&instance);
 | 
						|
      queued_event_free (event);
 | 
						|
    }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
g_file_monitor_source_dispose (GFileMonitorSource *fms)
 | 
						|
{
 | 
						|
  GHashTableIter iter;
 | 
						|
  gpointer seqiter;
 | 
						|
  QueuedEvent *event;
 | 
						|
 | 
						|
  g_mutex_lock (&fms->lock);
 | 
						|
 | 
						|
  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_is_empty (fms->pending_changes));
 | 
						|
  g_assert (g_hash_table_size (fms->pending_changes_table) == 0);
 | 
						|
  g_assert (fms->event_queue.length == 0);
 | 
						|
  g_weak_ref_set (&fms->instance_ref, 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 (g_weak_ref_get (&fms->instance_ref) == NULL);
 | 
						|
  g_weak_ref_clear (&fms->instance_ref);
 | 
						|
 | 
						|
  g_assert (g_sequence_is_empty (fms->pending_changes));
 | 
						|
  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,
 | 
						|
    NULL, NULL
 | 
						|
  };
 | 
						|
  GFileMonitorSource *fms;
 | 
						|
  GSource *source;
 | 
						|
 | 
						|
  source = g_source_new (&source_funcs, sizeof (GFileMonitorSource));
 | 
						|
  fms = (GFileMonitorSource *) source;
 | 
						|
 | 
						|
  g_source_set_static_name (source, "GFileMonitorSource");
 | 
						|
 | 
						|
  g_mutex_init (&fms->lock);
 | 
						|
  g_weak_ref_init (&fms->instance_ref, 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);
 | 
						|
 | 
						|
  source = g_file_monitor_source_new (local_monitor, filename, is_directory, flags);
 | 
						|
  local_monitor->source = source; /* owns the ref */
 | 
						|
 | 
						|
  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
 | 
						|
    }
 | 
						|
 | 
						|
  g_source_attach ((GSource *) source, context);
 | 
						|
 | 
						|
  G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor)->start (local_monitor,
 | 
						|
                                                         source->dirname, source->basename, source->filename,
 | 
						|
                                                         source);
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
g_local_file_monitor_finalize (GObject *object)
 | 
						|
{
 | 
						|
  GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
 | 
						|
 | 
						|
  g_source_unref ((GSource *) local_monitor->source);
 | 
						|
 | 
						|
  G_OBJECT_CLASS (g_local_file_monitor_parent_class)->finalize (object);
 | 
						|
}
 | 
						|
 | 
						|
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 (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_override_property (gobject_class, PROP_RATE_LIMIT, "rate-limit");
 | 
						|
}
 | 
						|
 | 
						|
static GLocalFileMonitor *
 | 
						|
g_local_file_monitor_new (gboolean   is_remote_fs,
 | 
						|
                          gboolean   is_directory,
 | 
						|
                          GError   **error)
 | 
						|
{
 | 
						|
  GType type = G_TYPE_INVALID;
 | 
						|
 | 
						|
  if (is_remote_fs)
 | 
						|
    type = _g_io_module_get_default_type (G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME,
 | 
						|
                                          "GIO_USE_FILE_MONITOR",
 | 
						|
                                          G_STRUCT_OFFSET (GLocalFileMonitorClass, is_supported));
 | 
						|
 | 
						|
  /* Fallback rather to poll file monitor for remote files, see gfile.c. */
 | 
						|
  if (type == G_TYPE_INVALID && (!is_remote_fs || is_directory))
 | 
						|
    type = _g_io_module_get_default_type (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
 | 
						|
                                          "GIO_USE_FILE_MONITOR",
 | 
						|
                                          G_STRUCT_OFFSET (GLocalFileMonitorClass, is_supported));
 | 
						|
 | 
						|
  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;
 | 
						|
 | 
						|
  is_remote_fs = g_local_file_is_nfs_home (pathname);
 | 
						|
 | 
						|
  monitor = g_local_file_monitor_new (is_remote_fs, is_directory, 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,
 | 
						|
                                    GClosureNotify         destroy_user_data,
 | 
						|
                                    GError               **error)
 | 
						|
{
 | 
						|
  GLocalFileMonitor *monitor;
 | 
						|
  gboolean is_remote_fs;
 | 
						|
 | 
						|
  is_remote_fs = g_local_file_is_nfs_home (pathname);
 | 
						|
 | 
						|
  monitor = g_local_file_monitor_new (is_remote_fs, is_directory, error);
 | 
						|
 | 
						|
  if (monitor)
 | 
						|
    {
 | 
						|
      if (callback)
 | 
						|
        g_signal_connect_data (monitor, "changed", G_CALLBACK (callback),
 | 
						|
                               user_data, destroy_user_data, 0  /* flags */);
 | 
						|
 | 
						|
      g_local_file_monitor_start (monitor, pathname, is_directory, flags, GLIB_PRIVATE_CALL(g_get_worker_context) ());
 | 
						|
    }
 | 
						|
 | 
						|
  return G_FILE_MONITOR (monitor);
 | 
						|
}
 |