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