[Spice-devel] File Transfer API

Jonathon Jongsma jjongsma at redhat.com
Wed Sep 16 09:23:46 PDT 2015


So, spice-gtk has supported file transfers from client to guest for
quite a while. But there's a longstanding issue that the user has no
feedback about whether the file is actually being transferred, or how
long it will take. The only real feedback that a user gets is after the
file transfer is completed: the vdagent will generally open a file
browser window to reveal the transferred file.

So I was poking around a bit with adding a file transfer progress
indication to virt-viewer, and I found that the file transfer API in
spice-gtk does not make this very easy. The main API involved here is
this:

void spice_main_file_copy_async(SpiceMainChannel *channel,
                                GFile **sources,
                                GFileCopyFlags flags,
                                GCancellable *cancellable,
                                GFileProgressCallback progress_callback,
                                gpointer progress_callback_data,
                                GAsyncReadyCallback callback,
                                gpointer user_data);

At first glance, it looks like this gives us everything we need, but
there are a few issues with it. The main ones are:


1. It's possible to pass an array of files to be transferred, but the
progress_callback is called for each file separately. But the
GFileProgressCallback function signature does not provide any way for
the caller to determine which file a given progress_callback() call is
associated with. For example, if you tried to transfer 2 different files
with sizes 100 and 200, you might get the following:
- progress_callback(0, 100, progress_callback_data)
- progress_callback(0, 200, progress_callback_data)
- progress_callback(50, 100, progress_callback_data)
- progress_callback(50, 200, progress_callback_data)
- progress_callback(100, 100, progress_callback_data)
- progress_callback(100, 200, progress_callback_data)
- progress_callback(150, 200, progress_callback_data)
- progress_callback(200, 200, progress_callback_data)

In this case, you could apply some heuristics to figure out which
callback applied to which file by examining the file sizes, but that is
not ideal, and is not always going to work. A workaround for this is to
sum up total progress internally and pass these values to the
progress_callback. In that case, you'd get something more like this:
- progress_callback(0, 300, progress_callback_data)
- progress_callback(0, 300, progress_callback_data)
- progress_callback(50, 300, progress_callback_data)
- progress_callback(100, 300, progress_callback_data)
- progress_callback(150, 300, progress_callback_data)
- progress_callback(200, 300, progress_callback_data)
- progress_callback(250, 300, progress_callback_data)
- progress_callback(300, 300, progress_callback_data)

That seems OK, but see #3 below as to why this isn't really a full
solution.



2. spice_main_file_copy_async() looks like a single transaction, so you
would expect it to call 'callback' after all files are finished being
transferred. But in reality, 'callback' is called once for each file
that is being transferred. This makes it difficult for the application
to keep track of the status of the entire transaction. It would be much
better to make it behave like every other gio-style async API and have a
single _async() call paired to a single _finish() call. 



3. Even if the spice_main_file_copy_async() API mentioned above did not
have any problems, it's difficult for an application to make use of it
if they also use the SpiceDisplay widget. This is because the
SpiceDisplay widget automatically handles drag-and-drop of files onto
the display. When a file is dropped, it immediately starts a file
transfer. This means that the application cannot use in its own
'progress_callback' or 'callback' functions in order to monitor and
display the file transfer progress. There's currently no way to disable
this automatic handling of drag-and-drop on the widget.


==========

We could deprecate this API, but I don't want to remove or break public
API. So we still need to support transfers created with the current
function. I'd like to propose a complementary API:

Add a new public object SpiceFileTransferTask with the following
characteristics:
 - it represents the transfer of a single file
 - it has a "progress" property, which represents the percentage of the
file that has been transferred. The application can connect to the
"notify::progress" signal to be notified when the progress of a transfer
changes.
 - it has a "finished" signal so that the application can tell when the
transfer of that file has completed
 - it has a "filename" property so that the application can display the
filename next to the progress of the transfer if desired
 - it has a "cancel()" method, so that an application can cancel an
individual file transfer[0]

In addition to this new object, add a "new-file-transfer" signal to
SpiceMainChannel. This signal will be emitted whenever a new file
transfer task has been initiated (presumably by the drag-and-drop
handler of the SpiceDisplay widget). 

This should allow us to keep our API stable and still allow applications
to monitor and display feedback of file transfers. Objections? Alternate
suggestions?

Since you managed to read this whole long-winded message, here's a
little screencast of a preliminary implementation in spicy using the
above approach:
http://people.freedesktop.org/~jjongsma/spicy-file-transfer-progress.ogv

Thanks,
Jonathon



More information about the Spice-devel mailing list