Upload ISO to the path /var/lib/kimchi/iso of the local disk
Signed-off-by: ssdxiao <ssdxiao(a)163.com>
---
contrib/kimchi.spec.fedora.in | 1 +
contrib/kimchi.spec.suse.in | 1 +
po/en_US.po | 3 +
po/pt_BR.po | 3 +
po/zh_CN.po | 3 +
src/kimchi/control/storagepools.py | 28 +-
src/nginx.conf.in | 1 +
ui/css/theme-default/upload.css | 43 ++
ui/js/resumable.js | 816 +++++++++++++++++++++++++++++++++
ui/js/src/kimchi.template_add_main.js | 27 ++
ui/pages/kimchi-ui.html.tmpl | 1 +
ui/pages/template-add.html.tmpl | 13 +
12 files changed, 938 insertions(+), 2 deletions(-)
create mode 100644 ui/css/theme-default/upload.css
create mode 100644 ui/js/resumable.js
diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in
index 2d4699b..771fccc 100644
--- a/contrib/kimchi.spec.fedora.in
+++ b/contrib/kimchi.spec.fedora.in
@@ -164,6 +164,7 @@ rm -rf $RPM_BUILD_ROOT
%{_datadir}/kimchi/ui/js/kimchi.min.js
%{_datadir}/kimchi/ui/js/jquery-ui.js
%{_datadir}/kimchi/ui/js/jquery.min.js
+%{_datadir}/kimchi/ui/js/resumable.js
%{_datadir}/kimchi/ui/js/modernizr.custom.2.6.2.min.js
%{_datadir}/kimchi/ui/js/novnc/*.js
%{_datadir}/kimchi/ui/js/spice/*.js
diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in
index 165f566..ad6aed4 100644
--- a/contrib/kimchi.spec.suse.in
+++ b/contrib/kimchi.spec.suse.in
@@ -86,6 +86,7 @@ rm -rf $RPM_BUILD_ROOT
%{_datadir}/kimchi/ui/js/kimchi.min.js
%{_datadir}/kimchi/ui/js/jquery-ui.js
%{_datadir}/kimchi/ui/js/jquery.min.js
+%{_datadir}/kimchi/ui/js/resumable.js
%{_datadir}/kimchi/ui/js/modernizr.custom.2.6.2.min.js
%{_datadir}/kimchi/ui/js/novnc/*.js
%{_datadir}/kimchi/ui/js/spice/*.js
diff --git a/po/en_US.po b/po/en_US.po
index 1ede7dc..6f5b100 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -1670,3 +1670,6 @@ msgstr "No templates found."
msgid "Clone"
msgstr ""
+
+msgid "Upload ISO Image"
+msgstr "Upload ISO Image"
diff --git a/po/pt_BR.po b/po/pt_BR.po
index 5ff54e0..d4d26ee 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -1777,3 +1777,6 @@ msgstr "Nenhum modelo encontrado."
msgid "Clone"
msgstr ""
+
+msgid "Upload ISO Image"
+msgstr "Carregar Imagem ISO"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index caef515..da62131 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -1679,3 +1679,6 @@ msgstr "没有发现模板"
msgid "Clone"
msgstr ""
+
+msgid "Upload ISO Image"
+msgstr "上传ISO镜像"
\ No newline at end of file
diff --git a/src/kimchi/control/storagepools.py b/src/kimchi/control/storagepools.py
index b75bca0..72b9f78 100644
--- a/src/kimchi/control/storagepools.py
+++ b/src/kimchi/control/storagepools.py
@@ -18,8 +18,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import cherrypy
-
-
+import os
+import errno
from kimchi.control.base import Collection, Resource
from kimchi.control.storagevolumes import IsoVolumes, StorageVolumes
from kimchi.control.utils import get_class_name, model_fn
@@ -28,6 +28,9 @@ from kimchi.model.storagepools import ISO_POOL_NAME
from kimchi.control.utils import UrlSubNode
+ISO_UPLOAD_DIR = "/var/lib/kimchi/iso/"
+
+
@UrlSubNode("storagepools", True, ['POST', 'DELETE'])
class StoragePools(Collection):
def __init__(self, model):
@@ -35,6 +38,11 @@ class StoragePools(Collection):
self.resource = StoragePool
isos = IsoPool(model)
setattr(self, ISO_POOL_NAME, isos)
+ try:
+ os.makedirs(ISO_UPLOAD_DIR, mode=0755)
Hi,
Thanks for your patches! As I'm working on another patch of
creating a dedicate storage pool, I suggest we create this dedicate pool
when kimchi start up, and only make dirs does not make it a storage
pool. we need to call libvirt API. If you are willing to help on upload,
could you pls rebase on my patch? Thanks!
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ pass
def create(self, params, *args):
try:
@@ -57,6 +65,22 @@ class StoragePools(Collection):
return resp
+ @cherrypy.expose
+ def upload(self, *args, **kwargs):
And found cherrypy on https upload has bug, and this bug seems not back
ported on ubuntu. Does cherrypy work for your code on https?
+ method = cherrypy.request.method.upper()
+ if method != "POST":
+ raise cherrypy.HTTPError(405)
+ fileName = kwargs["resumableFilename"]
+ chunkSize = kwargs["resumableChunkSize"]
+ chunkNumber = kwargs["resumableChunkNumber"]
+ position = int(chunkSize) * (int(chunkNumber)-1)
+
+ filePath = ISO_UPLOAD_DIR+fileName
+ fp = open(filePath, "a+")
+ fp.seek(position)
+ fp.write(kwargs["file"].fullvalue())
+ fp.close()
+
def _get_resources(self, filter_params):
try:
res_list = super(StoragePools, self)._get_resources(filter_params)
diff --git a/src/nginx.conf.in b/src/nginx.conf.in
index 38e643d..9568476 100644
--- a/src/nginx.conf.in
+++ b/src/nginx.conf.in
@@ -37,6 +37,7 @@ http {
access_log /var/log/nginx/access.log main;
sendfile on;
+ client_max_body_size 2m;
server {
listen $proxy_ssl_port ssl;
diff --git a/ui/css/theme-default/upload.css b/ui/css/theme-default/upload.css
new file mode 100644
index 0000000..9cdfe4f
--- /dev/null
+++ b/ui/css/theme-default/upload.css
@@ -0,0 +1,43 @@
+/*
+Uploadify
+Copyright (c) 2012 Reactive Apps, Ronnie Garcia
+Released under the MIT License
<
http://www.opensource.org/licenses/mit-license.php>
+*/
+
+.uploadify-button {
+ background-color: #505050;
+ background-image: linear-gradient(bottom, #505050 0%, #707070 100%);
+ background-image: -o-linear-gradient(bottom, #505050 0%, #707070 100%);
+ background-image: -moz-linear-gradient(bottom, #505050 0%, #707070 100%);
+ background-image: -webkit-linear-gradient(bottom, #505050 0%, #707070 100%);
+ background-image: -ms-linear-gradient(bottom, #505050 0%, #707070 100%);
+ background-image: -webkit-gradient(
+ linear,
+ left bottom,
+ left top,
+ color-stop(0, #505050),
+ color-stop(1, #707070)
+ );
+ background-position: center top;
+ background-repeat: no-repeat;
+ -webkit-border-radius: 30px;
+ -moz-border-radius: 30px;
+ border-radius: 30px;
+ border: 2px solid #808080;
+ color: #FFF;
+ height: 30px;
+ width: 120px;
+ font: bold 12px Arial, Helvetica, sans-serif;
+ text-align: center;
+ text-shadow: 0 -1px 0 rgba(0,0,0,0.25);
+}
+.uploadify-progress {
+ background-color: #E5E5E5;
+ margin-top: 10px;
+ width: 100%;
+}
+.uploadify-progress-bar {
+ background-color: #0099FF;
+ height: 3px;
+ width: 1px;
+}
diff --git a/ui/js/resumable.js b/ui/js/resumable.js
new file mode 100644
index 0000000..add21ec
--- /dev/null
+++ b/ui/js/resumable.js
@@ -0,0 +1,816 @@
+/*
+* MIT Licensed
+*
http://www.23developer.com/opensource
+*
http://github.com/23/resumable.js
+* Steffen Tiedemann Christensen, steffen(a)23company.com
+*/
+
+(function(){
+"use strict";
+
+ var Resumable = function(opts){
+ if ( !(this instanceof Resumable) ) {
+ return new Resumable(opts);
+ }
+ this.version = 1.0;
+ // SUPPORTED BY BROWSER?
+ // Check if these features are support by the browser:
+ // - File object type
+ // - Blob object type
+ // - FileList object type
+ // - slicing files
+ this.support = (
+ (typeof(File)!=='undefined')
+ &&
+ (typeof(Blob)!=='undefined')
+ &&
+ (typeof(FileList)!=='undefined')
+ &&
+
(!!Blob.prototype.webkitSlice||!!Blob.prototype.mozSlice||!!Blob.prototype.slice||false)
+ );
+ if(!this.support) return(false);
+
+
+ // PROPERTIES
+ var $ = this;
+ $.files = [];
+ $.defaults = {
+ chunkSize:1*1024*1024,
+ forceChunkSize:false,
+ simultaneousUploads:3,
+ fileParameterName:'file',
+ throttleProgressCallbacks:0.5,
+ query:{},
+ headers:{},
+ preprocess:null,
+ method:'multipart',
+ prioritizeFirstAndLastChunk:false,
+ target:'/',
+ testChunks:true,
+ generateUniqueIdentifier:null,
+ maxChunkRetries:undefined,
+ chunkRetryInterval:undefined,
+ permanentErrors:[404, 415, 500, 501],
+ maxFiles:undefined,
+ withCredentials:false,
+ xhrTimeout:0,
+ maxFilesErrorCallback:function (files, errorCount) {
+ var maxFiles = $.getOpt('maxFiles');
+ alert('Please upload ' + maxFiles + ' file' + (maxFiles === 1 ?
'' : 's') + ' at a time.');
+ },
+ minFileSize:1,
+ minFileSizeErrorCallback:function(file, errorCount) {
+ alert(file.fileName||file.name +' is too small, please upload files larger
than ' + $h.formatSize($.getOpt('minFileSize')) + '.');
+ },
+ maxFileSize:undefined,
+ maxFileSizeErrorCallback:function(file, errorCount) {
+ alert(file.fileName||file.name +' is too large, please upload files less
than ' + $h.formatSize($.getOpt('maxFileSize')) + '.');
+ },
+ fileType: [],
+ fileTypeErrorCallback: function(file, errorCount) {
+ alert(file.fileName||file.name +' has type not allowed, please upload files
of type ' + $.getOpt('fileType') + '.');
+ }
+ };
+ $.opts = opts||{};
+ $.getOpt = function(o) {
+ var $opt = this;
+ // Get multiple option if passed an array
+ if(o instanceof Array) {
+ var options = {};
+ $h.each(o, function(option){
+ options[option] = $opt.getOpt(option);
+ });
+ return options;
+ }
+ // Otherwise, just return a simple option
+ if ($opt instanceof ResumableChunk) {
+ if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; }
+ else { $opt = $opt.fileObj; }
+ }
+ if ($opt instanceof ResumableFile) {
+ if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; }
+ else { $opt = $opt.resumableObj; }
+ }
+ if ($opt instanceof Resumable) {
+ if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; }
+ else { return $opt.defaults[o]; }
+ }
+ };
+
+ // EVENTS
+ // catchAll(event, ...)
+ // fileSuccess(file), fileProgress(file), fileAdded(file, event), fileRetry(file),
fileError(file, message),
+ // complete(), progress(), error(message, file), pause()
+ $.events = [];
+ $.on = function(event,callback){
+ $.events.push(event.toLowerCase(), callback);
+ };
+ $.fire = function(){
+ // `arguments` is an object, not array, in FF, so:
+ var args = [];
+ for (var i=0; i<arguments.length; i++) args.push(arguments[i]);
+ // Find event listeners, and support pseudo-event `catchAll`
+ var event = args[0].toLowerCase();
+ for (var i=0; i<=$.events.length; i+=2) {
+ if($.events[i]==event) $.events[i+1].apply($,args.slice(1));
+ if($.events[i]=='catchall') $.events[i+1].apply(null,args);
+ }
+ if(event=='fileerror') $.fire('error', args[2], args[1]);
+ if(event=='fileprogress') $.fire('progress');
+ };
+
+
+ // INTERNAL HELPER METHODS (handy, but ultimately not part of uploading)
+ var $h = {
+ stopEvent: function(e){
+ e.stopPropagation();
+ e.preventDefault();
+ },
+ each: function(o,callback){
+ if(typeof(o.length)!=='undefined') {
+ for (var i=0; i<o.length; i++) {
+ // Array or FileList
+ if(callback(o[i])===false) return;
+ }
+ } else {
+ for (i in o) {
+ // Object
+ if(callback(i,o[i])===false) return;
+ }
+ }
+ },
+ generateUniqueIdentifier:function(file){
+ var custom = $.getOpt('generateUniqueIdentifier');
+ if(typeof custom === 'function') {
+ return custom(file);
+ }
+ var relativePath = file.webkitRelativePath||file.fileName||file.name; // Some
confusion in different versions of Firefox
+ var size = file.size;
+ return(size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img,
''));
+ },
+ contains:function(array,test) {
+ var result = false;
+
+ $h.each(array, function(value) {
+ if (value == test) {
+ result = true;
+ return false;
+ }
+ return true;
+ });
+
+ return result;
+ },
+ formatSize:function(size){
+ if(size<1024) {
+ return size + ' bytes';
+ } else if(size<1024*1024) {
+ return (size/1024.0).toFixed(0) + ' KB';
+ } else if(size<1024*1024*1024) {
+ return (size/1024.0/1024.0).toFixed(1) + ' MB';
+ } else {
+ return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB';
+ }
+ },
+ getTarget:function(params){
+ var target = $.getOpt('target');
+ if(target.indexOf('?') < 0) {
+ target += '?';
+ } else {
+ target += '&';
+ }
+ return target + params.join('&');
+ }
+ };
+
+ var onDrop = function(event){
+ $h.stopEvent(event);
+ appendFilesFromFileList(event.dataTransfer.files, event);
+ };
+ var onDragOver = function(e) {
+ e.preventDefault();
+ };
+
+ // INTERNAL METHODS (both handy and responsible for the heavy load)
+ var appendFilesFromFileList = function(fileList, event){
+ // check for uploading too many files
+ var errorCount = 0;
+ var o = $.getOpt(['maxFiles', 'minFileSize',
'maxFileSize', 'maxFilesErrorCallback',
'minFileSizeErrorCallback', 'maxFileSizeErrorCallback',
'fileType', 'fileTypeErrorCallback']);
+ if (typeof(o.maxFiles)!=='undefined' &&
o.maxFiles<(fileList.length+$.files.length)) {
+ // if single-file upload, file is already added, and trying to add 1 new file,
simply replace the already-added file
+ if (o.maxFiles===1 && $.files.length===1 && fileList.length===1)
{
+ $.removeFile($.files[0]);
+ } else {
+ o.maxFilesErrorCallback(fileList, errorCount++);
+ return false;
+ }
+ }
+ var files = [];
+ $h.each(fileList, function(file){
+ var fileName = file.name.split('.');
+ var fileType = fileName[fileName.length-1].toLowerCase();
+
+ if (o.fileType.length > 0 && !$h.contains(o.fileType, fileType)) {
+ o.fileTypeErrorCallback(file, errorCount++);
+ return false;
+ }
+
+ if (typeof(o.minFileSize)!=='undefined' &&
file.size<o.minFileSize) {
+ o.minFileSizeErrorCallback(file, errorCount++);
+ return false;
+ }
+ if (typeof(o.maxFileSize)!=='undefined' &&
file.size>o.maxFileSize) {
+ o.maxFileSizeErrorCallback(file, errorCount++);
+ return false;
+ }
+
+ // directories have size == 0
+ if (!$.getFromUniqueIdentifier($h.generateUniqueIdentifier(file)))
{(function(){
+ var f = new ResumableFile($, file);
+ window.setTimeout(function(){
+ $.files.push(f);
+ files.push(f);
+ f.container = (typeof event != 'undefined' ? event.srcElement :
null);
+ $.fire('fileAdded', f, event)
+ },0);
+ })()};
+ });
+ window.setTimeout(function(){
+ $.fire('filesAdded', files)
+ },0);
+ };
+
+ // INTERNAL OBJECT TYPES
+ function ResumableFile(resumableObj, file){
+ var $ = this;
+ $.opts = {};
+ $.getOpt = resumableObj.getOpt;
+ $._prevProgress = 0;
+ $.resumableObj = resumableObj;
+ $.file = file;
+ $.fileName = file.fileName||file.name; // Some confusion in different versions of
Firefox
+ $.size = file.size;
+ $.relativePath = file.webkitRelativePath || $.fileName;
+ $.uniqueIdentifier = $h.generateUniqueIdentifier(file);
+ $._pause = false;
+ $.container = '';
+ var _error = false;
+
+ // Callback when something happens within the chunk
+ var chunkEvent = function(event, message){
+ // event can be 'progress', 'success', 'error' or
'retry'
+ switch(event){
+ case 'progress':
+ $.resumableObj.fire('fileProgress', $);
+ break;
+ case 'error':
+ $.abort();
+ _error = true;
+ $.chunks = [];
+ $.resumableObj.fire('fileError', $, message);
+ break;
+ case 'success':
+ if(_error) return;
+ $.resumableObj.fire('fileProgress', $); // it's at least progress
+ if($.isComplete()) {
+ $.resumableObj.fire('fileSuccess', $, message);
+ }
+ break;
+ case 'retry':
+ $.resumableObj.fire('fileRetry', $);
+ break;
+ }
+ };
+
+ // Main code to set up a file object with chunks,
+ // packaged to be able to handle retries if needed.
+ $.chunks = [];
+ $.abort = function(){
+ // Stop current uploads
+ var abortCount = 0;
+ $h.each($.chunks, function(c){
+ if(c.status()=='uploading') {
+ c.abort();
+ abortCount++;
+ }
+ });
+ if(abortCount>0) $.resumableObj.fire('fileProgress', $);
+ };
+ $.cancel = function(){
+ // Reset this file to be void
+ var _chunks = $.chunks;
+ $.chunks = [];
+ // Stop current uploads
+ $h.each(_chunks, function(c){
+ if(c.status()=='uploading') {
+ c.abort();
+ $.resumableObj.uploadNextChunk();
+ }
+ });
+ $.resumableObj.removeFile($);
+ $.resumableObj.fire('fileProgress', $);
+ };
+ $.retry = function(){
+ $.bootstrap();
+ var firedRetry = false;
+ $.resumableObj.on('chunkingComplete', function(){
+ if(!firedRetry) $.resumableObj.upload();
+ firedRetry = true;
+ });
+ };
+ $.bootstrap = function(){
+ $.abort();
+ _error = false;
+ // Rebuild stack of chunks from file
+ $.chunks = [];
+ $._prevProgress = 0;
+ var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor;
+ var maxOffset = Math.max(round($.file.size/$.getOpt('chunkSize')),1);
+ for (var offset=0; offset<maxOffset; offset++) {(function(offset){
+ window.setTimeout(function(){
+ $.chunks.push(new ResumableChunk($.resumableObj, $, offset,
chunkEvent));
+ $.resumableObj.fire('chunkingProgress',$,offset/maxOffset);
+ },0);
+ })(offset)}
+ window.setTimeout(function(){
+ $.resumableObj.fire('chunkingComplete',$);
+ },0);
+ };
+ $.progress = function(){
+ if(_error) return(1);
+ // Sum up progress across everything
+ var ret = 0;
+ var error = false;
+ $h.each($.chunks, function(c){
+ if(c.status()=='error') error = true;
+ ret += c.progress(true); // get chunk progress relative to entire file
+ });
+ ret = (error ? 1 : (ret>0.999 ? 1 : ret));
+ ret = Math.max($._prevProgress, ret); // We don't want to lose percentages
when an upload is paused
+ $._prevProgress = ret;
+ return(ret);
+ };
+ $.isUploading = function(){
+ var uploading = false;
+ $h.each($.chunks, function(chunk){
+ if(chunk.status()=='uploading') {
+ uploading = true;
+ return(false);
+ }
+ });
+ return(uploading);
+ };
+ $.isComplete = function(){
+ var outstanding = false;
+ $h.each($.chunks, function(chunk){
+ var status = chunk.status();
+ if(status=='pending' || status=='uploading' ||
chunk.preprocessState === 1) {
+ outstanding = true;
+ return(false);
+ }
+ });
+ return(!outstanding);
+ };
+ $.pause = function(pause){
+ if(typeof(pause)==='undefined'){
+ $._pause = ($._pause ? false : true);
+ }else{
+ $._pause = pause;
+ }
+ };
+ $.isPaused = function() {
+ return $._pause;
+ };
+
+
+ // Bootstrap and return
+ $.resumableObj.fire('chunkingStart', $);
+ $.bootstrap();
+ return(this);
+ }
+
+ function ResumableChunk(resumableObj, fileObj, offset, callback){
+ var $ = this;
+ $.opts = {};
+ $.getOpt = resumableObj.getOpt;
+ $.resumableObj = resumableObj;
+ $.fileObj = fileObj;
+ $.fileObjSize = fileObj.size;
+ $.fileObjType = fileObj.file.type;
+ $.offset = offset;
+ $.callback = callback;
+ $.lastProgressCallback = (new Date);
+ $.tested = false;
+ $.retries = 0;
+ $.pendingRetry = false;
+ $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished
+
+ // Computed properties
+ var chunkSize = $.getOpt('chunkSize');
+ $.loaded = 0;
+ $.startByte = $.offset*chunkSize;
+ $.endByte = Math.min($.fileObjSize, ($.offset+1)*chunkSize);
+ if ($.fileObjSize-$.endByte < chunkSize &&
!$.getOpt('forceChunkSize')) {
+ // The last chunk will be bigger than the chunk size, but less than 2*chunkSize
+ $.endByte = $.fileObjSize;
+ }
+ $.xhr = null;
+
+ // test() makes a GET request without any data to see if the chunk has already
been uploaded in a previous session
+ $.test = function(){
+ // Set up request and listen for event
+ $.xhr = new XMLHttpRequest();
+
+ var testHandler = function(e){
+ $.tested = true;
+ var status = $.status();
+ if(status=='success') {
+ $.callback(status, $.message());
+ $.resumableObj.uploadNextChunk();
+ } else {
+ $.send();
+ }
+ };
+ $.xhr.addEventListener('load', testHandler, false);
+ $.xhr.addEventListener('error', testHandler, false);
+
+ // Add data from the query options
+ var params = [];
+ var customQuery = $.getOpt('query');
+ if(typeof customQuery == 'function') customQuery =
customQuery($.fileObj, $);
+ $h.each(customQuery, function(k,v){
+ params.push([encodeURIComponent(k),
encodeURIComponent(v)].join('='));
+ });
+ // Add extra data to identify chunk
+ params.push(['resumableChunkNumber',
encodeURIComponent($.offset+1)].join('='));
+ params.push(['resumableChunkSize',
encodeURIComponent($.getOpt('chunkSize'))].join('='));
+ params.push(['resumableCurrentChunkSize', encodeURIComponent($.endByte -
$.startByte)].join('='));
+ params.push(['resumableTotalSize',
encodeURIComponent($.fileObjSize)].join('='));
+ params.push(['resumableType',
encodeURIComponent($.fileObjType)].join('='));
+ params.push(['resumableIdentifier',
encodeURIComponent($.fileObj.uniqueIdentifier)].join('='));
+ params.push(['resumableFilename',
encodeURIComponent($.fileObj.fileName)].join('='));
+ params.push(['resumableRelativePath',
encodeURIComponent($.fileObj.relativePath)].join('='));
+ // Append the relevant chunk and send it
+ $.xhr.open('GET', $h.getTarget(params));
+ $.xhr.timeout = $.getOpt('xhrTimeout');
+ $.xhr.withCredentials = $.getOpt('withCredentials');
+ // Add data from header options
+ $h.each($.getOpt('headers'), function(k,v) {
+ $.xhr.setRequestHeader(k, v);
+ });
+ $.xhr.send(null);
+ };
+
+ $.preprocessFinished = function(){
+ $.preprocessState = 2;
+ $.send();
+ };
+
+ // send() uploads the actual data in a POST call
+ $.send = function(){
+ var preprocess = $.getOpt('preprocess');
+ if(typeof preprocess === 'function') {
+ switch($.preprocessState) {
+ case 0: preprocess($); $.preprocessState = 1; return;
+ case 1: return;
+ case 2: break;
+ }
+ }
+ if($.getOpt('testChunks') && !$.tested) {
+ $.test();
+ return;
+ }
+
+ // Set up request and listen for event
+ $.xhr = new XMLHttpRequest();
+
+ // Progress
+ $.xhr.upload.addEventListener('progress', function(e){
+ if( (new Date) - $.lastProgressCallback >
$.getOpt('throttleProgressCallbacks') * 1000 ) {
+ $.callback('progress');
+ $.lastProgressCallback = (new Date);
+ }
+ $.loaded=e.loaded||0;
+ }, false);
+ $.loaded = 0;
+ $.pendingRetry = false;
+ $.callback('progress');
+
+ // Done (either done, failed or retry)
+ var doneHandler = function(e){
+ var status = $.status();
+ if(status=='success'||status=='error') {
+ $.callback(status, $.message());
+ $.resumableObj.uploadNextChunk();
+ } else {
+ $.callback('retry', $.message());
+ $.abort();
+ $.retries++;
+ var retryInterval = $.getOpt('chunkRetryInterval');
+ if(retryInterval !== undefined) {
+ $.pendingRetry = true;
+ setTimeout($.send, retryInterval);
+ } else {
+ $.send();
+ }
+ }
+ };
+ $.xhr.addEventListener('load', doneHandler, false);
+ $.xhr.addEventListener('error', doneHandler, false);
+
+ // Set up the basic query data from Resumable
+ var query = {
+ resumableChunkNumber: $.offset+1,
+ resumableChunkSize: $.getOpt('chunkSize'),
+ resumableCurrentChunkSize: $.endByte - $.startByte,
+ resumableTotalSize: $.fileObjSize,
+ resumableType: $.fileObjType,
+ resumableIdentifier: $.fileObj.uniqueIdentifier,
+ resumableFilename: $.fileObj.fileName,
+ resumableRelativePath: $.fileObj.relativePath,
+ resumableTotalChunks: $.fileObj.chunks.length
+ };
+ // Mix in custom data
+ var customQuery = $.getOpt('query');
+ if(typeof customQuery == 'function') customQuery =
customQuery($.fileObj, $);
+ $h.each(customQuery, function(k,v){
+ query[k] = v;
+ });
+
+ var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice
? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' :
'slice'))),
+ bytes = $.fileObj.file[func]($.startByte,$.endByte),
+ data = null,
+ target = $.getOpt('target');
+
+ if ($.getOpt('method') === 'octet') {
+ // Add data from the query options
+ data = bytes;
+ var params = [];
+ $h.each(query, function(k,v){
+ params.push([encodeURIComponent(k),
encodeURIComponent(v)].join('='));
+ });
+ target = $h.getTarget(params);
+ } else {
+ // Add data from the query options
+ data = new FormData();
+ $h.each(query, function(k,v){
+ data.append(k,v);
+ });
+ data.append($.getOpt('fileParameterName'), bytes);
+ }
+
+ $.xhr.open('POST', target);
+ $.xhr.timeout = $.getOpt('xhrTimeout');
+ $.xhr.withCredentials = $.getOpt('withCredentials');
+ // Add data from header options
+ $h.each($.getOpt('headers'), function(k,v) {
+ $.xhr.setRequestHeader(k, v);
+ });
+ $.xhr.send(data);
+ };
+ $.abort = function(){
+ // Abort and reset
+ if($.xhr) $.xhr.abort();
+ $.xhr = null;
+ };
+ $.status = function(){
+ // Returns: 'pending', 'uploading', 'success',
'error'
+ if($.pendingRetry) {
+ // if pending retry then that's effectively the same as actively
uploading,
+ // there might just be a slight delay before the retry starts
+ return('uploading');
+ } else if(!$.xhr) {
+ return('pending');
+ } else if($.xhr.readyState<4) {
+ // Status is really 'OPENED', 'HEADERS_RECEIVED' or
'LOADING' - meaning that stuff is happening
+ return('uploading');
+ } else {
+ if($.xhr.status==200) {
+ // HTTP 200, perfect
+ return('success');
+ } else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) ||
$.retries >= $.getOpt('maxChunkRetries')) {
+ // HTTP 415/500/501, permanent error
+ return('error');
+ } else {
+ // this should never happen, but we'll reset and queue a retry
+ // a likely case for this would be 503 service unavailable
+ $.abort();
+ return('pending');
+ }
+ }
+ };
+ $.message = function(){
+ return($.xhr ? $.xhr.responseText : '');
+ };
+ $.progress = function(relative){
+ if(typeof(relative)==='undefined') relative = false;
+ var factor = (relative ? ($.endByte-$.startByte)/$.fileObjSize : 1);
+ if($.pendingRetry) return(0);
+ var s = $.status();
+ switch(s){
+ case 'success':
+ case 'error':
+ return(1*factor);
+ case 'pending':
+ return(0*factor);
+ default:
+ return($.loaded/($.endByte-$.startByte)*factor);
+ }
+ };
+ return(this);
+ }
+
+ // QUEUE
+ $.uploadNextChunk = function(){
+ var found = false;
+
+ // In some cases (such as videos) it's really handy to upload the first
+ // and last chunk of a file quickly; this let's the server check the
file's
+ // metadata and determine if there's even a point in continuing.
+ if ($.getOpt('prioritizeFirstAndLastChunk')) {
+ $h.each($.files, function(file){
+ if(file.chunks.length && file.chunks[0].status()=='pending'
&& file.chunks[0].preprocessState === 0) {
+ file.chunks[0].send();
+ found = true;
+ return(false);
+ }
+ if(file.chunks.length>1 &&
file.chunks[file.chunks.length-1].status()=='pending' &&
file.chunks[file.chunks.length-1].preprocessState === 0) {
+ file.chunks[file.chunks.length-1].send();
+ found = true;
+ return(false);
+ }
+ });
+ if(found) return(true);
+ }
+
+ // Now, simply look for the next, best thing to upload
+ $h.each($.files, function(file){
+ if(file.isPaused()===false){
+ $h.each(file.chunks, function(chunk){
+ if(chunk.status()=='pending' && chunk.preprocessState === 0)
{
+ chunk.send();
+ found = true;
+ return(false);
+ }
+ });
+ }
+ if(found) return(false);
+ });
+ if(found) return(true);
+
+ // The are no more outstanding chunks to upload, check is everything is done
+ var outstanding = false;
+ $h.each($.files, function(file){
+ if(!file.isComplete()) {
+ outstanding = true;
+ return(false);
+ }
+ });
+ if(!outstanding) {
+ // All chunks have been uploaded, complete
+ $.fire('complete');
+ }
+ return(false);
+ };
+
+
+ // PUBLIC METHODS FOR RESUMABLE.JS
+ $.assignBrowse = function(domNodes, isDirectory){
+ if(typeof(domNodes.length)=='undefined') domNodes = [domNodes];
+
+ $h.each(domNodes, function(domNode) {
+ var input;
+ if(domNode.tagName==='INPUT' && domNode.type==='file'){
+ input = domNode;
+ } else {
+ input = document.createElement('input');
+ input.setAttribute('type', 'file');
+ input.style.display = 'none';
+ domNode.addEventListener('click', function(){
+ input.style.opacity = 0;
+ input.style.display='block';
+ input.focus();
+ input.click();
+ input.style.display='none';
+ }, false);
+ domNode.appendChild(input);
+ }
+ var maxFiles = $.getOpt('maxFiles');
+ if (typeof(maxFiles)==='undefined'||maxFiles!=1){
+ input.setAttribute('multiple', 'multiple');
+ } else {
+ input.removeAttribute('multiple');
+ }
+ if(isDirectory){
+ input.setAttribute('webkitdirectory', 'webkitdirectory');
+ } else {
+ input.removeAttribute('webkitdirectory');
+ }
+ // When new files are added, simply append them to the overall list
+ input.addEventListener('change', function(e){
+ appendFilesFromFileList(e.target.files,e);
+ e.target.value = '';
+ }, false);
+ });
+ };
+ $.assignDrop = function(domNodes){
+ if(typeof(domNodes.length)=='undefined') domNodes = [domNodes];
+
+ $h.each(domNodes, function(domNode) {
+ domNode.addEventListener('dragover', onDragOver, false);
+ domNode.addEventListener('drop', onDrop, false);
+ });
+ };
+ $.unAssignDrop = function(domNodes) {
+ if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes];
+
+ $h.each(domNodes, function(domNode) {
+ domNode.removeEventListener('dragover', onDragOver);
+ domNode.removeEventListener('drop', onDrop);
+ });
+ };
+ $.isUploading = function(){
+ var uploading = false;
+ $h.each($.files, function(file){
+ if (file.isUploading()) {
+ uploading = true;
+ return(false);
+ }
+ });
+ return(uploading);
+ };
+ $.upload = function(){
+ // Make sure we don't start too many uploads at once
+ if($.isUploading()) return;
+ // Kick off the queue
+ $.fire('uploadStart');
+ for (var num=1; num<=$.getOpt('simultaneousUploads'); num++) {
+ $.uploadNextChunk();
+ }
+ };
+ $.pause = function(){
+ // Resume all chunks currently being uploaded
+ $h.each($.files, function(file){
+ file.abort();
+ });
+ $.fire('pause');
+ };
+ $.cancel = function(){
+ for(var i = $.files.length - 1; i >= 0; i--) {
+ $.files[i].cancel();
+ }
+ $.fire('cancel');
+ };
+ $.progress = function(){
+ var totalDone = 0;
+ var totalSize = 0;
+ // Resume all chunks currently being uploaded
+ $h.each($.files, function(file){
+ totalDone += file.progress()*file.size;
+ totalSize += file.size;
+ });
+ return(totalSize>0 ? totalDone/totalSize : 0);
+ };
+ $.addFile = function(file, event){
+ appendFilesFromFileList([file], event);
+ };
+ $.removeFile = function(file){
+ for(var i = $.files.length - 1; i >= 0; i--) {
+ if($.files[i] === file) {
+ $.files.splice(i, 1);
+ }
+ }
+ };
+ $.getFromUniqueIdentifier = function(uniqueIdentifier){
+ var ret = false;
+ $h.each($.files, function(f){
+ if(f.uniqueIdentifier==uniqueIdentifier) ret = f;
+ });
+ return(ret);
+ };
+ $.getSize = function(){
+ var totalSize = 0;
+ $h.each($.files, function(file){
+ totalSize += file.size;
+ });
+ return(totalSize);
+ };
+
+ return(this);
+ };
+
+
+ // Node.js-style export for Node and Component
+ if (typeof module != 'undefined') {
+ module.exports = Resumable;
+ } else if (typeof define === "function" && define.amd) {
+ // AMD/requirejs: Define the module
+ define(function(){
+ return Resumable;
+ });
+ } else {
+ // Browser: Expose to window
+ window.Resumable = Resumable;
+ }
+
+})();
diff --git a/ui/js/src/kimchi.template_add_main.js
b/ui/js/src/kimchi.template_add_main.js
index dbb3952..5651424 100644
--- a/ui/js/src/kimchi.template_add_main.js
+++ b/ui/js/src/kimchi.template_add_main.js
@@ -387,6 +387,33 @@ kimchi.template_add_main = function() {
}
}
};
+ //1-3 upload iso
+ $('#iso-upload').click(function() {
+ kimchi.switchPage('iso-type-box', 'iso-upload-box');
+ });
+
+ $('#iso-upload-box-back').click(function() {
+ kimchi.switchPage('iso-upload-box', 'iso-type-box',
'right');
+ });
+
+ var r = new Resumable({
+ target:'storagepools/upload'
+ });
+
+ r.on('fileProgress', function(file){
+ console.debug(file);
+ var element=document.getElementById("upload");
+ var progress = Math.round(file.progress()*100)+"%"
+ element.innerHTML=file.fileName+ "-" + progress;
+ var tmp=document.getElementById("movie");
+ tmp.innerHTML=['<div class="uploadify-progress"><div
class="uploadify-progress-bar" style="width:',
progress,'"></div></div>'].join("")
+ });
+
+ r.on('fileAdded', function(file, event){
+ r.upload();
+ });
+
+ r.assignBrowse(document.getElementById('browseButton'));
};
kimchi.template_check_url = function(url) {
diff --git a/ui/pages/kimchi-ui.html.tmpl b/ui/pages/kimchi-ui.html.tmpl
index 08b27a8..542cd43 100644
--- a/ui/pages/kimchi-ui.html.tmpl
+++ b/ui/pages/kimchi-ui.html.tmpl
@@ -38,6 +38,7 @@
<script src="$href('libs/jquery-ui.min.js')"></script>
<script
src="$href('libs/jquery-ui-i18n.min.js')"></script>
<script src="$href('js/kimchi.min.js')"></script>
+<script src="$href('js/resumable.js')"></script>
<!-- This is used for detecting if the UI needs to be built -->
<style type="text/css">
diff --git a/ui/pages/template-add.html.tmpl b/ui/pages/template-add.html.tmpl
index afe22dd..ecda083 100644
--- a/ui/pages/template-add.html.tmpl
+++ b/ui/pages/template-add.html.tmpl
@@ -41,6 +41,9 @@
<li>
<a id="iso-remote"
class="remote">$_("Remote ISO Image")</a>
</li>
+ <li>
+ <a id="iso-upload"
class="local">$_("Upload ISO Image")</a>
+ </li>
</ul>
</div>
@@ -204,6 +207,16 @@
</div>
</div>
+ <!-- 1-3-->
+ <div class="page" id="iso-upload-box">
+ <header>
+ <a class="back"
id="iso-upload-box-back"></a>
+ <h2 class="step-title">$_("Upload ISO
Image")</h2>
+ </header>
+ <a href="#" id="browseButton"
class="uploadify-button">Select files</a>
+ <div id="upload"></div>
+ <div id="movie"></div>
+ </div>
</div>
</div>
</div>