From 8d40ac23dafc29174020b06bfeeb3cfbcb2f229b Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 7 Oct 2016 16:27:54 +0200 Subject: [PATCH] Enable chunking for bigger files in authenticated web upload --- apps/files/appinfo/app.php | 2 + apps/files/js/app.js | 3 +- apps/files/js/file-upload.js | 71 ++++++++++++++++++++++++++++++------ apps/files/js/filelist.js | 3 +- apps/files/lib/App.php | 6 +++ core/js/config.php | 8 ++-- core/js/files/client.js | 19 ++++++++-- 7 files changed, 91 insertions(+), 21 deletions(-) diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php index 949f1d03ba99..26352795800e 100644 --- a/apps/files/appinfo/app.php +++ b/apps/files/appinfo/app.php @@ -58,3 +58,5 @@ \OC::$server->getConfig() ); }); + +\OCP\Util::connectHook('\OCP\Config', 'js', '\OCA\Files\App', 'extendJsConfig'); diff --git a/apps/files/js/app.js b/apps/files/js/app.js index d46a0b8f9df6..2b0ddf4f7903 100644 --- a/apps/files/js/app.js +++ b/apps/files/js/app.js @@ -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(); diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 459bfa53428c..e3a012dc3dc4 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -244,8 +244,8 @@ OC.FileUpload.prototype = { this.data.headers['X-OC-Mtime'] = file.lastModified / 1000; } - 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'] = @@ -258,7 +258,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 @@ -284,9 +284,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()) ); }, @@ -295,12 +304,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(); + } }, /** @@ -399,6 +416,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 @@ -758,6 +782,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() + }); if (options.url) { this.url = options.url; @@ -970,7 +1001,7 @@ OC.Uploader.prototype = _.extend({ } if (upload) { - upload.deleteUpload(); + upload.fail(); } }, /** @@ -1001,6 +1032,10 @@ OC.Uploader.prototype = _.extend({ } }; + if (options.maxChunkSize) { + this.fileUploadParam.maxChunkSize = options.maxChunkSize; + } + // initialize jquery fileupload (blueimp) var fileupload = this.$uploadEl.fileupload(this.fileUploadParam); @@ -1075,7 +1110,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) { @@ -1091,7 +1125,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) + @@ -1103,7 +1137,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) { diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 4bc9b665133b..177561ae0bbd 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -345,7 +345,8 @@ this._uploader = new OC.Uploader($uploadEl, { fileList: this, filesClient: this.filesClient, - dropZone: $('#content') + dropZone: $('#content'), + maxChunkSize: options.maxChunkSize }); this.setupUploadEvents(this._uploader); diff --git a/apps/files/lib/App.php b/apps/files/lib/App.php index f58d11ded769..ff1b7683858e 100644 --- a/apps/files/lib/App.php +++ b/apps/files/lib/App.php @@ -44,4 +44,10 @@ public static function getNavigationManager() { return self::$navigationManager; } + public static function extendJsConfig($array) { + $maxChunkSize = (int)(\OC::$server->getConfig()->getAppValue('files', 'max_chunk_size', (10 * 1024 * 1024))); + $array['array']['oc_appconfig']['files'] = [ + 'max_chunk_size' => $maxChunkSize + ]; + } } diff --git a/core/js/config.php b/core/js/config.php index 03bc196ccd1b..bc82b859b171 100644 --- a/core/js/config.php +++ b/core/js/config.php @@ -1,4 +1,5 @@ * @author Björn Schießle @@ -198,13 +199,14 @@ $array['oc_defaults']['docBaseUrl'] = $defaults->getDocBaseUrl(); $array['oc_defaults']['docPlaceholderUrl'] = $defaults->buildDocLinkToKey('PLACEHOLDER'); } -$array['oc_appconfig'] = json_encode($array['oc_appconfig']); -$array['oc_config'] = json_encode($array['oc_config']); -$array['oc_defaults'] = json_encode($array['oc_defaults']); // Allow hooks to modify the output values OC_Hook::emit('\OCP\Config', 'js', ['array' => &$array]); +$array['oc_appconfig'] = json_encode($array['oc_appconfig']); +$array['oc_config'] = json_encode($array['oc_config']); +$array['oc_defaults'] = json_encode($array['oc_defaults']); + // Echo it foreach ($array as $setting => $value) { echo("var ". $setting ."=".$value.";\n"); diff --git a/core/js/files/client.js b/core/js/files/client.js index d7d2447d34dc..c93b4903e10a 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -36,6 +36,7 @@ } url += options.host + this._root; + this._host = options.host; this._defaultHeaders = options.defaultHeaders || { 'X-Requested-With': 'XMLHttpRequest', 'requesttoken': OC.requestToken @@ -676,10 +677,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"'; } @@ -690,9 +692,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'; @@ -761,6 +763,16 @@ */ getBaseUrl: function() { return this._client.baseUrl; + }, + + /** + * Returns the host + * + * @since 10.0.3 + * @return {String} base URL + */ + getHost: function() { + return this._host; } }; @@ -798,7 +810,6 @@ var client = new OC.Files.Client({ host: OC.getHost(), - port: OC.getPort(), root: OC.linkToRemoteBase('webdav'), useHTTPS: OC.getProtocol() === 'https' });