Hi,
Agree with Royce.
This is the use case:
Machine A(where the kimchid is running).
Machine B (the browser which is connetting to the machine A's kimchi).
Step1: upload the iso(locate on the B) from B to A.
Step2: search the iso in A and create a template.
Step3: use the template boot a vm.
At 2014-06-12 04:30:38, "Royce Lv" <lvroyce@linux.vnet.ibm.com> wrote:
>On 2014年06月12日 01:20, Aline Manera wrote:
>>
>> Welcome to Kimchi, ssdxiao!
>> It is always a pleasure to see new faces around here.
>>
>> I've just applied your patches and made some tests.
>> I noticed you are not uploading new files but instead of that, you
>> copy local ISOs files to a new pool.
>Aline,
>
> This piece of code:
>
>+ filePath = ISO_UPLOAD_DIR+fileName
>+ fp = open(filePath, "a+")
>+ fp.seek(position)
>+ fp.write(kwargs["file"].fullvalue())
>
>Here the args passed by cherrypy http server, as I understand, is like a
>file stream from the client side, and this function writes the client
>side file
>to kimchi server directory.
>
>+ fp.close()
>
>In my test I noticed it successfully transferred the ISO, which is
>different from the case when I was using the kimchi upload demo code.
>So I think we can consider to take in upload part for our release if the
>UI part can be polished a little.
>
>Thanks again ssdxiao!
>>
>> As Royce mentioned, she is creating a ootb pool to guide users to save
>> their ISOs there.
>> So those ISOs can easily be found by shallow scan.
>>
>> By uploading, I'd except to user provides a remote URL to Kimchi
>> downloads the ISO and upload it to that pool.
>>
>> So we have 2 scenarios:
>> 1) local -> local
>> I am sure it is useful to user when he has the ootb pool.
>> And it also duplicates the ISOs in different places in the host system
>> In my mind, it would be better if the sysadmin moves the ISOs he
>> wants to use in Kimchi to the ootb pool dir
>>
>> 2) remote -> local
>> It means download an ISO from a remote URL and upload it to the
>> ootb pool
>> That way user doesn't need to do it manually if he doesn't want to
>> use ISO streaming.
>>
>>
>> On 06/09/2014 11:56 PM, ssdxiao wrote:
>>> Upload ISO to the path /var/lib/kimchi/iso of the local disk
>>>
>>> Signed-off-by: ssdxiao <ssdxiao@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)
>>> + 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):
>>> + 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@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>
>>
>> _______________________________________________
>> Kimchi-devel mailing list
>> Kimchi-devel@ovirt.org
>> http://lists.ovirt.org/mailman/listinfo/kimchi-devel
>