Add documentation, archive_sys FFI
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
* Add doxygen documentation generator into build * Create archive_sys low-level abstraction over libarchive
This commit is contained in:
parent
2b5e6a2659
commit
351849e03f
7 changed files with 2736 additions and 62 deletions
2432
doc/Doxyfile.in
Normal file
2432
doc/Doxyfile.in
Normal file
File diff suppressed because it is too large
Load diff
34
doc/meson.build
Normal file
34
doc/meson.build
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
doxygen = find_program('doxygen', required : false)
|
||||||
|
|
||||||
|
if not doxygen.found()
|
||||||
|
error('MESON_SKIP_TEST doxygen not found.')
|
||||||
|
endif
|
||||||
|
|
||||||
|
cdata = configuration_data()
|
||||||
|
cdata.set('VERSION', meson.project_version())
|
||||||
|
|
||||||
|
if find_program('dot', required : false).found()
|
||||||
|
cdata.set('HAVE_DOT', 'YES')
|
||||||
|
else
|
||||||
|
cdata.set('HAVE_DOT', 'NO')
|
||||||
|
endif
|
||||||
|
|
||||||
|
cdata.set('PROJECT_NAME', meson.project_name())
|
||||||
|
cdata.set('PROJECT_SRCDIR', join_paths(meson.current_source_dir(),'..','src',meson.project_name()))
|
||||||
|
cdata.set('PROJECT_DOCDIR', meson.current_source_dir())
|
||||||
|
cdata.set('PROJECT_TESTDIR', join_paths(meson.current_source_dir(),'..','test'))
|
||||||
|
|
||||||
|
doxyfile = configure_file(input: 'Doxyfile.in',
|
||||||
|
output: 'Doxyfile',
|
||||||
|
configuration: cdata,
|
||||||
|
install: false)
|
||||||
|
|
||||||
|
datadir = join_paths(get_option('datadir'), 'doc','xwim')
|
||||||
|
|
||||||
|
html_target = custom_target('doc',
|
||||||
|
build_always_stale : not meson.is_subproject(),
|
||||||
|
input: doxyfile,
|
||||||
|
output: 'html',
|
||||||
|
command: [doxygen, doxyfile],
|
||||||
|
install: not meson.is_subproject(),
|
||||||
|
install_dir: datadir)
|
|
@ -1,2 +1,6 @@
|
||||||
project('xwim', 'cpp', default_options : ['cpp_std=c++17'])
|
project('xwim', 'cpp',
|
||||||
|
version: '0.1',
|
||||||
|
default_options: ['cpp_std=c++17'])
|
||||||
|
|
||||||
subdir('src')
|
subdir('src')
|
||||||
|
subdir('doc')
|
||||||
|
|
|
@ -11,28 +11,17 @@ namespace logger = spdlog;
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "archive_sys.hpp"
|
||||||
#include "archive.hpp"
|
#include "archive.hpp"
|
||||||
#include "spec.hpp"
|
#include "spec.hpp"
|
||||||
|
|
||||||
namespace xwim {
|
namespace xwim {
|
||||||
|
|
||||||
static archive_entry* _archive_next_entry(archive* archive) {
|
|
||||||
int r; // libarchive error handling
|
|
||||||
archive_entry* entry;
|
|
||||||
logger::trace("Listing next archive entry");
|
|
||||||
r = archive_read_next_header(archive, &entry);
|
|
||||||
if (r != ARCHIVE_OK)
|
|
||||||
throw(ArchiveException{"Could not list archive", archive});
|
|
||||||
|
|
||||||
logger::trace("Got archive header");
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _spec_is_root_filename(ArchiveSpec* spec,
|
static void _spec_is_root_filename(ArchiveSpec* spec,
|
||||||
archive_entry* entry,
|
ArchiveEntrySys& entry,
|
||||||
std::filesystem::path* filepath) {
|
std::filesystem::path* filepath) {
|
||||||
std::filesystem::path entry_path{archive_entry_pathname(entry)};
|
auto entry_path = entry.path();
|
||||||
std::filesystem::path norm_stem = filepath->filename();
|
auto norm_stem = filepath->filename();
|
||||||
|
|
||||||
while (norm_stem.has_extension())
|
while (norm_stem.has_extension())
|
||||||
norm_stem = norm_stem.stem();
|
norm_stem = norm_stem.stem();
|
||||||
|
@ -48,28 +37,30 @@ static void _spec_is_root_filename(ArchiveSpec* spec,
|
||||||
logger::debug("\t-> Archive stem: {}", norm_stem.string());
|
logger::debug("\t-> Archive stem: {}", norm_stem.string());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _spec_is_root_dir(ArchiveSpec* spec, archive_entry* entry) {
|
static void _spec_is_root_dir(ArchiveSpec* spec, ArchiveEntrySys& entry) {
|
||||||
if (S_ISDIR(archive_entry_filetype(entry))) {
|
if (entry.is_directory()) {
|
||||||
logger::debug("Archive root is directory");
|
logger::debug("Archive root is directory");
|
||||||
spec->is_root_dir = true;
|
spec->is_root_dir = true;
|
||||||
} else {
|
} else {
|
||||||
logger::debug("Archive root is not a directory");
|
logger::debug("Archive root is not a directory");
|
||||||
spec->is_root_dir = false;
|
spec->is_root_dir = false;
|
||||||
}
|
}
|
||||||
logger::debug("\t-> Archive mode_t: {0:o}", archive_entry_filetype(entry));
|
logger::debug("\t-> Archive mode_t: {0:o}", entry.file_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _spec_has_single_root(ArchiveSpec* spec,
|
static void _spec_has_single_root(ArchiveSpec* spec,
|
||||||
archive_entry* first_entry,
|
ArchiveEntrySys& first_entry,
|
||||||
archive* archive) {
|
ArchiveReaderSys& archive_reader) {
|
||||||
std::filesystem::path first_entry_root =
|
std::filesystem::path first_entry_root = *(first_entry.path().begin());
|
||||||
*(std::filesystem::path{archive_entry_pathname(first_entry)}.begin());
|
|
||||||
logger::trace("Testing roots");
|
logger::trace("Testing roots");
|
||||||
|
|
||||||
spec->has_single_root = true;
|
spec->has_single_root = true;
|
||||||
archive_entry* entry;
|
|
||||||
while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) {
|
while (true) {
|
||||||
std::filesystem::path next_entry{archive_entry_pathname(entry)};
|
ArchiveEntrySys& entry = archive_reader.next();
|
||||||
|
if(entry.is_empty()) break;
|
||||||
|
|
||||||
|
auto next_entry = entry.path();
|
||||||
logger::trace("Path: {}, Root: {}", next_entry.string(),
|
logger::trace("Path: {}, Root: {}", next_entry.string(),
|
||||||
next_entry.begin()->string());
|
next_entry.begin()->string());
|
||||||
|
|
||||||
|
@ -83,25 +74,12 @@ static void _spec_has_single_root(ArchiveSpec* spec,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec->has_single_root)
|
if (spec->has_single_root)
|
||||||
logger::debug("Archive has single root: {}", first_entry_root.string());
|
logger::debug("Archive has single root: {}", first_entry_root.string());
|
||||||
}
|
}
|
||||||
|
|
||||||
Archive::Archive(std::filesystem::path path) : path{path} {
|
Archive::Archive(std::filesystem::path path) : path{path} {}
|
||||||
int r; // libarchive error handling
|
|
||||||
|
|
||||||
logger::trace("Setting up archive reader");
|
|
||||||
this->xwim_archive = archive_read_new();
|
|
||||||
archive_read_support_filter_all(this->xwim_archive);
|
|
||||||
archive_read_support_format_all(this->xwim_archive);
|
|
||||||
|
|
||||||
logger::trace("Reading archive at {}", path.c_str());
|
|
||||||
r = archive_read_open_filename(this->xwim_archive, path.c_str(), 10240);
|
|
||||||
if (r != ARCHIVE_OK)
|
|
||||||
throw ArchiveException{"Could not open archive file", this->xwim_archive};
|
|
||||||
|
|
||||||
logger::trace("Archive read succesfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
Archive::~Archive() {
|
Archive::~Archive() {
|
||||||
archive_read_free(this->xwim_archive);
|
archive_read_free(this->xwim_archive);
|
||||||
|
@ -110,39 +88,38 @@ Archive::~Archive() {
|
||||||
ArchiveSpec Archive::check() {
|
ArchiveSpec Archive::check() {
|
||||||
logger::trace("Creating archive spec for {}", this->path.string());
|
logger::trace("Creating archive spec for {}", this->path.string());
|
||||||
|
|
||||||
|
ArchiveReaderSys archive_reader {this->path};
|
||||||
|
|
||||||
ArchiveSpec archive_spec;
|
ArchiveSpec archive_spec;
|
||||||
|
|
||||||
archive_entry* first_entry = _archive_next_entry(this->xwim_archive);
|
ArchiveEntrySys& first_entry = archive_reader.next();
|
||||||
if (first_entry == nullptr) { // archive is empty
|
|
||||||
|
if (first_entry.is_empty()) { // archive is empty
|
||||||
logger::debug("Archive is empty");
|
logger::debug("Archive is empty");
|
||||||
return {false, false, false};
|
return {false, false, false};
|
||||||
}
|
}
|
||||||
|
|
||||||
logger::trace("Found archive entry {}", archive_entry_pathname(first_entry));
|
logger::trace("Found archive entry {}", first_entry.path_name());
|
||||||
|
|
||||||
_spec_is_root_filename(&archive_spec, first_entry, &this->path);
|
_spec_is_root_filename(&archive_spec, first_entry, &this->path);
|
||||||
_spec_is_root_dir(&archive_spec, first_entry);
|
_spec_is_root_dir(&archive_spec, first_entry);
|
||||||
_spec_has_single_root(&archive_spec, first_entry, this->xwim_archive);
|
_spec_has_single_root(&archive_spec, first_entry, archive_reader);
|
||||||
|
|
||||||
return archive_spec;
|
return archive_spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _extract_make_dir(ExtractSpec extract_spec,
|
|
||||||
archive* archive) {
|
|
||||||
if (extract_spec.make_dir) {
|
|
||||||
try {
|
|
||||||
logger::trace("Creating directory {}", extract_spec.dirname.string());
|
|
||||||
std::filesystem::create_directories(extract_spec.dirname);
|
|
||||||
} catch (std::filesystem::filesystem_error& err) {
|
|
||||||
throw ArchiveException{err.what(), archive};
|
|
||||||
} catch (std::bad_alloc& err) {
|
|
||||||
throw ArchiveException{err.what(), archive};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Archive::extract(ExtractSpec extract_spec) {
|
void Archive::extract(ExtractSpec extract_spec) {
|
||||||
_extract_make_dir(extract_spec, this->xwim_archive);
|
ArchiveExtractorSys extractor;
|
||||||
|
|
||||||
|
if(extract_spec.make_dir) {
|
||||||
|
logger::trace("Creating extract directory {}", extract_spec.dirname.string());
|
||||||
|
extractor = ArchiveExtractorSys{extract_spec.dirname};
|
||||||
|
} else {
|
||||||
|
extractor = ArchiveExtractorSys{};
|
||||||
|
}
|
||||||
|
|
||||||
|
ArchiveReaderSys reader{this->path};
|
||||||
|
extractor.extract_all(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace xwim
|
} // namespace xwim
|
||||||
|
|
125
src/archive_sys.cpp
Normal file
125
src/archive_sys.cpp
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
#include <archive_entry.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
namespace logger = spdlog;
|
||||||
|
|
||||||
|
#include "archive_sys.hpp"
|
||||||
|
|
||||||
|
#include <archive.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
bool xwim::ArchiveEntrySys::is_empty() {
|
||||||
|
return (this->ae.get() == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string xwim::ArchiveEntrySys::path_name() {
|
||||||
|
return archive_entry_pathname(this->ae.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path xwim::ArchiveEntrySys::path() {
|
||||||
|
return std::filesystem::path{this->path_name()};
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_t xwim::ArchiveEntrySys::file_type() {
|
||||||
|
return archive_entry_filetype(this->ae.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool xwim::ArchiveEntrySys::is_directory() {
|
||||||
|
return S_ISDIR(this->file_type());
|
||||||
|
}
|
||||||
|
|
||||||
|
xwim::ArchiveReaderSys::ArchiveReaderSys(std::filesystem::path& path) {
|
||||||
|
int r; // libarchive error handling
|
||||||
|
|
||||||
|
logger::trace("Setting up archive reader");
|
||||||
|
this->ar = archive_read_new();
|
||||||
|
archive_read_support_filter_all(this->ar);
|
||||||
|
archive_read_support_format_all(this->ar);
|
||||||
|
|
||||||
|
logger::trace("Reading archive at {}", path.c_str());
|
||||||
|
r = archive_read_open_filename(this->ar, path.c_str(), 10240);
|
||||||
|
if (r != ARCHIVE_OK)
|
||||||
|
throw ArchiveSysException{"Could not open archive file", this->ar};
|
||||||
|
|
||||||
|
logger::trace("Archive read succesfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
xwim::ArchiveReaderSys::~ArchiveReaderSys() {
|
||||||
|
archive_free(this->ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
xwim::ArchiveEntrySys& xwim::ArchiveReaderSys::next() {
|
||||||
|
int r; // libarchive error handling
|
||||||
|
logger::trace("Listing next archive entry");
|
||||||
|
archive_entry* ae;
|
||||||
|
r = archive_read_next_header(this->ar, &ae);
|
||||||
|
if (r != ARCHIVE_OK)
|
||||||
|
throw(ArchiveSysException{"Could not list archive", this->ar});
|
||||||
|
|
||||||
|
this->cur_entry = xwim::ArchiveEntrySys { ae };
|
||||||
|
|
||||||
|
|
||||||
|
logger::trace("Got archive header");
|
||||||
|
|
||||||
|
return this->cur_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
xwim::ArchiveEntrySys& xwim::ArchiveReaderSys::cur() {
|
||||||
|
return this->cur_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
xwim::ArchiveExtractorSys::ArchiveExtractorSys(std::filesystem::path& root) {
|
||||||
|
std::filesystem::create_directories(root);
|
||||||
|
std::filesystem::current_path(root);
|
||||||
|
|
||||||
|
this->writer = archive_write_disk_new();
|
||||||
|
archive_write_disk_set_standard_lookup(this->writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
xwim::ArchiveExtractorSys::ArchiveExtractorSys() {
|
||||||
|
this->writer = archive_write_disk_new();
|
||||||
|
archive_write_disk_set_standard_lookup(this->writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xwim::ArchiveExtractorSys::extract_all(xwim::ArchiveReaderSys& reader) {
|
||||||
|
for(;;) {
|
||||||
|
ArchiveEntrySys& entry = reader.next();
|
||||||
|
|
||||||
|
if(entry.is_empty()) break;
|
||||||
|
|
||||||
|
this->extract(reader, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int copy_data(struct archive* ar, struct archive* aw) {
|
||||||
|
int r;
|
||||||
|
const void* buff;
|
||||||
|
size_t size;
|
||||||
|
int64_t offset;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
r = archive_read_data_block(ar, &buff, &size, &offset);
|
||||||
|
if (r == ARCHIVE_EOF)
|
||||||
|
return (ARCHIVE_OK);
|
||||||
|
if (r != ARCHIVE_OK) {
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
r = archive_write_data_block(aw, buff, size, offset);
|
||||||
|
if (r != ARCHIVE_OK) {
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void xwim::ArchiveExtractorSys::extract(xwim::ArchiveReaderSys& reader, xwim::ArchiveEntrySys& entry) {
|
||||||
|
int r;
|
||||||
|
r = archive_write_header(this->writer, entry.ae.get());
|
||||||
|
if (r != ARCHIVE_OK) {
|
||||||
|
throw(ArchiveSysException("Could not extract entry", reader.ar));
|
||||||
|
}
|
||||||
|
|
||||||
|
r = copy_data(reader.ar, this->writer);
|
||||||
|
if (r != ARCHIVE_OK) {
|
||||||
|
throw(ArchiveSysException("Could not extract entry", reader.ar));
|
||||||
|
}
|
||||||
|
}
|
99
src/archive_sys.hpp
Normal file
99
src/archive_sys.hpp
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <archive.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace xwim {
|
||||||
|
|
||||||
|
class ArchiveEntrySys {
|
||||||
|
private:
|
||||||
|
std::function<void (archive_entry*)> ae_deleter = [](archive_entry* ae) { archive_entry_free(ae); };
|
||||||
|
std::unique_ptr<archive_entry, decltype(ae_deleter)> ae;
|
||||||
|
friend class ArchiveExtractorSys;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ArchiveEntrySys(std::unique_ptr<archive_entry> entry)
|
||||||
|
: ae{ std::unique_ptr<archive_entry, decltype(ae_deleter)>{entry.release(), ae_deleter} }
|
||||||
|
{}
|
||||||
|
ArchiveEntrySys()
|
||||||
|
: ae{ std::unique_ptr<archive_entry, decltype(ae_deleter)>{nullptr, ae_deleter} }
|
||||||
|
{}
|
||||||
|
ArchiveEntrySys(archive_entry* entry)
|
||||||
|
: ae{ std::unique_ptr<archive_entry, decltype(ae_deleter)>{entry, ae_deleter} }
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool is_empty();
|
||||||
|
std::string path_name();
|
||||||
|
std::filesystem::path path();
|
||||||
|
mode_t file_type();
|
||||||
|
bool is_directory();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** A reader for archive files
|
||||||
|
*
|
||||||
|
* Shim for `libarchive`. Iterates through
|
||||||
|
* entries of an archive with `next()`
|
||||||
|
*/
|
||||||
|
class ArchiveReaderSys {
|
||||||
|
private:
|
||||||
|
archive* ar;
|
||||||
|
ArchiveEntrySys cur_entry;
|
||||||
|
friend class ArchiveExtractorSys;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ArchiveReaderSys(std::filesystem::path& path);
|
||||||
|
~ArchiveReaderSys();
|
||||||
|
|
||||||
|
/** Returns the next archive entry
|
||||||
|
*
|
||||||
|
* @return archive_entry or nullptr if end of archive reached
|
||||||
|
*/
|
||||||
|
ArchiveEntrySys& next();
|
||||||
|
|
||||||
|
/** Returns the current archive entry
|
||||||
|
*
|
||||||
|
* @return archive_entry or nullptr if current entry at end of archive
|
||||||
|
*/
|
||||||
|
ArchiveEntrySys& cur();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** A extractor for archive files
|
||||||
|
*
|
||||||
|
* Shim for `libarchive`.
|
||||||
|
*/
|
||||||
|
class ArchiveExtractorSys {
|
||||||
|
private:
|
||||||
|
archive* writer;
|
||||||
|
public:
|
||||||
|
ArchiveExtractorSys(std::filesystem::path& root);
|
||||||
|
ArchiveExtractorSys();
|
||||||
|
|
||||||
|
void extract(ArchiveReaderSys& reader, ArchiveEntrySys& entry);
|
||||||
|
void extract_all(ArchiveReaderSys& reader);
|
||||||
|
|
||||||
|
void extract_header(ArchiveEntrySys& entry);
|
||||||
|
void extract_data();
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArchiveSysException : public std::exception {
|
||||||
|
private:
|
||||||
|
std::string _what;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ArchiveSysException(std::string what, archive* archive) {
|
||||||
|
if (archive_error_string(archive)) {
|
||||||
|
_what = fmt::format("{}: {}", what, archive_error_string(archive));
|
||||||
|
} else {
|
||||||
|
_what = fmt::format("{}", what);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ArchiveSysException(std::string what) {
|
||||||
|
_what = fmt::format("{}", what);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char* what() const noexcept { return this->_what.c_str(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace xwim
|
|
@ -1,6 +1,9 @@
|
||||||
src = ['main.cpp', 'archive.cpp']
|
src = ['main.cpp',
|
||||||
|
'archive.cpp',
|
||||||
|
'archive_sys.cpp']
|
||||||
inc = ['archive.hpp',
|
inc = ['archive.hpp',
|
||||||
'spec.hpp']
|
'spec.hpp',
|
||||||
|
'archive_sys.hpp']
|
||||||
|
|
||||||
libs = [dependency('libarchive', required: true),
|
libs = [dependency('libarchive', required: true),
|
||||||
dependency('fmt', required: true),
|
dependency('fmt', required: true),
|
||||||
|
|
Loading…
Reference in a new issue