Files
OpenVulkano/openVulkanoCpp/IO/Archive/ArchiveWriter.cpp
Georg Hagen 8716857efe Add archive writer support for existing c FILE*
also deduplicate some code
2025-05-19 20:52:29 +02:00

192 lines
6.8 KiB
C++

/*
* 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 "ArchiveWriter.hpp"
#include "LibArchiveHelper.hpp"
#include "ArchiveStreamBufferWriter.hpp"
#include "SimpleFileTypeShouldCompressChecker.hpp"
#include <archive.h>
#include <archive_entry.h>
#include <iostream>
#include <units.h>
namespace OpenVulkano
{
ArchiveWriter::ArchiveWriter(ArchiveConfiguration archiveConfiguration, const std::shared_ptr<spdlog::logger>& logger, const char* password)
: ArchiveBase(archive_write_new(), archive_entry_new(), logger)
, m_archiveConfig(archiveConfiguration)
, m_shouldCompress(&SimpleFileTypeShouldCompressChecker)
{ //TODO better error handling
ChkErr(archive_write_set_format(m_archive, archiveConfiguration.GetLibArchiveArchiveType()));
if (archiveConfiguration.type == ArchiveType::TAR)
{
ChkErr(archive_write_add_filter(m_archive, archiveConfiguration.GetLibArchiveCompressionType()));
}
if (archiveConfiguration.compression != CompressionType::NONE &&
archiveConfiguration.compressionLevel > 0)
{
std::string level = "compression-level=" + std::to_string(archiveConfiguration.compressionLevel);
ChkErr(archive_write_set_options(m_archive, level.c_str()));
}
if (password) ChkErr(archive_write_set_passphrase(m_archive, password));
}
ArchiveWriter::ArchiveWriter(const char* fileName, ArchiveConfiguration archiveConfiguration, const std::shared_ptr<spdlog::logger>& logger, const char* password)
: ArchiveWriter(archiveConfiguration, logger, password)
{
ChkErr(archive_write_open_filename(m_archive, fileName));
}
ArchiveWriter::ArchiveWriter(const std::filesystem::path& fileName, ArchiveConfiguration archiveConfiguration, const std::shared_ptr<spdlog::logger>& logger, const char* password)
: ArchiveWriter(archiveConfiguration, logger, password)
{
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnreachableCode"
if constexpr (std::is_same_v<std::filesystem::path::value_type, char>)
ChkErr(archive_write_open_filename(m_archive, (const char*)fileName.c_str()));
else
ChkErr(archive_write_open_filename_w(m_archive, (const wchar_t*)fileName.c_str()));
#pragma clang diagnostic pop
}
namespace
{
static int LibArchiveFileOpen(struct archive* a, void* clientData)
{
(void)clientData; /* UNUSED */
archive_write_set_bytes_in_last_block(a, 1);
return (ARCHIVE_OK);
}
static ssize_t LibArchiveFileWrite(struct archive* a, void* clientData, const void* buff, size_t length)
{
FILE* file = (FILE*)clientData;
size_t bytesWritten;
while (true)
{
bytesWritten = fwrite(buff, 1, length, file);
if (bytesWritten <= 0)
{
if (errno == EINTR) continue;
archive_set_error(a, errno, "Write error");
return (-1);
}
return (bytesWritten);
}
}
}
ArchiveWriter::ArchiveWriter(FILE* file, ArchiveConfiguration archiveConfiguration, const std::shared_ptr<spdlog::logger>& logger, const char* password)
: ArchiveWriter(archiveConfiguration, logger, password)
{
ChkErr(archive_write_open2(m_archive, file, LibArchiveFileOpen, LibArchiveFileWrite, NULL, NULL));
}
ArchiveWriter::~ArchiveWriter()
{
if (m_asBuffer) { m_asBuffer->Close(); m_asBuffer = nullptr; }
ChkErr(archive_write_close(m_archive));
archive_entry_free(m_archiveEntry);
}
bool ArchiveWriter::AddFile(const char* fileName, const char* inArchiveName)
{
archive_entry_clear(m_archiveEntry);
std::unique_ptr<archive, decltype(archive_free)*> archiveDisk(archive_read_disk_new(), archive_free);
LibArchiveHelper::CheckError(archive_read_disk_open(archiveDisk.get(), fileName), archiveDisk.get(), m_logger);
LibArchiveHelper::CheckError(archive_read_next_header2(archiveDisk.get(), m_archiveEntry), archiveDisk.get(), m_logger);
//archive_read_disk_entry_from_file(archiveDisk, m_archiveEntry, -1, nullptr);
archive_entry_set_pathname(m_archiveEntry, inArchiveName);
ChkErr(archive_write_header(m_archive, m_archiveEntry));
long result = LibArchiveHelper::CopyArchiveData(archiveDisk.get(), m_archive);
ChkErr(result);
LibArchiveHelper::CheckError(result, archiveDisk.get(), m_logger);
ChkErr(archive_write_finish_entry(m_archive));
m_bytesWritten += static_cast<size_t>(archive_entry_size(m_archiveEntry));
return true;
}
bool ArchiveWriter::AddFile(const FileDescription& description, const void* buffer)
{
WriteHeader(description);
bool ok = ChkErr(archive_write_data(m_archive, buffer, description.size));
ChkErr(archive_write_finish_entry(m_archive));
return ok;
}
bool ArchiveWriter::AddFile(const FileDescription& description, const std::vector<std::pair<const void*, size_t>>& buffers)
{
WriteHeader(description);
for(const auto& buffer : buffers)
{
if (buffer.first == nullptr) continue;
if (!ChkErr(archive_write_data(m_archive, buffer.first, buffer.second)))
{
return false;
}
}
ChkErr(archive_write_finish_entry(m_archive));
return true;
}
ArchiveOStream ArchiveWriter::AddFileStream(const FileDescription& description)
{
WriteHeader(description);
return ArchiveOStream(m_asBuffer = std::make_shared<ArchiveStreamBufferWriter>(this, description.size));
}
void ArchiveWriter::WriteHeader(const FileDescription& fileDescription)
{
if (m_asBuffer) { m_asBuffer->Close(); m_asBuffer = nullptr; }
if (m_archiveConfig.type.AllowsMixedCompressionLevels())
{
if (m_shouldCompress(fileDescription)) SetCompressed();
else SetUncompressed();
}
archive_entry_clear(m_archiveEntry);
archive_entry_set_pathname_utf8(m_archiveEntry, fileDescription.path.c_str());
if (fileDescription.size != FileDescription::UNKNOWN_SIZE)
{
archive_entry_set_size(m_archiveEntry, fileDescription.size);
}
else
{
if (!m_archiveConfig.type.AllowsUnknownFileSize())
throw std::invalid_argument("Only ZIP files allow files of unknown size");
archive_entry_unset_size(m_archiveEntry);
}
archive_entry_set_filetype(m_archiveEntry, LibArchiveHelper::GetFileType(fileDescription.type));
archive_entry_set_perm(m_archiveEntry, static_cast<mode_t>(fileDescription.permissions));
archive_entry_set_ctime(m_archiveEntry, fileDescription.createTime, 0);
archive_entry_set_mtime(m_archiveEntry, fileDescription.modTime, 0);
ChkErr(archive_write_header(m_archive, m_archiveEntry));
m_bytesWritten += fileDescription.size;
}
void ArchiveWriter::SetUncompressed()
{
if (!m_lastCompressed) return;
m_lastCompressed = false;
switch (m_archiveConfig.type)
{
case ArchiveType::ZIP: ChkErr(archive_write_zip_set_compression_store(m_archive));
default: ;
}
}
void ArchiveWriter::SetCompressed()
{
if (m_lastCompressed) return;
m_lastCompressed = true;
switch (m_archiveConfig.type)
{
case ArchiveType::ZIP: ChkErr(archive_write_zip_set_compression_deflate(m_archive));
default: ;
}
}
}