From 463f3d3a52509c89cda77910c20befcb20352c6e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 7 Jul 2020 18:44:41 -0400 Subject: [PATCH] directorylist: Add monitoring Add a GtkDirectoryList:monitored property, and keep a file monitor if it is set to TRUE. To ensure that the list reflects reality, we reload the directory when monitoring is turned on after the fact. This means that turning monitoring is expensive, while turning it off is cheap, so we default to monitoring being on. --- docs/reference/gtk/gtk4-sections.txt | 2 + gtk/gtkdirectorylist.c | 231 ++++++++++++++++++++++++++- gtk/gtkdirectorylist.h | 6 + 3 files changed, 235 insertions(+), 4 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 80809369b4..e5b667cf43 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1444,6 +1444,8 @@ gtk_directory_list_get_file gtk_directory_list_set_file gtk_directory_list_get_io_priority gtk_directory_list_set_io_priority +gtk_directory_list_get_monitored +gtk_directory_list_set_monitored gtk_directory_list_is_loading gtk_directory_list_get_error diff --git a/gtk/gtkdirectorylist.c b/gtk/gtkdirectorylist.c index 053305b674..b4a0d10014 100644 --- a/gtk/gtkdirectorylist.c +++ b/gtk/gtkdirectorylist.c @@ -62,6 +62,7 @@ enum { PROP_IO_PRIORITY, PROP_ITEM_TYPE, PROP_LOADING, + PROP_MONITORED, NUM_PROPERTIES }; @@ -70,8 +71,10 @@ struct _GtkDirectoryList GObject parent_instance; char *attributes; - int io_priority; GFile *file; + GFileMonitor *monitor; + gboolean monitored; + int io_priority; GCancellable *cancellable; GError *error; /* Error while loading */ @@ -147,13 +150,17 @@ gtk_directory_list_set_property (GObject *object, gtk_directory_list_set_io_priority (self, g_value_get_int (value)); break; + case PROP_MONITORED: + gtk_directory_list_set_monitored (self, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } -static void +static void gtk_directory_list_get_property (GObject *object, guint prop_id, GValue *value, @@ -187,6 +194,10 @@ gtk_directory_list_get_property (GObject *object, g_value_set_boolean (value, gtk_directory_list_is_loading (self)); break; + case PROP_MONITORED: + g_value_set_boolean (value, gtk_directory_list_get_monitored (self)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -204,12 +215,27 @@ gtk_directory_list_stop_loading (GtkDirectoryList *self) return TRUE; } +static void directory_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer data); + +static void +gtk_directory_list_stop_monitoring (GtkDirectoryList *self) +{ + if (self->monitor) + g_signal_handlers_disconnect_by_func (self->monitor, directory_changed, self); + g_clear_object (&self->monitor); +} + static void gtk_directory_list_dispose (GObject *object) { GtkDirectoryList *self = GTK_DIRECTORY_LIST (object); gtk_directory_list_stop_loading (self); + gtk_directory_list_stop_monitoring (self); g_clear_object (&self->file); g_clear_pointer (&self->attributes, g_free); @@ -301,6 +327,18 @@ gtk_directory_list_class_init (GtkDirectoryListClass *class) FALSE, GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkDirectoryList:monitored: + * + * %TRUE if the directory is monitored for changed + */ + properties[PROP_MONITORED] = + g_param_spec_boolean ("monitored", + P_("monitored"), + P_("TRUE if the directory is monitored for changes"), + TRUE, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); } @@ -309,6 +347,7 @@ gtk_directory_list_init (GtkDirectoryList *self) { self->items = g_sequence_new (g_object_unref); self->io_priority = G_PRIORITY_DEFAULT; + self->monitored = TRUE; } /** @@ -324,7 +363,6 @@ gtk_directory_list_init (GtkDirectoryList *self) GtkDirectoryList * gtk_directory_list_new (const char *attributes, GFile *file) - { g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL); @@ -410,7 +448,7 @@ gtk_directory_list_got_files_cb (GObject *source, { GFileInfo *info; GFile *file; - + info = l->data; file = g_file_enumerator_get_child (enumerator, info); g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); @@ -496,6 +534,141 @@ gtk_directory_list_start_loading (GtkDirectoryList *self) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); } +static void +got_new_file_info_cb (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GFile *file = G_FILE (source); + GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); + GFileInfo *info; + guint position; + + info = g_file_query_info_finish (file, res, NULL); + if (!info) + return; + + g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); + position = g_sequence_get_length (self->items); + g_sequence_append (self->items, info); + g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); +} + +static void +got_existing_file_info_cb (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GFile *file = G_FILE (source); + GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); + GFileInfo *info; + GSequenceIter *iter; + + info = g_file_query_info_finish (file, res, NULL); + if (!info) + return; + + g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); + + iter = g_sequence_get_begin_iter (self->items); + while (!g_sequence_iter_is_end (iter)) + { + GFileInfo *item = g_sequence_get (iter); + GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file")); + if (g_file_equal (f, file)) + { + guint position = g_sequence_iter_get_position (iter); + g_sequence_set (iter, g_object_ref (info)); + g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 1); + break; + } + } +} + +static void +gtk_directory_list_remove_file (GtkDirectoryList *self, + GFile *file) +{ + GSequenceIter *iter; + + iter = g_sequence_get_begin_iter (self->items); + while (!g_sequence_iter_is_end (iter)) + { + GFileInfo *item = g_sequence_get (iter); + GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file")); + if (g_file_equal (f, file)) + { + guint position = g_sequence_iter_get_position (iter); + g_sequence_remove (iter); + g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0); + break; + } + } +} + +static void +directory_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer data) +{ + GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); + switch (event) + { + case G_FILE_MONITOR_EVENT_CREATED: + g_file_query_info_async (file, + self->attributes, + G_FILE_QUERY_INFO_NONE, + self->io_priority, + self->cancellable, + got_new_file_info_cb, + self); + break; + + case G_FILE_MONITOR_EVENT_DELETED: + gtk_directory_list_remove_file (self, file); + break; + + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + g_file_query_info_async (file, + self->attributes, + G_FILE_QUERY_INFO_NONE, + self->io_priority, + self->cancellable, + got_existing_file_info_cb, + self); + break; + + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + case G_FILE_MONITOR_EVENT_UNMOUNTED: + case G_FILE_MONITOR_EVENT_MOVED: + case G_FILE_MONITOR_EVENT_RENAMED: + case G_FILE_MONITOR_EVENT_MOVED_IN: + case G_FILE_MONITOR_EVENT_MOVED_OUT: + default: + break; + } +} + +static void +gtk_directory_list_start_monitoring (GtkDirectoryList *self) +{ + g_assert (self->monitor == NULL); + self->monitor = g_file_monitor_directory (self->file, G_FILE_MONITOR_NONE, NULL, NULL); + g_signal_connect (self->monitor, "changed", G_CALLBACK (directory_changed), self); +} + +static void +gtk_directory_list_update_monitoring (GtkDirectoryList *self) +{ + gtk_directory_list_stop_monitoring (self); + if (self->file && self->monitored) + gtk_directory_list_start_monitoring (self); +} + /** * gtk_directory_list_set_file: * @self: a #GtkDirectoryList @@ -520,6 +693,7 @@ gtk_directory_list_set_file (GtkDirectoryList *self, g_set_object (&self->file, file); + gtk_directory_list_update_monitoring (self); gtk_directory_list_start_loading (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]); @@ -679,3 +853,52 @@ gtk_directory_list_get_error (GtkDirectoryList *self) return self->error; } +/** + * gtk_directory_list_set_monitored: + * @self: a #GtkDirectoryList + * @monitored: %TRUE to monitor the directory for changes + * + * Sets whether the directory list will monitor the directory + * for changes. If monitoring is enabled, the + * #GListModel::items-changed signal will be emitted when the + * directory contents change. + * + * When monitoring is turned on after the initial creation + * of the directory list, the directory is reloaded to avoid + * missing files that appeared between the initial loading + * and when monitoring was turned on. + */ +void +gtk_directory_list_set_monitored (GtkDirectoryList *self, + gboolean monitored) +{ + g_return_if_fail (GTK_IS_DIRECTORY_LIST (self)); + + if (self->monitored == monitored) + return; + + self->monitored = monitored; + + gtk_directory_list_update_monitoring (self); + if (monitored) + gtk_directory_list_start_loading (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MONITORED]); +} + +/** + * gtk_directory_list_get_monitored: + * @self: a #GtkDirectoryList + * + * Returns whether the directory list is monitoring + * the directory for changes. + * + * Returns: %TRUE if the directory is monitored + */ +gboolean +gtk_directory_list_get_monitored (GtkDirectoryList *self) +{ + g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), TRUE); + + return self->monitored; +} diff --git a/gtk/gtkdirectorylist.h b/gtk/gtkdirectorylist.h index 0952224c40..a41b4928af 100644 --- a/gtk/gtkdirectorylist.h +++ b/gtk/gtkdirectorylist.h @@ -62,6 +62,12 @@ gboolean gtk_directory_list_is_loading (GtkDirectoryLis GDK_AVAILABLE_IN_ALL const GError * gtk_directory_list_get_error (GtkDirectoryList *self); +GDK_AVAILABLE_IN_ALL +void gtk_directory_list_set_monitored (GtkDirectoryList *self, + gboolean monitored); +GDK_AVAILABLE_IN_ALL +gboolean gtk_directory_list_get_monitored (GtkDirectoryList *self); + G_END_DECLS #endif /* __GTK_DIRECTORY_LIST_H__ */