/* * Copyright (C) 2024 António Fernandes * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "nautilus-network-directory.h" #include #include "nautilus-directory-private.h" #include "nautilus-file-private.h" #include "nautilus-file-utilities.h" #include "nautilus-internal-place-file.h" #include "nautilus-recent-servers.h" #include "nautilus-scheme.h" struct _NautilusNetworkDirectory { NautilusDirectory parent_slot; NautilusDirectory *computer_backend_directory; gboolean computer_backend_done_loading; NautilusDirectory *network_backend_directory; gboolean network_backend_done_loading; NautilusRecentServers *recent_servers; GList *recent_server_files; gboolean recent_servers_done_loading; GList /**/ *callback_list; }; G_DEFINE_FINAL_TYPE_WITH_CODE (NautilusNetworkDirectory, nautilus_network_directory, NAUTILUS_TYPE_DIRECTORY, nautilus_ensure_extension_points (); g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME, g_define_type_id, NAUTILUS_NETWORK_DIRECTORY_PROVIDER_NAME, 0)); typedef struct { NautilusNetworkDirectory *self; NautilusDirectoryCallback callback; gpointer callback_data; gboolean computer_backend_ready; gboolean network_backend_ready; } NetworkCallback; static gboolean mountable_file_is_remote (NautilusFile *file) { g_autoptr (GIcon) icon = nautilus_file_get_gicon (file, NAUTILUS_FILE_ICON_FLAGS_NONE); /* HACK: It would be nice to have a "mountable::remote" attribute or * something like that. Until then, rely on icon name to guess. */ if (G_IS_THEMED_ICON (icon)) { const gchar * const *names = g_themed_icon_get_names (G_THEMED_ICON (icon)); for (gsize i = 0; names[i] != NULL; i++) { if (strstr (names[i], "network") || strstr (names[i], "remote")) { return TRUE; } } } return FALSE; } static GList * get_remote_mountables (GList *mountable_files) { GList *remotes = NULL; for (GList *l = mountable_files; l != NULL; l = l->next) { NautilusFile *file = NAUTILUS_FILE (l->data); if (mountable_file_is_remote (file)) { remotes = g_list_prepend (remotes, g_object_ref (file)); } } return remotes; } static gboolean real_are_all_files_seen (NautilusDirectory *directory) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); return (nautilus_directory_are_all_files_seen (self->computer_backend_directory) && nautilus_directory_are_all_files_seen (self->network_backend_directory) && self->recent_servers_done_loading); } static gboolean real_contains_file (NautilusDirectory *directory, NautilusFile *file) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); if (nautilus_file_get_directory (file) == directory) { /* Recent server files are directly owned by NautilusNetworkDirectory. */ return TRUE; } if (nautilus_directory_contains_file (self->network_backend_directory, file)) { return TRUE; } if (nautilus_directory_contains_file (self->computer_backend_directory, file)) { return mountable_file_is_remote (file); } return FALSE; } static void on_backend_directory_done_loading (NautilusDirectory *backend_directory, gpointer callback_data) { NautilusNetworkDirectory *self = callback_data; if (backend_directory == self->computer_backend_directory) { self->computer_backend_done_loading = TRUE; } else if (backend_directory == self->network_backend_directory) { self->network_backend_done_loading = TRUE; } else { /* Called from on_recent_servers_loading_changed () */ g_assert (backend_directory == (NautilusDirectory *) self && self->recent_servers_done_loading); } if (self->computer_backend_done_loading && self->network_backend_done_loading && self->recent_servers_done_loading) { nautilus_directory_emit_done_loading (NAUTILUS_DIRECTORY (self)); } } static void real_force_reload (NautilusDirectory *directory) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); self->computer_backend_done_loading = FALSE; nautilus_directory_force_reload (self->computer_backend_directory); self->network_backend_done_loading = FALSE; nautilus_directory_force_reload (self->network_backend_directory); self->recent_servers_done_loading = FALSE; nautilus_recent_servers_force_reload (self->recent_servers); } static void on_backend_directory_ready (NautilusDirectory *backend_directory, GList *unused_parameter, gpointer callback_data) { NetworkCallback *network_callback = callback_data; NautilusNetworkDirectory *self = network_callback->self; if (backend_directory == self->computer_backend_directory) { network_callback->computer_backend_ready = TRUE; } else if (backend_directory == self->network_backend_directory) { network_callback->network_backend_ready = TRUE; } else { g_assert (backend_directory == (NautilusDirectory *) self && network_callback->self->recent_servers_done_loading); } if (network_callback->computer_backend_ready && network_callback->network_backend_ready && network_callback->self->recent_servers_done_loading) { g_autolist (NautilusFile) files = nautilus_directory_get_file_list (NAUTILUS_DIRECTORY (self)); /* Invoke ready callback */ (*network_callback->callback)(NAUTILUS_DIRECTORY (self), files, network_callback->callback_data); /* Remove it from pending callbacks list and free it */ self->callback_list = g_list_remove (self->callback_list, network_callback); g_free (network_callback); } } static void on_recent_servers_loading_changed (GObject *object, GParamSpec *pspec, gpointer user_data) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (user_data); NautilusRecentServers *recent_servers = NAUTILUS_RECENT_SERVERS (object); gboolean is_loading = nautilus_recent_servers_get_loading (recent_servers); self->recent_servers_done_loading = !is_loading; if (self->recent_servers_done_loading) { NautilusDirectory *self_as_directory = NAUTILUS_DIRECTORY (self); on_backend_directory_done_loading (self_as_directory, self); for (GList *l = self->callback_list; l != NULL; l = l->next) { on_backend_directory_ready (self_as_directory, NULL, l->data); } } } static void on_computer_backend_directory_files_added (NautilusNetworkDirectory *self, GList *added_files) { g_autolist (NautilusFile) files = get_remote_mountables (added_files); if (files == NULL) { return; } nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), files); } static void on_computer_backend_directory_files_changed (NautilusNetworkDirectory *self, GList *changed_files) { g_autolist (NautilusFile) files = get_remote_mountables (changed_files); if (files == NULL) { return; } nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), files); } static NautilusFile * get_recent_server_file (NautilusDirectory *directory, GFileInfo *server_info) { g_autofree char *uri = g_strconcat (SCHEME_NETWORK_VIEW ":///", g_file_info_get_name (server_info), NULL); g_autoptr (NautilusFile) file = nautilus_file_get_by_uri (uri); nautilus_file_update_info (file, server_info); return g_steal_pointer (&file); } static void on_recent_servers_added (NautilusNetworkDirectory *self, GList *servers) { NautilusDirectory *dir = NAUTILUS_DIRECTORY (self); g_autolist (NautilusFile) added_files = NULL; for (GList *l = servers; l != NULL; l = l->next) { GFileInfo *server_info = l->data; g_autoptr (NautilusFile) file = get_recent_server_file (dir, server_info); self->recent_server_files = g_list_prepend (self->recent_server_files, g_object_ref (file)); added_files = g_list_prepend (added_files, g_steal_pointer (&file)); } nautilus_directory_emit_files_added (dir, added_files); } static void on_recent_servers_changed (NautilusNetworkDirectory *self, GList *servers) { g_autolist (NautilusFile) changed_files = NULL; for (GList *l = servers; l != NULL; l = l->next) { GFileInfo *server_info = l->data; NautilusFile *file = nautilus_directory_find_file_by_name (NAUTILUS_DIRECTORY (self), g_file_info_get_name (server_info)); if (file == NULL) { g_critical ("Notified change on recent server whose random GUID name was not known yet"); continue; } nautilus_file_update_info (file, server_info); changed_files = g_list_prepend (changed_files, g_object_ref (file)); } nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), changed_files); } static void on_recent_servers_removed (NautilusNetworkDirectory *self, GList *servers) { g_autolist (NautilusFile) removed_files = NULL; for (GList *l = servers; l != NULL; l = l->next) { GFileInfo *server_info = l->data; NautilusFile *file = nautilus_directory_find_file_by_name (NAUTILUS_DIRECTORY (self), g_file_info_get_name (server_info)); if (file == NULL) { g_critical ("Notified removal of recent server whose random GUID name was not known yet"); continue; } nautilus_file_mark_gone (file); /* Steal file from self->recent_server_files */ GList *link = g_list_find (self->recent_server_files, file); self->recent_server_files = g_list_remove_link (self->recent_server_files, link); removed_files = g_list_concat (link, removed_files); } nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), removed_files); } static NetworkCallback * network_callback_find (NautilusNetworkDirectory *self, NautilusDirectoryCallback callback, gpointer callback_data) { for (GList *l = self->callback_list; l != NULL; l = l->next) { NetworkCallback *network_callback = l->data; if (network_callback->callback == callback && network_callback->callback_data == callback_data) { return network_callback; } } return NULL; } static void real_call_when_ready (NautilusDirectory *directory, NautilusAttributes attributes, NautilusDirectoryCallback callback, gpointer callback_data) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); NetworkCallback *network_callback; network_callback = network_callback_find (self, callback, callback_data); if (network_callback != NULL) { g_warning ("tried to add a new callback while an old one was pending"); return; } network_callback = g_new0 (NetworkCallback, 1); network_callback->self = self; network_callback->callback = callback; network_callback->callback_data = callback_data; self->callback_list = g_list_prepend (self->callback_list, network_callback); nautilus_directory_call_when_ready (self->computer_backend_directory, attributes, on_backend_directory_ready, network_callback); nautilus_directory_call_when_ready (self->network_backend_directory, attributes, on_backend_directory_ready, network_callback); } static void real_cancel_callback (NautilusDirectory *directory, NautilusDirectoryCallback callback, gpointer callback_data) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); NetworkCallback *network_callback; network_callback = network_callback_find (self, callback, callback_data); if (network_callback == NULL) { /* No warning needed, because nautilus_directory_cancel_callback() is * meant to be used unconditionally as cleanup. */ return; } if (!network_callback->computer_backend_ready) { nautilus_directory_cancel_callback (self->computer_backend_directory, on_backend_directory_ready, network_callback); } if (!network_callback->network_backend_ready) { nautilus_directory_cancel_callback (self->network_backend_directory, on_backend_directory_ready, network_callback); } self->callback_list = g_list_remove (self->callback_list, network_callback); g_free (network_callback); } static void real_file_monitor_add (NautilusDirectory *directory, gconstpointer client, gboolean monitor_hidden_files, NautilusAttributes attributes, NautilusDirectoryCallback callback, gpointer callback_data) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); nautilus_directory_file_monitor_add (self->computer_backend_directory, client, monitor_hidden_files, attributes, NULL, NULL); nautilus_directory_file_monitor_add (self->network_backend_directory, client, monitor_hidden_files, attributes, NULL, NULL); if (callback != NULL) { g_autolist (NautilusFile) files = nautilus_directory_get_file_list (directory); (*callback)(directory, files, callback_data); } } static void real_file_monitor_remove (NautilusDirectory *directory, gconstpointer client) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); nautilus_directory_file_monitor_remove (self->computer_backend_directory, client); nautilus_directory_file_monitor_remove (self->network_backend_directory, client); } static GList * real_get_file_list (NautilusDirectory *directory) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); g_autolist (NautilusFile) computer_list = nautilus_directory_get_file_list (self->computer_backend_directory); g_autolist (NautilusFile) network_list = nautilus_directory_get_file_list (self->network_backend_directory); g_autolist (NautilusFile) recent_servers = nautilus_file_list_copy (self->recent_server_files); return g_list_concat (get_remote_mountables (computer_list), g_list_concat (g_steal_pointer (&recent_servers), g_steal_pointer (&network_list))); } static gboolean real_is_not_empty (NautilusDirectory *directory) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (directory); if (self->recent_server_files != NULL || nautilus_directory_is_not_empty (self->network_backend_directory)) { return TRUE; } if (nautilus_directory_is_not_empty (self->computer_backend_directory)) { g_autolist (NautilusFile) computer_list = nautilus_directory_get_file_list (self->computer_backend_directory); g_autolist (NautilusFile) remote_mountables = get_remote_mountables (computer_list); if (remote_mountables != NULL) { return TRUE; } } return FALSE; } static gboolean real_is_editable (NautilusDirectory *directory) { return FALSE; } static gboolean real_handles_location (GFile *location) { return g_file_has_uri_scheme (location, SCHEME_NETWORK_VIEW); } static NautilusFile * real_new_as_file (NautilusDirectory *directory) { return g_object_new (NAUTILUS_TYPE_INTERNAL_PLACE_FILE, "directory", directory, NULL); } static void nautilus_network_directory_dispose (GObject *object) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (object); g_clear_list (&self->recent_server_files, g_object_unref); g_clear_list (&self->callback_list, g_free); G_OBJECT_CLASS (nautilus_network_directory_parent_class)->dispose (object); } static void nautilus_network_directory_finalize (GObject *object) { NautilusNetworkDirectory *self = NAUTILUS_NETWORK_DIRECTORY (object); g_clear_object (&self->computer_backend_directory); g_clear_object (&self->network_backend_directory); g_clear_object (&self->recent_servers); G_OBJECT_CLASS (nautilus_network_directory_parent_class)->finalize (object); } static void nautilus_network_directory_init (NautilusNetworkDirectory *self) { self->computer_backend_directory = nautilus_directory_get_by_uri (SCHEME_COMPUTER ":///"); g_signal_connect_object (self->computer_backend_directory, "files-added", G_CALLBACK (on_computer_backend_directory_files_added), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->computer_backend_directory, "files-changed", G_CALLBACK (on_computer_backend_directory_files_changed), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->computer_backend_directory, "done-loading", G_CALLBACK (on_backend_directory_done_loading), self, G_CONNECT_DEFAULT); g_signal_connect_object (self->computer_backend_directory, "load-error", G_CALLBACK (nautilus_directory_emit_load_error), self, G_CONNECT_SWAPPED); self->network_backend_directory = nautilus_directory_get_by_uri (SCHEME_NETWORK ":///"); g_signal_connect_object (self->network_backend_directory, "files-added", G_CALLBACK (nautilus_directory_emit_files_added), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->network_backend_directory, "files-changed", G_CALLBACK (nautilus_directory_emit_files_changed), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->network_backend_directory, "done-loading", G_CALLBACK (on_backend_directory_done_loading), self, G_CONNECT_DEFAULT); g_signal_connect_object (self->network_backend_directory, "load-error", G_CALLBACK (nautilus_directory_emit_load_error), self, G_CONNECT_SWAPPED); self->recent_servers = nautilus_recent_servers_new (); g_signal_connect_object (self->recent_servers, "notify::loading", G_CALLBACK (on_recent_servers_loading_changed), self, G_CONNECT_DEFAULT); g_signal_connect_object (self->recent_servers, "added", G_CALLBACK (on_recent_servers_added), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->recent_servers, "changed", G_CALLBACK (on_recent_servers_changed), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->recent_servers, "removed", G_CALLBACK (on_recent_servers_removed), self, G_CONNECT_SWAPPED); } static void nautilus_network_directory_class_init (NautilusNetworkDirectoryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); NautilusDirectoryClass *directory_class = NAUTILUS_DIRECTORY_CLASS (klass); object_class->finalize = nautilus_network_directory_finalize; object_class->dispose = nautilus_network_directory_dispose; directory_class->are_all_files_seen = real_are_all_files_seen; directory_class->contains_file = real_contains_file; directory_class->force_reload = real_force_reload; directory_class->call_when_ready = real_call_when_ready; directory_class->cancel_callback = real_cancel_callback; directory_class->file_monitor_add = real_file_monitor_add; directory_class->file_monitor_remove = real_file_monitor_remove; directory_class->get_file_list = real_get_file_list; directory_class->is_not_empty = real_is_not_empty; directory_class->is_editable = real_is_editable; directory_class->handles_location = real_handles_location; directory_class->new_as_file = real_new_as_file; } NautilusNetworkDirectory * nautilus_network_directory_new (void) { g_autoptr (GFile) location = g_file_new_for_uri (SCHEME_NETWORK_VIEW ":///"); return g_object_new (NAUTILUS_TYPE_NETWORK_DIRECTORY, "location", location, NULL); }