Skip to content

Commit

Permalink
Implement mediacopier cli tool
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickziegler committed Feb 8, 2024
1 parent fef15a6 commit 4db424b
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 26 deletions.
3 changes: 3 additions & 0 deletions mediacopier-cli/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
find_package(CLI11 REQUIRED)
find_package(spdlog 1.9.2 REQUIRED)
find_package(toml11 REQUIRED)

set(TARGET_NAME mediacopier-cli)

Expand All @@ -11,6 +12,8 @@ set_target_properties(${TARGET_NAME} PROPERTIES
OUTPUT_NAME ${EXECUTABLE_NAME})

target_sources(${TARGET_NAME} PRIVATE
"source/config.cpp"
"source/config.hpp"
"source/main.cpp")

target_link_libraries(${TARGET_NAME} PUBLIC
Expand Down
97 changes: 97 additions & 0 deletions mediacopier-cli/source/config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "config.hpp"

#include <CLI/CLI.hpp>
/* Copyright (C) 2023 Patrick Ziegler <zipat@proton.me>
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include <spdlog/spdlog.h>
#include <toml.hpp>

#include <mediacopier/version.hpp>

#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;

constexpr static const char* DEFAULT_PATTERN = "%Y/%W/IMG_%Y%m%d_%H%M%S";
constexpr static const char* PERSISTENT_CONFIG = ".mediacopier";

std::pair<Config::ParseResult, int> Config::parseArgs(int argc, char *argv[]) noexcept
{
CLI::App app{mediacopier::MEDIACOPIER_PROJECT_NAME};
app.set_version_flag("-v,--version", mediacopier::MEDIACOPIER_VERSION);

auto copyapp = app.add_subcommand("copy", "Copy some files");
copyapp->callback([this]() { cmd = Command::Copy; });
copyapp->add_option("inputDir", inputDir)->required()->check(CLI::ExistingDirectory);
copyapp->add_option("outputDir", outputDir)->required();
copyapp->add_option("-p,--pattern", pattern);

auto moveapp = app.add_subcommand("move", "Move some files");
moveapp->callback([this]() { cmd = Command::Move; });
moveapp->add_option("inputDir", inputDir)->required()->check(CLI::ExistingDirectory);
moveapp->add_option("outputDir", outputDir)->required();
moveapp->add_option("-p,--pattern", pattern);

int ret = 0;
try {
app.parse(argc, argv);
return {ParseResult::Continue, ret};
} catch(const CLI::Success &err) {
// printing help message or version info
ret = app.exit(err);
return {ParseResult::Break, ret};
} catch(const CLI::ParseError &err) {
ret = app.exit(err);
return {ParseResult::Break, ret};
}
}

void Config::loadPersistentConfig() noexcept
{
auto persistentConfigFile = outputDir / PERSISTENT_CONFIG;
if (!fs::exists(persistentConfigFile)) {
return;
}
try {
const auto data = toml::parse(persistentConfigFile);
if (pattern.empty()) {
pattern = toml::find<std::string>(data, "pattern");
}
} catch (const std::exception& err) {
spdlog::error("Failed to parse config: " + std::string{err.what()});
}
}

void Config::storePersistentConfig() const noexcept
{
if (!fs::exists(outputDir)) {
return;
}
auto persistentConfigFile = outputDir / PERSISTENT_CONFIG;
toml::value lastRun{{"pattern", pattern}};
std::ofstream out{persistentConfigFile};
out << "# this file is updated on every run of mediacopier"
<< ", manual changes might be lost\n" << lastRun;
}

void Config::finalize() noexcept
{
if (pattern.empty()) {
pattern = DEFAULT_PATTERN;
}
}
40 changes: 40 additions & 0 deletions mediacopier-cli/source/config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* Copyright (C) 2023 Patrick Ziegler <zipat@proton.me>
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include <filesystem>

struct Config
{
enum class Command {
Copy,
Move
};

enum class ParseResult {
Continue,
Break
};

std::pair<ParseResult, int> parseArgs(int argc, char *argv[]) noexcept;
void loadPersistentConfig() noexcept;
void storePersistentConfig() const noexcept;
void finalize() noexcept;

Command cmd = Command::Copy;
std::filesystem::path inputDir;
std::filesystem::path outputDir;
std::string pattern;
};
126 changes: 100 additions & 26 deletions mediacopier-cli/source/main.cpp
Original file line number Diff line number Diff line change
@@ -1,38 +1,112 @@
#include <CLI/CLI.hpp>
/* Copyright (C) 2023 Patrick Ziegler <zipat@proton.me>
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <mediacopier/file_info_factory.hpp>
#include <mediacopier/file_register.hpp>
#include <mediacopier/operation_copy_jpeg.hpp>
#include <mediacopier/operation_move_jpeg.hpp>

int main(int argc, char** argv) {
CLI::App app{"App description"};
//argv = app.ensure_utf8(argv);
#include <range/v3/view/filter.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/iterator_range.hpp>

app.set_version_flag("-v,--version", "1.0.0");
#include <spdlog/spdlog.h>

std::string pattern = "default";
std::string filename = "default file";
std::string command = "none";
bool slim = false;
#include <atomic>
#include <csignal>

auto copyapp = app.add_subcommand("copy", "Copy some files");
copyapp->add_option("-p,--pattern", pattern, "A help string");
copyapp->callback([&command]() { command = "copy"; });
auto opt = copyapp->add_option("filename", filename);
opt->required();
#include "config.hpp"

auto moveapp = app.add_subcommand("move", "Move some files");
moveapp->add_option("-p,--pattern", pattern, "A help string");
namespace fs = std::filesystem;
namespace mc = mediacopier;

auto simapp = app.add_subcommand("simulate", "Do nothing, just simulate what would happen");
simapp->add_option("-p,--pattern", pattern, "A help string");
static volatile std::atomic<bool> operationCancelled(false);

auto guiapp = app.add_subcommand("gui");
guiapp->add_flag("--slim", slim, "Instantiate the slim version");
auto valid_media_files(const fs::path& path)
{
static auto is_regular_file = [](const fs::directory_entry& path) {
return fs::is_regular_file(path);
};
static auto is_valid = [](const mc::FileInfoPtr& file) {
return file != nullptr;
};
return ranges::make_iterator_range(
fs::recursive_directory_iterator(path),
fs::recursive_directory_iterator())
| ranges::views::filter(is_regular_file)
| ranges::views::transform(mc::to_file_info_ptr)
| ranges::views::filter(is_valid);
}

template <typename Operation>
auto exec(const Config& config) -> void
{
// register callback for graceful shutdown via CTRL-C
std::signal(SIGINT, [](int) -> void {
operationCancelled.store(true);
});

spdlog::info("Checking input directory..");
auto reg = mc::FileRegister{config.outputDir, config.pattern};
std::optional<fs::path> dest;

for (auto file : valid_media_files(config.inputDir)) {
if (operationCancelled.load()) {
spdlog::warn("Operation was cancelled..");
break;
}
try {
if ((dest = reg.add(file)).has_value()) {
spdlog::info("Processing: {} -> {}",
file->path().string(),
dest.value().string());
Operation op(dest.value());
file->accept(op);
}
} catch (const std::exception& err) {
spdlog::error(err.what());
}
}

spdlog::info("Removing duplicates in destination directory..");
reg.removeDuplicates();

spdlog::info("Done");
std::signal(SIGINT, SIG_DFL);
}

int main(int argc, char *argv[])
{
Config config;
const auto& [res, ret] = config.parseArgs(argc, argv);
if (res != Config::ParseResult::Continue) {
return ret;
}
config.loadPersistentConfig();
config.finalize();

CLI11_PARSE(app, argc, argv);
switch (config.cmd) {
case Config::Command::Copy:
exec<mediacopier::FileOperationCopyJpeg>(config);
break;
case Config::Command::Move:
exec<mediacopier::FileOperationMoveJpeg>(config);
break;
}

std::cout << pattern << std::endl
<< filename << std::endl
<< command << std::endl
<< slim << std::endl;
config.storePersistentConfig();
return 0;
}

0 comments on commit 4db424b

Please sign in to comment.