From 30c269c953b8938195e6e1e3f245f213f93d2045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr> Date: Thu, 31 Mar 2022 16:15:04 +0200 Subject: [PATCH] Fixed bugs data path (instance and dataset) --- .../data-path-form-control.component.html | 57 --- .../data-path-form-control.component.ts | 64 --- .../admin/admin-shared/components/index.ts | 6 +- ...> path-select-form-control.component.html} | 7 +- ... => path-select-form-control.component.ts} | 36 +- .../components/instance-form.component.html | 47 +- .../components/instance-form.component.ts | 11 +- .../dataset/dataset-form.component.html | 9 +- .../image/image-form.component.html | 5 +- .../actions/admin-file-explorer.actions.ts | 6 +- .../actions/attribute-distinct.actions.ts | 6 +- .../app/admin/store/actions/column.actions.ts | 6 +- .../app/admin/store/actions/table.actions.ts | 6 +- .../home/components/welcome.component.ts | 2 +- client/src/app/instance/instance.component.ts | 2 +- .../renderer/download-renderer.component.ts | 6 +- .../components/instance-card.component.ts | 2 +- .../app/shared/components/navbar.component.ts | 2 +- conf-dev/create-db.sh | 2 +- server/app/dependencies.php | 12 +- server/app/routes.php | 8 +- .../doctrine-proxy/__CG__AppEntityDataset.php | 40 +- .../doctrine-proxy/__CG__AppEntityImage.php | 415 ++++++++++++++++++ .../__CG__AppEntityInstance.php | 26 +- server/src/Action/AdminFileExplorerAction.php | 2 +- .../src/Action/DatasetFileExplorerAction.php | 50 ++- .../src/Action/DownloadInstanceFileAction.php | 96 ---- server/src/Action/InstanceAction.php | 1 + ...ion.php => InstanceFileExplorerAction.php} | 72 +-- server/src/Action/InstanceListAction.php | 1 + server/src/Entity/Instance.php | 18 + server/tests/Action/InstanceActionTest.php | 1 + .../tests/Action/InstanceListActionTest.php | 1 + 33 files changed, 663 insertions(+), 362 deletions(-) delete mode 100644 client/src/app/admin/admin-shared/components/data-path-form-control.component.html delete mode 100644 client/src/app/admin/admin-shared/components/data-path-form-control.component.ts rename client/src/app/admin/admin-shared/components/{file-select-form-control.component.html => path-select-form-control.component.html} (86%) rename client/src/app/admin/admin-shared/components/{file-select-form-control.component.ts => path-select-form-control.component.ts} (74%) create mode 100644 server/doctrine-proxy/__CG__AppEntityImage.php delete mode 100644 server/src/Action/DownloadInstanceFileAction.php rename server/src/Action/{DownloadFileAction.php => InstanceFileExplorerAction.php} (54%) diff --git a/client/src/app/admin/admin-shared/components/data-path-form-control.component.html b/client/src/app/admin/admin-shared/components/data-path-form-control.component.html deleted file mode 100644 index 0c0f9e99..00000000 --- a/client/src/app/admin/admin-shared/components/data-path-form-control.component.html +++ /dev/null @@ -1,57 +0,0 @@ -<form [formGroup]="form" novalidate> - <div class="form-group"> - <label for="data_path">Data path</label> - <div class="input-group mb-3"> - <div class="input-group-prepend"> - <button (click)="openModal(template); $event.stopPropagation()" class="btn btn-outline-secondary" type="button"> - <span class="fas fa-folder-open"></span> - </button> - </div> - <input type="text" class="form-control" id="data_path" name="data_path" formControlName="data_path"> - </div> - </div> -</form> - -<ng-template #template> - <div class="modal-header"> - <h4 class="modal-title pull-left">ANIS file explorer</h4> - </div> - <div> - <app-spinner *ngIf="filesIsLoading"></app-spinner> - - <p class="ml-3 mt-3"> - <span class="far fa-folder"></span> - {{ fileExplorerPath }} - </p> - <div *ngIf="filesIsLoaded" class="table-responsive"> - <table class="table table-hover" aria-describedby="File explorer"> - <thead> - <tr> - <th scope="col"></th> - <th scope="col">Name</th> - <th scope="col">Size</th> - <th scope="col">MimeType</th> - </tr> - </thead> - <tbody> - <tr *ngFor="let fileInfo of files" (click)="dataPathAction(fileInfo)" [class.pointer]="fileInfo.type === 'dir' && fileInfo.name !== '.'"> - <td> - <span *ngIf="fileInfo.type === 'dir'"><span class="far fa-folder"></span></span> - <span *ngIf="fileInfo.type === 'file'"><span class="far fa-file"></span></span> - </td> - <td class="align-middle"> - {{ fileInfo.name }} - </td> - <td class="align-middle">{{ fileInfo.size | formatFileSize: false }}</td> - <td class="align-middle">{{ fileInfo.mimetype }}</td> - </tr> - </tbody> - </table> - </div> - </div> - <div class="modal-footer"> - <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button> - - <button [disabled]="fileExplorerPristine" (click)="selectDirectory()" class="btn btn-primary">Select this directory</button> - </div> -</ng-template> diff --git a/client/src/app/admin/admin-shared/components/data-path-form-control.component.ts b/client/src/app/admin/admin-shared/components/data-path-form-control.component.ts deleted file mode 100644 index 7201e67a..00000000 --- a/client/src/app/admin/admin-shared/components/data-path-form-control.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * This file is part of Anis Client. - * - * @copyright Laboratoire d'Astrophysique de Marseille / CNRS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Component, Input, Output, TemplateRef, EventEmitter } from '@angular/core'; -import { FormGroup } from '@angular/forms'; - -import { BsModalService } from 'ngx-bootstrap/modal'; -import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; - -import { FileInfo } from 'src/app/admin/store/models'; - -@Component({ - selector: 'app-data-path-form-control', - templateUrl: 'data-path-form-control.component.html' -}) -export class DataPathFormControlComponent { - @Input() form: FormGroup; - @Input() files: FileInfo[]; - @Input() filesIsLoading: boolean; - @Input() filesIsLoaded: boolean; - @Output() loadRootDirectory: EventEmitter<string> = new EventEmitter(); - - modalRef: BsModalRef; - fileExplorerPath = ''; - fileExplorerPristine = true; - - constructor(private modalService: BsModalService) { } - - openModal(template: TemplateRef<any>) { - this.fileExplorerPristine = true; - this.fileExplorerPath = this.form.controls.data_path.value; - if (!this.fileExplorerPath) { - this.fileExplorerPath = ''; - } - this.modalRef = this.modalService.show(template); - this.loadRootDirectory.emit(this.fileExplorerPath); - } - - dataPathAction(fileInfo: FileInfo): void { - if (fileInfo.name === '.' || fileInfo.type !== 'dir') { - return; - } - - if (fileInfo.name === '..') { - this.fileExplorerPath = this.fileExplorerPath.substr(0, this.fileExplorerPath.lastIndexOf("/")); - } else { - this.fileExplorerPath += `/${fileInfo.name}`; - } - this.fileExplorerPristine = false; - this.loadRootDirectory.emit(this.fileExplorerPath); - } - - selectDirectory() { - this.form.controls.data_path.setValue(this.fileExplorerPath); - this.form.markAsDirty(); - this.modalRef.hide(); - } -} diff --git a/client/src/app/admin/admin-shared/components/index.ts b/client/src/app/admin/admin-shared/components/index.ts index 57eea4f1..b60b506e 100644 --- a/client/src/app/admin/admin-shared/components/index.ts +++ b/client/src/app/admin/admin-shared/components/index.ts @@ -8,11 +8,9 @@ */ import { DeleteBtnComponent } from './delete-btn.component'; -import { DataPathFormControlComponent } from './data-path-form-control.component'; -import { FileSelectFormControlComponent } from './file-select-form-control.component'; +import { PathSelectFormControlComponent } from './path-select-form-control.component'; export const adminSharedComponents = [ DeleteBtnComponent, - DataPathFormControlComponent, - FileSelectFormControlComponent + PathSelectFormControlComponent ]; diff --git a/client/src/app/admin/admin-shared/components/file-select-form-control.component.html b/client/src/app/admin/admin-shared/components/path-select-form-control.component.html similarity index 86% rename from client/src/app/admin/admin-shared/components/file-select-form-control.component.html rename to client/src/app/admin/admin-shared/components/path-select-form-control.component.html index b1f9db74..786c6539 100644 --- a/client/src/app/admin/admin-shared/components/file-select-form-control.component.html +++ b/client/src/app/admin/admin-shared/components/path-select-form-control.component.html @@ -37,7 +37,7 @@ <tr *ngFor="let fileInfo of files" (click)="click(fileInfo)" [class.table-active]="checkFileSelected(fileInfo)" - [class.pointer]="fileInfo.name !== '.'"> + [class.pointer]="checkPointer(fileInfo)"> <td> <span *ngIf="fileInfo.type === 'dir'"><span class="far fa-folder"></span></span> <span *ngIf="fileInfo.type === 'file'"><span class="far fa-file"></span></span> @@ -55,6 +55,9 @@ <div class="modal-footer"> <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button> - <button [disabled]="!fileSelected" (click)="selectFile()" class="btn btn-primary">Select this file</button> + <button [disabled]="selectType === 'file' && !fileSelected" (click)="onSubmit()" class="btn btn-primary"> + <ng-container *ngIf="selectType === 'file'">Select this file</ng-container> + <ng-container *ngIf="selectType === 'directory'">Select this directory</ng-container> + </button> </div> </ng-template> diff --git a/client/src/app/admin/admin-shared/components/file-select-form-control.component.ts b/client/src/app/admin/admin-shared/components/path-select-form-control.component.ts similarity index 74% rename from client/src/app/admin/admin-shared/components/file-select-form-control.component.ts rename to client/src/app/admin/admin-shared/components/path-select-form-control.component.ts index 5c4d0fc0..4f7926e0 100644 --- a/client/src/app/admin/admin-shared/components/file-select-form-control.component.ts +++ b/client/src/app/admin/admin-shared/components/path-select-form-control.component.ts @@ -16,10 +16,10 @@ import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; import { FileInfo } from 'src/app/admin/store/models'; @Component({ - selector: 'app-file-select-form-control', - templateUrl: 'file-select-form-control.component.html' + selector: 'app-path-select-form-control', + templateUrl: 'path-select-form-control.component.html' }) -export class FileSelectFormControlComponent implements OnChanges { +export class PathSelectFormControlComponent implements OnChanges { @Input() form: FormGroup; @Input() disabled: boolean = false; @Input() controlName: string; @@ -27,6 +27,7 @@ export class FileSelectFormControlComponent implements OnChanges { @Input() files: FileInfo[]; @Input() filesIsLoading: boolean; @Input() filesIsLoaded: boolean; + @Input() selectType: string; @Output() loadDirectory: EventEmitter<string> = new EventEmitter(); @Output() select: EventEmitter<FileInfo> = new EventEmitter(); @@ -77,19 +78,36 @@ export class FileSelectFormControlComponent implements OnChanges { } buildFilePath(fileInfo: FileInfo) { - let fileSelected = ''; + let fullPath = ''; if (this.fileExplorerPath !== '') { - fileSelected += `${this.fileExplorerPath}/`; + fullPath += `${this.fileExplorerPath}/`; + } else { + fullPath += '/'; } - return `${fileSelected}${fileInfo.name}`; + return `${fullPath}${fileInfo.name}`; } checkFileSelected(fileInfo: FileInfo) { - return this.buildFilePath(fileInfo) === this.fileSelected; + return this.buildFilePath(fileInfo) === this.fileSelected && this.selectType === 'file'; } - selectFile() { - this.form.controls[this.controlName].setValue(this.fileSelected); + checkPointer(fileInfo: FileInfo) { + if (this.selectType === 'file' && fileInfo.name !== '.') { + return true; + } else if (this.selectType === 'directory' && fileInfo.type === 'dir' && fileInfo.name !== '.') { + return true; + } else { + return false; + } + } + + onSubmit() { + if (this.selectType === 'file') { + this.form.controls[this.controlName].setValue(this.fileSelected); + } else { + this.form.controls[this.controlName].setValue(this.fileExplorerPath); + } + this.form.markAsDirty(); this.select.emit(this.fileInfoSelected); this.modalRef.hide(); diff --git a/client/src/app/admin/instance/components/instance-form.component.html b/client/src/app/admin/instance/components/instance-form.component.html index 8c9a484c..41712766 100644 --- a/client/src/app/admin/instance/components/instance-form.component.html +++ b/client/src/app/admin/instance/components/instance-form.component.html @@ -17,13 +17,27 @@ <label for="display">Display</label> <input type="number" class="form-control" id="display" name="display" formControlName="display"> </div> - <app-data-path-form-control + <app-path-select-form-control [form]="form" + [controlName]="'data_path'" + [controlLabel]="'Data path'" [files]="files" [filesIsLoading]="filesIsLoading" [filesIsLoaded]="filesIsLoaded" - (loadRootDirectory)="loadRootDirectory.emit($event)"> - </app-data-path-form-control> + [selectType]="'directory'" + (loadDirectory)="loadRootDirectory.emit($event)"> + </app-path-select-form-control> + <app-path-select-form-control + [form]="form" + [disabled]="isDataPathEmpty()" + [controlName]="'files_path'" + [controlLabel]="'Files path'" + [files]="files" + [filesIsLoading]="filesIsLoading" + [filesIsLoaded]="filesIsLoaded" + [selectType]="'directory'" + (loadDirectory)="onChangeFilesPath($event)"> + </app-path-select-form-control> <div class="custom-control custom-radio custom-control-inline"> <input type="radio" class="custom-control-input" id="public" formControlName="public" [value]="true"> <label class="custom-control-label" for="public"><span class="fas fa-globe"></span> Public</label> @@ -34,16 +48,17 @@ </div> </accordion-group> <accordion-group heading="Design" [isOpen]="true"> - <app-file-select-form-control + <app-path-select-form-control [form]="form" - [disabled]="isDataPathEmpty()" + [disabled]="isFilesPathEmpty()" [controlName]="'portal_logo'" [controlLabel]="'Portal logo'" [files]="files" [filesIsLoading]="filesIsLoading" [filesIsLoaded]="filesIsLoaded" + [selectType]="'file'" (loadDirectory)="onChangeFileSelect($event)"> - </app-file-select-form-control> + </app-path-select-form-control> <div class="form-row"> <div class="form-group col-md-6"> <label for="design_color_picker">Instance color (picker)</label> @@ -64,26 +79,28 @@ <input type="text" class="form-control" id="design_background_color_input" [value]="form.value.design_background_color" formControlName="design_background_color"> </div> </div> - <app-file-select-form-control + <app-path-select-form-control [form]="form" - [disabled]="isDataPathEmpty()" + [disabled]="isFilesPathEmpty()" [controlName]="'design_logo'" [controlLabel]="'Logo'" [files]="files" [filesIsLoading]="filesIsLoading" [filesIsLoaded]="filesIsLoaded" + [selectType]="'file'" (loadDirectory)="onChangeFileSelect($event)"> - </app-file-select-form-control> - <app-file-select-form-control + </app-path-select-form-control> + <app-path-select-form-control [form]="form" - [disabled]="isDataPathEmpty()" + [disabled]="isFilesPathEmpty()" [controlName]="'design_favicon'" [controlLabel]="'Favicon'" [files]="files" [filesIsLoading]="filesIsLoading" [filesIsLoaded]="filesIsLoaded" + [selectType]="'file'" (loadDirectory)="onChangeFileSelect($event)"> - </app-file-select-form-control> + </app-path-select-form-control> </accordion-group> <accordion-group heading="Home page" [isOpen]="true"> <div class="form-group"> @@ -97,15 +114,17 @@ <label for="home_component_text">Text</label> <textarea class="form-control" id="home_component_text" formControlName="home_component_text" rows="3"></textarea> </div> - <app-file-select-form-control + <app-path-select-form-control [form]="getHomeConfigFormGroup()" + [disabled]="isFilesPathEmpty()" [controlName]="'home_component_logo'" [controlLabel]="'Logo'" [files]="files" [filesIsLoading]="filesIsLoading" [filesIsLoaded]="filesIsLoaded" + [selectType]="'file'" (loadDirectory)="onChangeFileSelect($event)"> - </app-file-select-form-control> + </app-path-select-form-control> </div> </accordion-group> <accordion-group heading="Functionalities" [isOpen]="true"> diff --git a/client/src/app/admin/instance/components/instance-form.component.ts b/client/src/app/admin/instance/components/instance-form.component.ts index a9d6620b..cd351cb4 100644 --- a/client/src/app/admin/instance/components/instance-form.component.ts +++ b/client/src/app/admin/instance/components/instance-form.component.ts @@ -31,6 +31,7 @@ export class InstanceFormComponent implements OnInit { description: new FormControl('', [Validators.required]), display: new FormControl('', [Validators.required]), data_path: new FormControl(''), + files_path: new FormControl(''), public: new FormControl(true, [Validators.required]), portal_logo: new FormControl(''), design_color: new FormControl('#7AC29A', [Validators.required]), @@ -75,8 +76,16 @@ the fast implementation of a project data exchange platform in a dedicated infor return this.form.controls.data_path.value == ''; } + isFilesPathEmpty() { + return this.form.controls.files_path.value == ''; + } + + onChangeFilesPath(path: string) { + this.loadRootDirectory.emit(`${this.form.controls.data_path.value}${path}`) + } + onChangeFileSelect(path: string) { - this.loadRootDirectory.emit(`${this.form.controls.data_path.value}${path}`); + this.loadRootDirectory.emit(`${this.form.controls.data_path.value}${this.form.controls.files_path.value}${path}`); } getHomeConfigFormGroup() { diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html index 425366e6..0200ca1d 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html @@ -40,13 +40,16 @@ <label for="description">Description</label> <textarea class="form-control" rows="5" id="description" name="description" formControlName="description"></textarea> </div> - <app-data-path-form-control + <app-path-select-form-control [form]="form" + [controlName]="'data_path'" + [controlLabel]="'Data path'" [files]="files" [filesIsLoading]="filesIsLoading" [filesIsLoaded]="filesIsLoaded" - (loadRootDirectory)="onChangeDataPath($event)"> - </app-data-path-form-control> + [selectType]="'directory'" + (loadDirectory)="onChangeDataPath($event)"> + </app-path-select-form-control> <div class="form-group"> <label for="display">Display</label> <input type="number" class="form-control" id="display" name="display" formControlName="display"> diff --git a/client/src/app/admin/instance/dataset/components/image/image-form.component.html b/client/src/app/admin/instance/dataset/components/image/image-form.component.html index da9163ce..1c8392e9 100644 --- a/client/src/app/admin/instance/dataset/components/image/image-form.component.html +++ b/client/src/app/admin/instance/dataset/components/image/image-form.component.html @@ -1,16 +1,17 @@ <app-spinner *ngIf="fitsImageLimitsIsLoading"></app-spinner> <form *ngIf="!fitsImageLimitsIsLoading" [formGroup]="form" (ngSubmit)="submit()" novalidate> - <app-file-select-form-control + <app-path-select-form-control [form]="form" [controlName]="'file_path'" [controlLabel]="'File path'" [files]="files" [filesIsLoading]="filesIsLoading" [filesIsLoaded]="filesIsLoaded" + [selectType]="'file'" (loadDirectory)="onChangeFileSelect($event)" (select)="onFileSelect($event)"> - </app-file-select-form-control> + </app-path-select-form-control> <div class="form-group"> <label for="file_size">File size</label> <input type="number" class="form-control" id="file_size" name="file_size" formControlName="file_size"> diff --git a/client/src/app/admin/store/actions/admin-file-explorer.actions.ts b/client/src/app/admin/store/actions/admin-file-explorer.actions.ts index d73c963f..e54fda8b 100644 --- a/client/src/app/admin/store/actions/admin-file-explorer.actions.ts +++ b/client/src/app/admin/store/actions/admin-file-explorer.actions.ts @@ -11,6 +11,6 @@ import { createAction, props } from '@ngrx/store'; import { FileInfo } from '../models'; -export const loadFiles = createAction('[Metamodel] Load Files', props<{ path: string }>()); -export const loadFilesSuccess = createAction('[Metamodel] Load Files Success', props<{ files: FileInfo[] }>()); -export const loadFilesFail = createAction('[Metamodel] Load Files Fail'); \ No newline at end of file +export const loadFiles = createAction('[Admin] Load Files', props<{ path: string }>()); +export const loadFilesSuccess = createAction('[Admin] Load Files Success', props<{ files: FileInfo[] }>()); +export const loadFilesFail = createAction('[Admin] Load Files Fail'); \ No newline at end of file diff --git a/client/src/app/admin/store/actions/attribute-distinct.actions.ts b/client/src/app/admin/store/actions/attribute-distinct.actions.ts index 59f6d37d..0ca8bb7d 100644 --- a/client/src/app/admin/store/actions/attribute-distinct.actions.ts +++ b/client/src/app/admin/store/actions/attribute-distinct.actions.ts @@ -11,6 +11,6 @@ import { createAction, props } from '@ngrx/store'; import { Attribute } from 'src/app/metamodel/models'; -export const loadAttributeDistinctList = createAction('[Metamodel] Load Attribute Distinct List', props<{ attribute: Attribute }>()); -export const loadAttributeDistinctListSuccess = createAction('[Metamodel] Load Attribute List Distinct Success', props<{ values: string[] }>()); -export const loadAttributeDistinctListFail = createAction('[Metamodel] Load Attribute List Distinct Fail'); +export const loadAttributeDistinctList = createAction('[Admin] Load Attribute Distinct List', props<{ attribute: Attribute }>()); +export const loadAttributeDistinctListSuccess = createAction('[Admin] Load Attribute List Distinct Success', props<{ values: string[] }>()); +export const loadAttributeDistinctListFail = createAction('[Admin] Load Attribute List Distinct Fail'); diff --git a/client/src/app/admin/store/actions/column.actions.ts b/client/src/app/admin/store/actions/column.actions.ts index 77ccf234..7cfb955a 100644 --- a/client/src/app/admin/store/actions/column.actions.ts +++ b/client/src/app/admin/store/actions/column.actions.ts @@ -11,6 +11,6 @@ import { createAction, props } from '@ngrx/store'; import { Column } from '../models'; -export const loadColumnList = createAction('[Metamodel] Load Column List'); -export const loadColumnListSuccess = createAction('[Metamodel] Load Column List Success', props<{ columns: Column[] }>()); -export const loadColumnListFail = createAction('[Metamodel] Load Column List Fail'); +export const loadColumnList = createAction('[Admin] Load Column List'); +export const loadColumnListSuccess = createAction('[Admin] Load Column List Success', props<{ columns: Column[] }>()); +export const loadColumnListFail = createAction('[Admin] Load Column List Fail'); diff --git a/client/src/app/admin/store/actions/table.actions.ts b/client/src/app/admin/store/actions/table.actions.ts index 2e10cc5e..6538d572 100644 --- a/client/src/app/admin/store/actions/table.actions.ts +++ b/client/src/app/admin/store/actions/table.actions.ts @@ -9,6 +9,6 @@ import { createAction, props } from '@ngrx/store'; -export const loadTableList = createAction('[Metamodel] Load Table List', props<{ idDatabase: number }>()); -export const loadTableListSuccess = createAction('[Metamodel] Load Table List Success', props<{ tables: string[] }>()); -export const loadTableListFail = createAction('[Metamodel] Load Table List Fail'); +export const loadTableList = createAction('[Admin] Load Table List', props<{ idDatabase: number }>()); +export const loadTableListSuccess = createAction('[Admin] Load Table List Success', props<{ tables: string[] }>()); +export const loadTableListFail = createAction('[Admin] Load Table List Fail'); diff --git a/client/src/app/instance/home/components/welcome.component.ts b/client/src/app/instance/home/components/welcome.component.ts index 6eaf4c53..316ec2b1 100644 --- a/client/src/app/instance/home/components/welcome.component.ts +++ b/client/src/app/instance/home/components/welcome.component.ts @@ -30,6 +30,6 @@ export class WelcomeComponent { * @return string */ getLogoSrc(): string { - return `${this.apiUrl}/download-instance-file/${this.instance.name}/${this.instance.home_component_config.home_component_logo}`; + return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${this.instance.home_component_config.home_component_logo}`; } } diff --git a/client/src/app/instance/instance.component.ts b/client/src/app/instance/instance.component.ts index ed464740..fe75d5f4 100644 --- a/client/src/app/instance/instance.component.ts +++ b/client/src/app/instance/instance.component.ts @@ -73,7 +73,7 @@ export class InstanceComponent implements OnInit, OnDestroy { this.links.push({ label: instance.documentation_label, icon: 'fas fa-question', routerLink: 'documentation' }); } if (instance.design_favicon !== '') { - this.favIcon.href = `${this.config.apiUrl}/download-instance-file/${instance.name}/${instance.design_favicon}`; + this.favIcon.href = `${this.config.apiUrl}/instance/${instance.name}/file-explorer${instance.design_favicon}`; } this.title.innerHTML = instance.label; } diff --git a/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts b/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts index 9cdb554f..e2cfc9a3 100644 --- a/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts +++ b/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts @@ -37,7 +37,11 @@ export class DownloadRendererComponent { * @return string */ getHref(): string { - return `${getHost(this.appConfig.apiUrl)}/download-file/${this.datasetName}/${this.value}`; + let path = this.value; + if (path[0] !== '/') { + path = '/' + path; + } + return `${getHost(this.appConfig.apiUrl)}/dataset/${this.datasetName}/file-explorer${path}`; } /** diff --git a/client/src/app/portal/components/instance-card.component.ts b/client/src/app/portal/components/instance-card.component.ts index 28263dbc..7269b4ef 100644 --- a/client/src/app/portal/components/instance-card.component.ts +++ b/client/src/app/portal/components/instance-card.component.ts @@ -32,6 +32,6 @@ export class InstanceCardComponent { * @return string */ getLogoSrc(): string { - return `${this.apiUrl}/download-instance-file/${this.instance.name}/${this.instance.portal_logo}`; + return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${this.instance.portal_logo}`; } } diff --git a/client/src/app/shared/components/navbar.component.ts b/client/src/app/shared/components/navbar.component.ts index 24d37cb2..5af1e0ea 100644 --- a/client/src/app/shared/components/navbar.component.ts +++ b/client/src/app/shared/components/navbar.component.ts @@ -67,7 +67,7 @@ export class NavbarComponent { */ getLogoHref(): string { if (this.instance.design_logo) { - return `${this.apiUrl}/download-instance-file/${this.instance.name}/${this.instance.design_logo}`; + return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${this.instance.design_logo}`; } return 'assets/cesam_anis40.png'; } diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh index fed4add6..28a54e99 100644 --- a/conf-dev/create-db.sh +++ b/conf-dev/create-db.sh @@ -60,7 +60,7 @@ curl -d '{"label":"Spectra graph","value":"spectra_graph","display":20,"select_n curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' --header 'Content-Type: application/json' -X POST http://localhost/database # Add default instance -curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","display":10,"data_path":"\/DEFAULT","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"logo.png","design_favicon":"favicon.ico","home_component":"WelcomeComponent","home_component_config":{"home_component_text":"AstroNomical Information System","home_component_logo":"home_component_logo.png"},"samp_enabled":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance +curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","display":10,"data_path":"\/DEFAULT","files_path":"\/INSTANCE_FILES","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"logo.png","design_favicon":"favicon.ico","home_component":"WelcomeComponent","home_component_config":{"home_component_text":"AstroNomical Information System","home_component_logo":"home_component_logo.png"},"samp_enabled":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance # Add ANIS, SVOM and IRIS surveys curl -d '{"name":"anis_survey","label":"ANIS survey","description":"Survey used for testing","link":"https://anis.lam.fr","manager":"F. Agneray","id_database":1}' --header 'Content-Type: application/json' -X POST http://localhost/survey diff --git a/server/app/dependencies.php b/server/app/dependencies.php index fdd15944..366ed502 100644 --- a/server/app/dependencies.php +++ b/server/app/dependencies.php @@ -145,6 +145,10 @@ $container->set('App\Action\InstanceAction', function (ContainerInterface $c) { return new App\Action\InstanceAction($c->get('em')); }); +$container->set('App\Action\InstanceFileExplorerAction', function (ContainerInterface $c) { + return new App\Action\InstanceFileExplorerAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']); +}); + $container->set('App\Action\DatasetFamilyListAction', function (ContainerInterface $c) { return new App\Action\DatasetFamilyListAction($c->get('em')); }); @@ -260,11 +264,3 @@ $container->set('App\Action\DownloadArchiveAction', function (ContainerInterface $container->set('App\Action\DatasetFileExplorerAction', function (ContainerInterface $c) { return new App\Action\DatasetFileExplorerAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']); }); - -$container->set('App\Action\DownloadInstanceFileAction', function (ContainerInterface $c) { - return new App\Action\DownloadInstanceFileAction($c->get('em'), $c->get('settings')['data_path']); -}); - -$container->set('App\Action\DownloadFileAction', function (ContainerInterface $c) { - return new App\Action\DownloadFileAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']); -}); diff --git a/server/app/routes.php b/server/app/routes.php index 6247ccc5..b4e79e38 100644 --- a/server/app/routes.php +++ b/server/app/routes.php @@ -39,6 +39,7 @@ $app->group('', function (RouteCollectorProxy $group) { $group->map([OPTIONS, GET, PUT, DELETE], '/survey/{name}', App\Action\SurveyAction::class); $group->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class); $group->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class); + $group->map([OPTIONS, GET], '/instance/{name}/file-explorer[{fpath:.*}]', App\Action\InstanceFileExplorerAction::class); $group->map([OPTIONS, GET, POST], '/instance/{name}/group', App\Action\GroupListAction::class); $group->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class); $group->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\DatasetListByInstanceAction::class); @@ -46,6 +47,7 @@ $app->group('', function (RouteCollectorProxy $group) { $group->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class); $group->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class); $group->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class); + $group->map([OPTIONS, GET], '/dataset/{name}/file-explorer[{fpath:.*}]', App\Action\DatasetFileExplorerAction::class); $group->map([OPTIONS, GET, POST], '/dataset/{name}/criteria-family', App\Action\CriteriaFamilyListAction::class); $group->map([OPTIONS, GET, PUT, DELETE], '/criteria-family/{id}', App\Action\CriteriaFamilyAction::class); $group->map([OPTIONS, GET, POST], '/dataset/{name}/output-family', App\Action\OutputFamilyListAction::class); @@ -76,6 +78,7 @@ $app->group('', function (RouteCollectorProxy $group) { // Search actions $app->get('/search/{dname}', App\Action\SearchAction::class); +// Result actions $app->get('/start-task-create-result/{dname}', App\Action\StartTaskCreateResultAction::class); $app->get('/is-result-available/{id}', App\Action\IsResultAvailableAction::class); $app->get('/download-result/{dname}/{id}', App\Action\DownloadResultAction::class); @@ -84,8 +87,3 @@ $app->get('/download-result/{dname}/{id}', App\Action\DownloadResultAction::clas $app->get('/start-task-create-archive/{dname}', App\Action\StartTaskCreateArchiveAction::class); $app->get('/is-archive-available/{id}', App\Action\IsArchiveAvailableAction::class); $app->get('/download-archive/{dname}/{id}', App\Action\DownloadArchiveAction::class); - -// Explore and download individual files -$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/doctrine-proxy/__CG__AppEntityDataset.php b/server/doctrine-proxy/__CG__AppEntityDataset.php index eb60eb62..8b3dd799 100644 --- a/server/doctrine-proxy/__CG__AppEntityDataset.php +++ b/server/doctrine-proxy/__CG__AppEntityDataset.php @@ -67,10 +67,10 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy public function __sleep() { if ($this->__isInitialized__) { - return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'coneSearchPlotOpened', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; + return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'downloadEnabled', 'downloadOpened', 'downloadJson', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; } - return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'coneSearchPlotOpened', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; + return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'downloadEnabled', 'downloadOpened', 'downloadJson', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; } /** @@ -492,67 +492,67 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy /** * {@inheritDoc} */ - public function getConeSearchPlotOpened() + public function getDownloadEnabled() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConeSearchPlotOpened', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadEnabled', []); - return parent::getConeSearchPlotOpened(); + return parent::getDownloadEnabled(); } /** * {@inheritDoc} */ - public function setConeSearchPlotOpened($coneSearchPlotOpened) + public function setDownloadEnabled($downloadEnabled) { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConeSearchPlotOpened', [$coneSearchPlotOpened]); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadEnabled', [$downloadEnabled]); - return parent::setConeSearchPlotOpened($coneSearchPlotOpened); + return parent::setDownloadEnabled($downloadEnabled); } /** * {@inheritDoc} */ - public function getDownloadEnabled() + public function getDownloadOpened() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadEnabled', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadOpened', []); - return parent::getDownloadEnabled(); + return parent::getDownloadOpened(); } /** * {@inheritDoc} */ - public function setDownloadEnabled($downloadEnabled) + public function setDownloadOpened($downloadOpened) { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadEnabled', [$downloadEnabled]); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadOpened', [$downloadOpened]); - return parent::setDownloadEnabled($downloadEnabled); + return parent::setDownloadOpened($downloadOpened); } /** * {@inheritDoc} */ - public function getDownloadOpened() + public function getDownloadJson() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadOpened', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadJson', []); - return parent::getDownloadOpened(); + return parent::getDownloadJson(); } /** * {@inheritDoc} */ - public function setDownloadOpened($downloadOpened) + public function setDownloadJson($downloadJson) { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadOpened', [$downloadOpened]); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadJson', [$downloadJson]); - return parent::setDownloadOpened($downloadOpened); + return parent::setDownloadJson($downloadJson); } /** diff --git a/server/doctrine-proxy/__CG__AppEntityImage.php b/server/doctrine-proxy/__CG__AppEntityImage.php new file mode 100644 index 00000000..31bc931b --- /dev/null +++ b/server/doctrine-proxy/__CG__AppEntityImage.php @@ -0,0 +1,415 @@ +<?php + +namespace DoctrineProxies\__CG__\App\Entity; + + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR + */ +class Image extends \App\Entity\Image implements \Doctrine\ORM\Proxy\Proxy +{ + /** + * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with + * three parameters, being respectively the proxy object to be initialized, the method that triggered the + * initialization process and an array of ordered parameters that were passed to that method. + * + * @see \Doctrine\Common\Proxy\Proxy::__setInitializer + */ + public $__initializer__; + + /** + * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object + * + * @see \Doctrine\Common\Proxy\Proxy::__setCloner + */ + public $__cloner__; + + /** + * @var boolean flag indicating if this object was already initialized + * + * @see \Doctrine\Persistence\Proxy::__isInitialized + */ + public $__isInitialized__ = false; + + /** + * @var array<string, null> properties to be lazy loaded, indexed by property name + */ + public static $lazyPropertiesNames = array ( +); + + /** + * @var array<string, mixed> default values of properties to be lazy loaded, with keys being the property names + * + * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties + */ + public static $lazyPropertiesDefaults = array ( +); + + + + public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null) + { + + $this->__initializer__ = $initializer; + $this->__cloner__ = $cloner; + } + + + + + + + + /** + * + * @return array + */ + public function __sleep() + { + if ($this->__isInitialized__) { + return ['__isInitialized__', 'id', 'filePath', 'fileSize', 'raMin', 'raMax', 'decMin', 'decMax', 'stretch', 'pmin', 'pmax', 'dataset']; + } + + return ['__isInitialized__', 'id', 'filePath', 'fileSize', 'raMin', 'raMax', 'decMin', 'decMax', 'stretch', 'pmin', 'pmax', 'dataset']; + } + + /** + * + */ + public function __wakeup() + { + if ( ! $this->__isInitialized__) { + $this->__initializer__ = function (Image $proxy) { + $proxy->__setInitializer(null); + $proxy->__setCloner(null); + + $existingProperties = get_object_vars($proxy); + + foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) { + if ( ! array_key_exists($property, $existingProperties)) { + $proxy->$property = $defaultValue; + } + } + }; + + } + } + + /** + * + */ + public function __clone() + { + $this->__cloner__ && $this->__cloner__->__invoke($this, '__clone', []); + } + + /** + * Forces initialization of the proxy + */ + public function __load() + { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []); + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __isInitialized() + { + return $this->__isInitialized__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitialized($initialized) + { + $this->__isInitialized__ = $initialized; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitializer(\Closure $initializer = null) + { + $this->__initializer__ = $initializer; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __getInitializer() + { + return $this->__initializer__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setCloner(\Closure $cloner = null) + { + $this->__cloner__ = $cloner; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific cloning logic + */ + public function __getCloner() + { + return $this->__cloner__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + * @deprecated no longer in use - generated code now relies on internal components rather than generated public API + * @static + */ + public function __getLazyProperties() + { + return self::$lazyPropertiesDefaults; + } + + + /** + * {@inheritDoc} + */ + public function getId() + { + if ($this->__isInitialized__ === false) { + return (int) parent::getId(); + } + + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []); + + return parent::getId(); + } + + /** + * {@inheritDoc} + */ + public function getFilePath() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getFilePath', []); + + return parent::getFilePath(); + } + + /** + * {@inheritDoc} + */ + public function setFilePath($filePath) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setFilePath', [$filePath]); + + return parent::setFilePath($filePath); + } + + /** + * {@inheritDoc} + */ + public function getFileSize() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getFileSize', []); + + return parent::getFileSize(); + } + + /** + * {@inheritDoc} + */ + public function setFileSize($fileSize) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setFileSize', [$fileSize]); + + return parent::setFileSize($fileSize); + } + + /** + * {@inheritDoc} + */ + public function getRaMin() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getRaMin', []); + + return parent::getRaMin(); + } + + /** + * {@inheritDoc} + */ + public function setRaMin($raMin) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setRaMin', [$raMin]); + + return parent::setRaMin($raMin); + } + + /** + * {@inheritDoc} + */ + public function getRaMax() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getRaMax', []); + + return parent::getRaMax(); + } + + /** + * {@inheritDoc} + */ + public function setRaMax($raMax) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setRaMax', [$raMax]); + + return parent::setRaMax($raMax); + } + + /** + * {@inheritDoc} + */ + public function getDecMin() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDecMin', []); + + return parent::getDecMin(); + } + + /** + * {@inheritDoc} + */ + public function setDecMin($decMin) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDecMin', [$decMin]); + + return parent::setDecMin($decMin); + } + + /** + * {@inheritDoc} + */ + public function getDecMax() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDecMax', []); + + return parent::getDecMax(); + } + + /** + * {@inheritDoc} + */ + public function setDecMax($decMax) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDecMax', [$decMax]); + + return parent::setDecMax($decMax); + } + + /** + * {@inheritDoc} + */ + public function getStretch() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getStretch', []); + + return parent::getStretch(); + } + + /** + * {@inheritDoc} + */ + public function setStretch($stretch) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setStretch', [$stretch]); + + return parent::setStretch($stretch); + } + + /** + * {@inheritDoc} + */ + public function getPmin() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getPmin', []); + + return parent::getPmin(); + } + + /** + * {@inheritDoc} + */ + public function setPmin($pmin) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setPmin', [$pmin]); + + return parent::setPmin($pmin); + } + + /** + * {@inheritDoc} + */ + public function getPmax() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getPmax', []); + + return parent::getPmax(); + } + + /** + * {@inheritDoc} + */ + public function setPmax($pmax) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setPmax', [$pmax]); + + return parent::setPmax($pmax); + } + + /** + * {@inheritDoc} + */ + public function getDataset() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDataset', []); + + return parent::getDataset(); + } + + /** + * {@inheritDoc} + */ + public function jsonSerialize(): array + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'jsonSerialize', []); + + return parent::jsonSerialize(); + } + +} diff --git a/server/doctrine-proxy/__CG__AppEntityInstance.php b/server/doctrine-proxy/__CG__AppEntityInstance.php index d5f1d1d1..7b3ee869 100644 --- a/server/doctrine-proxy/__CG__AppEntityInstance.php +++ b/server/doctrine-proxy/__CG__AppEntityInstance.php @@ -67,10 +67,10 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy public function __sleep() { if ($this->__isInitialized__) { - return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; + return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'filesPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; } - return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; + return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'filesPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; } /** @@ -280,6 +280,28 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy return parent::setDataPath($dataPath); } + /** + * {@inheritDoc} + */ + public function getFilesPath() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getFilesPath', []); + + return parent::getFilesPath(); + } + + /** + * {@inheritDoc} + */ + public function setFilesPath($filesPath) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setFilesPath', [$filesPath]); + + return parent::setFilesPath($filesPath); + } + /** * {@inheritDoc} */ diff --git a/server/src/Action/AdminFileExplorerAction.php b/server/src/Action/AdminFileExplorerAction.php index 8f86b80e..d3100f8e 100644 --- a/server/src/Action/AdminFileExplorerAction.php +++ b/server/src/Action/AdminFileExplorerAction.php @@ -61,7 +61,7 @@ final class AdminFileExplorerAction $path = $this->dataPath; if (array_key_exists('fpath', $args)) { - $path .= DIRECTORY_SEPARATOR . $args['fpath']; + $path .= $args['fpath']; } if (!file_exists($path)) { diff --git a/server/src/Action/DatasetFileExplorerAction.php b/server/src/Action/DatasetFileExplorerAction.php index 077b0be7..84dabe47 100644 --- a/server/src/Action/DatasetFileExplorerAction.php +++ b/server/src/Action/DatasetFileExplorerAction.php @@ -16,7 +16,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Doctrine\ORM\EntityManagerInterface; use Slim\Exception\HttpNotFoundException; -use Slim\Exception\HttpBadRequestException; +use Nyholm\Psr7\Factory\Psr17Factory; /** * @author François Agneray <francois.agneray@lam.fr> @@ -71,7 +71,7 @@ final class DatasetFileExplorerAction extends AbstractAction } // Search the correct dataset with primary key - $datasetName = $args['dname']; + $datasetName = $args['name']; $dataset = $this->em->find('App\Entity\Dataset', $datasetName); // If dataset is not found 404 @@ -102,17 +102,11 @@ final class DatasetFileExplorerAction extends AbstractAction } // Dataset data_path - $path = $this->dataPath . $dataset->getFullDataPath(); + $instancePath = $dataset->getDatasetFamily()->getInstance()->getDataPath(); + $path = $this->dataPath . $instancePath . $dataset->getDataPath(); if (array_key_exists('fpath', $args)) { - $path .= DIRECTORY_SEPARATOR . $args['fpath']; - } - - if (is_file($path)) { - throw new HttpBadRequestException( - $request, - 'The requested path is a file' - ); + $path .= $args['fpath']; } if (!file_exists($path)) { @@ -122,18 +116,28 @@ final class DatasetFileExplorerAction extends AbstractAction ); } - $files = array(); - - foreach (scandir($path) as $file) { - $files[] = array( - 'name' => $file, - 'size' => filesize($path . DIRECTORY_SEPARATOR . $file), - 'type' => filetype($path . DIRECTORY_SEPARATOR . $file), - 'mimetype' => mime_content_type($path . DIRECTORY_SEPARATOR . $file) - ); + if (is_file($path)) { + // If the file found so stream it + $psr17Factory = new Psr17Factory(); + $stream = $psr17Factory->createStreamFromFile($path, 'r'); + + return $response->withBody($stream) + ->withHeader('Content-Type', mime_content_type($path)) + ->withHeader('Content-Length', filesize($path)); + } else { + $files = array(); + + foreach (scandir($path) as $file) { + $files[] = array( + 'name' => $file, + 'size' => filesize($path . DIRECTORY_SEPARATOR . $file), + 'type' => filetype($path . DIRECTORY_SEPARATOR . $file), + 'mimetype' => mime_content_type($path . DIRECTORY_SEPARATOR . $file) + ); + } + + $response->getBody()->write(json_encode($files)); + return $response; } - - $response->getBody()->write(json_encode($files)); - return $response; } } diff --git a/server/src/Action/DownloadInstanceFileAction.php b/server/src/Action/DownloadInstanceFileAction.php deleted file mode 100644 index c450dea0..00000000 --- a/server/src/Action/DownloadInstanceFileAction.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php - -/* - * This file is part of Anis Server. - * - * (c) Laboratoire d'Astrophysique de Marseille / CNRS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -declare(strict_types=1); - -namespace App\Action; - -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\ResponseInterface; -use Doctrine\ORM\EntityManagerInterface; -use Slim\Exception\HttpNotFoundException; -use Nyholm\Psr7\Factory\Psr17Factory; - -/** - * @author François Agneray <francois.agneray@lam.fr> - * @package App\Action - */ -final class DownloadInstanceFileAction extends AbstractAction -{ - /** - * Contains anis-server data path - * - * @var string - */ - private $dataPath; - - /** - * Create the classe before call __invoke to execute the action - * - * @param EntityManagerInterface $em Doctrine Entity Manager Interface - * @param string $dataPath Contains anis-server data path - */ - public function __construct(EntityManagerInterface $em, string $dataPath) - { - parent::__construct($em); - $this->dataPath = $dataPath; - } - - /** - * `GET` Returns the file found - * - * @param ServerRequestInterface $request PSR-7 This object represents the HTTP request - * @param ResponseInterface $response PSR-7 This object represents the HTTP response - * @param string[] $args This table contains information transmitted in the URL (see routes.php) - * - * @return ResponseInterface - */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - array $args - ): ResponseInterface { - if ($request->getMethod() === OPTIONS) { - return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); - } - - // Search the correct instance with primary key - $instanceName = $args['iname']; - $instance = $this->em->find('App\Entity\Instance', $instanceName); - - // If dataset is not found 404 - if (is_null($instance)) { - throw new HttpNotFoundException( - $request, - 'Instance with name ' . $instanceName . ' is not found' - ); - } - - // Search the file - $filePath = $this->dataPath . $instance->getDataPath() . DIRECTORY_SEPARATOR . $args['fpath']; - - // If the file not found 404 - if (!file_exists($filePath)) { - throw new HttpNotFoundException( - $request, - 'File with path ' . $args['fpath'] . ' is not found for the instance ' . $instanceName - ); - } - - // If the file found so stream it - $psr17Factory = new Psr17Factory(); - $stream = $psr17Factory->createStreamFromFile($filePath, 'r'); - - return $response->withBody($stream) - ->withHeader('Content-Disposition', 'attachment; filename=' . basename($filePath) . ';') - ->withHeader('Content-Type', mime_content_type($filePath)) - ->withHeader('Content-Length', filesize($filePath)); - } -} diff --git a/server/src/Action/InstanceAction.php b/server/src/Action/InstanceAction.php index 7f2b5789..a9044e59 100644 --- a/server/src/Action/InstanceAction.php +++ b/server/src/Action/InstanceAction.php @@ -99,6 +99,7 @@ final class InstanceAction extends AbstractAction $instance->setDescription($parsedBody['description']); $instance->setDisplay($parsedBody['display']); $instance->setDataPath($parsedBody['data_path']); + $instance->setFilesPath($parsedBody['files_path']); $instance->setPublic($parsedBody['public']); $instance->setPortalLogo($parsedBody['portal_logo']); $instance->setDesignColor($parsedBody['design_color']); diff --git a/server/src/Action/DownloadFileAction.php b/server/src/Action/InstanceFileExplorerAction.php similarity index 54% rename from server/src/Action/DownloadFileAction.php rename to server/src/Action/InstanceFileExplorerAction.php index 196a0ad3..f613a420 100644 --- a/server/src/Action/DownloadFileAction.php +++ b/server/src/Action/InstanceFileExplorerAction.php @@ -22,7 +22,7 @@ use Nyholm\Psr7\Factory\Psr17Factory; * @author François Agneray <francois.agneray@lam.fr> * @package App\Action */ -final class DownloadFileAction extends AbstractAction +final class InstanceFileExplorerAction extends AbstractAction { /** * Contains anis-server data path @@ -32,7 +32,7 @@ final class DownloadFileAction extends AbstractAction private $dataPath; /** - * Contains settings to handle Json Web Token + * Contains settings to handle Json Web Token (app/settings.php) * * @var array */ @@ -41,9 +41,9 @@ final class DownloadFileAction extends AbstractAction /** * Create the classe before call __invoke to execute the action * - * @param EntityManagerInterface $em Doctrine Entity Manager Interface - * @param string $dataPath Contains anis-server data path - * @param array $settings Settings about token + * @param EntityManagerInterface $em Doctrine Entity Manager Interface + * @param string $dataPath Contains anis-server data path + * @param array $settings Settings about token */ public function __construct(EntityManagerInterface $em, string $dataPath, array $settings) { @@ -53,7 +53,7 @@ final class DownloadFileAction extends AbstractAction } /** - * `GET` Returns the file found + * `GET` Returns the list of files if path is a directory or stream file * * @param ServerRequestInterface $request PSR-7 This object represents the HTTP request * @param ResponseInterface $response PSR-7 This object represents the HTTP response @@ -70,20 +70,18 @@ final class DownloadFileAction extends AbstractAction return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); } - // Search the correct dataset with primary key - $datasetName = $args['dname']; - $dataset = $this->em->find('App\Entity\Dataset', $datasetName); + // Search the correct instance with primary key + $instance = $this->em->find('App\Entity\Instance', $args['name']); - // If dataset is not found 404 - if (is_null($dataset)) { + // If instance is not found 404 + if (is_null($instance)) { throw new HttpNotFoundException( $request, - 'Dataset with name ' . $datasetName . ' is not found' + 'Instance with name ' . $args['name'] . ' is not found' ); } // If instance is private and authorization enabled - $instance = $dataset->getDatasetFamily()->getInstance(); if (!$instance->getPublic() && boolval($this->settings['enabled'])) { $this->verifyInstanceAuthorization( $request, @@ -92,33 +90,41 @@ final class DownloadFileAction extends AbstractAction ); } - // If dataset is private and authorization enabled - if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { - $this->verifyDatasetAuthorization( - $request, - $dataset->getName(), - explode(',', $this->settings['admin_roles']) - ); - } + $path = $this->dataPath . $instance->getDataPath() . $instance->getFilesPath(); - // Search the file - $filePath = $this->dataPath . $dataset->getFullDataPath() . DIRECTORY_SEPARATOR . $args['fpath']; + if (array_key_exists('fpath', $args)) { + $path .= $args['fpath']; + } - // If the file not found 404 - if (!file_exists($filePath)) { + if (!file_exists($path)) { throw new HttpNotFoundException( $request, - 'File with path ' . $args['fpath'] . ' is not found for the dataset ' . $datasetName + 'The requested path is not found' ); } - // If the file found so stream it - $psr17Factory = new Psr17Factory(); - $stream = $psr17Factory->createStreamFromFile($filePath, 'r'); + if (is_file($path)) { + // If the file found so stream it + $psr17Factory = new Psr17Factory(); + $stream = $psr17Factory->createStreamFromFile($path, 'r'); + + return $response->withBody($stream) + ->withHeader('Content-Type', mime_content_type($path)) + ->withHeader('Content-Length', filesize($path)); + } else { + $files = array(); - return $response->withBody($stream) - ->withHeader('Content-Disposition', 'attachment; filename=' . basename($filePath) . ';') - ->withHeader('Content-Type', mime_content_type($filePath)) - ->withHeader('Content-Length', filesize($filePath)); + foreach (scandir($path) as $file) { + $files[] = array( + 'name' => $file, + 'size' => filesize($path . DIRECTORY_SEPARATOR . $file), + 'type' => filetype($path . DIRECTORY_SEPARATOR . $file), + 'mimetype' => mime_content_type($path . DIRECTORY_SEPARATOR . $file) + ); + } + + $response->getBody()->write(json_encode($files)); + return $response; + } } } diff --git a/server/src/Action/InstanceListAction.php b/server/src/Action/InstanceListAction.php index 1dc9f4cb..40825374 100644 --- a/server/src/Action/InstanceListAction.php +++ b/server/src/Action/InstanceListAction.php @@ -132,6 +132,7 @@ final class InstanceListAction extends AbstractAction $instance->setDescription($parsedBody['description']); $instance->setDisplay($parsedBody['display']); $instance->setDataPath($parsedBody['data_path']); + $instance->setFilesPath($parsedBody['files_path']); $instance->setPublic($parsedBody['public']); $instance->setPortalLogo($parsedBody['portal_logo']); $instance->setDesignColor($parsedBody['design_color']); diff --git a/server/src/Entity/Instance.php b/server/src/Entity/Instance.php index 044e3261..bfc15464 100644 --- a/server/src/Entity/Instance.php +++ b/server/src/Entity/Instance.php @@ -59,6 +59,13 @@ class Instance implements \JsonSerializable */ protected $dataPath; + /** + * @var string + * + * @Column(type="string", name="files_path", nullable=true) + */ + protected $filesPath; + /** * @var bool * @@ -230,6 +237,16 @@ class Instance implements \JsonSerializable $this->dataPath = $dataPath; } + public function getFilesPath() + { + return $this->filesPath; + } + + public function setFilesPath($filesPath) + { + $this->filesPath = $filesPath; + } + public function getPublic() { return $this->public; @@ -412,6 +429,7 @@ class Instance implements \JsonSerializable 'description' => $this->getDescription(), 'display' => $this->getDisplay(), 'data_path' => $this->getDataPath(), + 'files_path' => $this->getFilesPath(), 'public' => $this->getPublic(), 'portal_logo' => $this->getPortalLogo(), 'design_color' => $this->getDesignColor(), diff --git a/server/tests/Action/InstanceActionTest.php b/server/tests/Action/InstanceActionTest.php index 2b79c1c9..1340c77e 100644 --- a/server/tests/Action/InstanceActionTest.php +++ b/server/tests/Action/InstanceActionTest.php @@ -82,6 +82,7 @@ final class InstanceActionTest extends TestCase 'description' => 'Test', 'display' => 10, 'data_path' => '/DEFAULT', + 'files_path' => '/INSTANCE_FILES', 'public' => true, 'portal_logo' => '', 'design_color' => '#7AC29A', diff --git a/server/tests/Action/InstanceListActionTest.php b/server/tests/Action/InstanceListActionTest.php index d263fcdd..2254c0b9 100644 --- a/server/tests/Action/InstanceListActionTest.php +++ b/server/tests/Action/InstanceListActionTest.php @@ -81,6 +81,7 @@ final class InstanceListActionTest extends TestCase 'description' => 'Test', 'display' => 10, 'data_path' => '/DEFAULT', + 'files_path' => '/INSTANCE_FILES', 'public' => true, 'portal_logo' => '', 'design_color' => '#7AC29A', -- GitLab