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/ --- enums.js | 6 ++++ filexfer.js | 26 ++++++++++++++ main.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++ spice.html | 1 + spice_auto.html | 1 + spicemsg.js | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 227 insertions(+), 2 deletions(-) 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..21ba350 --- /dev/null +++ b/filexfer.js @@ -0,0 +1,26 @@ +"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..5f36253 100644 --- a/main.js +++ b/main.js @@ -56,6 +56,7 @@ function SpiceMainConn() SpiceConn.apply(this, arguments); this.agent_msg_queue = []; + this.file_xfer_tasks = {}; } SpiceMainConn.prototype = Object.create(SpiceConn.prototype); @@ -172,6 +173,18 @@ SpiceMainConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_MAIN_AGENT_DATA) + { + var agent_data = new SpiceMsgcMainAgentData(0, 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 +249,81 @@ 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; + + if (typeof this.file_xfer_tasks === "undefined") + this.file_xfer_tasks = {}; + + task_id = Object.keys(this.file_xfer_tasks).length; + 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 - 20 - 12; + //VD_AGENT_MAX_DATA_SIZE - SpiceMsgcMainAgentData - VDAgentFileXferDataMessage; + var _this = this; + var sb = start_byte || 0, + eb = Math.min(sb + FILE_XFER_CHUNK_SIZE, file_xfer_task.file.size); + var slice, reader = new FileReader(); + + reader.onload = function(e) { + var xfer_data, agent_data, mr; + 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..2618dd8 100644 --- a/spicemsg.js +++ b/spicemsg.js @@ -441,8 +441,15 @@ function SpiceMsgcMainAgentData(type, data) this.protocol = VD_AGENT_PROTOCOL; this.type = type; this.opaque = 0; - this.size = data.buffer_size(); - this.data = data; + if (data.buffer_size) + { + this.size = data.buffer_size(); + this.data = data; + } + else + { + this.from_buffer(data); + } } SpiceMsgcMainAgentData.prototype = @@ -457,6 +464,20 @@ SpiceMsgcMainAgentData.prototype = dv.setUint32(at, this.size, true); at += 4; this.data.to_buffer(a, at); }, + 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; + } + }, buffer_size: function() { return 4 + 4 + 8 + 4 + this.data.buffer_size(); @@ -494,6 +515,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.data.length; + } +} + function SpiceMsgNotify(a, at) { this.from_buffer(a, at); -- 1.9.3 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel