From f2f5bbdb19d4b69fc7c1f33a64a6192dba1bef16 Mon Sep 17 00:00:00 2001
From: Johannes Goderbauer <j.goderbauer@adito.de>
Date: Thu, 18 Nov 2021 13:58:56 +0100
Subject: [PATCH] [Projekt: Entwicklung - Neon][TicketNr.: 1088762][Download
 mehrerer Dateien...

---
 process/Document_lib/process.js               |   2 +
 process/UnitTest_lib/process.js               |   4 +-
 process/ZippingUtil_lib/ZippingUtil_lib.aod   |  10 ++
 process/ZippingUtil_lib/documentation.adoc    |   1 +
 process/ZippingUtil_lib/process.js            |  98 +++++++++++
 process/ZippingUtil_test/ZippingUtil_test.aod |  11 ++
 process/ZippingUtil_test/process.js           | 159 ++++++++++++++++++
 7 files changed, 283 insertions(+), 2 deletions(-)
 create mode 100644 process/ZippingUtil_lib/ZippingUtil_lib.aod
 create mode 100644 process/ZippingUtil_lib/documentation.adoc
 create mode 100644 process/ZippingUtil_lib/process.js
 create mode 100644 process/ZippingUtil_test/ZippingUtil_test.aod
 create mode 100644 process/ZippingUtil_test/process.js

diff --git a/process/Document_lib/process.js b/process/Document_lib/process.js
index bf9a2ba925..4b84aea61c 100644
--- a/process/Document_lib/process.js
+++ b/process/Document_lib/process.js
@@ -1,3 +1,4 @@
+import("ZippingUtil_lib");
 import("Context_lib");
 import("system.util");
 import("system.translate");
@@ -38,6 +39,7 @@ DocumentUtil.downloadSelectedDocuments = function(pAssignmentName) {
             var fileNames = rows.map(function(value) {
                 return value["NAME"];
             });
+            fileNames = ZippingUtil.renameDuplicateFilenamesForZip(fileNames);
             neon.downloadToZip(translate.text("Files") + ".zip", binaryContents, fileNames);
         }
         else if(pAssignmentName == "ERRORLOG")
diff --git a/process/UnitTest_lib/process.js b/process/UnitTest_lib/process.js
index e39e6bf4ca..ea90797d2c 100644
--- a/process/UnitTest_lib/process.js
+++ b/process/UnitTest_lib/process.js
@@ -261,7 +261,7 @@ Tester.prototype.equals = function(pExpect, pCustomDescription)
 
     if(Utils.isObject(this.actualValue) || Utils.isObject(this.expectedValue))
     {
-        this.expectedDisplayValue = JSON.stringify(this.actualValue, _getCircularReplacer());
+        this.expectedDisplayValue = Array.isArray(this.expectedValue) ? this.expectedValue : JSON.stringify(this.expectedValue, _getCircularReplacer());
         this._testResult = Utils.isEqual(this.actualValue, this.expectedValue);
         this._generateAssertDescription({custom: pCustomDescription, operator: "===", name: "Object value"});
     }
@@ -735,7 +735,7 @@ Tester.prototype.hasMaxLength = function(pExpect, pCustomDescription)
 /**
  * Test if a callback function throws an exception
  *
- * @param {Number} pExpect the expected error
+ * @param {Error} pExpect the expected error
  * @param {(String|Object)} pCustomDescription an optional custom assert description or config object overwrite
  * @return {Tester}
  */
diff --git a/process/ZippingUtil_lib/ZippingUtil_lib.aod b/process/ZippingUtil_lib/ZippingUtil_lib.aod
new file mode 100644
index 0000000000..d69d519c6c
--- /dev/null
+++ b/process/ZippingUtil_lib/ZippingUtil_lib.aod
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2">
+  <name>ZippingUtil_lib</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <documentation>%aditoprj%/process/ZippingUtil_lib/documentation.adoc</documentation>
+  <process>%aditoprj%/process/ZippingUtil_lib/process.js</process>
+  <variants>
+    <element>LIBRARY</element>
+  </variants>
+</process>
diff --git a/process/ZippingUtil_lib/documentation.adoc b/process/ZippingUtil_lib/documentation.adoc
new file mode 100644
index 0000000000..d4b7fb6fba
--- /dev/null
+++ b/process/ZippingUtil_lib/documentation.adoc
@@ -0,0 +1 @@
+Provides versatile utility functions for packing/unpacking zip-files.
\ No newline at end of file
diff --git a/process/ZippingUtil_lib/process.js b/process/ZippingUtil_lib/process.js
new file mode 100644
index 0000000000..e3371fc1ed
--- /dev/null
+++ b/process/ZippingUtil_lib/process.js
@@ -0,0 +1,98 @@
+import("system.translate");
+
+/**
+ * Provides static methods for zipping/unzipping.
+ * Do not create an instance of this
+ * 
+ * @class
+ * @static
+ */
+function ZippingUtil()
+{
+    throw new Error(translate.text("[ZippingUtil.constructor]Cannot instantiate a static class."));
+}
+
+/**
+ * This function will determine duplicates in a list of filenames for a zip file and search for a matching, unique name for the duplicate. <br/>
+ * That is done by adding a number in bracets to the filename. That number is increased for each occurence and the first element ("the origin") is not
+ * changed at all.<br/>
+ * <br/>
+ * This function is perfect for preparing filenames for the neon.downloadToZip-Method, as the neon.downloadToZip would throw an error when filenames
+ * contain duplicates.<br/>
+ * <br/>
+ * Does not modify the input-array and if the input-array is emtpy a new empty array is returned.<br/>
+ * 
+ * @param {Array} pFileNames Not nullable parameter to provide the filenames that are searched for duplicates. 
+ * However it is not required that there are duplicates in the filelist. Example: ["offer.pdf", "offer.pdf"]
+ * @return {Array} Unique elements for the filenames; for the example of the pFileNames param this would be: ["offer.pdf", "offer(1).pdf"]. <br/>
+ *      The amount of elements does not change, nor the position of the elements. Only duplicates are renamed.
+ * 
+ * @throws <p><i>TypeError</i> when the pFileNames param is not an array or null. 
+ * <br/> <i>Error</i> when the limit of possible filenames is reached, the current limit is 1024. This does prevent endless loops
+ * </p>
+ */
+ZippingUtil.renameDuplicateFilenamesForZip = function(pFileNames)
+{
+    if (pFileNames == null || !Array.isArray(pFileNames))
+    {
+        throw new TypeError(translate.text("[ZippingUtil.renameDuplicateFilenamesForZip]FileNames type is invalid. An Array of filenames is required."));
+    }
+    
+    if (pFileNames.length == 0)
+    {
+        return [];
+    }
+    
+    var resultFileNameList = [];
+    var originFileNames = pFileNames; // this is only a reference -> do not modify this array!
+    
+    // map to skip known duplicates (if a lot of files are named the same key); key: name of the originated filename, value: the POSSIBLE next version
+    var duplicationInfo = new Map();
+    
+    // anonymous helper function to determine the next possible filename
+    var _findNextBestFilename = function(pFileName)
+    {
+        var additionalNo = duplicationInfo.has(pFileName) ? duplicationInfo.get(pFileName) : 0;
+        while (++additionalNo <= 1024) // 1024 is a random value we choosed to prevent endless attempts 
+        {
+            // if we are in this function, the pFileName is already a duplicated entry, so we do NOT have to check this here at first
+            var firstDot = pFileName.indexOf("."); // first position because of filenames like "myFile.tar.gz"
+            var primaryFileName;
+            var secondaryFileName = "";
+            if (firstDot <= 0)
+            {
+                primaryFileName = pFileName;
+            }
+            else 
+            {
+                primaryFileName = pFileName.substring(0, firstDot);
+                secondaryFileName = pFileName.substring(firstDot);
+            }
+            var composedFileName = primaryFileName + "(" + additionalNo + ")" + secondaryFileName;
+            duplicationInfo.set(pFileName, additionalNo);
+            if (!originFileNames.includes(composedFileName) && !resultFileNameList.includes(composedFileName))
+            {
+                return composedFileName
+            }
+        }
+        throw new Error(translate.text("[ZippingUtil.renameDuplicateFilenamesForZip]Limit for max length of filename iteration reached."));
+    }
+    
+    for (var i = 0, l = originFileNames.length; i < l; i++)
+    {
+        var fileName = originFileNames[i];
+        if (resultFileNameList.includes(fileName))
+        {
+            // now there is a problem and we want to add a more specific version of the filename to be unique
+            resultFileNameList.push(_findNextBestFilename(fileName));
+        }
+        else
+        {
+            // all good
+            resultFileNameList.push(fileName);
+
+        }
+    }
+    
+    return resultFileNameList;    
+}
diff --git a/process/ZippingUtil_test/ZippingUtil_test.aod b/process/ZippingUtil_test/ZippingUtil_test.aod
new file mode 100644
index 0000000000..e830bb6470
--- /dev/null
+++ b/process/ZippingUtil_test/ZippingUtil_test.aod
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2">
+  <name>ZippingUtil_test</name>
+  <title>[TEST] ZippingUtil_lib</title>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <icon>VAADIN:CHECK_CIRCLE</icon>
+  <process>%aditoprj%/process/ZippingUtil_test/process.js</process>
+  <variants>
+    <element>EXECUTABLE</element>
+  </variants>
+</process>
diff --git a/process/ZippingUtil_test/process.js b/process/ZippingUtil_test/process.js
new file mode 100644
index 0000000000..dd11cab654
--- /dev/null
+++ b/process/ZippingUtil_test/process.js
@@ -0,0 +1,159 @@
+import("system.translate");
+import("system.result");
+import("UnitTest_lib");
+import("ZippingUtil_lib");
+
+var constructorTests = new TestSuite("ZippingUtil.constructor", [
+            
+    new Test("should throw exception when trying to instantiate",
+        function(pTester) {
+            var expected = new Error(translate.text("[ZippingUtil.constructor]Cannot instantiate a static class."));
+
+            // do be able to catch the exception it is required to determine the actual value in a callback function
+            pTester.expectThat(function (){
+                return new ZippingUtil();
+            }).throwsException(expected).assert();
+        }
+        )
+    ]);
+
+var extendFlatFilesTests = new TestSuite("ZippingUtil.renameDuplicateFilenamesForZip", [
+    new Test("should not modify unique filenames",
+        function(pTester) {
+            var fileNameInput = ["file1.pdf", "file2.pdf", "file3.pdf"];
+            var actualValue = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+
+            pTester.expectThat(actualValue).isArray().assert();
+            pTester.expectThat(actualValue).equals(fileNameInput).assert();
+        }
+        ),
+            
+    new Test("should return empty array when zero filenames are given",
+        function(pTester) {
+            var fileNameInput = [];
+            var actualValue = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+
+            pTester.expectThat(actualValue).isArray().assert();
+            pTester.expectThat(actualValue).equals([]).assert();
+        }
+        ),
+
+    new Test("should modify duplicate filenames (where duplicated entries do not already exist)",
+        function(pTester) {
+            var fileNameInput = ["file1.pdf", "file1.pdf", "file1.pdf", "file2.pdf"];
+            var expectedResult = ["file1.pdf", "file1(1).pdf", "file1(2).pdf", "file2.pdf"];
+            
+            var actualValue = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+            
+            pTester.expectThat(actualValue).isArray().assert();
+            pTester.expectThat(actualValue).equals(expectedResult).assert();
+        }
+        ),
+            
+    new Test("should modify duplicate filenames (where duplicated entries do already exist once)",
+        function(pTester) {
+            var fileNameInput = ["file1.pdf", "file1.pdf", "file1(1).pdf", "file2.pdf"];
+            var expectedResult = ["file1.pdf", "file1(2).pdf", "file1(1).pdf", "file2.pdf"];
+            
+            var actualValue = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+            
+            pTester.expectThat(actualValue).isArray().assert();
+            pTester.expectThat(actualValue).equals(expectedResult).assert();
+        }
+        ),
+            
+    new Test("should modify duplicate filenames (where duplicated entries do already exist several times)",
+        function(pTester) {
+            var fileNameInput = ["file1(1).pdf", "file1(1).pdf", "file1(1).pdf", "file2.pdf"];
+            var expectedResult = ["file1(1).pdf", "file1(1)(1).pdf", "file1(1)(2).pdf", "file2.pdf"];
+            
+            var actualValue = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+            
+            pTester.expectThat(actualValue).isArray().assert();
+            pTester.expectThat(actualValue).equals(expectedResult).assert();
+        }
+        ),
+            
+    new Test("should throw exception when no array is passed as filenames",
+        function(pTester) {
+            var fileNameInput = "not an array";
+            var expected = new TypeError(translate.text("[ZippingUtil.renameDuplicateFilenamesForZip]FileNames type is invalid. An Array of filenames is required."));
+
+            // do be able to catch the exception it is required to determine the actual value in a callback function
+            pTester.expectThat(function (){
+                return ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+            }).throwsException(expected).assert();
+        }
+        ),
+            
+    new Test("should throw exception when null is passed as filenames",
+        function(pTester) {
+            var fileNameInput = null;
+            var expected = new TypeError(translate.text("[ZippingUtil.renameDuplicateFilenamesForZip]FileNames type is invalid. An Array of filenames is required."));
+
+            // do be able to catch the exception it is required to determine the actual value in a callback function
+            pTester.expectThat(function (){
+                return ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+            }).throwsException(expected).assert();
+        }
+        ),
+            
+    new Test("should rename files without filextension",
+        function(pTester) {
+            var fileNameInput = ["file1", "file1", "file1", "file2"];
+            var expectedResult = ["file1", "file1(1)", "file1(2)", "file2"];
+            
+            var actualValue = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+            
+            pTester.expectThat(actualValue).isArray().assert();
+            pTester.expectThat(actualValue).equals(expectedResult).assert();
+        }
+        ),
+            
+    new Test("should throw error when limit of loop renamings are exceeded",
+        function(pTester) {
+            var fileNameInput = new Array(1025 + 1).fill("filename.pdf");//"+ 1" because the first filename is always kept the same
+            var expected = new Error(translate.text("[ZippingUtil.renameDuplicateFilenamesForZip]Limit for max length of filename iteration reached."));
+
+            // do be able to catch the exception it is required to determine the actual value in a callback function
+            pTester.expectThat(function (){
+                var res = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+                return res;
+            }).throwsException(expected).assert();
+        }
+        ),
+            
+    new Test("should split at correct dot of file extension",
+        function(pTester) {
+            var fileNameInput = ["file1.tar.gz", "file1.tar.gz"];
+            var expectedResult = ["file1.tar.gz", "file1(1).tar.gz"];
+            
+            var actualValue = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+            
+            pTester.expectThat(actualValue).isArray().assert();
+            pTester.expectThat(actualValue).equals(expectedResult).assert();
+        }
+        ),
+            
+    new Test("should handle filenames with starting dot",
+        function(pTester) {
+            var fileNameInput = [".file1", ".file1", "...", "..."];
+            var expectedResult = [".file1", ".file1(1)", "...", "...(1)"];
+            
+            var actualValue = ZippingUtil.renameDuplicateFilenamesForZip(fileNameInput);
+            
+            pTester.expectThat(actualValue).isArray().assert();
+            pTester.expectThat(actualValue).equals(expectedResult).assert();
+        }
+        )
+    ]);
+
+var tester = new Tester("Test ZippingUtil_lib");
+
+tester.initCoverage(ZippingUtil);
+tester.test(constructorTests);
+tester.test(extendFlatFilesTests);
+
+tester.summary();
+
+result.object(tester.getResults());
-- 
GitLab