diff --git a/openVulkanoCpp/IO/Archive/ArchiveReader.cpp b/openVulkanoCpp/IO/Archive/ArchiveReader.cpp index 6841685..b8efd2e 100644 --- a/openVulkanoCpp/IO/Archive/ArchiveReader.cpp +++ b/openVulkanoCpp/IO/Archive/ArchiveReader.cpp @@ -57,6 +57,14 @@ namespace OpenVulkano bool ArchiveReader::Open(const char* archiveFile) { + if (archiveFile[0] == '\0') + { + if (m_logger) + { + m_logger->error("Unable to open archive file with an empty name!"); + } + return false; + } PrepOpen(); ChkErr(archive_read_open_filename(m_archive, archiveFile, BUFFER_SIZE)); ReadNextHeader(); diff --git a/openVulkanoCpp/IO/Archive/LibArchiveHelper.hpp b/openVulkanoCpp/IO/Archive/LibArchiveHelper.hpp index 2e7fee7..5607cb2 100644 --- a/openVulkanoCpp/IO/Archive/LibArchiveHelper.hpp +++ b/openVulkanoCpp/IO/Archive/LibArchiveHelper.hpp @@ -88,7 +88,7 @@ namespace OpenVulkano else if (archiveResult <= ARCHIVE_FAILED) lvl = spdlog::level::level_enum::err; const char* errorString = archive_error_string(arch); if (logger) logger->log(lvl, errorString ? errorString : "Unknown error while handling archive"); - if (archiveResult == ARCHIVE_FAILED) throw std::runtime_error(errorString); + if (archiveResult == ARCHIVE_FAILED || archiveResult == ARCHIVE_FATAL) throw std::runtime_error(errorString); return false; } } diff --git a/openVulkanoCpp/IO/FileDescription.hpp b/openVulkanoCpp/IO/FileDescription.hpp index 23bff4f..a793af2 100644 --- a/openVulkanoCpp/IO/FileDescription.hpp +++ b/openVulkanoCpp/IO/FileDescription.hpp @@ -19,7 +19,7 @@ namespace OpenVulkano std::string path; size_t size; std::filesystem::perms permissions; - time_t createTime, modTime; + time_t createTime = {}, modTime = {}; static FileDescription MakeDescriptionForFile(const char* path, size_t size) { diff --git a/tests/IO/Archive/ArchiveReader.cpp b/tests/IO/Archive/ArchiveReader.cpp new file mode 100644 index 0000000..59b9b8f --- /dev/null +++ b/tests/IO/Archive/ArchiveReader.cpp @@ -0,0 +1,154 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include + +#include "IO/Archive/ArchiveReader.hpp" +#include "IO/Archive/ArchiveWriter.hpp" +#include "IO/AppFolders.hpp" + +using namespace OpenVulkano; + +namespace +{ + std::string dummyArchiveDir() + { + return AppFolders::GetAppTempDir().string(); + } + + std::string dummyArchivePath() + { + return dummyArchiveDir() + "/test_archive.zip"; + } + + void makeDummyArchive() + { + ArchiveWriter writer(dummyArchivePath().c_str()); + FileDescription desc; + + desc.path = "stream1.txt"; + desc.size = 20; + desc.type = std::filesystem::file_type::regular; + desc.permissions = (std::filesystem::perms) 0644; + + { + auto stream = writer.AddFileStream(desc); + stream.write("Streaming data..", 17); + stream.flush(); + } + + desc.path = "stream2.txt"; + desc.size = 23; + desc.type = std::filesystem::file_type::regular; + desc.permissions = (std::filesystem::perms) 0644; + + { + auto stream = writer.AddFileStream(desc); + stream.write("Other streaming data..", 22); + stream.flush(); + } + } +} + +TEST_CASE("Dummy archive creation, default constructor", "[ArchiveReader]") +{ + makeDummyArchive(); + ArchiveReader reader; + REQUIRE(reader.IsOpen() == false); + + reader.Open(""); + REQUIRE(reader.IsOpen() == false); +} + +TEST_CASE("Open Archive from File", "[ArchiveReader]") +{ + { + std::string testArchive = dummyArchivePath(); + ArchiveReader reader; + bool opened = reader.Open(testArchive); + REQUIRE(opened == true); + REQUIRE(reader.IsOpen() == true); + REQUIRE(reader.HasNext() == true); + } + + { + const char* invalidArchive = "invalid_archive.tar"; + ArchiveReader reader; + REQUIRE_THROWS_AS(reader.Open(invalidArchive), std::runtime_error); + } +} + +TEST_CASE("Open Archive from Directory with Pattern", "[ArchiveReader]") +{ + std::filesystem::path testDir = dummyArchiveDir(); + std::string pattern = R"(.*\.zip$)"; + + { + size_t outSize = 0; + ArchiveReader reader; + bool opened = reader.Open(testDir, pattern, &outSize); + REQUIRE(opened == true); + REQUIRE(reader.IsOpen() == true); + REQUIRE(outSize > 0); + } + + { + size_t outSize = 0; + std::string invalidPattern = R"(\.invalid$)"; + ArchiveReader reader; + bool opened = reader.Open(testDir, invalidPattern, &outSize); + REQUIRE(opened == false); + REQUIRE(reader.IsOpen() == false); + REQUIRE(outSize == 0); + } +} + +TEST_CASE("ExtractNext and Skip Functions", "[ArchiveReader]") +{ + std::string testArchive = dummyArchivePath(); + ArchiveReader reader(testArchive); + + size_t extractedFiles = reader.ExtractRemaining(dummyArchiveDir().c_str()); + REQUIRE(extractedFiles > 0); + reader.SkipNext(); + REQUIRE(reader.HasNext() == false); + + bool foundDirectory = reader.SkipTill(std::filesystem::file_type::directory); + REQUIRE(foundDirectory == false); + REQUIRE(reader.HasNext() == false); +} + +TEST_CASE("Get Next File Operations", "[ArchiveReader]") +{ + std::string testArchive = dummyArchivePath(); + + ArchiveReader reader(testArchive); + + { + auto nextFile = reader.GetNextFile(); + REQUIRE(nextFile.has_value() == true); + REQUIRE(nextFile->first.size > 0); + REQUIRE(nextFile->second.Size() == nextFile->first.size); + } + + { + auto nextFile = reader.GetNextFileAsVector(); + REQUIRE(nextFile.has_value() == true); + REQUIRE(nextFile->first.size > 0); + REQUIRE(nextFile->second.size() == nextFile->first.size); + } + + { + std::ostringstream stream; + auto fileDescription = reader.StreamNextFile(stream); + REQUIRE(fileDescription.has_value() == false); + REQUIRE(stream.str().empty()); + } +} diff --git a/tests/IO/Archive/ArchiveWriter.cpp b/tests/IO/Archive/ArchiveWriter.cpp new file mode 100644 index 0000000..116e794 --- /dev/null +++ b/tests/IO/Archive/ArchiveWriter.cpp @@ -0,0 +1,180 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include + +#include "IO/Archive/ArchiveReader.hpp" +#include "IO/Archive/ArchiveWriter.hpp" +#include "IO/AppFolders.hpp" +#include "IO/FileDescription.hpp" + +using namespace OpenVulkano; + +TEST_CASE("AddFile from buffer", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_from_buffer.zip"; + + { + ArchiveWriter writer(archivePath.string().c_str()); + std::string buffer = "Hello"; + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "test.txt"; + desc.size = buffer.size(); + desc.permissions = (std::filesystem::perms) 0644; + REQUIRE(writer.AddFile(desc, buffer.c_str()) == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "test.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello"); +} + +TEST_CASE("AddFile from multiple buffers", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_from_buffers.zip"; + + { + ArchiveWriter writer(archivePath.string().c_str()); + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "test.txt"; + desc.size = 10; + desc.permissions = (std::filesystem::perms) 0644; + + const char* buffer1 = "Hello"; + const char* buffer2 = "World"; + std::vector> buffers = { { buffer1, std::strlen(buffer1) }, + { buffer2, std::strlen(buffer2) } }; + REQUIRE(writer.AddFile(desc, buffers) == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "test.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "HelloWorld"); +} + +TEST_CASE("AddFile from file", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto testFilePath = tempDir / "test.txt"; + auto archivePath = tempDir / "test_archive_from_file.zip"; + + std::ofstream testFile(testFilePath); + testFile << "Hello from file"; + testFile.close(); + + { + ArchiveWriter writer(archivePath.string().c_str()); + REQUIRE(writer.AddFile(testFilePath.string().c_str(), "test.txt") == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "test.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello from file"); +} + +TEST_CASE("AddFileStream", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_stream.zip"; + + { + ArchiveWriter writer(archivePath.string().c_str()); + std::string buffer = "Streamed data."; + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "stream.txt"; + desc.size = buffer.size(); + desc.permissions = (std::filesystem::perms) 0644; + + ArchiveOStream stream = writer.AddFileStream(desc); + stream << buffer; + stream.Close(); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "stream.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Streamed data."); +} + +TEST_CASE("Compression settings", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_compression.zip"; + + { + ArchiveConfiguration config(ArchiveType::ZIP, CompressionType::GZIP, 9); + ArchiveWriter writer(archivePath.string().c_str(), config); + + std::string buffer = "Hello"; + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "compressed.txt"; + desc.size = buffer.size(); + desc.permissions = (std::filesystem::perms) 0644; + writer.SetShouldCompressFunction([](const FileDescription&) { return true; }); + REQUIRE(writer.AddFile(desc, buffer.c_str()) == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "compressed.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello"); +} + +TEST_CASE("Uncompressed settings", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_uncompressed.zip"; + + { + ArchiveConfiguration config(ArchiveType::ZIP, CompressionType::NONE, 0); + ArchiveWriter writer(archivePath.string().c_str(), config); + + std::string buffer = "Hello"; + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "uncompressed.txt"; + desc.size = buffer.size(); + desc.permissions = (std::filesystem::perms) 0644; + + writer.SetShouldCompressFunction([](const FileDescription&) { return false; }); + REQUIRE(writer.AddFile(desc, buffer.c_str()) == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "uncompressed.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello"); +} \ No newline at end of file