summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2015-07-24 17:57:25 +0100
committerRichard Hughes <richard@hughsie.com>2015-07-27 08:43:01 +0100
commita228c324d8df884fc56c3464e373878e68139fa3 (patch)
tree7a0823546028bcbc40207a26d32e230fb21208ac
parent1c003a633aca7ee639dfdcd8bfd6f0bca2887a42 (diff)
downloadappstream-glib-a228c324d8df884fc56c3464e373878e68139fa3.tar.gz
Add AsMonitor an abstraction on top of GFileMonitor
This is required to get sane semantics from GFileMonitor() when using temporary files and atomic renames.
-rw-r--r--libappstream-glib/Makefile.am2
-rw-r--r--libappstream-glib/as-monitor.c505
-rw-r--r--libappstream-glib/as-monitor.h97
-rw-r--r--libappstream-glib/as-self-test.c235
4 files changed, 839 insertions, 0 deletions
diff --git a/libappstream-glib/Makefile.am b/libappstream-glib/Makefile.am
index 9491b2b..5d8549f 100644
--- a/libappstream-glib/Makefile.am
+++ b/libappstream-glib/Makefile.am
@@ -99,6 +99,8 @@ libappstream_glib_la_SOURCES = \
as-image-private.h \
as-inf.c \
as-inf.h \
+ as-monitor.c \
+ as-monitor.h \
as-node.c \
as-node-private.h \
as-problem.c \
diff --git a/libappstream-glib/as-monitor.c b/libappstream-glib/as-monitor.c
new file mode 100644
index 0000000..3501d62
--- /dev/null
+++ b/libappstream-glib/as-monitor.c
@@ -0,0 +1,505 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:as-monitor
+ * @short_description: a hashed array monitor of applications
+ * @include: appstream-glib.h
+ * @stability: Stable
+ *
+ * This object will monitor a set of directories for changes.
+ *
+ * See also: #AsApp
+ */
+
+#include "config.h"
+
+#include "as-cleanup.h"
+#include "as-monitor.h"
+
+
+
+typedef struct _AsMonitorPrivate AsMonitorPrivate;
+struct _AsMonitorPrivate
+{
+ GPtrArray *array; /* of GFileMonitor */
+ GPtrArray *files; /* of gchar* */
+ GPtrArray *queue_add; /* of gchar* */
+ GPtrArray *queue_changed; /* of gchar* */
+ GPtrArray *queue_temp; /* of gchar* */
+ guint pending_id;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (AsMonitor, as_monitor, G_TYPE_OBJECT)
+
+enum {
+ SIGNAL_ADDED,
+ SIGNAL_REMOVED,
+ SIGNAL_CHANGED,
+ SIGNAL_LAST
+};
+
+static guint signals [SIGNAL_LAST] = { 0 };
+
+#define GET_PRIVATE(o) (as_monitor_get_instance_private (o))
+
+/**
+ * as_monitor_error_quark:
+ *
+ * Return value: An error quark.
+ *
+ * Since: 0.4.2
+ **/
+GQuark
+as_monitor_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("AsMonitorError");
+ return quark;
+}
+
+/**
+ * as_monitor_finalize:
+ **/
+static void
+as_monitor_finalize (GObject *object)
+{
+ AsMonitor *monitor = AS_MONITOR (object);
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+
+ if (priv->pending_id)
+ g_source_remove (priv->pending_id);
+ g_ptr_array_unref (priv->array);
+ g_ptr_array_unref (priv->files);
+ g_ptr_array_unref (priv->queue_add);
+ g_ptr_array_unref (priv->queue_changed);
+ g_ptr_array_unref (priv->queue_temp);
+
+ G_OBJECT_CLASS (as_monitor_parent_class)->finalize (object);
+}
+
+/**
+ * as_monitor_init:
+ **/
+static void
+as_monitor_init (AsMonitor *monitor)
+{
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+ priv->array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+ priv->files = g_ptr_array_new_with_free_func (g_free);
+ priv->queue_add = g_ptr_array_new_with_free_func (g_free);
+ priv->queue_changed = g_ptr_array_new_with_free_func (g_free);
+ priv->queue_temp = g_ptr_array_new_with_free_func (g_free);
+}
+
+/**
+ * as_monitor_class_init:
+ **/
+static void
+as_monitor_class_init (AsMonitorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ /**
+ * AsMonitor::added:
+ * @monitor: the #AsMonitor instance that emitted the signal
+ * @filename: the filename
+ *
+ * The ::changed signal is emitted when a file has been added.
+ *
+ * Since: 0.4.2
+ **/
+ signals [SIGNAL_ADDED] =
+ g_signal_new ("added",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (AsMonitorClass, added),
+ NULL, NULL, g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ /**
+ * AsMonitor::removed:
+ * @monitor: the #AsMonitor instance that emitted the signal
+ * @filename: the filename
+ *
+ * The ::changed signal is emitted when a file has been removed.
+ *
+ * Since: 0.4.2
+ **/
+ signals [SIGNAL_REMOVED] =
+ g_signal_new ("removed",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (AsMonitorClass, removed),
+ NULL, NULL, g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ /**
+ * AsMonitor::changed:
+ * @monitor: the #AsMonitor instance that emitted the signal
+ * @filename: the filename
+ *
+ * The ::changed signal is emitted when a watched file has changed.
+ *
+ * Since: 0.4.2
+ **/
+ signals [SIGNAL_CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (AsMonitorClass, changed),
+ NULL, NULL, g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ object_class->finalize = as_monitor_finalize;
+}
+
+/**
+ * _g_file_monitor_to_string:
+ */
+static const gchar *
+_g_file_monitor_to_string (GFileMonitorEvent ev)
+{
+ if (ev == G_FILE_MONITOR_EVENT_CHANGED)
+ return "CHANGED";
+ if (ev == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+ return "CHANGES_DONE_HINT";
+ if (ev == G_FILE_MONITOR_EVENT_DELETED)
+ return "DELETED";
+ if (ev == G_FILE_MONITOR_EVENT_CREATED)
+ return "CREATED";
+ if (ev == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED)
+ return "ATTRIBUTE_CHANGED";
+ if (ev == G_FILE_MONITOR_EVENT_PRE_UNMOUNT)
+ return "PRE_UNMOUNT";
+ if (ev == G_FILE_MONITOR_EVENT_UNMOUNTED)
+ return "UNMOUNTED";
+ if (ev == G_FILE_MONITOR_EVENT_MOVED)
+ return "MOVED";
+ return NULL;
+}
+
+/**
+ * _g_ptr_array_str_find:
+ */
+static const gchar *
+_g_ptr_array_str_find (GPtrArray *array, const gchar *fn)
+{
+ guint i;
+ const gchar *tmp;
+ for (i = 0; i < array->len; i++) {
+ tmp = g_ptr_array_index (array, i);
+ if (g_strcmp0 (tmp, fn) == 0)
+ return fn;
+ }
+ return NULL;
+}
+
+/**
+ * _g_ptr_array_str_remove:
+ */
+static gboolean
+_g_ptr_array_str_remove (GPtrArray *array, const gchar *fn)
+{
+ guint i;
+ const gchar *tmp;
+ for (i = 0; i < array->len; i++) {
+ tmp = g_ptr_array_index (array, i);
+ if (g_strcmp0 (tmp, fn) == 0) {
+ g_ptr_array_remove_index_fast (array, i);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * _g_ptr_array_str_add:
+ */
+static void
+_g_ptr_array_str_add (GPtrArray *array, const gchar *fn)
+{
+ if (_g_ptr_array_str_find (array, fn) != NULL)
+ return;
+ g_ptr_array_add (array, g_strdup (fn));
+}
+
+/**
+ * as_monitor_emit_added:
+ */
+static void
+as_monitor_emit_added (AsMonitor *monitor, const gchar *filename)
+{
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+ g_debug ("Emit ::added(%s)", filename);
+ g_signal_emit (monitor, signals[SIGNAL_ADDED], 0, filename);
+ _g_ptr_array_str_add (priv->files, filename);
+}
+
+/**
+ * as_monitor_emit_removed:
+ */
+static void
+as_monitor_emit_removed (AsMonitor *monitor, const gchar *filename)
+{
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+ g_debug ("Emit ::removed(%s)", filename);
+ g_signal_emit (monitor, signals[SIGNAL_REMOVED], 0, filename);
+ _g_ptr_array_str_remove (priv->files, filename);
+}
+
+/**
+ * as_monitor_emit_changed:
+ */
+static void
+as_monitor_emit_changed (AsMonitor *monitor, const gchar *filename)
+{
+ g_debug ("Emit ::changed(%s)", filename);
+ g_signal_emit (monitor, signals[SIGNAL_CHANGED], 0, filename);
+}
+
+/**
+ * as_monitor_process_pending:
+ **/
+static void
+as_monitor_process_pending (AsMonitor *monitor)
+{
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+ guint i;
+ const gchar *tmp;
+
+ /* stop the timer */
+ if (priv->pending_id) {
+ g_source_remove (priv->pending_id);
+ priv->pending_id = 0;
+ }
+
+ /* emit all the pending changed signals */
+ for (i = 0; i < priv->queue_changed->len; i++) {
+ tmp = g_ptr_array_index (priv->queue_changed, i);
+ as_monitor_emit_changed (monitor, tmp);
+ }
+ g_ptr_array_set_size (priv->queue_changed, 0);
+
+ /* emit all the pending add signals */
+ for (i = 0; i < priv->queue_add->len; i++) {
+ tmp = g_ptr_array_index (priv->queue_add, i);
+ /* did we atomically replace an existing file */
+ if (_g_ptr_array_str_find (priv->files, tmp) != NULL) {
+ g_debug ("detecting atomic replace of existing file");
+ as_monitor_emit_changed (monitor, tmp);
+ } else {
+ as_monitor_emit_added (monitor, tmp);
+ }
+ }
+ g_ptr_array_set_size (priv->queue_add, 0);
+}
+
+/**
+ * as_monitor_process_pending_trigger_cb:
+ **/
+static gboolean
+as_monitor_process_pending_trigger_cb (gpointer user_data)
+{
+ AsMonitor *monitor = AS_MONITOR (user_data);
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+
+ g_debug ("No CHANGES_DONE_HINT, catching in timeout");
+ as_monitor_process_pending (monitor);
+ priv->pending_id = 0;
+ return FALSE;
+}
+
+/**
+ * as_monitor_process_pending_trigger:
+ **/
+static void
+as_monitor_process_pending_trigger (AsMonitor *monitor)
+{
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+ if (priv->pending_id)
+ g_source_remove (priv->pending_id);
+ priv->pending_id = g_timeout_add (800,
+ as_monitor_process_pending_trigger_cb,
+ monitor);
+}
+
+/**
+ * as_monitor_file_changed_cb:
+ *
+ * touch newfile -> CREATED+CHANGED+ATTRIBUTE_CHANGED+CHANGES_DONE_HINT
+ * or, just CREATED
+ * touch newfile -> ATTRIBUTE_CHANGED+CHANGES_DONE_HINT
+ * echo "1" > newfile -> CHANGED+CHANGES_DONE_HINT
+ * rm newfile -> DELETED
+ **/
+static void
+as_monitor_file_changed_cb (GFileMonitor *mon,
+ GFile *file, GFile *other_file,
+ GFileMonitorEvent event_type,
+ AsMonitor *monitor)
+{
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+ const gchar *tmp;
+ gboolean is_temp;
+ _cleanup_free_ gchar *filename = NULL;
+ _cleanup_free_ gchar *filename_other = NULL;
+
+ /* get both filenames */
+ filename = g_file_get_path (file);
+ is_temp = !g_file_test (filename, G_FILE_TEST_EXISTS);
+ if (other_file != NULL)
+ filename_other = g_file_get_path (other_file);
+ g_debug ("modified: %s %s [%i]", filename,
+ _g_file_monitor_to_string (event_type), is_temp);
+
+ switch (event_type) {
+ case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+ as_monitor_process_pending (monitor);
+ break;
+ case G_FILE_MONITOR_EVENT_CREATED:
+ if (!is_temp) {
+ _g_ptr_array_str_add (priv->queue_add, filename);
+ } else {
+ _g_ptr_array_str_add (priv->queue_temp, filename);
+ }
+ /* file monitors do not send CHANGES_DONE_HINT */
+ as_monitor_process_pending_trigger (monitor);
+ break;
+ case G_FILE_MONITOR_EVENT_DELETED:
+ as_monitor_emit_removed (monitor, filename);
+ break;
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ /* if the file is not pending and not a temp file, add */
+ if (_g_ptr_array_str_find (priv->queue_add, filename) == NULL &&
+ _g_ptr_array_str_find (priv->queue_temp, filename) == NULL) {
+ _g_ptr_array_str_add (priv->queue_changed, filename);
+ }
+ break;
+ case G_FILE_MONITOR_EVENT_MOVED:
+ /* a temp file that was just created and atomically
+ * renamed to its final destination */
+ tmp = _g_ptr_array_str_find (priv->queue_temp, filename);
+ if (tmp != NULL) {
+ g_debug ("detected atomic save, adding %s", filename_other);
+ g_ptr_array_remove_fast (priv->queue_temp, (gpointer) tmp);
+ if (_g_ptr_array_str_find (priv->files, filename_other) != NULL)
+ as_monitor_emit_changed (monitor, filename_other);
+ else
+ as_monitor_emit_added (monitor, filename_other);
+ } else {
+ g_debug ("detected rename, treating it as remove->add");
+ as_monitor_emit_removed (monitor, filename);
+ as_monitor_emit_added (monitor, filename_other);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * as_monitor_add_directory:
+ **/
+gboolean
+as_monitor_add_directory (AsMonitor *monitor,
+ const gchar *filename,
+ GCancellable *cancellable,
+ GError **error)
+{
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+ const gchar *tmp;
+ _cleanup_object_unref_ GFileMonitor *mon = NULL;
+ _cleanup_object_unref_ GFile *file = NULL;
+ _cleanup_dir_close_ GDir *dir = NULL;
+
+ /* find the files already in the directory */
+ dir = g_dir_open (filename, 0, error);
+ if (dir == NULL)
+ return FALSE;
+ while ((tmp = g_dir_read_name (dir)) != NULL) {
+ _cleanup_free_ gchar *fn = NULL;
+ fn = g_build_filename (filename, tmp, NULL);
+ g_debug ("adding existing file: %s", fn);
+ _g_ptr_array_str_add (priv->files, fn);
+ }
+
+ /* create new file monitor */
+ file = g_file_new_for_path (filename);
+ mon = g_file_monitor_directory (file, G_FILE_MONITOR_SEND_MOVED,
+ cancellable, error);
+ if (mon == NULL)
+ return FALSE;
+ g_signal_connect (mon, "changed",
+ G_CALLBACK (as_monitor_file_changed_cb), monitor);
+ g_ptr_array_add (priv->array, g_object_ref (mon));
+
+ return TRUE;
+}
+
+/**
+ * as_monitor_add_file:
+ **/
+gboolean
+as_monitor_add_file (AsMonitor *monitor,
+ const gchar *filename,
+ GCancellable *cancellable,
+ GError **error)
+{
+ AsMonitorPrivate *priv = GET_PRIVATE (monitor);
+ _cleanup_object_unref_ GFile *file = NULL;
+ _cleanup_object_unref_ GFileMonitor *mon = NULL;
+
+ /* already watched */
+ if (_g_ptr_array_str_find (priv->files, filename) != NULL)
+ return TRUE;
+
+ /* create new file monitor */
+ file = g_file_new_for_path (filename);
+ mon = g_file_monitor_file (file, G_FILE_MONITOR_NONE,
+ cancellable, error);
+ if (mon == NULL)
+ return FALSE;
+ g_signal_connect (mon, "changed",
+ G_CALLBACK (as_monitor_file_changed_cb), monitor);
+ g_ptr_array_add (priv->array, g_object_ref (mon));
+
+ /* only add if actually exists */
+ if (g_file_test (filename, G_FILE_TEST_EXISTS))
+ _g_ptr_array_str_add (priv->files, filename);
+
+ return TRUE;
+}
+
+/**
+ * as_monitor_new:
+ *
+ * Creates a new #AsMonitor.
+ *
+ * Returns: (transfer full): a #AsMonitor
+ *
+ * Since: 0.4.2
+ **/
+AsMonitor *
+as_monitor_new (void)
+{
+ AsMonitor *monitor;
+ monitor = g_object_new (AS_TYPE_MONITOR, NULL);
+ return AS_MONITOR (monitor);
+}
diff --git a/libappstream-glib/as-monitor.h b/libappstream-glib/as-monitor.h
new file mode 100644
index 0000000..0906f09
--- /dev/null
+++ b/libappstream-glib/as-monitor.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#if !defined (__APPSTREAM_GLIB_H) && !defined (AS_COMPILATION)
+#error "Only <appstream-glib.h> can be included directly."
+#endif
+
+#ifndef __AS_MONITOR_H
+#define __AS_MONITOR_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define AS_TYPE_MONITOR (as_monitor_get_type())
+#define AS_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), AS_TYPE_MONITOR, AsMonitor))
+#define AS_MONITOR_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), AS_TYPE_MONITOR, AsMonitorClass))
+#define AS_IS_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), AS_TYPE_MONITOR))
+#define AS_IS_MONITOR_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), AS_TYPE_MONITOR))
+#define AS_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), AS_TYPE_MONITOR, AsMonitorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _AsMonitor AsMonitor;
+typedef struct _AsMonitorClass AsMonitorClass;
+
+struct _AsMonitor
+{
+ GObject parent;
+};
+
+struct _AsMonitorClass
+{
+ GObjectClass parent_class;
+ void (*added) (AsMonitor *monitor,
+ const gchar *filename);
+ void (*removed) (AsMonitor *monitor,
+ const gchar *filename);
+ void (*changed) (AsMonitor *monitor,
+ const gchar *filename);
+ /*< private >*/
+ void (*_as_reserved1) (void);
+ void (*_as_reserved2) (void);
+ void (*_as_reserved3) (void);
+ void (*_as_reserved4) (void);
+ void (*_as_reserved5) (void);
+ void (*_as_reserved6) (void);
+ void (*_as_reserved7) (void);
+};
+
+/**
+ * AsMonitorError:
+ * @AS_MONITOR_ERROR_FAILED: Generic failure
+ *
+ * The error type.
+ **/
+typedef enum {
+ AS_MONITOR_ERROR_FAILED,
+ /*< private >*/
+ AS_MONITOR_ERROR_LAST
+} AsMonitorError;
+
+#define AS_MONITOR_ERROR as_monitor_error_quark ()
+
+GType as_monitor_get_type (void);
+AsMonitor *as_monitor_new (void);
+GQuark as_monitor_error_quark (void);
+
+gboolean as_monitor_add_directory (AsMonitor *monitor,
+ const gchar *filename,
+ GCancellable *cancellable,
+ GError **error);
+gboolean as_monitor_add_file (AsMonitor *monitor,
+ const gchar *filename,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __AS_MONITOR_H */
diff --git a/libappstream-glib/as-self-test.c b/libappstream-glib/as-self-test.c
index 9e7dc96..a117c92 100644
--- a/libappstream-glib/as-self-test.c
+++ b/libappstream-glib/as-self-test.c
@@ -22,6 +22,7 @@
#include "config.h"
#include <glib.h>
+#include <glib/gstdio.h>
#include <stdlib.h>
#include "as-app-private.h"
@@ -31,6 +32,7 @@
#include "as-icon-private.h"
#include "as-image-private.h"
#include "as-inf.h"
+#include "as-monitor.h"
#include "as-node-private.h"
#include "as-problem.h"
#include "as-provide-private.h"
@@ -84,6 +86,237 @@ as_test_get_filename (const gchar *filename)
return g_strdup (full_tmp);
}
+static GMainLoop *_test_loop = NULL;
+static guint _test_loop_timeout_id = 0;
+
+static gboolean
+as_test_hang_check_cb (gpointer user_data)
+{
+ g_main_loop_quit (_test_loop);
+ _test_loop_timeout_id = 0;
+ _test_loop = NULL;
+ return G_SOURCE_REMOVE;
+}
+
+/**
+ * as_test_loop_run_with_timeout:
+ **/
+static void
+as_test_loop_run_with_timeout (guint timeout_ms)
+{
+ g_assert (_test_loop_timeout_id == 0);
+ g_assert (_test_loop == NULL);
+ _test_loop = g_main_loop_new (NULL, FALSE);
+ _test_loop_timeout_id = g_timeout_add (timeout_ms, as_test_hang_check_cb, NULL);
+ g_main_loop_run (_test_loop);
+}
+
+/**
+ * as_test_loop_quit:
+ **/
+static void
+as_test_loop_quit (void)
+{
+ if (_test_loop_timeout_id > 0) {
+ g_source_remove (_test_loop_timeout_id);
+ _test_loop_timeout_id = 0;
+ }
+ if (_test_loop != NULL) {
+ g_main_loop_quit (_test_loop);
+ g_main_loop_unref (_test_loop);
+ _test_loop = NULL;
+ }
+}
+
+static void
+monitor_test_cb (AsMonitor *mon, const gchar *filename, guint *cnt)
+{
+ (*cnt)++;
+}
+
+static void
+as_test_monitor_dir_func (void)
+{
+ gboolean ret;
+ guint cnt_added = 0;
+ guint cnt_removed = 0;
+ guint cnt_changed = 0;
+ _cleanup_object_unref_ AsMonitor *mon = NULL;
+ _cleanup_error_free_ GError *error = NULL;
+ const gchar *tmpdir = "/tmp/monitor-test/usr/share/app-info/xmls";
+ _cleanup_free_ gchar *tmpfile = NULL;
+ _cleanup_free_ gchar *tmpfile_new = NULL;
+ _cleanup_free_ gchar *cmd_touch = NULL;
+
+ tmpfile = g_build_filename (tmpdir, "test.txt", NULL);
+ tmpfile_new = g_build_filename (tmpdir, "newtest.txt", NULL);
+ g_unlink (tmpfile);
+ g_unlink (tmpfile_new);
+
+ mon = as_monitor_new ();
+ g_signal_connect (mon, "added",
+ G_CALLBACK (monitor_test_cb), &cnt_added);
+ g_signal_connect (mon, "removed",
+ G_CALLBACK (monitor_test_cb), &cnt_removed);
+ g_signal_connect (mon, "changed",
+ G_CALLBACK (monitor_test_cb), &cnt_changed);
+
+ /* add watch */
+ g_mkdir_with_parents (tmpdir, 0700);
+ ret = as_monitor_add_directory (mon, tmpdir, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ /* touch file */
+ cmd_touch = g_strdup_printf ("touch %s", tmpfile);
+ ret = g_spawn_command_line_sync (cmd_touch, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 1);
+ g_assert_cmpint (cnt_removed, ==, 0);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ /* just change the mtime */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ ret = g_spawn_command_line_sync (cmd_touch, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 0);
+ g_assert_cmpint (cnt_removed, ==, 0);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ /* delete it */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ g_unlink (tmpfile);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 0);
+ g_assert_cmpint (cnt_removed, ==, 1);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ /* save a new file with temp copy */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ ret = g_file_set_contents (tmpfile, "foo", -1, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 1);
+ g_assert_cmpint (cnt_removed, ==, 0);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ /* modify file with temp copy */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ ret = g_file_set_contents (tmpfile, "bar", -1, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 0);
+ g_assert_cmpint (cnt_removed, ==, 0);
+ g_assert_cmpint (cnt_changed, ==, 1);
+
+ /* rename the file */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ g_rename (tmpfile, tmpfile_new);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 1);
+ g_assert_cmpint (cnt_removed, ==, 1);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ g_unlink (tmpfile);
+ g_unlink (tmpfile_new);
+}
+
+static void
+as_test_monitor_file_func (void)
+{
+ gboolean ret;
+ guint cnt_added = 0;
+ guint cnt_removed = 0;
+ guint cnt_changed = 0;
+ _cleanup_object_unref_ AsMonitor *mon = NULL;
+ _cleanup_error_free_ GError *error = NULL;
+ const gchar *tmpfile = "/tmp/one.txt";
+ const gchar *tmpfile_new = "/tmp/two.txt";
+ _cleanup_free_ gchar *cmd_touch = NULL;
+
+ g_unlink (tmpfile);
+ g_unlink (tmpfile_new);
+
+ mon = as_monitor_new ();
+ g_signal_connect (mon, "added",
+ G_CALLBACK (monitor_test_cb), &cnt_added);
+ g_signal_connect (mon, "removed",
+ G_CALLBACK (monitor_test_cb), &cnt_removed);
+ g_signal_connect (mon, "changed",
+ G_CALLBACK (monitor_test_cb), &cnt_changed);
+
+ /* add a single file */
+ ret = as_monitor_add_file (mon, tmpfile, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ /* touch file */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ cmd_touch = g_strdup_printf ("touch %s", tmpfile);
+ ret = g_spawn_command_line_sync (cmd_touch, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 1);
+ g_assert_cmpint (cnt_removed, ==, 0);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ /* just change the mtime */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ ret = g_spawn_command_line_sync (cmd_touch, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 0);
+ g_assert_cmpint (cnt_removed, ==, 0);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ /* delete it */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ g_unlink (tmpfile);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 0);
+ g_assert_cmpint (cnt_removed, ==, 1);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ /* save a new file with temp copy */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ ret = g_file_set_contents (tmpfile, "foo", -1, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 1);
+ g_assert_cmpint (cnt_removed, ==, 0);
+ g_assert_cmpint (cnt_changed, ==, 0);
+
+ /* modify file with temp copy */
+ cnt_added = cnt_removed = cnt_changed = 0;
+ ret = g_file_set_contents (tmpfile, "bar", -1, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ as_test_loop_run_with_timeout (2000);
+ as_test_loop_quit ();
+ g_assert_cmpint (cnt_added, ==, 0);
+ g_assert_cmpint (cnt_removed, ==, 0);
+ g_assert_cmpint (cnt_changed, ==, 1);
+}
+
static void
as_test_tag_func (void)
{
@@ -3802,6 +4035,8 @@ main (int argc, char **argv)
g_test_add_func ("/AppStream/utils{spdx-token}", as_test_utils_spdx_token_func);
g_test_add_func ("/AppStream/utils{install-filename}", as_test_utils_install_filename_func);
g_test_add_func ("/AppStream/utils{vercmp}", as_test_utils_vercmp_func);
+ g_test_add_func ("/AppStream/monitor{dir}", as_test_monitor_dir_func);
+ g_test_add_func ("/AppStream/monitor{file}", as_test_monitor_file_func);
g_test_add_func ("/AppStream/yaml", as_test_yaml_func);
g_test_add_func ("/AppStream/store", as_test_store_func);
g_test_add_func ("/AppStream/store{demote}", as_test_store_demote_func);