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

Pavel Grunt pgrunt at redhat.com
Thu Jan 8 06:19:45 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/
---
v2:
 - adds task counter, missing check for the task existence, SpiceMsgMainAgentData
---
 enums.js        |   6 ++++
 filexfer.js     |  25 +++++++++++++
 main.js         |  95 +++++++++++++++++++++++++++++++++++++++++++++++++
 spice.html      |   1 +
 spice_auto.html |   1 +
 spicemsg.js     | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 234 insertions(+), 1 deletion(-)
 create mode 100644 filexfer.js

diff --git a/enums.js b/enums.js
index d99b38e..fe08d42 100644
--- a/enums.js
+++ b/enums.js
@@ -307,6 +307,7 @@ var SPICE_CURSOR_TYPE_ALPHA     = 0,
 var SPICE_VIDEO_CODEC_TYPE_MJPEG = 1;
 
 var VD_AGENT_PROTOCOL = 1;
+var VD_AGENT_MAX_DATA_SIZE = 2048;
 
 var VD_AGENT_MOUSE_STATE            = 1,
     VD_AGENT_MONITORS_CONFIG        = 2,
@@ -322,3 +323,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 d10b594..5c4a470 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;
 }
 
@@ -236,6 +250,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])
+    {
+        this.log_err("do not have file task id "+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 = VD_AGENT_MAX_DATA_SIZE
+                               - SpiceMsgcMainAgentData.prototype.buffer_size()
+                               - VDAgentFileXferDataMessage.prototype.buffer_size();
+    var _this = this;
+    var sb, eb;
+    var slice, reader;
+
+    if (!file_xfer_task || !this.file_xfer_tasks[file_xfer_task.id])
+        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.length,
+                                                       e.target.result);
+        _this.send_agent_message(VD_AGENT_FILE_XFER_DATA, xfer_data);
+        if (eb - sb == 0)
+            return;
+
+        _this.file_xfer_read(file_xfer_task, eb);
+    };
+
+    slice = file_xfer_task.file.slice(sb, eb);
+    reader.readAsBinaryString(slice);
+}
+
+SpiceMainConn.prototype.file_xfer_completed = function(file_xfer_task, error)
+{
+    if (error)
+        this.log_err(error);
+
+    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..895334c 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);
@@ -459,7 +482,7 @@ SpiceMsgcMainAgentData.prototype =
     },
     buffer_size: function()
     {
-        return 4 + 4 + 8 + 4 + this.data.buffer_size();
+        return 4 + 4 + 8 + 4 + ((this.data) ? this.data.buffer_size() : 0);
     }
 }
 
@@ -494,6 +517,88 @@ 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++) {
+            dv.setUint8(at, this.string.charCodeAt(i));
+            at++;
+        };
+    },
+    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;
+        for (i = 0; i < this.data.length; i++, at++)
+            dv.setUint8(at, this.data.charCodeAt(i));
+    },
+    buffer_size: function()
+    {
+        return 12 + (this.size || 0);
+    }
+}
+
 function SpiceMsgNotify(a, at)
 {
     this.from_buffer(a, at);
-- 
1.9.3



More information about the Spice-devel mailing list