[Kimchi-devel] [PATCH] Support to upload ISO
ssdxiao
ssdxiao at 163.com
Tue Jun 10 08:51:20 UTC 2014
Thanks for your replay .
I test my patch , I find nginx will proxy https to http, so my patch can work .
Kimchi listens to 127.0.0.1 , and not allow other to access , why we must support https using cherrypy? .
I will see your patch and work on upload with you .
At 2014-06-10 11:52:35, "Royce Lv" <lvroyce at linux.vnet.ibm.com> wrote:
>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>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ovirt.org/pipermail/kimchi-devel/attachments/20140610/79731889/attachment.html>
More information about the Kimchi-devel
mailing list