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 <danxuliu@gmail.com>
This commit is contained in:
parent
8ee765a617
commit
cd8d13b9e6
|
@ -57,3 +57,5 @@ $templateManager->registerTemplate('application/vnd.oasis.opendocument.spreadshe
|
||||||
'name' => $l->t('Recent'),
|
'name' => $l->t('Recent'),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
\OCP\Util::connectHook('\OCP\Config', 'js', '\OCA\Files\App', 'extendJsConfig');
|
||||||
|
|
|
@ -93,7 +93,8 @@
|
||||||
direction: $('#defaultFileSortingDirection').val()
|
direction: $('#defaultFileSortingDirection').val()
|
||||||
},
|
},
|
||||||
config: this._filesConfig,
|
config: this._filesConfig,
|
||||||
enableUpload: true
|
enableUpload: true,
|
||||||
|
maxChunkSize: OC.appConfig.files && OC.appConfig.files.max_chunk_size
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.files.initialize();
|
this.files.initialize();
|
||||||
|
|
|
@ -220,8 +220,8 @@ OC.FileUpload.prototype = {
|
||||||
this.data.headers['If-None-Match'] = '*';
|
this.data.headers['If-None-Match'] = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
var userName = this.uploader.filesClient.getUserName();
|
var userName = this.uploader.davClient.getUserName();
|
||||||
var password = this.uploader.filesClient.getPassword();
|
var password = this.uploader.davClient.getPassword();
|
||||||
if (userName) {
|
if (userName) {
|
||||||
// copy username/password from DAV client
|
// copy username/password from DAV client
|
||||||
this.data.headers['Authorization'] =
|
this.data.headers['Authorization'] =
|
||||||
|
@ -234,7 +234,7 @@ OC.FileUpload.prototype = {
|
||||||
&& this.getFile().size > this.uploader.fileUploadParam.maxChunkSize
|
&& this.getFile().size > this.uploader.fileUploadParam.maxChunkSize
|
||||||
) {
|
) {
|
||||||
data.isChunked = true;
|
data.isChunked = true;
|
||||||
chunkFolderPromise = this.uploader.filesClient.createDirectory(
|
chunkFolderPromise = this.uploader.davClient.createDirectory(
|
||||||
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
|
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
|
||||||
);
|
);
|
||||||
// TODO: if fails, it means same id already existed, need to retry
|
// TODO: if fails, it means same id already existed, need to retry
|
||||||
|
@ -260,9 +260,18 @@ OC.FileUpload.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
var uid = OC.getCurrentUser().uid;
|
var uid = OC.getCurrentUser().uid;
|
||||||
return this.uploader.filesClient.move(
|
return this.uploader.davClient.move(
|
||||||
'uploads/' + encodeURIComponent(uid) + '/' + encodeURIComponent(this.getId()) + '/.file',
|
'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())
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -271,12 +280,20 @@ OC.FileUpload.prototype = {
|
||||||
*/
|
*/
|
||||||
abort: function() {
|
abort: function() {
|
||||||
if (this.data.isChunked) {
|
if (this.data.isChunked) {
|
||||||
// delete transfer directory for this upload
|
this._deleteChunkFolder();
|
||||||
this.uploader.filesClient.remove(
|
|
||||||
'uploads/' + encodeURIComponent(OC.getCurrentUser().uid) + '/' + encodeURIComponent(this.getId())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.data.abort();
|
this.data.abort();
|
||||||
|
this.deleteUpload();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fail the upload
|
||||||
|
*/
|
||||||
|
fail: function() {
|
||||||
|
this.deleteUpload();
|
||||||
|
if (this.data.isChunked) {
|
||||||
|
this._deleteChunkFolder();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -375,6 +392,13 @@ OC.Uploader.prototype = _.extend({
|
||||||
*/
|
*/
|
||||||
filesClient: null,
|
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
|
* 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
|
* @link https://github.com/New-Bamboo/example-ajax-upload/blob/master/public/index.html
|
||||||
|
@ -721,6 +745,13 @@ OC.Uploader.prototype = _.extend({
|
||||||
|
|
||||||
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: this.filesClient.getHost(),
|
||||||
|
root: OC.linkToRemoteBase('dav'),
|
||||||
|
useHTTPS: OC.getProtocol() === 'https',
|
||||||
|
userName: this.filesClient.getUserName(),
|
||||||
|
password: this.filesClient.getPassword()
|
||||||
|
});
|
||||||
|
|
||||||
$uploadEl = $($uploadEl);
|
$uploadEl = $($uploadEl);
|
||||||
this.$uploadEl = $uploadEl;
|
this.$uploadEl = $uploadEl;
|
||||||
|
@ -920,7 +951,7 @@ OC.Uploader.prototype = _.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (upload) {
|
if (upload) {
|
||||||
upload.deleteUpload();
|
upload.fail();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -951,6 +982,10 @@ OC.Uploader.prototype = _.extend({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (options.maxChunkSize) {
|
||||||
|
this.fileUploadParam.maxChunkSize = options.maxChunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
// initialize jquery fileupload (blueimp)
|
// initialize jquery fileupload (blueimp)
|
||||||
var fileupload = this.$uploadEl.fileupload(this.fileUploadParam);
|
var fileupload = this.$uploadEl.fileupload(this.fileUploadParam);
|
||||||
|
|
||||||
|
@ -1041,7 +1076,6 @@ OC.Uploader.prototype = _.extend({
|
||||||
self.log('progress handle fileuploadstop', e, data);
|
self.log('progress handle fileuploadstop', e, data);
|
||||||
|
|
||||||
self.clear();
|
self.clear();
|
||||||
self._hideProgressBar();
|
|
||||||
self.trigger('stop', e, data);
|
self.trigger('stop', e, data);
|
||||||
});
|
});
|
||||||
fileupload.on('fileuploadfail', function(e, data) {
|
fileupload.on('fileuploadfail', function(e, data) {
|
||||||
|
@ -1096,7 +1130,7 @@ OC.Uploader.prototype = _.extend({
|
||||||
// modify the request to adjust it to our own chunking
|
// modify the request to adjust it to our own chunking
|
||||||
var upload = self.getUpload(data);
|
var upload = self.getUpload(data);
|
||||||
var range = data.contentRange.split(' ')[1];
|
var range = data.contentRange.split(' ')[1];
|
||||||
var chunkId = range.split('/')[0];
|
var chunkId = range.split('/')[0].split('-')[0];
|
||||||
data.url = OC.getRootPath() +
|
data.url = OC.getRootPath() +
|
||||||
'/remote.php/dav/uploads' +
|
'/remote.php/dav/uploads' +
|
||||||
'/' + encodeURIComponent(OC.getCurrentUser().uid) +
|
'/' + encodeURIComponent(OC.getCurrentUser().uid) +
|
||||||
|
@ -1108,7 +1142,20 @@ OC.Uploader.prototype = _.extend({
|
||||||
fileupload.on('fileuploaddone', function(e, data) {
|
fileupload.on('fileuploaddone', function(e, data) {
|
||||||
var upload = self.getUpload(data);
|
var upload = self.getUpload(data);
|
||||||
upload.done().then(function() {
|
upload.done().then(function() {
|
||||||
|
self._hideProgressBar();
|
||||||
self.trigger('done', e, upload);
|
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) {
|
fileupload.on('fileuploaddrop', function(e, data) {
|
||||||
|
|
|
@ -357,7 +357,8 @@
|
||||||
this._uploader = new OC.Uploader($uploadEl, {
|
this._uploader = new OC.Uploader($uploadEl, {
|
||||||
fileList: this,
|
fileList: this,
|
||||||
filesClient: this.filesClient,
|
filesClient: this.filesClient,
|
||||||
dropZone: $('#content')
|
dropZone: $('#content'),
|
||||||
|
maxChunkSize: options.maxChunkSize
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setupUploadEvents(this._uploader);
|
this.setupUploadEvents(this._uploader);
|
||||||
|
|
|
@ -53,4 +53,14 @@ class App {
|
||||||
return self::$navigationManager;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
url += options.host + this._root;
|
url += options.host + this._root;
|
||||||
|
this._host = options.host;
|
||||||
this._defaultHeaders = options.defaultHeaders || {
|
this._defaultHeaders = options.defaultHeaders || {
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
'requesttoken': OC.requestToken
|
'requesttoken': OC.requestToken
|
||||||
|
@ -698,10 +699,11 @@
|
||||||
* @param {String} destinationPath destination path
|
* @param {String} destinationPath destination path
|
||||||
* @param {boolean} [allowOverwrite=false] true to allow overwriting,
|
* @param {boolean} [allowOverwrite=false] true to allow overwriting,
|
||||||
* false otherwise
|
* false otherwise
|
||||||
|
* @param {Object} [headers=null] additional headers
|
||||||
*
|
*
|
||||||
* @return {Promise} promise
|
* @return {Promise} promise
|
||||||
*/
|
*/
|
||||||
move: function(path, destinationPath, allowOverwrite) {
|
move: function(path, destinationPath, allowOverwrite, headers) {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
throw 'Missing argument "path"';
|
throw 'Missing argument "path"';
|
||||||
}
|
}
|
||||||
|
@ -712,9 +714,9 @@
|
||||||
var self = this;
|
var self = this;
|
||||||
var deferred = $.Deferred();
|
var deferred = $.Deferred();
|
||||||
var promise = deferred.promise();
|
var promise = deferred.promise();
|
||||||
var headers = {
|
headers = _.extend({}, headers, {
|
||||||
'Destination' : this._buildUrl(destinationPath)
|
'Destination' : this._buildUrl(destinationPath)
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!allowOverwrite) {
|
if (!allowOverwrite) {
|
||||||
headers.Overwrite = 'F';
|
headers.Overwrite = 'F';
|
||||||
|
@ -828,6 +830,16 @@
|
||||||
*/
|
*/
|
||||||
getBaseUrl: function() {
|
getBaseUrl: function() {
|
||||||
return this._client.baseUrl;
|
return this._client.baseUrl;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the host
|
||||||
|
*
|
||||||
|
* @since 13.0.0
|
||||||
|
* @return {String} base URL
|
||||||
|
*/
|
||||||
|
getHost: function() {
|
||||||
|
return this._host;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue