This is an automated email from the ASF dual-hosted git repository.

zehnder pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new 4495b463b Update of PR #2300 (#2356)
4495b463b is described below

commit 4495b463bc000f92e562217a2dd164b754cb82ba
Author: Philipp Zehnder <[email protected]>
AuthorDate: Fri Dec 29 08:46:17 2023 +0100

    Update of PR #2300 (#2356)
    
    * Serialize non-primitive types and store in Influx
    
    * extract RawFieldSerializer
    
    * rename test
    
    * delete old
    
    * temp
    
    * redesign file api
    
    * fix
    
    * Update 
streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigration.java
    
    Co-authored-by: Tim <[email protected]>
    
    * Update 
ui/src/app/files/dialog/file-rename/file-rename-dialog.component.html
    
    Co-authored-by: Tim <[email protected]>
    
    * Update 
streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
    
    Co-authored-by: Tim <[email protected]>
    
    * Update 
streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
    
    Co-authored-by: Tim <[email protected]>
    
    * refactor(#2181): Refactor DuplicateFilesRenameMigration
    
    * Add comment for migration
    
    * fix(#2356): Fix linting error
    
    * fix(#2356): Fix checkstyle proble
    
    ---------
    
    Co-authored-by: muyangye <[email protected]>
    Co-authored-by: Tim <[email protected]>
---
 .../streampipes/rest/impl/PipelineElementFile.java |  19 ++-
 streampipes-service-core/pom.xml                   |   6 +
 .../core/migrations/AvailableMigrations.java       |   4 +-
 .../v095/DuplicateFilesRenameMigration.java        | 142 +++++++++++++++++++++
 .../v095/DuplicateFilesRenameMigrationTest.java    | 120 +++++++++++++++++
 .../storage/api/IFileMetadataStorage.java          |   2 +
 .../couchdb/impl/FileMetadataStorageImpl.java      |   5 +
 .../src/lib/apis/files.service.ts                  |   6 +
 .../static-file-input.component.ts                 |  86 ++++++++++---
 .../file-rename/file-rename-dialog.component.html  |  72 +++++++++++
 .../file-rename/file-rename-dialog.component.scss  |  18 +--
 .../file-rename/file-rename-dialog.component.ts    |  34 ++---
 .../file-upload/file-upload-dialog.component.html  |  42 +++++-
 .../file-upload/file-upload-dialog.component.ts    |  39 +++++-
 ui/src/app/files/files.module.ts                   |   4 +
 15 files changed, 539 insertions(+), 60 deletions(-)

diff --git 
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
 
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
index 72a3f4d0a..2a71a5a0a 100644
--- 
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
+++ 
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
@@ -61,7 +61,8 @@ public class PipelineElementFile extends 
AbstractAuthGuardedRestResource {
           FileManager.storeFile(
               getAuthenticatedUsername(),
               fileDetail.getOriginalFilename(),
-              fileDetail.getInputStream());
+              fileDetail.getInputStream()
+          );
       return ok(metadata);
     } catch (Exception e) {
       return fail();
@@ -78,7 +79,8 @@ public class PipelineElementFile extends 
AbstractAuthGuardedRestResource {
   @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
   @PreAuthorize(AuthConstants.HAS_READ_FILE_PRIVILEGE)
   public ResponseEntity<List<FileMetadata>> getFileInfo(
-      @RequestParam(value = "filetypes", required = false) String filetypes) {
+      @RequestParam(value = "filetypes", required = false) String filetypes
+  ) {
     return ok(FileManager.getAllFiles(filetypes));
   }
 
@@ -119,11 +121,22 @@ public class PipelineElementFile extends 
AbstractAuthGuardedRestResource {
     } catch (IOException e) {
       throw new SpMessageException(
           org.springframework.http.HttpStatus.NOT_FOUND,
-          Notifications.error("File not found"));
+          Notifications.error("File not found")
+      );
     }
   }
 
   private byte[] getFileContents(File file) throws IOException {
     return Files.readAllBytes(file.toPath());
   }
+
+  @GetMapping(path = "/allFilenames", produces = 
MediaType.APPLICATION_JSON_VALUE)
+  @PreAuthorize(AuthConstants.HAS_READ_FILE_PRIVILEGE)
+  public ResponseEntity<List<String>> getAllOriginalFilenames() {
+    return ok(FileManager.getAllFiles()
+                         .stream()
+                         .map(fileMetadata -> 
fileMetadata.getOriginalFilename()
+                                                          .toLowerCase())
+                         .toList());
+  }
 }
diff --git a/streampipes-service-core/pom.xml b/streampipes-service-core/pom.xml
index fdb5c5fd1..48390b1c7 100644
--- a/streampipes-service-core/pom.xml
+++ b/streampipes-service-core/pom.xml
@@ -108,6 +108,12 @@
             <groupId>jakarta.annotation</groupId>
             <artifactId>jakarta.annotation-api</artifactId>
         </dependency>
+      <!-- Test dependencies -->
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
 
     <build>
diff --git 
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
 
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
index b389241b4..be23f8f0d 100644
--- 
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
+++ 
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
@@ -25,6 +25,7 @@ import 
org.apache.streampipes.service.core.migrations.v070.CreateFileAssetTypeMi
 import 
org.apache.streampipes.service.core.migrations.v090.UpdateUsernameViewMigration;
 import org.apache.streampipes.service.core.migrations.v093.AdapterMigration;
 import 
org.apache.streampipes.service.core.migrations.v093.StoreEmailTemplatesMigration;
+import 
org.apache.streampipes.service.core.migrations.v095.DuplicateFilesRenameMigration;
 
 import java.util.Arrays;
 import java.util.List;
@@ -38,7 +39,8 @@ public class AvailableMigrations {
         new CreateFileAssetTypeMigration(),
         new UpdateUsernameViewMigration(),
         new AdapterMigration(),
-        new StoreEmailTemplatesMigration()
+        new StoreEmailTemplatesMigration(),
+        new DuplicateFilesRenameMigration()
     );
   }
 }
diff --git 
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigration.java
 
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigration.java
new file mode 100644
index 000000000..e5e1f7647
--- /dev/null
+++ 
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigration.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.service.core.migrations.v095;
+
+import org.apache.streampipes.model.file.FileMetadata;
+import org.apache.streampipes.service.core.migrations.Migration;
+import org.apache.streampipes.storage.management.StorageDispatcher;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class DuplicateFilesRenameMigration implements Migration {
+  @Override
+  public boolean shouldExecute() {
+    return true;
+  }
+
+  // Starting from v0.95, StreamPipes will use file name as the unique 
identifier of files
+  // This migration renames all the files that have duplicate names to ensure 
uniqueness
+  @Override
+  public void executeMigration() {
+    var fileMetadataStorage = StorageDispatcher.INSTANCE.getNoSqlStore()
+                                                        
.getFileMetadataStorage();
+
+    var filesToUpdate = 
getFilesToUpdate(fileMetadataStorage.getAllFileMetadataDescriptions());
+
+    filesToUpdate.forEach(fileMetadata -> 
fileMetadataStorage.updateFileMetadata(fileMetadata));
+  }
+
+
+  /**
+   * Takes the list of files and groups all files with the same name together.
+   * The result is a list for each file name that has more than one file 
associated with it.
+   */
+  private List<List<FileMetadata>> 
getListsOfFilesWithSameName(List<FileMetadata> filesWithOldName) {
+    var duplicateFileMap = filesWithOldName.stream()
+                                           .collect(
+                                               Collectors.groupingBy(file -> 
file.getOriginalFilename()
+                                                                               
  .toLowerCase()));
+    return duplicateFileMap.values()
+                           .stream()
+                           .filter(files -> files.size() > 1)
+                           .toList();
+  }
+
+  @Override
+  public String getDescription() {
+    return "Rename files with duplicate names.";
+  }
+
+  /**
+   * Takes the files searches for duplicates and renames them and returns the 
files that must be updated
+   */
+  protected List<FileMetadata> getFilesToUpdate(List<FileMetadata> 
filesWithOldName) {
+
+    var groupsOfFilesWithSameName = 
getListsOfFilesWithSameName(filesWithOldName);
+
+    return groupsOfFilesWithSameName.stream()
+                                    .flatMap(filesWithSameName ->
+                                                 
renameFilesWithSameName(filesWithSameName).stream())
+                                    .collect(Collectors.toList());
+  }
+
+
+  /**
+   * Takes a list of files with the same name renames them and returns the 
updated list
+   */
+  private List<FileMetadata> renameFilesWithSameName(List<FileMetadata> 
filesWithSameName) {
+    return IntStream.range(1, filesWithSameName.size())
+                    .mapToObj(i -> {
+                      var metadata = filesWithSameName.get(i);
+                      return renameFile(metadata, i);
+                    })
+                    .toList();
+  }
+
+
+  /**
+   * Takes a file and renames it with a new name based the number of 
occurrences of the file name
+   */
+  private FileMetadata renameFile(FileMetadata fileMetadata, int index) {
+    var oldFilename = fileMetadata.getOriginalFilename();
+
+    var fileNameWithoutType = removeFileType(oldFilename);
+    var fileTypeSuffix = getFileType(oldFilename);
+
+    var newFileName = createNewFileName(index, fileNameWithoutType, 
fileTypeSuffix);
+
+    fileMetadata.setOriginalFilename(newFileName);
+
+    return fileMetadata;
+  }
+
+  /**
+   * Creates the new file name for a file with a duplicate name.
+   */
+  private String createNewFileName(
+      int index,
+      String fileName,
+      String fileType
+  ) {
+    return String.format(
+        "%s(%d)%s",
+        fileName,
+        index + 1,
+        fileType
+    );
+  }
+
+  /**
+   * Returns file name without file type suffix.
+   */
+  private String removeFileType(String fileName) {
+    var indexBeforeFileType = fileName.lastIndexOf('.');
+    return fileName.substring(0, indexBeforeFileType);
+  }
+
+  /**
+   * Returns the file type a given file name.
+   */
+  private String getFileType(String fileName) {
+    var indexBeforeFileType = fileName.lastIndexOf('.');
+    return fileName.substring(indexBeforeFileType);
+  }
+}
diff --git 
a/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigrationTest.java
 
b/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigrationTest.java
new file mode 100644
index 000000000..18b35846f
--- /dev/null
+++ 
b/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigrationTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.service.core.migrations.v095;
+
+
+import org.apache.streampipes.model.file.FileMetadata;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class DuplicateFilesRenameMigrationTest {
+
+  private DuplicateFilesRenameMigration migration;
+
+  private static final String FILE_NAME = "file.txt";
+  private static final String NEW_FILE_NAME_2 = "file(2).txt";
+  private static final String NEW_FILE_NAME_3 = "file(3).txt";
+
+  @Before
+  public void setUp() {
+    migration = new DuplicateFilesRenameMigration();
+  }
+
+  @Test
+  public void getFilesToUpdateHandlesNoDuplicates() {
+    var filesWithOldName = createFiles(List.of(
+        FILE_NAME
+    ));
+    var result = migration.getFilesToUpdate(filesWithOldName);
+    assertTrue(result.isEmpty());
+  }
+
+  @Test
+  public void getFilesToUpdateHandlesSingleDuplicate() {
+    var filesWithOldName = createFiles(List.of(
+        FILE_NAME,
+        FILE_NAME
+    ));
+    var result = migration.getFilesToUpdate(filesWithOldName);
+    assertEquals(1, result.size());
+    assertEquals(
+        NEW_FILE_NAME_2,
+        result.get(0)
+              .getOriginalFilename()
+    );
+  }
+
+  @Test
+  public void getFilesToUpdateHandlesMultipleDuplicates() {
+    var filesWithOldName = createFiles(List.of(
+        FILE_NAME,
+        FILE_NAME,
+        FILE_NAME
+    ));
+    var result = migration.getFilesToUpdate(filesWithOldName);
+    assertEquals(2, result.size());
+    assertEquals(
+        NEW_FILE_NAME_2,
+        result.get(0)
+              .getOriginalFilename()
+    );
+    assertEquals(
+        NEW_FILE_NAME_3,
+        result.get(1)
+              .getOriginalFilename()
+    );
+  }
+
+  @Test
+  public void getFilesToUpdateHandlesMixedDuplicatesAndUniqueFiles() {
+    var filesWithOldName = createFiles(List.of(
+        FILE_NAME,
+        FILE_NAME,
+        "unique.txt"
+    ));
+    var result = migration.getFilesToUpdate(filesWithOldName);
+    assertEquals(1, result.size());
+    assertEquals(
+        NEW_FILE_NAME_2,
+        result.get(0)
+              .getOriginalFilename()
+    );
+  }
+
+  /**
+   * Creates a list of FileMetadata objects from a list of file names.
+   */
+  private List<FileMetadata> createFiles(List<String> fileNames) {
+    return fileNames.stream()
+                    .map(this::createFileMetadata)
+                    .toList();
+  }
+
+  private FileMetadata createFileMetadata(String fileName) {
+    var fileMetadata = new FileMetadata();
+    fileMetadata.setOriginalFilename(fileName);
+    return fileMetadata;
+  }
+}
\ No newline at end of file
diff --git 
a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
 
b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
index 4abaa66c7..13eae66ed 100644
--- 
a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
+++ 
b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
@@ -32,4 +32,6 @@ public interface IFileMetadataStorage {
   void deleteFileMetadata(String id);
 
   void addFileMetadata(FileMetadata fileMetadata);
+
+  void updateFileMetadata(FileMetadata fileMetadata);
 }
diff --git 
a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/FileMetadataStorageImpl.java
 
b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/FileMetadataStorageImpl.java
index e55819946..618b10885 100644
--- 
a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/FileMetadataStorageImpl.java
+++ 
b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/FileMetadataStorageImpl.java
@@ -65,4 +65,9 @@ public class FileMetadataStorageImpl extends 
AbstractDao<FileMetadata> implement
   public void addFileMetadata(FileMetadata fileMetadata) {
     persist(fileMetadata);
   }
+
+  @Override
+  public void updateFileMetadata(FileMetadata fileMetadata) {
+    update(fileMetadata);
+  }
 }
diff --git 
a/ui/projects/streampipes/platform-services/src/lib/apis/files.service.ts 
b/ui/projects/streampipes/platform-services/src/lib/apis/files.service.ts
index 1439093ad..2c7f759bb 100644
--- a/ui/projects/streampipes/platform-services/src/lib/apis/files.service.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/apis/files.service.ts
@@ -90,4 +90,10 @@ export class FilesService {
             },
         );
     }
+
+    getAllOriginalFilenames(): Observable<string[]> {
+        return this.http.get<string[]>(
+            this.platformServicesCommons.apiBasePath + '/files/allFilenames',
+        );
+    }
 }
diff --git 
a/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
 
b/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
index 9ad435b11..079a7449f 100644
--- 
a/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
+++ 
b/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
@@ -16,7 +16,13 @@
  *
  */
 
-import { Component, EventEmitter, OnInit, Output } from '@angular/core';
+import {
+    Component,
+    EventEmitter,
+    NgModule,
+    OnInit,
+    Output,
+} from '@angular/core';
 import { HttpEventType, HttpResponse } from '@angular/common/http';
 import {
     FilesService,
@@ -26,6 +32,8 @@ import {
 import { ConfigurationInfo } from '../../../connect/model/ConfigurationInfo';
 import { AbstractValidatedStaticPropertyRenderer } from 
'../base/abstract-validated-static-property';
 import { UntypedFormControl, ValidatorFn, Validators } from '@angular/forms';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
+import { FileRenameDialogComponent } from 
'../../../files/dialog/file-rename/file-rename-dialog.component';
 
 @Component({
     selector: 'sp-static-file-input',
@@ -40,6 +48,8 @@ export class StaticFileInputComponent
 
     public chooseExistingFileControl = new UntypedFormControl();
 
+    dialogRef: MatDialogRef<FileRenameDialogComponent>;
+
     fileName: string;
 
     selectedUploadFile: File;
@@ -54,7 +64,10 @@ export class StaticFileInputComponent
 
     filesLoaded = false;
 
-    constructor(private filesService: FilesService) {
+    constructor(
+        private filesService: FilesService,
+        public dialog: MatDialog,
+    ) {
         super();
     }
 
@@ -122,24 +135,44 @@ export class StaticFileInputComponent
     }
 
     upload() {
-        this.uploadStatus = 0;
         if (this.selectedUploadFile !== undefined) {
-            this.filesService.uploadFile(this.selectedUploadFile).subscribe(
-                event => {
-                    if (event.type === HttpEventType.UploadProgress) {
-                        this.uploadStatus = Math.round(
-                            (100 * event.loaded) / event.total,
-                        );
-                    } else if (event instanceof HttpResponse) {
-                        const internalFilename = event.body.internalFilename;
-                        this.parentForm.controls[this.fieldName].setValue(
-                            internalFilename,
-                        );
-                        this.fetchFileMetadata(internalFilename);
+            this.filesService
+                .getAllOriginalFilenames()
+                .subscribe(allFileNames => {
+                    if (
+                        !allFileNames.includes(
+                            this.selectedUploadFile.name.toLowerCase(),
+                        )
+                    ) {
+                        this.uploadStatus = 0;
+                        this.filesService
+                            .uploadFile(this.selectedUploadFile)
+                            .subscribe(
+                                event => {
+                                    if (
+                                        event.type ===
+                                        HttpEventType.UploadProgress
+                                    ) {
+                                        this.uploadStatus = Math.round(
+                                            (100 * event.loaded) / event.total,
+                                        );
+                                    } else if (event instanceof HttpResponse) {
+                                        const internalFilename =
+                                            event.body.internalFilename;
+                                        this.parentForm.controls[
+                                            this.fieldName
+                                        ].setValue(internalFilename);
+                                        this.fetchFileMetadata(
+                                            internalFilename,
+                                        );
+                                    }
+                                },
+                                error => {},
+                            );
+                    } else {
+                        this.openRenameDialog();
                     }
-                },
-                error => {},
-            );
+                });
         }
     }
 
@@ -163,4 +196,21 @@ export class StaticFileInputComponent
         this.staticProperty.locationPath = value.internalFilename;
         this.parentForm.updateValueAndValidity();
     }
+
+    openRenameDialog() {
+        this.dialogRef = this.dialog.open(FileRenameDialogComponent);
+        this.dialogRef.afterClosed().subscribe(data => {
+            if (data) {
+                this.fileName = data;
+                this.selectedUploadFile = new File(
+                    [this.selectedUploadFile],
+                    this.fileName,
+                    {
+                        type: this.selectedUploadFile.type,
+                        lastModified: this.selectedUploadFile.lastModified,
+                    },
+                );
+            }
+        });
+    }
 }
diff --git 
a/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.html 
b/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.html
new file mode 100644
index 000000000..121faeac0
--- /dev/null
+++ b/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.html
@@ -0,0 +1,72 @@
+<!--
+~ Licensed to the Apache Software Foundation (ASF) under one or more
+~ contributor license agreements.  See the NOTICE file distributed with
+~ this work for additional information regarding copyright ownership.
+~ The ASF licenses this file to You under the Apache License, Version 2.0
+~ (the "License"); you may not use this file except in compliance with
+~ the License.  You may obtain a copy of the License at
+~
+~    http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+~
+-->
+
+<div class="sp-dialog-container">
+    <div class="sp-dialog-content p-15">
+        <div fxFlex="100">
+            <div fxFlex="100" style="margin: 5px; width: 100%">
+                <div fxLayout="column" style="width: 100%">
+                    <div fxFlex="100" fxLayoutAlign="center" fxLayout="column">
+                        <b>
+                            <h4>
+                                A file with the same name already exists. 
Please
+                                give your file a new name or select one of the
+                                existing files. it from existing files or 
rename
+                                it:
+                            </h4>
+                        </b>
+                        <div class="input-group">
+                            <span style="margin-right: 10px"
+                                >new filename:</span
+                            >
+                            <mat-form-field>
+                                <input
+                                    matInput
+                                    placeholder="new filename"
+                                    [(ngModel)]="fileName"
+                                />
+                            </mat-form-field>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <mat-divider></mat-divider>
+    <div class="sp-dialog-actions">
+        <button
+            mat-button
+            mat-raised-button
+            color="accent"
+            [mat-dialog-close]="fileName"
+            data-cy="sp-file-management-store-file"
+            style="margin-right: 10px"
+        >
+            Rename file
+        </button>
+        <button
+            mat-button
+            mat-raised-button
+            class="mat-basic"
+            (click)="cancel()"
+            style="margin-right: 10px"
+        >
+            Cancel
+        </button>
+    </div>
+</div>
diff --git 
a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
 b/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.scss
similarity index 65%
copy from 
streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
copy to ui/src/app/files/dialog/file-rename/file-rename-dialog.component.scss
index 4abaa66c7..704f843e4 100644
--- 
a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
+++ b/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.scss
@@ -15,21 +15,5 @@
  * limitations under the License.
  *
  */
-package org.apache.streampipes.storage.api;
 
-import org.apache.streampipes.model.file.FileMetadata;
-
-import java.util.List;
-
-public interface IFileMetadataStorage {
-
-  FileMetadata getMetadataById(String id);
-
-  List<FileMetadata> getAllFileMetadataDescriptions();
-
-  List<FileMetadata> getFilteredFileMetadataDescriptions(String filetype);
-
-  void deleteFileMetadata(String id);
-
-  void addFileMetadata(FileMetadata fileMetadata);
-}
+@import 'src/scss/sp/sp-dialog';
diff --git 
a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
 b/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.ts
similarity index 58%
copy from 
streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
copy to ui/src/app/files/dialog/file-rename/file-rename-dialog.component.ts
index 4abaa66c7..24702ba3a 100644
--- 
a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IFileMetadataStorage.java
+++ b/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.ts
@@ -15,21 +15,23 @@
  * limitations under the License.
  *
  */
-package org.apache.streampipes.storage.api;
 
-import org.apache.streampipes.model.file.FileMetadata;
-
-import java.util.List;
-
-public interface IFileMetadataStorage {
-
-  FileMetadata getMetadataById(String id);
-
-  List<FileMetadata> getAllFileMetadataDescriptions();
-
-  List<FileMetadata> getFilteredFileMetadataDescriptions(String filetype);
-
-  void deleteFileMetadata(String id);
-
-  void addFileMetadata(FileMetadata fileMetadata);
+import { Component, Inject } from '@angular/core';
+import { DialogRef } from '@angular/cdk/dialog';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+
+@Component({
+    selector: 'sp-file-rename-dialog-component',
+    templateUrl: './file-rename-dialog.component.html',
+    styleUrls: ['./file-rename-dialog.component.scss'],
+})
+export class FileRenameDialogComponent {
+    constructor(
+        private dialogRef: DialogRef<FileRenameDialogComponent>,
+        @Inject(MAT_DIALOG_DATA) public fileName: string,
+    ) {}
+
+    cancel() {
+        this.dialogRef.close();
+    }
 }
diff --git 
a/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.html 
b/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.html
index 7fc060395..6d83e5a5c 100644
--- a/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.html
+++ b/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.html
@@ -18,7 +18,7 @@
 
 <div class="sp-dialog-container">
     <div class="sp-dialog-content p-15">
-        <div fxFlex="100">
+        <div fxFlex="100" *ngIf="this.duplicateFileNames.length === 0">
             <div fxFlex="100" style="margin: 5px; width: 100%">
                 <mat-form-field
                     style="width: 95%"
@@ -82,10 +82,38 @@
                 </button>
             </div>
         </div>
+        <div
+            *ngIf="this.duplicateFileNames.length !== 0"
+            fxLayout="column"
+            style="width: 100%"
+        >
+            <div fxFlex="100" fxLayoutAlign="center" fxLayout="column">
+                <b>
+                    <h4>
+                        The following files already exist. Please rename them.
+                    </h4>
+                </b>
+                <div
+                    *ngFor="let name of this.duplicateFileNames; let i = index"
+                >
+                    <div class="input-group">
+                        <span style="margin-right: 10px">{{ name }}:</span>
+                        <mat-form-field>
+                            <input
+                                matInput
+                                placeholder="e.g. {{ '_' + name }}"
+                                [(ngModel)]="this.renamedFileNames[i]"
+                            />
+                        </mat-form-field>
+                    </div>
+                </div>
+            </div>
+        </div>
     </div>
     <mat-divider></mat-divider>
     <div class="sp-dialog-actions">
         <button
+            *ngIf="this.duplicateFileNames.length === 0"
             mat-button
             mat-raised-button
             color="accent"
@@ -96,6 +124,18 @@
         >
             Import files
         </button>
+
+        <button
+            *ngIf="this.duplicateFileNames.length !== 0"
+            mat-button
+            mat-raised-button
+            color="accent"
+            (click)="renameDuplicateFiles()"
+            [disabled]="fileNames.length === 0"
+            style="margin-right: 10px"
+        >
+            Rename files
+        </button>
         <button
             mat-button
             mat-raised-button
diff --git 
a/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.ts 
b/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.ts
index d932dbab5..e5c1a8b3f 100644
--- a/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.ts
+++ b/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.ts
@@ -29,6 +29,8 @@ import { FilesService } from '@streampipes/platform-services';
 export class FileUploadDialogComponent {
     inputValue: string;
     fileNames: string[] = [];
+    duplicateFileNames: string[] = [];
+    renamedFileNames: string[] = [];
 
     selectedUploadFiles: FileList;
 
@@ -56,10 +58,18 @@ export class FileUploadDialogComponent {
     }
 
     store() {
-        this.uploadStatus = 0;
-        if (this.selectedUploadFiles.length > 0) {
-            this.uploadFile(0);
-        }
+        this.filesService.getAllOriginalFilenames().subscribe(data => {
+            const allFileNames = new Set(data);
+            this.duplicateFileNames = this.fileNames.filter(fileName =>
+                allFileNames.has(fileName.toLowerCase()),
+            );
+            if (this.duplicateFileNames.length === 0) {
+                this.uploadStatus = 0;
+                if (this.selectedUploadFiles.length > 0) {
+                    this.uploadFile(0);
+                }
+            }
+        });
     }
 
     uploadFile(index: number): void {
@@ -87,4 +97,25 @@ export class FileUploadDialogComponent {
     cancel() {
         this.dialogRef.close();
     }
+
+    renameDuplicateFiles() {
+        const dataTransfer = new DataTransfer();
+        for (let i = 0; i < this.fileNames.length; i++) {
+            let fileName = this.fileNames[i];
+            let index = this.duplicateFileNames.indexOf(fileName);
+            if (index !== -1) {
+                this.fileNames[i] = this.renamedFileNames[index];
+                fileName = this.renamedFileNames[index];
+            }
+            let selectedUploadFile = this.selectedUploadFiles[i];
+            const renamedFile = new File([selectedUploadFile], fileName, {
+                type: selectedUploadFile.type,
+                lastModified: selectedUploadFile.lastModified,
+            });
+            dataTransfer.items.add(renamedFile);
+        }
+        this.selectedUploadFiles = dataTransfer.files;
+        this.duplicateFileNames = [];
+        this.renamedFileNames = [];
+    }
 }
diff --git a/ui/src/app/files/files.module.ts b/ui/src/app/files/files.module.ts
index 1569438ac..a93ce7171 100644
--- a/ui/src/app/files/files.module.ts
+++ b/ui/src/app/files/files.module.ts
@@ -41,6 +41,8 @@ import { MatTooltipModule } from '@angular/material/tooltip';
 import { PlatformServicesModule } from '@streampipes/platform-services';
 import { RouterModule } from '@angular/router';
 import { SharedUiModule } from '@streampipes/shared-ui';
+import { FileRenameDialogComponent } from 
'./dialog/file-rename/file-rename-dialog.component';
+import { MatDialogModule } from '@angular/material/dialog';
 
 @NgModule({
     imports: [
@@ -76,11 +78,13 @@ import { SharedUiModule } from '@streampipes/shared-ui';
                 ],
             },
         ]),
+        MatDialogModule,
     ],
     declarations: [
         FilesComponent,
         FileOverviewComponent,
         FileUploadDialogComponent,
+        FileRenameDialogComponent,
     ],
     providers: [],
 })


Reply via email to