diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8830768c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings + +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Declare files that will always have CRLF line endings on checkout. +# This is needed for the unit tests to pass when unzipping files. +Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile.txt text eol=crlf + +Benchmark/Files/MultiTextFileZip/TextFileOne.txt text eol=crlf +Benchmark/Files/MultiTextFileZip/TextFileTwo.txt text eol=crlf +Benchmark/Files/MultiTextFileZip/ThirdTextFile.txt text eol=crlf + +Benchmark/Files/MultiTextFileAndSubDirZip/DirFileOne/TextFileOne.txt text eol=crlf +Benchmark/Files/MultiTextFileAndSubDirZip/TextFile.txt text eol=crlf +Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/OtherDir/DummyFile.txt text eol=crlf +Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/OtherDir/FilleInDirectory.txt text eol=crlf +Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt text eol=crlf diff --git a/.gitignore b/.gitignore index 1e544a8b..d0670fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ main.exe Benchmark*.txt* Benchmark/Files/*.compressed.* Benchmark/Files/*.decompressed.* +Benchmark/Files/*.zipped.* +Benchmark/Files/*.unzipped.* # Test generated files Test*.txt* diff --git a/.gitmodules b/.gitmodules index 1387f43a..92c1e067 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "External/zlib"] path = External/zlib url = https://github.com/madler/zlib.git +[submodule "External/minizip-ng"] + path = External/minizip-ng + url = https://github.com/zlib-ng/minizip-ng diff --git a/.vscode/settings.json b/.vscode/settings.json index 18ee967f..20d42b57 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,5 +29,31 @@ "unistd.h": "c", "bitset": "c", "ftw.h": "c" + "zip_minizip.h": "c", + "unzip.h": "c", + "unzip_minizip.h": "c", + "testzipunzipminizip.h": "c", + "unity_fixture.h": "c", + "testdeflateinflatezlib.h": "c", + "cstdlib": "c", + "rope": "c", + "*.def": "c", + "testunzipminizip.h": "c", + "unity_fixture_internals.h": "c", + "unity_memory.h": "c", + "string_view": "c", + "regex": "c", + "*.inc": "c", + "zipcontentinfo.h": "c", + "iunzip.h": "c", + "typeindex": "c", + "chrono": "c", + "typeinfo": "c", + "ioapi.h": "c", + "stdlib.h": "c", + "stat.h": "c", + "types.h": "c", + "unistd.h": "c", + "dirent.h": "c" }, } \ No newline at end of file diff --git a/Benchmark/Files/MultiTextFileAndSubDirZip/DirFileOne/TextFileOne.txt b/Benchmark/Files/MultiTextFileAndSubDirZip/DirFileOne/TextFileOne.txt new file mode 100644 index 00000000..596bd08b --- /dev/null +++ b/Benchmark/Files/MultiTextFileAndSubDirZip/DirFileOne/TextFileOne.txt @@ -0,0 +1,2 @@ +This is +the first text file. \ No newline at end of file diff --git a/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/OtherDir/DummyFile.txt b/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/OtherDir/DummyFile.txt new file mode 100644 index 00000000..49fe534f --- /dev/null +++ b/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/OtherDir/DummyFile.txt @@ -0,0 +1 @@ +Some dummy file with empty second line diff --git a/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/OtherDir/FilleInDirectory.txt b/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/OtherDir/FilleInDirectory.txt new file mode 100644 index 00000000..aefc10b2 --- /dev/null +++ b/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/OtherDir/FilleInDirectory.txt @@ -0,0 +1 @@ +This file is in a directory \ No newline at end of file diff --git a/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt b/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt new file mode 100644 index 00000000..63123b36 --- /dev/null +++ b/Benchmark/Files/MultiTextFileAndSubDirZip/DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt @@ -0,0 +1,3 @@ +The 3rd file contains + +an empty line on line 2. \ No newline at end of file diff --git a/Benchmark/Files/MultiTextFileAndSubDirZip/MultiTextFileAndSubDirZip_deflate.zip b/Benchmark/Files/MultiTextFileAndSubDirZip/MultiTextFileAndSubDirZip_deflate.zip new file mode 100644 index 00000000..2c5c4667 Binary files /dev/null and b/Benchmark/Files/MultiTextFileAndSubDirZip/MultiTextFileAndSubDirZip_deflate.zip differ diff --git a/Benchmark/Files/MultiTextFileAndSubDirZip/MultiTextFileAndSubDirZip_store.zip b/Benchmark/Files/MultiTextFileAndSubDirZip/MultiTextFileAndSubDirZip_store.zip new file mode 100644 index 00000000..def85703 Binary files /dev/null and b/Benchmark/Files/MultiTextFileAndSubDirZip/MultiTextFileAndSubDirZip_store.zip differ diff --git a/Benchmark/Files/MultiTextFileAndSubDirZip/Readme.md b/Benchmark/Files/MultiTextFileAndSubDirZip/Readme.md new file mode 100644 index 00000000..b9f4d3e3 --- /dev/null +++ b/Benchmark/Files/MultiTextFileAndSubDirZip/Readme.md @@ -0,0 +1,15 @@ +The zip file has been created with the build in compressions option from the Windows 11 Pro 23H2 file explorer. + +1. Select the files to zip. +2. Right-click > "Compress to..." > "Additional options" +3. For the `*_store.zip` file: + - Archive format: `ZIP` + - Compression method: `Store` + - Retain symbolic links: `checked` (this is the default) +4. For the `*_deflate.zip` file: + - Archive format: `ZIP` + - Compression method: `Deflate` + - Compression level: `6` (this is the default) + - Retain symbolic links: `checked` (this is the default) + + diff --git a/Benchmark/Files/MultiTextFileAndSubDirZip/TextFile.txt b/Benchmark/Files/MultiTextFileAndSubDirZip/TextFile.txt new file mode 100644 index 00000000..63123b36 --- /dev/null +++ b/Benchmark/Files/MultiTextFileAndSubDirZip/TextFile.txt @@ -0,0 +1,3 @@ +The 3rd file contains + +an empty line on line 2. \ No newline at end of file diff --git a/Benchmark/Files/MultiTextFileZip/MultiTextFileZip_deflate.zip b/Benchmark/Files/MultiTextFileZip/MultiTextFileZip_deflate.zip new file mode 100644 index 00000000..73ec92fb Binary files /dev/null and b/Benchmark/Files/MultiTextFileZip/MultiTextFileZip_deflate.zip differ diff --git a/Benchmark/Files/MultiTextFileZip/MultiTextFileZip_store.zip b/Benchmark/Files/MultiTextFileZip/MultiTextFileZip_store.zip new file mode 100644 index 00000000..dc8d91f3 Binary files /dev/null and b/Benchmark/Files/MultiTextFileZip/MultiTextFileZip_store.zip differ diff --git a/Benchmark/Files/MultiTextFileZip/Readme.md b/Benchmark/Files/MultiTextFileZip/Readme.md new file mode 100644 index 00000000..b9f4d3e3 --- /dev/null +++ b/Benchmark/Files/MultiTextFileZip/Readme.md @@ -0,0 +1,15 @@ +The zip file has been created with the build in compressions option from the Windows 11 Pro 23H2 file explorer. + +1. Select the files to zip. +2. Right-click > "Compress to..." > "Additional options" +3. For the `*_store.zip` file: + - Archive format: `ZIP` + - Compression method: `Store` + - Retain symbolic links: `checked` (this is the default) +4. For the `*_deflate.zip` file: + - Archive format: `ZIP` + - Compression method: `Deflate` + - Compression level: `6` (this is the default) + - Retain symbolic links: `checked` (this is the default) + + diff --git a/Benchmark/Files/MultiTextFileZip/TextFileOne.txt b/Benchmark/Files/MultiTextFileZip/TextFileOne.txt new file mode 100644 index 00000000..596bd08b --- /dev/null +++ b/Benchmark/Files/MultiTextFileZip/TextFileOne.txt @@ -0,0 +1,2 @@ +This is +the first text file. \ No newline at end of file diff --git a/Benchmark/Files/MultiTextFileZip/TextFileTwo.txt b/Benchmark/Files/MultiTextFileZip/TextFileTwo.txt new file mode 100644 index 00000000..81582c29 --- /dev/null +++ b/Benchmark/Files/MultiTextFileZip/TextFileTwo.txt @@ -0,0 +1,2 @@ +Second file is this +. \ No newline at end of file diff --git a/Benchmark/Files/MultiTextFileZip/ThirdTextFile.txt b/Benchmark/Files/MultiTextFileZip/ThirdTextFile.txt new file mode 100644 index 00000000..63123b36 --- /dev/null +++ b/Benchmark/Files/MultiTextFileZip/ThirdTextFile.txt @@ -0,0 +1,3 @@ +The 3rd file contains + +an empty line on line 2. \ No newline at end of file diff --git a/Benchmark/Files/README.md b/Benchmark/Files/README.md new file mode 100644 index 00000000..46d70a35 --- /dev/null +++ b/Benchmark/Files/README.md @@ -0,0 +1 @@ +All these files are created on windows. Meaning that by default they end in `\r\n` rather than `\n`. This is important when testing that unzipped files are identical as the original. \ No newline at end of file diff --git a/Benchmark/Files/SmallBasicTextFileZip/Readme.md b/Benchmark/Files/SmallBasicTextFileZip/Readme.md new file mode 100644 index 00000000..b9f4d3e3 --- /dev/null +++ b/Benchmark/Files/SmallBasicTextFileZip/Readme.md @@ -0,0 +1,15 @@ +The zip file has been created with the build in compressions option from the Windows 11 Pro 23H2 file explorer. + +1. Select the files to zip. +2. Right-click > "Compress to..." > "Additional options" +3. For the `*_store.zip` file: + - Archive format: `ZIP` + - Compression method: `Store` + - Retain symbolic links: `checked` (this is the default) +4. For the `*_deflate.zip` file: + - Archive format: `ZIP` + - Compression method: `Deflate` + - Compression level: `6` (this is the default) + - Retain symbolic links: `checked` (this is the default) + + diff --git a/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile.txt b/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile.txt new file mode 100644 index 00000000..81365b1a --- /dev/null +++ b/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile.txt @@ -0,0 +1,4 @@ +This is a test file. +On the second line there are eight words + The third line has a tab. +And the last line is a longer piece. Consising of two sentences. \ No newline at end of file diff --git a/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile_deflate.zip b/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile_deflate.zip new file mode 100644 index 00000000..f47a4cfe Binary files /dev/null and b/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile_deflate.zip differ diff --git a/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile_store.zip b/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile_store.zip new file mode 100644 index 00000000..84cbb062 Binary files /dev/null and b/Benchmark/Files/SmallBasicTextFileZip/SmallBasicTextFile_store.zip differ diff --git a/CoDeLib/CMakeLists.txt b/CoDeLib/CMakeLists.txt index 5a8535a1..762b20b0 100644 --- a/CoDeLib/CMakeLists.txt +++ b/CoDeLib/CMakeLists.txt @@ -19,17 +19,20 @@ set(CoDeLib_PUBLIC_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include/CoDeLib) set(COMMON_HEADERS ${CoDeLib_PUBLIC_INCLUDE_PATH}/IDeflate.h ${CoDeLib_PUBLIC_INCLUDE_PATH}/IInflate.h + ${CoDeLib_PUBLIC_INCLUDE_PATH}/IUnZip.h ) install(FILES ${COMMON_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CoDeLib) add_subdirectory(Deflate_zlib) add_subdirectory(Inflate_zlib) add_subdirectory(FileUtils) +add_subdirectory(UnZip_minizip) add_subdirectory(RaiiString) +add_subdirectory(ZipContentInfo) add_subdirectory(Test) install( - TARGETS CoDeLib Deflate_zlib Inflate_zlib RaiiString FileUtils + TARGETS CoDeLib Deflate_zlib Inflate_zlib UnZip_minizip RaiiString ZipContentInfo FileUtils EXPORT CoDeLibTargets INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/CoDeLib/Config.cmake.in b/CoDeLib/Config.cmake.in index 5d530219..3adaf2f3 100644 --- a/CoDeLib/Config.cmake.in +++ b/CoDeLib/Config.cmake.in @@ -7,4 +7,6 @@ check_required_components(CoDeLib) include(CMakeFindDependencyMacro) set(ZLIB_USE_STATIC_LIBS "ON") -find_dependency(ZLIB REQUIRED) \ No newline at end of file +find_dependency(ZLIB REQUIRED) + +find_dependency(minizip REQUIRED) \ No newline at end of file diff --git a/CoDeLib/CustomDeflate.md b/CoDeLib/CustomDeflate.md index 1f6a2277..ab327d3b 100644 --- a/CoDeLib/CustomDeflate.md +++ b/CoDeLib/CustomDeflate.md @@ -4,4 +4,5 @@ # refs -- https://pnrsolution.org/Datacenter/Vol4/Issue1/58.pdf \ No newline at end of file +- https://pnrsolution.org/Datacenter/Vol4/Issue1/58.pdf +- https://nachtimwald.com/2019/09/08/making-minizip-easier-to-use/ \ No newline at end of file diff --git a/CoDeLib/Deflate_zlib/CMakeLists.txt b/CoDeLib/Deflate_zlib/CMakeLists.txt index 1175f1a5..86654718 100644 --- a/CoDeLib/Deflate_zlib/CMakeLists.txt +++ b/CoDeLib/Deflate_zlib/CMakeLists.txt @@ -10,7 +10,6 @@ set(ZLIB_USE_STATIC_LIBS "ON") find_package(ZLIB REQUIRED) target_link_libraries(Deflate_zlib PRIVATE ZLIB::ZLIB) -message(STATUS "CoDeLib_PUBLIC_INCLUDE_PATH: ${CoDeLib_PUBLIC_INCLUDE_PATH}") set(Deflate_zlib_PUBLIC_INCLUDE_PATH ${CoDeLib_PUBLIC_INCLUDE_PATH}/Deflate_zlib) set(Deflate_zlib_PUBLIC_HEADERS ${Deflate_zlib_PUBLIC_INCLUDE_PATH}/Deflate_zlib.h diff --git a/CoDeLib/FileUtils/src/FileUtils.c b/CoDeLib/FileUtils/src/FileUtils.c index bf6ec560..74f6fc12 100644 --- a/CoDeLib/FileUtils/src/FileUtils.c +++ b/CoDeLib/FileUtils/src/FileUtils.c @@ -291,6 +291,9 @@ bool FilesAreEqual(FILE *pFile1, FILE *pFile2) { const size_t fileSize2 = GetFileSizeInBytes(pFile2); if (fileSize1 != fileSize2) { + printf("File sizes not equal:\n"); + printf(" fileSize1: %lu\n", (unsigned long)fileSize1); + printf(" fileSize2: %lu\n", (unsigned long)fileSize2); return false; } @@ -306,6 +309,9 @@ bool FilesAreEqual(FILE *pFile1, FILE *pFile2) { bytesRead2 = fread(buffer2, 1, bufferSize, pFile2); if (bytesRead1 != bytesRead2) { + printf("Bytes read not equal:\n"); + printf(" bytesRead1: %lu\n", (unsigned long)bytesRead1); + printf(" bytesRead2: %lu\n", (unsigned long)bytesRead2); return false; } @@ -315,12 +321,16 @@ bool FilesAreEqual(FILE *pFile1, FILE *pFile2) { const int errorFile2 = ferror(pFile2); if ((eofFile1 && !eofFile2) || (!eofFile1 && eofFile2)) { printf("EOF indicator set only by one file\n"); + return false; } else if ((!eofFile1 && errorFile1) || (!eofFile2 && errorFile2)) { printf("Error indicator set"); return false; } if (memcmp(buffer1, buffer2, bytesRead1) != 0) { + printf("Buffers not equal\n"); + printf(" buffer1: %s\n", buffer1); + printf(" buffer2: %s\n", buffer2); return false; } } while (bytesRead1 > 0); diff --git a/CoDeLib/Test/CMakeLists.txt b/CoDeLib/Test/CMakeLists.txt index 33204ffc..8b78df59 100644 --- a/CoDeLib/Test/CMakeLists.txt +++ b/CoDeLib/Test/CMakeLists.txt @@ -2,17 +2,16 @@ add_executable(CoDeLib_Test src/main.c src/TestRaiiString.c src/TestDeflateInflateZlib.c - src/TestFileUtils.c) + src/TestFileUtils.c + src/TestUnZipMinizip.c + src/TestUnZipMinizipInflateZlib.c + src/TestZipContentInfo.c +) target_include_directories(CoDeLib_Test PUBLIC $ ) -target_link_libraries(CoDeLib_Test PRIVATE Deflate_zlib) -target_link_libraries(CoDeLib_Test PRIVATE Inflate_zlib) -target_link_libraries(CoDeLib_Test PRIVATE RaiiString) -target_link_libraries(CoDeLib_Test PRIVATE FileUtils) - FetchContent_Declare( Unity GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git @@ -23,4 +22,14 @@ FetchContent_Declare( # If cache is not used, you will get `Policy CMP0077 is not set: option() honors normal variables.` set(UNITY_EXTENSION_FIXTURE ON CACHE INTERNAL "") FetchContent_MakeAvailable(Unity) + +target_link_libraries(CoDeLib_Test PRIVATE Deflate_zlib) +target_link_libraries(CoDeLib_Test PRIVATE Inflate_zlib) +target_link_libraries(CoDeLib_Test PRIVATE FileUtils) +target_link_libraries(CoDeLib_Test PRIVATE UnZip_minizip) +target_link_libraries(CoDeLib_Test PRIVATE RaiiString) +target_link_libraries(CoDeLib_Test PRIVATE ZipContentInfo) + target_link_libraries(CoDeLib_Test PRIVATE unity) + + diff --git a/CoDeLib/Test/src/TestUnZipMinizip.c b/CoDeLib/Test/src/TestUnZipMinizip.c new file mode 100644 index 00000000..2e034631 --- /dev/null +++ b/CoDeLib/Test/src/TestUnZipMinizip.c @@ -0,0 +1,461 @@ +#include "TestUnZipMinizip.h" +#include "unity_fixture.h" +#include +#include +#include +#include +#include +#include +#include + +static char *g_pFullPathToBenchmarkTestFiles; + +void SetupTestUnZipMinizip(char *pFullPathToBenchmarkTestFiles) { + g_pFullPathToBenchmarkTestFiles = pFullPathToBenchmarkTestFiles; +} + +bool RaiiStringIsInArray(const RaiiString *string, const RaiiString *pArray, + const size_t arraySize) { + for (size_t i = 0; i < arraySize; ++i) { + if (strcmp(string->pString, pArray[i].pString) == 0) { + return true; + } + } + return false; +} + +TEST_GROUP(TestUnZipMinizip); + +static RaiiString g_someUnZippedDirPath; +static RaiiString g_someZipPath; +static RaiiString g_pathToSmallBasicTextFileZipStore; +static RaiiString g_pathToSmallBasicTextFileZipSource; +static RaiiString g_pathToMultiTextFileZipStore; +static RaiiString g_pathToMultiTextFileZipSource; +static RaiiString g_pathToMultiTextFileAndSubDirZipStore; +static RaiiString g_pathToMultiTextFileAndSubDirZipSource; + +TEST_SETUP(TestUnZipMinizip) { + g_someUnZippedDirPath = + RaiiStringCreateFromCString("SomePath/someUnZippedDirPath.zip"); + g_someZipPath = RaiiStringCreateFromCString("SomePath/someZipPath.zip"); + + g_pathToSmallBasicTextFileZipStore = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString( + &g_pathToSmallBasicTextFileZipStore, + "/SmallBasicTextFileZip/SmallBasicTextFile_store.zip"); + + g_pathToSmallBasicTextFileZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToSmallBasicTextFileZipSource, + "/SmallBasicTextFileZip/"); + + g_pathToMultiTextFileZipStore = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileZipStore, + "/MultiTextFileZip/MultiTextFileZip_store.zip"); + + g_pathToMultiTextFileZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileZipSource, + "/MultiTextFileZip/"); + + g_pathToMultiTextFileAndSubDirZipStore = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString( + &g_pathToMultiTextFileAndSubDirZipStore, + "/MultiTextFileAndSubDirZip/MultiTextFileAndSubDirZip_store.zip"); + + g_pathToMultiTextFileAndSubDirZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileAndSubDirZipSource, + "/MultiTextFileAndSubDirZip/"); +} + +TEST_TEAR_DOWN(TestUnZipMinizip) { + RaiiStringClean(&g_someUnZippedDirPath); + RaiiStringClean(&g_someZipPath); + RaiiStringClean(&g_pathToSmallBasicTextFileZipStore); + RaiiStringClean(&g_pathToSmallBasicTextFileZipSource); + RaiiStringClean(&g_pathToMultiTextFileZipStore); + RaiiStringClean(&g_pathToMultiTextFileZipSource); + RaiiStringClean(&g_pathToMultiTextFileAndSubDirZipStore); + RaiiStringClean(&g_pathToMultiTextFileAndSubDirZipSource); + + if (PathExists("./tmp/")) { + TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/")); + } +} + +//============================== +// UnZip() +//============================== + +TEST(TestUnZipMinizip, test_UnZip_ReturnsUnZipErrorIfZipFileInfoPtrIsNull) { + + UNZIP_RETURN_CODES result = + unzip_minizip.UnZip(NULL, &g_someUnZippedDirPath); + TEST_ASSERT_EQUAL(UNZIP_ERROR, result); +} + +TEST(TestUnZipMinizip, test_UnZip_ReturnsUnZipErrorIfZipFileStrPtrIsNull) { + RAII_ZIPCONTENTINFO zipInfo = + (ZipContentInfo){.zipFilePath = (RaiiString){NULL, 0}, + .pUnZippedFilePathArray = NULL, + .unZippedFileCount = 0}; + + UNZIP_RETURN_CODES result = + unzip_minizip.UnZip(&zipInfo, &g_someUnZippedDirPath); + + TEST_ASSERT_EQUAL(UNZIP_ERROR, result); +} + +TEST(TestUnZipMinizip, test_UnZip_ReturnsUnZipErrorIfDstPathPtrIsNull) { + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToSmallBasicTextFileZipStore); + + UNZIP_RETURN_CODES result = unzip_minizip.UnZip(&zipInfo, NULL); + TEST_ASSERT_EQUAL(UNZIP_ERROR, result); +} + +TEST(TestUnZipMinizip, test_UnZip_ReturnsUnZipErrorIfDstPathStrPtrIsNull) { + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToSmallBasicTextFileZipStore); + + RAII_STRING unZippedDirPathNull = {NULL, 0}; + + UNZIP_RETURN_CODES result = + unzip_minizip.UnZip(&zipInfo, &unZippedDirPathNull); + TEST_ASSERT_EQUAL(UNZIP_ERROR, result); +} + +TEST(TestUnZipMinizip, test_UnZip_ReturnsUnZipErrorIfZipFileDoesNotExist) { + RAII_ZIPCONTENTINFO zipInfo = ZipContentInfoCreate(&g_someZipPath); + UNZIP_RETURN_CODES result = + unzip_minizip.UnZip(&zipInfo, &g_someUnZippedDirPath); + TEST_ASSERT_EQUAL(UNZIP_ERROR, result); +} + +TEST( + TestUnZipMinizip, + test_UnZip_UnZipSingleFileZipGivesCorrectFileInZipInZipContentInfoAndCreatesFiles) { + + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToSmallBasicTextFileZipStore); + + RAII_STRING pathToUnzippedFiles = + RaiiStringCreateFromCString("./tmp/SmallBasicTextFile_store_unZipped/"); + RAII_STRING pathToUnzippedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFiles.pString); + RaiiStringAppend_cString(&pathToUnzippedFilesWithFile, + "SmallBasicTextFile.txt"); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFiles); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + TEST_ASSERT_EQUAL(1, zipInfo.unZippedFileCount); + TEST_ASSERT_NOT_NULL(zipInfo.pUnZippedFilePathArray); + TEST_ASSERT_EQUAL_STRING(pathToUnzippedFilesWithFile.pString, + zipInfo.pUnZippedFilePathArray[0].pString); + + TEST_ASSERT_TRUE(PathExists(pathToUnzippedFilesWithFile.pString)); +} + +TEST(TestUnZipMinizip, + test_UnZip_UnZipAddsTrailingForwardslashToOutputDirPathIfNotPresent) { + + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToSmallBasicTextFileZipStore); + + RAII_STRING pathToUnzippedFiles = + RaiiStringCreateFromCString("./tmp/SmallBasicTextFile_store_unZipped"); + RAII_STRING pathToUnzippedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFiles.pString); + RaiiStringAppend_cString(&pathToUnzippedFilesWithFile, + "/SmallBasicTextFile.txt"); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFiles); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + TEST_ASSERT_EQUAL(1, zipInfo.unZippedFileCount); + TEST_ASSERT_NOT_NULL(zipInfo.pUnZippedFilePathArray); + TEST_ASSERT_EQUAL_STRING(pathToUnzippedFilesWithFile.pString, + zipInfo.pUnZippedFilePathArray[0].pString); + + TEST_ASSERT_TRUE(PathExists(pathToUnzippedFilesWithFile.pString)); +} + +TEST( + TestUnZipMinizip, + test_UnZip_UnZipMultiFileZipGivesCorrectFilesInZipInZipContentInfoAndCreatesFiles) { + + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToMultiTextFileZipStore); + + RAII_STRING pathToUnzippedFiles = + RaiiStringCreateFromCString("./tmp/MultiTextFileZip_store_unZipped/"); + + RaiiString pExpectedPaths[] = { + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + }; + + RaiiStringAppend_cString(&pExpectedPaths[0], "TextFileOne.txt"); + RaiiStringAppend_cString(&pExpectedPaths[1], "TextFileTwo.txt"); + RaiiStringAppend_cString(&pExpectedPaths[2], "ThirdTextFile.txt"); + + const size_t expectedFileCount = + sizeof(pExpectedPaths) / sizeof(pExpectedPaths[0]); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFiles); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + TEST_ASSERT_EQUAL(expectedFileCount, zipInfo.unZippedFileCount); + TEST_ASSERT_NOT_NULL(zipInfo.pUnZippedFilePathArray); + for (size_t i = 0; i < expectedFileCount; ++i) { + TEST_ASSERT_TRUE(RaiiStringIsInArray(&pExpectedPaths[i], + zipInfo.pUnZippedFilePathArray, + zipInfo.unZippedFileCount)); + TEST_ASSERT_TRUE(PathExists(pExpectedPaths[i].pString)); + } + + for (size_t i = 0; i < expectedFileCount; ++i) { + RaiiStringClean(&pExpectedPaths[i]); + } +} + +TEST( + TestUnZipMinizip, + test_UnZip_UnZipMultiFileAndSubDirZipGivesCorrectFilesInZipInZipContentInfoAndCreatesFiles) { + + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToMultiTextFileAndSubDirZipStore); + + RAII_STRING pathToUnzippedFiles = RaiiStringCreateFromCString( + "./tmp/MultiTextFileAndSubDirZip_store_unZipped/"); + + RaiiString pExpectedPaths[] = { + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + RaiiStringCreateFromCString(pathToUnzippedFiles.pString), + }; + + RaiiStringAppend_cString(&pExpectedPaths[0], "DirFileOne/"); + RaiiStringAppend_cString(&pExpectedPaths[1], "DirFileOne/TextFileOne.txt"); + RaiiStringAppend_cString(&pExpectedPaths[2], "DirOfDirs/"); + RaiiStringAppend_cString(&pExpectedPaths[3], "DirOfDirs/OtherDir/"); + RaiiStringAppend_cString(&pExpectedPaths[4], + "DirOfDirs/OtherDir/DummyFile.txt"); + RaiiStringAppend_cString(&pExpectedPaths[5], + "DirOfDirs/OtherDir/FilleInDirectory.txt"); + RaiiStringAppend_cString(&pExpectedPaths[6], + "DirOfDirs/SubDirWithCopyOfTopLevelFile/"); + RaiiStringAppend_cString( + &pExpectedPaths[7], + "DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt"); + RaiiStringAppend_cString(&pExpectedPaths[8], "TextFile.txt"); + + const size_t expectedFileCount = + sizeof(pExpectedPaths) / sizeof(pExpectedPaths[0]); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFiles); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + TEST_ASSERT_EQUAL(expectedFileCount, zipInfo.unZippedFileCount); + TEST_ASSERT_NOT_NULL(zipInfo.pUnZippedFilePathArray); + for (size_t i = 0; i < expectedFileCount; ++i) { + TEST_ASSERT_TRUE(RaiiStringIsInArray(&pExpectedPaths[i], + zipInfo.pUnZippedFilePathArray, + zipInfo.unZippedFileCount)); + TEST_ASSERT_TRUE(PathExists(pExpectedPaths[i].pString)); + } + + for (size_t i = 0; i < expectedFileCount; ++i) { + RaiiStringClean(&pExpectedPaths[i]); + } +} + +TEST(TestUnZipMinizip, + test_UnZip_UnZipSingleFileZipWritesDataCorrectlyToTheFile) { + + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToSmallBasicTextFileZipStore); + + RAII_STRING pathToUnzippedFile = + RaiiStringCreateFromCString("./tmp/SmallBasicTextFile_store_unZipped/"); + + RAII_STRING pathToSourceWithFilesWithFile = RaiiStringCreateFromCString( + g_pathToSmallBasicTextFileZipSource.pString); + + RAII_STRING pathToUnzippedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFile.pString); + RaiiStringAppend_cString(&pathToUnzippedFilesWithFile, + "SmallBasicTextFile.txt"); + + RaiiStringAppend_cString(&pathToSourceWithFilesWithFile, + "SmallBasicTextFile.txt"); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFile); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + FILE *pFileSource = NULL; + FILE *pFileUnzipped = NULL; + + OpenFileWithMode(&pFileSource, &pathToSourceWithFilesWithFile, "rb"); + OpenFileWithMode(&pFileUnzipped, &pathToUnzippedFilesWithFile, "rb"); + + TEST_ASSERT_TRUE(FilesAreEqual(pFileSource, pFileUnzipped)); + + fclose(pFileSource); + fclose(pFileUnzipped); +} + +TEST(TestUnZipMinizip, + test_UnZip_UnZipMultiFileZipWritesDataCorrectlyToTheFiles) { + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToMultiTextFileZipStore); + + RaiiString pExpectedPaths[] = { + RaiiStringCreateFromCString("TextFileOne.txt"), + RaiiStringCreateFromCString("TextFileTwo.txt"), + RaiiStringCreateFromCString("ThirdTextFile.txt"), + }; + const size_t expectedFileCount = + sizeof(pExpectedPaths) / sizeof(pExpectedPaths[0]); + + RAII_STRING pathToUnzippedFiles = + RaiiStringCreateFromCString("./tmp/MultiTextFileZip_store_unZipped/"); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFiles); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + for (size_t i = 0; i < expectedFileCount; ++i) { + RAII_STRING pathToUnzippedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFiles.pString); + RaiiStringAppend_cString(&pathToUnzippedFilesWithFile, + pExpectedPaths[i].pString); + + FILE *pFileSource = NULL; + FILE *pFileUnzipped = NULL; + + RAII_STRING pathToSourceWithFilesWithFile = + RaiiStringCreateFromCString(g_pathToMultiTextFileZipSource.pString); + RaiiStringAppend_cString(&pathToSourceWithFilesWithFile, + pExpectedPaths[i].pString); + + OpenFileWithMode(&pFileSource, &pathToSourceWithFilesWithFile, "rb"); + OpenFileWithMode(&pFileUnzipped, &pathToUnzippedFilesWithFile, "rb"); + + TEST_ASSERT_TRUE(FilesAreEqual(pFileSource, pFileUnzipped)); + + fclose(pFileSource); + fclose(pFileUnzipped); + } + + for (size_t i = 0; i < expectedFileCount; ++i) { + RaiiStringClean(&pExpectedPaths[i]); + } +} + +TEST(TestUnZipMinizip, + test_UnZip_UnZipMultiFileAndSubDirZipWritesDataCorrectlyToTheFiles) { + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToMultiTextFileAndSubDirZipStore); + + RaiiString pExpectedPaths[] = { + RaiiStringCreateFromCString("DirFileOne/TextFileOne.txt"), + RaiiStringCreateFromCString("DirOfDirs/OtherDir/DummyFile.txt"), + RaiiStringCreateFromCString("DirOfDirs/OtherDir/FilleInDirectory.txt"), + RaiiStringCreateFromCString( + "DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt"), + RaiiStringCreateFromCString("TextFile.txt"), + }; + const size_t expectedFileCount = + sizeof(pExpectedPaths) / sizeof(pExpectedPaths[0]); + + RAII_STRING pathToUnzippedFiles = RaiiStringCreateFromCString( + "./tmp/MultiTextFileAndSubDirZip_store_unZipped/"); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFiles); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + for (size_t i = 0; i < expectedFileCount; ++i) { + RAII_STRING pathToUnzippedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFiles.pString); + RaiiStringAppend_cString(&pathToUnzippedFilesWithFile, + pExpectedPaths[i].pString); + + FILE *pFileSource = NULL; + FILE *pFileUnzipped = NULL; + + RAII_STRING pathToSourceWithFilesWithFile = RaiiStringCreateFromCString( + g_pathToMultiTextFileAndSubDirZipSource.pString); + RaiiStringAppend_cString(&pathToSourceWithFilesWithFile, + pExpectedPaths[i].pString); + + OpenFileWithMode(&pFileSource, &pathToSourceWithFilesWithFile, "rb"); + OpenFileWithMode(&pFileUnzipped, &pathToUnzippedFilesWithFile, "rb"); + + TEST_ASSERT_TRUE(FilesAreEqual(pFileSource, pFileUnzipped)); + + fclose(pFileSource); + fclose(pFileUnzipped); + } + + for (size_t i = 0; i < expectedFileCount; ++i) { + RaiiStringClean(&pExpectedPaths[i]); + } +} + +//============================== +// TEST_GROUP_RUNNER +//============================== + +TEST_GROUP_RUNNER(TestUnZipMinizip) { + // UnZip() + RUN_TEST_CASE(TestUnZipMinizip, + test_UnZip_ReturnsUnZipErrorIfZipFileInfoPtrIsNull); + RUN_TEST_CASE(TestUnZipMinizip, + test_UnZip_ReturnsUnZipErrorIfZipFileStrPtrIsNull); + + RUN_TEST_CASE(TestUnZipMinizip, + test_UnZip_ReturnsUnZipErrorIfDstPathPtrIsNull); + RUN_TEST_CASE(TestUnZipMinizip, + test_UnZip_ReturnsUnZipErrorIfDstPathStrPtrIsNull); + RUN_TEST_CASE(TestUnZipMinizip, + test_UnZip_ReturnsUnZipErrorIfZipFileDoesNotExist); + RUN_TEST_CASE( + TestUnZipMinizip, + test_UnZip_UnZipSingleFileZipGivesCorrectFileInZipInZipContentInfoAndCreatesFiles); + RUN_TEST_CASE( + TestUnZipMinizip, + test_UnZip_UnZipAddsTrailingForwardslashToOutputDirPathIfNotPresent); + RUN_TEST_CASE( + TestUnZipMinizip, + test_UnZip_UnZipMultiFileZipGivesCorrectFilesInZipInZipContentInfoAndCreatesFiles); + RUN_TEST_CASE( + TestUnZipMinizip, + test_UnZip_UnZipMultiFileAndSubDirZipGivesCorrectFilesInZipInZipContentInfoAndCreatesFiles); + RUN_TEST_CASE(TestUnZipMinizip, + test_UnZip_UnZipSingleFileZipWritesDataCorrectlyToTheFile); + RUN_TEST_CASE(TestUnZipMinizip, + test_UnZip_UnZipMultiFileZipWritesDataCorrectlyToTheFiles); + RUN_TEST_CASE( + TestUnZipMinizip, + test_UnZip_UnZipMultiFileAndSubDirZipWritesDataCorrectlyToTheFiles); +} diff --git a/CoDeLib/Test/src/TestUnZipMinizip.h b/CoDeLib/Test/src/TestUnZipMinizip.h new file mode 100644 index 00000000..585f3c8e --- /dev/null +++ b/CoDeLib/Test/src/TestUnZipMinizip.h @@ -0,0 +1,3 @@ +#pragma once + +void SetupTestUnZipMinizip(char *pFullPathToBenchmarkTestFiles); diff --git a/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.c b/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.c new file mode 100644 index 00000000..3d71af4b --- /dev/null +++ b/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.c @@ -0,0 +1,312 @@ +#include "TestUnZipMinizipInflateZlib.h" +#include "unity_fixture.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static char *g_pFullPathToBenchmarkTestFiles; + +void SetupTestUnZipMinizipInflateZlib(char *pFullPathToBenchmarkTestFiles) { + g_pFullPathToBenchmarkTestFiles = pFullPathToBenchmarkTestFiles; +} + +TEST_GROUP(TestUnZipMinizipInflateZlib); + +static RaiiString g_someUnZippedDirPath; +static RaiiString g_someZipPath; +static RaiiString g_pathToSmallBasicTextFileZipDeflate; +static RaiiString g_pathToSmallBasicTextFileZipSource; +static RaiiString g_pathToMultiTextFileZipDeflate; +static RaiiString g_pathToMultiTextFileZipSource; +static RaiiString g_pathToMultiTextFileAndSubDirZipDeflate; +static RaiiString g_pathToMultiTextFileAndSubDirZipSource; + +TEST_SETUP(TestUnZipMinizipInflateZlib) { + g_someUnZippedDirPath = + RaiiStringCreateFromCString("SomePath/someUnZippedDirPath.zip"); + g_someZipPath = RaiiStringCreateFromCString("SomePath/someZipPath.zip"); + + g_pathToSmallBasicTextFileZipDeflate = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToSmallBasicTextFileZipDeflate, + "/SmallBasicTextFileZip/" + "SmallBasicTextFile_deflate.zip"); + + g_pathToSmallBasicTextFileZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToSmallBasicTextFileZipSource, + "/SmallBasicTextFileZip/"); + + g_pathToMultiTextFileZipDeflate = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileZipDeflate, + "/MultiTextFileZip/" + "MultiTextFileZip_deflate.zip"); + + g_pathToMultiTextFileZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileZipSource, + "/MultiTextFileZip/"); + + g_pathToMultiTextFileAndSubDirZipDeflate = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileAndSubDirZipDeflate, + "/MultiTextFileAndSubDirZip/" + "MultiTextFileAndSubDirZip_deflate.zip"); + + g_pathToMultiTextFileAndSubDirZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileAndSubDirZipSource, + "/MultiTextFileAndSubDirZip/"); +} + +TEST_TEAR_DOWN(TestUnZipMinizipInflateZlib) { + RaiiStringClean(&g_someUnZippedDirPath); + RaiiStringClean(&g_someZipPath); + RaiiStringClean(&g_pathToSmallBasicTextFileZipDeflate); + RaiiStringClean(&g_pathToSmallBasicTextFileZipSource); + RaiiStringClean(&g_pathToMultiTextFileZipDeflate); + RaiiStringClean(&g_pathToMultiTextFileZipSource); + RaiiStringClean(&g_pathToMultiTextFileAndSubDirZipDeflate); + RaiiStringClean(&g_pathToMultiTextFileAndSubDirZipSource); + + if (PathExists("./tmp/")) { + TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/")); + } +} + +//============================== +// UnZip() + Inflate() +//============================== + +TEST(TestUnZipMinizipInflateZlib, + test_UnZipAndInflateSingleFileZipWritesDataCorrectlyToTheFile) { + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToSmallBasicTextFileZipDeflate); + + RAII_STRING pathToUnzippedFile = RaiiStringCreateFromCString( + "./tmp/SmallBasicTextFile_deflate_unZipped/"); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFile); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + RAII_STRING pathToUnzippedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFile.pString); + RaiiStringAppend_cString(&pathToUnzippedFilesWithFile, + "SmallBasicTextFile.txt"); + + RAII_STRING pathToUnzippedInflatedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFile.pString); + RaiiStringAppend_cString(&pathToUnzippedInflatedFilesWithFile, + "SmallBasicTextFile.decompressed.txt"); + + RAII_STRING pathToSourceWithFilesWithFile = RaiiStringCreateFromCString( + g_pathToSmallBasicTextFileZipSource.pString); + RaiiStringAppend_cString(&pathToSourceWithFilesWithFile, + "SmallBasicTextFile.txt"); + + FILE *pFileUnzipped = NULL; + FILE *pFileUnzippedInflated = NULL; + + OpenFileWithMode(&pFileUnzipped, &pathToUnzippedFilesWithFile, "rb"); + + // Note: It is important to open the file in binary mode. + // Otherwise, a \r (cariage return) character will implicitely + // add a \n (newline) character. + OpenFileWithMode(&pFileUnzippedInflated, + &pathToUnzippedInflatedFilesWithFile, "w+b"); + + const INFLATE_RETURN_CODES statusInflate = + inflate_zlib.Inflate(pFileUnzipped, pFileUnzippedInflated, NULL); + TEST_ASSERT_EQUAL(INFLATE_SUCCESS, statusInflate); + + fclose(pFileUnzipped); + + FILE *pFileSource = NULL; + OpenFileWithMode(&pFileSource, &pathToSourceWithFilesWithFile, "rb"); + + TEST_ASSERT_TRUE(FilesAreEqual(pFileSource, pFileUnzippedInflated)); + + fclose(pFileSource); + fclose(pFileUnzippedInflated); +} + +TEST(TestUnZipMinizipInflateZlib, + test_UnZipAndInflateMultiFileZipWritesDataCorrectlyToTheFiles) { + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToMultiTextFileZipDeflate); + + RAII_STRING pathToUnzippedFiles = + RaiiStringCreateFromCString("./tmp/MultiTextFileZip_deflate_unZipped/"); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFiles); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + RaiiString pExpectedPaths[] = { + RaiiStringCreateFromCString("TextFileOne.txt"), + RaiiStringCreateFromCString("TextFileTwo.txt"), + RaiiStringCreateFromCString("ThirdTextFile.txt"), + }; + const size_t expectedFileCount = + sizeof(pExpectedPaths) / sizeof(pExpectedPaths[0]); + + RaiiString pPathsToDecompressedFiles[] = { + RaiiStringCreateFromCString("TextFileOne.decompressed.txt"), + RaiiStringCreateFromCString("TextFileTwo.decompressed.txt"), + RaiiStringCreateFromCString("ThirdTextFile.decompressed.txt"), + }; + + for (size_t i = 0; i < expectedFileCount; ++i) { + + RAII_STRING pathToUnzippedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFiles.pString); + RaiiStringAppend_cString(&pathToUnzippedFilesWithFile, + pExpectedPaths[i].pString); + + RAII_STRING pathToUnzippedInflatedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFiles.pString); + RaiiStringAppend_cString(&pathToUnzippedInflatedFilesWithFile, + pPathsToDecompressedFiles[i].pString); + + RAII_STRING pathToSourceWithFilesWithFile = + RaiiStringCreateFromCString(g_pathToMultiTextFileZipSource.pString); + RaiiStringAppend_cString(&pathToSourceWithFilesWithFile, + pExpectedPaths[i].pString); + + FILE *pFileUnzipped = NULL; + FILE *pFileUnzippedInflated = NULL; + + OpenFileWithMode(&pFileUnzipped, &pathToUnzippedFilesWithFile, "rb"); + + // Note: It is important to open the file in binary mode. + // Otherwise, a \r (cariage return) character will implicitely + // add a \n (newline) character. + OpenFileWithMode(&pFileUnzippedInflated, + &pathToUnzippedInflatedFilesWithFile, "w+b"); + + const INFLATE_RETURN_CODES statusInflate = + inflate_zlib.Inflate(pFileUnzipped, pFileUnzippedInflated, NULL); + TEST_ASSERT_EQUAL(INFLATE_SUCCESS, statusInflate); + + fclose(pFileUnzipped); + + FILE *pFileSource = NULL; + OpenFileWithMode(&pFileSource, &pathToSourceWithFilesWithFile, "rb"); + + TEST_ASSERT_TRUE(FilesAreEqual(pFileSource, pFileUnzippedInflated)); + + fclose(pFileSource); + fclose(pFileUnzippedInflated); + } + + for (size_t i = 0; i < expectedFileCount; ++i) { + RaiiStringClean(&pExpectedPaths[i]); + RaiiStringClean(&pPathsToDecompressedFiles[i]); + } +} + +TEST(TestUnZipMinizipInflateZlib, + test_UnZipAndInflateMultiFileAndSubDirZipWritesDataCorrectlyToTheFiles) { + RAII_ZIPCONTENTINFO zipInfo = + ZipContentInfoCreate(&g_pathToMultiTextFileAndSubDirZipDeflate); + + RAII_STRING pathToUnzippedFiles = RaiiStringCreateFromCString( + "./tmp/MultiTextFileAndSubDirZip_deflate_unZipped/"); + + const UNZIP_RETURN_CODES statusUnzip = + unzip_minizip.UnZip(&zipInfo, &pathToUnzippedFiles); + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnzip); + + RaiiString pExpectedPaths[] = { + RaiiStringCreateFromCString("DirFileOne/TextFileOne.txt"), + RaiiStringCreateFromCString("DirOfDirs/OtherDir/DummyFile.txt"), + RaiiStringCreateFromCString("DirOfDirs/OtherDir/FilleInDirectory.txt"), + RaiiStringCreateFromCString( + "DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt"), + RaiiStringCreateFromCString("TextFile.txt"), + }; + const size_t expectedFileCount = + sizeof(pExpectedPaths) / sizeof(pExpectedPaths[0]); + + RaiiString pPathsToDecompressedFiles[] = { + RaiiStringCreateFromCString("DirFileOne/TextFileOne.decompressed.txt"), + RaiiStringCreateFromCString( + "DirOfDirs/OtherDir/DummyFile.decompressed.txt"), + RaiiStringCreateFromCString( + "DirOfDirs/OtherDir/FilleInDirectory.decompressed.txt"), + RaiiStringCreateFromCString( + "DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.decompressed.txt"), + RaiiStringCreateFromCString("TextFile.decompressed.txt"), + }; + + for (size_t i = 0; i < expectedFileCount; ++i) { + + RAII_STRING pathToUnzippedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFiles.pString); + RaiiStringAppend_cString(&pathToUnzippedFilesWithFile, + pExpectedPaths[i].pString); + + RAII_STRING pathToUnzippedInflatedFilesWithFile = + RaiiStringCreateFromCString(pathToUnzippedFiles.pString); + RaiiStringAppend_cString(&pathToUnzippedInflatedFilesWithFile, + pPathsToDecompressedFiles[i].pString); + + RAII_STRING pathToSourceWithFilesWithFile = RaiiStringCreateFromCString( + g_pathToMultiTextFileAndSubDirZipSource.pString); + RaiiStringAppend_cString(&pathToSourceWithFilesWithFile, + pExpectedPaths[i].pString); + + FILE *pFileUnzipped = NULL; + FILE *pFileUnzippedInflated = NULL; + + OpenFileWithMode(&pFileUnzipped, &pathToUnzippedFilesWithFile, "rb"); + + // Note: It is important to open the file in binary mode. + // Otherwise, a \r (cariage return) character will implicitely + // add a \n (newline) character. + OpenFileWithMode(&pFileUnzippedInflated, + &pathToUnzippedInflatedFilesWithFile, "w+b"); + + const INFLATE_RETURN_CODES statusInflate = + inflate_zlib.Inflate(pFileUnzipped, pFileUnzippedInflated, NULL); + TEST_ASSERT_EQUAL(INFLATE_SUCCESS, statusInflate); + + fclose(pFileUnzipped); + + FILE *pFileSource = NULL; + OpenFileWithMode(&pFileSource, &pathToSourceWithFilesWithFile, "rb"); + + TEST_ASSERT_TRUE(FilesAreEqual(pFileSource, pFileUnzippedInflated)); + + fclose(pFileSource); + fclose(pFileUnzippedInflated); + } + + for (size_t i = 0; i < expectedFileCount; ++i) { + RaiiStringClean(&pExpectedPaths[i]); + RaiiStringClean(&pPathsToDecompressedFiles[i]); + } +} + +//============================== +// TEST_GROUP_RUNNER +//============================== + +TEST_GROUP_RUNNER(TestUnZipMinizipInflateZlib) { + RUN_TEST_CASE( + TestUnZipMinizipInflateZlib, + test_UnZipAndInflateSingleFileZipWritesDataCorrectlyToTheFile); + RUN_TEST_CASE( + TestUnZipMinizipInflateZlib, + test_UnZipAndInflateMultiFileZipWritesDataCorrectlyToTheFiles); + RUN_TEST_CASE( + TestUnZipMinizipInflateZlib, + test_UnZipAndInflateMultiFileAndSubDirZipWritesDataCorrectlyToTheFiles); +} diff --git a/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.h b/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.h new file mode 100644 index 00000000..682d5a69 --- /dev/null +++ b/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.h @@ -0,0 +1,3 @@ +#pragma once + +void SetupTestUnZipMinizipInflateZlib(char *pFullPathToBenchmarkTestFiles); diff --git a/CoDeLib/Test/src/TestZipContentInfo.c b/CoDeLib/Test/src/TestZipContentInfo.c new file mode 100644 index 00000000..048ff078 --- /dev/null +++ b/CoDeLib/Test/src/TestZipContentInfo.c @@ -0,0 +1,263 @@ +#include "unity_fixture.h" +#include +#include +#include + +TEST_GROUP(TestZipContentInfo); + +TEST_SETUP(TestZipContentInfo) {} + +TEST_TEAR_DOWN(TestZipContentInfo) {} + +//============================== +// ZipContentInfoCreate(...) +//============================== + +TEST( + TestZipContentInfo, + test_ZipContentInfoCreate_ReturnsEmptyZipContentInfoIfZipFilePathPtrIsNull) { + + RAII_ZIPCONTENTINFO zipContentInfo = ZipContentInfoCreate(NULL); + + TEST_ASSERT_NULL(zipContentInfo.zipFilePath.pString); + TEST_ASSERT_NULL(zipContentInfo.pUnZippedFilePathArray); + TEST_ASSERT_EQUAL(0, zipContentInfo.unZippedFileCount); +} + +TEST(TestZipContentInfo, test_ZipContentInfoCreate_CreatesCopyOfZipeFilePath) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + RAII_ZIPCONTENTINFO zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + TEST_ASSERT_NOT_NULL(zipContentInfo.zipFilePath.pString); + TEST_ASSERT_EQUAL_STRING(zipFilePath.pString, + zipContentInfo.zipFilePath.pString); + TEST_ASSERT_NOT_EQUAL(zipFilePath.pString, + zipContentInfo.zipFilePath.pString); +} + +TEST(TestZipContentInfo, test_ZipContentInfoCreate_SetsCorrectInitialValues) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("myZip.zip"); + RAII_ZIPCONTENTINFO zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + TEST_ASSERT_NOT_NULL(zipContentInfo.zipFilePath.pString); + TEST_ASSERT_NULL(zipContentInfo.pUnZippedFilePathArray); + TEST_ASSERT_EQUAL(0, zipContentInfo.unZippedFileCount); +} + +//============================== +// ZipContentInfoAddUnzippedFilePath(...) +//============================== + +TEST( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsFalseIfZipFileInfoPtrIsNull) { + + RAII_STRING fileName = RaiiStringCreateFromCString("file1.txt"); + bool result = ZipContentInfoAddUnzippedFilePath(NULL, &fileName); + TEST_ASSERT_FALSE(result); +} + +TEST(TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsFalseIfFileNamePtrIsNull) { + + RAII_ZIPCONTENTINFO zipContentInfo = (ZipContentInfo){ + .zipFilePath = RaiiStringCreateFromCString("someZipFile.zip"), + .pUnZippedFilePathArray = NULL, + .unZippedFileCount = 0}; + + bool result = ZipContentInfoAddUnzippedFilePath(&zipContentInfo, NULL); + TEST_ASSERT_FALSE(result); +} + +TEST( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsFalseIfZipFilePathStringPtrIsNull) { + + RAII_ZIPCONTENTINFO zipContentInfo = + (ZipContentInfo){.zipFilePath = {NULL, 0}, + .pUnZippedFilePathArray = NULL, + .unZippedFileCount = 0}; + + RAII_STRING fileName = RaiiStringCreateFromCString("file1.txt"); + + bool result = ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &fileName); + TEST_ASSERT_FALSE(result); +} + +TEST( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsFalseIfFileNameStringPtrIsNull) { + + RAII_ZIPCONTENTINFO zipContentInfo = (ZipContentInfo){ + .zipFilePath = RaiiStringCreateFromCString("someZipFile.zip"), + .pUnZippedFilePathArray = NULL, + .unZippedFileCount = 0}; + + RAII_STRING fileName = (RaiiString){NULL, 0}; + + bool result = ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &fileName); + TEST_ASSERT_FALSE(result); +} + +TEST(TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsTrueIfAllPtrsValid) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + RAII_ZIPCONTENTINFO zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + RAII_STRING fileName = RaiiStringCreateFromCString("file1.txt"); + + bool result = ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &fileName); + TEST_ASSERT_TRUE(result); +} + +TEST(TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_CreatesCopyOfFileName) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + RAII_ZIPCONTENTINFO zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + RAII_STRING fileName1 = RaiiStringCreateFromCString("file1.txt"); + ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &fileName1); + + TEST_ASSERT_NOT_NULL(zipContentInfo.pUnZippedFilePathArray[0].pString); + TEST_ASSERT_NOT_EQUAL(fileName1.pString, + zipContentInfo.pUnZippedFilePathArray[0].pString); +} + +TEST( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_IncrementsFileCounterFileOneAndAddsFileNameToArray) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + RAII_ZIPCONTENTINFO zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + RAII_STRING fileName1 = RaiiStringCreateFromCString("file1.txt"); + ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &fileName1); + + TEST_ASSERT_EQUAL(1, zipContentInfo.unZippedFileCount); + TEST_ASSERT_EQUAL_STRING(fileName1.pString, + zipContentInfo.pUnZippedFilePathArray[0].pString); +} + +TEST(TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_CanAddMultipleFiles) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + RAII_ZIPCONTENTINFO zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + RAII_STRING fileName1 = RaiiStringCreateFromCString("file1.txt"); + ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &fileName1); + + RAII_STRING fileName2 = RaiiStringCreateFromCString("file2.txt"); + ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &fileName2); + + TEST_ASSERT_EQUAL(2, zipContentInfo.unZippedFileCount); + TEST_ASSERT_EQUAL_STRING(fileName1.pString, + zipContentInfo.pUnZippedFilePathArray[0].pString); + TEST_ASSERT_EQUAL_STRING(fileName2.pString, + zipContentInfo.pUnZippedFilePathArray[1].pString); +} + +//============================== +// ZipContentInfoClean(...) +//============================== + +TEST( + TestZipContentInfo, + test_ZipContentInfoClean_SetsZipFileNameToNullptrIfThereIsNoFileNameArray) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + ZipContentInfo zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + ZipContentInfoClean(&zipContentInfo); + TEST_ASSERT_NULL(zipContentInfo.zipFilePath.pString); + TEST_ASSERT_NULL(zipContentInfo.pUnZippedFilePathArray); + TEST_ASSERT_EQUAL(0, zipContentInfo.unZippedFileCount); +} + +TEST(TestZipContentInfo, + test_ZipContentInfoClean_DoesNotCleanProvidedZipFilePath) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + ZipContentInfo zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + ZipContentInfoClean(&zipContentInfo); + TEST_ASSERT_NOT_NULL(zipFilePath.pString); + TEST_ASSERT_EQUAL_STRING("SomePath/myZip.zip", zipFilePath.pString); + TEST_ASSERT_EQUAL(19, zipFilePath.lengthWithTermination); +} + +TEST(TestZipContentInfo, test_ZipContentInfoClean_CleansAllFileNamesInArray) { + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + ZipContentInfo zipContentInfo = ZipContentInfoCreate(&zipFilePath); + + RAII_STRING file1 = RaiiStringCreateFromCString("file1.txt"); + RAII_STRING file2 = RaiiStringCreateFromCString("SomeDir/file2.txt"); + RAII_STRING file3 = RaiiStringCreateFromCString("file3.txt"); + ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &file1); + ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &file2); + ZipContentInfoAddUnzippedFilePath(&zipContentInfo, &file3); + + ZipContentInfoClean(&zipContentInfo); + TEST_ASSERT_NULL(zipContentInfo.zipFilePath.pString); + TEST_ASSERT_NULL(zipContentInfo.pUnZippedFilePathArray); + TEST_ASSERT_EQUAL(0, zipContentInfo.unZippedFileCount); +} + +//============================== +// RAII_ZIPCONTENTINFO +//============================== + +// TODO: Create tests + +//============================== +// TEST_GROUP_RUNNER +//============================== + +TEST_GROUP_RUNNER(TestZipContentInfo) { + // ZipContentInfoCreate() + RUN_TEST_CASE( + TestZipContentInfo, + test_ZipContentInfoCreate_ReturnsEmptyZipContentInfoIfZipFilePathPtrIsNull); + RUN_TEST_CASE(TestZipContentInfo, + test_ZipContentInfoCreate_CreatesCopyOfZipeFilePath); + RUN_TEST_CASE(TestZipContentInfo, + test_ZipContentInfoCreate_SetsCorrectInitialValues); + + // ZipContentInfoAddUnzippedFilePath() + RUN_TEST_CASE( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsFalseIfZipFileInfoPtrIsNull); + RUN_TEST_CASE( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsFalseIfFileNamePtrIsNull); + RUN_TEST_CASE( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsFalseIfZipFilePathStringPtrIsNull); + RUN_TEST_CASE( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsFalseIfFileNameStringPtrIsNull); + RUN_TEST_CASE( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_ReturnsTrueIfAllPtrsValid); + RUN_TEST_CASE(TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_CreatesCopyOfFileName); + RUN_TEST_CASE( + TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_IncrementsFileCounterFileOneAndAddsFileNameToArray); + RUN_TEST_CASE(TestZipContentInfo, + test_ZipContentInfoAddUnzippedFilePath_CanAddMultipleFiles); + + // ZipContentInfoClean() + RUN_TEST_CASE( + TestZipContentInfo, + test_ZipContentInfoClean_SetsZipFileNameToNullptrIfThereIsNoFileNameArray); + RUN_TEST_CASE(TestZipContentInfo, + test_ZipContentInfoClean_DoesNotCleanProvidedZipFilePath); + RUN_TEST_CASE(TestZipContentInfo, + test_ZipContentInfoClean_CleansAllFileNamesInArray); +} diff --git a/CoDeLib/Test/src/main.c b/CoDeLib/Test/src/main.c index fce95116..cfe9cf7d 100644 --- a/CoDeLib/Test/src/main.c +++ b/CoDeLib/Test/src/main.c @@ -2,12 +2,18 @@ #include "TestDeflateInflateZlib.h" #include "TestFileUtils.h" +#include "TestUnZipMinizip.h" +#include "TestUnZipMinizipInflateZlib.h" #include +#include static void RunAllTests(void) { RUN_TEST_GROUP(TestRaiiString); RUN_TEST_GROUP(TestDeflateInflateZlib); RUN_TEST_GROUP(TestFileUtils); + RUN_TEST_GROUP(TestUnZipMinizip); + RUN_TEST_GROUP(TestUnZipMinizipInflateZlib); + RUN_TEST_GROUP(TestZipContentInfo); } int main(int argc, const char **argv) { @@ -28,6 +34,8 @@ int main(int argc, const char **argv) { SetupTestDeflateInflateZlib(fullPathToBenchmarkTestFiles.pString); SetupTestFileUtils(fullPathToBenchmarkTestFiles.pString, currentWorkingDirectory.pString); + SetupTestUnZipMinizip(fullPathToBenchmarkTestFiles.pString); + SetupTestUnZipMinizipInflateZlib(fullPathToBenchmarkTestFiles.pString); return UnityMain(argc, argv, RunAllTests); } diff --git a/CoDeLib/UnZip_minizip/CMakeLists.txt b/CoDeLib/UnZip_minizip/CMakeLists.txt new file mode 100644 index 00000000..c108b40e --- /dev/null +++ b/CoDeLib/UnZip_minizip/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(UnZip_minizip STATIC + src/UnZip_minizip.c) + +target_include_directories(UnZip_minizip PUBLIC + $ + $ +) + +find_package(MINIZIP REQUIRED) +target_link_libraries(UnZip_minizip PRIVATE MINIZIP::minizip) + +set(UnZip_minizip_PUBLIC_INCLUDE_PATH ${CoDeLib_PUBLIC_INCLUDE_PATH}/UnZip_minizip) +set(UnZip_minizip_PUBLIC_HEADERS + ${UnZip_minizip_PUBLIC_INCLUDE_PATH}/UnZip_minizip.h +) +install(FILES ${UnZip_minizip_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CoDeLib/UnZip_minizip) diff --git a/CoDeLib/UnZip_minizip/src/UnZip_minizip.c b/CoDeLib/UnZip_minizip/src/UnZip_minizip.c new file mode 100644 index 00000000..c495df91 --- /dev/null +++ b/CoDeLib/UnZip_minizip/src/UnZip_minizip.c @@ -0,0 +1,172 @@ +#include +#include + +// minizip +#include +#include + +//============================== +// Helper functions +//============================== + +/*! +@brief Gets the filename that zfile is currently handling. +@param zfile The unzFile object. +@param pFInfo The unz_file_info64 object to be filled. +@return The filename that zfile is currently handling. The caller is responsible +for cleaning up the returned RaiiString. +*/ +RaiiString _GetCurrentFilenameInZip(unzFile zfile, unz_file_info64 *pFInfo, + bool *pIsutf8); + +//============================== +// Interface functions +//============================== + +UNZIP_RETURN_CODES +UnZip(ZipContentInfo *const pZipInfo, const RaiiString *const pOutputDirPath) { + if (pZipInfo == NULL || pZipInfo->zipFilePath.pString == NULL || + pOutputDirPath == NULL || pOutputDirPath->pString == NULL) { + return UNZIP_ERROR; + } + + unzFile uzfile = unzOpen64(pZipInfo->zipFilePath.pString); + if (uzfile == NULL) { + printf("Could not open %s for unzipping\n", + pZipInfo->zipFilePath.pString); + return UNZIP_ERROR; + } + + // TODO: Create a RaiiStringAppend_ function that returns a copy of the + // new string. That way the following line can be moved outside the + // loop. + RAII_STRING localOutputDirPath = + RaiiStringCreateFromCString(pOutputDirPath->pString); + + if (localOutputDirPath + .pString[localOutputDirPath.lengthWithTermination - 2] != '/') { + RaiiStringAppend_cString(&localOutputDirPath, "/"); + } + + if (!PathExists(localOutputDirPath.pString)) { + RecursiveMkdir(localOutputDirPath.pString); + } + + UNZIP_RETURN_CODES status = UNZIP_SUCCESS; + do { + // TODO: Create a RaiiStringAppend_ function that returns a copy of the + // new string. That way the following line can be moved outside the + // loop. + RAII_STRING unZippedDestPath = + RaiiStringCreateFromCString(localOutputDirPath.pString); + + // So far, pIsutf8 is not used and can thus be NULL + unz_file_info64 fInfo; + RAII_STRING currentFilename = + _GetCurrentFilenameInZip(uzfile, &fInfo, NULL); + if (currentFilename.pString == NULL) { + status = UNZIP_ERROR; + break; + } + + if (currentFilename.lengthWithTermination == 1) { + // Empty filename + status = UNZIP_ERROR; + break; + } + + RaiiStringAppend_RaiiString(&unZippedDestPath, ¤tFilename); + ZipContentInfoAddUnzippedFilePath(pZipInfo, &unZippedDestPath); + + if (fInfo.uncompressed_size == 0 && + unZippedDestPath.pString[unZippedDestPath.lengthWithTermination - + 2] == '/') { + // Is a directory + RecursiveMkdir(unZippedDestPath.pString); + } else { + // Is a file + const int raw = 1; // 1: unzip as raw data + int unzOpenStatus = unzOpenCurrentFile2(uzfile, NULL, NULL, raw); + if (unzOpenStatus != UNZ_OK) { + status = UNZIP_ERROR; + break; + } + + // Note: It is important to open the file in binary mode. + // Otherwise, a \r (cariage return) character will implicitely + // add a \n (newline) character. + FILE *pFile = fopen(unZippedDestPath.pString, "wb"); + if (pFile == NULL) { + status = UNZIP_ERROR; + break; + } + + int readCount = 0; + do { + char buffer[256]; + readCount = + unzReadCurrentFile(uzfile, buffer, sizeof(buffer) - 1); + buffer[readCount] = '\0'; + if (readCount < 0) { + status = UNZIP_ERROR; + break; + } + + if (readCount > 0) { + fwrite(buffer, 1, readCount, pFile); + } + } while (readCount > 0); + + fclose(pFile); + unzCloseCurrentFile(uzfile); + } + + if (status != UNZIP_SUCCESS) { + break; + } + + int nextFileStatus = unzGoToNextFile(uzfile); + if (nextFileStatus != MZ_OK) { + // If the next file is not found all files are processed and the + // loop will exit. + break; + } + } while (status == UNZIP_SUCCESS); + + unzClose(uzfile); + return status; +} + +const struct IUnZip unzip_minizip = { + .UnZip = UnZip, +}; + +//============================== +// Helper functions +// +// Based on code from: +// https://nachtimwald.com/2019/09/08/making-minizip-easier-to-use/ +//============================== + +RaiiString _GetCurrentFilenameInZip(unzFile zfile, unz_file_info64 *pFInfo, + bool *pIsutf8) { + RaiiString emptyString = (RaiiString){NULL, 0}; + + if (zfile == NULL || pFInfo == NULL) { + return emptyString; + } + + char filename[MAX_PATH_LENGTH_WTH_TERMINATOR]; + + int getFileInfoStatus = unzGetCurrentFileInfo64( + zfile, pFInfo, &filename[0], sizeof(filename), NULL, 0, NULL, 0); + if (getFileInfoStatus != MZ_OK) { + return emptyString; + } + + if (pIsutf8 != NULL) { + *pIsutf8 = (pFInfo->flag & (1 << 11)) ? true : false; + } + + return RaiiStringCreateFromCString(filename); +} diff --git a/CoDeLib/ZipContentInfo/CMakeLists.txt b/CoDeLib/ZipContentInfo/CMakeLists.txt new file mode 100644 index 00000000..ce816c61 --- /dev/null +++ b/CoDeLib/ZipContentInfo/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_library(ZipContentInfo STATIC + src/ZipContentInfo.c) + +target_include_directories(ZipContentInfo PUBLIC + $ + $ +) \ No newline at end of file diff --git a/CoDeLib/ZipContentInfo/src/ZipContentInfo.c b/CoDeLib/ZipContentInfo/src/ZipContentInfo.c new file mode 100644 index 00000000..0e419cfc --- /dev/null +++ b/CoDeLib/ZipContentInfo/src/ZipContentInfo.c @@ -0,0 +1,67 @@ +#include +#include + +ZipContentInfo ZipContentInfoCreate(const RaiiString *const pZipFilePath) { + ZipContentInfo zipInfo = (ZipContentInfo){.zipFilePath = {NULL, 0}, + .pUnZippedFilePathArray = NULL, + .unZippedFileCount = 0}; + + if (pZipFilePath == NULL) { + return zipInfo; + } + + // TODO: Use the copy function from RaiiString once it is created. + zipInfo.zipFilePath = RaiiStringCreateFromCString(pZipFilePath->pString); + + return zipInfo; +} + +void ZipContentInfoClean(ZipContentInfo *pZipInfo) { + if (pZipInfo == NULL) { + return; + } + + RaiiStringClean(&(pZipInfo->zipFilePath)); + + if (pZipInfo->pUnZippedFilePathArray != NULL) { + for (size_t i = 0; i < pZipInfo->unZippedFileCount; ++i) { + RaiiStringClean(&(pZipInfo->pUnZippedFilePathArray[i])); + } + + free(pZipInfo->pUnZippedFilePathArray); + pZipInfo->pUnZippedFilePathArray = NULL; + } + + pZipInfo->unZippedFileCount = 0; +} + +bool ZipContentInfoAddUnzippedFilePath(ZipContentInfo *const pZipInfo, + const RaiiString *const pFileName) { + if (pZipInfo == NULL || pZipInfo->zipFilePath.pString == NULL || + pFileName == NULL || pFileName->pString == NULL) { + return false; + } + + const size_t newCount = pZipInfo->unZippedFileCount + 1; + RaiiString *pNewArray = NULL; + + if (pZipInfo->pUnZippedFilePathArray == NULL) { + pNewArray = (RaiiString *)calloc(1, sizeof(RaiiString)); + } else { + pNewArray = (RaiiString *)realloc(pZipInfo->pUnZippedFilePathArray, + newCount * sizeof(RaiiString)); + } + + if (pNewArray == NULL) { + return false; + } + + // TODO: Simply paths by removing redundant '/'. + // TODO: Use the copy function from RaiiString once it is created. + pNewArray[newCount - 1] = RaiiStringCreateFromCString(pFileName->pString); + + pZipInfo->pUnZippedFilePathArray = pNewArray; + pZipInfo->unZippedFileCount = newCount; + + return true; +} diff --git a/CoDeLib/include/CoDeLib/IUnZip.h b/CoDeLib/include/CoDeLib/IUnZip.h new file mode 100644 index 00000000..ecefe2c7 --- /dev/null +++ b/CoDeLib/include/CoDeLib/IUnZip.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +typedef enum { UNZIP_SUCCESS, UNZIP_ERROR } UNZIP_RETURN_CODES; + +struct IUnZip { + /*! + * @brief Un-zips the input zip file and writes the output to the output + * files. + * @param pZipInfo The information about the zip file. The filenames in + * pZipInfo will be filled by the unzipping function. The caller is + * responsible for cleaning up the provided pZipInfo pointer. + * @param pOutputDirPath The path to the directory where the unzipped files + * will be stored. The directory must exist. + * @return UNZIP_SUCCESS if the un-zipping was successful, UNZIP_ERROR + * otherwise. + */ + UNZIP_RETURN_CODES(*UnZip) + (ZipContentInfo *const pZipInfo, const RaiiString *const pOutputDirPath); +}; diff --git a/CoDeLib/include/CoDeLib/UnZip_minizip/UnZip_minizip.h b/CoDeLib/include/CoDeLib/UnZip_minizip/UnZip_minizip.h new file mode 100644 index 00000000..b8b17eae --- /dev/null +++ b/CoDeLib/include/CoDeLib/UnZip_minizip/UnZip_minizip.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const struct IUnZip unzip_minizip; diff --git a/CoDeLib/include/CoDeLib/ZipContentInfo.h b/CoDeLib/include/CoDeLib/ZipContentInfo.h new file mode 100644 index 00000000..49094c63 --- /dev/null +++ b/CoDeLib/include/CoDeLib/ZipContentInfo.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +// clang-format off +/*! +Usage example: + + RAII_STRING zipFilePath = RaiiStringCreateFromCString("SomePath/myZip.zip"); + RAII_ZIPCONTENTINFO zipContentInfo = ZipContentInfoCreate(&zipFilePath); +*/ +// clang-format on + +#define RAII_ZIPCONTENTINFO \ + ZipContentInfo __attribute__((cleanup(ZipContentInfoClean))) + +typedef struct { + // The name of the zip file. + RaiiString zipFilePath; + + // The pointer to an array with all the files names in the zip file. + // The names are stored with any (sub)directories in the name. + // + // When unzipping the zip file, the files array is created and filled by the + // unzipping function. + RaiiString *pUnZippedFilePathArray; + + // The number of files in the zip file. + // + // When unzipping the zip file, the fileCount is set by the unzipping + // function. + size_t unZippedFileCount; +} ZipContentInfo; + +/*! +@brief Creates a ZipContentInfo object with the given zip file path. +@param pZipFilePath The path to the zip file. A deep copy is made and stored in +zipFilePath. +@return The ZipContentInfo object. +*/ +ZipContentInfo ZipContentInfoCreate(const RaiiString *const pZipFilePath); + +/*! +@brief Cleans the ZipContentInfo object. All RaiiString objects are +cleaned/freed. Vaues are set to NULL/0. +@param pZipInfo The ZipContentInfo object. +*/ +void ZipContentInfoClean(ZipContentInfo *pZipInfo); + +/*! +@brief Adds a file path to the list of unzipped files. +@param pZipInfo The ZipContentInfo object. +@param pFileName The file path to add. A deep copy is made and stored in +zipFilePath. +@return true if the file path was added successfully, false otherwise. +*/ +bool ZipContentInfoAddUnzippedFilePath(ZipContentInfo *const pZipInfo, + const RaiiString *const pFileName); diff --git a/External/minizip-ng b/External/minizip-ng new file mode 160000 index 00000000..6d45beb9 --- /dev/null +++ b/External/minizip-ng @@ -0,0 +1 @@ +Subproject commit 6d45beb98d713ce191c371b625cc9421ff56acd0 diff --git a/Scripts/BuildAndInstallCoDeLib.py b/Scripts/BuildAndInstallCoDeLib.py index 23af455c..c7551998 100644 --- a/Scripts/BuildAndInstallCoDeLib.py +++ b/Scripts/BuildAndInstallCoDeLib.py @@ -49,6 +49,9 @@ def BuildAndInstallCoDeLib( ExternalZlibLibInstallPath = Path( ExternalLibPath / "zlib/Install" / targetPlatformString / BuildTypeString ) + ExternalMinizipNgLibInstallPath = Path( + ExternalLibPath / "minizip-ng/Install" / targetPlatformString / BuildTypeString + ) CoDeLibRootPath = Path(RepositoryRootPath / ProjectName) BuildDirectory = Path( @@ -70,14 +73,15 @@ def BuildAndInstallCoDeLib( print("==============================") print(ProjectName + ": Configuring ({})".format(BuildTypeString)) print("==============================") - configureCommand = 'cmake -G "{0}" -DCMAKE_TOOLCHAIN_FILE="{1}" -S "{2}" -B "{3}" -DCMAKE_INSTALL_PREFIX="{4}" -DZLIB_ROOT="{5}" -DCMAKE_BUILD_TYPE={6}'.format( + configureCommand = 'cmake -G "{0}" -DCMAKE_TOOLCHAIN_FILE="{1}" -S "{2}" -B "{3}" -DCMAKE_INSTALL_PREFIX="{4}" -DCMAKE_BUILD_TYPE={5} -DZLIB_ROOT="{6}" -DCMAKE_PREFIX_PATH="{7}"'.format( buildEnv.GetCmakeGenerator(), buildEnv.GetCustomToolChainPath(), CoDeLibRootPath, BuildDirectory, InstallDirectory, - ExternalZlibLibInstallPath, BuildTypeString, + ExternalZlibLibInstallPath, + ExternalMinizipNgLibInstallPath, ) print(configureCommand) subprocess.run( diff --git a/Scripts/BuildAndInstallExternalLibs.py b/Scripts/BuildAndInstallExternalLibs.py index b2a5cb27..916ca13d 100644 --- a/Scripts/BuildAndInstallExternalLibs.py +++ b/Scripts/BuildAndInstallExternalLibs.py @@ -112,8 +112,92 @@ def BuildAndInstallZlib( ) +############################## +# minizip-ng +############################## +def BuildAndInstallMinizipNg( + buildEnv: EnvironmentConfig.EnvironmentConfiguration, + buildConfig: EnvironmentConfig.BuildConfig = EnvironmentConfig.BuildConfig.DEBUG, +): + ProjectName = "minizip-ng" + + BuildTypeString = EnvironmentConfig.BuildConfig.ToCMakeBuildType(buildConfig) + targetPlatformString = EnvironmentConfig.Platform.PlatformToOsName( + buildEnv.GetTargetPlatform() + ) + + TopLevelCMakeListsDirectory = Path(ExternalLibPath / ProjectName) + BuildDirectory = Path( + ExternalLibPath / ProjectName / "Build" / targetPlatformString / BuildTypeString + ) + InstallDirectory = Path( + ExternalLibPath + / ProjectName + / "Install" + / targetPlatformString + / BuildTypeString + ) + + if not BuildDirectory.exists(): + BuildDirectory.mkdir(parents=True) + + if InstallDirectory.exists(): + shutil.rmtree(InstallDirectory) + InstallDirectory.mkdir(parents=True) + + os.chdir(RepositoryRootPath) + + # All options for minizip-ng that are not needed are turned off (all off them) except for MZ_COMPAT + # In this project minizing-ng is used only for zip file creation and extraction + print("==============================") + print(ProjectName + ": Configuring ({})".format(BuildTypeString)) + print("==============================") + configureCommand = 'cmake -G "{0}" -DCMAKE_TOOLCHAIN_FILE="{1}" -S {2} -B {3} -DCMAKE_INSTALL_PREFIX="{4}" -DCMAKE_BUILD_TYPE={5} -DBUILD_SHARED_LIBS=OFF -DMZ_COMPAT=ON -DMZ_ZLIB=OFF -DMZ_BZIP2=OFF -DMZ_LZMA=OFF -DMZ_ZSTD=OFF -DMZ_LIBCOMP=OFF -DMZ_FETCH_LIBS=OFF -DMZ_PKCRYPT=OFF -DMZ_WZAES=OFF -DMZ_OPENSSL=OFF -DMZ_LIBBSD=OFF -DMZ_ICONV=OFF'.format( + buildEnv.GetCmakeGenerator(), + buildEnv.GetCustomToolChainPath(), + TopLevelCMakeListsDirectory, + BuildDirectory, + InstallDirectory, + BuildTypeString, + ) + print(configureCommand) + subprocess.run( + configureCommand, + shell=True, + check=True, + ) + + print("==============================") + print(ProjectName + ": Building ({})".format(BuildTypeString)) + print("==============================") + buildCommand = "cmake --build {0}".format(BuildDirectory) + print(buildCommand) + subprocess.run( + buildCommand, + shell=True, + check=True, + ) + + print("==============================") + print(ProjectName + ": Installing ({})".format(BuildTypeString)) + print("==============================") + installCommand = "cmake --install {0}".format(BuildDirectory) + print(installCommand) + subprocess.run( + installCommand, + shell=True, + check=True, + ) + + BuildEnv = EnvironmentConfig.EnvironmentConfiguration( RepositoryRootPath, targetPlatform ) + +# zlib BuildAndInstallZlib(BuildEnv, EnvironmentConfig.BuildConfig.DEBUG) BuildAndInstallZlib(BuildEnv, EnvironmentConfig.BuildConfig.RELEASE) + +# minizip-ng +BuildAndInstallMinizipNg(BuildEnv, EnvironmentConfig.BuildConfig.DEBUG) +BuildAndInstallMinizipNg(BuildEnv, EnvironmentConfig.BuildConfig.RELEASE) diff --git a/Scripts/BuildBenchmark.py b/Scripts/BuildBenchmark.py index 3e97c360..df44e152 100644 --- a/Scripts/BuildBenchmark.py +++ b/Scripts/BuildBenchmark.py @@ -55,6 +55,9 @@ def BuildBenchmark( ExternalZlibLibInstallPath = Path( ExternalLibPath / "zlib/Install" / targetPlatformString / BuildTypeString ) + ExternalMinizipNgLibInstallPath = Path( + ExternalLibPath / "minizip-ng/Install" / targetPlatformString / BuildTypeString + ) BenchmarkRootPath = Path(RepositoryRootPath / ProjectName) BuildDirectory = Path( @@ -69,14 +72,15 @@ def BuildBenchmark( print("==============================") print(ProjectName + ": Configuring ({})".format(BuildTypeString)) print("==============================") - configureCommand = 'cmake -G "{0}" -DCMAKE_TOOLCHAIN_FILE="{1}" -DCMAKE_PREFIX_PATH="{2}" -S {3} -B {4} -DZLIB_ROOT="{5}" -DCMAKE_BUILD_TYPE={6}'.format( + configureCommand = 'cmake -G "{0}" -DCMAKE_TOOLCHAIN_FILE="{1}" -S {2} -B {3} -DCMAKE_BUILD_TYPE={4} -DZLIB_ROOT="{5}" -DCMAKE_PREFIX_PATH="{6};{7}"'.format( buildEnv.GetCmakeGenerator(), buildEnv.GetCustomToolChainPath(), - CoDeLibInstallDirectory, BenchmarkRootPath, BuildDirectory, - ExternalZlibLibInstallPath, BuildTypeString, + ExternalZlibLibInstallPath, + CoDeLibInstallDirectory, + ExternalMinizipNgLibInstallPath, ) subprocess.run( configureCommand, diff --git a/Scripts/CleanAll.py b/Scripts/CleanAll.py index ada4126e..7866e48d 100644 --- a/Scripts/CleanAll.py +++ b/Scripts/CleanAll.py @@ -16,6 +16,8 @@ ExternalLibPath = Path(ProjectRootPath / "External") ExternalZlibLibBuildPath = Path(ExternalLibPath / "zlib/Build") ExternalZlibLibInstallPath = Path(ExternalLibPath / "zlib/Install") +ExternalMinizipNgLibBuildPath = Path(ExternalLibPath / "minizip-ng/Build") +ExternalMinizipNgLibInstallPath = Path(ExternalLibPath / "minizip-ng/Install") DirectoriesToRemove = [ BenchmarkBuildPath, @@ -23,6 +25,8 @@ CoDeLibInstallPath, ExternalZlibLibBuildPath, ExternalZlibLibInstallPath, + ExternalMinizipNgLibBuildPath, + ExternalMinizipNgLibInstallPath, ]