diff --git a/packages/file-handling/README.md b/packages/file-handling/README.md index 0c5d44dbe9398cc13e1d65ccbf2099148c589ece..535965548bdd0d30dd6ce0d199be721cea6cd9dc 100644 --- a/packages/file-handling/README.md +++ b/packages/file-handling/README.md @@ -2,6 +2,8 @@ [GitLab Repository](https://gitlab.tugraz.at/VPU/WebComponents/FileUpload) +Files will be uploaded sequentially (not parallel) to prevent overburdening the destination server. + ## Usage ```html @@ -19,13 +21,15 @@ - `deferred` (optional): if set files will not be uploaded immediately but only queued - use method `uploadFile` or `uploadOneQueuedFile` to really upload the queued file - example `<vpu-fileupload deferred></vpu-fileupload>` -- `allowed-mime-types` (optional): if set accept only files matching mime types +- `allowed-mime-types` (optional): if set accepts only files matching mime types - example `<vpu-fileupload allowed-mime-types='application/pdf'></vpu-fileupload>` ... PDFs only - example `<vpu-fileupload allowed-mime-types='image/*'></vpu-fileupload>` ... images (of all sub types) only - example `<vpu-fileupload allowed-mime-types='image/png,text/plain'></vpu-fileupload>` ... PNGs or TXTs only - example `<vpu-fileupload allowed-mime-types='*/*'></vpu-fileupload>` ... all file types (default) - `disabled` (optional): disable input control - example `<vpu-fileupload disabled>` +- `decompress-zip` (optional): decompress zip file and queue the contained files + - example `<vpu-fileupload decompress-zip>` ## Local development diff --git a/packages/file-handling/src/fileupload.js b/packages/file-handling/src/fileupload.js index 5349f93ac8b652aed790a644fb01f551bf28af56..42559432628673de165aa82fcb23d32c5fecac13 100644 --- a/packages/file-handling/src/fileupload.js +++ b/packages/file-handling/src/fileupload.js @@ -45,6 +45,7 @@ export class FileUpload extends ScopedElementsMixin(VPULitElement) { this.queuedFiles = []; this.queuedFilesCount = 0; this.disabled = false; + this.decompressZip = false; this._queueKey = 0; } @@ -64,13 +65,14 @@ export class FileUpload extends ScopedElementsMixin(VPULitElement) { url: { type: String }, allowedMimeTypes: { type: String, attribute: 'allowed-mime-types' }, text: { type: String }, - buttonLabel: { type: String, attribute: 'button-label'}, - uploadInProgress: { type: Boolean, attribute: false}, - multipleUploadInProgress: { type: Boolean, attribute: false}, - alwaysSendFile: { type: Boolean, attribute: 'always-send-file'}, - isDeferred: { type: Boolean, attribute: 'deferred'}, + buttonLabel: { type: String, attribute: 'button-label' }, + uploadInProgress: { type: Boolean, attribute: false }, + multipleUploadInProgress: { type: Boolean, attribute: false }, + alwaysSendFile: { type: Boolean, attribute: 'always-send-file' }, + isDeferred: { type: Boolean, attribute: 'deferred' }, queuedFilesCount: { type: Number, attribute: false }, disabled: { type: Boolean }, + decompressZip: { type: Boolean, attribute: 'decompress-zip' }, }; } @@ -174,19 +176,15 @@ export class FileUpload extends ScopedElementsMixin(VPULitElement) { console.log('file \'' + file.name + '\' has size=0 and is denied!') return; } - if (this.allowedMimeTypes) { - // check if file is allowed - const [fileMainType, fileSubType] = file.type.split('/'); - const mimeTypes = this.allowedMimeTypes.split(','); - let deny = true; - mimeTypes.forEach((str) => { - const [mainType, subType] = str.split('/'); - deny = deny && ((mainType !== '*' && mainType !== fileMainType) || (subType !== '*' && subType !== fileSubType)); - }); - if (deny) { - console.log(`mime type ${file.type} of file '${file.name}' is not compatible with ${this.allowedMimeTypes}`); - return; - } + + // check if we want to decompress the zip and queue the contained files + if (this.decompressZip && file.type === "application/zip") { + // add decompressed files to tempFilesToHandle + tempFilesToHandle = tempFilesToHandle.concat(await this.decompressZIP(file)); + + return; + } else if (this.allowedMimeTypes && !this.checkFileType(file)) { + return; } tempFilesToHandle.push(file); @@ -206,6 +204,66 @@ export class FileUpload extends ScopedElementsMixin(VPULitElement) { }, 100); } + checkFileType(file) { + // check if file is allowed + const [fileMainType, fileSubType] = file.type.split('/'); + const mimeTypes = this.allowedMimeTypes.split(','); + let deny = true; + + mimeTypes.forEach((str) => { + const [mainType, subType] = str.split('/'); + deny = deny && ((mainType !== '*' && mainType !== fileMainType) || (subType !== '*' && subType !== fileSubType)); + }); + + if (deny) { + console.log(`mime type ${file.type} of file '${file.name}' is not compatible with ${this.allowedMimeTypes}`); + + return false; + } + + return true; + } + + /** + * Decompress files synchronously + * + * @param file + * @returns {Promise<[]>} + */ + async decompressZIP(file) { + // see: https://stuk.github.io/jszip/ + let JSZip = (await import('jszip/dist/jszip.js')).default; + let filesToHandle = []; + + // load zip file + await JSZip.loadAsync(file) + .then(async (zip) => { + // we are not using zip.forEach because we need to handle those files synchronously which + // isn't supported by JSZip (see https://github.com/Stuk/jszip/issues/281) + // using zip.files directly works great! + await commonUtils.asyncObjectForEach(zip.files, async (zipEntry) => { + // TODO: find way to check mime type, see https://github.com/Stuk/jszip/issues/626 + // if (!this.checkFileType(zipEntry)) { + // return; + // } + + await zipEntry.async("blob") + .then((blob) => { + blob.name = zipEntry.name; + filesToHandle.push(blob); + }, (e) => { + // handle the error + console.error("Decompressing of file in " + file.name + " failed:" + e.message); + }); + }); + }, function (e) { + // handle the error + console.error("Loading of " + file.name + " failed:" + e.message); + }); + + return filesToHandle; + } + async sendFinishedEvent(response, file, sendFile = false) { if (response === undefined) { return;