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