diff options
author | Richard Hughes <richard@hughsie.com> | 2015-07-24 17:57:25 +0100 |
---|---|---|
committer | Richard Hughes <richard@hughsie.com> | 2015-07-27 08:43:01 +0100 |
commit | a228c324d8df884fc56c3464e373878e68139fa3 (patch) | |
tree | 7a0823546028bcbc40207a26d32e230fb21208ac | |
parent | 1c003a633aca7ee639dfdcd8bfd6f0bca2887a42 (diff) | |
download | appstream-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.am | 2 | ||||
-rw-r--r-- | libappstream-glib/as-monitor.c | 505 | ||||
-rw-r--r-- | libappstream-glib/as-monitor.h | 97 | ||||
-rw-r--r-- | libappstream-glib/as-self-test.c | 235 |
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); |