Add ArchiveReaderIterator

This commit is contained in:
Georg Hagen
2025-05-22 21:50:45 +02:00
parent ee2a579450
commit 3d56c6a410
6 changed files with 397 additions and 31 deletions

View File

@@ -59,7 +59,7 @@ namespace OpenVulkano::AR::Playback
int GetNextFrameId() 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)); return std::stoi(name.substr(0, name.length() - 5));
} }

View File

@@ -205,16 +205,6 @@ namespace OpenVulkano
return archive_read_support_format_all; 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 ArchiveReader::ExtractRemaining(const std::filesystem::path &targetDir)
{ {
size_t count = 0; size_t count = 0;
@@ -258,7 +248,7 @@ namespace OpenVulkano
return HasNext(); return HasNext();
} }
FileDescription ArchiveReader::GetNextDescription() const FileDescription ArchiveReader::GetDescription() const
{ {
return { return {
LibArchiveHelper::GetFileType(archive_entry_filetype(m_archiveEntry)), LibArchiveHelper::GetFileType(archive_entry_filetype(m_archiveEntry)),
@@ -270,11 +260,21 @@ namespace OpenVulkano
}; };
} }
std::pair<FileDescription, Array<char>> ArchiveReader::GetNext()
{
if (!HasNext()) throw std::runtime_error("Reached end of file!");
std::pair<FileDescription, Array<char>> file = {GetDescription(), Array<char>() };
file.second = Array<char>(file.first.size);
ChkErr(archive_read_data(m_archive, file.second.Data(), file.second.Size()));
ReadNextHeader();
return file;
}
std::optional<FileDescription> ArchiveReader::GetNextDirectory() std::optional<FileDescription> ArchiveReader::GetNextDirectory()
{ {
if (SkipTill(std::filesystem::file_type::directory)) if (SkipTill(std::filesystem::file_type::directory))
{ {
return GetNextDescription(); return GetDescription();
} }
return std::nullopt; return std::nullopt;
} }
@@ -283,7 +283,7 @@ namespace OpenVulkano
{ {
if (SkipTill(std::filesystem::file_type::regular)) if (SkipTill(std::filesystem::file_type::regular))
{ {
FileDescription fileDescription = GetNextDescription(); FileDescription fileDescription = GetDescription();
std::filesystem::path outputFilePath; std::filesystem::path outputFilePath;
if (m_filePathRewrite) if (m_filePathRewrite)
outputFilePath = targetDir / m_filePathRewrite(fileDescription.path); outputFilePath = targetDir / m_filePathRewrite(fileDescription.path);
@@ -317,7 +317,7 @@ namespace OpenVulkano
{ {
if (SkipTill(std::filesystem::file_type::regular)) if (SkipTill(std::filesystem::file_type::regular))
{ {
std::pair<FileDescription, Array<char>> file = { GetNextDescription(), Array<char>() }; std::pair<FileDescription, Array<char>> file = {GetDescription(), Array<char>() };
file.second = Array<char>(file.first.size); file.second = Array<char>(file.first.size);
ChkErr(archive_read_data(m_archive, file.second.Data(), file.second.Size())); ChkErr(archive_read_data(m_archive, file.second.Data(), file.second.Size()));
ReadNextHeader(); ReadNextHeader();
@@ -330,7 +330,7 @@ namespace OpenVulkano
{ {
if (SkipTill(std::filesystem::file_type::regular)) if (SkipTill(std::filesystem::file_type::regular))
{ {
std::pair<FileDescription, std::vector<char>> file = { GetNextDescription(), std::vector<char>() }; std::pair<FileDescription, std::vector<char>> file = {GetDescription(), std::vector<char>() };
file.second.resize(file.first.size); file.second.resize(file.first.size);
ChkErr(archive_read_data(m_archive, file.second.data(), file.second.size())); ChkErr(archive_read_data(m_archive, file.second.data(), file.second.size()));
ReadNextHeader(); ReadNextHeader();
@@ -359,7 +359,7 @@ namespace OpenVulkano
{ {
if (SkipTill(std::filesystem::file_type::regular)) if (SkipTill(std::filesystem::file_type::regular))
{ {
FileDescription fileDescription = GetNextDescription(); FileDescription fileDescription = GetDescription();
LibArchiveEntryStreamBuffer streamBuffer(m_archive); LibArchiveEntryStreamBuffer streamBuffer(m_archive);
std::istream stream(&streamBuffer); std::istream stream(&streamBuffer);
streamReader(fileDescription, stream); streamReader(fileDescription, stream);
@@ -381,7 +381,7 @@ namespace OpenVulkano
ChkErr(r); ChkErr(r);
stream.write(static_cast<const char*>(buffer), size); stream.write(static_cast<const char*>(buffer), size);
} }
FileDescription fileDescription = GetNextDescription(); FileDescription fileDescription = GetDescription();
ReadNextHeader(); ReadNextHeader();
return fileDescription; return fileDescription;
} }

View File

@@ -7,6 +7,7 @@
#pragma once #pragma once
#include "ArchiveBase.hpp" #include "ArchiveBase.hpp"
#include "ArchiveReaderIterator.hpp"
#include "Data/Containers/Array.hpp" #include "Data/Containers/Array.hpp"
#include "ArchiveType.hpp" #include "ArchiveType.hpp"
#include <string_view> #include <string_view>
@@ -54,8 +55,6 @@ namespace OpenVulkano
[[nodiscard]] bool IsOpen() const { return m_open; } [[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);
size_t ExtractRemaining(const std::filesystem::path& targetDir, const std::function<void(const FileDescription&)>& extractionCallback); size_t ExtractRemaining(const std::filesystem::path& targetDir, const std::function<void(const FileDescription&)>& extractionCallback);
@@ -67,29 +66,42 @@ namespace OpenVulkano
bool SkipTill(std::filesystem::file_type type = std::filesystem::file_type::regular); bool SkipTill(std::filesystem::file_type type = std::filesystem::file_type::regular);
[[nodiscard]] FileDescription GetNextDescription() const; [[nodiscard]] FileDescription GetDescription() const;
std::optional<FileDescription> GetNextDirectory(); [[nodiscard]] std::pair<FileDescription, Array<char>> GetNext();
[[nodiscard]] std::optional<FileDescription> GetNextDirectory();
[[deprecated]] std::optional<FileDescription> ExtractNext(std::string_view targetDir) { return ExtractNext(std::filesystem::path(targetDir)); } [[deprecated]] std::optional<FileDescription> ExtractNext(std::string_view targetDir) { return ExtractNext(std::filesystem::path(targetDir)); }
std::optional<FileDescription> ExtractNext(const std::filesystem::path& targetDir);
std::optional<std::pair<FileDescription, Array<char>>> GetNextFile(); [[nodiscard]] std::optional<FileDescription> ExtractNext(const std::filesystem::path& targetDir);
std::optional<std::pair<FileDescription, std::vector<char>>> GetNextFileAsVector(); [[nodiscard]] std::optional<std::pair<FileDescription, Array<char>>> GetNextFile();
std::optional<std::pair<FileDescription, Array<char>>> GetFile(const std::string& path); [[nodiscard]] std::optional<std::pair<FileDescription, std::vector<char>>> GetNextFileAsVector();
std::optional<FileDescription> StreamNextFile(std::ostream& stream); [[nodiscard]] std::optional<std::pair<FileDescription, Array<char>>> GetFile(const std::string& path);
[[nodiscard]] std::optional<FileDescription> StreamNextFile(std::ostream& stream);
bool GetNextFileAsStream(const std::function<void(const FileDescription&, std::istream&)>& streamReader); bool GetNextFileAsStream(const std::function<void(const FileDescription&, std::istream&)>& 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; } 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<ArchiveReader*>(this)); }
[[nodiscard]] const_iterator end() const { return ArchiveReaderIterator(nullptr); }
[[nodiscard]] const_iterator cbegin() const { return begin(); }
[[nodiscard]] const_iterator cend() const { return end(); }
private: private:
void ReadNextHeader(); void ReadNextHeader();

View File

@@ -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;
}
}

View File

@@ -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 <iterator>
#include <memory>
namespace OpenVulkano
{
class ArchiveReader;
class ArchiveReaderIterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = std::pair<FileDescription, Array<char>>;
using difference_type = std::ptrdiff_t;
using pointer = const value_type*;
using reference = const value_type&;
private:
ArchiveReader* m_reader;
std::pair<FileDescription, Array<char>> 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);
}
};
}

View File

@@ -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 <catch2/catch_all.hpp>
#include "IO/Archive/ArchiveReader.hpp"
#include "IO/Archive/ArchiveReaderIterator.hpp"
#include "IO/Archive/ArchiveWriter.hpp"
#include "IO/AppFolders.hpp"
#include <filesystem>
#include <string>
#include <vector>
#include <algorithm>
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<std::pair<std::string, std::string>> 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<std::string> 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());
}
}