Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Enable chunking for bigger files in authenticated web upload
This commit adds chunked uploads in the Web UI (for authenticated users,
but not for public uploads). To do that the server endpoint used by the
uploader is changed from WebDAV v1 to WebDAV v2. The chunking itself is
done automatically by the jQuery-File-Upload plugin when the
"maxChunkSize" parameter is set; in "fileuploadchunksend" the request is
adjusted to adapt the behaviour of the plugin to the one expected by
"uploads/" in WebDAV v2.

The chunk size to be used by the Web UI can be set in the
"max_chunk_size" parameter of the Files app configuration. By default it
is set to 10MiB.

Signed-off-by: Daniel Calviño Sánchez <[email protected]>
  • Loading branch information
Vincent Petry authored and danxuliu committed Nov 3, 2017
commit cd8d13b9e6e7bbf0e17f31c60021804ac4772939
2 changes: 2 additions & 0 deletions apps/files/appinfo/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@
'name' => $l->t('Recent'),
];
});

\OCP\Util::connectHook('\OCP\Config', 'js', '\OCA\Files\App', 'extendJsConfig');
3 changes: 2 additions & 1 deletion apps/files/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
direction: $('#defaultFileSortingDirection').val()
},
config: this._filesConfig,
enableUpload: true
enableUpload: true,
maxChunkSize: OC.appConfig.files && OC.appConfig.files.max_chunk_size
}
);
this.files.initialize();
Expand Down
71 changes: 59 additions & 12 deletions apps/files/js/file-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ OC.FileUpload.prototype = {
this.data.headers['If-None-Match'] = '*';
}

var userName = this.uploader.filesClient.getUserName();
var password = this.uploader.filesClient.getPassword();
var userName = this.uploader.davClient.getUserName();
var password = this.uploader.davClient.getPassword();
if (userName) {
// copy username/password from DAV client
this.data.headers['Authorization'] =
Expand All @@ -234,7 +234,7 @@ OC.FileUpload.prototype = {
&& this.getFile().size > this.uploader.fileUploadParam.maxChunkSize
) {
data.isChunked = true;
chunkFolderPromise = this.uploader.filesClient.createDirectory(
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
Expand All @@ -260,9 +260,18 @@ OC.FileUpload.prototype = {
}

var uid = OC.getCurrentUser().uid;
return this.uploader.filesClient.move(
return this.uploader.davClient.move(
'uploads/' + encodeURIComponent(uid) + '/' + encodeURIComponent(this.getId()) + '/.file',
'files/' + encodeURIComponent(uid) + '/' + OC.joinPaths(this.getFullPath(), this.getFileName())
'files/' + encodeURIComponent(uid) + '/' + OC.joinPaths(this.getFullPath(), this.getFileName()),
true,
{'X-OC-Mtime': this.getFile().lastModified / 1000}
);
},

_deleteChunkFolder: function() {
// delete transfer directory for this upload
this.uploader.davClient.remove(
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
);
},

Expand All @@ -271,12 +280,20 @@ OC.FileUpload.prototype = {
*/
abort: function() {
if (this.data.isChunked) {
// delete transfer directory for this upload
this.uploader.filesClient.remove(
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
);
this._deleteChunkFolder();
}
this.data.abort();
this.deleteUpload();
},

/**
* Fail the upload
*/
fail: function() {
this.deleteUpload();
if (this.data.isChunked) {
this._deleteChunkFolder();
}
},

/**
Expand Down Expand Up @@ -375,6 +392,13 @@ OC.Uploader.prototype = _.extend({
*/
filesClient: null,

/**
* Webdav client pointing at the root "dav" endpoint
*
* @type OC.Files.Client
*/
davClient: null,

/**
* Function that will allow us to know if Ajax uploads are supported
* @link https://github.com/New-Bamboo/example-ajax-upload/blob/master/public/index.html
Expand Down Expand Up @@ -721,6 +745,13 @@ OC.Uploader.prototype = _.extend({

this.fileList = options.fileList;
this.filesClient = options.filesClient || OC.Files.getClient();
this.davClient = new OC.Files.Client({
host: this.filesClient.getHost(),
root: OC.linkToRemoteBase('dav'),
useHTTPS: OC.getProtocol() === 'https',
userName: this.filesClient.getUserName(),
password: this.filesClient.getPassword()
});

$uploadEl = $($uploadEl);
this.$uploadEl = $uploadEl;
Expand Down Expand Up @@ -920,7 +951,7 @@ OC.Uploader.prototype = _.extend({
}

if (upload) {
upload.deleteUpload();
upload.fail();
}
},
/**
Expand Down Expand Up @@ -951,6 +982,10 @@ OC.Uploader.prototype = _.extend({
}
};

if (options.maxChunkSize) {
this.fileUploadParam.maxChunkSize = options.maxChunkSize;
}

// initialize jquery fileupload (blueimp)
var fileupload = this.$uploadEl.fileupload(this.fileUploadParam);

Expand Down Expand Up @@ -1041,7 +1076,6 @@ OC.Uploader.prototype = _.extend({
self.log('progress handle fileuploadstop', e, data);

self.clear();
self._hideProgressBar();
self.trigger('stop', e, data);
});
fileupload.on('fileuploadfail', function(e, data) {
Expand Down Expand Up @@ -1096,7 +1130,7 @@ OC.Uploader.prototype = _.extend({
// 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];
var chunkId = range.split('/')[0].split('-')[0];
data.url = OC.getRootPath() +
'/remote.php/dav/uploads' +
'/' + encodeURIComponent(OC.getCurrentUser().uid) +
Expand All @@ -1108,7 +1142,20 @@ OC.Uploader.prototype = _.extend({
fileupload.on('fileuploaddone', function(e, data) {
var upload = self.getUpload(data);
upload.done().then(function() {
self._hideProgressBar();
self.trigger('done', e, upload);
}).fail(function(status) {
self._hideProgressBar();
if (status === 507) {
// not enough space
OC.Notification.show(t('files', 'Not enough free space'), {type: 'error'});
self.cancelUploads();
} else if (status === 409) {
OC.Notification.show(t('files', 'Target folder does not exist any more'), {type: 'error'});
} else {
OC.Notification.show(t('files', 'Error when assembling chunks, status code {status}', {status: status}), {type: 'error'});
}
self.trigger('fail', e, data);
});
});
fileupload.on('fileuploaddrop', function(e, data) {
Expand Down
3 changes: 2 additions & 1 deletion apps/files/js/filelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@
this._uploader = new OC.Uploader($uploadEl, {
fileList: this,
filesClient: this.filesClient,
dropZone: $('#content')
dropZone: $('#content'),
maxChunkSize: options.maxChunkSize
});

this.setupUploadEvents(this._uploader);
Expand Down
10 changes: 10 additions & 0 deletions apps/files/lib/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,14 @@ public static function getNavigationManager() {
return self::$navigationManager;
}

public static function extendJsConfig($settings) {
$appConfig = json_decode($settings['array']['oc_appconfig'], true);

$maxChunkSize = (int)(\OC::$server->getConfig()->getAppValue('files', 'max_chunk_size', (10 * 1024 * 1024)));
$appConfig['files'] = [
'max_chunk_size' => $maxChunkSize
];

$settings['array']['oc_appconfig'] = json_encode($appConfig);
}
}
18 changes: 15 additions & 3 deletions core/js/files/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
}

url += options.host + this._root;
this._host = options.host;
this._defaultHeaders = options.defaultHeaders || {
'X-Requested-With': 'XMLHttpRequest',
'requesttoken': OC.requestToken
Expand Down Expand Up @@ -698,10 +699,11 @@
* @param {String} destinationPath destination path
* @param {boolean} [allowOverwrite=false] true to allow overwriting,
* false otherwise
* @param {Object} [headers=null] additional headers
*
* @return {Promise} promise
*/
move: function(path, destinationPath, allowOverwrite) {
move: function(path, destinationPath, allowOverwrite, headers) {
if (!path) {
throw 'Missing argument "path"';
}
Expand All @@ -712,9 +714,9 @@
var self = this;
var deferred = $.Deferred();
var promise = deferred.promise();
var headers = {
headers = _.extend({}, headers, {
'Destination' : this._buildUrl(destinationPath)
};
});

if (!allowOverwrite) {
headers.Overwrite = 'F';
Expand Down Expand Up @@ -828,6 +830,16 @@
*/
getBaseUrl: function() {
return this._client.baseUrl;
},

/**
* Returns the host
*
* @since 13.0.0
* @return {String} base URL
*/
getHost: function() {
return this._host;
}
};

Expand Down