From 3d56c6a410518d280a5f6224797518a5e3326395 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Thu, 22 May 2025 21:50:45 +0200 Subject: [PATCH] Add ArchiveReaderIterator --- .../AR/Provider/Playback/ArPlaybackReader.hpp | 2 +- openVulkanoCpp/IO/Archive/ArchiveReader.cpp | 34 +-- openVulkanoCpp/IO/Archive/ArchiveReader.hpp | 38 ++- .../IO/Archive/ArchiveReaderIterator.cpp | 38 +++ .../IO/Archive/ArchiveReaderIterator.hpp | 63 +++++ tests/IO/Archive/ArchiveReaderIterator.cpp | 253 ++++++++++++++++++ 6 files changed, 397 insertions(+), 31 deletions(-) create mode 100644 openVulkanoCpp/IO/Archive/ArchiveReaderIterator.cpp create mode 100644 openVulkanoCpp/IO/Archive/ArchiveReaderIterator.hpp create mode 100644 tests/IO/Archive/ArchiveReaderIterator.cpp diff --git a/openVulkanoCpp/AR/Provider/Playback/ArPlaybackReader.hpp b/openVulkanoCpp/AR/Provider/Playback/ArPlaybackReader.hpp index 0d7b716..254b784 100644 --- a/openVulkanoCpp/AR/Provider/Playback/ArPlaybackReader.hpp +++ b/openVulkanoCpp/AR/Provider/Playback/ArPlaybackReader.hpp @@ -59,7 +59,7 @@ namespace OpenVulkano::AR::Playback int GetNextFrameId() { - std::string name = m_archiveMetadata.GetNextDescription().path; + std::string name = m_archiveMetadata.GetDescription().path; return std::stoi(name.substr(0, name.length() - 5)); } diff --git a/openVulkanoCpp/IO/Archive/ArchiveReader.cpp b/openVulkanoCpp/IO/Archive/ArchiveReader.cpp index d9fb1ff..de27ecb 100644 --- a/openVulkanoCpp/IO/Archive/ArchiveReader.cpp +++ b/openVulkanoCpp/IO/Archive/ArchiveReader.cpp @@ -205,16 +205,6 @@ namespace OpenVulkano return archive_read_support_format_all; } - size_t ArchiveReader::ExtractRemaining(std::string_view targetDir) - { - size_t count = 0; - while (ExtractNext(targetDir)) - { - count++; - } - return count; - } - size_t ArchiveReader::ExtractRemaining(const std::filesystem::path &targetDir) { size_t count = 0; @@ -258,7 +248,7 @@ namespace OpenVulkano return HasNext(); } - FileDescription ArchiveReader::GetNextDescription() const + FileDescription ArchiveReader::GetDescription() const { return { LibArchiveHelper::GetFileType(archive_entry_filetype(m_archiveEntry)), @@ -270,11 +260,21 @@ namespace OpenVulkano }; } + std::pair> ArchiveReader::GetNext() + { + if (!HasNext()) throw std::runtime_error("Reached end of file!"); + std::pair> file = {GetDescription(), Array() }; + file.second = Array(file.first.size); + ChkErr(archive_read_data(m_archive, file.second.Data(), file.second.Size())); + ReadNextHeader(); + return file; + } + std::optional ArchiveReader::GetNextDirectory() { if (SkipTill(std::filesystem::file_type::directory)) { - return GetNextDescription(); + return GetDescription(); } return std::nullopt; } @@ -283,7 +283,7 @@ namespace OpenVulkano { if (SkipTill(std::filesystem::file_type::regular)) { - FileDescription fileDescription = GetNextDescription(); + FileDescription fileDescription = GetDescription(); std::filesystem::path outputFilePath; if (m_filePathRewrite) outputFilePath = targetDir / m_filePathRewrite(fileDescription.path); @@ -317,7 +317,7 @@ namespace OpenVulkano { if (SkipTill(std::filesystem::file_type::regular)) { - std::pair> file = { GetNextDescription(), Array() }; + std::pair> file = {GetDescription(), Array() }; file.second = Array(file.first.size); ChkErr(archive_read_data(m_archive, file.second.Data(), file.second.Size())); ReadNextHeader(); @@ -330,7 +330,7 @@ namespace OpenVulkano { if (SkipTill(std::filesystem::file_type::regular)) { - std::pair> file = { GetNextDescription(), std::vector() }; + std::pair> file = {GetDescription(), std::vector() }; file.second.resize(file.first.size); ChkErr(archive_read_data(m_archive, file.second.data(), file.second.size())); ReadNextHeader(); @@ -359,7 +359,7 @@ namespace OpenVulkano { if (SkipTill(std::filesystem::file_type::regular)) { - FileDescription fileDescription = GetNextDescription(); + FileDescription fileDescription = GetDescription(); LibArchiveEntryStreamBuffer streamBuffer(m_archive); std::istream stream(&streamBuffer); streamReader(fileDescription, stream); @@ -381,7 +381,7 @@ namespace OpenVulkano ChkErr(r); stream.write(static_cast(buffer), size); } - FileDescription fileDescription = GetNextDescription(); + FileDescription fileDescription = GetDescription(); ReadNextHeader(); return fileDescription; } diff --git a/openVulkanoCpp/IO/Archive/ArchiveReader.hpp b/openVulkanoCpp/IO/Archive/ArchiveReader.hpp index d23ddd7..c6593f1 100644 --- a/openVulkanoCpp/IO/Archive/ArchiveReader.hpp +++ b/openVulkanoCpp/IO/Archive/ArchiveReader.hpp @@ -7,6 +7,7 @@ #pragma once #include "ArchiveBase.hpp" +#include "ArchiveReaderIterator.hpp" #include "Data/Containers/Array.hpp" #include "ArchiveType.hpp" #include @@ -54,8 +55,6 @@ namespace OpenVulkano [[nodiscard]] bool IsOpen() const { return m_open; } - [[deprecated]] size_t ExtractRemaining(std::string_view targetDir); - size_t ExtractRemaining(const std::filesystem::path& targetDir); size_t ExtractRemaining(const std::filesystem::path& targetDir, const std::function& extractionCallback); @@ -67,29 +66,42 @@ namespace OpenVulkano bool SkipTill(std::filesystem::file_type type = std::filesystem::file_type::regular); - [[nodiscard]] FileDescription GetNextDescription() const; + [[nodiscard]] FileDescription GetDescription() const; - std::optional GetNextDirectory(); + [[nodiscard]] std::pair> GetNext(); + + [[nodiscard]] std::optional GetNextDirectory(); [[deprecated]] std::optional ExtractNext(std::string_view targetDir) { return ExtractNext(std::filesystem::path(targetDir)); } - - std::optional ExtractNext(const std::filesystem::path& targetDir); - std::optional>> GetNextFile(); + [[nodiscard]] std::optional ExtractNext(const std::filesystem::path& targetDir); - std::optional>> GetNextFileAsVector(); + [[nodiscard]] std::optional>> GetNextFile(); - std::optional>> GetFile(const std::string& path); + [[nodiscard]] std::optional>> GetNextFileAsVector(); - std::optional StreamNextFile(std::ostream& stream); + [[nodiscard]] std::optional>> GetFile(const std::string& path); + + [[nodiscard]] std::optional StreamNextFile(std::ostream& stream); bool GetNextFileAsStream(const std::function& streamReader); - const decltype(m_passwordCallback)& GetPasswordCallback() const { return m_passwordCallback; } + [[nodiscard]] const decltype(m_passwordCallback)& GetPasswordCallback() const { return m_passwordCallback; } void SetPathRewriteFunction(const decltype(m_filePathRewrite)& rewriteFunc) { m_filePathRewrite = rewriteFunc; } - - const decltype(m_filePathRewrite)& GetPathRewriteFunction() const { return m_filePathRewrite; } + + [[nodiscard]] const decltype(m_filePathRewrite)& GetPathRewriteFunction() const { return m_filePathRewrite; } + + public: + using iterator = ArchiveReaderIterator; + using const_iterator = ArchiveReaderIterator; + + [[nodiscard]] iterator begin() { return ArchiveReaderIterator(this); } + [[nodiscard]] iterator end() { return ArchiveReaderIterator(nullptr); } + [[nodiscard]] const_iterator begin() const { return ArchiveReaderIterator(const_cast(this)); } + [[nodiscard]] const_iterator end() const { return ArchiveReaderIterator(nullptr); } + [[nodiscard]] const_iterator cbegin() const { return begin(); } + [[nodiscard]] const_iterator cend() const { return end(); } private: void ReadNextHeader(); diff --git a/openVulkanoCpp/IO/Archive/ArchiveReaderIterator.cpp b/openVulkanoCpp/IO/Archive/ArchiveReaderIterator.cpp new file mode 100644 index 0000000..9a98471 --- /dev/null +++ b/openVulkanoCpp/IO/Archive/ArchiveReaderIterator.cpp @@ -0,0 +1,38 @@ +/* + * 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 "ArchiveReaderIterator.hpp" +#include "ArchiveReader.hpp" + +namespace OpenVulkano +{ + ArchiveReaderIterator::ArchiveReaderIterator(ArchiveReader* reader) : m_reader(reader) + { + if (m_reader) + { + if (m_reader->HasNext()) m_current = m_reader->GetNext(); + else m_reader = nullptr; + } + } + + ArchiveReaderIterator& ArchiveReaderIterator::operator++() + { + if (m_reader) + { + if (m_reader->HasNext()) + { + m_current = m_reader->GetNext(); + } + else + { + m_current.first.Reset(); + m_current.second.Reset(); + m_reader = nullptr; + } + } + return *this; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/IO/Archive/ArchiveReaderIterator.hpp b/openVulkanoCpp/IO/Archive/ArchiveReaderIterator.hpp new file mode 100644 index 0000000..08a344d --- /dev/null +++ b/openVulkanoCpp/IO/Archive/ArchiveReaderIterator.hpp @@ -0,0 +1,63 @@ +/* + * 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/. + */ + +#pragma once + +#include "IO/FileDescription.hpp" +#include "Data/Containers/Array.hpp" +#include +#include + +namespace OpenVulkano +{ + class ArchiveReader; + + class ArchiveReaderIterator + { + public: + using iterator_category = std::input_iterator_tag; + using value_type = std::pair>; + using difference_type = std::ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + + private: + ArchiveReader* m_reader; + std::pair> m_current; + + public: + explicit ArchiveReaderIterator(ArchiveReader* reader = nullptr); + + reference operator*() const + { + return m_current; + } + + pointer operator->() const + { + return &m_current; + } + + ArchiveReaderIterator& operator++(); + + ArchiveReaderIterator operator++(int) + { + ArchiveReaderIterator tmp = *this; + ++(*this); + return tmp; + } + + bool operator==(const ArchiveReaderIterator& other) const + { + return m_reader == other.m_reader; + } + + bool operator!=(const ArchiveReaderIterator& other) const + { + return !(*this == other); + } + }; +} \ No newline at end of file diff --git a/tests/IO/Archive/ArchiveReaderIterator.cpp b/tests/IO/Archive/ArchiveReaderIterator.cpp new file mode 100644 index 0000000..7b44273 --- /dev/null +++ b/tests/IO/Archive/ArchiveReaderIterator.cpp @@ -0,0 +1,253 @@ +/* + * 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 "IO/Archive/ArchiveReader.hpp" +#include "IO/Archive/ArchiveReaderIterator.hpp" +#include "IO/Archive/ArchiveWriter.hpp" +#include "IO/AppFolders.hpp" + +#include +#include +#include +#include + +using namespace OpenVulkano; + +namespace +{ + std::filesystem::path iteratorTestArchiveDir() + { + return AppFolders::GetAppTempDir(); + } + + std::filesystem::path iteratorTestArchivePath() + { + return iteratorTestArchiveDir() / "test_iterator_archive.zip"; + } + + void makeIteratorTestArchive() + { + ArchiveWriter writer(iteratorTestArchivePath()); + + // Add multiple files for iteration testing + std::vector> files = { + {"file1.txt", "Content of file 1"}, + {"file2.txt", "Content of file 2"}, + {"subdir/file3.txt", "Content of file 3"}, + {"subdir/file4.txt", "Content of file 4"}, + {"readme.md", "# Test Archive"} + }; + + for (const auto& [path, content]: files) + { + FileDescription desc = FileDescription::MkFile(path, content.size()); + writer.AddFile(desc, content.data()); + } + } +} + +TEST_CASE("Iterator Basic Operations", "[ArchiveReaderIterator]") +{ + makeIteratorTestArchive(); + + SECTION("Begin and End") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto begin_it = reader.begin(); + auto end_it = reader.end(); + + REQUIRE(begin_it != end_it); + REQUIRE(end_it == reader.end()); + } + + SECTION("Dereference Operations") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto it = reader.begin(); + REQUIRE(it != reader.end()); + + // Test operator* + const auto& desc = *it; + REQUIRE(!desc.first.path.empty()); + REQUIRE(desc.first.size > 0); + REQUIRE(desc.first.type == std::filesystem::file_type::regular); + + // Test operator-> + REQUIRE(it->first.path == desc.first.path); + REQUIRE(it->first.size == desc.first.size); + } +} + +TEST_CASE("Iterator Traversal", "[ArchiveReaderIterator]") +{ + makeIteratorTestArchive(); + + SECTION("Pre-increment") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto it = reader.begin(); + auto first_path = it->first.path; + + ++it; + REQUIRE(it != reader.end()); + REQUIRE(it->first.path != first_path); + } + + SECTION("Post-increment") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto it = reader.begin(); + auto first_path = it->first.path; + + auto old_it = it++; + REQUIRE(old_it->first.path == first_path); + REQUIRE(it->first.path != first_path); + } + + SECTION("Complete Traversal") + { + ArchiveReader reader(iteratorTestArchivePath()); + int count = 0; + for (auto it = reader.begin(); it != reader.end(); ++it) + { + count++; + } + REQUIRE(count == 5); // We added 5 files in makeIteratorTestArchive + } +} + +TEST_CASE("Range-based For Loop", "[ArchiveReaderIterator]") +{ + makeIteratorTestArchive(); + + SECTION("Basic Range Loop") + { + ArchiveReader reader(iteratorTestArchivePath()); + std::vector paths; + for (const auto& entry: reader) + { + paths.push_back(entry.first.path); + } + + REQUIRE(paths.size() == 5); + REQUIRE(std::find(paths.begin(), paths.end(), "file1.txt") != paths.end()); + REQUIRE(std::find(paths.begin(), paths.end(), "readme.md") != paths.end()); + } + + SECTION("Const Iterator") + { + ArchiveReader reader(iteratorTestArchivePath()); + const ArchiveReader& const_reader = reader; + int count = 0; + + for (const auto& entry: const_reader) + { + REQUIRE(!entry.first.path.empty()); + count++; + } + + REQUIRE(count == 5); + } +} + +TEST_CASE("Iterator Edge Cases", "[ArchiveReaderIterator]") +{ + SECTION("Empty Archive") + { + // Create empty archive + std::filesystem::path emptyPath = iteratorTestArchiveDir() / "empty.zip"; + { + ArchiveWriter writer(emptyPath); + } + + ArchiveReader reader(emptyPath); + REQUIRE(reader.begin() == reader.end()); + + int count = 0; + for (const auto& entry : reader) + { + (void)entry; + count++; + } + REQUIRE(count == 0); + } + + SECTION("Iterator Invalidation") + { + makeIteratorTestArchive(); + + ArchiveReader reader(iteratorTestArchivePath()); + + auto it = reader.begin(); + REQUIRE(it != reader.end()); + + // Advance to end + while (it != reader.end()) + { + ++it; + } + + // Further increment should not crash + ++it; + REQUIRE(it == reader.end()); + } +} + +TEST_CASE("Iterator with STL Algorithms", "[ArchiveReaderIterator]") +{ + makeIteratorTestArchive(); + + SECTION("std::distance") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto distance = std::distance(reader.begin(), reader.end()); + REQUIRE(distance == 5); + } + + SECTION("std::count_if") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto txt_count = std::count_if(reader.begin(), reader.end(), + [](const auto& desc) + { return desc.first.path.find(".txt") != std::string::npos; }); + REQUIRE(txt_count == 4); + } + + SECTION("std::find_if") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto it = std::find_if(reader.begin(), reader.end(), + [](const auto& desc) + { return desc.first.path == "readme.md"; }); + + REQUIRE(it != reader.end()); + REQUIRE(it->first.path == "readme.md"); + } +} + +TEST_CASE("Iterator Copy and Assignment", "[ArchiveReaderIterator]") +{ + makeIteratorTestArchive(); + + SECTION("Copy Constructor") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto it1 = reader.begin(), it2 = it1; + REQUIRE(it1 == it2); + REQUIRE(it1->first.path == it2->first.path); + } + + SECTION("Assignment Operator") + { + ArchiveReader reader(iteratorTestArchivePath()); + auto it1 = reader.begin(), it2 = reader.end(); + it2 = it1; + REQUIRE(it1 == it2); + REQUIRE(it2 != reader.end()); + } +}