Skip to content

Commit 5d3b31c

Browse files
Vincent Petrydanxuliu
authored andcommitted
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.
1 parent 8ee765a commit 5d3b31c

File tree

6 files changed

+90
-17
lines changed

6 files changed

+90
-17
lines changed

apps/files/appinfo/app.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@
5757
'name' => $l->t('Recent'),
5858
];
5959
});
60+
61+
\OCP\Util::connectHook('\OCP\Config', 'js', '\OCA\Files\App', 'extendJsConfig');

apps/files/js/app.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@
9393
direction: $('#defaultFileSortingDirection').val()
9494
},
9595
config: this._filesConfig,
96-
enableUpload: true
96+
enableUpload: true,
97+
maxChunkSize: OC.appConfig.files && OC.appConfig.files.max_chunk_size
9798
}
9899
);
99100
this.files.initialize();

apps/files/js/file-upload.js

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ OC.FileUpload.prototype = {
220220
this.data.headers['If-None-Match'] = '*';
221221
}
222222

223-
var userName = this.uploader.filesClient.getUserName();
224-
var password = this.uploader.filesClient.getPassword();
223+
var userName = this.uploader.davClient.getUserName();
224+
var password = this.uploader.davClient.getPassword();
225225
if (userName) {
226226
// copy username/password from DAV client
227227
this.data.headers['Authorization'] =
@@ -234,7 +234,7 @@ OC.FileUpload.prototype = {
234234
&& this.getFile().size > this.uploader.fileUploadParam.maxChunkSize
235235
) {
236236
data.isChunked = true;
237-
chunkFolderPromise = this.uploader.filesClient.createDirectory(
237+
chunkFolderPromise = this.uploader.davClient.createDirectory(
238238
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
239239
);
240240
// TODO: if fails, it means same id already existed, need to retry
@@ -260,9 +260,18 @@ OC.FileUpload.prototype = {
260260
}
261261

262262
var uid = OC.getCurrentUser().uid;
263-
return this.uploader.filesClient.move(
263+
return this.uploader.davClient.move(
264264
'uploads/' + encodeURIComponent(uid) + '/' + encodeURIComponent(this.getId()) + '/.file',
265-
'files/' + encodeURIComponent(uid) + '/' + OC.joinPaths(this.getFullPath(), this.getFileName())
265+
'files/' + encodeURIComponent(uid) + '/' + OC.joinPaths(this.getFullPath(), this.getFileName()),
266+
true,
267+
{'X-OC-Mtime': this.getFile().lastModified / 1000}
268+
);
269+
},
270+
271+
_deleteChunkFolder: function() {
272+
// delete transfer directory for this upload
273+
this.uploader.davClient.remove(
274+
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
266275
);
267276
},
268277

@@ -271,12 +280,20 @@ OC.FileUpload.prototype = {
271280
*/
272281
abort: function() {
273282
if (this.data.isChunked) {
274-
// delete transfer directory for this upload
275-
this.uploader.filesClient.remove(
276-
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
277-
);
283+
this._deleteChunkFolder();
278284
}
279285
this.data.abort();
286+
this.deleteUpload();
287+
},
288+
289+
/**
290+
* Fail the upload
291+
*/
292+
fail: function() {
293+
this.deleteUpload();
294+
if (this.data.isChunked) {
295+
this._deleteChunkFolder();
296+
}
280297
},
281298

282299
/**
@@ -375,6 +392,13 @@ OC.Uploader.prototype = _.extend({
375392
*/
376393
filesClient: null,
377394

395+
/**
396+
* Webdav client pointing at the root "dav" endpoint
397+
*
398+
* @type OC.Files.Client
399+
*/
400+
davClient: null,
401+
378402
/**
379403
* Function that will allow us to know if Ajax uploads are supported
380404
* @link https://github.com/New-Bamboo/example-ajax-upload/blob/master/public/index.html
@@ -721,6 +745,13 @@ OC.Uploader.prototype = _.extend({
721745

722746
this.fileList = options.fileList;
723747
this.filesClient = options.filesClient || OC.Files.getClient();
748+
this.davClient = new OC.Files.Client({
749+
host: this.filesClient.getHost(),
750+
root: OC.linkToRemoteBase('dav'),
751+
useHTTPS: OC.getProtocol() === 'https',
752+
userName: this.filesClient.getUserName(),
753+
password: this.filesClient.getPassword()
754+
});
724755

725756
$uploadEl = $($uploadEl);
726757
this.$uploadEl = $uploadEl;
@@ -920,7 +951,7 @@ OC.Uploader.prototype = _.extend({
920951
}
921952

922953
if (upload) {
923-
upload.deleteUpload();
954+
upload.fail();
924955
}
925956
},
926957
/**
@@ -951,6 +982,10 @@ OC.Uploader.prototype = _.extend({
951982
}
952983
};
953984

985+
if (options.maxChunkSize) {
986+
this.fileUploadParam.maxChunkSize = options.maxChunkSize;
987+
}
988+
954989
// initialize jquery fileupload (blueimp)
955990
var fileupload = this.$uploadEl.fileupload(this.fileUploadParam);
956991

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

10431078
self.clear();
1044-
self._hideProgressBar();
10451079
self.trigger('stop', e, data);
10461080
});
10471081
fileupload.on('fileuploadfail', function(e, data) {
@@ -1096,7 +1130,7 @@ OC.Uploader.prototype = _.extend({
10961130
// modify the request to adjust it to our own chunking
10971131
var upload = self.getUpload(data);
10981132
var range = data.contentRange.split(' ')[1];
1099-
var chunkId = range.split('/')[0];
1133+
var chunkId = range.split('/')[0].split('-')[0];
11001134
data.url = OC.getRootPath() +
11011135
'/remote.php/dav/uploads' +
11021136
'/' + encodeURIComponent(OC.getCurrentUser().uid) +
@@ -1108,7 +1142,20 @@ OC.Uploader.prototype = _.extend({
11081142
fileupload.on('fileuploaddone', function(e, data) {
11091143
var upload = self.getUpload(data);
11101144
upload.done().then(function() {
1145+
self._hideProgressBar();
11111146
self.trigger('done', e, upload);
1147+
}).fail(function(status) {
1148+
self._hideProgressBar();
1149+
if (status === 507) {
1150+
// not enough space
1151+
OC.Notification.show(t('files', 'Not enough free space'), {type: 'error'});
1152+
self.cancelUploads();
1153+
} else if (status === 409) {
1154+
OC.Notification.show(t('files', 'Target folder does not exist any more'), {type: 'error'});
1155+
} else {
1156+
OC.Notification.show(t('files', 'Error when assembling chunks, status code {status}', {status: status}), {type: 'error'});
1157+
}
1158+
self.trigger('fail', e, data);
11121159
});
11131160
});
11141161
fileupload.on('fileuploaddrop', function(e, data) {

apps/files/js/filelist.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,8 @@
357357
this._uploader = new OC.Uploader($uploadEl, {
358358
fileList: this,
359359
filesClient: this.filesClient,
360-
dropZone: $('#content')
360+
dropZone: $('#content'),
361+
maxChunkSize: options.maxChunkSize
361362
});
362363

363364
this.setupUploadEvents(this._uploader);

apps/files/lib/App.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,14 @@ public static function getNavigationManager() {
5353
return self::$navigationManager;
5454
}
5555

56+
public static function extendJsConfig($settings) {
57+
$appConfig = json_decode($settings['array']['oc_appconfig'], true);
58+
59+
$maxChunkSize = (int)(\OC::$server->getConfig()->getAppValue('files', 'max_chunk_size', (10 * 1024 * 1024)));
60+
$appConfig['files'] = [
61+
'max_chunk_size' => $maxChunkSize
62+
];
63+
64+
$settings['array']['oc_appconfig'] = json_encode($appConfig);
65+
}
5666
}

core/js/files/client.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
}
3838

3939
url += options.host + this._root;
40+
this._host = options.host;
4041
this._defaultHeaders = options.defaultHeaders || {
4142
'X-Requested-With': 'XMLHttpRequest',
4243
'requesttoken': OC.requestToken
@@ -698,10 +699,11 @@
698699
* @param {String} destinationPath destination path
699700
* @param {boolean} [allowOverwrite=false] true to allow overwriting,
700701
* false otherwise
702+
* @param {Object} [headers=null] additional headers
701703
*
702704
* @return {Promise} promise
703705
*/
704-
move: function(path, destinationPath, allowOverwrite) {
706+
move: function(path, destinationPath, allowOverwrite, headers) {
705707
if (!path) {
706708
throw 'Missing argument "path"';
707709
}
@@ -712,9 +714,9 @@
712714
var self = this;
713715
var deferred = $.Deferred();
714716
var promise = deferred.promise();
715-
var headers = {
717+
headers = _.extend({}, headers, {
716718
'Destination' : this._buildUrl(destinationPath)
717-
};
719+
});
718720

719721
if (!allowOverwrite) {
720722
headers.Overwrite = 'F';
@@ -828,6 +830,16 @@
828830
*/
829831
getBaseUrl: function() {
830832
return this._client.baseUrl;
833+
},
834+
835+
/**
836+
* Returns the host
837+
*
838+
* @since 13.0.0
839+
* @return {String} base URL
840+
*/
841+
getHost: function() {
842+
return this._host;
831843
}
832844
};
833845

0 commit comments

Comments
 (0)