|
/********************************************************************
|
|
* CONFIDENTIAL - All source code is the propriety and confidential *
|
|
* information of David Sorber. *
|
|
* *
|
|
* Copyright 2024 David Sorber *
|
|
* Unpublished -- all rights reserved under the copyright laws *
|
|
* of the United States. *
|
|
********************************************************************/
|
|
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "CLI11.hpp"
|
|
#include "indicators.hpp"
|
|
|
|
#include "CopyManagerOptions.h"
|
|
#include "CopyManager.h"
|
|
#include "Logger.h"
|
|
|
|
namespace sfs = std::filesystem;
|
|
|
|
const std::string VERSION("v0.2.1");
|
|
|
|
const std::string BOLD{"\033[1m"};
|
|
const std::string ENDC{"\033[0m"};
|
|
const std::string RED{"\033[31m"};
|
|
const std::string YELLOW{"\033[33m"};
|
|
const std::string PURPLE{"\033[35m"};
|
|
const std::string LBLUE{"\033[1;34m"};
|
|
const std::string UP_ONE = "\033[1A";
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
std::cout << "copytool -- " << VERSION << "\n" << std::endl;
|
|
CLI::App app{"copytool"};
|
|
|
|
// Required fundamental options
|
|
std::string srcPath;
|
|
std::string destPath;
|
|
|
|
// Add required positional arguments
|
|
app.add_option("source_path", srcPath,
|
|
"source path; copy files FROM this path")->required()
|
|
->check(CLI::ExistingDirectory);
|
|
app.add_option("dest_path", destPath,
|
|
"destination path; copy files TO this path")->required()
|
|
->check(CLI::ExistingDirectory);
|
|
|
|
// Logging related options
|
|
bool enableLog = DBS::CopyManagerOptions::DEFAULT_ENABLE_LOG;
|
|
bool logToStderr = DBS::CopyManagerOptions::DEFAULT_LOG_TO_STDERR;
|
|
std::string logFilePath{DBS::CopyManagerOptions::DEFAULT_LOG_FILE_PATH};
|
|
int logLevel = DBS::CopyManagerOptions::DEFAULT_LOG_LEVEL;
|
|
|
|
// Add logging options
|
|
app.add_flag("--enable-log", enableLog, "enable the log (default: disabled)");
|
|
app.add_flag("--log-to-stderr", logToStderr, "write log messages to stderr");
|
|
app.add_option("-l,--log-file-path", logFilePath, "log file path");
|
|
app.add_option("--log-level", logLevel,
|
|
"default log message level (error=0, warn=1, info=2, debug=3, trace=4)")
|
|
->check(CLI::Range(0, 4));
|
|
|
|
// Other options
|
|
uint32_t flowControlThreshold = DBS::CopyManagerOptions::DEFAULT_FC_THRESHOLD;
|
|
uint32_t numCopyThreads = DBS::CopyManagerOptions::DEFAULT_NUM_COPY_THREADS;
|
|
bool copyTopLevel = DBS::CopyManagerOptions::DEFAULT_COPY_TOPLEVEL;
|
|
bool preservePerms = DBS::CopyManagerOptions::DEFAULT_PRESERVE_PERMS;
|
|
bool preserveOwner = DBS::CopyManagerOptions::DEFAULT_PRESERVE_OWNER;
|
|
bool preserveGroup = DBS::CopyManagerOptions::DEFAULT_PRESERVE_GROUP;
|
|
|
|
app.add_option("--flow-control-threshold", flowControlThreshold,
|
|
"flow control threshold (default: "
|
|
+ std::to_string(flowControlThreshold) + ")")
|
|
->check(CLI::Range(100, 65535));
|
|
app.add_option("-n,--num-copy-threads", numCopyThreads,
|
|
"number of copy threads to spawn (default: "
|
|
+ std::to_string(numCopyThreads) + ")")
|
|
->check(CLI::Range(1, 512));
|
|
app.add_flag("--copy-top-level", copyTopLevel,
|
|
"copy top level source directory to destination rather then "
|
|
"the contents of the top level source directory (which is "
|
|
"the default behavior)");
|
|
|
|
app.add_flag("-p,--preserve-perms", preservePerms,
|
|
"preserve file/directory permissions");
|
|
app.add_flag("-o,--preserve-owner", preserveOwner,
|
|
"preserve file/directory owner");
|
|
app.add_flag("-g,--preserve-group", preserveGroup,
|
|
"preserve file/directory group");
|
|
|
|
|
|
// Parse options
|
|
CLI11_PARSE(app, argc, argv);
|
|
|
|
// Build options object
|
|
DBS::CopyManagerOptions options(srcPath,
|
|
destPath,
|
|
enableLog,
|
|
logToStderr,
|
|
logFilePath,
|
|
logLevel,
|
|
flowControlThreshold,
|
|
numCopyThreads,
|
|
copyTopLevel,
|
|
preservePerms,
|
|
preserveOwner,
|
|
preserveGroup);
|
|
|
|
// Prime the logger
|
|
try
|
|
{
|
|
DBS::Logger::getInstance(DBS::off, &options);
|
|
}
|
|
catch (const std::runtime_error& excep)
|
|
{
|
|
std::cerr << BOLD << RED << "ERROR: " << ENDC << excep.what()
|
|
<< "\n" << std::endl;
|
|
return -1;
|
|
}
|
|
|
|
LOG(DBS::info) << "copytool -- DBS";
|
|
|
|
// Display a warning if the specified number of copy threads exceeds the
|
|
// number of cores in the system
|
|
uint32_t numCores = std::thread::hardware_concurrency();
|
|
if (numCopyThreads > numCores)
|
|
{
|
|
std::cout << BOLD << YELLOW << "WARNING: " << ENDC
|
|
<< "The number of copy threads specified ("
|
|
<< numCopyThreads << ") exceeds the number of cores in the "
|
|
<< "system (" << numCores << "); proceed with caution.\n"
|
|
<< std::endl;
|
|
}
|
|
|
|
// Create the copy manager and run it
|
|
DBS::CopyManager manager(options);
|
|
|
|
// The callback structure requires a global
|
|
indicators::BlockProgressBar pBarEta{
|
|
indicators::option::BarWidth{80},
|
|
indicators::option::Start{"["},
|
|
indicators::option::End{"]"},
|
|
indicators::option::PrefixText{"Complete: "},
|
|
indicators::option::ShowElapsedTime{true},
|
|
indicators::option::ShowRemainingTime{true},
|
|
};
|
|
|
|
// Run the manager
|
|
manager.run();
|
|
|
|
indicators::show_console_cursor(false);
|
|
while (manager.isRunning())
|
|
{
|
|
// Calculate percent complete
|
|
double percent = (static_cast<double>(manager.getProcessedEntries()) /
|
|
manager.getTotalEntries()) * 100;
|
|
|
|
pBarEta.set_progress(percent);
|
|
pBarEta.set_option(indicators::option::PostfixText{ " -- " +
|
|
std::to_string(manager.getProcessedEntries()) + "/" +
|
|
std::to_string(manager.getTotalEntries())
|
|
});
|
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
std::cout << std::endl << UP_ONE;
|
|
}
|
|
|
|
int rc = manager.waitForComplete();
|
|
|
|
if (manager.iterationError())
|
|
{
|
|
std::cerr << BOLD << RED << "ERROR: " << ENDC << "An iteration exception "
|
|
<< "occurred that was likely caused by a permissions error. "
|
|
<< "Please rerun as root (using sudo)." << std::endl;
|
|
}
|
|
else
|
|
{
|
|
pBarEta.set_progress(100.0);
|
|
pBarEta.set_option(indicators::option::PostfixText{" -- " +
|
|
std::to_string(manager.getProcessedEntries()) + "/" +
|
|
std::to_string(manager.getTotalEntries())
|
|
});
|
|
pBarEta.mark_as_completed();
|
|
indicators::show_console_cursor(true);
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
// Display warning to user if any invalid/unsupported directory entries were
|
|
// found while copying
|
|
if (manager.getInvalidEntries() > 0)
|
|
{
|
|
std::cout << BOLD << YELLOW << "WARNING: " << ENDC
|
|
<< manager.getInvalidEntries() << " unsupported directory "
|
|
<< "entries (symlinks, FIFOs, etc.) were NOT copied."
|
|
<< std::endl;
|
|
}
|
|
|
|
std::cout << std::endl;
|
|
|
|
return rc;
|
|
}
|