Add support for chunked upload
Hacked around Blueimp's jquery.fileupload to make it work with our new chunking API. Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
This commit is contained in:
parent
c68e273664
commit
786e858d23
|
@ -18,7 +18,7 @@
|
||||||
* - TODO music upload button
|
* - TODO music upload button
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global jQuery, humanFileSize */
|
/* global jQuery, humanFileSize, md5 */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File upload object
|
* File upload object
|
||||||
|
@ -34,12 +34,21 @@
|
||||||
OC.FileUpload = function(uploader, data) {
|
OC.FileUpload = function(uploader, data) {
|
||||||
this.uploader = uploader;
|
this.uploader = uploader;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
var path = OC.joinPaths(this.uploader.fileList.getCurrentDirectory(), this.getFile().name);
|
||||||
|
this.id = 'web-file-upload-' + md5(path) + '-' + (new Date()).getTime();
|
||||||
};
|
};
|
||||||
OC.FileUpload.CONFLICT_MODE_DETECT = 0;
|
OC.FileUpload.CONFLICT_MODE_DETECT = 0;
|
||||||
OC.FileUpload.CONFLICT_MODE_OVERWRITE = 1;
|
OC.FileUpload.CONFLICT_MODE_OVERWRITE = 1;
|
||||||
OC.FileUpload.CONFLICT_MODE_AUTORENAME = 2;
|
OC.FileUpload.CONFLICT_MODE_AUTORENAME = 2;
|
||||||
OC.FileUpload.prototype = {
|
OC.FileUpload.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique upload id
|
||||||
|
*
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
id: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload element
|
* Upload element
|
||||||
*
|
*
|
||||||
|
@ -66,6 +75,15 @@ OC.FileUpload.prototype = {
|
||||||
*/
|
*/
|
||||||
_newName: null,
|
_newName: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the unique upload id
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
getId: function() {
|
||||||
|
return this.id;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the file to be uploaded
|
* Returns the file to be uploaded
|
||||||
*
|
*
|
||||||
|
@ -143,6 +161,7 @@ OC.FileUpload.prototype = {
|
||||||
* Submit the upload
|
* Submit the upload
|
||||||
*/
|
*/
|
||||||
submit: function() {
|
submit: function() {
|
||||||
|
var self = this;
|
||||||
var data = this.data;
|
var data = this.data;
|
||||||
var file = this.getFile();
|
var file = this.getFile();
|
||||||
|
|
||||||
|
@ -192,19 +211,54 @@ OC.FileUpload.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chunkFolderPromise;
|
||||||
|
if ($.support.blobSlice
|
||||||
|
&& this.uploader.fileUploadParam.maxChunkSize
|
||||||
|
&& this.getFile().size > this.uploader.fileUploadParam.maxChunkSize
|
||||||
|
) {
|
||||||
|
data.isChunked = true;
|
||||||
|
chunkFolderPromise = this.uploader.davClient.createDirectory(
|
||||||
|
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
|
||||||
|
);
|
||||||
|
// TODO: if fails, it means same id already existed, need to retry
|
||||||
|
} else {
|
||||||
|
chunkFolderPromise = $.Deferred().resolve().promise();
|
||||||
|
}
|
||||||
|
|
||||||
// wait for creation of the required directory before uploading
|
// wait for creation of the required directory before uploading
|
||||||
folderPromise.then(function() {
|
$.when(folderPromise, chunkFolderPromise).then(function() {
|
||||||
data.submit();
|
data.submit();
|
||||||
}, function() {
|
}, function() {
|
||||||
data.abort();
|
self.abort();
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process end of transfer
|
||||||
|
*/
|
||||||
|
done: function() {
|
||||||
|
if (!this.data.isChunked) {
|
||||||
|
return $.Deferred().resolve().promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
var uid = OC.getCurrentUser().uid;
|
||||||
|
return this.uploader.davClient.move(
|
||||||
|
'uploads/' + encodeURIComponent(uid) + '/' + encodeURIComponent(this.getId()) + '/.file',
|
||||||
|
'files/' + encodeURIComponent(uid) + '/' + OC.joinPaths(this.getFullPath(), this.getFileName())
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abort the upload
|
* Abort the upload
|
||||||
*/
|
*/
|
||||||
abort: function() {
|
abort: function() {
|
||||||
|
if (this.data.isChunked) {
|
||||||
|
// delete transfer directory for this upload
|
||||||
|
this.uploader.davClient.remove(
|
||||||
|
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
|
||||||
|
);
|
||||||
|
}
|
||||||
this.data.abort();
|
this.data.abort();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -280,7 +334,7 @@ OC.Uploader = function() {
|
||||||
this.init.apply(this, arguments);
|
this.init.apply(this, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
OC.Uploader.prototype = {
|
OC.Uploader.prototype = _.extend({
|
||||||
/**
|
/**
|
||||||
* @type Array<OC.FileUpload>
|
* @type Array<OC.FileUpload>
|
||||||
*/
|
*/
|
||||||
|
@ -384,7 +438,7 @@ OC.Uploader.prototype = {
|
||||||
self.filesClient.createDirectory(fullPath).always(function(status) {
|
self.filesClient.createDirectory(fullPath).always(function(status) {
|
||||||
// 405 is expected if the folder already exists
|
// 405 is expected if the folder already exists
|
||||||
if ((status >= 200 && status < 300) || status === 405) {
|
if ((status >= 200 && status < 300) || status === 405) {
|
||||||
self.$uploadEl.trigger($.Event('fileuploadcreatedfolder'), fullPath);
|
self.trigger('createdfolder', fullPath);
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -407,8 +461,8 @@ OC.Uploader.prototype = {
|
||||||
submitUploads: function(uploads) {
|
submitUploads: function(uploads) {
|
||||||
var self = this;
|
var self = this;
|
||||||
_.each(uploads, function(upload) {
|
_.each(uploads, function(upload) {
|
||||||
upload.submit();
|
|
||||||
self._uploads[upload.data.uploadId] = upload;
|
self._uploads[upload.data.uploadId] = upload;
|
||||||
|
upload.submit();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -611,16 +665,6 @@ OC.Uploader.prototype = {
|
||||||
this.$uploadEl.trigger(new $.Event('resized'));
|
this.$uploadEl.trigger(new $.Event('resized'));
|
||||||
},
|
},
|
||||||
|
|
||||||
on: function() {
|
|
||||||
// forward events to upload element
|
|
||||||
this.$uploadEl.on.apply(this.$uploadEl, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
off: function() {
|
|
||||||
// forward events to upload element
|
|
||||||
this.$uploadEl.off.apply(this.$uploadEl, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the given file is known to be a received shared file
|
* Returns whether the given file is known to be a received shared file
|
||||||
*
|
*
|
||||||
|
@ -655,6 +699,12 @@ OC.Uploader.prototype = {
|
||||||
|
|
||||||
this.fileList = options.fileList;
|
this.fileList = options.fileList;
|
||||||
this.filesClient = options.filesClient || OC.Files.getClient();
|
this.filesClient = options.filesClient || OC.Files.getClient();
|
||||||
|
this.davClient = new OC.Files.Client({
|
||||||
|
host: OC.getHost(),
|
||||||
|
port: OC.getPort(),
|
||||||
|
root: OC.getRootPath() + '/remote.php/dav/',
|
||||||
|
useHTTPS: OC.getProtocol() === 'https'
|
||||||
|
});
|
||||||
|
|
||||||
$uploadEl = $($uploadEl);
|
$uploadEl = $($uploadEl);
|
||||||
this.$uploadEl = $uploadEl;
|
this.$uploadEl = $uploadEl;
|
||||||
|
@ -669,6 +719,7 @@ OC.Uploader.prototype = {
|
||||||
dropZone: options.dropZone, // restrict dropZone to content div
|
dropZone: options.dropZone, // restrict dropZone to content div
|
||||||
autoUpload: false,
|
autoUpload: false,
|
||||||
sequentialUploads: true,
|
sequentialUploads: true,
|
||||||
|
maxChunkSize: 10000000,
|
||||||
//singleFileUploads is on by default, so the data.files array will always have length 1
|
//singleFileUploads is on by default, so the data.files array will always have length 1
|
||||||
/**
|
/**
|
||||||
* on first add of every selection
|
* on first add of every selection
|
||||||
|
@ -692,7 +743,7 @@ OC.Uploader.prototype = {
|
||||||
|
|
||||||
var upload = new OC.FileUpload(self, data);
|
var upload = new OC.FileUpload(self, data);
|
||||||
// can't link directly due to jQuery not liking cyclic deps on its ajax object
|
// can't link directly due to jQuery not liking cyclic deps on its ajax object
|
||||||
data.uploadId = _.uniqueId('file-upload');
|
data.uploadId = upload.getId();
|
||||||
|
|
||||||
// we need to collect all data upload objects before
|
// we need to collect all data upload objects before
|
||||||
// starting the upload so we can check their existence
|
// starting the upload so we can check their existence
|
||||||
|
@ -842,7 +893,10 @@ OC.Uploader.prototype = {
|
||||||
},
|
},
|
||||||
fail: function(e, data) {
|
fail: function(e, data) {
|
||||||
var upload = self.getUpload(data);
|
var upload = self.getUpload(data);
|
||||||
var status = upload.getResponseStatus();
|
var status = null;
|
||||||
|
if (upload) {
|
||||||
|
status = upload.getResponseStatus();
|
||||||
|
}
|
||||||
self.log('fail', e, upload);
|
self.log('fail', e, upload);
|
||||||
|
|
||||||
if (data.textStatus === 'abort') {
|
if (data.textStatus === 'abort') {
|
||||||
|
@ -914,10 +968,7 @@ OC.Uploader.prototype = {
|
||||||
// add progress handlers
|
// add progress handlers
|
||||||
fileupload.on('fileuploadadd', function(e, data) {
|
fileupload.on('fileuploadadd', function(e, data) {
|
||||||
self.log('progress handle fileuploadadd', e, data);
|
self.log('progress handle fileuploadadd', e, data);
|
||||||
//show cancel button
|
self.trigger('add', e, data);
|
||||||
//if (data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie?
|
|
||||||
// $('#uploadprogresswrapper .stop').show();
|
|
||||||
//}
|
|
||||||
});
|
});
|
||||||
// add progress handlers
|
// add progress handlers
|
||||||
fileupload.on('fileuploadstart', function(e, data) {
|
fileupload.on('fileuploadstart', function(e, data) {
|
||||||
|
@ -933,10 +984,12 @@ OC.Uploader.prototype = {
|
||||||
+ '</span></em>');
|
+ '</span></em>');
|
||||||
$('#uploadprogressbar').tipsy({gravity:'n', fade:true, live:true});
|
$('#uploadprogressbar').tipsy({gravity:'n', fade:true, live:true});
|
||||||
self._showProgressBar();
|
self._showProgressBar();
|
||||||
|
self.trigger('start', e, data);
|
||||||
});
|
});
|
||||||
fileupload.on('fileuploadprogress', function(e, data) {
|
fileupload.on('fileuploadprogress', function(e, data) {
|
||||||
self.log('progress handle fileuploadprogress', e, data);
|
self.log('progress handle fileuploadprogress', e, data);
|
||||||
//TODO progressbar in row
|
//TODO progressbar in row
|
||||||
|
self.trigger('progress', e, data);
|
||||||
});
|
});
|
||||||
fileupload.on('fileuploadprogressall', function(e, data) {
|
fileupload.on('fileuploadprogressall', function(e, data) {
|
||||||
self.log('progress handle fileuploadprogressall', e, data);
|
self.log('progress handle fileuploadprogressall', e, data);
|
||||||
|
@ -999,6 +1052,7 @@ OC.Uploader.prototype = {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
$('#uploadprogressbar').progressbar('value', progress);
|
$('#uploadprogressbar').progressbar('value', progress);
|
||||||
|
self.trigger('progressall', e, data);
|
||||||
});
|
});
|
||||||
fileupload.on('fileuploadstop', function(e, data) {
|
fileupload.on('fileuploadstop', function(e, data) {
|
||||||
self.log('progress handle fileuploadstop', e, data);
|
self.log('progress handle fileuploadstop', e, data);
|
||||||
|
@ -1012,6 +1066,7 @@ OC.Uploader.prototype = {
|
||||||
if (data.errorThrown === 'abort') {
|
if (data.errorThrown === 'abort') {
|
||||||
self._hideProgressBar();
|
self._hideProgressBar();
|
||||||
}
|
}
|
||||||
|
self.trigger('fail', e, data);
|
||||||
});
|
});
|
||||||
var disableDropState = function() {
|
var disableDropState = function() {
|
||||||
$('#app-content').removeClass('file-drag');
|
$('#app-content').removeClass('file-drag');
|
||||||
|
@ -1046,22 +1101,36 @@ OC.Uploader.prototype = {
|
||||||
filerow.find('.thumbnail').addClass('icon-filetype-folder-drag-accept');
|
filerow.find('.thumbnail').addClass('icon-filetype-folder-drag-accept');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
fileupload.on('fileuploaddragleave fileuploaddrop', disableDropState);
|
fileupload.on('fileuploaddragleave fileuploaddrop', function (){
|
||||||
} else {
|
$('#app-content').removeClass('file-drag');
|
||||||
// for all browsers that don't support the progress bar
|
$('.dropping-to-dir').removeClass('dropping-to-dir');
|
||||||
// IE 8 & 9
|
$('.dir-drop').removeClass('dir-drop');
|
||||||
|
$('.icon-filetype-folder-drag-accept').removeClass('icon-filetype-folder-drag-accept');
|
||||||
// show a spinner
|
|
||||||
fileupload.on('fileuploadstart', function() {
|
|
||||||
$('#upload').addClass('icon-loading');
|
|
||||||
$('#upload .icon-upload').hide();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// hide a spinner
|
fileupload.on('fileuploadchunksend', function(e, data) {
|
||||||
fileupload.on('fileuploadstop fileuploadfail', function() {
|
// modify the request to adjust it to our own chunking
|
||||||
$('#upload').removeClass('icon-loading');
|
var upload = self.getUpload(data);
|
||||||
$('#upload .icon-upload').show();
|
var range = data.contentRange.split(' ')[1];
|
||||||
|
var chunkId = range.split('/')[0];
|
||||||
|
data.url = OC.getRootPath() +
|
||||||
|
'/remote.php/dav/uploads' +
|
||||||
|
'/' + encodeURIComponent(OC.getCurrentUser().uid) +
|
||||||
|
'/' + encodeURIComponent(upload.getId()) +
|
||||||
|
'/' + encodeURIComponent(chunkId);
|
||||||
|
delete data.contentRange;
|
||||||
|
delete data.headers['Content-Range'];
|
||||||
});
|
});
|
||||||
|
fileupload.on('fileuploaddone', function(e, data) {
|
||||||
|
var upload = self.getUpload(data);
|
||||||
|
upload.done().then(function() {
|
||||||
|
self.trigger('done', e, upload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fileupload.on('fileuploaddrop', function(e, data) {
|
||||||
|
self.trigger('drop', e, data);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1079,6 +1148,6 @@ OC.Uploader.prototype = {
|
||||||
|
|
||||||
return this.fileUploadParam;
|
return this.fileUploadParam;
|
||||||
}
|
}
|
||||||
};
|
}, OC.Backbone.Events);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2661,16 +2661,18 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup file upload events related to the file-upload plugin
|
* Setup file upload events related to the file-upload plugin
|
||||||
|
*
|
||||||
|
* @param {OC.Uploader} uploader
|
||||||
*/
|
*/
|
||||||
setupUploadEvents: function($uploadEl) {
|
setupUploadEvents: function(uploader) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self._uploads = {};
|
self._uploads = {};
|
||||||
|
|
||||||
// detect the progress bar resize
|
// detect the progress bar resize
|
||||||
$uploadEl.on('resized', this._onResize);
|
uploader.on('resized', this._onResize);
|
||||||
|
|
||||||
$uploadEl.on('fileuploaddrop', function(e, data) {
|
uploader.on('drop', function(e, data) {
|
||||||
self._uploader.log('filelist handle fileuploaddrop', e, data);
|
self._uploader.log('filelist handle fileuploaddrop', e, data);
|
||||||
|
|
||||||
if (self.$el.hasClass('hidden')) {
|
if (self.$el.hasClass('hidden')) {
|
||||||
|
@ -2734,7 +2736,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$uploadEl.on('fileuploadadd', function(e, data) {
|
uploader.on('add', function(e, data) {
|
||||||
self._uploader.log('filelist handle fileuploadadd', e, data);
|
self._uploader.log('filelist handle fileuploadadd', e, data);
|
||||||
|
|
||||||
// add ui visualization to existing folder
|
// add ui visualization to existing folder
|
||||||
|
@ -2766,16 +2768,16 @@
|
||||||
* when file upload done successfully add row to filelist
|
* when file upload done successfully add row to filelist
|
||||||
* update counter when uploading to sub folder
|
* update counter when uploading to sub folder
|
||||||
*/
|
*/
|
||||||
$uploadEl.on('fileuploaddone', function(e, data) {
|
uploader.on('done', function(e, upload) {
|
||||||
self._uploader.log('filelist handle fileuploaddone', e, data);
|
self._uploader.log('filelist handle fileuploaddone', e, data);
|
||||||
|
|
||||||
|
var data = upload.data;
|
||||||
var status = data.jqXHR.status;
|
var status = data.jqXHR.status;
|
||||||
if (status < 200 || status >= 300) {
|
if (status < 200 || status >= 300) {
|
||||||
// error was handled in OC.Uploads already
|
// error was handled in OC.Uploads already
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var upload = self._uploader.getUpload(data);
|
|
||||||
var fileName = upload.getFileName();
|
var fileName = upload.getFileName();
|
||||||
var fetchInfoPromise = self.addAndFetchFileInfo(fileName, upload.getFullPath());
|
var fetchInfoPromise = self.addAndFetchFileInfo(fileName, upload.getFullPath());
|
||||||
if (!self._uploads) {
|
if (!self._uploads) {
|
||||||
|
@ -2785,10 +2787,10 @@
|
||||||
self._uploads[fileName] = fetchInfoPromise;
|
self._uploads[fileName] = fetchInfoPromise;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$uploadEl.on('fileuploadcreatedfolder', function(e, fullPath) {
|
uploader.on('createdfolder', function(e, fullPath) {
|
||||||
self.addAndFetchFileInfo(OC.basename(fullPath), OC.dirname(fullPath));
|
self.addAndFetchFileInfo(OC.basename(fullPath), OC.dirname(fullPath));
|
||||||
});
|
});
|
||||||
$uploadEl.on('fileuploadstop', function() {
|
uploader.on('stop', function() {
|
||||||
self._uploader.log('filelist handle fileuploadstop');
|
self._uploader.log('filelist handle fileuploadstop');
|
||||||
|
|
||||||
// prepare list of uploaded file names in the current directory
|
// prepare list of uploaded file names in the current directory
|
||||||
|
@ -2804,7 +2806,7 @@
|
||||||
});
|
});
|
||||||
self.updateStorageStatistics();
|
self.updateStorageStatistics();
|
||||||
});
|
});
|
||||||
$uploadEl.on('fileuploadfail', function(e, data) {
|
uploader.on('fail', function(e, data) {
|
||||||
self._uploader.log('filelist handle fileuploadfail', e, data);
|
self._uploader.log('filelist handle fileuploadfail', e, data);
|
||||||
|
|
||||||
self._uploads = [];
|
self._uploads = [];
|
||||||
|
|
Loading…
Reference in New Issue