[Spice-devel] [PATCH spice-gtk 1/2] Add webdav channel
Alon Levy
alevy at redhat.com
Mon Mar 17 00:54:05 PDT 2014
On 02/28/2014 02:16 PM, Marc-André Lureau wrote:
> From: Marc-André Lureau <marcandre.lureau at redhat.com>
>
> See spice-common for protocol details. phodav, a webdav server library,
> is imported thanks to a submodule, until this project has a stable API
> and releases.
>
> The webdav channel is reponsible for handling port events and
> multiplexing the request streams. Extra care has been made to avoid
> blocking and to enable some fairness between concurrent streams, however
> this has been particularly tricky and is likely to have some issues
> left.
>
> The webdav server is run in a seperate thread, using libsoup. The client
> communication is done via a local tcp socket, but protected to only
> accept local connection and with a pretty strong password.
>
> The home directory is exported for the remote to browse, which seems to
> be a sensible default atm.
I didn't understand everything (I'm confused by the soup part), but it
works, and looks good to me.
ACK series
> ---
> .gitmodules | 3 +
> autogen.sh | 2 +-
> configure.ac | 4 +
> doc/reference/spice-gtk-docs.xml | 1 +
> doc/reference/spice-gtk-sections.txt | 17 +
> doc/reference/spice-gtk.types | 4 +-
> gtk/Makefile.am | 9 +-
> gtk/channel-webdav.c | 733 +++++++++++++++++++++++++++++++++++
> gtk/channel-webdav.h | 68 ++++
> gtk/map-file | 1 +
> gtk/phodav | 1 +
> gtk/spice-channel.c | 6 +
> gtk/spice-client.h | 1 +
> gtk/spice-glib-sym-file | 1 +
> gtk/spice-session-priv.h | 3 +
> gtk/spice-session.c | 1 +
> spice-common | 2 +-
> 17 files changed, 852 insertions(+), 5 deletions(-)
> create mode 100644 gtk/channel-webdav.c
> create mode 100644 gtk/channel-webdav.h
> create mode 160000 gtk/phodav
>
> diff --git a/.gitmodules b/.gitmodules
> index 0c618ee..cfce54a 100644
> --- a/.gitmodules
> +++ b/.gitmodules
> @@ -1,3 +1,6 @@
> [submodule "spice-common"]
> path = spice-common
> url = ../spice-common
> +[submodule "gtk/phodav"]
> + path = gtk/phodav
> + url = git://git.gnome.org/phodav
> diff --git a/autogen.sh b/autogen.sh
> index 0c18272..d71be70 100755
> --- a/autogen.sh
> +++ b/autogen.sh
> @@ -6,6 +6,7 @@ srcdir=`dirname $0`
> test -z "$srcdir" && srcdir=.
>
> git submodule update --init --recursive
> +(cd "$srcdir/gtk/phodav/" && intltoolize -f)
>
> gtkdocize
> autoreconf -v --force --install
> @@ -15,4 +16,3 @@ if [ -z "$NOCONFIGURE" ]; then
> echo "Running configure with --enable-maintainer-mode --enable-gtk-doc --with-gtk=3.0 --enable-vala ${1+"$@"}"
> "$srcdir"/configure --enable-maintainer-mode --enable-gtk-doc --with-gtk=3.0 --enable-vala ${1+"$@"}
> fi
> -
> diff --git a/configure.ac b/configure.ac
> index 192d748..d30ec40 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -75,6 +75,8 @@ AC_CONFIG_SUBDIRS([spice-common])
> COMMON_CFLAGS='-I ${top_srcdir}/spice-common/ -I ${top_srcdir}/spice-common/spice-protocol/'
> AC_SUBST(COMMON_CFLAGS)
>
> +AC_CONFIG_SUBDIRS([gtk/phodav])
> +
> SPICE_GTK_MAJOR_VERSION=`echo $PACKAGE_VERSION | cut -d. -f1`
> SPICE_GTK_MINOR_VERSION=`echo $PACKAGE_VERSION | cut -d. -f2`
> SPICE_GTK_MICRO_VERSION=`echo $PACKAGE_VERSION | cut -d. -f3 | cut -d- -f1`
> @@ -267,6 +269,8 @@ PKG_CHECK_MODULES(GTHREAD, gthread-2.0 > 2.0.0)
> AC_SUBST(GTHREAD_CFLAGS)
> AC_SUBST(GTHREAD_LIBS)
>
> +PKG_CHECK_MODULES(SOUP, libsoup-2.4)
> +
> AC_ARG_WITH([audio],
> AS_HELP_STRING([--with-audio=@<:@gstreamer/pulse/auto/no@:>@], [Select audio backend @<:@default=auto@:>@]),
> [],
> diff --git a/doc/reference/spice-gtk-docs.xml b/doc/reference/spice-gtk-docs.xml
> index d2c1a2b..5faea74 100644
> --- a/doc/reference/spice-gtk-docs.xml
> +++ b/doc/reference/spice-gtk-docs.xml
> @@ -37,6 +37,7 @@
> <xi:include href="xml/channel-smartcard.xml"/>
> <xi:include href="xml/channel-usbredir.xml"/>
> <xi:include href="xml/channel-port.xml"/>
> + <xi:include href="xml/channel-webdav.xml"/>
> </chapter>
>
> <chapter>
> diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
> index 9232a23..caaa92c 100644
> --- a/doc/reference/spice-gtk-sections.txt
> +++ b/doc/reference/spice-gtk-sections.txt
> @@ -461,3 +461,20 @@ spice_uri_get_type
> <SUBSECTION Private>
> SpiceURIPrivate
> </SECTION>
> +
> +<SECTION>
> +<FILE>channel-webdav</FILE>
> +<TITLE>SpiceWebdavChannel</TITLE>
> +SpiceWebdavChannel
> +SpiceWebdavChannelClass
> +<SUBSECTION Standard>
> +SPICE_IS_WEBDAV_CHANNEL
> +SPICE_IS_WEBDAV_CHANNEL_CLASS
> +SPICE_TYPE_WEBDAV_CHANNEL
> +SPICE_WEBDAV_CHANNEL
> +SPICE_WEBDAV_CHANNEL_CLASS
> +SPICE_WEBDAV_CHANNEL_GET_CLASS
> +spice_webdav_channel_get_type
> +<SUBSECTION Private>
> +SpiceWebdavChannelPrivate
> +</SECTION>
> diff --git a/doc/reference/spice-gtk.types b/doc/reference/spice-gtk.types
> index 2f52845..db0374a 100644
> --- a/doc/reference/spice-gtk.types
> +++ b/doc/reference/spice-gtk.types
> @@ -13,6 +13,7 @@
> #include "channel-record.h"
> #include "channel-smartcard.h"
> #include "channel-usbredir.h"
> +#include "channel-webdav.h"
> #include "spice-gtk-session.h"
> #include "spice-widget.h"
> #include "spice-grabsequence.h"
> @@ -42,4 +43,5 @@ spice_usbredir_channel_get_type
> spice_usb_device_get_type
> spice_usb_device_manager_get_type
> spice_usb_device_widget_get_type
> -spice_port_channel_get_type
> \ No newline at end of file
> +spice_port_channel_get_type
> +spice_webdav_channel_get_type
> \ No newline at end of file
> diff --git a/gtk/Makefile.am b/gtk/Makefile.am
> index 8a16120..ebb00b8 100644
> --- a/gtk/Makefile.am
> +++ b/gtk/Makefile.am
> @@ -1,6 +1,6 @@
> NULL =
>
> -SUBDIRS =
> +SUBDIRS = phodav
>
> if WITH_CONTROLLER
> SUBDIRS += controller
> @@ -96,6 +96,7 @@ SPICE_COMMON_CPPFLAGS = \
> $(SMARTCARD_CFLAGS) \
> $(USBREDIR_CFLAGS) \
> $(GUDEV_CFLAGS) \
> + $(SOUP_CFLAGS) \
> $(NULL)
>
> AM_CPPFLAGS = \
> @@ -185,8 +186,9 @@ libspice_client_glib_2_0_la_LDFLAGS = \
> libspice_client_glib_2_0_la_LIBADD = \
> $(top_builddir)/spice-common/common/libspice-common.la \
> $(top_builddir)/spice-common/common/libspice-common-client.la \
> + phodav/libphodav.la \
> $(GLIB2_LIBS) \
> - $(GIO_LIBS) \
> + $(SOUP_LIBS) \
> $(GOBJECT2_LIBS) \
> $(CELT051_LIBS) \
> $(OPUS_LIBS) \
> @@ -236,6 +238,7 @@ libspice_client_glib_2_0_la_SOURCES = \
> gio-coroutine.h \
> \
> channel-base.c \
> + channel-webdav.c \
> channel-cursor.c \
> channel-display.c \
> channel-display-priv.h \
> @@ -303,6 +306,7 @@ libspice_client_glibinclude_HEADERS = \
> channel-record.h \
> channel-smartcard.h \
> channel-usbredir.h \
> + channel-webdav.h \
> usb-device-manager.h \
> smartcard-manager.h \
> $(NULL)
> @@ -598,6 +602,7 @@ glib_introspection_files = \
> spice-glib-enums.c \
> spice-option.c \
> spice-util.c \
> + channel-webdav.c \
> channel-cursor.c \
> channel-display.c \
> channel-inputs.c \
> diff --git a/gtk/channel-webdav.c b/gtk/channel-webdav.c
> new file mode 100644
> index 0000000..28760f5
> --- /dev/null
> +++ b/gtk/channel-webdav.c
> @@ -0,0 +1,733 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2013 Red Hat, Inc.
> +
> + 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.1 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 "spice-client.h"
> +#include "spice-common.h"
> +#include "spice-channel-priv.h"
> +#include "spice-session-priv.h"
> +#include "spice-marshal.h"
> +#include "glib-compat.h"
> +#include "vmcstream.h"
> +
> +static PhodavServer* phodav_server_get(SpiceSession *session, gint *port);
> +
> +/**
> + * SECTION:channel-webdav
> + * @short_description: exports a directory
> + * @title: WebDAV Channel
> + * @section_id:
> + * @see_also: #SpiceChannel
> + * @stability: Stable
> + * @include: channel-webdav.h
> + *
> + * The "webdav" channel exports a directory to the guest for file
> + * manipulation (read/write/copy etc). The underlying protocol is
> + * implemented using WebDAV (RFC 4918)
> + *
> + * Since: 0.24
> + */
> +
> +#define SPICE_WEBDAV_CHANNEL_GET_PRIVATE(obj) \
> + (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelPrivate))
> +
> +typedef struct _OutputQueue OutputQueue;
> +
> +struct _SpiceWebdavChannelPrivate {
> + SpiceVmcStream *stream;
> + GCancellable *cancellable;
> + GHashTable *clients;
> + OutputQueue *queue;
> +
> + gboolean demuxing;
> + struct _demux {
> + gint64 client;
> + guint16 size;
> + guint8 *buf;
> + } demux;
> +};
> +
> +G_DEFINE_TYPE(SpiceWebdavChannel, spice_webdav_channel, SPICE_TYPE_PORT_CHANNEL)
> +
> +static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
> +
> +struct _OutputQueue {
> + GOutputStream *output;
> + gboolean flushing;
> + guint idle_id;
> + GQueue *queue;
> +};
> +
> +typedef struct _OutputQueueElem {
> + OutputQueue *queue;
> + const guint8 *buf;
> + gsize size;
> + GFunc cb;
Perhaps comment / change name to reflect purpose - callback after data
has been sent (g_output_queue_flush_async)
> + gpointer user_data;
> +} OutputQueueElem;
> +
> +static OutputQueue* output_queue_new(GOutputStream *output)
> +{
> + OutputQueue *queue = g_new0(OutputQueue, 1);
> +
> + queue->output = g_object_ref(output);
> + queue->queue = g_queue_new();
> +
> + return queue;
> +}
> +
> +static void output_queue_free(OutputQueue *queue)
> +{
> + g_warn_if_fail(g_queue_get_length(queue->queue) == 0);
> + g_warn_if_fail(!queue->flushing);
> + g_warn_if_fail(!queue->idle_id);
Warning on idle_id == 0 but you check for it to be non zero below -
should the warning be removed?
> +
> + g_queue_free_full(queue->queue, g_free);
> + g_clear_object(&queue->output);
> + if (queue->idle_id)
> + g_source_remove(queue->idle_id);
> + g_free(queue);
> +}
> +
> +static gboolean output_queue_idle(gpointer user_data);
> +
> +static void output_queue_flush_cb(GObject *source_object,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + GError *error = NULL;
> + OutputQueueElem *e = user_data;
> + OutputQueue *q = e->queue;
> +
> + q->flushing = FALSE;
> + g_output_stream_flush_finish(G_OUTPUT_STREAM(source_object),
> + res, &error);
> + if (error)
> + g_warning("error: %s", error->message);
> +
> + g_clear_error(&error);
> +
> + if (!q->idle_id)
> + q->idle_id = g_idle_add(output_queue_idle, q);
> +
> + g_free(e);
> +}
> +
> +static gboolean output_queue_idle(gpointer user_data)
> +{
> + OutputQueue *q = user_data;
> + OutputQueueElem *e;
> + GError *error = NULL;
> +
> + if (q->flushing) {
> + q->idle_id = 0;
> + return FALSE;
> + }
> +
> + e = g_queue_pop_head(q->queue);
> + if (!e) {
> + q->idle_id = 0;
> + return FALSE;
> + }
> +
> + g_output_stream_write_all(q->output, e->buf, e->size, NULL, NULL, &error);
> + if (error)
> + goto err;
> + else if (e->cb)
> + e->cb(q, e->user_data);
> +
> + q->flushing = TRUE;
> + g_output_stream_flush_async(q->output, G_PRIORITY_DEFAULT, NULL, output_queue_flush_cb, e);
> +
> + return TRUE;
> +
> +err:
> + g_warning("error: %s", error->message);
> + g_clear_error(&error);
> +
> + q->idle_id = 0;
> + return FALSE;
> +}
> +
> +static void output_queue_push(OutputQueue *q, const guint8 *buf, gsize size,
> + GFunc pushed_cb, gpointer user_data)
> +{
> + OutputQueueElem *e = g_new(OutputQueueElem, 1);
> +
> + e->buf = buf;
> + e->size = size;
> + e->cb = pushed_cb;
> + e->user_data = user_data;
> + e->queue = q;
> + g_queue_push_tail(q->queue, e);
> +
> + if (!q->idle_id && !q->flushing)
> + q->idle_id = g_idle_add(output_queue_idle, q);
> +}
> +
> +typedef struct Client
> +{
> + guint refs;
> + SpiceWebdavChannel *self;
> + GSocketConnection *conn;
> + OutputQueue *output;
> + gint64 id;
> + GCancellable *cancellable;
> +
> + struct _mux {
> + gint64 id;
> + guint16 size;
> + guint8 *buf;
> + } mux;
> +} Client;
> +
> +static void
> +client_unref(Client *client)
> +{
> + if (--client->refs > 0)
> + return;
> +
> + g_free(client->mux.buf);
> + output_queue_free(client->output);
> +
> + g_object_unref(client->conn);
> + g_object_unref(client->cancellable);
> +
> + g_free(client);
> +}
> +
> +static Client *
> +client_ref(Client *client)
> +{
> + client->refs++;
> + return client;
> +}
> +
> +static void client_start_read(SpiceWebdavChannel *self, Client *client);
> +
> +static void remove_client(SpiceWebdavChannel *self, Client *client)
> +{
> + SpiceWebdavChannelPrivate *c;
> +
> + if (g_cancellable_is_cancelled(client->cancellable))
> + return;
> +
> + g_cancellable_cancel(client->cancellable);
> +
> + c = self->priv;
> + g_hash_table_remove(c->clients, &client->id);
> +}
> +
> +static void mux_pushed_cb(OutputQueue *q, gpointer user_data)
> +{
> + Client *client = user_data;
> +
> + if (client->mux.size == 0) {
> + remove_client(client->self, client);
> + } else {
> + client_start_read(client->self, client);
> + }
> +
> + client_unref(client);
> +}
> +
> +static void server_reply_cb(GObject *source_object,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + Client *client = user_data;
> + SpiceWebdavChannel *self = client->self;
> + SpiceWebdavChannelPrivate *c = self->priv;
> + GError *err = NULL;
> + gssize size;
> +
> + size = g_input_stream_read_finish(G_INPUT_STREAM(source_object), res, &err);
> + if (err || g_cancellable_is_cancelled(client->cancellable))
> + goto end;
> +
> + g_return_if_fail(size <= G_MAXUINT16);
> + g_return_if_fail(size >= 0);
> + client->mux.size = size;
> +
> + output_queue_push(c->queue, (guint8 *)&client->mux.id, sizeof(gint64), NULL, NULL);
> + client->mux.size = GUINT16_TO_LE(client->mux.size);
> + output_queue_push(c->queue, (guint8 *)&client->mux.size, sizeof(guint16), NULL, NULL);
> + output_queue_push(c->queue, (guint8 *)client->mux.buf, size, (GFunc)mux_pushed_cb, client);
> +
> + return;
> +
> +end:
> + if (err) {
> + if (!g_cancellable_is_cancelled(client->cancellable))
> + g_warning("read error: %s", err->message);
> + remove_client(self, client);
> + g_clear_error(&err);
> + }
> +
> + client_unref(client);
> +}
> +
> +static void client_start_read(SpiceWebdavChannel *self, Client *client)
> +{
> + GInputStream *input;
> +
> + input = g_io_stream_get_input_stream(G_IO_STREAM(client->conn));
> + g_input_stream_read_async(input, client->mux.buf, G_MAXUINT16,
> + G_PRIORITY_DEFAULT, client->cancellable, server_reply_cb,
> + client_ref(client));
> +}
> +
> +static void start_demux(SpiceWebdavChannel *self);
> +
> +static void pushed_client_cb(OutputQueue *q, gpointer user_data)
> +{
> + Client *client = user_data;
> + SpiceWebdavChannel *self = client->self;
> + SpiceWebdavChannelPrivate *c = self->priv;
> +
> + c->demuxing = FALSE;
> + start_demux(self);
> +}
> +
> +static void demux_to_client(SpiceWebdavChannel *self,
> + Client *client)
> +{
> + SpiceWebdavChannelPrivate *c = self->priv;
> + gssize size = c->demux.size;
> +
> + CHANNEL_DEBUG(self, "pushing %ld to client %p", size, client);
> +
> + if (size != 0) {
> + output_queue_push(client->output, (guint8 *)c->demux.buf, size,
> + (GFunc)pushed_client_cb, client);
> + } else {
> + remove_client(self, client);
> + c->demuxing = FALSE;
> + start_demux(self);
> + }
> +}
> +
> +static void magic_written(GObject *source_object,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + Client *client = user_data;
> + SpiceWebdavChannel *self = client->self;
> + SpiceWebdavChannelPrivate *c = self->priv;
> + gssize bytes_written;
> + GError *err = NULL;
> + SpiceSession *session;
> +
> + session = spice_channel_get_session(SPICE_CHANNEL(self));
> + bytes_written = g_output_stream_write_finish(G_OUTPUT_STREAM(source_object),
> + res, &err);
> +
> + if (err || bytes_written != sizeof(session->priv->webdav_magic))
> + goto error;
> +
> + client_start_read(self, client);
> + g_hash_table_insert(c->clients, &client->id, client);
> +
> + demux_to_client(self, client);
> +
> + return;
> +
> +error:
> + if (err) {
> + g_critical("socket creation failed %s", err->message);
> + g_clear_error(&err);
> + }
> + if (client) {
> + client_unref(client);
> + }
> +}
> +
> +static void client_connected(GObject *source_object,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + SpiceWebdavChannel *self = user_data;
> + SpiceWebdavChannelPrivate *c = self->priv;
> + GSocketClient *sclient = G_SOCKET_CLIENT(source_object);
> + GError *err = NULL;
> + GSocketConnection *conn;
> + SpiceSession *session;
> + Client *client = NULL;
> + GOutputStream *output;
> +
> + session = spice_channel_get_session(SPICE_CHANNEL(self));
> +
> + conn = g_socket_client_connect_to_host_finish(sclient, res, &err);
> + g_object_unref(sclient);
> + if (err)
> + goto error;
> +
> + client = g_new0(Client, 1);
> + client->refs = 1;
> + client->id = c->demux.client;
> + client->self = self;
> + client->conn = conn;
> + client->mux.id = GINT64_TO_LE(client->id);
> + client->mux.buf = g_malloc(G_MAXUINT16);
Would be nice to use an actual constant for this instead of relying on
G_MAXUINT16 here and above.
> + client->cancellable = g_cancellable_new();
> +
> + output = g_buffered_output_stream_new(g_io_stream_get_output_stream(G_IO_STREAM(conn)));
> + client->output = output_queue_new(output);
> + g_object_unref(output);
> +
> + g_output_stream_write_async(g_io_stream_get_output_stream(G_IO_STREAM(conn)),
> + session->priv->webdav_magic, sizeof(session->priv->webdav_magic),
> + G_PRIORITY_DEFAULT, c->cancellable,
> + magic_written, client);
> + return;
> +
> +error:
> + if (err) {
> + g_critical("socket creation failed %s", err->message);
> + g_clear_error(&err);
> + }
> + if (client) {
> + client_unref(client);
> + }
> +}
> +
> +static void start_client(SpiceWebdavChannel *self)
> +{
> + SpiceWebdavChannelPrivate *c = self->priv;
> + GSocketClient *sclient;
> + gint davport = -1;
> + SpiceSession *session;
> +
> + session = spice_channel_get_session(SPICE_CHANNEL(self));
> + phodav_server_get(session, &davport);
> + CHANNEL_DEBUG(self, "starting client %" G_GINT64_FORMAT, c->demux.client);
> +
> + sclient = g_socket_client_new();
> + g_socket_client_connect_to_host_async(sclient, "localhost", davport,
> + c->cancellable, client_connected, self);
> +}
> +
> +static void data_read_cb(GObject *source_object,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + SpiceWebdavChannel *self = user_data;
> + SpiceWebdavChannelPrivate *c;
> + Client *client;
> + GError *error = NULL;
> + gssize size;
> +
> + size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
> + if (error) {
> + g_warning("error: %s", error->message);
> + g_clear_error(&error);
> + return;
> + }
> +
> + c = self->priv;
> + g_return_if_fail(size == c->demux.size);
> +
> + client = g_hash_table_lookup(c->clients, &c->demux.client);
> +
> + if (client)
> + demux_to_client(self, client);
> + else
> + start_client(self);
> +}
> +
> +
> +static void size_read_cb(GObject *source_object,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + SpiceWebdavChannel *self = user_data;
> + SpiceWebdavChannelPrivate *c;
> + GInputStream *istream = G_INPUT_STREAM(source_object);
> + GError *error = NULL;
> + gssize size;
> +
> + size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
> + if (error || size != sizeof(guint16))
> + goto end;
> +
> + c = self->priv;
> + c->demux.size = GUINT16_FROM_LE(c->demux.size);
> + spice_vmc_input_stream_read_all_async(istream,
> + c->demux.buf, c->demux.size,
> + G_PRIORITY_DEFAULT, c->cancellable, data_read_cb, self);
> + return;
> +
> +end:
> + if (error) {
> + g_warning("error: %s", error->message);
> + g_clear_error(&error);
> + }
> +}
> +
> +static void client_read_cb(GObject *source_object,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
> + SpiceWebdavChannel *self = user_data;
> + SpiceWebdavChannelPrivate *c = self->priv;
> + GInputStream *istream = G_INPUT_STREAM(source_object);
> + GError *error = NULL;
> + gssize size;
> +
> + size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
> + if (error || size != sizeof(gint64))
> + goto end;
> +
> + c->demux.client = GINT64_FROM_LE(c->demux.client);
> + spice_vmc_input_stream_read_all_async(istream,
> + &c->demux.size, sizeof(guint16),
> + G_PRIORITY_DEFAULT, c->cancellable, size_read_cb, self);
> + return;
> +
> +end:
> + if (error) {
> + g_warning("error: %s", error->message);
> + g_clear_error(&error);
> + }
> +}
> +
> +static void start_demux(SpiceWebdavChannel *self)
> +{
> + SpiceWebdavChannelPrivate *c = self->priv;
> + GInputStream *istream = g_io_stream_get_input_stream(G_IO_STREAM(c->stream));
> +
> + if (c->demuxing)
> + return;
> +
> + c->demuxing = TRUE;
> +
> + CHANNEL_DEBUG(self, "start demux");
> + spice_vmc_input_stream_read_all_async(istream, &c->demux.client, sizeof(gint64),
> + G_PRIORITY_DEFAULT, c->cancellable, client_read_cb, self);
> +
> +}
> +
> +static void port_event(SpiceWebdavChannel *self, gint event)
> +{
> + SpiceWebdavChannelPrivate *c = self->priv;
> +
> + CHANNEL_DEBUG(self, "port event:%d", event);
> + if (event == SPICE_PORT_EVENT_OPENED) {
> + g_cancellable_reset(c->cancellable);
> + start_demux(self);
> + } else {
> + g_cancellable_cancel(c->cancellable);
> + c->demuxing = FALSE;
> + g_hash_table_remove_all(c->clients);
> + }
> +}
> +
> +static void client_remove_unref(gpointer data)
> +{
> + Client *client = data;
> +
> + g_cancellable_cancel(client->cancellable);
> + client_unref(client);
> +}
> +
> +static void spice_webdav_channel_init(SpiceWebdavChannel *channel)
> +{
> + SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL_GET_PRIVATE(channel);
> +
> + channel->priv = c;
> + c->stream = spice_vmc_stream_new(SPICE_CHANNEL(channel));
> + c->cancellable = g_cancellable_new();
> + c->clients = g_hash_table_new_full(g_int64_hash, g_int64_equal,
> + NULL, client_remove_unref);
> + c->demux.buf = g_malloc(G_MAXUINT16);
> +
> + GOutputStream *ostream = g_io_stream_get_output_stream(G_IO_STREAM(c->stream));
> + c->queue = output_queue_new(ostream);
> +}
> +
> +static void spice_webdav_channel_finalize(GObject *object)
> +{
> + SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL(object)->priv;
> +
> + g_free(c->demux.buf);
> +
> + G_OBJECT_CLASS(spice_webdav_channel_parent_class)->finalize(object);
> +}
> +
> +static void spice_webdav_channel_dispose(GObject *object)
> +{
> + SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL(object)->priv;
> +
> + g_cancellable_cancel(c->cancellable);
> + g_clear_object(&c->cancellable);
> + g_clear_pointer(&c->queue, output_queue_free);
> + g_clear_object(&c->stream);
> + g_hash_table_unref(c->clients);
> +
> + G_OBJECT_CLASS(spice_webdav_channel_parent_class)->dispose(object);
> +}
> +
> +static void spice_webdav_channel_up(SpiceChannel *channel)
> +{
> + CHANNEL_DEBUG(channel, "up");
> +}
> +
> +static void spice_webdav_channel_class_init(SpiceWebdavChannelClass *klass)
> +{
> + GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
> + SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
> +
> + gobject_class->dispose = spice_webdav_channel_dispose;
> + gobject_class->finalize = spice_webdav_channel_finalize;
> + channel_class->handle_msg = spice_webdav_handle_msg;
> + channel_class->channel_up = spice_webdav_channel_up;
> +
> + g_signal_override_class_handler("port-event",
> + SPICE_TYPE_WEBDAV_CHANNEL,
> + G_CALLBACK(port_event));
> +
> + g_type_class_add_private(klass, sizeof(SpiceWebdavChannelPrivate));
> +}
> +
> +/* coroutine context */
> +static void webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *in)
> +{
> + SpiceWebdavChannel *self = SPICE_WEBDAV_CHANNEL(channel);
> + SpiceWebdavChannelPrivate *c = self->priv;
> + int size;
> + uint8_t *buf;
> +
> + buf = spice_msg_in_raw(in, &size);
> + CHANNEL_DEBUG(channel, "len:%d buf:%p", size, buf);
> +
> + spice_vmc_input_stream_co_data(
> + SPICE_VMC_INPUT_STREAM(g_io_stream_get_input_stream(G_IO_STREAM(c->stream))),
> + buf, size);
> +}
> +
> +
> +/* coroutine context */
> +static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
> +{
> + int type = spice_msg_in_type(msg);
> + SpiceChannelClass *parent_class;
> +
> + parent_class = SPICE_CHANNEL_CLASS(spice_webdav_channel_parent_class);
> +
> + if (type == SPICE_MSG_SPICEVMC_DATA)
> + webdav_handle_msg(channel, msg);
> + else if (parent_class->handle_msg)
> + parent_class->handle_msg(channel, msg);
> + else
> + g_return_if_reached();
> +}
> +
> +
> +
> +static void new_connection(SoupSocket *sock,
> + SoupSocket *new,
> + gpointer user_data)
> +{
> + SpiceSession *session = user_data;
> + SoupAddress *addr;
> + GSocketAddress *gaddr;
> + GInetAddress *iaddr;
> + guint port;
> + guint8 magic[16];
> + gsize nread;
> + gboolean success = FALSE;
> + SoupSocketIOStatus status;
> +
> + /* note: this is sync calls, since webdav server is in a seperate thread */
> + addr = soup_socket_get_remote_address(new);
> + gaddr = soup_address_get_gsockaddr(addr);
> + iaddr = g_inet_socket_address_get_address(G_INET_SOCKET_ADDRESS(gaddr));
> + port = g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(gaddr));
> +
> + SPICE_DEBUG("port %d %p", port, iaddr);
> + if (!g_inet_address_get_is_loopback(iaddr)) {
> + g_warn_if_reached();
> + goto end;
> + }
> +
> + g_object_set(new, "non-blocking", FALSE, NULL);
> + status = soup_socket_read(new, magic, sizeof(magic), &nread, NULL, NULL);
> + if (status != SOUP_SOCKET_OK) {
> + g_warning("bad initial socket read: %d", status);
> + goto end;
> + }
> + g_object_set(new, "non-blocking", TRUE, NULL);
> +
> + /* check we got the right magic */
> + if (memcmp(session->priv->webdav_magic, magic, sizeof(magic))) {
> + g_warn_if_reached();
> + goto end;
> + }
> +
> + success = TRUE;
> +
> +end:
> + if (!success) {
> + g_warn_if_reached();
> + soup_socket_disconnect(new);
> + g_signal_stop_emission_by_name(sock, "new_connection");
> + }
> + g_object_unref(gaddr);
> +}
> +
> +static PhodavServer* webdav_server_new(SpiceSession *session)
> +{
> + PhodavServer *dav;
> + SoupServer *server;
> + SoupSocket *listener;
> + int i;
> +
> + g_warn_if_fail(!session->priv->webdav);
> +
> + dav = phodav_server_new(0, g_get_user_special_dir(G_USER_DIRECTORY_PUBLIC_SHARE));
> + session->priv->webdav = dav;
> + for (i = 0; i < sizeof(session->priv->webdav_magic); i++)
> + session->priv->webdav_magic[i] = g_random_int_range(0, 255);
> +
> + server = phodav_server_get_soup_server(dav);
> + listener = soup_server_get_listener(server);
> + spice_g_signal_connect_object(listener, "new_connection",
> + G_CALLBACK(new_connection), session,
> + 0);
> +
> + return dav;
> +}
> +
> +static PhodavServer* phodav_server_get(SpiceSession *session, gint *port)
> +{
> + g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
> +
> + PhodavServer *self;
> + static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
> +
> + g_static_mutex_lock(&mutex);
> + self = session->priv->webdav;
> + if (self == NULL) {
> + self = webdav_server_new(session);
> + phodav_server_run(self);
> + }
> + g_static_mutex_unlock(&mutex);
> +
> + if (port)
> + *port = phodav_server_get_port(self);
> +
> + return self;
> +}
> diff --git a/gtk/channel-webdav.h b/gtk/channel-webdav.h
> new file mode 100644
> index 0000000..7940706
> --- /dev/null
> +++ b/gtk/channel-webdav.h
> @@ -0,0 +1,68 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2013 Red Hat, Inc.
> +
> + 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.1 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/>.
> +*/
> +#ifndef __SPICE_WEBDAV_CHANNEL_H__
> +#define __SPICE_WEBDAV_CHANNEL_H__
> +
> +#include <gio/gio.h>
> +#include "spice-client.h"
> +#include "channel-port.h"
> +
> +G_BEGIN_DECLS
> +
> +#define SPICE_TYPE_WEBDAV_CHANNEL (spice_webdav_channel_get_type())
> +#define SPICE_WEBDAV_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannel))
> +#define SPICE_WEBDAV_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelClass))
> +#define SPICE_IS_WEBDAV_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_WEBDAV_CHANNEL))
> +#define SPICE_IS_WEBDAV_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_WEBDAV_CHANNEL))
> +#define SPICE_WEBDAV_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelClass))
> +
> +typedef struct _SpiceWebdavChannel SpiceWebdavChannel;
> +typedef struct _SpiceWebdavChannelClass SpiceWebdavChannelClass;
> +typedef struct _SpiceWebdavChannelPrivate SpiceWebdavChannelPrivate;
> +
> +/**
> + * SpiceWebdavChannel:
> + *
> + * The #SpiceWebdavChannel struct is opaque and should not be accessed directly.
> + */
> +struct _SpiceWebdavChannel {
> + SpicePortChannel parent;
> +
> + /*< private >*/
> + SpiceWebdavChannelPrivate *priv;
> + /* Do not add fields to this struct */
> +};
> +
> +/**
> + * SpiceWebdavChannelClass:
> + * @parent_class: Parent class.
> + *
> + * Class structure for #SpiceWebdavChannel.
> + */
> +struct _SpiceWebdavChannelClass {
> + SpicePortChannelClass parent_class;
> +
> + /*< private >*/
> + /* Do not add fields to this struct */
> +};
> +
> +GType spice_webdav_channel_get_type(void);
> +
> +G_END_DECLS
> +
> +#endif /* __SPICE_WEBDAV_CHANNEL_H__ */
> diff --git a/gtk/map-file b/gtk/map-file
> index f98680c..90f14f1 100644
> --- a/gtk/map-file
> +++ b/gtk/map-file
> @@ -132,6 +132,7 @@ spice_uri_set_port;
> spice_uri_set_scheme;
> spice_uri_set_user;
> spice_uri_to_string;
> +spice_webdav_channel_get_type;
> local:
> *;
> };
> diff --git a/gtk/phodav b/gtk/phodav
> new file mode 160000
> index 0000000..46082ea
> --- /dev/null
> +++ b/gtk/phodav
> @@ -0,0 +1 @@
> +Subproject commit 46082ead2f214c6cbf9acc98fa7ed1c0d8436355
> diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
> index 632a286..a9c1076 100644
> --- a/gtk/spice-channel.c
> +++ b/gtk/spice-channel.c
> @@ -1878,6 +1878,7 @@ static const char *to_string[] = {
> [ SPICE_CHANNEL_SMARTCARD ] = "smartcard",
> [ SPICE_CHANNEL_USBREDIR ] = "usbredir",
> [ SPICE_CHANNEL_PORT ] = "port",
> + [ SPICE_CHANNEL_WEBDAV ] = "webdav",
> };
>
> /**
> @@ -1938,6 +1939,7 @@ gchar *spice_channel_supported_string(void)
> #ifdef USE_USBREDIR
> spice_channel_type_to_string(SPICE_CHANNEL_USBREDIR),
> #endif
> + spice_channel_type_to_string(SPICE_CHANNEL_WEBDAV),
> NULL);
> }
>
> @@ -2002,6 +2004,10 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
> break;
> }
> #endif
> + case SPICE_CHANNEL_WEBDAV: {
> + gtype = SPICE_TYPE_WEBDAV_CHANNEL;
> + break;
> + }
> case SPICE_CHANNEL_PORT:
> gtype = SPICE_TYPE_PORT_CHANNEL;
> break;
> diff --git a/gtk/spice-client.h b/gtk/spice-client.h
> index 98aaffe..0e1e49d 100644
> --- a/gtk/spice-client.h
> +++ b/gtk/spice-client.h
> @@ -43,6 +43,7 @@
> #include "channel-smartcard.h"
> #include "channel-usbredir.h"
> #include "channel-port.h"
> +#include "channel-webdav.h"
>
> #include "smartcard-manager.h"
> #include "usb-device-manager.h"
> diff --git a/gtk/spice-glib-sym-file b/gtk/spice-glib-sym-file
> index 2aa17cb..878dd12 100644
> --- a/gtk/spice-glib-sym-file
> +++ b/gtk/spice-glib-sym-file
> @@ -106,3 +106,4 @@ spice_uri_set_scheme
> spice_uri_set_user
> spice_uri_to_string
> spice_session_get_proxy_uri
> +spice_webdav_channel_get_type
> diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h
> index 1aae342..cf9f9d1 100644
> --- a/gtk/spice-session-priv.h
> +++ b/gtk/spice-session-priv.h
> @@ -23,6 +23,7 @@
> #include "desktop-integration.h"
> #include "spice-session.h"
> #include "spice-gtk-session.h"
> +#include "phodav/libphodav/phodav.h"
> #include "spice-channel-cache.h"
> #include "decode.h"
>
> @@ -107,6 +108,8 @@ struct _SpiceSessionPrivate {
> SpiceGtkSession *gtk_session;
> SpiceUsbDeviceManager *usb_manager;
> SpicePlaybackChannel *playback_channel;
> + PhodavServer *webdav;
> + guint8 webdav_magic[16];
> };
>
> SpiceSession *spice_session_new_from_session(SpiceSession *session);
> diff --git a/gtk/spice-session.c b/gtk/spice-session.c
> index 09556dc..ea32cf7 100644
> --- a/gtk/spice-session.c
> +++ b/gtk/spice-session.c
> @@ -212,6 +212,7 @@ spice_session_dispose(GObject *gobject)
> g_clear_object(&s->gtk_session);
> g_clear_object(&s->usb_manager);
> g_clear_object(&s->proxy);
> + g_clear_object(&s->webdav);
>
> /* Chain up to the parent class */
> if (G_OBJECT_CLASS(spice_session_parent_class)->dispose)
> diff --git a/spice-common b/spice-common
> index 96ca358..74e3a04 160000
> --- a/spice-common
> +++ b/spice-common
> @@ -1 +1 @@
> -Subproject commit 96ca358669cd32d17ce51f30de3cdbf0a1c0518c
> +Subproject commit 74e3a04d5bed51febaa51f41405ca4779f232d6a
>
More information about the Spice-devel
mailing list