Implement methods for transfering files from client to guest

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/
This commit is contained in:
Pavel Grunt 2015-01-14 17:44:40 +01:00 committed by Jeremy White
parent 633f01050b
commit b9b8567c9d
6 changed files with 234 additions and 0 deletions

View File

@ -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;

25
filexfer.js Normal file
View File

@ -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;
}

95
main.js
View File

@ -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);
@ -171,6 +173,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;
}
@ -245,6 +259,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;

View File

@ -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>

View File

@ -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>

View File

@ -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);