* 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 <http://www.gnu.org/licenses/>.
*
* Author: Alexander Larsson <alexl@redhat.com>
*/
-#include <config.h>
+#include "config.h"
#include <string.h>
#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
* Monitors a file or directory for changes.
*
* To obtain a #GFileMonitor for a file or directory, use
- * g_file_monitor_file() or g_file_monitor_directory().
+ * 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.
+ * 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);
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 */
/* 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_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,
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;
}
g_hash_table_destroy (monitor->priv->rate_limiter);
-
- if (G_OBJECT_CLASS (g_file_monitor_parent_class)->finalize)
- (*G_OBJECT_CLASS (g_file_monitor_parent_class)->finalize) (object);
+
+ 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;
/* Make sure we cancel on last unref */
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_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;
* 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"),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GFileMonitorClass, changed),
NULL, NULL,
- _gio_marshal_VOID__OBJECT_OBJECT_ENUM,
+ NULL,
G_TYPE_NONE, 3,
G_TYPE_FILE, G_TYPE_FILE, G_TYPE_FILE_MONITOR_EVENT);
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);
}
/**
/**
* 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)
{
}
}
-typedef struct {
- GFileMonitor *monitor;
+struct _FileChange {
GFile *child;
GFile *other_file;
GFileMonitorEvent event_type;
-} FileChange;
-
-static gboolean
-emit_cb (gpointer data)
-{
- FileChange *change = data;
- g_signal_emit (change->monitor, signals[CHANGED], 0,
- change->child, change->other_file, change->event_type);
- return FALSE;
-}
+};
static void
file_change_free (FileChange *change)
{
- g_object_unref (change->monitor);
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,
{
GSource *source;
FileChange *change;
+ GFileMonitorPrivate *priv;
+
+ priv = monitor->priv;
change = g_slice_new (FileChange);
- change->monitor = g_object_ref (monitor);
change->child = g_object_ref (child);
if (other_file)
change->other_file = g_object_ref (other_file);
change->other_file = NULL;
change->event_type = event_type;
- source = g_idle_source_new ();
- g_source_set_priority (source, 0);
-
- g_source_set_callback (source, emit_cb, change, (GDestroyNotify)file_change_free);
- g_source_attach (source, NULL);
- g_source_unref (source);
+ 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
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 eventualy */
+ /* 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)
{
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, NULL);
+ g_source_attach (source, monitor->priv->context);
monitor->priv->timeout = source;
monitor->priv->timeout_fires_at = data.time_now + data.min_time;
{
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, NULL);
+ g_source_attach (source, monitor->priv->context);
monitor->priv->timeout = source;
monitor->priv->timeout_fires_at = data.time_now + data.min_time;
* has taken place. Should be called from file monitor
* implementations only.
*
- * The signal will be emitted from an idle handler.
+ * 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,
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 eventualy */
+ /* 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);
}
update_rate_limiter_timeout (monitor, limiter->send_virtual_changes_done_at);
}
}
-
-#define __G_FILE_MONITOR_C__
-#include "gioaliasdef.c"