From c87245bf8d10012aa53f5289c13e5ef4d6482549 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Fri, 4 Mar 2022 21:16:21 +0100
Subject: [PATCH] Uniqid for dataset files archive

---
 .../components/result/download.component.html |  5 +---
 .../components/result/download.component.ts   | 20 +++++++-------
 .../store/actions/download-file.actions.ts    |  2 +-
 .../store/effects/download-file.effects.ts    | 10 +++----
 .../store/reducers/download-file.reducer.ts   | 11 ++++++++
 server/app/routes.php                         |  2 +-
 server/src/Action/ArchiveAction.php           |  5 ++--
 .../src/Action/ArchiveIsAvailableAction.php   |  4 +--
 server/src/Action/DownloadArchiveAction.php   | 26 ++++++++++++++++---
 tasks/src/anis_tasks/archive.py               |  2 +-
 10 files changed, 58 insertions(+), 29 deletions(-)

diff --git a/client/src/app/instance/search/components/result/download.component.html b/client/src/app/instance/search/components/result/download.component.html
index aa6200cf..fc7758ea 100644
--- a/client/src/app/instance/search/components/result/download.component.html
+++ b/client/src/app/instance/search/components/result/download.component.html
@@ -44,14 +44,11 @@
                     <a *ngIf="!archiveInProgress" [href]="createFilesArchiveUrl()" (click)="createFilesArchive($event, createFilesArchiveUrl())" class="btn btn-outline-primary" title="Download an archive with all files">
                         <span class="fas fa-archive"></span> Files archive
                     </a>
-                    <div *ngIf="archiveInProgress && !archiveIsAvailable">
+                    <div *ngIf="archiveInProgress">
                         <span class="fas fa-circle-notch fa-spin fa-3x"></span>
                         <span class="sr-only">Loading...</span>
                         Please wait archive is under construction...
                     </div>
-                    <a *ngIf="archiveIsAvailable" [href]="getArchiveUrl()" (click)="click($event, getArchiveUrl(), 'zip')" class="btn btn-outline-primary" title="Download an archive with all files">
-                        <span class="fas fa-archive"></span> {{ archiveName }}
-                    </a>
                 </div>
             </div>
         </div>
diff --git a/client/src/app/instance/search/components/result/download.component.ts b/client/src/app/instance/search/components/result/download.component.ts
index 4b3c199a..9a8f21ad 100644
--- a/client/src/app/instance/search/components/result/download.component.ts
+++ b/client/src/app/instance/search/components/result/download.component.ts
@@ -8,7 +8,7 @@
  */
 
 import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
-import { HttpClient, HttpRequest, HttpEventType } from '@angular/common/http';
+import { HttpClient } from '@angular/common/http';
 import { interval, Subscription} from 'rxjs';
 
 import { Criterion, ConeSearch, criterionToString } from '../../../store/models';
@@ -36,8 +36,8 @@ export class DownloadComponent implements OnDestroy {
     @Output() startsDownloadingFile: EventEmitter<{url: string, filename: string}> = new EventEmitter();
 
     archiveName = '';
+    archiveId ='';
     archiveInProgress = false;
-    archiveIsAvailable = false;
     archiveIsAvailableSubscription: Subscription
 
     constructor(private appConfig: AppConfigService, private http: HttpClient) { }
@@ -96,7 +96,7 @@ export class DownloadComponent implements OnDestroy {
     /**
      * Allows to download file.
      */
-     click(event, href, extension): void {
+    click(event, href, extension): void {
         event.preventDefault();
 
         const url = href;
@@ -110,7 +110,7 @@ export class DownloadComponent implements OnDestroy {
      *
      * @fires EventEmitter<string>
      */
-     broadcastVotable(): void {
+    broadcastVotable(): void {
         this.broadcast.emit(this.getUrl('votable'));
     }
 
@@ -131,15 +131,16 @@ export class DownloadComponent implements OnDestroy {
     }
 
     getArchiveUrl() {
-        return `${getHost(this.appConfig.apiUrl)}/download-archive?archive_name=${this.archiveName}`;
+        return `${getHost(this.appConfig.apiUrl)}/download-archive/${this.datasetSelected}?archive_id=${this.archiveId}`;
     }
 
     testArchiveIsAvailable() {
-        const url = `${getHost(this.appConfig.apiUrl)}/archive-is-available?archive_name=${this.archiveName}`;
+        const url = `${getHost(this.appConfig.apiUrl)}/archive-is-available?archive_id=${this.archiveId}`;
         this.http.get<{"archive_is_available": boolean}>(url).subscribe(data => {
             if (data.archive_is_available) {
-                this.archiveIsAvailable = true;
+                this.archiveInProgress = false;
                 this.archiveIsAvailableSubscription.unsubscribe();
+                this.startsDownloadingFile.emit({ url: this.getArchiveUrl(), filename: this.archiveName })
             }
         });
     }
@@ -150,9 +151,10 @@ export class DownloadComponent implements OnDestroy {
     createFilesArchive(event, href): void {
         event.preventDefault();
 
-        this.http.get<{"archive": string}>(href).subscribe(data => {
+        this.http.get<{"archive_name": string, "archive_id": string}>(href).subscribe(data => {
             this.archiveInProgress = true;
-            this.archiveName = data.archive;
+            this.archiveName = data.archive_name;
+            this.archiveId = data.archive_id;
             this.archiveIsAvailableSubscription = interval(1000).subscribe(() => this.testArchiveIsAvailable());
         });
     }
diff --git a/client/src/app/instance/store/actions/download-file.actions.ts b/client/src/app/instance/store/actions/download-file.actions.ts
index f5ff418d..a6d140f2 100644
--- a/client/src/app/instance/store/actions/download-file.actions.ts
+++ b/client/src/app/instance/store/actions/download-file.actions.ts
@@ -11,4 +11,4 @@ import { createAction, props } from '@ngrx/store';
 
 export const startsDownloadingFile = createAction('[File] Starts Downloading File', props<{ url: string, filename: string }>());
 export const updateDownloadProgress = createAction('[File] Update Download Progress', props<{ progress: number, filename: string }>());
-export const startsArchiveFilesCreation = createAction('[File] Starts Archive Files Creation');
+export const fileDownloaded = createAction('[File] File Downloaded', props<{ filename: string }>());
diff --git a/client/src/app/instance/store/effects/download-file.effects.ts b/client/src/app/instance/store/effects/download-file.effects.ts
index 21091c77..2a4754ba 100644
--- a/client/src/app/instance/store/effects/download-file.effects.ts
+++ b/client/src/app/instance/store/effects/download-file.effects.ts
@@ -10,11 +10,9 @@
 import { Injectable } from '@angular/core';
 import { HttpEventType } from '@angular/common/http';
 
-import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
 import { Store } from '@ngrx/store';
-import { of } from 'rxjs';
-import { map, tap, mergeMap, catchError } from 'rxjs/operators';
-import { ToastrService } from 'ngx-toastr';
+import { map, mergeMap } from 'rxjs/operators';
 
 import { DownloadFileService } from '../services/download-file.service';
 import * as downloadFileActions from '../actions/download-file.actions';
@@ -42,6 +40,7 @@ export class DownloadFileEffects {
                         }
 
                         if (event.type === HttpEventType.Response) {
+                            this.store.dispatch(downloadFileActions.fileDownloaded({ filename: action.filename }));
                             this.downloadFileService.saveDownloadedFile(event.body as Blob, action.filename);
                         }
                     })
@@ -53,7 +52,6 @@ export class DownloadFileEffects {
     constructor(
         private actions$: Actions,
         private downloadFileService: DownloadFileService,
-        private store: Store<{ }>,
-        private toastr: ToastrService
+        private store: Store<{ }>
     ) {}
 }
diff --git a/client/src/app/instance/store/reducers/download-file.reducer.ts b/client/src/app/instance/store/reducers/download-file.reducer.ts
index c0021b46..d30e4bf1 100644
--- a/client/src/app/instance/store/reducers/download-file.reducer.ts
+++ b/client/src/app/instance/store/reducers/download-file.reducer.ts
@@ -45,6 +45,17 @@ export const fileReducer = createReducer(
                 progress: progress
             }
         ]
+    })),
+    on(downloadFileActions.fileDownloaded, (state, { filename }) => ({
+        ...state,
+        downloadedFiles: [
+            ...state.downloadedFiles.filter(f => f.name !== filename),
+            {
+                name: filename,
+                state: 'DONE',
+                progress: 100
+            }
+        ]
     }))
 );
 
diff --git a/server/app/routes.php b/server/app/routes.php
index d44c212b..634a3599 100644
--- a/server/app/routes.php
+++ b/server/app/routes.php
@@ -70,7 +70,7 @@ $app->group('', function (RouteCollectorProxy $group) {
 $app->get('/search/{dname}', App\Action\SearchAction::class);
 $app->get('/archive/{dname}', App\Action\ArchiveAction::class);
 $app->get('/archive-is-available', App\Action\ArchiveIsAvailableAction::class);
-$app->get('/download-archive', App\Action\DownloadArchiveAction::class);
+$app->get('/download-archive/{dname}', App\Action\DownloadArchiveAction::class);
 $app->get('/dataset-file-explorer/{dname}[{fpath:.*}]', App\Action\DatasetFileExplorerAction::class);
 $app->get('/download-instance-file/{iname}/[{fpath:.*}]', App\Action\DownloadInstanceFileAction::class);
 $app->get('/download-file/{dname}/[{fpath:.*}]', App\Action\DownloadFileAction::class);
diff --git a/server/src/Action/ArchiveAction.php b/server/src/Action/ArchiveAction.php
index 6a743ab6..a6009f5a 100644
--- a/server/src/Action/ArchiveAction.php
+++ b/server/src/Action/ArchiveAction.php
@@ -112,12 +112,13 @@ final class ArchiveAction extends AbstractAction
 
         // Create the name of the future archive
         $archiveName = 'archive_' . $dataset->getName() . '_' . (new \DateTime())->format('Y-m-d\TH:i:s') . '.zip';
+        $archiveId = uniqid();
 
         // Publish message in the archive queue
         $channel = $this->rmq->channel();
         $channel->queue_declare('archive', false, false, false, false);
         $msg = new AMQPMessage(json_encode(array(
-            'archive_name' => $archiveName,
+            'archive_id' => $archiveId,
             'dataset_name' => $datasetName,
             'query' => $request->getUri()->getQuery(),
             'param_a' => $queryParams['a']
@@ -125,7 +126,7 @@ final class ArchiveAction extends AbstractAction
         $channel->basic_publish($msg, '', 'archive');
 
         // Just returns the future archive name
-        $payload = json_encode(array('archive' => $archiveName));
+        $payload = json_encode(array('archive_name' => $archiveName, 'archive_id' => $archiveId));
         $response->getBody()->write($payload);
         return $response;
     }
diff --git a/server/src/Action/ArchiveIsAvailableAction.php b/server/src/Action/ArchiveIsAvailableAction.php
index 39f3a4ec..81219d4b 100644
--- a/server/src/Action/ArchiveIsAvailableAction.php
+++ b/server/src/Action/ArchiveIsAvailableAction.php
@@ -68,10 +68,10 @@ final class ArchiveIsAvailableAction extends AbstractAction
         }
         
         $queryParams = $request->getQueryParams();
-        $archiveName = $queryParams['archive_name'];
+        $archiveId = $queryParams['archive_id'] . '.zip';
 
         // Search the file
-        $filePath = $this->dataPath . '/ARCHIVE/' . $archiveName;
+        $filePath = $this->dataPath . '/ARCHIVE/' . $archiveId;
 
         $isAvailable = false;
         if (file_exists($filePath)) {
diff --git a/server/src/Action/DownloadArchiveAction.php b/server/src/Action/DownloadArchiveAction.php
index d3795acc..13ce380b 100644
--- a/server/src/Action/DownloadArchiveAction.php
+++ b/server/src/Action/DownloadArchiveAction.php
@@ -66,18 +66,38 @@ final class DownloadArchiveAction extends AbstractAction
         if ($request->getMethod() === OPTIONS) {
             return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
         }
+
+        // Search the correct dataset with primary key
+        $dataset = $this->em->find('App\Entity\Dataset', $args['dname']);
+
+        // If dataset is not found 404
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['dname'] . ' is not found'
+            );
+        }
+
+        // If dataset is private and authorization enabled
+        if (!$dataset->getPublic() && boolval($this->settings['enabled'])) {
+            $this->verifyDatasetAuthorization(
+                $request,
+                $dataset->getName(),
+                explode(',', $this->settings['admin_roles'])
+            );
+        }
         
         $queryParams = $request->getQueryParams();
-        $archiveName = $queryParams['archive_name'];
+        $archiveId = $queryParams['archive_id'];
 
         // Search the file
-        $filePath = $this->dataPath . '/ARCHIVE/' . $archiveName;
+        $filePath = $this->dataPath . '/ARCHIVE/' . $archiveId . '.zip';
 
         // If the file not found 404
         if (!file_exists($filePath)) {
             throw new HttpNotFoundException(
                 $request,
-                'Archive file with name ' . $archiveName . ' is not found'
+                'Archive file with name ' . $archiveId . '.zip is not found'
             );
         }
 
diff --git a/tasks/src/anis_tasks/archive.py b/tasks/src/anis_tasks/archive.py
index 8cca0299..1f799b77 100644
--- a/tasks/src/anis_tasks/archive.py
+++ b/tasks/src/anis_tasks/archive.py
@@ -21,7 +21,7 @@ def archive_handler(ch, method, properties, body):
 
     # create a ZipFile object
     data_path = utils.get_data_path()
-    zip_path = data_path + "/ARCHIVE/" + message["archive_name"]
+    zip_path = data_path + "/ARCHIVE/" + message["archive_id"] + ".zip"
     zip = ZipFile(zip_path + ".tmp", 'w')
 
     # Search files
-- 
GitLab