[Spice-devel] [PATCH spice-html5 v3 5/7] Implement methods for transfering files from client to guest

Pavel Grunt pgrunt at redhat.com
Wed Jan 14 08:44:40 PST 2015


It is possible to transfer files from the client to the guest
using File API [0] when a spice vd agent is connected.

Methods for the transfer are based on spice-gtk implementation.

[0] http://www.w3.org/TR/file-upload/
---
v3:
 - Fixes issues with IE11 by using readAsArrayBuffer instead of readAsBinaryString
 - User is informed when file is transferred
v2:
 - adds task counter, missing check for the task existence, SpiceMsgMainAgentData
---
 enums.js        |   5 +++
 filexfer.js     |  25 +++++++++++++
 main.js         |  95 +++++++++++++++++++++++++++++++++++++++++++++++++
 spice.html      |   1 +
 spice_auto.html |   1 +
 spicemsg.js     | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 234 insertions(+)
 create mode 100644 filexfer.js

diff --git a/enums.js b/enums.js
index 4b678df..07aa343 100644
--- a/enums.js
+++ b/enums.js
@@ -328,3 +328,8 @@ var VD_AGENT_MOUSE_STATE            = 1,
     VD_AGENT_FILE_XFER_DATA         =12,
     VD_AGENT_CLIENT_DISCONNECTED    =13,
     VD_AGENT_MAX_CLIPBOARD          =14;
+
+var VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA = 0,
+    VD_AGENT_FILE_XFER_STATUS_CANCELLED     = 1,
+    VD_AGENT_FILE_XFER_STATUS_ERROR         = 2,
+    VD_AGENT_FILE_XFER_STATUS_SUCCESS       = 3;
diff --git a/filexfer.js b/filexfer.js
new file mode 100644
index 0000000..d472240
--- /dev/null
+++ b/filexfer.js
@@ -0,0 +1,25 @@
+"use strict";
+/*
+   Copyright (C) 2014 Red Hat, Inc.
+
+   This file is part of spice-html5.
+
+   spice-html5 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 3 of the License, or
+   (at your option) any later version.
+
+   spice-html5 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 spice-html5.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+function SpiceFileXferTask(id, file)
+{
+    this.id = id;
+    this.file = file;
+}
diff --git a/main.js b/main.js
index ca81c60..54e6963 100644
--- a/main.js
+++ b/main.js
@@ -56,6 +56,8 @@ function SpiceMainConn()
     SpiceConn.apply(this, arguments);
 
     this.agent_msg_queue = [];
+    this.file_xfer_tasks = {};
+    this.file_xfer_task_id = 0;
 }
 
 SpiceMainConn.prototype = Object.create(SpiceConn.prototype);
@@ -172,6 +174,18 @@ SpiceMainConn.prototype.process_channel_message = function(msg)
         return true;
     }
 
+    if (msg.type == SPICE_MSG_MAIN_AGENT_DATA)
+    {
+        var agent_data = new SpiceMsgMainAgentData(msg.data);
+        if (agent_data.type == VD_AGENT_FILE_XFER_STATUS)
+        {
+            this.handle_file_xfer_status(new VDAgentFileXferStatusMessage(agent_data.data));
+            return true;
+        }
+
+        return false;
+    }
+
     return false;
 }
 
@@ -246,6 +260,87 @@ SpiceMainConn.prototype.resize_window = function(flags, width, height, depth, x,
     this.send_agent_message(VD_AGENT_MONITORS_CONFIG, monitors_config);
 }
 
+SpiceMainConn.prototype.file_xfer_start = function(file)
+{
+    var task_id, xfer_start, task;
+
+    task_id = this.file_xfer_task_id++;
+    task = new SpiceFileXferTask(task_id, file);
+    this.file_xfer_tasks[task_id] = task;
+    xfer_start = new VDAgentFileXferStartMessage(task_id, file.name, file.size);
+    this.send_agent_message(VD_AGENT_FILE_XFER_START, xfer_start);
+}
+
+SpiceMainConn.prototype.handle_file_xfer_status = function(file_xfer_status)
+{
+    var xfer_error, xfer_task;
+    if (!this.file_xfer_tasks[file_xfer_status.id])
+    {
+        return;
+    }
+    xfer_task = this.file_xfer_tasks[file_xfer_status.id];
+    switch (file_xfer_status.result)
+    {
+        case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
+            this.file_xfer_read(xfer_task);
+            return;
+        case VD_AGENT_FILE_XFER_STATUS_CANCELLED:
+            xfer_error = "transfer is cancelled by spice agent";
+            break;
+        case VD_AGENT_FILE_XFER_STATUS_ERROR:
+            xfer_error = "some errors occurred in the spice agent";
+            break;
+        case VD_AGENT_FILE_XFER_STATUS_SUCCESS:
+            break;
+        default:
+            xfer_error = "unhandled status type: " + file_xfer_status.result;
+            break;
+    }
+
+    this.file_xfer_completed(xfer_task, xfer_error)
+}
+
+SpiceMainConn.prototype.file_xfer_read = function(file_xfer_task, start_byte)
+{
+    var FILE_XFER_CHUNK_SIZE = 32 * VD_AGENT_MAX_DATA_SIZE;
+    var _this = this;
+    var sb, eb;
+    var slice, reader;
+
+    if (!file_xfer_task ||
+        !this.file_xfer_tasks[file_xfer_task.id] ||
+        (start_byte > 0 && start_byte == file_xfer_task.file.size))
+    {
+        return;
+    }
+
+    sb = start_byte || 0,
+    eb = Math.min(sb + FILE_XFER_CHUNK_SIZE, file_xfer_task.file.size);
+
+    reader = new FileReader();
+    reader.onload = function(e)
+    {
+        var xfer_data = new VDAgentFileXferDataMessage(file_xfer_task.id,
+                                                       e.target.result.byteLength,
+                                                       e.target.result);
+        _this.send_agent_message(VD_AGENT_FILE_XFER_DATA, xfer_data);
+        _this.file_xfer_read(file_xfer_task, eb);
+    };
+
+    slice = file_xfer_task.file.slice(sb, eb);
+    reader.readAsArrayBuffer(slice);
+}
+
+SpiceMainConn.prototype.file_xfer_completed = function(file_xfer_task, error)
+{
+    if (error)
+        this.log_err(error);
+    else
+        this.log_info("transfer of '" + file_xfer_task.file.name +"' was successful");
+
+    delete this.file_xfer_tasks[file_xfer_task.id];
+}
+
 SpiceMainConn.prototype.connect_agent = function()
 {
     this.agent_connected = true;
diff --git a/spice.html b/spice.html
index e830eb3..fc53a2a 100644
--- a/spice.html
+++ b/spice.html
@@ -55,6 +55,7 @@
         <script src="thirdparty/sha1.js"></script>
         <script src="ticket.js"></script>
         <script src="resize.js"></script>
+        <script src="filexfer.js"></script>
         <link rel="stylesheet" type="text/css" href="spice.css" />
 
         <script>
diff --git a/spice_auto.html b/spice_auto.html
index 72c5be2..48dcae1 100644
--- a/spice_auto.html
+++ b/spice_auto.html
@@ -55,6 +55,7 @@
         <script src="thirdparty/sha1.js"></script>
         <script src="ticket.js"></script>
         <script src="resize.js"></script>
+        <script src="filexfer.js"></script>
         <link rel="stylesheet" type="text/css" href="spice.css" />
 
         <script>
diff --git a/spicemsg.js b/spicemsg.js
index 0983ae7..ec2034c 100644
--- a/spicemsg.js
+++ b/spicemsg.js
@@ -348,6 +348,29 @@ SpiceMsgMainMouseMode.prototype =
     },
 }
 
+function SpiceMsgMainAgentData(a, at)
+{
+    this.from_buffer(a, at);
+}
+
+SpiceMsgMainAgentData.prototype =
+{
+    from_buffer: function(a, at)
+    {
+        at = at || 0;
+        var dv = new SpiceDataView(a);
+        this.protocol = dv.getUint32(at, true); at += 4;
+        this.type = dv.getUint32(at, true); at += 4;
+        this.opaque = dv.getUint64(at, true); at += 8;
+        this.size = dv.getUint32(at, true); at += 4;
+        if (a.byteLength > at)
+        {
+            this.data = a.slice(at);
+            at += this.data.byteLength;
+        }
+    }
+}
+
 function SpiceMsgMainAgentTokens(a, at)
 {
     this.from_buffer(a, at);
@@ -494,6 +517,90 @@ VDAgentMonitorsConfig.prototype =
     }
 }
 
+function VDAgentFileXferStatusMessage(data, result)
+{
+    if (result)
+    {
+        this.id = data;
+        this.result = result;
+    }
+    else
+        this.from_buffer(data);
+}
+
+VDAgentFileXferStatusMessage.prototype =
+{
+    to_buffer: function(a, at)
+    {
+        at = at || 0;
+        var dv = new SpiceDataView(a);
+        dv.setUint32(at, this.id, true); at += 4;
+        dv.setUint32(at, this.result, true); at += 4;
+    },
+    from_buffer: function(a, at)
+    {
+        at = at || 0;
+        var dv = new SpiceDataView(a);
+        this.id = dv.getUint32(at, true); at += 4;
+        this.result = dv.getUint32(at, true); at += 4;
+        return at;
+    },
+    buffer_size: function()
+    {
+        return 8;
+    }
+}
+
+function VDAgentFileXferStartMessage(id, name, size)
+{
+    this.id = id;
+    this.string = "[vdagent-file-xfer]\n"+"name="+name+"\nsize="+size+"\n";
+}
+
+VDAgentFileXferStartMessage.prototype =
+{
+    to_buffer: function(a, at)
+    {
+        at = at || 0;
+        var dv = new SpiceDataView(a);
+        dv.setUint32(at, this.id, true); at += 4;
+        for (var i = 0; i < this.string.length; i++, at++)
+            dv.setUint8(at, this.string.charCodeAt(i));
+    },
+    buffer_size: function()
+    {
+        return 4 + this.string.length + 1;
+    }
+}
+
+function VDAgentFileXferDataMessage(id, size, data)
+{
+    this.id = id;
+    this.size = size;
+    this.data = data;
+}
+
+VDAgentFileXferDataMessage.prototype =
+{
+    to_buffer: function(a, at)
+    {
+        at = at || 0;
+        var dv = new SpiceDataView(a);
+        dv.setUint32(at, this.id, true); at += 4;
+        dv.setUint64(at, this.size, true); at += 8;
+        if (this.data && this.data.byteLength > 0)
+        {
+            var u8arr = new Uint8Array(this.data);
+            for (var i = 0; i < u8arr.length; i++, at++)
+                dv.setUint8(at, u8arr[i]);
+        }
+    },
+    buffer_size: function()
+    {
+        return 12 + this.size;
+    }
+}
+
 function SpiceMsgNotify(a, at)
 {
     this.from_buffer(a, at);
-- 
1.9.3



More information about the Spice-devel mailing list