Skip to content

Commit

Permalink
Add support for chunked upload
Browse files Browse the repository at this point in the history
Hacked around Blueimp's jquery.fileupload to make it work with our new
chunking API.
  • Loading branch information
Vincent Petry committed Aug 31, 2016
1 parent 6b8cfc7 commit 325f7e4
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 45 deletions.
135 changes: 99 additions & 36 deletions apps/files/js/file-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* - TODO music upload button
*/

/* global jQuery, humanFileSize */
/* global jQuery, humanFileSize, md5 */

/**
* File upload object
Expand All @@ -34,12 +34,21 @@
OC.FileUpload = function(uploader, data) {
this.uploader = uploader;
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_OVERWRITE = 1;
OC.FileUpload.CONFLICT_MODE_AUTORENAME = 2;
OC.FileUpload.prototype = {

/**
* Unique upload id
*
* @type string
*/
id: null,

/**
* Upload element
*
Expand All @@ -66,6 +75,15 @@ OC.FileUpload.prototype = {
*/
_newName: null,

/**
* Returns the unique upload id
*
* @return string
*/
getId: function() {
return this.id;
},

/**
* Returns the file to be uploaded
*
Expand Down Expand Up @@ -143,6 +161,7 @@ OC.FileUpload.prototype = {
* Submit the upload
*/
submit: function() {
var self = this;
var data = this.data;
var file = this.getFile();

Expand Down Expand Up @@ -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
folderPromise.then(function() {
$.when(folderPromise, chunkFolderPromise).then(function() {
data.submit();
}, 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: 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();
},

Expand Down Expand Up @@ -280,7 +334,7 @@ OC.Uploader = function() {
this.init.apply(this, arguments);
};

OC.Uploader.prototype = {
OC.Uploader.prototype = _.extend({
/**
* @type Array<OC.FileUpload>
*/
Expand Down Expand Up @@ -384,7 +438,7 @@ OC.Uploader.prototype = {
self.filesClient.createDirectory(fullPath).always(function(status) {
// 405 is expected if the folder already exists
if ((status >= 200 && status < 300) || status === 405) {
self.$uploadEl.trigger($.Event('fileuploadcreatedfolder'), fullPath);
self.trigger('createdfolder', fullPath);
deferred.resolve();
return;
}
Expand All @@ -407,8 +461,8 @@ OC.Uploader.prototype = {
submitUploads: function(uploads) {
var self = this;
_.each(uploads, function(upload) {
upload.submit();
self._uploads[upload.data.uploadId] = upload;
upload.submit();
});
},

Expand Down Expand Up @@ -611,16 +665,6 @@ OC.Uploader.prototype = {
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
*
Expand Down Expand Up @@ -654,6 +698,12 @@ OC.Uploader.prototype = {

this.fileList = options.fileList;
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);
this.$uploadEl = $uploadEl;
Expand All @@ -668,6 +718,7 @@ OC.Uploader.prototype = {
dropZone: options.dropZone, // restrict dropZone to content div
autoUpload: false,
sequentialUploads: true,
maxChunkSize: 10000000,
//singleFileUploads is on by default, so the data.files array will always have length 1
/**
* on first add of every selection
Expand All @@ -691,7 +742,7 @@ OC.Uploader.prototype = {

var upload = new OC.FileUpload(self, data);
// 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
// starting the upload so we can check their existence
Expand Down Expand Up @@ -845,7 +896,10 @@ OC.Uploader.prototype = {
},
fail: function(e, data) {
var upload = self.getUpload(data);
var status = upload.getResponseStatus();
var status = null;
if (upload) {
status = upload.getResponseStatus();
}
self.log('fail', e, upload);

if (data.textStatus === 'abort') {
Expand Down Expand Up @@ -918,10 +972,7 @@ OC.Uploader.prototype = {
// add progress handlers
fileupload.on('fileuploadadd', function(e, data) {
self.log('progress handle fileuploadadd', e, data);
//show cancel button
//if (data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie?
// $('#uploadprogresswrapper .stop').show();
//}
self.trigger('add', e, data);
});
// add progress handlers
fileupload.on('fileuploadstart', function(e, data) {
Expand All @@ -937,10 +988,12 @@ OC.Uploader.prototype = {
+ '</span></em>');
$('#uploadprogressbar').tipsy({gravity:'n', fade:true, live:true});
self._showProgressBar();
self.trigger('start', e, data);
});
fileupload.on('fileuploadprogress', function(e, data) {
self.log('progress handle fileuploadprogress', e, data);
//TODO progressbar in row
self.trigger('progress', e, data);
});
fileupload.on('fileuploadprogressall', function(e, data) {
self.log('progress handle fileuploadprogressall', e, data);
Expand Down Expand Up @@ -1003,6 +1056,7 @@ OC.Uploader.prototype = {
})
);
$('#uploadprogressbar').progressbar('value', progress);
self.trigger('progressall', e, data);
});
fileupload.on('fileuploadstop', function(e, data) {
self.log('progress handle fileuploadstop', e, data);
Expand All @@ -1016,23 +1070,32 @@ OC.Uploader.prototype = {
if (data.errorThrown === 'abort') {
self._hideProgressBar();
}
self.trigger('fail', e, data);
});

} else {
// for all browsers that don't support the progress bar
// IE 8 & 9

// show a spinner
fileupload.on('fileuploadstart', function() {
$('#upload').addClass('icon-loading');
$('#upload .icon-upload').hide();
fileupload.on('fileuploadchunksend', function(e, data) {
// modify the request to adjust it to our own chunking
var upload = self.getUpload(data);
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'];
});

// hide a spinner
fileupload.on('fileuploadstop fileuploadfail', function() {
$('#upload').removeClass('icon-loading');
$('#upload .icon-upload').show();
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);
});

}
}

Expand All @@ -1050,6 +1113,6 @@ OC.Uploader.prototype = {

return this.fileUploadParam;
}
};
}, OC.Backbone.Events);


20 changes: 11 additions & 9 deletions apps/files/js/filelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -2623,16 +2623,18 @@

/**
* Setup file upload events related to the file-upload plugin
*
* @param {OC.Uploader} uploader
*/
setupUploadEvents: function($uploadEl) {
setupUploadEvents: function(uploader) {
var self = this;

self._uploads = {};

// 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);

if (self.$el.hasClass('hidden')) {
Expand Down Expand Up @@ -2695,7 +2697,7 @@
}
}
});
$uploadEl.on('fileuploadadd', function(e, data) {
uploader.on('add', function(e, data) {
self._uploader.log('filelist handle fileuploadadd', e, data);

// add ui visualization to existing folder
Expand Down Expand Up @@ -2727,16 +2729,16 @@
* when file upload done successfully add row to filelist
* 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);

var data = upload.data;
var status = data.jqXHR.status;
if (status < 200 || status >= 300) {
// error was handled in OC.Uploads already
return;
}

var upload = self._uploader.getUpload(data);
var fileName = upload.getFileName();
var fetchInfoPromise = self.addAndFetchFileInfo(fileName, upload.getFullPath());
if (!self._uploads) {
Expand All @@ -2746,10 +2748,10 @@
self._uploads[fileName] = fetchInfoPromise;
}
});
$uploadEl.on('fileuploadcreatedfolder', function(e, fullPath) {
uploader.on('createdfolder', function(e, fullPath) {
self.addAndFetchFileInfo(OC.basename(fullPath), OC.dirname(fullPath));
});
$uploadEl.on('fileuploadstop', function() {
uploader.on('stop', function() {
self._uploader.log('filelist handle fileuploadstop');

// prepare list of uploaded file names in the current directory
Expand All @@ -2765,7 +2767,7 @@
});
self.updateStorageStatistics();
});
$uploadEl.on('fileuploadfail', function(e, data) {
uploader.on('fail', function(e, data) {
self._uploader.log('filelist handle fileuploadfail', e, data);

self._uploads = [];
Expand Down

0 comments on commit 325f7e4

Please sign in to comment.