internal-place-file: Mount network:/// on ready request

The whole application freezes the first time `x-network-view:///` is
visited during a session (or after `killall gvfsd-network`).

This happens because GDaemonFile makes sync DBus calls to mount the
`network:///` location when we call `g_file_monitor_directory()`[0],
which is done by the view when the ready callback is invoked.

In order to avoid this, ensure the `network:///` location is mounted
before invoking the ready callback for the `x-network-view:///` file.

(This achieves a result which is similar to accessing `network:///`
directly, or any other location which is slow to mount: the location
is not changed until after the mount succeeds. I don't think this is
good UX, but it's an entirely different problem which is not specific
to the Network view at all.)

[0] More context on https://gitlab.gnome.org/GNOME/gvfs/-/issues/455
This commit is contained in:
António Fernandes 2024-05-26 18:08:07 +01:00
parent f0d250d0cc
commit 41c0c1f624

View file

@ -9,6 +9,7 @@
#include "nautilus-internal-place-file.h"
#include <glib/gi18n.h>
#include <gio/gio.h>
#include "nautilus-file-private.h"
#include "nautilus-scheme.h"
@ -18,6 +19,9 @@
struct _NautilusInternalPlaceFile
{
NautilusFile parent_instance;
GList *callbacks;
GCancellable *network_mount_cancellable;
};
G_DEFINE_TYPE (NautilusInternalPlaceFile, nautilus_internal_place_file, NAUTILUS_TYPE_FILE);
@ -36,13 +40,79 @@ real_monitor_remove (NautilusFile *file,
{
}
typedef struct
{
NautilusFileCallback callback;
gpointer callback_data;
} InternalPlaceFileCallback;
static void
network_mount_callback (GObject *source_object,
GAsyncResult *result,
gpointer data)
{
NautilusInternalPlaceFile *self = NAUTILUS_INTERNAL_PLACE_FILE (data);
g_autoptr (GError) error = NULL;
(void) g_file_mount_enclosing_volume_finish (G_FILE (source_object), result, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
return;
}
/* Clear cancellable and steal callbacks list because call_when_ready() may
* be called again inside the `for` loop. */
g_clear_object (&self->network_mount_cancellable);
GList *callbacks = g_steal_pointer (&self->callbacks);
for (GList *l = callbacks; l != NULL; l = l->next)
{
InternalPlaceFileCallback *callback = l->data;
(*callback->callback)(NAUTILUS_FILE (self), callback->callback_data);
}
g_list_free_full (callbacks, g_free);
}
static void
real_call_when_ready (NautilusFile *file,
NautilusFileAttributes file_attributes,
NautilusFileCallback callback,
gpointer callback_data)
{
if (callback != NULL)
NautilusInternalPlaceFile *self = NAUTILUS_INTERNAL_PLACE_FILE (file);
if (NAUTILUS_IS_NETWORK_DIRECTORY (file->details->directory))
{
/* WORKAROUND:
* We must ensure network:/// is mounted before calling it "ready",
* otherwise GDaemonFile makes sync D-Bus calls to mount network://
* when the view tries do add a monitor, blocking the main thread and
* freezing the UI.
* See: https://gitlab.gnome.org/GNOME/gvfs/-/issues/455
*/
if (callback != NULL)
{
InternalPlaceFileCallback data = {callback, callback_data};
self->callbacks = g_list_prepend (self->callbacks,
g_memdup2 (&data, sizeof (data)));
}
/* If there is a cancellable, we are already mounting */
if (self->network_mount_cancellable == NULL)
{
g_autoptr (GFile) network_backend = g_file_new_for_uri (SCHEME_NETWORK ":///");
self->network_mount_cancellable = g_cancellable_new ();
g_file_mount_enclosing_volume (network_backend,
G_MOUNT_MOUNT_NONE,
NULL,
self->network_mount_cancellable,
network_mount_callback, self);
}
}
else if (callback != NULL)
{
/* Internal place attributes are static, so its always ready. */
(*callback)(file, callback_data);
@ -54,6 +124,11 @@ real_cancel_call_when_ready (NautilusFile *file,
NautilusFileCallback callback,
gpointer callback_data)
{
NautilusInternalPlaceFile *self = NAUTILUS_INTERNAL_PLACE_FILE (file);
g_clear_list (&self->callbacks, g_free);
g_cancellable_cancel (self->network_mount_cancellable);
g_clear_object (&self->network_mount_cancellable);
}
static gboolean
@ -93,6 +168,18 @@ nautilus_internal_place_file_constructed (GObject *object)
file->details->file_info_is_up_to_date = TRUE;
}
static void
nautilus_internal_place_file_dispose (GObject *object)
{
NautilusInternalPlaceFile *self = NAUTILUS_INTERNAL_PLACE_FILE (object);
g_clear_list (&self->callbacks, g_free);
g_cancellable_cancel (self->network_mount_cancellable);
g_clear_object (&self->network_mount_cancellable);
G_OBJECT_CLASS (nautilus_internal_place_file_parent_class)->dispose (object);
}
static void
nautilus_internal_place_file_class_init (NautilusInternalPlaceFileClass *klass)
{
@ -101,6 +188,7 @@ nautilus_internal_place_file_class_init (NautilusInternalPlaceFileClass *klass)
/* We need to know the parent directory, which is a construction property.*/
object_class->constructed = nautilus_internal_place_file_constructed;
object_class->dispose = nautilus_internal_place_file_dispose;
file_class->default_file_type = G_FILE_TYPE_DIRECTORY;