diff --git a/backend-Prevent-usage-of-NULL.patch b/backend-Prevent-usage-of-NULL.patch new file mode 100644 index 0000000..60f03e2 --- /dev/null +++ b/backend-Prevent-usage-of-NULL.patch @@ -0,0 +1,43 @@ +From d1ab5870e828df259897598298c2ca66b587426a Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Wed, 29 Aug 2018 15:36:57 +0200 +Subject: [PATCH] backend: Prevent usage of NULL + +g_dbus_connection_get_peer_credentials may fail in some cases, see +the documentation, and thus we have to check the return value before +use in order to prevent warnings like the following: + +g_credentials_get_unix_pid: assertion 'G_IS_CREDENTIALS (credentials)' failed + +Print simply -1 if pid can't be obtained this way. +--- + daemon/gvfsbackend.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/daemon/gvfsbackend.c b/daemon/gvfsbackend.c +index 673070fa..4fd3455c 100644 +--- a/daemon/gvfsbackend.c ++++ b/daemon/gvfsbackend.c +@@ -609,14 +609,17 @@ g_vfs_backend_invocation_first_handler (GVfsDBusMount *object, + { + GDBusConnection *connection; + GCredentials *credentials; ++ pid_t pid = -1; + + connection = g_dbus_method_invocation_get_connection (invocation); + credentials = g_dbus_connection_get_peer_credentials (connection); ++ if (credentials) ++ pid = g_credentials_get_unix_pid (credentials, NULL); + +- g_debug ("backend_dbus_handler %s:%s (pid=%u)\n", ++ g_debug ("backend_dbus_handler %s:%s (pid=%ld)\n", + g_dbus_method_invocation_get_interface_name (invocation), + g_dbus_method_invocation_get_method_name (invocation), +- g_credentials_get_unix_pid (credentials, NULL)); ++ (long)pid); + + if (backend->priv->block_requests) + { +-- +2.53.0 + diff --git a/daemon-Add-process-name-to-debug-handler-log.patch b/daemon-Add-process-name-to-debug-handler-log.patch new file mode 100644 index 0000000..c07cbbd --- /dev/null +++ b/daemon-Add-process-name-to-debug-handler-log.patch @@ -0,0 +1,47 @@ +From 9e38c56f99372b2474a780ac0c7c7d9b38e490cf Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Mon, 11 May 2026 17:48:26 +0200 +Subject: [PATCH] daemon: Add process name to debug handler log + +Made-with: Cursor (Claude) +--- + daemon/gvfsbackend.c | 17 +++++++++++++---- + 1 file changed, 13 insertions(+), 4 deletions(-) + +diff --git a/daemon/gvfsbackend.c b/daemon/gvfsbackend.c +index fa1389ea..7dfd5300 100644 +--- a/daemon/gvfsbackend.c ++++ b/daemon/gvfsbackend.c +@@ -636,16 +636,25 @@ g_vfs_backend_invocation_first_handler (GVfsDBusMount *object, + GDBusConnection *connection; + GCredentials *credentials; + pid_t pid = -1; ++ g_autofree gchar *comm = NULL; + + connection = g_dbus_method_invocation_get_connection (invocation); + credentials = g_dbus_connection_get_peer_credentials (connection); + if (credentials) + pid = g_credentials_get_unix_pid (credentials, NULL); + +- g_debug ("backend_dbus_handler %s:%s (pid=%ld)\n", +- g_dbus_method_invocation_get_interface_name (invocation), +- g_dbus_method_invocation_get_method_name (invocation), +- (long)pid); ++ if (pid > 0) ++ { ++ g_autofree gchar *comm_path = g_strdup_printf ("/proc/%ld/comm", (long)pid); ++ ++ if (g_file_get_contents (comm_path, &comm, NULL, NULL)) ++ g_strstrip (comm); ++ } ++ ++ g_debug ("backend_dbus_handler %s:%s (pid=%ld comm=%s)\n", ++ g_dbus_method_invocation_get_interface_name (invocation), ++ g_dbus_method_invocation_get_method_name (invocation), ++ (long)pid, comm ? comm : ""); + + if (backend->priv->block_requests) + { +-- +2.53.0 + diff --git a/gvfs.spec b/gvfs.spec index 62ea475..1eae6bf 100644 --- a/gvfs.spec +++ b/gvfs.spec @@ -25,7 +25,7 @@ Name: gvfs Version: 1.36.2 -Release: 20%{?dist} +Release: 21%{?dist} Summary: Backends for the gio framework in GLib License: GPLv3 and LGPLv2+ and BSD and MPLv2.0 @@ -99,6 +99,29 @@ Patch28: udisks2-monitor-performance-273.patch Patch29: udisks2-monitor-performance-290.patch Patch30: udisks2-monitor-performance-297.patch +# Improve trash backend performance +# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/287 +# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/175 +# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/169 +# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/11 +Patch31: backend-Prevent-usage-of-NULL.patch +Patch32: trash-Add-is_root-function.patch +Patch33: trash-Remove-unused-ui_hook-property.patch +Patch34: trash-Remove-unused-is_homedir-property.patch +Patch35: trash-Do-not-create-monitors-for-non-root-items.patch +Patch36: trash-Run-blocking-methods-on-a-thread-pool.patch +Patch37: trash-Add-worker-thread.patch +Patch38: trash-Chain-up-finalize.patch +Patch39: trash-Rate-limit-mount-updates.patch +Patch40: trash-Add-debug-prints-for-mounts-handling.patch +Patch41: trash-Use-GHashTable-for-mount-tracking.patch +Patch42: trash-Use-GHashTable-for-trash-directory-items.patch +Patch43: trash-Rate-limit-size-change-notifications.patch +Patch44: trash-Use-weak-refs-for-monitor-lifecycle.patch +Patch45: trash-Defer-mount-monitoring-to-active-watchers-only.patch +Patch46: daemon-Add-process-name-to-debug-handler-log.patch +Patch47: trash-Skip-mount-table-reparsing-when-unchanged.patch + BuildRequires: pkgconfig BuildRequires: pkgconfig(glib-2.0) >= %{glib2_version} BuildRequires: pkgconfig(dbus-glib-1) @@ -489,6 +512,10 @@ killall -USR1 gvfsd >&/dev/null || : %{_datadir}/installed-tests %changelog +* Tue Jun 02 2026 Ondrej Holy - 1.36.2-21 +- Improve trash backend performance + Resolves: RHEL-127445 + * Fri Feb 06 2026 Milan Crha - 1.36.2-20 - udisks2: Correct add to hash table for items which can clash in the monitor (RHEL-76484) diff --git a/trash-Add-debug-prints-for-mounts-handling.patch b/trash-Add-debug-prints-for-mounts-handling.patch new file mode 100644 index 0000000..01cbc39 --- /dev/null +++ b/trash-Add-debug-prints-for-mounts-handling.patch @@ -0,0 +1,60 @@ +From 78cc4e391d4f9e6e17af83e980f9d819b7fd208e Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 15:32:58 +0200 +Subject: [PATCH] trash: Add debug prints for mounts handling + +Backport of commit e0018263. + +Adapted for older GLib API: `g_unix_mount_entry_get_device_path`/`get_mount_path`/`get_fs_type` replaced with `g_unix_mount_get_device_path`/`g_unix_mount_get_mount_path`/`g_unix_mount_get_fs_type`. +--- + daemon/trashlib/trashwatcher.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index fe2ee44..c195234 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -245,12 +245,22 @@ trash_watcher_remount_do (TrashWatcher *watcher) + if (result < 0) + { + /* new entry. add it. */ ++ g_debug ("trash_watcher_remount_do: insert %s %s %s\n", ++ g_unix_mount_get_device_path (new->data), ++ g_unix_mount_get_mount_path (new->data), ++ g_unix_mount_get_fs_type (new->data)); ++ + trash_mount_insert (watcher, &old, new->data); + new = new->next; + } + else if (result > 0) + { + /* old entry. remove it. */ ++ g_debug ("trash_watcher_remount_do: remove %s %s %s\n", ++ g_unix_mount_get_device_path ((*old)->mount_entry), ++ g_unix_mount_get_mount_path ((*old)->mount_entry), ++ g_unix_mount_get_fs_type ((*old)->mount_entry)); ++ + trash_mount_remove (old); + } + else +@@ -271,6 +281,8 @@ trash_watcher_remount_timeout (gpointer user_data) + { + TrashWatcher *watcher = user_data; + ++ g_debug ("trash_watcher_remount_timeout\n"); ++ + watcher->update_id = 0; + + trash_watcher_remount_do (watcher); +@@ -281,6 +293,8 @@ trash_watcher_remount_timeout (gpointer user_data) + static void + trash_watcher_remount (TrashWatcher *watcher) + { ++ g_debug ("trash_watcher_remount\n"); ++ + if (watcher->update_id != 0) + return; + +-- +2.53.0 + diff --git a/trash-Add-is_root-function.patch b/trash-Add-is_root-function.patch new file mode 100644 index 0000000..c5039aa --- /dev/null +++ b/trash-Add-is_root-function.patch @@ -0,0 +1,94 @@ +From 86805f19dbcb55a4ad6fa2a00670d00b927c9d47 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Thu, 19 Jan 2023 12:57:30 +0100 +Subject: [PATCH] trash: Add is_root function + +Let's make the code more readable using the is_root function instead +of char comparison. +--- + daemon/gvfsbackendtrash.c | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c +index 9702b860..d7ec4f77 100644 +--- a/daemon/gvfsbackendtrash.c ++++ b/daemon/gvfsbackendtrash.c +@@ -44,6 +44,12 @@ struct OPAQUE_TYPE__GVfsBackendTrash + + G_DEFINE_TYPE (GVfsBackendTrash, g_vfs_backend_trash, G_VFS_TYPE_BACKEND); + ++static gboolean ++is_root (const char *filename) ++{ ++ return (filename[0] == '/' && filename[1] == '\0'); ++} ++ + static GVfsMonitor * + trash_backend_get_file_monitor (GVfsBackendTrash *backend, + gboolean create) +@@ -229,7 +235,7 @@ trash_backend_open_for_read (GVfsBackend *vfs_backend, + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend); + GError *error = NULL; + +- if (filename[1] == '\0') ++ if (is_root (filename)) + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, + _("Can’t open directory")); + +@@ -397,7 +403,7 @@ trash_backend_delete (GVfsBackend *vfs_backend, + GError *error = NULL; + g_debug ("before job: %d\n", G_OBJECT(job)->ref_count); + +- if (filename[1] == '\0') ++ if (is_root (filename)) + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("The trash folder may not be deleted")); + else +@@ -456,7 +462,7 @@ trash_backend_pull (GVfsBackend *vfs_backend, + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend); + GError *error = NULL; + +- if (source[1] == '\0') ++ if (is_root (source)) + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("The trash folder may not be deleted")); + else +@@ -683,7 +689,7 @@ trash_backend_enumerate (GVfsBackend *vfs_backend, + + trash_watcher_rescan (backend->watcher); + +- if (filename[1]) ++ if (!is_root (filename)) + trash_backend_enumerate_non_root (backend, job, filename, + attribute_matcher, flags); + else +@@ -729,7 +735,7 @@ trash_backend_query_info (GVfsBackend *vfs_backend, + if (!backend->file_monitor && !backend->dir_monitor) + trash_watcher_rescan (backend->watcher); + +- if (filename[1]) ++ if (!is_root (filename)) + { + GError *error = NULL; + gboolean is_toplevel; +@@ -832,7 +838,7 @@ trash_backend_create_dir_monitor (GVfsBackend *vfs_backend, + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend); + GVfsMonitor *monitor; + +- if (filename[1]) ++ if (!is_root (filename)) + monitor = g_vfs_monitor_new (vfs_backend); + else + monitor = trash_backend_get_dir_monitor (backend, TRUE); +@@ -853,7 +859,7 @@ trash_backend_create_file_monitor (GVfsBackend *vfs_backend, + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend); + GVfsMonitor *monitor; + +- if (filename[1]) ++ if (!is_root (filename)) + monitor = g_vfs_monitor_new (vfs_backend); + else + monitor = trash_backend_get_file_monitor (backend, TRUE); +-- +2.53.0 + diff --git a/trash-Add-worker-thread.patch b/trash-Add-worker-thread.patch new file mode 100644 index 0000000..8cfe6cc --- /dev/null +++ b/trash-Add-worker-thread.patch @@ -0,0 +1,295 @@ +From 890b2a747bbcd9ed220fb217aba41b9ef0188ba1 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Wed, 7 Jun 2023 15:49:41 +0200 +Subject: [PATCH] trash: Add worker thread + +The previous commit makes the backend a bit more resilience of against +stale NFS mounts, but still a lot of I/O operations (e.g. +`trash_watcher_rescan`) run on the main thread and can easily block the +whole backend. This is especially problem for the `create_dir_monitor` and +`create_file_monitor` methods that doesn't have asynchronous variants. +This may cause hangs for nautilus, or file chooser dialog, but also for +the whole gnome-shell session (resp. desktop-icons extension). Let's add +a worker thread to ensure that the `create_dir_monitor` and +`create_file_monitor` methods response immediately. + +Related: https://bugzilla.redhat.com/show_bug.cgi?id=2152538 +--- + daemon/gvfsbackendtrash.c | 156 ++++++++++++++++++++++++++++++++------ + 1 file changed, 131 insertions(+), 25 deletions(-) + +diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c +index b4d3d089..0966242a 100644 +--- a/daemon/gvfsbackendtrash.c ++++ b/daemon/gvfsbackendtrash.c +@@ -36,6 +36,10 @@ struct OPAQUE_TYPE__GVfsBackendTrash + GVfsMonitor *file_monitor; + GVfsMonitor *dir_monitor; + ++ GMainContext *worker_context; ++ GMainLoop *worker_loop; ++ GThread *worker_thread; ++ + TrashWatcher *watcher; + TrashRoot *root; + +@@ -44,6 +48,94 @@ struct OPAQUE_TYPE__GVfsBackendTrash + + G_DEFINE_TYPE (GVfsBackendTrash, g_vfs_backend_trash, G_VFS_TYPE_BACKEND); + ++typedef struct ++{ ++ GSourceFunc source_func; ++ gpointer user_data; ++ GMutex mutex; ++ GCond cond; ++ gboolean completed; ++} ContextInvokeData; ++ ++static gboolean ++source_func_wrapper (gpointer user_data) ++{ ++ ContextInvokeData *data = user_data; ++ ++ g_mutex_lock (&data->mutex); ++ ++ while (data->source_func (data->user_data)); ++ data->completed = TRUE; ++ ++ g_cond_signal (&data->cond); ++ g_mutex_unlock (&data->mutex); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++trash_backend_worker_thread_queue_and_wait (GVfsBackendTrash *backend, ++ GSourceFunc source_func) ++{ ++ ContextInvokeData data; ++ ++ data.source_func = source_func; ++ data.user_data = backend; ++ ++ g_mutex_init (&data.mutex); ++ g_cond_init (&data.cond); ++ data.completed = FALSE; ++ ++ g_mutex_lock (&data.mutex); ++ ++ g_main_context_invoke (backend->worker_context, ++ source_func_wrapper, ++ &data); ++ ++ while (!data.completed) ++ { ++ g_cond_wait (&data.cond, &data.mutex); ++ } ++ ++ g_mutex_unlock (&data.mutex); ++ ++ g_mutex_clear (&data.mutex); ++ g_cond_clear (&data.cond); ++} ++ ++static void ++trash_backend_worker_thread_queue (GVfsBackendTrash *backend, ++ GSourceFunc source_func) ++{ ++ g_main_context_invoke (backend->worker_context, source_func, backend); ++} ++ ++static gboolean ++watch_func (gpointer user_data) ++{ ++ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (user_data); ++ ++ trash_watcher_watch (backend->watcher); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static gboolean ++rescan_func (gpointer user_data) ++{ ++ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (user_data); ++ ++ trash_watcher_rescan (backend->watcher); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static gboolean ++ready_func (gpointer user_data) ++{ ++ return G_SOURCE_REMOVE; ++} ++ + static gboolean + is_root (const char *filename) + { +@@ -63,7 +155,7 @@ trash_backend_get_file_monitor (GVfsBackendTrash *backend, + * no possibility here for creating more than one new monitor. + */ + if (backend->dir_monitor == NULL) +- trash_watcher_watch (backend->watcher); ++ trash_backend_worker_thread_queue (backend, watch_func); + + backend->file_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); + } +@@ -84,7 +176,7 @@ trash_backend_get_dir_monitor (GVfsBackendTrash *backend, + * no possibility here for creating more than one new monitor. + */ + if (backend->file_monitor == NULL) +- trash_watcher_watch (backend->watcher); ++ trash_backend_worker_thread_queue (backend, watch_func); + + backend->dir_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); + } +@@ -164,7 +256,6 @@ trash_backend_item_count_changed (gpointer user_data) + } + } + +- + static GFile * + trash_backend_get_file (GVfsBackendTrash *backend, + const char *filename, +@@ -177,6 +268,8 @@ trash_backend_get_file (GVfsBackendTrash *backend, + TrashItem *item; + GFile *file; + ++ trash_backend_worker_thread_queue_and_wait (backend, rescan_func); ++ + file = NULL; + filename++; + +@@ -243,9 +336,6 @@ trash_backend_open_for_read (GVfsBackend *vfs_backend, + { + GFile *real; + +- if (!backend->file_monitor && !backend->dir_monitor) +- trash_watcher_rescan (backend->watcher); +- + real = trash_backend_get_file (backend, filename, NULL, NULL, &error); + + if (real) +@@ -404,9 +494,6 @@ trash_backend_delete (GVfsBackend *vfs_backend, + TrashItem *item; + GFile *real; + +- if (!backend->file_monitor && !backend->dir_monitor) +- trash_watcher_rescan (backend->watcher); +- + real = trash_backend_get_file (backend, filename, + &item, &is_toplevel, &error); + +@@ -461,9 +548,6 @@ trash_backend_pull (GVfsBackend *vfs_backend, + TrashItem *item; + GFile *real; + +- if (!backend->file_monitor && !backend->dir_monitor) +- trash_watcher_rescan (backend->watcher); +- + real = trash_backend_get_file (backend, source, &item, + &is_toplevel, &error); + +@@ -585,6 +669,8 @@ trash_backend_enumerate_root (GVfsBackendTrash *backend, + + g_vfs_job_succeeded (G_VFS_JOB (job)); + ++ trash_backend_worker_thread_queue_and_wait (backend, rescan_func); ++ + items = trash_root_get_items (backend->root); + + for (node = items; node; node = node->next) +@@ -675,8 +761,6 @@ trash_backend_enumerate (GVfsBackend *vfs_backend, + + g_assert (filename[0] == '/'); + +- trash_watcher_rescan (backend->watcher); +- + if (!is_root (filename)) + trash_backend_enumerate_non_root (backend, job, filename, + attribute_matcher, flags); +@@ -684,6 +768,31 @@ trash_backend_enumerate (GVfsBackend *vfs_backend, + trash_backend_enumerate_root (backend, job, attribute_matcher, flags); + } + ++static gpointer ++thread_func (gpointer user_data) ++{ ++ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (user_data); ++ ++ g_main_context_push_thread_default (backend->worker_context); ++ backend->worker_loop = g_main_loop_new (backend->worker_context, FALSE); ++ ++ backend->root = trash_root_new (trash_backend_item_created, ++ trash_backend_item_deleted, ++ trash_backend_item_count_changed, ++ backend); ++ backend->watcher = trash_watcher_new (backend->root); ++ ++ g_main_loop_run (backend->worker_loop); ++ ++ trash_watcher_free (backend->watcher); ++ trash_root_free (backend->root); ++ ++ g_main_context_pop_thread_default (backend->worker_context); ++ g_main_loop_unref (backend->worker_loop); ++ ++ return NULL; ++} ++ + static void + trash_backend_mount (GVfsBackend *vfs_backend, + GVfsJobMount *job, +@@ -695,11 +804,12 @@ trash_backend_mount (GVfsBackend *vfs_backend, + + backend->file_monitor = NULL; + backend->dir_monitor = NULL; +- backend->root = trash_root_new (trash_backend_item_created, +- trash_backend_item_deleted, +- trash_backend_item_count_changed, +- backend); +- backend->watcher = trash_watcher_new (backend->root); ++ ++ backend->worker_context = g_main_context_new (); ++ backend->worker_thread = g_thread_new ("Trash Worker Thread", ++ thread_func, ++ backend); ++ trash_backend_worker_thread_queue_and_wait (backend, ready_func); + + g_vfs_job_succeeded (G_VFS_JOB (job)); + } +@@ -716,9 +826,6 @@ trash_backend_query_info (GVfsBackend *vfs_backend, + + g_assert (filename[0] == '/'); + +- if (!backend->file_monitor && !backend->dir_monitor) +- trash_watcher_rescan (backend->watcher); +- + if (!is_root (filename)) + { + GError *error = NULL; +@@ -762,6 +869,8 @@ trash_backend_query_info (GVfsBackend *vfs_backend, + GIcon *icon; + int n_items; + ++ trash_backend_worker_thread_queue_and_wait (backend, rescan_func); ++ + n_items = trash_root_get_n_items (backend->root); + + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); +@@ -878,9 +987,6 @@ trash_backend_finalize (GObject *object) + if (backend->dir_monitor) + g_object_unref (backend->dir_monitor); + backend->dir_monitor = NULL; +- +- trash_watcher_free (backend->watcher); +- trash_root_free (backend->root); + } + + static void +-- +2.53.0 + diff --git a/trash-Chain-up-finalize.patch b/trash-Chain-up-finalize.patch new file mode 100644 index 0000000..2f52e7a --- /dev/null +++ b/trash-Chain-up-finalize.patch @@ -0,0 +1,30 @@ +From f1cdb812f8fa6f06239183923f920025a05e0712 Mon Sep 17 00:00:00 2001 +From: Maximiliano Sandoval +Date: Fri, 10 Apr 2026 23:52:16 +0200 +Subject: [PATCH] trash: Chain up finalize + +Currently, `trash_backend_finalize()` only releases backend-specific +monitors. Without chaining up, parent `GObject` finalization is +skipped, which can leave parent-side teardown incomplete. Let's call +`g_vfs_backend_trash_parent_class->finalize()` at the end of +`trash_backned_finalize()`. +--- + daemon/gvfsbackendtrash.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c +index 0966242a..1dcb44f0 100644 +--- a/daemon/gvfsbackendtrash.c ++++ b/daemon/gvfsbackendtrash.c +@@ -987,6 +987,8 @@ trash_backend_finalize (GObject *object) + if (backend->dir_monitor) + g_object_unref (backend->dir_monitor); + backend->dir_monitor = NULL; ++ ++ G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize (object); + } + + static void +-- +2.53.0 + diff --git a/trash-Defer-mount-monitoring-to-active-watchers-only.patch b/trash-Defer-mount-monitoring-to-active-watchers-only.patch new file mode 100644 index 0000000..af63bbc --- /dev/null +++ b/trash-Defer-mount-monitoring-to-active-watchers-only.patch @@ -0,0 +1,84 @@ +From 5e34cc212b32e58fd7b6721d92548e983fecd991 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 15:54:33 +0200 +Subject: [PATCH] trash: Defer mount monitoring to active watchers only + +Backport of commit b53d45db. + +Adjusted context for target version. +--- + daemon/trashlib/trashwatcher.c | 26 +++++++++++++++++++++----- + 1 file changed, 21 insertions(+), 5 deletions(-) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index bd658e1..3a1bc9f 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -139,6 +139,7 @@ struct OPAQUE_TYPE__TrashWatcher + TrashRoot *root; + + GUnixMountMonitor *mount_monitor; ++ gulong mounts_changed_id; + GHashTable *mounts; /* mount_path -> TrashMount */ + guint update_id; + +@@ -339,8 +340,7 @@ trash_watcher_new (TrashRoot *root) + watcher->watching = FALSE; + watcher->update_id = 0; + watcher->mount_monitor = g_unix_mount_monitor_get (); +- g_signal_connect_swapped (watcher->mount_monitor, "mounts_changed", +- G_CALLBACK (trash_watcher_remount), watcher); ++ watcher->mounts_changed_id = 0; + + user_datadir = g_file_new_for_path (g_get_user_data_dir ()); + homedir_trashdir = g_file_get_child (user_datadir, "Trash/files"); +@@ -378,9 +378,17 @@ trash_watcher_watch (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + ++ g_debug ("trash_watcher_watch\n"); ++ + if (watcher->watching) + return; + ++ watcher->mounts_changed_id = ++ g_signal_connect_swapped (watcher->mount_monitor, "mounts_changed", ++ G_CALLBACK (trash_watcher_remount), watcher); ++ ++ trash_watcher_remount_do (watcher); ++ + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_watch (watcher->homedir_trashdir); + +@@ -404,9 +412,17 @@ trash_watcher_unwatch (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + ++ g_debug ("trash_watcher_unwatch\n"); ++ + if (!watcher->watching) + return; + ++ g_signal_handler_disconnect (watcher->mount_monitor, ++ watcher->mounts_changed_id); ++ watcher->mounts_changed_id = 0; ++ ++ g_clear_handle_id (&watcher->update_id, g_source_remove); ++ + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_unwatch (watcher->homedir_trashdir); + +@@ -430,7 +446,9 @@ trash_watcher_rescan (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + +- if (watcher->update_id != 0) ++ if (!watcher->watching) ++ trash_watcher_remount_do (watcher); ++ else if (watcher->update_id != 0) + { + g_source_remove (watcher->update_id); + trash_watcher_remount_timeout (watcher); +-- +2.53.0 + diff --git a/trash-Do-not-create-monitors-for-non-root-items.patch b/trash-Do-not-create-monitors-for-non-root-items.patch new file mode 100644 index 0000000..a26c129 --- /dev/null +++ b/trash-Do-not-create-monitors-for-non-root-items.patch @@ -0,0 +1,57 @@ +From 85a789c800ef2773af6eb8884fef746d67d3cafe Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Fri, 20 Jan 2023 09:17:00 +0100 +Subject: [PATCH] trash: Do not create monitors for non-root items + +The `create_dir_monitor` and `create_file_monitor` methods never fail +currently. However, no events are emitted for non-root items. Let's +return the `G_IO_ERROR_NOT_SUPPORTED` error for non-root items instead. +--- + daemon/gvfsbackendtrash.c | 22 ++++++++++++++++------ + 1 file changed, 16 insertions(+), 6 deletions(-) + +diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c +index d7ec4f77..76c9e006 100644 +--- a/daemon/gvfsbackendtrash.c ++++ b/daemon/gvfsbackendtrash.c +@@ -839,10 +839,15 @@ trash_backend_create_dir_monitor (GVfsBackend *vfs_backend, + GVfsMonitor *monitor; + + if (!is_root (filename)) +- monitor = g_vfs_monitor_new (vfs_backend); +- else +- monitor = trash_backend_get_dir_monitor (backend, TRUE); ++ { ++ g_vfs_job_failed (G_VFS_JOB (job), ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ _("Operation not supported")); ++ return TRUE; ++ } + ++ monitor = trash_backend_get_dir_monitor (backend, TRUE); + g_vfs_job_create_monitor_set_monitor (job, monitor); + g_vfs_job_succeeded (G_VFS_JOB (job)); + g_object_unref (monitor); +@@ -860,10 +865,15 @@ trash_backend_create_file_monitor (GVfsBackend *vfs_backend, + GVfsMonitor *monitor; + + if (!is_root (filename)) +- monitor = g_vfs_monitor_new (vfs_backend); +- else +- monitor = trash_backend_get_file_monitor (backend, TRUE); ++ { ++ g_vfs_job_failed (G_VFS_JOB (job), ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ _("Operation not supported")); ++ return TRUE; ++ } + ++ monitor = trash_backend_get_file_monitor (backend, TRUE); + g_vfs_job_create_monitor_set_monitor (job, monitor); + g_vfs_job_succeeded (G_VFS_JOB (job)); + g_object_unref (monitor); +-- +2.53.0 + diff --git a/trash-Rate-limit-mount-updates.patch b/trash-Rate-limit-mount-updates.patch new file mode 100644 index 0000000..751fc14 --- /dev/null +++ b/trash-Rate-limit-mount-updates.patch @@ -0,0 +1,119 @@ +From 181da9f0d10a9e75795db5bb138214a89382271f Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Fri, 21 Nov 2025 13:28:55 +0100 +Subject: [PATCH] trash: Rate limit mount updates + +Currently, the trash daemon processes every `mounts_changed` signal. +This leads to high CPU usage when many mount events occur in a short +timeframe. Let's rate limit mount processing to avoid high CPU usage +in this case. The timeout is randomly chosen within the interval +50-500 ms to better balance the load when multiple daemons are +running simultaneously. + +Related: https://gitlab.gnome.org/GNOME/gvfs/-/issues/814 +--- + daemon/trashlib/trashwatcher.c | 39 ++++++++++++++++++++++++++++++++-- + 1 file changed, 37 insertions(+), 2 deletions(-) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index 80a24d3d..664380c6 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -142,6 +142,7 @@ struct OPAQUE_TYPE__TrashWatcher + + GUnixMountMonitor *mount_monitor; + TrashMount *mounts; ++ guint update_id; + + TrashDir *homedir_trashdir; + WatchType homedir_type; +@@ -158,6 +159,8 @@ struct _TrashMount + TrashMount *next; + }; + ++#define UPDATE_TIMEOUT 100 /* ms */ ++ + static void + trash_mount_insert (TrashWatcher *watcher, + TrashMount ***mount_ptr_ptr, +@@ -247,7 +250,7 @@ ignore_trash_mount (GUnixMountEntry *mount) + } + + static void +-trash_watcher_remount (TrashWatcher *watcher) ++trash_watcher_remount_do (TrashWatcher *watcher) + { + TrashMount **old; + GList *mounts; +@@ -298,6 +301,29 @@ trash_watcher_remount (TrashWatcher *watcher) + g_list_free (mounts); + } + ++static gboolean ++trash_watcher_remount_timeout (gpointer user_data) ++{ ++ TrashWatcher *watcher = user_data; ++ ++ watcher->update_id = 0; ++ ++ trash_watcher_remount_do (watcher); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++trash_watcher_remount (TrashWatcher *watcher) ++{ ++ if (watcher->update_id != 0) ++ return; ++ ++ watcher->update_id = g_timeout_add (UPDATE_TIMEOUT * g_random_double_range (0.5, 5), ++ trash_watcher_remount_timeout, ++ watcher); ++} ++ + TrashWatcher * + trash_watcher_new (TrashRoot *root) + { +@@ -310,6 +336,7 @@ trash_watcher_new (TrashRoot *root) + watcher->root = root; + watcher->mounts = NULL; + watcher->watching = FALSE; ++ watcher->update_id = 0; + watcher->mount_monitor = g_unix_mount_monitor_get (); + g_signal_connect_swapped (watcher->mount_monitor, "mounts_changed", + G_CALLBACK (trash_watcher_remount), watcher); +@@ -328,7 +355,7 @@ trash_watcher_new (TrashRoot *root) + g_object_unref (homedir_trashdir); + g_object_unref (user_datadir); + +- trash_watcher_remount (watcher); ++ trash_watcher_remount_do (watcher); + + return watcher; + } +@@ -336,6 +363,8 @@ trash_watcher_new (TrashRoot *root) + void + trash_watcher_free (TrashWatcher *watcher) + { ++ g_clear_handle_id (&watcher->update_id, g_source_remove); ++ + /* We just leak everything here, as this is not normally hit. + This used to be a g_assert_not_reached(), and that got hit when + mounting the trash backend failed due to the trash already being +@@ -387,6 +416,12 @@ trash_watcher_rescan (TrashWatcher *watcher) + { + TrashMount *mount; + ++ if (watcher->update_id != 0) ++ { ++ g_source_remove (watcher->update_id); ++ trash_watcher_remount_timeout (watcher); ++ } ++ + if (!watcher->watching || watcher->homedir_type != TRASH_WATCHER_TRUSTED) + trash_dir_rescan (watcher->homedir_trashdir); + +-- +2.53.0 + diff --git a/trash-Rate-limit-size-change-notifications.patch b/trash-Rate-limit-size-change-notifications.patch new file mode 100644 index 0000000..63569d5 --- /dev/null +++ b/trash-Rate-limit-size-change-notifications.patch @@ -0,0 +1,88 @@ +From 68a056dd3764c795c5d9d5844e2cc3f20f9eaf1d Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 3 Feb 2026 16:47:50 +0100 +Subject: [PATCH] trash: Rate limit size change notifications + +Currently, the `attribute-changed` signal is emitted after every file +change. This can lead to higher CPU usage if too many changes occur +in a short time frame. To avoid this, let's limit the frequency of the +signal to a maximum of 100 ms. + +Co-Authored-By: Claude + +Related: https://gitlab.gnome.org/GNOME/gvfs/-/issues/814 +--- + daemon/trashlib/trashitem.c | 26 +++++++++++++++++++++++--- + 1 file changed, 23 insertions(+), 3 deletions(-) + +diff --git a/daemon/trashlib/trashitem.c b/daemon/trashlib/trashitem.c +index a5b5b166..08dd8ad8 100644 +--- a/daemon/trashlib/trashitem.c ++++ b/daemon/trashlib/trashitem.c +@@ -30,8 +30,12 @@ struct OPAQUE_TYPE__TrashRoot + + GHashTable *item_table; + int old_size; ++ ++ guint size_change_id; + }; + ++#define SIZE_CHANGE_TIMEOUT 100 /* ms */ ++ + struct OPAQUE_TYPE__TrashItem + { + TrashRoot *root; +@@ -283,6 +287,17 @@ trash_item_invoke_closure (NotifyClosure *closure) + g_slice_free (NotifyClosure, closure); + } + ++static gboolean ++trash_root_size_change_timeout (gpointer user_data) ++{ ++ TrashRoot *root = user_data; ++ ++ root->size_change_id = 0; ++ root->size_change (root->user_data); ++ ++ return G_SOURCE_REMOVE; ++} ++ + void + trash_root_thaw (TrashRoot *root) + { +@@ -308,10 +323,13 @@ trash_root_thaw (TrashRoot *root) + size_changed = root->old_size != size; + root->old_size = size; + +- g_rw_lock_writer_unlock (&root->lock); ++ /* Rate limit size_change notifications */ ++ if (size_changed && root->size_change_id == 0) ++ root->size_change_id = g_timeout_add (SIZE_CHANGE_TIMEOUT, ++ trash_root_size_change_timeout, ++ root); + +- if (size_changed) +- root->size_change (root->user_data); ++ g_rw_lock_writer_unlock (&root->lock); + } + + static void +@@ -341,6 +359,7 @@ trash_root_new (trash_item_notify create, + root->item_table = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, trash_item_removed); + root->old_size = 0; ++ root->size_change_id = 0; + + return root; + } +@@ -348,6 +367,7 @@ trash_root_new (trash_item_notify create, + void + trash_root_free (TrashRoot *root) + { ++ g_clear_handle_id (&root->size_change_id, g_source_remove); + g_hash_table_destroy (root->item_table); + + while (!g_queue_is_empty (root->notifications)) +-- +2.53.0 + diff --git a/trash-Remove-unused-is_homedir-property.patch b/trash-Remove-unused-is_homedir-property.patch new file mode 100644 index 0000000..47e9642 --- /dev/null +++ b/trash-Remove-unused-is_homedir-property.patch @@ -0,0 +1,24 @@ +From f8504e445976830287dcb44a51514dc4c15fe528 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Wed, 7 Jun 2023 15:50:51 +0200 +Subject: [PATCH] trash: Remove unused is_homedir property + +--- + daemon/trashlib/trashitem.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/daemon/trashlib/trashitem.c b/daemon/trashlib/trashitem.c +index 08912856..a5b5b166 100644 +--- a/daemon/trashlib/trashitem.c ++++ b/daemon/trashlib/trashitem.c +@@ -29,7 +29,6 @@ struct OPAQUE_TYPE__TrashRoot + gpointer user_data; + + GHashTable *item_table; +- gboolean is_homedir; + int old_size; + }; + +-- +2.53.0 + diff --git a/trash-Remove-unused-ui_hook-property.patch b/trash-Remove-unused-ui_hook-property.patch new file mode 100644 index 0000000..161a4fd --- /dev/null +++ b/trash-Remove-unused-ui_hook-property.patch @@ -0,0 +1,68 @@ +From 07f55a1dae9af0852fed9d774a6ab7d5a924d5e1 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Fri, 20 Jan 2023 15:43:10 +0100 +Subject: [PATCH] trash: Remove unused ui_hook property + +--- + daemon/trashlib/trashdir.c | 11 ----------- + daemon/trashlib/trashdir.h | 5 ----- + 2 files changed, 16 deletions(-) + +diff --git a/daemon/trashlib/trashdir.c b/daemon/trashlib/trashdir.c +index 0d7d2b1b..707dff6c 100644 +--- a/daemon/trashlib/trashdir.c ++++ b/daemon/trashlib/trashdir.c +@@ -373,8 +373,6 @@ trash_dir_rescan (TrashDir *dir) + trash_dir_empty (dir); + } + +-static trash_dir_ui_hook ui_hook; +- + TrashDir * + trash_dir_new (TrashRoot *root, + gboolean watching, +@@ -411,20 +409,11 @@ trash_dir_new (TrashRoot *root, + else + dir->watch = NULL; + +- if (ui_hook) +- ui_hook (dir, dir->directory); +- + g_free (rel); + + return dir; + } + +-void +-trash_dir_set_ui_hook (trash_dir_ui_hook _ui_hook) +-{ +- ui_hook = _ui_hook; +-} +- + void + trash_dir_free (TrashDir *dir) + { +diff --git a/daemon/trashlib/trashdir.h b/daemon/trashlib/trashdir.h +index 5612f9ba..4c39d516 100644 +--- a/daemon/trashlib/trashdir.h ++++ b/daemon/trashlib/trashdir.h +@@ -15,9 +15,6 @@ + + typedef struct OPAQUE_TYPE__TrashDir TrashDir; + +-typedef void (*trash_dir_ui_hook) (TrashDir *dir, +- GFile *directory); +- + TrashDir *trash_dir_new (TrashRoot *root, + gboolean watching, + gboolean is_homedir, +@@ -31,6 +28,4 @@ void trash_dir_watch (TrashDir *dir); + void trash_dir_unwatch (TrashDir *dir); + void trash_dir_rescan (TrashDir *dir); + +-void trash_dir_set_ui_hook (trash_dir_ui_hook ui_hook); +- + #endif /* _trashdir_h_ */ +-- +2.53.0 + diff --git a/trash-Run-blocking-methods-on-a-thread-pool.patch b/trash-Run-blocking-methods-on-a-thread-pool.patch new file mode 100644 index 0000000..acac284 --- /dev/null +++ b/trash-Run-blocking-methods-on-a-thread-pool.patch @@ -0,0 +1,241 @@ +From 3889e4dec938a8023b8120545d4b53083b209dab Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Thu, 19 Jan 2023 12:12:52 +0100 +Subject: [PATCH] trash: Run blocking methods on a thread pool + +All jobs use `_try` methods currently. This is wrong as most of them can +block. This works quite ok except the case of stale NFS mounts that can +block the main loop forever. Let's run the jobs on a thread pool instead. +This reverts commit 01fe5a61 except for the `create_dir_monitor` and +`create_file_monitor` methods in order to avoid issues like +https://bugzilla.gnome.org/show_bug.cgi?id=723305. +--- + daemon/gvfsbackendtrash.c | 68 ++++++++++++++------------------------- + 1 file changed, 25 insertions(+), 43 deletions(-) + +diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c +index 76c9e006..b4d3d089 100644 +--- a/daemon/gvfsbackendtrash.c ++++ b/daemon/gvfsbackendtrash.c +@@ -227,7 +227,7 @@ trash_backend_get_file (GVfsBackendTrash *backend, + } + + /* ======================= method implementations ======================= */ +-static gboolean ++static void + trash_backend_open_for_read (GVfsBackend *vfs_backend, + GVfsJobOpenForRead *job, + const char *filename) +@@ -261,18 +261,16 @@ trash_backend_open_for_read (GVfsBackend *vfs_backend, + g_vfs_job_open_for_read_set_can_seek (job, TRUE); + g_vfs_job_succeeded (G_VFS_JOB (job)); + +- return TRUE; ++ return; + } + } + } + + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); +- +- return TRUE; + } + +-static gboolean ++static void + trash_backend_read (GVfsBackend *backend, + GVfsJobRead *job, + GVfsBackendHandle handle, +@@ -290,16 +288,14 @@ trash_backend_read (GVfsBackend *backend, + g_vfs_job_read_set_size (job, bytes); + g_vfs_job_succeeded (G_VFS_JOB (job)); + +- return TRUE; ++ return; + } + + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); +- +- return TRUE; + } + +-static gboolean ++static void + trash_backend_seek_on_read (GVfsBackend *backend, + GVfsJobSeekRead *job, + GVfsBackendHandle handle, +@@ -313,13 +309,11 @@ trash_backend_seek_on_read (GVfsBackend *backend, + g_vfs_job_seek_read_set_offset (job, g_seekable_tell (handle)); + g_vfs_job_succeeded (G_VFS_JOB (job)); + +- return TRUE; ++ return; + } + + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); +- +- return TRUE; + } + + static void +@@ -349,7 +343,7 @@ trash_backend_query_info_on_read (GVfsBackend *backend, + } + } + +-static gboolean ++static void + trash_backend_close_read (GVfsBackend *backend, + GVfsJobCloseRead *job, + GVfsBackendHandle handle) +@@ -361,15 +355,13 @@ trash_backend_close_read (GVfsBackend *backend, + g_vfs_job_succeeded (G_VFS_JOB (job)); + g_object_unref (handle); + +- return TRUE; ++ return; + } + + g_object_unref (handle); + + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); +- +- return TRUE; + } + + static gboolean +@@ -394,7 +386,7 @@ trash_backend_schedule_thaw (GVfsBackendTrash *backend) + backend); + } + +-static gboolean ++static void + trash_backend_delete (GVfsBackend *vfs_backend, + GVfsJobDelete *job, + const char *filename) +@@ -435,7 +427,7 @@ trash_backend_delete (GVfsBackend *vfs_backend, + g_vfs_job_succeeded (G_VFS_JOB (job)); + trash_item_unref (item); + +- return TRUE; ++ return; + } + } + +@@ -445,11 +437,9 @@ trash_backend_delete (GVfsBackend *vfs_backend, + + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); +- +- return TRUE; + } + +-static gboolean ++static void + trash_backend_pull (GVfsBackend *vfs_backend, + GVfsJobPull *job, + const gchar *source, +@@ -505,7 +495,7 @@ trash_backend_pull (GVfsBackend *vfs_backend, + trash_item_unref (item); + g_object_unref (real); + +- return TRUE; ++ return; + } + } + +@@ -517,8 +507,6 @@ trash_backend_pull (GVfsBackend *vfs_backend, + + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); +- +- return TRUE; + } + + static void +@@ -676,7 +664,7 @@ trash_backend_enumerate_non_root (GVfsBackendTrash *backend, + g_error_free (error); + } + +-static gboolean ++static void + trash_backend_enumerate (GVfsBackend *vfs_backend, + GVfsJobEnumerate *job, + const char *filename, +@@ -694,11 +682,9 @@ trash_backend_enumerate (GVfsBackend *vfs_backend, + attribute_matcher, flags); + else + trash_backend_enumerate_root (backend, job, attribute_matcher, flags); +- +- return TRUE; + } + +-static gboolean ++static void + trash_backend_mount (GVfsBackend *vfs_backend, + GVfsJobMount *job, + GMountSpec *mount_spec, +@@ -716,11 +702,9 @@ trash_backend_mount (GVfsBackend *vfs_backend, + backend->watcher = trash_watcher_new (backend->root); + + g_vfs_job_succeeded (G_VFS_JOB (job)); +- +- return TRUE; + } + +-static gboolean ++static void + trash_backend_query_info (GVfsBackend *vfs_backend, + GVfsJobQueryInfo *job, + const char *filename, +@@ -764,7 +748,7 @@ trash_backend_query_info (GVfsBackend *vfs_backend, + trash_item_unref (item); + g_object_unref (real_info); + +- return TRUE; ++ return; + } + + trash_item_unref (item); +@@ -798,8 +782,6 @@ trash_backend_query_info (GVfsBackend *vfs_backend, + + g_vfs_job_succeeded (G_VFS_JOB (job)); + } +- +- return TRUE; + } + + static gboolean +@@ -926,17 +908,17 @@ g_vfs_backend_trash_class_init (GVfsBackendTrashClass *class) + + gobject_class->finalize = trash_backend_finalize; + +- backend_class->try_mount = trash_backend_mount; +- backend_class->try_open_for_read = trash_backend_open_for_read; +- backend_class->try_read = trash_backend_read; +- backend_class->try_seek_on_read = trash_backend_seek_on_read; ++ backend_class->mount = trash_backend_mount; ++ backend_class->open_for_read = trash_backend_open_for_read; ++ backend_class->read = trash_backend_read; ++ backend_class->seek_on_read = trash_backend_seek_on_read; + backend_class->query_info_on_read = trash_backend_query_info_on_read; +- backend_class->try_close_read = trash_backend_close_read; +- backend_class->try_query_info = trash_backend_query_info; ++ backend_class->close_read = trash_backend_close_read; ++ backend_class->query_info = trash_backend_query_info; + backend_class->try_query_fs_info = trash_backend_query_fs_info; +- backend_class->try_enumerate = trash_backend_enumerate; +- backend_class->try_delete = trash_backend_delete; +- backend_class->try_pull = trash_backend_pull; ++ backend_class->enumerate = trash_backend_enumerate; ++ backend_class->delete = trash_backend_delete; ++ backend_class->pull = trash_backend_pull; + backend_class->try_create_dir_monitor = trash_backend_create_dir_monitor; + backend_class->try_create_file_monitor = trash_backend_create_file_monitor; + } +-- +2.53.0 + diff --git a/trash-Skip-mount-table-reparsing-when-unchanged.patch b/trash-Skip-mount-table-reparsing-when-unchanged.patch new file mode 100644 index 0000000..c5318a5 --- /dev/null +++ b/trash-Skip-mount-table-reparsing-when-unchanged.patch @@ -0,0 +1,47 @@ +From 1ae1e68794a01b524d91c3da0addeca4d060b795 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 15:54:37 +0200 +Subject: [PATCH] trash: Skip mount table reparsing when unchanged + +Backport of commit e1821cdc. + +Adapted for older GLib API: `g_unix_mount_entries_get`/`g_unix_mount_entries_changed_since` replaced with `g_unix_mounts_get`/`g_unix_mounts_changed_since`. +--- + daemon/trashlib/trashwatcher.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index e44ab06..59303f0 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -142,6 +142,7 @@ struct OPAQUE_TYPE__TrashWatcher + gulong mounts_changed_id; + GHashTable *mounts; /* mount_path -> TrashMount */ + guint update_id; ++ guint64 last_mount_time; + + TrashDir *homedir_trashdir; + WatchType homedir_type; +@@ -246,7 +247,10 @@ trash_watcher_remount_do (TrashWatcher *watcher) + gpointer key, value; + const char *mount_path; + +- mounts = g_unix_mounts_get (NULL); ++ if (!g_unix_mounts_changed_since (watcher->last_mount_time)) ++ return; ++ ++ mounts = g_unix_mounts_get (&watcher->last_mount_time); + mount_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + for (l = mounts; l != NULL; l = l->next) +@@ -339,6 +343,7 @@ trash_watcher_new (TrashRoot *root) + watcher->mounts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + watcher->watching = FALSE; + watcher->update_id = 0; ++ watcher->last_mount_time = 0; + watcher->mount_monitor = g_unix_mount_monitor_get (); + watcher->mounts_changed_id = 0; + +-- +2.53.0 + diff --git a/trash-Use-GHashTable-for-mount-tracking.patch b/trash-Use-GHashTable-for-mount-tracking.patch new file mode 100644 index 0000000..8030c0c --- /dev/null +++ b/trash-Use-GHashTable-for-mount-tracking.patch @@ -0,0 +1,280 @@ +From 0f166b6f4f3e38144c779b3e6b55d777c6637b0e Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Wed, 3 Jun 2026 08:37:56 +0200 +Subject: [PATCH] trash: Use GHashTable for mount tracking + +Backport of commit 64f8853f. + +Adapted for older GLib API: `g_unix_mount_entry_*` replaced with `g_unix_mount_*`, `g_unix_mount_entries_get` replaced with `g_unix_mounts_get`. +--- + daemon/trashlib/trashwatcher.c | 156 +++++++++++++++++---------------- + 1 file changed, 80 insertions(+), 76 deletions(-) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index d8d6ef8..426f97f 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -139,7 +139,7 @@ struct OPAQUE_TYPE__TrashWatcher + TrashRoot *root; + + GUnixMountMonitor *mount_monitor; +- TrashMount *mounts; ++ GHashTable *mounts; /* mount_path -> TrashMount */ + guint update_id; + + TrashDir *homedir_trashdir; +@@ -153,16 +153,13 @@ struct _TrashMount + GUnixMountEntry *mount_entry; + TrashDir *dirs[2]; + WatchType type; +- +- TrashMount *next; + }; + + #define UPDATE_TIMEOUT 100 /* ms */ + + static void +-trash_mount_insert (TrashWatcher *watcher, +- TrashMount ***mount_ptr_ptr, +- GUnixMountEntry *mount_entry) ++trash_mount_insert (TrashWatcher *watcher, ++ GUnixMountEntry *mount_entry) + { + const char *mountpoint; + gboolean watching; +@@ -192,24 +189,15 @@ trash_mount_insert (TrashWatcher *watcher, + mount->dirs[1] = trash_dir_new (watcher->root, watching, FALSE, mountpoint, + ".Trash-%d/files", (int) getuid ()); + +- mount->next = **mount_ptr_ptr; +- +- **mount_ptr_ptr = mount; +- *mount_ptr_ptr = &mount->next; ++ g_hash_table_insert (watcher->mounts, g_strdup (mountpoint), mount); + } + + static void +-trash_mount_remove (TrashMount **mount_ptr) ++trash_mount_free (TrashMount *mount) + { +- TrashMount *mount = *mount_ptr; +- +- /* first, the dirs */ + trash_dir_free (mount->dirs[0]); + trash_dir_free (mount->dirs[1]); + +- /* detach from list */ +- *mount_ptr = mount->next; +- + g_unix_mount_free (mount->mount_entry); + g_slice_free (TrashMount, mount); + } +@@ -249,63 +237,64 @@ ignore_trash_mount (GUnixMountEntry *mount) + static void + trash_watcher_remount_do (TrashWatcher *watcher) + { +- TrashMount **old; ++ GHashTable *mount_paths; ++ GHashTableIter iter; + GList *mounts; +- GList *new; ++ GList *l; ++ gpointer key, value; ++ const char *mount_path; + + mounts = g_unix_mounts_get (NULL); +- mounts = g_list_sort (mounts, (GCompareFunc) g_unix_mount_compare); ++ mount_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + +- old = &watcher->mounts; +- new = mounts; +- +- /* synchronise the two lists */ +- while (*old || new) ++ for (l = mounts; l != NULL; l = l->next) + { +- int result; ++ g_autoptr(GUnixMountEntry) mount_entry = l->data; + +- if (new && ignore_trash_mount (new->data)) ++ if (ignore_trash_mount (mount_entry)) + { +- g_unix_mount_free (new->data); +- new = new->next; ++ g_debug ("trash_watcher_remount_do: ignore %s %s %s\n", ++ g_unix_mount_get_device_path (mount_entry), ++ g_unix_mount_get_mount_path (mount_entry), ++ g_unix_mount_get_fs_type (mount_entry)); + continue; + } + +- if ((result = (new == NULL) - (*old == NULL)) == 0) +- result = g_unix_mount_compare (new->data, (*old)->mount_entry); +- +- if (result < 0) ++ mount_path = g_unix_mount_get_mount_path (mount_entry); ++ if (!g_hash_table_contains (watcher->mounts, mount_path)) + { +- /* new entry. add it. */ + g_debug ("trash_watcher_remount_do: insert %s %s %s\n", +- g_unix_mount_get_device_path (new->data), +- g_unix_mount_get_mount_path (new->data), +- g_unix_mount_get_fs_type (new->data)); ++ g_unix_mount_get_device_path (mount_entry), ++ g_unix_mount_get_mount_path (mount_entry), ++ g_unix_mount_get_fs_type (mount_entry)); + +- trash_mount_insert (watcher, &old, new->data); +- new = new->next; ++ trash_mount_insert (watcher, g_steal_pointer (&mount_entry)); + } +- else if (result > 0) +- { +- /* old entry. remove it. */ +- g_debug ("trash_watcher_remount_do: remove %s %s %s\n", +- g_unix_mount_get_device_path ((*old)->mount_entry), +- g_unix_mount_get_mount_path ((*old)->mount_entry), +- g_unix_mount_get_fs_type ((*old)->mount_entry)); + +- trash_mount_remove (old); +- } +- else ++ g_hash_table_add (mount_paths, g_strdup (mount_path)); ++ } ++ ++ g_list_free (mounts); ++ ++ g_hash_table_iter_init (&iter, watcher->mounts); ++ while (g_hash_table_iter_next (&iter, &key, &value)) ++ { ++ TrashMount *mount = value; ++ mount_path = key; ++ ++ if (!g_hash_table_contains (mount_paths, mount_path)) + { +- /* match. no change. */ +- g_unix_mount_free (new->data); ++ g_debug ("trash_watcher_remount_do: remove %s %s %s\n", ++ g_unix_mount_get_device_path (mount->mount_entry), ++ g_unix_mount_get_mount_path (mount->mount_entry), ++ g_unix_mount_get_fs_type (mount->mount_entry)); + +- old = &(*old)->next; +- new = new->next; ++ trash_mount_free (mount); ++ g_hash_table_iter_remove (&iter); + } + } + +- g_list_free (mounts); ++ g_hash_table_destroy (mount_paths); + } + + static gboolean +@@ -345,7 +334,7 @@ trash_watcher_new (TrashRoot *root) + + watcher = g_slice_new (TrashWatcher); + watcher->root = root; +- watcher->mounts = NULL; ++ watcher->mounts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + watcher->watching = FALSE; + watcher->update_id = 0; + watcher->mount_monitor = g_unix_mount_monitor_get (); +@@ -385,19 +374,24 @@ trash_watcher_free (TrashWatcher *watcher) + void + trash_watcher_watch (TrashWatcher *watcher) + { +- TrashMount *mount; ++ GHashTableIter iter; ++ gpointer value; + + g_assert (!watcher->watching); + + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_watch (watcher->homedir_trashdir); + +- for (mount = watcher->mounts; mount; mount = mount->next) +- if (mount->type != TRASH_WATCHER_NO_WATCH) +- { +- trash_dir_watch (mount->dirs[0]); +- trash_dir_watch (mount->dirs[1]); +- } ++ g_hash_table_iter_init (&iter, watcher->mounts); ++ while (g_hash_table_iter_next (&iter, NULL, &value)) ++ { ++ TrashMount *mount = value; ++ if (mount->type != TRASH_WATCHER_NO_WATCH) ++ { ++ trash_dir_watch (mount->dirs[0]); ++ trash_dir_watch (mount->dirs[1]); ++ } ++ } + + watcher->watching = TRUE; + } +@@ -405,19 +399,24 @@ trash_watcher_watch (TrashWatcher *watcher) + void + trash_watcher_unwatch (TrashWatcher *watcher) + { +- TrashMount *mount; ++ GHashTableIter iter; ++ gpointer value; + + g_assert (watcher->watching); + + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_unwatch (watcher->homedir_trashdir); + +- for (mount = watcher->mounts; mount; mount = mount->next) +- if (mount->type != TRASH_WATCHER_NO_WATCH) +- { +- trash_dir_unwatch (mount->dirs[0]); +- trash_dir_unwatch (mount->dirs[1]); +- } ++ g_hash_table_iter_init (&iter, watcher->mounts); ++ while (g_hash_table_iter_next (&iter, NULL, &value)) ++ { ++ TrashMount *mount = value; ++ if (mount->type != TRASH_WATCHER_NO_WATCH) ++ { ++ trash_dir_unwatch (mount->dirs[0]); ++ trash_dir_unwatch (mount->dirs[1]); ++ } ++ } + + watcher->watching = FALSE; + } +@@ -425,7 +424,8 @@ trash_watcher_unwatch (TrashWatcher *watcher) + void + trash_watcher_rescan (TrashWatcher *watcher) + { +- TrashMount *mount; ++ GHashTableIter iter; ++ gpointer value; + + if (watcher->update_id != 0) + { +@@ -436,10 +436,14 @@ trash_watcher_rescan (TrashWatcher *watcher) + if (!watcher->watching || watcher->homedir_type != TRASH_WATCHER_TRUSTED) + trash_dir_rescan (watcher->homedir_trashdir); + +- for (mount = watcher->mounts; mount; mount = mount->next) +- if (!watcher->watching || mount->type != TRASH_WATCHER_TRUSTED) +- { +- trash_dir_rescan (mount->dirs[0]); +- trash_dir_rescan (mount->dirs[1]); +- } ++ g_hash_table_iter_init (&iter, watcher->mounts); ++ while (g_hash_table_iter_next (&iter, NULL, &value)) ++ { ++ TrashMount *mount = value; ++ if (!watcher->watching || mount->type != TRASH_WATCHER_TRUSTED) ++ { ++ trash_dir_rescan (mount->dirs[0]); ++ trash_dir_rescan (mount->dirs[1]); ++ } ++ } + } +-- +2.54.0 + diff --git a/trash-Use-GHashTable-for-trash-directory-items.patch b/trash-Use-GHashTable-for-trash-directory-items.patch new file mode 100644 index 0000000..05820cd --- /dev/null +++ b/trash-Use-GHashTable-for-trash-directory-items.patch @@ -0,0 +1,221 @@ +From 97d9831042ca1c5c13fdb6cd9bfb2e2fe77f94f8 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Wed, 3 Jun 2026 08:45:00 +0200 +Subject: [PATCH] trash: Use GHashTable for trash directory items + +Backport of commit 665ff6b8. + +Adjusted context for target version. +--- + daemon/trashlib/trashdir.c | 122 +++++++++++++++---------------------- + 1 file changed, 48 insertions(+), 74 deletions(-) + +diff --git a/daemon/trashlib/trashdir.c b/daemon/trashlib/trashdir.c +index 23aa0d7..610f2c3 100644 +--- a/daemon/trashlib/trashdir.c ++++ b/daemon/trashlib/trashdir.c +@@ -16,7 +16,7 @@ + struct OPAQUE_TYPE__TrashDir + { + TrashRoot *root; +- GSList *items; ++ GHashTable *items; /* basename -> GFile */ + + GFile *directory; + GFile *topdir; +@@ -27,28 +27,6 @@ struct OPAQUE_TYPE__TrashDir + GFileMonitor *monitor; + }; + +-static gint +-compare_basename (gconstpointer a, +- gconstpointer b) +-{ +- GFile *file_a, *file_b; +- char *name_a, *name_b; +- gint result; +- +- file_a = (GFile *) a; +- file_b = (GFile *) b; +- +- name_a = g_file_get_basename (file_a); +- name_b = g_file_get_basename (file_b); +- +- result = strcmp (name_a, name_b); +- +- g_free (name_a); +- g_free (name_b); +- +- return result; +-} +- + static void + trash_dir_query_mtime (TrashDir *dir, GTimeVal *mtime) + { +@@ -70,47 +48,36 @@ trash_dir_query_mtime (TrashDir *dir, GTimeVal *mtime) + } + + static void +-trash_dir_set_files (TrashDir *dir, +- GSList *items) ++trash_dir_set_files (TrashDir *dir, ++ GHashTable *items) + { +- GSList **old, *new; +- +- items = g_slist_sort (items, (GCompareFunc) compare_basename); +- old = &dir->items; +- new = items; ++ GHashTableIter iter; ++ gpointer key, value; + +- while (new || *old) ++ g_hash_table_iter_init (&iter, dir->items); ++ while (g_hash_table_iter_next (&iter, &key, &value)) + { +- int result; +- +- if ((result = (new == NULL) - (*old == NULL)) == 0) +- result = compare_basename (new->data, (*old)->data); +- +- if (result < 0) +- { +- /* new entry. add it. */ +- *old = g_slist_prepend (*old, new->data); /* take reference */ +- old = &(*old)->next; +- trash_root_add_item (dir->root, new->data, dir->topdir, dir->is_homedir); +- new = new->next; +- } +- else if (result > 0) ++ if (!g_hash_table_contains (items, key)) + { + /* old entry. remove it. */ +- trash_root_remove_item (dir->root, (*old)->data, dir->is_homedir); +- g_object_unref ((*old)->data); +- *old = g_slist_delete_link (*old, *old); ++ trash_root_remove_item (dir->root, value, dir->is_homedir); ++ g_hash_table_iter_remove (&iter); + } +- else ++ } ++ ++ g_hash_table_iter_init (&iter, items); ++ while (g_hash_table_iter_next (&iter, &key, &value)) ++ { ++ if (!g_hash_table_contains (dir->items, key)) + { +- /* match. no change. */ +- old = &(*old)->next; +- g_object_unref (new->data); +- new = new->next; ++ /* new entry. add it. */ ++ g_hash_table_iter_steal (&iter); ++ g_hash_table_insert (dir->items, key, value); ++ trash_root_add_item (dir->root, value, dir->topdir, dir->is_homedir); + } + } + +- g_slist_free (items); ++ g_hash_table_unref (items); + + trash_root_thaw (dir->root); + } +@@ -118,16 +85,23 @@ trash_dir_set_files (TrashDir *dir, + static void + trash_dir_empty (TrashDir *dir) + { +- trash_dir_set_files (dir, NULL); ++ GHashTable *empty; ++ ++ empty = g_hash_table_new (g_str_hash, g_str_equal); ++ trash_dir_set_files (dir, empty); + } + + static void + trash_dir_enumerate (TrashDir *dir) + { + GFileEnumerator *enumerator; +- GSList *files = NULL; ++ GHashTable *files = NULL; + + trash_dir_query_mtime (dir, &dir->mtime); ++ ++ files = g_hash_table_new_full (g_str_hash, g_str_equal, ++ g_free, g_object_unref); ++ + enumerator = g_file_enumerate_children (dir->directory, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, +@@ -140,10 +113,11 @@ trash_dir_enumerate (TrashDir *dir) + while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL))) + { + GFile *file; ++ const gchar *name; + +- file = g_file_get_child (dir->directory, +- g_file_info_get_name (info)); +- files = g_slist_prepend (files, file); ++ name = g_file_info_get_name (info); ++ file = g_file_get_child (dir->directory, name); ++ g_hash_table_insert (files, g_strdup (name), file); + + g_object_unref (info); + } +@@ -165,24 +139,20 @@ trash_dir_changed (GFileMonitor *monitor, + + if (event_type == G_FILE_MONITOR_EVENT_CREATED) + { +- dir->items = g_slist_insert_sorted (dir->items, +- g_object_ref (file), +- (GCompareFunc) compare_basename); +- trash_root_add_item (dir->root, file, dir->topdir, dir->is_homedir); ++ g_autofree gchar *name = g_file_get_basename (file); ++ ++ if (!g_hash_table_contains (dir->items, name)) ++ { ++ g_hash_table_insert (dir->items, g_steal_pointer (&name), g_object_ref (file)); ++ trash_root_add_item (dir->root, file, dir->topdir, dir->is_homedir); ++ } + } + + else if (event_type == G_FILE_MONITOR_EVENT_DELETED) + { +- GSList *node; ++ g_autofree char *name = g_file_get_basename (file); + +- node = g_slist_find_custom (dir->items, +- file, +- (GCompareFunc) compare_basename); +- if (node) +- { +- g_object_unref (node->data); +- dir->items = g_slist_delete_link (dir->items, node); +- } ++ g_hash_table_remove (dir->items, name); + trash_root_remove_item (dir->root, file, dir->is_homedir); + } + +@@ -386,7 +356,8 @@ trash_dir_new (TrashRoot *root, + dir = g_slice_new (TrashDir); + + dir->root = root; +- dir->items = NULL; ++ dir->items = g_hash_table_new_full (g_str_hash, g_str_equal, ++ g_free, g_object_unref); + dir->topdir = g_file_new_for_path (mount_point); + dir->directory = g_file_get_child (dir->topdir, rel); + dir->monitor = NULL; +@@ -416,7 +387,9 @@ trash_dir_free (TrashDir *dir) + if (dir->monitor) + g_object_unref (dir->monitor); + +- trash_dir_set_files (dir, NULL); ++ trash_dir_empty (dir); ++ ++ g_hash_table_unref (dir->items); + + g_object_unref (dir->directory); + g_object_unref (dir->topdir); +-- +2.53.0 + diff --git a/trash-Use-weak-refs-for-monitor-lifecycle.patch b/trash-Use-weak-refs-for-monitor-lifecycle.patch new file mode 100644 index 0000000..51850f0 --- /dev/null +++ b/trash-Use-weak-refs-for-monitor-lifecycle.patch @@ -0,0 +1,223 @@ +From b15490d80dc8044dd546c8f5fba171bacb92c49c Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 15:54:17 +0200 +Subject: [PATCH] trash: Use weak refs for monitor lifecycle + +Backport of commit 1080ca11. + +Adjusted context for target version. +--- + daemon/gvfsbackendtrash.c | 119 ++++++++++++++++++++++----------- + daemon/trashlib/trashwatcher.c | 6 +- + 2 files changed, 84 insertions(+), 41 deletions(-) + +diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c +index 1dcb44f..6d0af56 100644 +--- a/daemon/gvfsbackendtrash.c ++++ b/daemon/gvfsbackendtrash.c +@@ -33,8 +33,8 @@ struct OPAQUE_TYPE__GVfsBackendTrash + { + GVfsBackend parent_instance; + +- GVfsMonitor *file_monitor; +- GVfsMonitor *dir_monitor; ++ GWeakRef file_monitor; ++ GWeakRef dir_monitor; + + GMainContext *worker_context; + GMainLoop *worker_loop; +@@ -111,15 +111,32 @@ trash_backend_worker_thread_queue (GVfsBackendTrash *backend, + } + + static gboolean +-watch_func (gpointer user_data) ++sync_watch_state_func (gpointer user_data) + { + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (user_data); ++ GVfsMonitor *file_monitor; ++ GVfsMonitor *dir_monitor; + +- trash_watcher_watch (backend->watcher); ++ file_monitor = g_weak_ref_get (&backend->file_monitor); ++ dir_monitor = g_weak_ref_get (&backend->dir_monitor); ++ ++ if (file_monitor != NULL || dir_monitor != NULL) ++ trash_watcher_watch (backend->watcher); ++ else ++ trash_watcher_unwatch (backend->watcher); ++ ++ g_clear_object (&file_monitor); ++ g_clear_object (&dir_monitor); + + return G_SOURCE_REMOVE; + } + ++static void ++trash_backend_queue_watch_state_sync (GVfsBackendTrash *backend) ++{ ++ trash_backend_worker_thread_queue (backend, sync_watch_state_func); ++} ++ + static gboolean + rescan_func (gpointer user_data) + { +@@ -142,46 +159,62 @@ is_root (const char *filename) + return (filename[0] == '/' && filename[1] == '\0'); + } + ++static void ++trash_backend_file_monitor_destroyed (gpointer data, ++ GObject *where_the_object_was) ++{ ++ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (data); ++ ++ trash_backend_queue_watch_state_sync (backend); ++} ++ ++static void ++trash_backend_dir_monitor_destroyed (gpointer data, ++ GObject *where_the_object_was) ++{ ++ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (data); ++ ++ trash_backend_queue_watch_state_sync (backend); ++} ++ + static GVfsMonitor * + trash_backend_get_file_monitor (GVfsBackendTrash *backend, + gboolean create) + { +- if (backend->file_monitor == NULL && create == FALSE) +- return NULL; ++ GVfsMonitor *monitor; + +- else if (backend->file_monitor == NULL) +- { +- /* 'create' is only ever set in the main thread, so we will have +- * no possibility here for creating more than one new monitor. +- */ +- if (backend->dir_monitor == NULL) +- trash_backend_worker_thread_queue (backend, watch_func); ++ monitor = g_weak_ref_get (&backend->file_monitor); ++ if (monitor != NULL || create == FALSE) ++ return monitor; + +- backend->file_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); +- } ++ monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); ++ g_weak_ref_set (&backend->file_monitor, monitor); ++ g_object_weak_ref (G_OBJECT (monitor), ++ trash_backend_file_monitor_destroyed, backend); ++ ++ trash_backend_queue_watch_state_sync (backend); + +- return g_object_ref (backend->file_monitor); ++ return monitor; + } + + static GVfsMonitor * + trash_backend_get_dir_monitor (GVfsBackendTrash *backend, + gboolean create) + { +- if (backend->dir_monitor == NULL && create == FALSE) +- return NULL; ++ GVfsMonitor *monitor; + +- else if (backend->dir_monitor == NULL) +- { +- /* 'create' is only ever set in the main thread, so we will have +- * no possibility here for creating more than one new monitor. +- */ +- if (backend->file_monitor == NULL) +- trash_backend_worker_thread_queue (backend, watch_func); ++ monitor = g_weak_ref_get (&backend->dir_monitor); ++ if (monitor != NULL || create == FALSE) ++ return monitor; + +- backend->dir_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); +- } ++ monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); ++ g_weak_ref_set (&backend->dir_monitor, monitor); ++ g_object_weak_ref (G_OBJECT (monitor), ++ trash_backend_dir_monitor_destroyed, backend); ++ ++ trash_backend_queue_watch_state_sync (backend); + +- return g_object_ref (backend->dir_monitor); ++ return monitor; + } + + static void +@@ -802,8 +835,8 @@ trash_backend_mount (GVfsBackend *vfs_backend, + { + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend); + +- backend->file_monitor = NULL; +- backend->dir_monitor = NULL; ++ g_weak_ref_init (&backend->file_monitor, NULL); ++ g_weak_ref_init (&backend->dir_monitor, NULL); + + backend->worker_context = g_main_context_new (); + backend->worker_thread = g_thread_new ("Trash Worker Thread", +@@ -976,17 +1009,25 @@ static void + trash_backend_finalize (GObject *object) + { + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (object); ++ GVfsMonitor *monitor; + +- /* get rid of these first to stop a flood of event notifications +- * from being emitted while we're tearing down the TrashWatcher +- */ +- if (backend->file_monitor) +- g_object_unref (backend->file_monitor); +- backend->file_monitor = NULL; ++ monitor = g_weak_ref_get (&backend->file_monitor); ++ if (monitor != NULL) ++ { ++ g_object_weak_unref (G_OBJECT (monitor), ++ trash_backend_file_monitor_destroyed, backend); ++ g_object_unref (monitor); ++ } ++ g_weak_ref_clear (&backend->file_monitor); + +- if (backend->dir_monitor) +- g_object_unref (backend->dir_monitor); +- backend->dir_monitor = NULL; ++ monitor = g_weak_ref_get (&backend->dir_monitor); ++ if (monitor != NULL) ++ { ++ g_object_weak_unref (G_OBJECT (monitor), ++ trash_backend_dir_monitor_destroyed, backend); ++ g_object_unref (monitor); ++ } ++ g_weak_ref_clear (&backend->dir_monitor); + + G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize (object); + } +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index 1888bed..bd658e1 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -378,7 +378,8 @@ trash_watcher_watch (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + +- g_assert (!watcher->watching); ++ if (watcher->watching) ++ return; + + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_watch (watcher->homedir_trashdir); +@@ -403,7 +404,8 @@ trash_watcher_unwatch (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + +- g_assert (watcher->watching); ++ if (!watcher->watching) ++ return; + + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_unwatch (watcher->homedir_trashdir); +-- +2.53.0 +