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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel