Skip to content

Commit

Permalink
dev: Zip multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
enzoevers committed Jan 15, 2025
1 parent fd7b481 commit 8e23e1d
Show file tree
Hide file tree
Showing 14 changed files with 983 additions and 4 deletions.
4 changes: 3 additions & 1 deletion CoDeLib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ set(COMMON_HEADERS
${CoDeLib_PUBLIC_INCLUDE_PATH}/IDeflate.h
${CoDeLib_PUBLIC_INCLUDE_PATH}/IInflate.h
${CoDeLib_PUBLIC_INCLUDE_PATH}/IUnZip.h
${CoDeLib_PUBLIC_INCLUDE_PATH}/IZip.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(Zip_minizip)
add_subdirectory(RaiiString)
add_subdirectory(ZipContentInfo)
add_subdirectory(Test)

install(
TARGETS CoDeLib Deflate_zlib Inflate_zlib UnZip_minizip RaiiString ZipContentInfo FileUtils
TARGETS CoDeLib Deflate_zlib Inflate_zlib UnZip_minizip Zip_minizip RaiiString ZipContentInfo FileUtils
EXPORT CoDeLibTargets
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
Expand Down
3 changes: 2 additions & 1 deletion CoDeLib/CustomDeflate.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
# refs

- https://pnrsolution.org/Datacenter/Vol4/Issue1/58.pdf
- https://nachtimwald.com/2019/09/08/making-minizip-easier-to-use/
- https://nachtimwald.com/2019/09/08/making-minizip-easier-to-use/
- https://pkwaredownloads.blob.core.windows.net/pkware-general/Documentation/APPNOTE-6.3.9.TXT
3 changes: 2 additions & 1 deletion CoDeLib/FileUtils/src/FileUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ size_t ExtractLastPartOfPath(const char *const pPath, char *const pDestBuffer,
return startIndexOfNameInPath;
}

void OpenFileWithMode(FILE **pInFile, RaiiString *pFullPath, char *pOpenMode) {
void OpenFileWithMode(FILE **pInFile, const RaiiString *const pFullPath,
const char *const pOpenMode) {
*pInFile = fopen(pFullPath->pString, pOpenMode);
if (*pInFile == NULL) {
printf("Failed to open file: %s\n", pFullPath->pString);
Expand Down
3 changes: 3 additions & 0 deletions CoDeLib/Test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ add_executable(CoDeLib_Test
src/TestDeflateInflateZlib.c
src/TestFileUtils.c
src/TestUnZipMinizip.c
src/TestZipMinizip.c
src/TestUnZipMinizipInflateZlib.c
src/TestZipMinizipUnZipMinizip.c
src/TestZipContentInfo.c
)

Expand All @@ -27,6 +29,7 @@ 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 Zip_minizip)
target_link_libraries(CoDeLib_Test PRIVATE RaiiString)
target_link_libraries(CoDeLib_Test PRIVATE ZipContentInfo)

Expand Down
308 changes: 308 additions & 0 deletions CoDeLib/Test/src/TestZipMinizip.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
#include "TestZipMinizip.h"
#include "unity_fixture.h"
#include <CoDeLib/FileUtils/FileUtils.h>
#include <CoDeLib/RaiiString/RaiiString.h>
#include <CoDeLib/Zip_minizip/Zip_minizip.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

// https://en.wikipedia.org/wiki/ZIP_(file_format)#Design

typedef struct {
/*!
@brief The offset in the zip file where the signature should be, relative to
the corresponding collection.
*/
size_t offset;
size_t bytesCount;
const char *pBytes;
} ZipStructureKnownValues;

typedef struct {
ZipStructureKnownValues signature;
ZipStructureKnownValues versionMinimum;
ZipStructureKnownValues generalPurposeBitFlag;
ZipStructureKnownValues compressionMethod;
ZipStructureKnownValues lastModificationFileTime;
ZipStructureKnownValues lastModificationFileDate;
ZipStructureKnownValues crc32;
ZipStructureKnownValues compressedSize;
ZipStructureKnownValues uncompressedSize;
ZipStructureKnownValues filenameLength;
ZipStructureKnownValues extraFieldLength;
ZipStructureKnownValues filename;
ZipStructureKnownValues extraField;
} LocalFileHeaderContent;

typedef struct {
ZipStructureKnownValues signature;
ZipStructureKnownValues crc32;
ZipStructureKnownValues compressedSize;
ZipStructureKnownValues uncompressedSize;
} DataDescriptorContent;

typedef struct {
ZipStructureKnownValues signature;
ZipStructureKnownValues sizeOfExtraFieldChunk;
ZipStructureKnownValues uncompressedSize;
ZipStructureKnownValues compressedSize;
ZipStructureKnownValues offsetLocalHeaderRecord;
ZipStructureKnownValues diskStartNumber;
} Zip64ExtendedInformationExtraFieldContent;

typedef struct {
ZipStructureKnownValues signature;
ZipStructureKnownValues versionMadeBy;
ZipStructureKnownValues versionNeededToExtract;
ZipStructureKnownValues generalPurposeBitFlag;
ZipStructureKnownValues compressionMethod;
ZipStructureKnownValues lastModificationFileTime;
ZipStructureKnownValues lastModificationFileDate;
ZipStructureKnownValues crc32;
ZipStructureKnownValues compressedSize;
ZipStructureKnownValues uncompressedSize;
ZipStructureKnownValues filenameLength;
ZipStructureKnownValues extraFieldLength;
ZipStructureKnownValues fileCommentLength;
ZipStructureKnownValues diskNumberStart;
ZipStructureKnownValues internalFileAttributes;
ZipStructureKnownValues externalFileAttributes;
ZipStructureKnownValues relativeOffsetOfLocalHeader;
ZipStructureKnownValues filename;
ZipStructureKnownValues extraField;
ZipStructureKnownValues fileComment;
} CentralDirectoryFileHeaderContent;

typedef struct {
ZipStructureKnownValues signature;
ZipStructureKnownValues diskNumber;
ZipStructureKnownValues diskWithStartOfCentralDirectory;
ZipStructureKnownValues numberOfCentralDirectoryRecordsOnThisDisk;
ZipStructureKnownValues totalNumberOfCentralDirectoryRecords;
ZipStructureKnownValues sizeOfCentralDirectory;
ZipStructureKnownValues offsetOfStartOfCentralDirectory;
ZipStructureKnownValues commentLength;
ZipStructureKnownValues comment;
} EndOfCentralDirectoryRecordContent;

typedef struct {
ZipStructureKnownValues signature;
ZipStructureKnownValues sizeOfEocd64Min12;
ZipStructureKnownValues versionMadeBy;
ZipStructureKnownValues versionNeededToExtract;
ZipStructureKnownValues diskNumber;
ZipStructureKnownValues diskWithStartOfCentralDirectory;
ZipStructureKnownValues numberOfCentralDirectoryRecordsOnThisDisk;
ZipStructureKnownValues totalNumberOfCentralDirectoryRecords;
ZipStructureKnownValues sizeOfCentralDirectory;
ZipStructureKnownValues offsetOfStartOfCentralDirectory;
ZipStructureKnownValues comment;
} Zip64EndOfCentralDirectoryRecordContent;

static const LocalFileHeaderContent g_localFileHeaderContent_Zip64Deflate
__attribute__((unused)) = {
.signature = {0, 4, "\x50\x4b\x03\x04"},
.versionMinimum = {4, 2, NULL},
.generalPurposeBitFlag = {6, 2, NULL},
.compressionMethod = {8, 2, "\x08\x00"},
.lastModificationFileTime = {10, 2, NULL},
.lastModificationFileDate = {12, 2, NULL},
.crc32 = {14, 4, NULL},
.compressedSize = {18, 4, "\xff\xff\xff\xff"},
.uncompressedSize = {22, 4, "\xff\xff\xff\xff"},
.filenameLength = {26, 2, NULL},
.extraFieldLength = {28, 2, NULL},
.filename = {30, 0, NULL},
.extraField = {30, 0, NULL},
};

static const DataDescriptorContent g_dataDescriptorContentWithSignature_Zip64
__attribute__((unused)) = {
.signature = {0, 4, "\x50\x4b\x07\x08"},
.crc32 = {4, 4, NULL},
.compressedSize = {8, 8, "\xff\xff\xff\xff\xff\xff\xff\xff"},
.uncompressedSize = {16, 8, "\xff\xff\xff\xff\xff\xff\xff\xff"},
};

static char *g_pFullPathToBenchmarkTestFiles;

void SetupTestZipMinizip(char *pFullPathToBenchmarkTestFiles) {
g_pFullPathToBenchmarkTestFiles = pFullPathToBenchmarkTestFiles;
}

TEST_GROUP(TestZipMinizip);

static RaiiString g_pathToSmallBasicTextFileZipDeflate;
static RaiiString g_pathToSmallBasicTextFileZipSource;
static RaiiString g_pathToMultiTextFileZipSource;
static RaiiString g_pathToMultiTextFileAndSubDirZipSource;

TEST_SETUP(TestZipMinizip) {
g_pathToSmallBasicTextFileZipDeflate =
RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles);
RaiiStringAppend_cString(&g_pathToSmallBasicTextFileZipDeflate,
"/SmallBasicTextFileZip/"
"SmallBasicTextFile_deflate.zip");

g_pathToSmallBasicTextFileZipSource =
RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles);
RaiiStringAppend_cString(&g_pathToSmallBasicTextFileZipSource,
"/SmallBasicTextFileZip/");

g_pathToMultiTextFileZipSource =
RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles);
RaiiStringAppend_cString(&g_pathToMultiTextFileZipSource,
"/MultiTextFileZip/");

g_pathToMultiTextFileAndSubDirZipSource =
RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles);
RaiiStringAppend_cString(&g_pathToMultiTextFileAndSubDirZipSource,
"/MultiTextFileAndSubDirZip/");
}

TEST_TEAR_DOWN(TestZipMinizip) {
RaiiStringClean(&g_pathToSmallBasicTextFileZipDeflate);
RaiiStringClean(&g_pathToSmallBasicTextFileZipSource);
RaiiStringClean(&g_pathToMultiTextFileZipSource);
RaiiStringClean(&g_pathToMultiTextFileAndSubDirZipSource);

if (PathExists("./tmp/")) {
TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/"));
}
}

//==============================
// Zip()
//==============================

TEST(TestZipMinizip, test_Zip_ReturnsErrorIfOutputZipPathIsNullptr) {
const char *pInputPathArray[] = {
"123",
"asdf",
};

const ZIP_RETURN_CODES statusZip =
zip_minizip.Zip(NULL, &pInputPathArray[0], 2);
TEST_ASSERT_EQUAL(ZIP_ERROR, statusZip);
}

TEST(TestZipMinizip, test_Zip_ReturnsErrorIfInputPathArrayIsNullptr) {
const char dummyString[] = "123";

const ZIP_RETURN_CODES statusZip =
zip_minizip.Zip(&dummyString[0], NULL, 2);
TEST_ASSERT_EQUAL(ZIP_ERROR, statusZip);
}

TEST(TestZipMinizip, test_Zip_ReturnsErrorIfProvidedSizeIsZero) {
const char dummyString[] = "123";
const char *pInputPathArray[] = {
"123",
"asdf",
};

const ZIP_RETURN_CODES statusZip =
zip_minizip.Zip(&dummyString[0], &pInputPathArray[0], 0);
TEST_ASSERT_EQUAL(ZIP_ERROR, statusZip);
}

TEST(TestZipMinizip, test_Zip_ReturnsErrorIfZipFileAlreadyExists) {
RAII_STRING dummyZipPath = RaiiStringCreateFromCString("./tmp/");
RecursiveMkdir(dummyZipPath.pString);

RaiiStringAppend_cString(&dummyZipPath, "SomeZip.zip");
FILE *pDummyFile = fopen(dummyZipPath.pString, "w");
TEST_ASSERT_NOT_NULL(pDummyFile);
fclose(pDummyFile);

const char *pInputPathArray[] = {
"123",
};

const ZIP_RETURN_CODES statusZip =
zip_minizip.Zip(dummyZipPath.pString, &pInputPathArray[0], 1);
TEST_ASSERT_EQUAL(ZIP_ERROR, statusZip);
}

TEST(TestZipMinizip,
test_Zip_CreatesZipFileAfterZippingInCorrectPathProvidingRelativePath) {
RAII_STRING inputTextFilePath = RaiiStringCreateFromCString(
g_pathToSmallBasicTextFileZipSource.pString);
RaiiStringAppend_cString(&inputTextFilePath, "SmallBasicTextFile.txt");

const char *pInputPathArray[] = {
inputTextFilePath.pString,
};

RAII_STRING outputZipPath =
RaiiStringCreateFromCString("./tmp/SmallBasicTextFile.zip");

TEST_ASSERT_FALSE(PathExists(outputZipPath.pString));

const ZIP_RETURN_CODES statusZip =
zip_minizip.Zip(outputZipPath.pString, &pInputPathArray[0], 1);
TEST_ASSERT_EQUAL(ZIP_SUCCESS, statusZip);

TEST_ASSERT_TRUE(PathExists(outputZipPath.pString));
}

TEST(TestZipMinizip,
test_Zip_CreatesZipFileAfterZippingInCorrectPathProvidingAbsolutePath) {
RAII_STRING inputTextFilePath = RaiiStringCreateFromCString(
g_pathToSmallBasicTextFileZipSource.pString);
RaiiStringAppend_cString(&inputTextFilePath, "SmallBasicTextFile.txt");

const char *pInputPathArray[] = {
inputTextFilePath.pString,
};

char cwdBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR];
GetCurrentWorkingDirectory(&cwdBuffer[0], MAX_PATH_LENGTH_WTH_TERMINATOR);

RAII_STRING outputZipPath = RaiiStringCreateFromCString(&cwdBuffer[0]);
RaiiStringAppend_cString(&outputZipPath, "tmp/SmallBasicTextFile.zip");

TEST_ASSERT_FALSE(PathExists(outputZipPath.pString));

const ZIP_RETURN_CODES statusZip =
zip_minizip.Zip(outputZipPath.pString, &pInputPathArray[0], 1);
TEST_ASSERT_EQUAL(ZIP_SUCCESS, statusZip);

TEST_ASSERT_TRUE(PathExists(outputZipPath.pString));
}

// TODO: Create tests that test that single files and directories are added to
// the zip file.

// TEST(TestZipMinizip, test_Zip_ExternallyCreatedZipHasExpectedHeaderBytes) {
// TEST_IGNORE_MESSAGE("Not implemented yet");
// }

// TEST(TestZipMinizip, test_Zip_CreatedZipHasCorrentHeaderBytes) {
// TEST_IGNORE_MESSAGE("Not implemented yet");
// }

//==============================
// TEST_GROUP_RUNNER
//==============================

TEST_GROUP_RUNNER(TestZipMinizip) {
// Zip()
RUN_TEST_CASE(TestZipMinizip,
test_Zip_ReturnsErrorIfOutputZipPathIsNullptr);
RUN_TEST_CASE(TestZipMinizip,
test_Zip_ReturnsErrorIfInputPathArrayIsNullptr);
RUN_TEST_CASE(TestZipMinizip, test_Zip_ReturnsErrorIfProvidedSizeIsZero);
RUN_TEST_CASE(TestZipMinizip, test_Zip_ReturnsErrorIfZipFileAlreadyExists);
RUN_TEST_CASE(
TestZipMinizip,
test_Zip_CreatesZipFileAfterZippingInCorrectPathProvidingRelativePath);
RUN_TEST_CASE(
TestZipMinizip,
test_Zip_CreatesZipFileAfterZippingInCorrectPathProvidingAbsolutePath);
// RUN_TEST_CASE(TestZipMinizip,
// test_Zip_ExternallyCreatedZipHasExpectedHeaderBytes);
// RUN_TEST_CASE(TestZipMinizip, test_Zip_CreatedZipHasCorrentHeaderBytes);
}
3 changes: 3 additions & 0 deletions CoDeLib/Test/src/TestZipMinizip.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

void SetupTestZipMinizip(char *pFullPathToBenchmarkTestFiles);
Loading

0 comments on commit 8e23e1d

Please sign in to comment.