mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 14:10:30 +00:00
32f55a74b0
The filetransfer protocol says to use application/vnd.portal.filetransfer, but I used application/vnd.portal.files when I implemented the protocol. Oops. This commit dds the correct mimetype, but we still support the old one to preserve interoperatibility with existing flatpaks using GTK 4.6. Fixes: #5182
588 lines
17 KiB
C
588 lines
17 KiB
C
/*
|
|
* Copyright (C) 2018 Matthias Clasen
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "gdkcontentformats.h"
|
|
#include "gdkcontentserializer.h"
|
|
#include "gdkcontentdeserializer.h"
|
|
|
|
#include <gio/gio.h>
|
|
|
|
#ifdef G_OS_UNIX
|
|
|
|
#include <gio/gunixfdlist.h>
|
|
|
|
#ifndef O_PATH
|
|
#define O_PATH 0
|
|
#endif
|
|
|
|
#ifndef O_CLOEXEC
|
|
#define O_CLOEXEC 0
|
|
#else
|
|
#define HAVE_O_CLOEXEC 1
|
|
#endif
|
|
|
|
#include "filetransferportalprivate.h"
|
|
|
|
static GDBusProxy *file_transfer_proxy = NULL;
|
|
|
|
typedef struct {
|
|
GTask *task;
|
|
const char **files;
|
|
int len;
|
|
int start;
|
|
} AddFileData;
|
|
|
|
static void add_files (GDBusProxy *proxy,
|
|
AddFileData *afd);
|
|
|
|
static void
|
|
add_files_done (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer data)
|
|
{
|
|
GDBusProxy *proxy = G_DBUS_PROXY (object);
|
|
AddFileData *afd = data;
|
|
GError *error = NULL;
|
|
GVariant *ret;
|
|
|
|
ret = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, NULL, result, &error);
|
|
if (ret == NULL)
|
|
{
|
|
g_task_return_error (afd->task, error);
|
|
g_object_unref (afd->task);
|
|
g_free (afd);
|
|
return;
|
|
}
|
|
|
|
g_variant_unref (ret);
|
|
|
|
if (afd->start >= afd->len)
|
|
{
|
|
g_task_return_boolean (afd->task, TRUE);
|
|
g_object_unref (afd->task);
|
|
g_free (afd);
|
|
return;
|
|
}
|
|
|
|
add_files (proxy, afd);
|
|
}
|
|
|
|
/* We call AddFiles in chunks of 16 to avoid running into
|
|
* the per-message fd limit of the bus.
|
|
*/
|
|
static void
|
|
add_files (GDBusProxy *proxy,
|
|
AddFileData *afd)
|
|
{
|
|
GUnixFDList *fd_list;
|
|
GVariantBuilder fds, options;
|
|
int i;
|
|
char *key;
|
|
|
|
g_variant_builder_init (&fds, G_VARIANT_TYPE ("ah"));
|
|
fd_list = g_unix_fd_list_new ();
|
|
|
|
for (i = 0; afd->files[afd->start + i]; i++)
|
|
{
|
|
int fd;
|
|
int fd_in;
|
|
GError *error = NULL;
|
|
|
|
if (i == 16)
|
|
break;
|
|
|
|
fd = open (afd->files[afd->start + i], O_PATH | O_CLOEXEC);
|
|
if (fd == -1)
|
|
{
|
|
g_task_return_new_error (afd->task, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to open %s", afd->files[afd->start + i]);
|
|
g_object_unref (afd->task);
|
|
g_free (afd);
|
|
g_object_unref (fd_list);
|
|
return;
|
|
}
|
|
|
|
#ifndef HAVE_O_CLOEXEC
|
|
fcntl (fd, F_SETFD, FD_CLOEXEC);
|
|
#endif
|
|
fd_in = g_unix_fd_list_append (fd_list, fd, &error);
|
|
close (fd);
|
|
|
|
if (fd_in == -1)
|
|
{
|
|
g_task_return_error (afd->task, error);
|
|
g_object_unref (afd->task);
|
|
g_free (afd);
|
|
g_object_unref (fd_list);
|
|
return;
|
|
}
|
|
|
|
g_variant_builder_add (&fds, "h", fd_in);
|
|
}
|
|
|
|
afd->start += 16;
|
|
|
|
key = (char *)g_object_get_data (G_OBJECT (afd->task), "key");
|
|
|
|
g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
|
|
g_dbus_proxy_call_with_unix_fd_list (proxy,
|
|
"AddFiles",
|
|
g_variant_new ("(saha{sv})", key, &fds, &options),
|
|
0, -1,
|
|
fd_list,
|
|
NULL,
|
|
add_files_done, afd);
|
|
|
|
g_object_unref (fd_list);
|
|
}
|
|
|
|
static void
|
|
start_session_done (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer data)
|
|
{
|
|
GDBusProxy *proxy = G_DBUS_PROXY (object);
|
|
AddFileData *afd = data;
|
|
GError *error = NULL;
|
|
GVariant *ret;
|
|
const char *key;
|
|
|
|
ret = g_dbus_proxy_call_finish (proxy, result, &error);
|
|
if (ret == NULL)
|
|
{
|
|
g_task_return_error (afd->task, error);
|
|
g_object_unref (afd->task);
|
|
g_free (afd);
|
|
return;
|
|
}
|
|
|
|
g_variant_get (ret, "(&s)", &key);
|
|
|
|
g_object_set_data_full (G_OBJECT (afd->task), "key", g_strdup (key), g_free);
|
|
|
|
g_variant_unref (ret);
|
|
|
|
add_files (proxy, afd);
|
|
}
|
|
|
|
void
|
|
file_transfer_portal_register_files (const char **files,
|
|
gboolean writable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer data)
|
|
{
|
|
GTask *task;
|
|
AddFileData *afd;
|
|
GVariantBuilder options;
|
|
|
|
task = g_task_new (NULL, NULL, callback, data);
|
|
|
|
if (file_transfer_proxy == NULL)
|
|
{
|
|
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"No portal found");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
afd = g_new (AddFileData, 1);
|
|
afd->task = task;
|
|
afd->files = files;
|
|
afd->len = g_strv_length ((char **)files);
|
|
afd->start = 0;
|
|
|
|
g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_add (&options, "{sv}", "writable", g_variant_new_boolean (writable));
|
|
g_variant_builder_add (&options, "{sv}", "autostop", g_variant_new_boolean (TRUE));
|
|
|
|
g_dbus_proxy_call (file_transfer_proxy, "StartTransfer",
|
|
g_variant_new ("(a{sv})", &options),
|
|
0, -1, NULL, start_session_done, afd);
|
|
}
|
|
|
|
gboolean
|
|
file_transfer_portal_register_files_finish (GAsyncResult *result,
|
|
char **key,
|
|
GError **error)
|
|
{
|
|
if (g_task_propagate_boolean (G_TASK (result), error))
|
|
{
|
|
*key = g_strdup (g_object_get_data (G_OBJECT (result), "key"));
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
retrieve_files_done (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer data)
|
|
{
|
|
GDBusProxy *proxy = G_DBUS_PROXY (object);
|
|
GTask *task = data;
|
|
GError *error = NULL;
|
|
GVariant *ret;
|
|
char **files;
|
|
|
|
ret = g_dbus_proxy_call_finish (proxy, result, &error);
|
|
if (ret == NULL)
|
|
{
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_variant_get (ret, "(^a&s)", &files);
|
|
|
|
g_object_set_data_full (G_OBJECT (task), "files", g_strdupv (files), (GDestroyNotify)g_strfreev);
|
|
|
|
g_variant_unref (ret);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
void
|
|
file_transfer_portal_retrieve_files (const char *key,
|
|
GAsyncReadyCallback callback,
|
|
gpointer data)
|
|
{
|
|
GTask *task;
|
|
GVariantBuilder options;
|
|
|
|
task = g_task_new (NULL, NULL, callback, data);
|
|
|
|
if (file_transfer_proxy == NULL)
|
|
{
|
|
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"No portal found");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
|
|
g_dbus_proxy_call (file_transfer_proxy,
|
|
"RetrieveFiles",
|
|
g_variant_new ("(sa{sv})", key, &options),
|
|
0, -1, NULL,
|
|
retrieve_files_done, task);
|
|
}
|
|
|
|
gboolean
|
|
file_transfer_portal_retrieve_files_finish (GAsyncResult *result,
|
|
char ***files,
|
|
GError **error)
|
|
{
|
|
if (g_task_propagate_boolean (G_TASK (result), error))
|
|
{
|
|
*files = g_strdupv (g_object_get_data (G_OBJECT (result), "files"));
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* serializer */
|
|
|
|
static void
|
|
file_serializer_finish (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer serializer)
|
|
{
|
|
GOutputStream *stream = G_OUTPUT_STREAM (source);
|
|
GError *error = NULL;
|
|
|
|
if (!g_output_stream_write_all_finish (stream, result, NULL, &error))
|
|
gdk_content_serializer_return_error (serializer, error);
|
|
else
|
|
gdk_content_serializer_return_success (serializer);
|
|
}
|
|
|
|
static void
|
|
portal_ready (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer serializer)
|
|
{
|
|
GError *error = NULL;
|
|
char *key;
|
|
|
|
if (!file_transfer_portal_register_files_finish (result, &key, &error))
|
|
{
|
|
gdk_content_serializer_return_error (serializer, error);
|
|
return;
|
|
}
|
|
|
|
g_output_stream_write_all_async (gdk_content_serializer_get_output_stream (serializer),
|
|
key,
|
|
strlen (key) + 1,
|
|
gdk_content_serializer_get_priority (serializer),
|
|
gdk_content_serializer_get_cancellable (serializer),
|
|
file_serializer_finish,
|
|
serializer);
|
|
gdk_content_serializer_set_task_data (serializer, key, g_free);
|
|
}
|
|
|
|
static void
|
|
portal_file_serializer (GdkContentSerializer *serializer)
|
|
{
|
|
GFile *file;
|
|
const GValue *value;
|
|
GPtrArray *files;
|
|
|
|
files = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
value = gdk_content_serializer_get_value (serializer);
|
|
|
|
if (G_VALUE_HOLDS (value, G_TYPE_FILE))
|
|
{
|
|
file = g_value_get_object (gdk_content_serializer_get_value (serializer));
|
|
if (file)
|
|
g_ptr_array_add (files, g_file_get_path (file));
|
|
g_ptr_array_add (files, NULL);
|
|
}
|
|
else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
|
|
{
|
|
GSList *l;
|
|
|
|
for (l = g_value_get_boxed (value); l; l = l->next)
|
|
g_ptr_array_add (files, g_file_get_path (l->data));
|
|
|
|
g_ptr_array_add (files, NULL);
|
|
}
|
|
|
|
/* this call doesn't copy the strings, so keep the array around until the registration is done */
|
|
file_transfer_portal_register_files ((const char **)files->pdata, TRUE, portal_ready, serializer);
|
|
gdk_content_serializer_set_task_data (serializer, files, (GDestroyNotify)g_ptr_array_unref);
|
|
}
|
|
|
|
/* deserializer */
|
|
|
|
static void
|
|
portal_finish (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer deserializer)
|
|
{
|
|
char **files = NULL;
|
|
GError *error = NULL;
|
|
GValue *value;
|
|
|
|
if (!file_transfer_portal_retrieve_files_finish (result, &files, &error))
|
|
{
|
|
gdk_content_deserializer_return_error (deserializer, error);
|
|
return;
|
|
}
|
|
|
|
value = gdk_content_deserializer_get_value (deserializer);
|
|
if (G_VALUE_HOLDS (value, G_TYPE_FILE))
|
|
{
|
|
if (files[0] != NULL)
|
|
g_value_take_object (value, g_file_new_for_path (files[0]));
|
|
}
|
|
else
|
|
{
|
|
GSList *l = NULL;
|
|
gsize i;
|
|
|
|
for (i = 0; files[i] != NULL; i++)
|
|
l = g_slist_prepend (l, g_file_new_for_path (files[i]));
|
|
g_value_take_boxed (value, g_slist_reverse (l));
|
|
}
|
|
g_strfreev (files);
|
|
|
|
gdk_content_deserializer_return_success (deserializer);
|
|
}
|
|
|
|
static void
|
|
portal_file_deserializer_finish (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer deserializer)
|
|
{
|
|
GOutputStream *stream = G_OUTPUT_STREAM (source);
|
|
GError *error = NULL;
|
|
gssize written;
|
|
char *key;
|
|
|
|
written = g_output_stream_splice_finish (stream, result, &error);
|
|
if (written < 0)
|
|
{
|
|
gdk_content_deserializer_return_error (deserializer, error);
|
|
return;
|
|
}
|
|
|
|
/* write terminating NULL */
|
|
if (!g_output_stream_write (stream, "", 1, NULL, &error))
|
|
{
|
|
gdk_content_deserializer_return_error (deserializer, error);
|
|
return;
|
|
}
|
|
|
|
key = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (stream));
|
|
if (key == NULL)
|
|
{
|
|
GError *gerror = g_error_new (G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"Could not convert data from %s to %s",
|
|
gdk_content_deserializer_get_mime_type (deserializer),
|
|
g_type_name (gdk_content_deserializer_get_gtype (deserializer)));
|
|
gdk_content_deserializer_return_error (deserializer, gerror);
|
|
return;
|
|
}
|
|
|
|
file_transfer_portal_retrieve_files (key, portal_finish, deserializer);
|
|
gdk_content_deserializer_set_task_data (deserializer, key, g_free);
|
|
}
|
|
|
|
static void
|
|
portal_file_deserializer (GdkContentDeserializer *deserializer)
|
|
{
|
|
GOutputStream *output;
|
|
|
|
output = g_memory_output_stream_new_resizable ();
|
|
|
|
g_output_stream_splice_async (output,
|
|
gdk_content_deserializer_get_input_stream (deserializer),
|
|
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
|
|
gdk_content_deserializer_get_priority (deserializer),
|
|
gdk_content_deserializer_get_cancellable (deserializer),
|
|
portal_file_deserializer_finish,
|
|
deserializer);
|
|
g_object_unref (output);
|
|
}
|
|
|
|
static void
|
|
connection_closed (GDBusConnection *connection,
|
|
gboolean remote_peer_vanished,
|
|
GError *error)
|
|
{
|
|
g_clear_object (&file_transfer_proxy);
|
|
}
|
|
|
|
static void
|
|
finish_registration (void)
|
|
{
|
|
gdk_content_register_serializer (G_TYPE_FILE,
|
|
"application/vnd.portal.filetransfer",
|
|
portal_file_serializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
gdk_content_register_serializer (GDK_TYPE_FILE_LIST,
|
|
"application/vnd.portal.filetransfer",
|
|
portal_file_serializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
gdk_content_register_deserializer ("application/vnd.portal.filetransfer",
|
|
GDK_TYPE_FILE_LIST,
|
|
portal_file_deserializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
gdk_content_register_deserializer ("application/vnd.portal.filetransfer",
|
|
G_TYPE_FILE,
|
|
portal_file_deserializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* FIXME: I missed up and used the wrong mime type here when
|
|
* I implemented my own protocol. Keep these around for a while
|
|
* so we can interoperate with existing flatpaks using GTK 4.6
|
|
*/
|
|
gdk_content_register_serializer (G_TYPE_FILE,
|
|
"application/vnd.portal.files",
|
|
portal_file_serializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
gdk_content_register_serializer (GDK_TYPE_FILE_LIST,
|
|
"application/vnd.portal.files",
|
|
portal_file_serializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
gdk_content_register_deserializer ("application/vnd.portal.files",
|
|
GDK_TYPE_FILE_LIST,
|
|
portal_file_deserializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
gdk_content_register_deserializer ("application/vnd.portal.files",
|
|
G_TYPE_FILE,
|
|
portal_file_deserializer,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* Free the singleton when the connection closes, important for test */
|
|
g_signal_connect (g_dbus_proxy_get_connection (G_DBUS_PROXY (file_transfer_proxy)),
|
|
"closed", G_CALLBACK (connection_closed), NULL);
|
|
}
|
|
|
|
static gboolean
|
|
proxy_has_owner (GDBusProxy *proxy)
|
|
{
|
|
char *owner;
|
|
|
|
owner = g_dbus_proxy_get_name_owner (proxy);
|
|
if (owner)
|
|
{
|
|
g_free (owner);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
file_transfer_portal_register (void)
|
|
{
|
|
static gboolean called;
|
|
|
|
if (!called)
|
|
{
|
|
called = TRUE;
|
|
|
|
file_transfer_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
|
|
| G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
|
|
| G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
|
NULL,
|
|
"org.freedesktop.portal.Documents",
|
|
"/org/freedesktop/portal/documents",
|
|
"org.freedesktop.portal.FileTransfer",
|
|
NULL,
|
|
NULL);
|
|
|
|
if (file_transfer_proxy && !proxy_has_owner (file_transfer_proxy))
|
|
g_clear_object (&file_transfer_proxy);
|
|
|
|
if (file_transfer_proxy)
|
|
finish_registration ();
|
|
}
|
|
}
|
|
|
|
|
|
#endif /* G_OS_UNIX */
|