351 lines
10 KiB
C++
351 lines
10 KiB
C++
/*
|
|
Copyright© 2021 John Sennesael
|
|
|
|
UsenetSearch 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.
|
|
|
|
UsenetSearch 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 UsenetSearch. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "usenetsearch/Application.h"
|
|
|
|
#include "usenetsearch/Logger.h"
|
|
#include "usenetsearch/StringUtils.h"
|
|
|
|
#include <csignal>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace usenetsearch {
|
|
|
|
sig_atomic_t _signalled_to_stop{0};
|
|
|
|
void sigHandler(int)
|
|
{
|
|
_signalled_to_stop = 1;
|
|
}
|
|
|
|
std::unique_ptr<Application> Application::m_instance;
|
|
|
|
void Application::AddBooleanOption(
|
|
char option,
|
|
const std::string& help,
|
|
std::function<void(bool)> onParse,
|
|
bool defaultValue)
|
|
{
|
|
auto val = std::make_shared<CommandLineOptionValue<bool>>();
|
|
val->type = CommandLineOptionType::Boolean;
|
|
val->option = option;
|
|
val->helpText = help;
|
|
val->value = defaultValue;
|
|
val->onParse = onParse;
|
|
m_commandLineArguments.emplace_back(std::move(val));
|
|
}
|
|
|
|
void Application::AddIntegerOption(
|
|
char option,
|
|
const std::string& help,
|
|
std::function<void(int)> onParse,
|
|
int defaultValue)
|
|
{
|
|
auto val = std::make_shared<CommandLineOptionValue<int>>();
|
|
val->type = CommandLineOptionType::Integer;
|
|
val->option = option;
|
|
val->helpText = help;
|
|
val->value = defaultValue;
|
|
val->onParse = onParse;
|
|
m_commandLineArguments.emplace_back(std::move(val));
|
|
}
|
|
|
|
void Application::AddFileOption(
|
|
char option,
|
|
const std::string& help,
|
|
std::function<void(std::filesystem::path)> onParse,
|
|
std::filesystem::path defaultValue)
|
|
{
|
|
auto val = std::make_shared<CommandLineOptionValue<
|
|
std::filesystem::path>>();
|
|
val->type = CommandLineOptionType::Path;
|
|
val->option = option;
|
|
val->helpText = help;
|
|
val->value = defaultValue;
|
|
val->onParse = onParse;
|
|
m_commandLineArguments.emplace_back(std::move(val));
|
|
}
|
|
|
|
void Application::AddStringOption(
|
|
char option,
|
|
const std::string& help,
|
|
std::function<void(std::string)> onParse,
|
|
std::string defaultValue)
|
|
{
|
|
auto val = std::make_shared<CommandLineOptionValue<
|
|
std::string>>();
|
|
val->type = CommandLineOptionType::String;
|
|
val->option = option;
|
|
val->helpText = help;
|
|
val->value = defaultValue;
|
|
val->onParse = onParse;
|
|
m_commandLineArguments.emplace_back(std::move(val));
|
|
}
|
|
|
|
Application& Application::Get()
|
|
{
|
|
if (m_instance == nullptr)
|
|
{
|
|
m_instance = std::make_unique<Application>();
|
|
}
|
|
return *m_instance;
|
|
}
|
|
|
|
bool Application::ShouldStop() const
|
|
{
|
|
const bool result = _signalled_to_stop == 1;
|
|
if (result)
|
|
{
|
|
Logger::Get().Info(LOGID("Application"), "Been signalled to stop.");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Application::Usage(const std::string& programName)
|
|
{
|
|
std::cout << "UsenetSearch - usenet search indexer" << std::endl;
|
|
std::cout << "Copyright© 2021 John Sennesael" << std::endl << std::endl;
|
|
std::cout << "Usage:" << std::endl << std::endl;
|
|
std::cout << programName;
|
|
std::cout << "\t";
|
|
// List default options
|
|
std::cout << "[-c <config filename>] ";
|
|
std::cout << "[-h] ";
|
|
// List custom options
|
|
for (const auto& optionValue: m_commandLineArguments)
|
|
{
|
|
switch (optionValue->type)
|
|
{
|
|
case CommandLineOptionType::Boolean:
|
|
std::cout << "[-" << optionValue->option << "] ";
|
|
break;
|
|
case CommandLineOptionType::Integer:
|
|
std::cout << "[-" << optionValue->option << " <number>] ";
|
|
break;
|
|
case CommandLineOptionType::Path:
|
|
std::cout << "[-" << optionValue->option << " <path>] ";
|
|
break;
|
|
case CommandLineOptionType::String:
|
|
std::cout << "[-" << optionValue->option << " <value>] ";
|
|
break;
|
|
}
|
|
}
|
|
std::cout << std::endl << std::endl;
|
|
// List standard detailed help
|
|
std::cout << "-c <file>\tSets configuration file to use" << std::endl;
|
|
std::cout << "-h\t\tShow help (this text)." << std::endl;
|
|
// List custom options detailed help
|
|
for (const auto& optionValue: m_commandLineArguments)
|
|
{
|
|
switch (optionValue->type)
|
|
{
|
|
case CommandLineOptionType::Boolean:
|
|
std::cout << "-" << optionValue->option << "\t"
|
|
<< optionValue->helpText << std::endl;
|
|
break;
|
|
case CommandLineOptionType::Integer:
|
|
std::cout << "-" << optionValue->option << " <number>\t"
|
|
<< optionValue->helpText << std::endl;
|
|
break;
|
|
case CommandLineOptionType::Path:
|
|
std::cout << "-" << optionValue->option << " <path>\t"
|
|
<< optionValue->helpText << std::endl;
|
|
break;
|
|
case CommandLineOptionType::String:
|
|
std::cout << "-" << optionValue->option << " <value>\t"
|
|
<< optionValue->helpText << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
Application::Application() : m_filter(m_config)
|
|
{
|
|
std::cout.setf(std::ios::unitbuf);
|
|
}
|
|
|
|
bool Application::CanRun() const
|
|
{
|
|
return m_canRun;
|
|
}
|
|
|
|
Configuration& Application::GetConfig()
|
|
{
|
|
return m_config;
|
|
}
|
|
|
|
Database& Application::GetDb()
|
|
{
|
|
return m_db;
|
|
}
|
|
|
|
void Application::ExecuteCustomOption(
|
|
std::shared_ptr<CommandLineOption>& opt,
|
|
const std::string& value)
|
|
{
|
|
switch (opt->type)
|
|
{
|
|
case CommandLineOptionType::Boolean:
|
|
{
|
|
std::shared_ptr<CommandLineOptionValue<bool>> castedOption =
|
|
std::dynamic_pointer_cast<CommandLineOptionValue<bool>>(
|
|
opt
|
|
);
|
|
if (castedOption == nullptr)
|
|
{
|
|
throw std::runtime_error(
|
|
"Could not cast cli arg to the correct type."
|
|
);
|
|
}
|
|
castedOption->onParse(true);
|
|
}
|
|
break;
|
|
case CommandLineOptionType::Integer:
|
|
{
|
|
std::shared_ptr<CommandLineOptionValue<int>> castedOption =
|
|
std::dynamic_pointer_cast<CommandLineOptionValue<int>>(
|
|
opt
|
|
);
|
|
if (castedOption == nullptr)
|
|
{
|
|
throw std::runtime_error(
|
|
"Could not cast cli arg to the correct type."
|
|
);
|
|
}
|
|
castedOption->onParse(std::stoi(value));
|
|
}
|
|
break;
|
|
case CommandLineOptionType::String:
|
|
{
|
|
std::shared_ptr<CommandLineOptionValue<std::string>>
|
|
castedOption = std::dynamic_pointer_cast<
|
|
CommandLineOptionValue<std::string>>(
|
|
opt
|
|
);
|
|
if (castedOption == nullptr)
|
|
{
|
|
throw std::runtime_error(
|
|
"Could not cast cli arg to the correct type."
|
|
);
|
|
}
|
|
castedOption->onParse(value);
|
|
}
|
|
break;
|
|
case CommandLineOptionType::Path:
|
|
{
|
|
std::shared_ptr<CommandLineOptionValue<
|
|
std::filesystem::path>> castedOption =
|
|
std::dynamic_pointer_cast<
|
|
CommandLineOptionValue<
|
|
std::filesystem::path
|
|
>
|
|
>(opt);
|
|
if (castedOption == nullptr)
|
|
{
|
|
throw std::runtime_error(
|
|
"Could not cast cli arg to the correct type."
|
|
);
|
|
}
|
|
castedOption->onParse(value);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Filter& Application::GetFilter()
|
|
{
|
|
return m_filter;
|
|
}
|
|
|
|
bool Application::Init(int argc, char* argv[])
|
|
{
|
|
ParseArgs(argc, argv);
|
|
if (!m_canRun) return false;
|
|
// Install signal handlers.
|
|
void (*prev_handler)(int);
|
|
prev_handler = signal(SIGINT, sigHandler);
|
|
prev_handler = signal(SIGTERM, sigHandler);
|
|
prev_handler = signal(SIGTSTP, sigHandler);
|
|
// Read config, setup db
|
|
m_config.Open(m_configFile);
|
|
m_db.MaxTreeDepth(m_config.MaxTreeDepth());
|
|
m_db.Open(m_config.DatabasePath());
|
|
m_filter.Init();
|
|
return true;
|
|
}
|
|
|
|
void Application::ParseArgs(int argc, char* argv[])
|
|
{
|
|
// Parse args.
|
|
for (int argn = 1; argn != argc; ++argn)
|
|
{
|
|
std::string curr_opt = argv[argn];
|
|
std::string next_opt = "";
|
|
if (argn+1 < argc) next_opt=argv[argn+1];
|
|
if (curr_opt == "-c")
|
|
{
|
|
if ((next_opt == "") or (StringStartsWith("-", next_opt)))
|
|
{
|
|
std::cerr << "Missing argument to -c option." << std::endl;
|
|
Usage(argv[0]);
|
|
return;
|
|
}
|
|
argn++;
|
|
m_configFile = argv[argn];
|
|
}
|
|
else if (curr_opt == "-h")
|
|
{
|
|
Usage(argv[0]);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Parse custom options.
|
|
bool parsed{false};
|
|
for (auto optionValue: m_commandLineArguments)
|
|
{
|
|
if ((std::string{"-"} + optionValue->option) == curr_opt)
|
|
{
|
|
if (optionValue->type == CommandLineOptionType::Boolean)
|
|
{
|
|
ExecuteCustomOption(optionValue);
|
|
}
|
|
else
|
|
{
|
|
argn++;
|
|
ExecuteCustomOption(optionValue, argv[argn]);
|
|
}
|
|
parsed = true;
|
|
}
|
|
}
|
|
if (!parsed)
|
|
{
|
|
std::cerr << "Invalid option: " << curr_opt << std::endl;
|
|
Usage(argv[0]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
m_canRun = true;
|
|
}
|
|
|
|
} // namespace usenetsearch
|