Avoid copying around the entire file table so that the speed doesn't decrease as the archive has more files.
345 lines
10 KiB
C++
345 lines
10 KiB
C++
#include <fstream>
|
|
#include <unordered_set>
|
|
#include <ReArchive/ReArchive.h>
|
|
#include <ReArchive/types/Header.h>
|
|
#include <ReArchive/types/FileTable.h>
|
|
#include <ReArchive/types/FileEntry.h>
|
|
#include <cassert>
|
|
|
|
using ReArchive::Header;
|
|
using ReArchive::FileTable;
|
|
using ReArchive::FileEntry;
|
|
|
|
std::unordered_set<std::filesystem::path> locked {};
|
|
|
|
Header GetHeader(const unsigned char* archive) {
|
|
ReArchive::Header h = ReArchive::Header::DeSerialize(archive);
|
|
return h;
|
|
}
|
|
|
|
/// @param header our header.
|
|
/// @param in Our input stream to the file.
|
|
/// @note Does not close the input stream.
|
|
FileTable GetFileTable(const Header& header, std::ifstream& in) {
|
|
in.seekg(0, std::ios::end);
|
|
int64_t file_table_size = in.tellg() - header.FileTableOffset();
|
|
in.seekg(header.FileTableOffset(), std::ios::beg);
|
|
|
|
std::vector<unsigned char> buffer(file_table_size);
|
|
in.read(reinterpret_cast<char *>(buffer.data()), buffer.size());
|
|
|
|
unsigned char* ptr = buffer.data();
|
|
int64_t file_table_entry_count = be64toh(*reinterpret_cast<int64_t*>(ptr));
|
|
ptr += sizeof(int64_t);
|
|
|
|
FileTable result;
|
|
for (int64_t i = 0; i < file_table_entry_count; i++) {
|
|
// Out of bounds.
|
|
assert(ptr < (buffer.data() + buffer.size()));
|
|
|
|
int64_t string_size = be64toh(*reinterpret_cast<const int64_t*>(ptr));
|
|
ptr += sizeof(int64_t);
|
|
|
|
std::string path(reinterpret_cast<const char*>(ptr), string_size);
|
|
ptr += string_size;
|
|
|
|
int64_t data_size = be64toh(*reinterpret_cast<const int64_t*>(ptr));
|
|
ptr += sizeof(int64_t);
|
|
|
|
int64_t data_offset = be64toh(*reinterpret_cast<const int64_t*>(ptr));
|
|
ptr += sizeof(int64_t);
|
|
|
|
result.Append({ data_size, data_offset, path });
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::pair<bool, FileTable> ReArchive::ReadFileTable(const std::filesystem::path& archive) {
|
|
if (!std::filesystem::exists(archive))
|
|
return {false, {}};
|
|
|
|
// Busy-wait.
|
|
while (locked.contains(archive)) {}
|
|
locked.insert(archive);
|
|
|
|
std::ifstream in(archive, std::ios::binary);
|
|
if (!in)
|
|
return {false, {}};
|
|
|
|
in.seekg(0, std::ios::end);
|
|
if (in.tellg() < Header::Size())
|
|
return {false, {}};
|
|
in.seekg(0, std::ios::beg);
|
|
|
|
std::vector<unsigned char> buffer (Header::Size());
|
|
in.read(reinterpret_cast<char *>(buffer.data()), (int64_t) buffer.size());
|
|
if (buffer[0] != 'R' || buffer[1] != 'S' || buffer[2] != 'A')
|
|
return {false, {}};
|
|
|
|
auto header = GetHeader(buffer.data());
|
|
auto file_table = GetFileTable(header, in);
|
|
in.close();
|
|
|
|
// Remove lock.
|
|
auto position = locked.find(archive);
|
|
if (position != locked.end())
|
|
locked.erase(position);
|
|
|
|
return {true, file_table};
|
|
}
|
|
|
|
bool ReArchive::CreateArchive(const std::filesystem::path& filesystem_path, bool use_compression, FileTable* running_tally) {
|
|
if (std::filesystem::exists(filesystem_path))
|
|
return false;
|
|
|
|
while (locked.contains(filesystem_path)) {}
|
|
locked.insert(filesystem_path);
|
|
|
|
std::ofstream file(filesystem_path, std::ios::binary);
|
|
if (!file)
|
|
return false;
|
|
|
|
auto serialized_file_header = Header::Serialize(Header(use_compression, Header::Size()));
|
|
auto file_table = FileTable();
|
|
auto serialized_file_table = FileTable::Serialize(file_table);
|
|
|
|
file.write(reinterpret_cast<const char*>(serialized_file_header.data()), (int64_t) serialized_file_header.size());
|
|
file.write(reinterpret_cast<const char*>(serialized_file_table.data()), (int64_t) serialized_file_table.size());
|
|
file.close();
|
|
|
|
if (running_tally)
|
|
*running_tally = file_table;
|
|
|
|
// Remove lock.
|
|
auto position = locked.find(filesystem_path);
|
|
if (position != locked.end())
|
|
locked.erase(position);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ReArchive::WriteFile(const std::filesystem::path& archive, const std::filesystem::path& file_path, const unsigned char* file_data, const int64_t& byte_count, FileTable* running_tally) {
|
|
if (!std::filesystem::exists(archive))
|
|
return false;
|
|
|
|
// Busy-wait.
|
|
while (locked.contains(archive)) {}
|
|
locked.insert(archive);
|
|
|
|
std::ifstream in(archive, std::ios::binary);
|
|
if (!in)
|
|
return false;
|
|
|
|
in.seekg(0, std::ios::end);
|
|
if (in.tellg() < Header::Size())
|
|
return false;
|
|
in.seekg(0, std::ios::beg);
|
|
|
|
std::vector<unsigned char> buffer (Header::Size());
|
|
in.read(reinterpret_cast<char *>(buffer.data()), (int64_t) buffer.size());
|
|
if (buffer[0] != 'R' || buffer[1] != 'S' || buffer[2] != 'A')
|
|
return false;
|
|
|
|
auto header = GetHeader(buffer.data());
|
|
auto file_table = GetFileTable(header, in);
|
|
|
|
if (file_table.Contains(file_path))
|
|
return false;
|
|
|
|
in.close();
|
|
|
|
std::ofstream out(archive, std::ios::binary | std::ios::out | std::ios::in);
|
|
if (!out)
|
|
return false;
|
|
|
|
out.seekp(header.FileTableOffset(), std::ios::beg);
|
|
out.write(reinterpret_cast<const char *>(file_data), byte_count);
|
|
|
|
file_table.Append(FileEntry(byte_count, header.FileTableOffset(), file_path));
|
|
header.FileTableOffset(out.tellp());
|
|
|
|
auto new_file_table = FileTable::Serialize(file_table);
|
|
out.write(reinterpret_cast<const char *>(new_file_table.data()), (int64_t) new_file_table.size());
|
|
|
|
auto new_header = Header::Serialize(header);
|
|
out.seekp(0, std::ios::beg);
|
|
out.write(reinterpret_cast<const char *>(new_header.data()), (int64_t) new_header.size());
|
|
out.close();
|
|
|
|
// Remove lock.
|
|
auto position = locked.find(archive);
|
|
if (position != locked.end())
|
|
locked.erase(position);
|
|
|
|
if (running_tally)
|
|
*running_tally = file_table;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ReArchive::OverwriteFile(const std::filesystem::path& archive, const std::filesystem::path& file_path, const unsigned char* file_data, const int64_t& byte_count) {
|
|
if (!std::filesystem::exists(archive))
|
|
return false;
|
|
|
|
// Busy-wait.
|
|
while (locked.contains(archive)) {}
|
|
locked.insert(archive);
|
|
|
|
std::ifstream in(archive, std::ios::binary);
|
|
if (!in)
|
|
return false;
|
|
|
|
in.seekg(0, std::ios::end);
|
|
if (in.tellg() < Header::Size())
|
|
return false;
|
|
in.seekg(0, std::ios::beg);
|
|
|
|
std::vector<unsigned char> buffer (Header::Size());
|
|
in.read(reinterpret_cast<char *>(buffer.data()), (int64_t) buffer.size());
|
|
if (buffer[0] != 'R' || buffer[1] != 'S' || buffer[2] != 'A')
|
|
return false;
|
|
|
|
auto header = GetHeader(buffer.data());
|
|
auto file_table = GetFileTable(header, in);
|
|
|
|
const FileEntry* target = nullptr;
|
|
auto file_entries = file_table.GetEntries();
|
|
|
|
auto value = file_entries->find(file_path);
|
|
if (value != file_entries->end())
|
|
target = &value->second;
|
|
|
|
if (!target)
|
|
return false;
|
|
|
|
if (byte_count != target->Size())
|
|
return false;
|
|
|
|
in.close();
|
|
std::ofstream out(archive, std::ios::binary | std::ios::out | std::ios::in);
|
|
if (!out)
|
|
return false;
|
|
|
|
out.seekp(target->Offset(), std::ios::beg);
|
|
out.write(reinterpret_cast<const char *>(file_data), byte_count);
|
|
out.close();
|
|
|
|
// Remove lock.
|
|
auto position = locked.find(archive);
|
|
if (position != locked.end())
|
|
locked.erase(position);
|
|
|
|
return true;
|
|
}
|
|
|
|
std::vector<unsigned char> ReArchive::ReadFile(const std::filesystem::path& archive, const std::filesystem::path& file_path) {
|
|
if (!std::filesystem::exists(archive))
|
|
return {};
|
|
|
|
// Busy-wait.
|
|
while (locked.contains(archive)) {}
|
|
locked.insert(archive);
|
|
|
|
std::ifstream in(archive, std::ios::binary);
|
|
if (!in)
|
|
return {};
|
|
|
|
in.seekg(0, std::ios::end);
|
|
if (in.tellg() < Header::Size())
|
|
return {};
|
|
in.seekg(0, std::ios::beg);
|
|
|
|
std::vector<unsigned char> buffer (Header::Size());
|
|
in.read(reinterpret_cast<char *>(buffer.data()), (int64_t) buffer.size());
|
|
if (buffer[0] != 'R' || buffer[1] != 'S' || buffer[2] != 'A')
|
|
return {};
|
|
|
|
auto header = GetHeader(buffer.data());
|
|
auto file_table = GetFileTable(header, in);
|
|
|
|
const FileEntry* target = nullptr;
|
|
auto file_entries = file_table.GetEntries();
|
|
|
|
auto value = file_entries->find(file_path);
|
|
if (value != file_entries->end())
|
|
target = &value->second;
|
|
|
|
if (!target)
|
|
return {};
|
|
|
|
std::vector<unsigned char> result(target->Size());
|
|
in.seekg(target->Offset(), std::ios::beg);
|
|
in.read(reinterpret_cast<char*>(result.data()), (int64_t) result.size());
|
|
in.close();
|
|
|
|
// Remove lock.
|
|
auto position = locked.find(archive);
|
|
if (position != locked.end())
|
|
locked.erase(position);
|
|
|
|
return result;
|
|
}
|
|
|
|
// I tried to do this several different ways but this seems to be the best approach - Redacted.
|
|
bool ReArchive::EraseFile(const std::filesystem::path& archive, const std::filesystem::path& file_path, FileTable* running_tally) {
|
|
|
|
if (!std::filesystem::exists(archive))
|
|
return false;
|
|
|
|
// Busy-wait.
|
|
while (locked.contains(archive)) {}
|
|
|
|
std::ifstream in(archive, std::ios::binary);
|
|
if (!in)
|
|
return false;
|
|
|
|
in.seekg(0, std::ios::end);
|
|
int64_t file_size = in.tellg();
|
|
if (file_size < Header::Size())
|
|
return false;
|
|
in.seekg(0, std::ios::beg);
|
|
|
|
std::vector<unsigned char> buffer (Header::Size());
|
|
in.read(reinterpret_cast<char *>(buffer.data()), (int64_t) buffer.size());
|
|
if (buffer[0] != 'R' || buffer[1] != 'S' || buffer[2] != 'A')
|
|
return false;
|
|
|
|
auto current_header = GetHeader(buffer.data());
|
|
auto current_file_table = GetFileTable(current_header, in);
|
|
|
|
// TODO randomize the name more than that.
|
|
if (!CreateArchive(archive.string() + ".tmp", current_header.Compressed()))
|
|
return false;
|
|
|
|
auto file_entries = current_file_table.GetEntries();
|
|
auto value = file_entries->find(file_path);
|
|
if (value != file_entries->end())
|
|
file_entries->erase(value);
|
|
|
|
for (auto& e : *file_entries) {
|
|
auto file_buffer = ReadFile(archive, e.first);
|
|
if (!WriteFile(archive.string() + ".tmp", e.first, file_buffer.data(), (int64_t) file_buffer.size())) {
|
|
std::filesystem::remove(archive.string() + ".tmp");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Busy-wait.
|
|
while (locked.contains(archive)) {}
|
|
locked.insert(archive);
|
|
|
|
std::filesystem::remove(archive);
|
|
std::filesystem::rename(archive.string() + ".tmp", archive);
|
|
|
|
// Remove lock.
|
|
auto position = locked.find(archive);
|
|
if (position != locked.end())
|
|
locked.erase(position);
|
|
|
|
if (running_tally) {
|
|
auto result = ReadFileTable(archive);
|
|
if (result.first)
|
|
*running_tally = result.second;
|
|
}
|
|
return true;
|
|
} |