X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gio%2Fgfilemonitor.c;h=c81f6fcb5bf4c9f98dbd6b63d286046e78e0eb0d;hb=51fac05d73f8363de821eb0d6940dedca13a8c0f;hp=fdfbc1160b59a94fbeb449aae4a62d545184cb32;hpb=41d1650c9b6b0b8368c0648d2885b83e18020303;p=platform%2Fupstream%2Fglib.git diff --git a/gio/gfilemonitor.c b/gio/gfilemonitor.c index fdfbc11..c81f6fc 100644 --- a/gio/gfilemonitor.c +++ b/gio/gfilemonitor.c @@ -13,60 +13,89 @@ * 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, write to the - * Free Software Foundation, Inc., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. + * Public License along with this library; if not, see . * * Author: Alexander Larsson */ -#include +#include "config.h" #include #include "gfilemonitor.h" -#include "gio-marshal.h" +#include "gioenumtypes.h" +#include "gfile.h" #include "gvfs.h" #include "glibintl.h" -#include "gioalias.h" + +struct _FileChange; +typedef struct _FileChange FileChange; +static void file_change_free (FileChange *change); /** * SECTION:gfilemonitor * @short_description: File Monitor - * @see_also: #GDirectoryMonitor + * @include: gio/gio.h * - * Monitors a file for changes. - * + * Monitors a file or directory for changes. + * + * To obtain a #GFileMonitor for a file or directory, use + * g_file_monitor(), g_file_monitor_file(), or + * g_file_monitor_directory(). + * + * To get informed about changes to the file or directory you are + * monitoring, connect to the #GFileMonitor::changed signal. The + * signal will be emitted in the + * [thread-default main context][g-main-context-push-thread-default] + * of the thread that the monitor was created in + * (though if the global default main context is blocked, this may + * cause notifications to be blocked even if the thread-default + * context is still running). **/ +G_LOCK_DEFINE_STATIC(cancelled); + enum { CHANGED, LAST_SIGNAL }; -G_DEFINE_ABSTRACT_TYPE (GFileMonitor, g_file_monitor, G_TYPE_OBJECT); +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 { gboolean cancelled; int rate_limit_msec; /* Rate limiting change events */ - guint32 last_sent_change_time; /* Some monotonic clock in msecs */ - GFile *last_sent_change_file; - - guint send_delayed_change_timeout; + GHashTable *rate_limiter; + + GMutex mutex; + GSource *pending_file_change_source; + GSList *pending_file_changes; /* FileChange */ - /* Virtual CHANGES_DONE_HINT emission */ - GSource *virtual_changes_done_timeout; - GFile *virtual_changes_done_file; + GSource *timeout; + guint32 timeout_fires_at; + + GMainContext *context; }; enum { PROP_0, PROP_RATE_LIMIT, - PROP_CANCELLED + 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) + static void g_file_monitor_set_property (GObject *object, guint prop_id, @@ -83,6 +112,12 @@ g_file_monitor_set_property (GObject *object, 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 (); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -108,7 +143,9 @@ g_file_monitor_get_property (GObject *object, break; case PROP_CANCELLED: + G_LOCK (cancelled); g_value_set_boolean (value, priv->cancelled); + G_UNLOCK (cancelled); break; default: @@ -122,6 +159,12 @@ g_file_monitor_get_property (GObject *object, 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) @@ -130,44 +173,49 @@ g_file_monitor_finalize (GObject *object) monitor = G_FILE_MONITOR (object); - if (monitor->priv->last_sent_change_file) - g_object_unref (monitor->priv->last_sent_change_file); + if (monitor->priv->timeout) + { + g_source_destroy (monitor->priv->timeout); + g_source_unref (monitor->priv->timeout); + } - if (monitor->priv->send_delayed_change_timeout != 0) - g_source_remove (monitor->priv->send_delayed_change_timeout); + g_hash_table_destroy (monitor->priv->rate_limiter); - if (monitor->priv->virtual_changes_done_file) - g_object_unref (monitor->priv->virtual_changes_done_file); + g_main_context_unref (monitor->priv->context); + g_mutex_clear (&monitor->priv->mutex); - if (monitor->priv->virtual_changes_done_timeout) - g_source_destroy (monitor->priv->virtual_changes_done_timeout); - - if (G_OBJECT_CLASS (g_file_monitor_parent_class)->finalize) - (*G_OBJECT_CLASS (g_file_monitor_parent_class)->finalize) (object); + 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; /* Make sure we cancel on last unref */ - if (!monitor->priv->cancelled) - g_file_monitor_cancel (monitor); - - if (G_OBJECT_CLASS (g_file_monitor_parent_class)->dispose) - (*G_OBJECT_CLASS (g_file_monitor_parent_class)->dispose) (object); + g_file_monitor_cancel (monitor); + + G_OBJECT_CLASS (g_file_monitor_parent_class)->dispose (object); } static void g_file_monitor_class_init (GFileMonitorClass *klass) { GObjectClass *object_class; - - g_type_class_add_private (klass, sizeof (GFileMonitorPrivate)); - + object_class = G_OBJECT_CLASS (klass); object_class->finalize = g_file_monitor_finalize; object_class->dispose = g_file_monitor_dispose; @@ -178,10 +226,16 @@ g_file_monitor_class_init (GFileMonitorClass *klass) * GFileMonitor::changed: * @monitor: a #GFileMonitor. * @file: a #GFile. - * @other_file: a #GFile. + * @other_file: (allow-none): a #GFile or #NULL. * @event_type: a #GFileMonitorEvent. - * - * Emitted when a file has been changed. + * + * Emitted when @file has been changed. + * + * If using #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"), @@ -189,9 +243,9 @@ g_file_monitor_class_init (GFileMonitorClass *klass) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GFileMonitorClass, changed), NULL, NULL, - _gio_marshal_VOID__OBJECT_OBJECT_INT, + NULL, G_TYPE_NONE, 3, - G_TYPE_FILE, G_TYPE_FILE, G_TYPE_INT); + G_TYPE_FILE, G_TYPE_FILE, G_TYPE_FILE_MONITOR_EVENT); g_object_class_install_property (object_class, PROP_RATE_LIMIT, @@ -211,15 +265,24 @@ g_file_monitor_class_init (GFileMonitorClass *klass) 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_TYPE_INSTANCE_GET_PRIVATE (monitor, - G_TYPE_FILE_MONITOR, - GFileMonitorPrivate); + 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); } /** @@ -233,9 +296,15 @@ g_file_monitor_init (GFileMonitor *monitor) gboolean g_file_monitor_is_cancelled (GFileMonitor *monitor) { + gboolean res; + g_return_val_if_fail (G_IS_FILE_MONITOR (monitor), FALSE); - return monitor->priv->cancelled; + G_LOCK (cancelled); + res = monitor->priv->cancelled; + G_UNLOCK (cancelled); + + return res; } /** @@ -253,10 +322,16 @@ g_file_monitor_cancel (GFileMonitor* monitor) g_return_val_if_fail (G_IS_FILE_MONITOR (monitor), FALSE); + G_LOCK (cancelled); if (monitor->priv->cancelled) - return TRUE; + { + 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); @@ -266,19 +341,21 @@ g_file_monitor_cancel (GFileMonitor* monitor) /** * g_file_monitor_set_rate_limit: * @monitor: a #GFileMonitor. - * @limit_msecs: a integer with the limit in milliseconds to - * poll for changes. + * @limit_msecs: a non-negative integer with the limit in milliseconds + * to poll for changes * * Sets the rate limit to which the @monitor will report - * consecutive change events to the same file. - * - **/ + * consecutive change events to the same file. + */ void g_file_monitor_set_rate_limit (GFileMonitor *monitor, - int limit_msecs) + 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) { @@ -287,10 +364,97 @@ g_file_monitor_set_rate_limit (GFileMonitor *monitor, } } +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_thread_gettime() / (1000 * 1000); + return g_get_monotonic_time () / G_TIME_SPAN_MILLISECOND; } static guint32 @@ -303,191 +467,280 @@ time_difference (guint32 from, guint32 to) /* 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 -update_last_sent_change (GFileMonitor *monitor, GFile *file, guint32 time_now) +rate_limiter_send_virtual_changes_done_now (GFileMonitor *monitor, + RateLimiter *limiter) { - if (monitor->priv->last_sent_change_file != file) + if (limiter->send_virtual_changes_done_at != 0) { - if (monitor->priv->last_sent_change_file) - { - g_object_unref (monitor->priv->last_sent_change_file); - monitor->priv->last_sent_change_file = NULL; - } - if (file) - monitor->priv->last_sent_change_file = g_object_ref (file); + emit_in_idle (monitor, limiter->file, NULL, + G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); + limiter->send_virtual_changes_done_at = 0; } - - monitor->priv->last_sent_change_time = time_now; } static void -send_delayed_change_now (GFileMonitor *monitor) +rate_limiter_send_delayed_change_now (GFileMonitor *monitor, + RateLimiter *limiter, + guint32 time_now) { - if (monitor->priv->send_delayed_change_timeout) + if (limiter->send_delayed_change_at != 0) { - g_signal_emit (monitor, signals[CHANGED], 0, - monitor->priv->last_sent_change_file, NULL, - G_FILE_MONITOR_EVENT_CHANGED); - - g_source_remove (monitor->priv->send_delayed_change_timeout); - monitor->priv->send_delayed_change_timeout = 0; - - /* Same file, new last_sent time */ - monitor->priv->last_sent_change_time = get_time_msecs (); + 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 -delayed_changed_event_timeout (gpointer data) +calc_min_time (GFileMonitor *monitor, + RateLimiter *limiter, + guint32 time_now, + guint32 *min_time) { - GFileMonitor *monitor = data; + gboolean delete_me; + guint32 expire_at; - send_delayed_change_now (monitor); - - return FALSE; -} + delete_me = TRUE; -static void -schedule_delayed_change (GFileMonitor *monitor, GFile *file, guint32 delay_msec) -{ - if (monitor->priv->send_delayed_change_timeout == 0) /* Only set the timeout once */ + if (limiter->last_sent_change_time != 0) { - monitor->priv->send_delayed_change_timeout = - g_timeout_add (delay_msec, delayed_changed_event_timeout, monitor); + /* 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)); + } } -} -static void -cancel_delayed_change (GFileMonitor *monitor) -{ - if (monitor->priv->send_delayed_change_timeout != 0) + if (limiter->send_delayed_change_at != 0) { - g_source_remove (monitor->priv->send_delayed_change_timeout); - monitor->priv->send_delayed_change_timeout = 0; + delete_me = FALSE; + *min_time = MIN (*min_time, + time_difference (time_now, limiter->send_delayed_change_at)); } -} -/* Virtual changes_done_hint support: */ - -static void -send_virtual_changes_done_now (GFileMonitor *monitor) -{ - if (monitor->priv->virtual_changes_done_timeout) + if (limiter->send_virtual_changes_done_at != 0) { - g_signal_emit (monitor, signals[CHANGED], 0, - monitor->priv->virtual_changes_done_file, NULL, - G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); - - g_source_destroy (monitor->priv->virtual_changes_done_timeout); - monitor->priv->virtual_changes_done_timeout = NULL; - - g_object_unref (monitor->priv->virtual_changes_done_file); - monitor->priv->virtual_changes_done_file = NULL; + delete_me = FALSE; + *min_time = MIN (*min_time, + time_difference (time_now, limiter->send_virtual_changes_done_at)); } + + return delete_me; } static gboolean -virtual_changes_done_timeout (gpointer data) +foreach_rate_limiter_fire (gpointer key, + gpointer value, + gpointer user_data) { - GFileMonitor *monitor = data; + RateLimiter *limiter = value; + ForEachData *data = user_data; - send_virtual_changes_done_now (monitor); + 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); - return FALSE; + 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 void -schedule_virtual_change_done (GFileMonitor *monitor, GFile *file) +static gboolean +rate_limiter_timeout (gpointer timeout_data) { + GFileMonitor *monitor = timeout_data; + ForEachData data; GSource *source; - source = g_timeout_source_new_seconds (DEFAULT_VIRTUAL_CHANGES_DONE_DELAY_SECS); + 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); - g_source_set_callback (source, virtual_changes_done_timeout, monitor, NULL); - g_source_attach (source, NULL); - monitor->priv->virtual_changes_done_timeout = source; - monitor->priv->virtual_changes_done_file = g_object_ref (file); - g_source_unref (source); + /* 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 -cancel_virtual_changes_done (GFileMonitor *monitor) +update_rate_limiter_timeout (GFileMonitor *monitor, + guint new_time) { - if (monitor->priv->virtual_changes_done_timeout) + 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) { - g_source_destroy (monitor->priv->virtual_changes_done_timeout); - monitor->priv->virtual_changes_done_timeout = NULL; + 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); - g_object_unref (monitor->priv->virtual_changes_done_file); - monitor->priv->virtual_changes_done_file = NULL; + monitor->priv->timeout = source; + monitor->priv->timeout_fires_at = data.time_now + data.min_time; } } /** * g_file_monitor_emit_event: * @monitor: a #GFileMonitor. - * @file: a #GFile. + * @child: a #GFile. * @other_file: a #GFile. - * @event_type: a #GFileMonitorEvent - * - * Emits a file monitor event. This is mainly necessary for implementations - * of GFileMonitor. + * @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 *file, - GFile *other_file, - GFileMonitorEvent event_type) +g_file_monitor_emit_event (GFileMonitor *monitor, + 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 (file)); + g_return_if_fail (G_IS_FILE (child)); + + limiter = g_hash_table_lookup (monitor->priv->rate_limiter, child); if (event_type != G_FILE_MONITOR_EVENT_CHANGED) { - send_delayed_change_now (monitor); - update_last_sent_change (monitor, NULL, 0); - if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) - cancel_virtual_changes_done (monitor); - else - send_virtual_changes_done_now (monitor); - g_signal_emit (monitor, signals[CHANGED], 0, file, other_file, event_type); + 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 (monitor->priv->last_sent_change_file) + if (limiter) { - since_last = time_difference (monitor->priv->last_sent_change_time, time_now); + 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; - schedule_delayed_change (monitor, file, - monitor->priv->rate_limit_msec - since_last); + 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) { - g_signal_emit (monitor, signals[CHANGED], 0, file, other_file, event_type); + emit_in_idle (monitor, child, other_file, event_type); - cancel_delayed_change (monitor); - update_last_sent_change (monitor, file, time_now); + 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. */ - cancel_virtual_changes_done (monitor); - schedule_virtual_change_done (monitor, file); + + 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); } } - -#define __G_FILE_MONITOR_C__ -#include "gioaliasdef.c"