[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