[Kimchi-devel] [PATCH] Support to upload ISO
Royce Lv
lvroyce at linux.vnet.ibm.com
Tue Jun 10 03:52:35 UTC 2014
On 2014年06月10日 10:56, ssdxiao wrote:
> Upload ISO to the path /var/lib/kimchi/iso of the local disk
>
> Signed-off-by: ssdxiao <ssdxiao at 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):
I have tried cherrypy upload:
https://bitbucket.org/cherrypy/cherrypy/issue/1068/file-upload-crashes-when-using-https
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 at 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>
More information about the Kimchi-devel
mailing list