From d9cbf47036a2b9029e8e5a4788a08d3dab802d5d Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Mon, 3 Aug 2020 00:52:37 +0200 Subject: [PATCH] Relative root path in archive, clang-format, refactoring Compressed archives now contain a single root which consists of the file or folder being compressed. Before, the archive root contained the full relative path form the current working directory to the compressed file or folder. This is unintuitive in most cases and not dwim. --- .clang-format | 169 ++++++++++++++++++++++++++++++++++++++- README.md | 6 +- meson.build | 5 +- src/archive_sys.cpp | 190 ++++++++++++++++++++++++++++---------------- 4 files changed, 296 insertions(+), 74 deletions(-) diff --git a/.clang-format b/.clang-format index 34fe704..f2dd0de 100644 --- a/.clang-format +++ b/.clang-format @@ -1 +1,168 @@ -BasedOnStyle: Chromium \ No newline at end of file +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +... + diff --git a/README.md b/README.md index 0e9f116..0dae38a 100644 --- a/README.md +++ b/README.md @@ -126,12 +126,12 @@ Per default xwim chooses an appropriate log level according to your build type - off # Contributing -While xwim is still in incubator phase (i.e. before version 1.0) it's main +While xwim is still in incubator phase (i.e. before version 1.0) its main repository is hosted on https://git.friedl.net/incubator/xwim with a mirror on https://github.com/arminfriedl/xwim. With the first stable release it will most -likely move to GitHub as it's main repository. +likely move to GitHub as its main repository. -If you want to contribute, you can either issue a pull request on it's Github +If you want to contribute, you can either issue a pull request on its Github mirror (will be cherry picked into the main repository) or send patches to dev[at]friedl[dot]net. diff --git a/meson.build b/meson.build index d382a12..a315056 100644 --- a/meson.build +++ b/meson.build @@ -1,8 +1,9 @@ project('xwim', 'cpp', - version: '0.2', + version: '0.3', default_options: ['cpp_std=c++17', 'warning_level=3', - 'b_coverage=true']) + 'b_coverage=true', + 'b_ndebug=if-release']) subdir('src') subdir('doc') diff --git a/src/archive_sys.cpp b/src/archive_sys.cpp index 7c2fdc0..e46b1bc 100644 --- a/src/archive_sys.cpp +++ b/src/archive_sys.cpp @@ -1,20 +1,22 @@ -#include #include +#include #include + #include "archive.hpp" #include "fileformats.hpp" #include "spec.hpp" namespace logger = spdlog; -#include "archive_sys.hpp" - #include + #include #include -bool xwim::ArchiveEntryView::is_empty() { - return (this->ae == nullptr); -} +#include "archive_sys.hpp" + +namespace fs = std::filesystem; + +bool xwim::ArchiveEntryView::is_empty() { return (this->ae == nullptr); } std::string xwim::ArchiveEntryView::path_name() { if (!this->ae) throw ArchiveSysException{"Access to invalid archive entry"}; @@ -22,9 +24,9 @@ std::string xwim::ArchiveEntryView::path_name() { return archive_entry_pathname(this->ae); } -std::filesystem::path xwim::ArchiveEntryView::path() { +fs::path xwim::ArchiveEntryView::path() { if (!this->ae) throw ArchiveSysException{"Access to invalid archive entry"}; - return std::filesystem::path{this->path_name()}; + return fs::path{this->path_name()}; } mode_t xwim::ArchiveEntryView::file_type() { @@ -36,7 +38,7 @@ bool xwim::ArchiveEntryView::is_directory() { return S_ISDIR(this->file_type()); } -xwim::ArchiveReaderSys::ArchiveReaderSys(std::filesystem::path& path) { +xwim::ArchiveReaderSys::ArchiveReaderSys(fs::path &path) { int r; // libarchive error handling logger::trace("Setting up archive reader"); @@ -63,8 +65,12 @@ bool xwim::ArchiveReaderSys::advance() { logger::trace("Advancing reader to next archive entry"); r = archive_read_next_header(this->ar, &this->ae); - if (r == ARCHIVE_EOF) { this->ae = nullptr; return false; } - if (r != ARCHIVE_OK) throw(ArchiveSysException{"Could not list archive", this->ar}); + if (r == ARCHIVE_EOF) { + this->ae = nullptr; + return false; + } + if (r != ARCHIVE_OK) + throw(ArchiveSysException{"Could not list archive", this->ar}); logger::trace("Got entry {}", archive_entry_pathname(ae)); return true; @@ -74,16 +80,17 @@ const xwim::ArchiveEntryView xwim::ArchiveReaderSys::cur() { return ArchiveEntryView{this->ae}; } -xwim::ArchiveExtractorSys::ArchiveExtractorSys(std::filesystem::path& root) { +xwim::ArchiveExtractorSys::ArchiveExtractorSys(fs::path &root) { logger::trace("Constructing ArchiveExtractorSys with path {}", root.string()); - std::filesystem::create_directories(root); - std::filesystem::current_path(root); + fs::create_directories(root); + fs::current_path(root); this->writer = archive_write_disk_new(); archive_write_disk_set_standard_lookup(this->writer); - logger::trace("Constructed ArchiveExtractorSys at {:p}", (void*) this->writer); + logger::trace("Constructed ArchiveExtractorSys at {:p}", + (void *)this->writer); } xwim::ArchiveExtractorSys::ArchiveExtractorSys() { @@ -91,19 +98,20 @@ xwim::ArchiveExtractorSys::ArchiveExtractorSys() { this->writer = archive_write_disk_new(); archive_write_disk_set_standard_lookup(this->writer); - logger::trace("Constructed ArchiveExtractorSys at {:p}", (void*) this->writer); + logger::trace("Constructed ArchiveExtractorSys at {:p}", + (void *)this->writer); } -void xwim::ArchiveExtractorSys::extract_all(xwim::ArchiveReaderSys& reader) { - while(reader.advance()) { +void xwim::ArchiveExtractorSys::extract_all(xwim::ArchiveReaderSys &reader) { + while (reader.advance()) { this->extract_entry(reader); } } // forward declared -static int copy_data(struct archive* ar, struct archive* aw); +static int copy_data(struct archive *ar, struct archive *aw); -void xwim::ArchiveExtractorSys::extract_entry(xwim::ArchiveReaderSys& reader) { +void xwim::ArchiveExtractorSys::extract_entry(xwim::ArchiveReaderSys &reader) { int r; r = archive_write_header(this->writer, reader.ae); if (r != ARCHIVE_OK) { @@ -116,90 +124,73 @@ void xwim::ArchiveExtractorSys::extract_entry(xwim::ArchiveReaderSys& reader) { } } -xwim::ArchiveExtractorSys::~ArchiveExtractorSys(){ - logger::trace("Destructing ArchiveExtractorSys at {:p}", (void*) this->writer); - if(this->writer) { +xwim::ArchiveExtractorSys::~ArchiveExtractorSys() { + logger::trace("Destructing ArchiveExtractorSys at {:p}", + (void *)this->writer); + if (this->writer) { archive_write_close(this->writer); archive_write_free(this->writer); } } -xwim::ArchiveCompressorSys::ArchiveCompressorSys(std::filesystem::path& root, xwim::CompressSpec compress_spec): root{root}, compress_spec{compress_spec} { +xwim::ArchiveCompressorSys::ArchiveCompressorSys( + fs::path &root, xwim::CompressSpec compress_spec) + : root{root}, compress_spec{compress_spec} { this->new_archive = archive_write_new(); - for(xwim::archive_filter filter: this->compress_spec.filters) { + for (xwim::archive_filter filter : this->compress_spec.filters) { archive_write_add_filter(this->new_archive, filter); } archive_write_set_format(this->new_archive, this->compress_spec.format); } +// forward declared +static fs::path archive_path_norm(const fs::path &root, + const xwim::CompressSpec &compress_spec); + void xwim::ArchiveCompressorSys::compress() { - std::filesystem::path archive_path{this->root}; - if(!std::filesystem::exists(archive_path)) { - logger::error("Non-existing path: {}", archive_path.string()); - throw ArchiveSysException{"Path does not exists"}; - } + fs::path archive_path = archive_path_norm(this->root, this->compress_spec); - std::filesystem::file_status file_status = std::filesystem::status(archive_path); + logger::debug("Writing archive at: {}", archive_path.filename().c_str()); - if(file_status.type() != std::filesystem::file_type::directory - && file_status.type() != std::filesystem::file_type::regular) { - logger::error("Unknown path type: {}", file_status.type()); - throw ArchiveSysException{"Unknown path type"}; - } + archive_write_open_filename(this->new_archive, + archive_path.filename().c_str()); - if ((file_status.permissions() & std::filesystem::perms::owner_read) == - std::filesystem::perms::none && - (file_status.permissions() & std::filesystem::perms::group_read) == - std::filesystem::perms::none && - (file_status.permissions() & std::filesystem::perms::others_read) == - std::filesystem::perms::none) { - logger::error("Cannot read path with permissions: {}", - file_status.permissions()); - throw ArchiveSysException{"Unreadable path"}; - } - - if(file_status.type() == std::filesystem::file_type::regular) { - while(archive_path.has_extension()) { - archive_path.replace_extension(); - } - } - - archive_path.concat(this->compress_spec.extension); - logger::debug("Writing archive at: {}", std::filesystem::absolute(archive_path).c_str()); - archive_write_open_filename(this->new_archive, std::filesystem::absolute(archive_path).c_str()); - - archive* disk = archive_read_disk_new(); + archive *disk = archive_read_disk_new(); archive_read_disk_set_standard_lookup(disk); int r; - r = archive_read_disk_open(disk, std::filesystem::relative(this->root).c_str()); - if(r != ARCHIVE_OK) { + r = archive_read_disk_open(disk, fs::relative(this->root).c_str()); + if (r != ARCHIVE_OK) { throw ArchiveSysException("Could not open path for archiving", disk); } - archive_entry* entry; + archive_entry *entry; char buff[16384]; for (;;) { entry = archive_entry_new(); r = archive_read_next_header2(disk, entry); - if (r == ARCHIVE_EOF) - break; + if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) { throw ArchiveSysException("Could not read next archive entry", disk); } archive_read_disk_descend(disk); + + const char* ae_path = archive_entry_pathname(entry); + fs::path ae_rel_path = fs::relative(fs::path(ae_path), this->root.parent_path()); + archive_entry_set_pathname(entry, ae_rel_path.c_str()); logger::trace("Processing entry {}", archive_entry_pathname(entry)); r = archive_write_header(this->new_archive, entry); if (r < ARCHIVE_OK) { throw ArchiveSysException("Could not write header for archive entry", - this->new_archive); + this->new_archive); } + if (r > ARCHIVE_FAILED) { int fd = open(archive_entry_sourcepath(entry), O_RDONLY); ssize_t len = read(fd, buff, sizeof(buff)); @@ -209,21 +200,84 @@ void xwim::ArchiveCompressorSys::compress() { } close(fd); } + logger::trace("Entry written {}", archive_entry_pathname(entry)); archive_entry_free(entry); } } xwim::ArchiveCompressorSys::~ArchiveCompressorSys() { - logger::trace("Destructing ArchiveExtractorSys at {:p}", (void*) this->new_archive); - if(this->new_archive) { + logger::trace("Destructing ArchiveExtractorSys at {:p}", + (void *)this->new_archive); + if (this->new_archive) { archive_write_close(this->new_archive); archive_write_free(this->new_archive); } } -static int copy_data(struct archive* ar, struct archive* aw) { +/** Creates an archive path from the path to compress and normalizes it + * + * Note that currently only single arguments are allowed for `xwim` to + * minimize ambiguity. + * + * The archive path is determined from the argument file/directory by: + * 1. If file: + * 1.1. Stem the filename + * 1.2. Append an extension appropriate for the archive format (from the + * spec) + * 2. If directory: + * 2.1. Remove any trailing '/' + * 2.2. Append an extension appropriate for the archive format (from the + * spec) + */ +static fs::path archive_path_norm(const fs::path &root, + const xwim::CompressSpec &compress_spec) { + fs::path archive_path{root}; + fs::file_status archive_path_stat = fs::status(archive_path); + + std::set known_types = {fs::file_type::directory, fs::file_type::regular}; + fs::perms flag_mask = + fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read; + + if (!fs::exists(archive_path)) { + logger::error("Non-existing path: {}", archive_path.string()); + throw xwim::ArchiveSysException{"Path does not exists"}; + } + + if (!known_types.count(archive_path_stat.type())) { + logger::error("Unknown path type: {}", archive_path_stat.type()); + throw xwim::ArchiveSysException{"Unknown path type"}; + } + + if ((archive_path_stat.permissions() & flag_mask) == fs::perms::none) { + logger::error("Cannot read path with permissions: {}", + archive_path_stat.permissions()); + throw xwim::ArchiveSysException{"Unreadable path"}; + } + + if (archive_path_stat.type() == fs::file_type::regular) { + while (archive_path.has_extension()) { + archive_path.replace_extension(); + } + } + + if (archive_path_stat.type() == fs::file_type::directory) { + if (archive_path.string().back() == '/') { + logger::trace("Found trailing / in path"); + std::string ps = archive_path.string(); + ps.erase(ps.size() - 1, 1); + + archive_path = fs::path{ps}; + logger::trace("Normalized path to {}", archive_path.string()); + } + } + + archive_path.concat(compress_spec.extension); + return archive_path; +} + +static int copy_data(struct archive *ar, struct archive *aw) { int r; - const void* buff; + const void *buff; size_t size; int64_t offset;