diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d7b194c --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +--- +BasedOnStyle: Mozilla +IndentWidth: 4 +... +# vim: set filetype=yaml: diff --git a/.cmake-format.yaml b/.cmake-format.yaml new file mode 100644 index 0000000..d9179f9 --- /dev/null +++ b/.cmake-format.yaml @@ -0,0 +1,12 @@ +format: + line_width: 120 + tab_size: 4 + use_tabchars: true + fractional_tab_policy: round-up + max_subgroups_hwrap: 2 + max_pargs_hwrap: 3 + dangle_parens: true + dangle_align: prefix + min_prefix_chars: 0 + max_prefix_chars: 16 + keyword_case: upper diff --git a/.gitignore b/.gitignore index f0b69a4..3032325 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ tags *.exe *.out *.app +/version.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ae4414..407fc0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,2 +1,91 @@ # CMake configuration for demo project cmake_minimum_required(VERSION 3.16) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_CLANG_TIDY clang-tidy) + +include(ProcessorCount) +ProcessorCount(CPU_COUNT) +if(CPU_COUNT EQUAL 0) + set(CPU_COUNT 4) +endif() +message(STATUS "Counted ${CPU_COUNT} cores") +set(CTEST_BUILD_FLAGS -j${CPU_COUNT}) +set(ctest_test_args ${ctest_test_args} PARALLEL_LEVEL ${CPU_COUNT}) + +execute_process( + COMMAND git describe --tags --dirty --always + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE VERSION_STRING + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +string( + REGEX + REPLACE "^v?([0-9]+)\\.([0-9]+)\\.([0-9]+).*$" + "\\1.\\2.\\3" + MODIFIED_VERSION_STRING + "${VERSION_STRING}" +) +project( + collector + VERSION ${MODIFIED_VERSION_STRING} + LANGUAGES CXX +) + +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) +set(BOOST_ENABLE_CMAKE ON) + +FetchContent_Declare( + boostorg + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-1.80.0 + GIT_PROGRESS TRUE + GIT_CONFIG fetch.parallel=${CPU_COUNT} submodule.fetchJobs=${CPU_COUNT} + GIT_SHALLOW TRUE +) + +FetchContent_Declare( + dir_monitor + GIT_REPOSITORY https://github.com/schtobia/dir_monitor.git + GIT_TAG master + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) + +FetchContent_Populate(boostorg) +add_subdirectory(${boostorg_SOURCE_DIR} ${boostorg_BINARY_DIR}) + +FetchContent_MakeAvailable(dir_monitor) + +enable_testing() + +configure_file(version.hpp.in ${PROJECT_SOURCE_DIR}/version.hpp) + +add_executable(${PROJECT_NAME}_test test.cpp) +target_compile_options( + ${PROJECT_NAME}_test + PUBLIC -Wall + -Wextra + -Wshadow + -Wnon-virtual-dtor +) + +target_link_libraries( + ${PROJECT_NAME}_test + PRIVATE Boost::filesystem + Boost::system + Boost::unit_test_framework + dir_monitor +) + +add_test(NAME test1 COMMAND $[PROJECT_NAME}_test) +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries( + ${PROJECT_NAME} + PRIVATE Boost::filesystem + Boost::system + Boost::program_options + dir_monitor +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1bd0b00 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Tobias Schmidl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/collector.hpp b/collector.hpp new file mode 100644 index 0000000..fc77fb1 --- /dev/null +++ b/collector.hpp @@ -0,0 +1,56 @@ +/** + * @file collector.hpp + * Collects file events and acts upon them + * + * @author Tobias Schmidl + * @copyright Copyright © 2022, Tobias Schmidl + * SPDX-License-Identifier: MIT + */ + +#pragma once +#include "monitor.hpp" +#include +#include + +// for system() +#include +#include + +constexpr const char* FILTER = "^Core\\.[^.][^.]*\\..*\\.lz4"; + +namespace collector::collector { + +class Collector +{ + monitor::Monitor _monitor; + std::filesystem::path _target_file; + + public: + Collector(boost::asio::io_service& service, + std::filesystem::path&& root_path, + std::filesystem::path&& target_file) + : _monitor(service, std::move(root_path)) + , _target_file(std::move(target_file)) + { + } + Collector(boost::asio::io_service& service, + const std::filesystem::path& root_path, + const std::filesystem::path& target_file) + : _monitor(service, root_path) + , _target_file(target_file) + { + } + + bool operator()() + { + const auto& fs_event = + _monitor.watch(std::regex(FILTER), + boost::asio::dir_monitor_event::event_type::added); + // big fugly hack + std::string call_command = + std::string("/usr/bin/tar cvf ") + _target_file.generic_string() + + std::string(" ") + _monitor.get_root_path().generic_string(); + return system(call_command.c_str()) == 0; + } +}; +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..6ab7138 --- /dev/null +++ b/main.cpp @@ -0,0 +1,62 @@ +/** + * @file main.cpp + * The main executable + * + * @author Tobias Schmidl + * @copyright Copyright © 2022, Tobias Schmidl + * SPDX-License-Identifier: MIT + */ + +#include "collector.hpp" +#include "monitor.hpp" +#include "version.hpp" +#include +#include +#include + +using namespace collector::monitor; +using namespace collector::collector; +using namespace boost::program_options; + +int +main(int argc, const char* argv[]) +{ + std::cerr << argv[0] << " Version v" << VERSION << "\n"; + options_description description{ "Options" }; + description.add_options()("help,h", "Help screen")( + "input_dir,i", value()->required(), "Input Directory")( + "output_file,o", value()->required(), "Output Tarball"); + variables_map vm; + try { + store(parse_command_line(argc, argv, description), vm); + notify(vm); + } catch (std::exception& err) { + std::cerr << "Error: " << err.what() << "\n"; + std::cerr << description << std::endl; + return 1; + } + + std::filesystem::path input_dir, output_file; + if (vm.count("help")) { + std::cerr << description << std::endl; + return 1; + } + if (vm.count("input_dir")) { + input_dir = vm["input_dir"].as(); + if (!std::filesystem::is_directory(input_dir)) { + std::cerr << input_dir << " is not a valid directory." << std::endl; + return 1; + } else + std::cerr << "input dir: " << input_dir << "\n"; + } + if (vm.count("output_file")) { + output_file = vm["output_file"].as(); + std::cout << "output file: " << output_file << "\n"; + } + + boost::asio::io_service service; + + Collector collector(service, input_dir, output_file); + return (collector() && std::filesystem::is_regular_file(output_file)) ? 0 + : 2; +} diff --git a/monitor.hpp b/monitor.hpp new file mode 100644 index 0000000..430a780 --- /dev/null +++ b/monitor.hpp @@ -0,0 +1,90 @@ +/** + * @file monitor.hpp + * A dir_monitor wrapper for specific paths + * + * @author Tobias Schmidl + * @copyright Copyright © 2022, Tobias Schmidl + * SPDX-License-Identifier: MIT + */ + +#pragma once +#include +#include +#include + +namespace collector::monitor { + +class Monitor +{ + private: + boost::asio::dir_monitor _monitor; + std::filesystem::path _root_path; + + public: + explicit Monitor(boost::asio::io_service& io_service, + std::filesystem::path&& root_path) + : _monitor(io_service) + , _root_path(std::move(root_path)) + { + _monitor.add_directory(_root_path); + for (const auto& dir_entry : + std::filesystem::recursive_directory_iterator(_root_path)) { + if (dir_entry.is_directory()) + _monitor.add_directory(dir_entry.path()); + } + } + + explicit Monitor(boost::asio::io_service& io_service, + const std::filesystem::path& root_path) + : _monitor(io_service) + , _root_path(root_path) + { + _monitor.add_directory(_root_path); + for (const auto& dir_entry : + std::filesystem::recursive_directory_iterator(_root_path)) { + if (dir_entry.is_directory()) + _monitor.add_directory(dir_entry.path()); + } + } + + /** + * watches any for any event that matches the given search_pattern + */ + boost::asio::dir_monitor_event watch(const std::regex& search_pattern) + { + boost::asio::dir_monitor_event monitored_event; + do { + monitored_event = _monitor.monitor(); + } while (!std::regex_match( + monitored_event.path.filename().generic_string(), search_pattern)); + return monitored_event; + } + + /** + * watches any for the specified event_type that also matches the given + * search_pattern + */ + boost::asio::dir_monitor_event watch( + const std::regex& search_pattern, + const boost::asio::dir_monitor_event::event_type event_type) + { + boost::asio::dir_monitor_event monitored_event; + do { + monitored_event = _monitor.monitor(); + } while ( + !std::regex_match(monitored_event.path.filename().generic_string(), + search_pattern) || + monitored_event.type != event_type); + return monitored_event; + } + + const auto& get_root_path() const { return _root_path; } + virtual ~Monitor() = default; + + Monitor() = delete; + Monitor(Monitor&& other) = delete; + Monitor(const Monitor& other) = delete; + Monitor& operator=(Monitor&& other) = delete; + Monitor& operator=(const Monitor& other) = delete; +}; +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..855370c --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +cmakelang[yaml]~=0.6.13 diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..3f1a286 --- /dev/null +++ b/test.cpp @@ -0,0 +1,66 @@ +/** + * @file test.cpp + * The unit test executable + * + * @author Tobias Schmidl + * @copyright Copyright © 2022, Tobias Schmidl + * SPDX-License-Identifier: MIT + */ + +#include "collector.hpp" +#include "monitor.hpp" +#include "version.hpp" +#define BOOST_TEST_MODULE Collector test +#include +#include +#include +#include +#include + +using namespace collector::monitor; +using namespace collector::collector; +using namespace std::chrono_literals; + +boost::asio::io_service service; + +BOOST_AUTO_TEST_CASE(monitor_test) +{ + BOOST_TEST_MESSAGE("Test version " << VERSION); + const std::filesystem::path test_file{ "test_file" }; + std::filesystem::remove(test_file); + const auto create_file_result = std::async([&test_file]() { + std::this_thread::sleep_for(2s); + std::ofstream{ test_file }; + }); + Monitor monitor(service, std::filesystem::current_path()); + const auto& watch_result = + monitor.watch(std::regex(test_file.generic_string()), + boost::asio::dir_monitor_event::event_type::added); + BOOST_TEST(std::filesystem::equivalent(test_file, + watch_result.path.generic_string())); + std::filesystem::remove(test_file); +} + +BOOST_AUTO_TEST_CASE(collector_test) +{ + BOOST_TEST_MESSAGE("Test version " << VERSION); + const std::filesystem::path target_file{ "test.tar" }, + test_input_dir{ "test_input" }; + std::filesystem::remove(target_file); + std::filesystem::remove_all(test_input_dir); + std::filesystem::create_directory(test_input_dir); + const auto create_file_result = std::async([&test_input_dir]() { + std::this_thread::sleep_for(5s); + std::ofstream test_file{ test_input_dir / "should_be_in_it" }; + test_file << "Hello World." << std::endl; + test_file.flush(); + + std::ofstream{ test_input_dir / "Core.TEST.00.lz4" }; + std::ofstream{ test_input_dir / "might_not_be_in_it" }; + }); + Collector collector(service, test_input_dir, target_file); + BOOST_TEST(collector()); + BOOST_TEST(std::filesystem::is_regular_file(target_file)); + std::filesystem::remove(target_file); + std::filesystem::remove_all(test_input_dir); +} diff --git a/version.hpp.in b/version.hpp.in new file mode 100644 index 0000000..0c206a9 --- /dev/null +++ b/version.hpp.in @@ -0,0 +1,5 @@ +#define VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define VERSION_PATCH @PROJECT_VERSION_PATCH@ +#define VERSION_TWEAK @PROJECT_VERSION_TWEAK@ +#define VERSION "@PROJECT_VERSION@"