Refactored namespaces
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ArchiveZipper.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring> // strerror
|
||||
#include <fstream>
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace lms::zip
|
||||
{
|
||||
std::unique_ptr<IZipper> createArchiveZipper(const EntryContainer& entries)
|
||||
{
|
||||
return std::make_unique<ArchiveZipper>(entries);
|
||||
}
|
||||
|
||||
class FileException : public Exception
|
||||
{
|
||||
public:
|
||||
FileException(const std::filesystem::path& p, std::string_view message)
|
||||
: Exception{ "File '" + p.string() + "': " + std::string {message} }
|
||||
{}
|
||||
|
||||
FileException(const std::filesystem::path& p, std::string_view message, int err)
|
||||
: Exception{ "File '" + p.string() + "': " + std::string {message} + ": " + ::strerror(err) }
|
||||
{}
|
||||
};
|
||||
|
||||
class ArchiveException : public Exception
|
||||
{
|
||||
public:
|
||||
ArchiveException(struct ::archive* arch)
|
||||
: Exception{ getError(arch) }
|
||||
{}
|
||||
|
||||
static std::string_view getError(struct ::archive* arch)
|
||||
{
|
||||
const char* str{ archive_error_string(arch) };
|
||||
if (!str)
|
||||
{
|
||||
static std::string unknownError{ "Unknown archive error" };
|
||||
return unknownError;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
void ArchiveZipper::ArchiveDeleter::operator()(struct ::archive* arch)
|
||||
{
|
||||
const int res{ ::archive_write_free(arch) };
|
||||
if (res != ARCHIVE_OK)
|
||||
LMS_LOG(UTILS, ERROR, "Failure while freeing archive control struct: " << std::string{ ::strerror(res) });
|
||||
}
|
||||
|
||||
void ArchiveZipper::ArchiveEntryDeleter::operator()(struct ::archive_entry* archEntry)
|
||||
{
|
||||
::archive_entry_free(archEntry);
|
||||
}
|
||||
|
||||
ArchiveZipper::ArchiveZipper(const EntryContainer& entries)
|
||||
: _entries{ entries }
|
||||
, _readBuffer(_readBufferSize, {})
|
||||
, _currentEntry{ std::cbegin(_entries) }
|
||||
{
|
||||
_archive = ArchivePtr{ ::archive_write_new() };
|
||||
if (!_archive)
|
||||
throw Exception{ "Cannot create archive control struct" };
|
||||
|
||||
auto archiveOpen{ [](struct ::archive*, void*)
|
||||
{
|
||||
return ARCHIVE_OK;
|
||||
} };
|
||||
|
||||
auto archiveWrite{ [](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t
|
||||
{
|
||||
ArchiveZipper* zipper {static_cast<ArchiveZipper*>(clientData)};
|
||||
return zipper->onWriteCallback(static_cast<const std::byte*>(buff), n);
|
||||
} };
|
||||
|
||||
auto archiveClose{ [](struct ::archive*, void*)
|
||||
{
|
||||
return ARCHIVE_OK;
|
||||
} };
|
||||
|
||||
if (::archive_write_set_bytes_per_block(_archive.get(), _writeBlockSize) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
// 1 => no padding for last block
|
||||
if (::archive_write_set_bytes_in_last_block(_archive.get(), 1) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
if (::archive_write_set_format_zip(_archive.get()) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
if (::archive_write_set_option(_archive.get(), "zip", "compression", "deflate") != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
int res{ ::archive_write_open(_archive.get(), this, archiveOpen, archiveWrite, archiveClose) };
|
||||
if (res != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
}
|
||||
|
||||
std::uint64_t ArchiveZipper::writeSome(std::ostream& output)
|
||||
{
|
||||
assert(!_currentOutputStream);
|
||||
|
||||
_currentOutputStream = &output;
|
||||
_bytesWrittenInCurrentOutputStream = 0;
|
||||
|
||||
while (_bytesWrittenInCurrentOutputStream == 0)
|
||||
{
|
||||
if (!_currentArchiveEntry)
|
||||
{
|
||||
if (_currentEntry == std::cend(_entries))
|
||||
{
|
||||
if (::archive_write_close(_archive.get()) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
_archive.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
_currentArchiveEntry = createArchiveEntry(*_currentEntry);
|
||||
_currentEntryOffset = 0;
|
||||
if (::archive_write_header(_archive.get(), _currentArchiveEntry.get()) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
}
|
||||
|
||||
if (writeSomeCurrentFileData())
|
||||
{
|
||||
// entry complete
|
||||
if (::archive_write_finish_entry(_archive.get()) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
_currentArchiveEntry.reset();
|
||||
_currentEntry++;
|
||||
}
|
||||
}
|
||||
|
||||
_currentOutputStream = nullptr;
|
||||
return _bytesWrittenInCurrentOutputStream;
|
||||
}
|
||||
|
||||
bool ArchiveZipper::isComplete() const
|
||||
{
|
||||
return !_archive;
|
||||
}
|
||||
|
||||
void ArchiveZipper::abort()
|
||||
{
|
||||
LMS_LOG(UTILS, DEBUG, "Aborting zip creation");
|
||||
if (_archive)
|
||||
{
|
||||
::archive_write_fail(_archive.get());
|
||||
_archive.reset();
|
||||
}
|
||||
}
|
||||
|
||||
static ::mode_t permsToMode(const std::filesystem::perms p)
|
||||
{
|
||||
using std::filesystem::perms;
|
||||
::mode_t mode{};
|
||||
|
||||
auto testPerm{ [](perms p, perms permToTest)
|
||||
{
|
||||
return (p & permToTest) == permToTest;
|
||||
} };
|
||||
|
||||
if (testPerm(p, perms::owner_read))
|
||||
mode |= S_IRUSR;
|
||||
if (testPerm(p, perms::owner_write))
|
||||
mode |= S_IWUSR;
|
||||
if (testPerm(p, perms::owner_exec))
|
||||
mode |= S_IXUSR;
|
||||
if (testPerm(p, perms::group_read))
|
||||
mode |= S_IRGRP;
|
||||
if (testPerm(p, perms::group_write))
|
||||
mode |= S_IWGRP;
|
||||
if (testPerm(p, perms::group_exec))
|
||||
mode |= S_IXGRP;
|
||||
if (testPerm(p, perms::others_read))
|
||||
mode |= S_IROTH;
|
||||
if (testPerm(p, perms::others_write))
|
||||
mode |= S_IWOTH;
|
||||
if (testPerm(p, perms::others_exec))
|
||||
mode |= S_IXOTH;
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
ArchiveZipper::ArchiveEntryPtr ArchiveZipper::createArchiveEntry(const Entry& entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!std::filesystem::is_regular_file(entry.filePath))
|
||||
throw FileException{ entry.filePath, "not a regular file" };
|
||||
|
||||
ArchiveEntryPtr archiveEntry{ archive_entry_new() };
|
||||
if (!archiveEntry)
|
||||
throw Exception{ "Cannot create archive entry control struct" };
|
||||
|
||||
archive_entry_set_pathname(archiveEntry.get(), entry.fileName.c_str());
|
||||
archive_entry_set_size(archiveEntry.get(), std::filesystem::file_size(entry.filePath));
|
||||
archive_entry_set_mode(archiveEntry.get(), permsToMode(std::filesystem::status(entry.filePath).permissions()));
|
||||
archive_entry_set_filetype(archiveEntry.get(), AE_IFREG);
|
||||
|
||||
return archiveEntry;
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& error)
|
||||
{
|
||||
throw FileException{ entry.filePath, error.what() };
|
||||
}
|
||||
}
|
||||
|
||||
bool ArchiveZipper::writeSomeCurrentFileData()
|
||||
{
|
||||
assert(_currentEntry != std::cend(_entries));
|
||||
|
||||
std::ifstream ifs{ _currentEntry->filePath.c_str(), std::ios_base::binary };
|
||||
if (!ifs)
|
||||
throw FileException{ _currentEntry->filePath, "cannot open file", errno };
|
||||
|
||||
ifs.seekg(0, std::ios::end);
|
||||
const std::uint64_t fileSize{ static_cast<std::uint64_t>(ifs.tellg()) };
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
|
||||
// TODO: store file size?
|
||||
if (fileSize < _currentEntryOffset)
|
||||
throw FileException{ _currentEntry->filePath, "size changed?" };
|
||||
|
||||
const std::uint64_t bytesToRead{ std::min(fileSize - _currentEntryOffset, static_cast<std::uint64_t>(_readBufferSize)) };
|
||||
|
||||
// read from file
|
||||
if (!ifs.seekg(_currentEntryOffset, std::ios::beg))
|
||||
throw FileException{ _currentEntry->filePath, "seek failed", errno };
|
||||
|
||||
if (!ifs.read(reinterpret_cast<char*>(&_readBuffer[0]), bytesToRead))
|
||||
throw FileException{ _currentEntry->filePath, "read failed", errno };
|
||||
|
||||
const std::uint64_t actualBytesRead{ static_cast<std::uint64_t>(ifs.gcount()) };
|
||||
|
||||
// write to archive
|
||||
{
|
||||
std::uint64_t remainingBytesToWrite{ actualBytesRead };
|
||||
while (remainingBytesToWrite > 0)
|
||||
{
|
||||
const auto writtenBytes{ archive_write_data(_archive.get(), &_readBuffer[actualBytesRead - remainingBytesToWrite], remainingBytesToWrite) };
|
||||
if (writtenBytes < 0)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
assert(static_cast<std::uint64_t>(writtenBytes) <= remainingBytesToWrite);
|
||||
remainingBytesToWrite -= writtenBytes;
|
||||
}
|
||||
}
|
||||
|
||||
_currentEntryOffset += actualBytesRead;
|
||||
return (_currentEntryOffset >= fileSize);
|
||||
}
|
||||
|
||||
std::int64_t ArchiveZipper::onWriteCallback(const std::byte* buffer, std::size_t bufferSize)
|
||||
{
|
||||
if (!_currentOutputStream)
|
||||
{
|
||||
archive_set_error(_archive.get(), EIO, "IO error: operation cancelled");
|
||||
return -1;
|
||||
}
|
||||
|
||||
_currentOutputStream->write(reinterpret_cast<const char*>(buffer), bufferSize);
|
||||
if (!*_currentOutputStream)
|
||||
throw Exception{ "Failed to write " + std::to_string(bufferSize) + " bytes in final archive output!" };
|
||||
|
||||
_bytesWrittenInCurrentOutputStream += bufferSize;
|
||||
|
||||
return bufferSize;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user