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.

Expose g_local_file_is_remote() internally for NFS detection.

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.  Those will come in future commits.
This commit is contained in:
Ryan Lortie
2015-01-12 14:59:35 -05:00
parent 779c809a3d
commit 2737ab3201
21 changed files with 1101 additions and 1335 deletions

View File

@@ -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,69 +48,38 @@ static void file_change_free (FileChange *change);
* context is still running).
**/
G_LOCK_DEFINE_STATIC(cancelled);
#define DEFAULT_RATE_LIMIT_MSECS 800
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;
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 {
PROP_0,
PROP_RATE_LIMIT,
PROP_CANCELLED,
PROP_CONTEXT
};
/* 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)
enum
{
PROP_0,
PROP_RATE_LIMIT,
PROP_CANCELLED
};
static guint g_file_monitor_changed_signal;
static void
g_file_monitor_set_property (GObject *object,
guint prop_id,
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);
@@ -211,13 +124,18 @@ g_file_monitor_dispose (GObject *object)
G_OBJECT_CLASS (g_file_monitor_parent_class)->dispose (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_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_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)
{
G_UNLOCK (cancelled);
return TRUE;
}
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);
if (!monitor->priv->cancelled)
{
G_FILE_MONITOR_GET_CLASS (monitor)->cancel (monitor);
monitor->priv->cancelled = TRUE;
g_object_notify (G_OBJECT (monitor), "cancelled");
}
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");
}
g_object_set (monitor, "rate-limit", limit_msecs, NULL);
}
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);
}
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);
}