From ff972b194ff36252e9974327f66f05ea394fd704 Mon Sep 17 00:00:00 2001 From: Aki Date: Thu, 11 Aug 2022 21:46:05 +0200 Subject: Implemented inserting entries into archive --- ArchiveEx/Archive.cpp | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++ ArchiveEx/Archive.h | 3 ++ 2 files changed, 131 insertions(+) diff --git a/ArchiveEx/Archive.cpp b/ArchiveEx/Archive.cpp index 0a99982..68375f2 100644 --- a/ArchiveEx/Archive.cpp +++ b/ArchiveEx/Archive.cpp @@ -113,6 +113,114 @@ Archive::Find(const char* filepath) const } +int +Archive::Update(const char* filepath) +{ + const std::size_t namelen = std::strlen(filepath); + if (NAMELEN <= namelen) + return -1; + UniqueFileHandle source {std::fopen(filepath, "rb"), &std::fclose}; + int err = std::fseek(source.get(), 0, SEEK_END); + if (-1 == err) + return -1; + const std::size_t uncompressed_size = std::ftell(source.get()); + err = std::fseek(source.get(), 0, SEEK_SET); + if (-1 == err || 0 == uncompressed_size) + return -1; + auto uncompressed = std::make_unique(uncompressed_size); + const auto read_result = std::fread(uncompressed.get(), 1, uncompressed_size, source.get()); + if (read_result != uncompressed_size) + return -1; + source.reset(nullptr); + uLongf compressed_size = uncompressed_size * 1.1 + 12; + auto compressed = std::make_unique(compressed_size); + err = compress(compressed.get(), &compressed_size, uncompressed.get(), uncompressed_size); + if (Z_OK != err) + return -1; + uncompressed.reset(); + int index; + const int previous_index = Find(filepath); + if (-1 != previous_index) { + for (auto& block : blocks) { + if (previous_index == block) + block = UNUSED_BLOCK; + } + index = previous_index; + } + else { + index = entries.size(); + entries.emplace_back(); + } + const auto destination_block = FindFreeSpot(compressed_size); + const auto total_blocks = BytesToBlocks(compressed_size); + if (blocks.size() < destination_block + total_blocks) + blocks.resize(destination_block + total_blocks, UNUSED_BLOCK); + for (std::size_t i = destination_block; i < destination_block + total_blocks; ++i) + blocks[i] = index; + std::strcpy(entries[index].name, filepath); + entries[index].original_size = uncompressed_size; + entries[index].compressed_size = compressed_size; + entries[index].offset = destination_block * BLOCK_SIZE; + UniqueFileHandle archive {std::fopen(path, "rb+"), &std::fclose}; + err = std::fseek(archive.get(), sizeof(Header) + entries[index].offset, SEEK_SET); + if (-1 == err) + return -1; + const auto write_result = std::fwrite(compressed.get(), 1, compressed_size, archive.get()); + if (write_result != compressed_size) + return -1; + archive.reset(nullptr); + WriteMeta(); + return index; +} + + +void +Archive::WriteMeta() +{ + const std::size_t total_entries = entries.size(); + const std::size_t dirsize = total_entries + DIRECTORY_MARGIN; + const std::size_t uncompressed_size = dirsize * sizeof(Entry); + entries.reserve(dirsize); + uLongf compressed_size = uncompressed_size * 1.1 + 12; + auto compressed = std::make_unique(compressed_size); + int err = compress( + compressed.get(), + &compressed_size, + reinterpret_cast(entries.data()), + uncompressed_size); + if (Z_OK != err) + throw "could not compress directory metadata"; + for (auto& block : blocks) { + if (DIRECTORY_BLOCK == block) + block = UNUSED_BLOCK; + } + const auto destination_block = FindFreeSpot(compressed_size); + const auto total_blocks = BytesToBlocks(compressed_size); + if (blocks.size() < destination_block + total_blocks) + blocks.resize(destination_block + total_blocks, UNUSED_BLOCK); + for (std::size_t i = destination_block; i < destination_block + total_blocks; ++i) + blocks[i] = DIRECTORY_BLOCK; + const std::size_t offset = destination_block * BLOCK_SIZE; + header.total_entries = total_entries; + header.directory.total_blocks = total_blocks; // Unused + header.directory.compressed_size = compressed_size; + header.directory.offset = offset; + UniqueFileHandle archive {std::fopen(path, "rb+"), &std::fclose}; + err = std::fseek(archive.get(), 0, SEEK_SET); + if (-1 == err) + throw "could not write header (seek)"; + err = std::fwrite(&header, sizeof(Header), 1, archive.get()); + if (1 != err) + throw "could not write header (write)"; + err = std::fseek(archive.get(), sizeof(Header) + offset, SEEK_SET); + if (-1 == err) + throw "could not write directory (seek)"; + err = std::fwrite(compressed.get(), 1, compressed_size, archive.get()); + if (static_cast(compressed_size) != err) + throw "could not write directory (write)"; +} + + void Archive::UpdateBlockMap() { @@ -136,6 +244,26 @@ Archive::UpdateBlockMap() } +std::size_t +Archive::FindFreeSpot(const std::size_t bytes) const +{ + const std::size_t blocks_needed = BytesToBlocks(bytes); + std::size_t index = 1; + std::size_t so_far = 0; + for (; index <= blocks.size(); ++index) { + if (UNUSED_BLOCK == blocks[index - 1]) + so_far++; + else + so_far = 0; + if (blocks_needed <= so_far) + break; + } + if (blocks.size() < index) + index = blocks.size(); + return index - so_far; +} + + void Archive::PrintNamesOfEntries() const { diff --git a/ArchiveEx/Archive.h b/ArchiveEx/Archive.h index e19c718..5330aea 100644 --- a/ArchiveEx/Archive.h +++ b/ArchiveEx/Archive.h @@ -40,7 +40,10 @@ public: int Expand(int index, std::uint8_t*& buffer, bool null_terminated=false) const; int Expand(const char* filepath, std::uint8_t*& buffer, bool null_terminated=false) const; int Find(const char* filepath) const; + int Update(const char* filepath); + void WriteMeta(); void UpdateBlockMap(); + std::size_t FindFreeSpot(std::size_t bytes) const; void PrintNamesOfEntries() const; void PrintBlocks() const; std::size_t DirectoryBlocks() const; -- cgit v1.1