Pārlūkot izejas kodu

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>
tags/v13.0.0beta1
Vincent Petry pirms 7 gadiem
vecāks
revīzija
cd8d13b9e6

+ 2
- 0
apps/files/appinfo/app.php Parādīt failu

@@ -57,3 +57,5 @@ $templateManager->registerTemplate('application/vnd.oasis.opendocument.spreadshe
'name' => $l->t('Recent'),
];
});

\OCP\Util::connectHook('\OCP\Config', 'js', '\OCA\Files\App', 'extendJsConfig');

+ 2
- 1
apps/files/js/app.js Parādīt failu

@@ -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();

+ 59
- 12
apps/files/js/file-upload.js Parādīt failu

@@ -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'] =
@@ -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
@@ -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())
);
},

@@ -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();
}
},

/**
@@ -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
@@ -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;
@@ -920,7 +951,7 @@ OC.Uploader.prototype = _.extend({
}

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)
var fileupload = this.$uploadEl.fileupload(this.fileUploadParam);

@@ -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) {
@@ -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) +
@@ -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) {

+ 2
- 1
apps/files/js/filelist.js Parādīt failu

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

+ 10
- 0
apps/files/lib/App.php Parādīt failu

@@ -53,4 +53,14 @@ class App {
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);
}
}

+ 15
- 3
core/js/files/client.js Parādīt failu

@@ -37,6 +37,7 @@
}

url += options.host + this._root;
this._host = options.host;
this._defaultHeaders = options.defaultHeaders || {
'X-Requested-With': 'XMLHttpRequest',
'requesttoken': OC.requestToken
@@ -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"';
}
@@ -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';
@@ -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;
}
};


Notiek ielāde…
Atcelt
Saglabāt