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

@ -185,8 +185,6 @@ application_sources = \
local_sources = \
ghttpproxy.c \
ghttpproxy.h \
glocaldirectorymonitor.c \
glocaldirectorymonitor.h \
glocalfile.c \
glocalfile.h \
glocalfileenumerator.c \

View File

@ -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);

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);
}

View File

@ -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;

View File

@ -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"
@ -37,6 +36,8 @@
#include "gtlsbackend.h"
#include "gvfs.h"
#include "gnotificationbackend.h"
#include "ginitable.h"
#include "gnetworkmonitor.h"
#ifdef G_OS_WIN32
#include "gregistrysettingsbackend.h"
#endif
@ -886,17 +887,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);
@ -979,15 +978,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);
@ -1070,20 +1063,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

View File

@ -1,245 +0,0 @@
/* 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 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 "glocaldirectorymonitor.h"
#include "gunixmounts.h"
#include "giomodule-priv.h"
#include "gfile.h"
#include "gioerror.h"
#include "glibintl.h"
#include <string.h>
enum
{
PROP_0,
PROP_DIRNAME,
PROP_FLAGS
};
static gboolean g_local_directory_monitor_cancel (GFileMonitor *monitor);
static void mounts_changed (GUnixMountMonitor *mount_monitor,
gpointer user_data);
G_DEFINE_ABSTRACT_TYPE (GLocalDirectoryMonitor, g_local_directory_monitor, G_TYPE_FILE_MONITOR)
static void
g_local_directory_monitor_finalize (GObject *object)
{
GLocalDirectoryMonitor *local_monitor;
local_monitor = G_LOCAL_DIRECTORY_MONITOR (object);
g_free (local_monitor->dirname);
if (local_monitor->mount_monitor)
{
g_signal_handlers_disconnect_by_func (local_monitor->mount_monitor, mounts_changed, local_monitor);
g_object_unref (local_monitor->mount_monitor);
local_monitor->mount_monitor = NULL;
}
G_OBJECT_CLASS (g_local_directory_monitor_parent_class)->finalize (object);
}
static void
g_local_directory_monitor_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GLocalDirectoryMonitor *local_monitor = G_LOCAL_DIRECTORY_MONITOR (object);
switch (property_id)
{
case PROP_DIRNAME:
local_monitor->dirname = g_value_dup_string (value);
break;
case PROP_FLAGS:
local_monitor->flags = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
void
g_local_directory_monitor_start (GLocalDirectoryMonitor *local_monitor)
{
GLocalDirectoryMonitorClass *class;
class = G_LOCAL_DIRECTORY_MONITOR_GET_CLASS (local_monitor);
if (!class->mount_notify && (local_monitor->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->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 (mounts_changed), local_monitor, 0);
#endif
}
if (class->start)
class->start (local_monitor);
}
static void
g_local_directory_monitor_class_init (GLocalDirectoryMonitorClass* klass)
{
GObjectClass* gobject_class = G_OBJECT_CLASS (klass);
GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass);
gobject_class->finalize = g_local_directory_monitor_finalize;
gobject_class->set_property = g_local_directory_monitor_set_property;
file_monitor_class->cancel = g_local_directory_monitor_cancel;
g_object_class_install_property (gobject_class,
PROP_DIRNAME,
g_param_spec_string ("dirname",
P_("Directory name"),
P_("Directory 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));
klass->mount_notify = FALSE;
}
static void
g_local_directory_monitor_init (GLocalDirectoryMonitor *local_monitor)
{
}
static void
mounts_changed (GUnixMountMonitor *mount_monitor,
gpointer user_data)
{
GLocalDirectoryMonitor *local_monitor = user_data;
#ifdef G_OS_UNIX
GUnixMountEntry *mount;
#endif
gboolean is_mounted;
GFile *file;
/* Emulate unmount detection */
#ifdef G_OS_UNIX
mount = g_unix_mount_at (local_monitor->dirname, NULL);
is_mounted = mount != NULL;
if (mount)
g_unix_mount_free (mount);
#else
/*claim everything was mounted */
is_mounted = TRUE;
#endif
if (local_monitor->was_mounted != is_mounted)
{
if (local_monitor->was_mounted && !is_mounted)
{
file = g_file_new_for_path (local_monitor->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;
}
}
GFileMonitor*
_g_local_directory_monitor_new (const char *dirname,
GFileMonitorFlags flags,
GMainContext *context,
gboolean is_remote_fs,
gboolean do_start,
GError **error)
{
GFileMonitor *monitor = NULL;
GType type = G_TYPE_INVALID;
if (is_remote_fs)
type = _g_io_module_get_default_type (G_NFS_DIRECTORY_MONITOR_EXTENSION_POINT_NAME,
"GIO_USE_FILE_MONITOR",
G_STRUCT_OFFSET (GLocalDirectoryMonitorClass, is_supported));
if (type == G_TYPE_INVALID)
type = _g_io_module_get_default_type (G_LOCAL_DIRECTORY_MONITOR_EXTENSION_POINT_NAME,
"GIO_USE_FILE_MONITOR",
G_STRUCT_OFFSET (GLocalDirectoryMonitorClass, is_supported));
if (type != G_TYPE_INVALID)
monitor = G_FILE_MONITOR (g_object_new (type, "dirname", dirname, "flags", flags, "context", context, NULL));
else
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Unable to find default local directory monitor type"));
if (monitor && do_start)
g_local_directory_monitor_start (G_LOCAL_DIRECTORY_MONITOR (monitor));
return monitor;
}
static gboolean
g_local_directory_monitor_cancel (GFileMonitor *monitor)
{
GLocalDirectoryMonitor *local_monitor = G_LOCAL_DIRECTORY_MONITOR (monitor);
if (local_monitor->mount_monitor)
{
g_signal_handlers_disconnect_by_func (local_monitor->mount_monitor, mounts_changed, local_monitor);
g_object_unref (local_monitor->mount_monitor);
local_monitor->mount_monitor = NULL;
}
return TRUE;
}

View File

@ -1,84 +0,0 @@
/* 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 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>
*/
#ifndef __G_LOCAL_DIRECTORY_MONITOR_H__
#define __G_LOCAL_DIRECTORY_MONITOR_H__
#include <gio/gfilemonitor.h>
#include "gunixmounts.h"
G_BEGIN_DECLS
#define G_TYPE_LOCAL_DIRECTORY_MONITOR (g_local_directory_monitor_get_type ())
#define G_LOCAL_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LOCAL_DIRECTORY_MONITOR, GLocalDirectoryMonitor))
#define G_LOCAL_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_LOCAL_DIRECTORY_MONITOR, GLocalDirectoryMonitorClass))
#define G_IS_LOCAL_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LOCAL_DIRECTORY_MONITOR))
#define G_IS_LOCAL_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LOCAL_DIRECTORY_MONITOR))
#define G_LOCAL_DIRECTORY_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_LOCAL_DIRECTORY_MONITOR, GLocalDirectoryMonitorClass))
#define G_LOCAL_DIRECTORY_MONITOR_EXTENSION_POINT_NAME "gio-local-directory-monitor"
#define G_NFS_DIRECTORY_MONITOR_EXTENSION_POINT_NAME "gio-nfs-directory-monitor"
typedef struct _GLocalDirectoryMonitor GLocalDirectoryMonitor;
typedef struct _GLocalDirectoryMonitorClass GLocalDirectoryMonitorClass;
struct _GLocalDirectoryMonitor
{
GFileMonitor parent_instance;
gchar *dirname;
GFileMonitorFlags flags;
/* For mount emulation */
gboolean was_mounted;
GUnixMountMonitor *mount_monitor;
};
struct _GLocalDirectoryMonitorClass
{
GFileMonitorClass parent_class;
gboolean mount_notify;
gboolean (* is_supported) (void);
void (* start) (GLocalDirectoryMonitor *local_monitor);
};
#ifdef G_OS_UNIX
GLIB_AVAILABLE_IN_ALL
#endif
GType g_local_directory_monitor_get_type (void) G_GNUC_CONST;
GFileMonitor * _g_local_directory_monitor_new (const char *dirname,
GFileMonitorFlags flags,
GMainContext *context,
gboolean is_remote_fs,
gboolean do_start,
GError **error);
void g_local_directory_monitor_start (GLocalDirectoryMonitor *local_monitor);
/* Actually in glocalfile.c */
GLocalDirectoryMonitor * g_local_directory_monitor_new_in_worker (const char *pathname,
GFileMonitorFlags flags,
GError **error);
G_END_DECLS
#endif /* __G_LOCAL_DIRECTORY_MONITOR_H__ */

View File

@ -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);
}
GLocalFile *local_file = G_LOCAL_FILE (file);
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);
return g_local_file_monitor_new_for_path (local_file->filename, FALSE, flags, error);
}
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);
}
/* Here is the GLocalFile implementation of g_file_measure_disk_usage().
*
* If available, we use fopenat() in preference to filenames for

View File

@ -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);

View File

@ -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;
static void
g_local_file_monitor_init (GLocalFileMonitor* local_monitor)
/* 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_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
pending_change_free (gpointer data)
{
GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
PendingChange *change = data;
switch (property_id)
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 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);
break;
change->dirty = TRUE;
case PROP_FLAGS:
local_monitor->flags = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
g_sequence_sort_changed (iter, pending_change_compare_ready_time, fms);
}
}
void
g_local_file_monitor_start (GLocalFileMonitor *local_monitor)
static gboolean
g_file_monitor_source_get_pending_change_dirty (GFileMonitorSource *fms,
GSequenceIter *iter)
{
GLocalFileMonitorClass *class;
PendingChange *change;
class = G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor);
change = g_sequence_get (iter);
if (class->start)
class->start (local_monitor);
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 G_FILE_MONITOR_EVENT_CHANGED:
g_assert (!other && !rename_to);
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 (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);
}
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);
}
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;
/* 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)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = g_local_file_monitor_set_property;
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)
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,
GError **error)
{
GFileMonitor *monitor = NULL;
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;
}
if (monitor && do_start)
g_local_file_monitor_start (G_LOCAL_FILE_MONITOR (monitor));
return monitor;
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_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 G_FILE_MONITOR (monitor);
}

View File

@ -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);
/* 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);
/* Actually in glocalfile.c */
GLocalFileMonitor * g_local_file_monitor_new_in_worker (const char *pathname,
GFileMonitorFlags flags,
GError **error);
G_END_DECLS

View File

@ -15,8 +15,6 @@ libinotify_la_SOURCES = \
inotify-helper.h \
ginotifyfilemonitor.c \
ginotifyfilemonitor.h \
ginotifydirectorymonitor.c \
ginotifydirectorymonitor.h \
$(NULL)
libinotify_la_CFLAGS = \

View File

@ -31,7 +31,7 @@
struct _GInotifyDirectoryMonitor
{
GLocalDirectoryMonitor parent_instance;
GLocalFileMonitor parent_instance;
inotify_sub *sub;
};

View File

@ -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,81 +33,13 @@
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))
static void
g_inotify_file_monitor_finalize (GObject *object)
{
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);
}
static void
g_inotify_file_monitor_start (GLocalFileMonitor *local_monitor)
{
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;
}
g_io_extension_point_implement (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
g_define_type_id, "inotify", 20))
static gboolean
g_inotify_file_monitor_is_supported (void)
@ -114,6 +47,55 @@ g_inotify_file_monitor_is_supported (void)
return _ih_startup ();
}
static void
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);
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_cancel (GFileMonitor *monitor)
{
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_finalize (GObject *object)
{
GInotifyFileMonitor *inotify_monitor = G_INOTIFY_FILE_MONITOR (object);
/* must surely have been cancelled already */
g_assert (!inotify_monitor->sub);
G_OBJECT_CLASS (g_inotify_file_monitor_parent_class)->finalize (object);
}
static void
g_inotify_file_monitor_init (GInotifyFileMonitor* monitor)
{
}
static void
g_inotify_file_monitor_class_init (GInotifyFileMonitorClass* klass)
{
@ -121,33 +103,10 @@ g_inotify_file_monitor_class_init (GInotifyFileMonitorClass* klass)
GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass);
GLocalFileMonitorClass *local_file_monitor_class = G_LOCAL_FILE_MONITOR_CLASS (klass);
gobject_class->finalize = g_inotify_file_monitor_finalize;
file_monitor_class->cancel = g_inotify_file_monitor_cancel;
local_file_monitor_class->is_supported = g_inotify_file_monitor_is_supported;
local_file_monitor_class->start = g_inotify_file_monitor_start;
}
static void
g_inotify_file_monitor_init (GInotifyFileMonitor* monitor)
{
}
static gboolean
g_inotify_file_monitor_cancel (GFileMonitor* monitor)
{
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;
}
if (G_FILE_MONITOR_CLASS (g_inotify_file_monitor_parent_class)->cancel)
(*G_FILE_MONITOR_CLASS (g_inotify_file_monitor_parent_class)->cancel) (monitor);
return TRUE;
local_file_monitor_class->mount_notify = TRUE;
file_monitor_class->cancel = g_inotify_file_monitor_cancel;
gobject_class->finalize = g_inotify_file_monitor_finalize;
}

View File

@ -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

View File

@ -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:

View File

@ -63,7 +63,7 @@ ik_event_new (struct inotify_event *kevent,
if (event->len)
event->name = g_strdup (kevent->name);
else
event->name = g_strdup ("");
event->name = NULL;
return event;
}
@ -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;
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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);

View File

@ -63,7 +63,6 @@ gio/giostream.c
gio/glib-compile-resources.c
gio/glib-compile-schemas.c
gio/gloadableicon.c
gio/glocaldirectorymonitor.c
gio/glocalfile.c
gio/glocalfileenumerator.c
gio/glocalfileinfo.c