From 328fa1116706ac6ced2176428b2cfa6e6431620b Mon Sep 17 00:00:00 2001 From: Salim Terres Date: Wed, 22 Jan 2025 16:45:02 +0100 Subject: [PATCH] Bug #14108: Fix bugs (Incorrect size: When uploading a directory from the file explorer, the displayed size is 0 bytes, Data loss: Several pieces of data are lost after validation.) Bug #14108: In drag & drop, handling empty folders (If the empty folder is located inside another parent directory, an error message will be displayed). Bug #14108: Change wording. Bug #14108: With upload files/directories, handling empty folders (If the empty folder is located inside another parent directory, an error message will be displayed). Bug #14108: fix removing directories in drag& drop. Bug #14108: fix TUs front. Bug #14108: refactoring - rename custome file. --- .../add-units/add-units.component.html | 1 + .../add-units/add-units.component.ts | 8 +- .../create-project.component.html | 1 + .../create-project.component.ts | 9 +- .../projects/collect/src/assets/i18n/en.json | 1 + .../projects/collect/src/assets/i18n/fr.json | 1 + .../file-selector.component.html | 2 +- .../file-selector.component.spec.ts | 2 + .../file-selector/file-selector.component.ts | 101 ++++++++++++++---- .../drag-and-drop/drag-and-drop.directive.ts | 52 ++++++++- .../vitamui-library/src/lib/models/file.ts | 40 +++++++ .../src/lib/models/zip/zip-file.class.ts | 46 ++++++-- 12 files changed, 228 insertions(+), 36 deletions(-) create mode 100644 ui/ui-frontend/projects/vitamui-library/src/lib/models/file.ts diff --git a/ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.html b/ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.html index c96a69c6c18..8126c553140 100644 --- a/ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.html +++ b/ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.html @@ -18,6 +18,7 @@ [multipleFiles]="true" [maxSizeInBytes]="maxSizeInBytes" [directoryMode]="true" + [zipFile]="zipFile" (filesChanged)="setFilesToUpload($event)" > diff --git a/ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.ts b/ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.ts index 03b255f152c..b7c9cf5a355 100644 --- a/ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.ts +++ b/ui/ui-frontend/projects/collect/src/app/collect/archive-search-collect/add-units/add-units.component.ts @@ -69,6 +69,7 @@ export class AddUnitsComponent implements OnInit { protected readonly maxSizeInBytes = 10 * Math.pow(1024, 3); // 10 Gb public stepIndex = 0; public stepCount = 2; + public zipFile: ZipFile; isLoading = false; @@ -94,6 +95,7 @@ export class AddUnitsComponent implements OnInit { } private loadAttachementUnits() { + this.zipFile = new ZipFile(this.data.transaction.id); const sortingCriteria = { criteria: 'Title', sorting: Direction.ASCENDANT }; const criteriaWithId: SearchCriteriaEltDto = { criteria: 'DescriptionLevel', @@ -136,17 +138,17 @@ export class AddUnitsComponent implements OnInit { setFilesToUpload(files: File[]) { this.filesToUpload = files; + this.zipFile = this.zipFile.addFiles(this.filesToUpload); } validateAndUpload() { this.isLoading = true; - const zipFile = new ZipFile(this.data.transaction.id); - from(zipFile.addFiles(this.filesToUpload).generateZip()) + from(this.zipFile.generateZip()) .pipe( switchMap((content) => this.archiveCollectService.uploadZip(content, this.data.transaction.id, this.linkParentIdControl.value.included[0]), ), - tap((httpEvent) => zipFile.updateUploadingZipFileStatus(httpEvent)), + tap((httpEvent) => this.zipFile.updateUploadingZipFileStatus(httpEvent)), last((httpEvent) => httpEvent.type === HttpEventType.Response), finalize(() => (this.isLoading = false)), ) diff --git a/ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.html b/ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.html index 0d78b5864ec..9690b0efcb6 100644 --- a/ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.html +++ b/ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.html @@ -236,6 +236,7 @@ [multipleFiles]="true" [maxSizeInBytes]="uploadMaxSizeInBytes" [directoryMode]="true" + [zipFile]="zipFile" (filesChanged)="setFilesToUpload($event)" > diff --git a/ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.ts b/ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.ts index d184cb75491..9dd790c3339 100644 --- a/ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.ts +++ b/ui/ui-frontend/projects/collect/src/app/collect/projects/create-project/create-project.component.ts @@ -92,6 +92,7 @@ export class CreateProjectComponent implements OnInit, AfterViewChecked { selectedFlowType: FlowType = FlowType.FIX; public stepIndex = 0; public stepCount = 6; + public zipFile = new ZipFile(); projectForm: FormGroup; @@ -188,6 +189,7 @@ export class CreateProjectComponent implements OnInit, AfterViewChecked { setFilesToUpload(files: File[]) { this.filesToUpload = files; + this.zipFile = this.zipFile.addFiles(this.filesToUpload); } /*** Form validator Step : Description du versement ***/ @@ -341,8 +343,7 @@ export class CreateProjectComponent implements OnInit, AfterViewChecked { this.isLoading = true; const project: Project = this.formToProject(); this.moveToNextStep(); - const zipFile = new ZipFile(); - this.zipFileStatus$ = zipFile.zipFileStatus$; + this.zipFileStatus$ = this.zipFile.zipFileStatus$; this.projectsService .create(project) .pipe( @@ -356,9 +357,9 @@ export class CreateProjectComponent implements OnInit, AfterViewChecked { ), switchMap((transaction) => this.transactionsService.create(transaction)), tap((createdTransactionResponse) => (transactionId = createdTransactionResponse.id)), - switchMap(() => zipFile.addFiles(this.filesToUpload).generateZip()), + switchMap(() => this.zipFile.generateZip()), switchMap((content) => this.archiveCollectService.uploadZip(content, transactionId)), - tap((httpEvent) => zipFile.updateUploadingZipFileStatus(httpEvent)), + tap((httpEvent) => this.zipFile.updateUploadingZipFileStatus(httpEvent)), last((httpEvent) => httpEvent.type === HttpEventType.Response), finalize(() => { this.isLoading = false; diff --git a/ui/ui-frontend/projects/collect/src/assets/i18n/en.json b/ui/ui-frontend/projects/collect/src/assets/i18n/en.json index c83bc0fc6b4..d7357ce22e4 100644 --- a/ui/ui-frontend/projects/collect/src/assets/i18n/en.json +++ b/ui/ui-frontend/projects/collect/src/assets/i18n/en.json @@ -50,6 +50,7 @@ "LOAD_MORE_RESULTS": "Load more results...", "NO_ACCESS_CONTRACT": "No access contract is associated with the user", "UPLOAD_FILE_ALREADY_IMPORTED": "File already imported!", + "UPLOAD_EMPTY_FOLDER_IMPORTED": "The imported folder contains at least one empty folder!", "PROJECT_DESCRIPTION_SUB_TITLE": "Remittance description", "PROJECT_DESCRIPTION_LABEL": "Entitled", "PROJECT_DESCRIPTION_DESC": "Description", diff --git a/ui/ui-frontend/projects/collect/src/assets/i18n/fr.json b/ui/ui-frontend/projects/collect/src/assets/i18n/fr.json index 8d4a7eb3533..aa9bef33c5d 100644 --- a/ui/ui-frontend/projects/collect/src/assets/i18n/fr.json +++ b/ui/ui-frontend/projects/collect/src/assets/i18n/fr.json @@ -53,6 +53,7 @@ "LOAD_MORE_RESULTS": "Afficher plus de résultats...", "NO_ACCESS_CONTRACT": "Aucun contrat d'accès n'est associé à l'utilisateur", "UPLOAD_FILE_ALREADY_IMPORTED": "Dossier déjà importer !", + "UPLOAD_EMPTY_FOLDER_IMPORTED": "Le dossier importé comporte au moins un dossier vide!", "PROJECT_DESCRIPTION_SUB_TITLE": "Description du versement", "PROJECT_DESCRIPTION_LABEL": "Intitulé", "PROJECT_DESCRIPTION_DESC": "Description", diff --git a/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.html b/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.html index 21820b17053..0c84f86bcdf 100644 --- a/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.html +++ b/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.html @@ -10,7 +10,7 @@ (change)="handleFilesSelection($event.target.files)" [accept]="extensions?.join(',')" [multiple]="multipleFiles" - [attr.webkitdirectory]="directoryMode ? '' : null" + *ngIf="!directoryMode" /> @if (fileList) { diff --git a/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.spec.ts b/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.spec.ts index af7794c5ffe..41a8cfa3efa 100644 --- a/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.spec.ts +++ b/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.spec.ts @@ -39,6 +39,7 @@ import { FileSelectorComponent } from './file-selector.component'; import { TranslateModule } from '@ngx-translate/core'; import { PipesModule } from '../../pipes/pipes.module'; import { LoggerModule } from '../../logger'; +import { ZipFile } from '../../../../lib/models/zip/zip-file.class'; describe('FileSelectorComponent', () => { let component: FileSelectorComponent; @@ -51,6 +52,7 @@ describe('FileSelectorComponent', () => { fixture = TestBed.createComponent(FileSelectorComponent); component = fixture.componentInstance; + component.zipFile = new ZipFile(); fixture.detectChanges(); }); diff --git a/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.ts b/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.ts index cfdb7525c27..b5cc53ffb0a 100644 --- a/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.ts +++ b/ui/ui-frontend/projects/vitamui-library/src/app/modules/components/file-selector/file-selector.component.ts @@ -36,17 +36,21 @@ */ import { Component, ContentChild, ElementRef, EventEmitter, Input, Output, TemplateRef, ViewChild } from '@angular/core'; import { DragAndDropDirective } from '../../directives/drag-and-drop/drag-and-drop.directive'; -import { TranslateModule } from '@ngx-translate/core'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { PipesModule } from '../../pipes/pipes.module'; import { DisplayFile } from './display-file.interface'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { ZipFile } from '../../../../lib/models/zip/zip-file.class'; +import { CustomFile } from '../../../../lib/models/file'; +import { Logger } from '../../logger'; @Component({ selector: 'vitamui-file-selector', templateUrl: './file-selector.component.html', styleUrl: './file-selector.component.scss', standalone: true, - imports: [DragAndDropDirective, TranslateModule, NgIf, NgForOf, PipesModule, NgTemplateOutlet], + imports: [DragAndDropDirective, TranslateModule, NgIf, NgForOf, PipesModule, NgTemplateOutlet, MatSnackBarModule], }) export class FileSelectorComponent { /** @@ -57,6 +61,7 @@ export class FileSelectorComponent { /** only a directory can be chosen */ @Input() directoryMode = false; @Input() maxSizeInBytes: number; // TODO: do some control on the file size? + @Input() zipFile: ZipFile; @ViewChild('inputFiles') inputFiles: ElementRef; @@ -66,12 +71,25 @@ export class FileSelectorComponent { @Output() filesChanged = new EventEmitter(); private files: File[] = []; + private filesWithRelativePath: CustomFile[] = []; protected displayFiles: DisplayFile[] = []; + constructor( + private translationService: TranslateService, + private snackBar: MatSnackBar, + private logger: Logger, + ) {} + handleFilesSelection(files: FileList | File[]) { - if (!this.multipleFiles && this.files.length > 0) { + if (!this.multipleFiles && this.files?.length > 0) { return; } + + if (this.zipFile.directoryExistInZipFile(files)) { + this.snackBar.open(this.translationService.instant('COLLECT.UPLOAD_FILE_ALREADY_IMPORTED'), null, { duration: 3000 }); + return; + } + // Filter to keep only the ones matching extension list (useful for drag & drop and to make sure no other type has been selected) const filteredFiles = Array.from(files) .filter((file) => !this.extensions?.length || this.extensions.some((ext) => file.name.toLowerCase().endsWith(ext.toLowerCase()))) @@ -94,26 +112,16 @@ export class FileSelectorComponent { this.resetInput(); } - private getDirectory(files: FileList | File[]): DisplayFile { - let path = files[0].webkitRelativePath; - if (path.indexOf('/') !== -1) { - path = path.split('/')[0]; - return { - name: path, - size: Array.from(files).reduce((acc, file) => acc + file.size, 0), - directory: true, - }; - } - return null; - } - openFileSelectorOSDialog() { - this.inputFiles.nativeElement.click(); + this.directoryMode ? this.onSelectFolder() : this.inputFiles.nativeElement.click(); } removeFile(displayFile: DisplayFile) { if (displayFile.directory) { - this.files = this.files.filter((file) => !file.webkitRelativePath.startsWith(displayFile.name)); + this.files = this.files.filter( + (file: CustomFile) => + !file.costumeRelativePath?.startsWith(displayFile.name) || !file.webkitRelativePath?.startsWith(displayFile.name), + ); } else { this.files.splice( this.files.findIndex((file) => file.name === displayFile.name), @@ -121,6 +129,7 @@ export class FileSelectorComponent { ); } this.filesChanged.emit(this.files); + this.zipFile.remove(displayFile.name); this.displayFiles.splice(this.displayFiles.indexOf(displayFile), 1); } @@ -128,8 +137,64 @@ export class FileSelectorComponent { * Reset the value to allow a new "change" event. */ private resetInput(): void { + this.filesWithRelativePath = []; if (this.inputFiles) { this.inputFiles.nativeElement.value = ''; } } + + private getDirectory(files: FileList | any[]): DisplayFile { + if (files.length === 0) return null; + const firstFile: any = files[0]; + let path = firstFile?.costumeRelativePath || firstFile?.webkitRelativePath || firstFile?.relativePath || firstFile?.name; + if (path?.indexOf('/') !== -1) { + path = path?.split('/')[0]; + return { + name: path, + size: Array.from(files).reduce((acc, file) => acc + file.size, 0), + directory: true, + }; + } + return null; + } + + async onSelectFolder(): Promise { + try { + // Prompt the user to select a folder + const directoryHandle = await (window as any)?.showDirectoryPicker(); + + // Browse through folders and files + await this.readDirectory(directoryHandle); + + this.handleFilesSelection(this.filesWithRelativePath); + } catch (err) { + this.logger.error(this, err); + } + } + + private async isFolderEmpty(folderHandle: any): Promise { + for await (const _ of folderHandle.values()) { + return false; // If an item is found, the folder is not empty + } + return true; // No item found, the folder is empty + } + + private async readDirectory(directoryHandle: any, currentPath = directoryHandle?.name || ''): Promise { + for await (const [name, handle] of directoryHandle.entries()) { + if (handle.kind === 'file') { + // If it's a file, add it with its relative path + const file: CustomFile = await handle.getFile(); + file.costumeRelativePath = `${currentPath}/${name}`; + this.filesWithRelativePath = [...this.filesWithRelativePath, file]; + } else if (handle.kind === 'directory') { + const subFolderEmpty = await this.isFolderEmpty(handle); + if (subFolderEmpty) { + this.snackBar.open(this.translationService.instant('COLLECT.UPLOAD_EMPTY_FOLDER_IMPORTED'), null, { duration: 3000 }); + } + + // If it's a folder, read its contents recursively + await this.readDirectory(handle, `${currentPath}/${name}`); + } + } + } } diff --git a/ui/ui-frontend/projects/vitamui-library/src/app/modules/directives/drag-and-drop/drag-and-drop.directive.ts b/ui/ui-frontend/projects/vitamui-library/src/app/modules/directives/drag-and-drop/drag-and-drop.directive.ts index 44d07a21bc8..2d6943ca3d5 100644 --- a/ui/ui-frontend/projects/vitamui-library/src/app/modules/directives/drag-and-drop/drag-and-drop.directive.ts +++ b/ui/ui-frontend/projects/vitamui-library/src/app/modules/directives/drag-and-drop/drag-and-drop.directive.ts @@ -35,7 +35,9 @@ * knowledge of the CeCILL-C license and that you accept its terms. */ import { Directive, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core'; -import { Logger } from '../../logger/logger'; +import { Logger } from '../../logger'; +import { TranslateService } from '@ngx-translate/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; @Directive({ standalone: true, @@ -55,6 +57,8 @@ export class DragAndDropDirective { @Input() hoverClass = 'on-over'; constructor( + private translationService: TranslateService, + private snackBar: MatSnackBar, private elementRef: ElementRef, private logger: Logger, ) {} @@ -82,7 +86,12 @@ export class DragAndDropDirective { event.stopPropagation(); this.elementRef.nativeElement.classList.remove(this.hoverClass); - let fileEntries = await this.getAllFileEntries(event.dataTransfer.items); + const items = event.dataTransfer?.items; + if (!items) return; + + await this.findEmptySubdirectories(items); + + let fileEntries = await this.getAllFileEntries(items); // Filter files if (!this.enableFileDragAndDrop) { fileEntries = fileEntries.filter((fileEntry) => fileEntry.fullPath.split('/').length - 1 !== 1); @@ -173,4 +182,43 @@ export class DragAndDropDirective { this.logger.error(this, err); } }; + + private async findEmptySubdirectories(items: DataTransferItemList) { + for (const item of Array.from(items)) { + const entry = item.webkitGetAsEntry(); + if (entry && entry.isDirectory) { + this.checkSubdirectories(entry) + .then((emptyFolders) => { + if (emptyFolders?.length) + this.snackBar.open(this.translationService.instant('COLLECT.UPLOAD_EMPTY_FOLDER_IMPORTED'), null, { duration: 3000 }); + }) + .catch((err) => this.logger.error(this, err)); + } + } + } + + private async checkSubdirectories(directoryEntry: any) { + let emptyFolders: string[] = []; + const reader = directoryEntry.createReader(); + + reader.readEntries(async (entries: any[]) => { + for (const entry of entries) { + if (entry.isDirectory) { + const isEmpty = await this.isDirectoryEmpty(entry); + if (isEmpty) emptyFolders.push(entry.name); + } + } + }); + return emptyFolders; + } + + private isDirectoryEmpty(directoryEntry: any): Promise { + return new Promise((resolve) => { + const reader = directoryEntry.createReader(); + reader.readEntries((entries: any[]) => { + // A folder is empty if it contains neither files nor subfolders + resolve(entries.length === 0); + }); + }); + } } diff --git a/ui/ui-frontend/projects/vitamui-library/src/lib/models/file.ts b/ui/ui-frontend/projects/vitamui-library/src/lib/models/file.ts new file mode 100644 index 00000000000..6537eff1577 --- /dev/null +++ b/ui/ui-frontend/projects/vitamui-library/src/lib/models/file.ts @@ -0,0 +1,40 @@ +/* + * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2019-2022) + * and the signatories of the "VITAM - Accord du Contributeur" agreement. + * + * contact@programmevitam.fr + * + * This software is a computer program whose purpose is to implement + * implement a digital archiving front-office system for the secure and + * efficient high volumetry VITAM solution. + * + * This software is governed by the CeCILL-C license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL-C + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ + +export interface CustomFile extends File { + costumeRelativePath: string; +} diff --git a/ui/ui-frontend/projects/vitamui-library/src/lib/models/zip/zip-file.class.ts b/ui/ui-frontend/projects/vitamui-library/src/lib/models/zip/zip-file.class.ts index bbf52ad6d0f..fa214a7fae7 100644 --- a/ui/ui-frontend/projects/vitamui-library/src/lib/models/zip/zip-file.class.ts +++ b/ui/ui-frontend/projects/vitamui-library/src/lib/models/zip/zip-file.class.ts @@ -64,25 +64,30 @@ export class ZipFile { return this; } for (let i = 0; i < files.length; i++) { - const item = files[i]; - this.zipFile.file(item.webkitRelativePath, item); + const item: any = files[i]; + const filePath = item?.costumeRelativePath || item?.webkitRelativePath || item?.relativePath || item?.name; + this.zipFile.file(filePath, item); this.zipFileStatus.size += item.size; } return this; } + /** + * Removes the file or folder from the archive + * + * @param path Relative path of file or folder + * @return Returns the JSZip instance + */ + remove(path: string): void { + this.zipFile.remove(path); + } + generateZip(): Promise { return this.zipFile .generateInternalStream({ type: 'blob' }) .accumulate((metadata) => this.updateZipFileStatus(metadata.currentFile, metadata.percent)); } - private updateZipFileStatus(metadataCurrentFile: string, metadataPercent: number) { - this.zipFileStatus.currentFile = metadataCurrentFile; - this.zipFileStatus.currentFileUploadedSize = metadataPercent; - this.zipFileStatus$.next(this.zipFileStatus); - } - updateUploadingZipFileStatus(data: HttpEvent) { if (!data) return; let progressPercent = 0; @@ -97,4 +102,29 @@ export class ZipFile { this.zipFileStatus.uploadedSize = progressPercent; this.zipFileStatus$.next(this.zipFileStatus); } + + directoryExistInZipFile(files: DataTransferItemList | FileList | File[]) { + let name = this.directoryNameOfFirstFile(files); + const indexName = Object.values(this.zipFile.files) + .filter((f) => f.dir) + .map((f) => f.name.slice(0, -1)) + .findIndex((n) => n === name); + return indexName !== -1; + } + + /*** Private methods ***/ + + private directoryNameOfFirstFile(files: any) { + let name = files[0]?.webkitRelativePath || files[0]?.relativePath; + if (name?.indexOf('/') !== -1) { + name = name?.split('/')[0]; + } + return name; + } + + private updateZipFileStatus(metadataCurrentFile: string, metadataPercent: number) { + this.zipFileStatus.currentFile = metadataCurrentFile; + this.zipFileStatus.currentFileUploadedSize = metadataPercent; + this.zipFileStatus$.next(this.zipFileStatus); + } }