1
0
forked from blue/mlc

some more thoughts about config

This commit is contained in:
Blue 2023-10-07 19:36:20 -03:00
parent 870842f63d
commit 6c7356598a
Signed by untrusted user: blue
GPG Key ID: 9B203B252A63EE38
10 changed files with 284 additions and 31 deletions

View File

@ -1,3 +1,10 @@
function(make_includable input_file output_file)
file(READ ${input_file} content)
set(delim "for_c++_include")
set(content "R\"${delim}(\n${content})${delim}\"")
file(WRITE ${output_file} "${content}")
endfunction(make_includable)
set(SOURCES set(SOURCES
main.cpp main.cpp
help.cpp help.cpp
@ -20,3 +27,6 @@ set(HEADERS
) )
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
make_includable(default.conf ${CMAKE_BINARY_DIR}/generated/default.conf)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR})

View File

@ -71,13 +71,12 @@ void Collection::convert(const std::string& outPath) {
switch (entry.status().type()) { switch (entry.status().type()) {
case fs::file_type::regular: { case fs::file_type::regular: {
fs::path sourcePath = entry.path(); fs::path sourcePath = entry.path();
fs::path dstPath = out / sourcePath.filename(); fs::path dstPath = out / sourcePath.stem();
if (isMusic(sourcePath)) { if (isMusic(sourcePath))
dstPath.replace_extension(".mp3");
taskManager->queueJob(sourcePath, dstPath); taskManager->queueJob(sourcePath, dstPath);
} else { else
fs::copy_file(sourcePath, dstPath, fs::copy_options::overwrite_existing); fs::copy_file(sourcePath, dstPath, fs::copy_options::overwrite_existing);
}
//std::cout << sourcePath << " => " << dstPath << std::endl; //std::cout << sourcePath << " => " << dstPath << std::endl;
} break; } break;
case fs::file_type::directory: { case fs::file_type::directory: {

23
src/default.conf Normal file
View File

@ -0,0 +1,23 @@
# This is a default autogenerated MLC config
# The syntax goes like this:
# key value
#
# Use # sign for comments
#
# All printed comented out values are default values
#
# You should have one of this files as ~/.config/mlc.conf,
# if you have started mlc at least once.
# This is going to be used every time you launch mlc,
# so, edit it if you wish to change default behaviour.
#
# Alternatively, you can launch `mlc -c customConfig.conf`
# if you wish to override ~/.config/mlc.conf file
# Log level, regulates minimal message severity to be printed
# Allowed values are: debug, info, minor, major, warning, error, fatal
#level info
# Output type
# Allowed values are: mp3 (more comming soon)
#type mp3

View File

@ -1,5 +1,15 @@
#include "loggable.h" #include "loggable.h"
constexpr std::array<std::string_view, Loggable::_sevetirySize> levels({
"debug",
"info",
"minor",
"major",
"warning",
"error",
"fatal"
});
Loggable::Loggable(Loggable::Severity severity): Loggable::Loggable(Loggable::Severity severity):
currentSeverity(severity), currentSeverity(severity),
history() history()
@ -16,3 +26,11 @@ void Loggable::log(Loggable::Severity severity, const std::string& comment) cons
std::list<std::pair<Loggable::Severity, std::string>> Loggable::getHistory() const { std::list<std::pair<Loggable::Severity, std::string>> Loggable::getHistory() const {
return history; return history;
} }
Loggable::Severity Loggable::stringToSeverity(const std::string& line) {
unsigned char dist = std::distance(levels.begin(), std::find(levels.begin(), levels.end(), line));
if (dist < _sevetirySize)
return static_cast<Severity>(dist);
return _sevetirySize;
}

View File

@ -2,6 +2,9 @@
#include <list> #include <list>
#include <string> #include <string>
#include <array>
#include <string_view>
#include <algorithm>
class Loggable { class Loggable {
public: public:
@ -12,7 +15,8 @@ public:
major, major,
warning, warning,
error, error,
fatal fatal,
_sevetirySize
}; };
typedef std::pair<Severity, std::string> Message; typedef std::pair<Severity, std::string> Message;
@ -22,6 +26,8 @@ public:
void log (Severity severity, const std::string& comment) const; void log (Severity severity, const std::string& comment) const;
std::list<Message> getHistory() const; std::list<Message> getHistory() const;
static Severity stringToSeverity(const std::string& line);
private: private:
const Severity currentSeverity; const Severity currentSeverity;
mutable std::list<Message> history; mutable std::list<Message> history;

View File

@ -1,6 +1,8 @@
#include <iostream> #include <iostream>
#include <fstream>
#include <string> #include <string>
#include <chrono> #include <chrono>
#include <memory>
#include <unistd.h> #include <unistd.h>
#include "FLAC/stream_decoder.h" #include "FLAC/stream_decoder.h"
@ -12,14 +14,14 @@
#include "settings.h" #include "settings.h"
int main(int argc, char **argv) { int main(int argc, char **argv) {
Settings settings(argc, argv); std::shared_ptr<Settings> settings = std::make_shared<Settings>(argc, argv);
switch (settings.getAction()) { switch (settings->getAction()) {
case Settings::help: case Settings::help:
printHelp(); printHelp();
return 0; return 0;
case Settings::printConfig: case Settings::config:
//printHelp(); std::cout << settings->defaultConfig() << std::endl;
return 0; return 0;
case Settings::convert: case Settings::convert:
std::cout << "Converting..." << std::endl; std::cout << "Converting..." << std::endl;
@ -29,12 +31,23 @@ int main(int argc, char **argv) {
return -1; return -1;
} }
TaskManager taskManager; if (!settings->readConfigFile() && settings->isConfigDefault()) {
std::string defaultConfigPath = settings->getConfigPath();
std::ofstream file(defaultConfigPath, std::ios::out | std::ios::trunc);
if (file.is_open()) {
std::cout << "Writing default config to " << defaultConfigPath << std::endl;
file << settings->defaultConfig();
} else {
std::cout << "Couldn't open " << defaultConfigPath << " to write default config" << std::endl;
}
}
TaskManager taskManager(settings);
taskManager.start(); taskManager.start();
std::chrono::time_point start = std::chrono::system_clock::now(); std::chrono::time_point start = std::chrono::system_clock::now();
Collection collection(settings.getInput(), &taskManager); Collection collection(settings->getInput(), &taskManager);
collection.convert(settings.getOutput()); collection.convert(settings->getOutput());
taskManager.printProgress(); taskManager.printProgress();
taskManager.wait(); taskManager.wait();

View File

@ -1,14 +1,32 @@
#include "settings.h" #include "settings.h"
static const char* defaultConfig =
#include "generated/default.conf"
;
constexpr std::array<std::string_view, Settings::_actionsSize> actions({ constexpr std::array<std::string_view, Settings::_actionsSize> actions({
"convert", "convert",
"help", "help",
"printConfig" "config"
}); });
constexpr std::array<std::string_view, Settings::_optionsSize> options({
"level",
"type"
});
constexpr std::array<std::string_view, Settings::_typesSize> types({
"mp3"
});
bool is_space(char ch){
return std::isspace(static_cast<unsigned char>(ch));
}
Settings::Settings(int argc, char ** argv): Settings::Settings(int argc, char ** argv):
arguments(), arguments(),
action(std::nullopt), action(std::nullopt),
outputType(std::nullopt),
input(std::nullopt), input(std::nullopt),
output(std::nullopt), output(std::nullopt),
logLevel(std::nullopt) logLevel(std::nullopt)
@ -23,10 +41,9 @@ void Settings::parseArguments() {
for (int i = 0; i < arguments.size(); ++i) { for (int i = 0; i < arguments.size(); ++i) {
const std::string_view& arg = arguments[i]; const std::string_view& arg = arguments[i];
if (i == 0) { if (i == 0) {
std::string_view act = stripFlags(arg); Action act = stringToAction(stripFlags(arg));
int dist = std::distance(actions.begin(), std::find(actions.begin(), actions.end(), act)); if (act < _actionsSize) {
if (dist < _actionsSize) { action = act;
action = static_cast<Settings::Action>(dist);
continue; continue;
} }
} }
@ -52,6 +69,13 @@ Settings::Action Settings::getAction() const {
return convert; return convert;
} }
Settings::Type Settings::getType() const {
if (outputType.has_value())
return outputType.value();
else
return mp3;
}
std::string Settings::getInput() const { std::string Settings::getInput() const {
if (input.has_value()) if (input.has_value())
return input.value(); return input.value();
@ -66,6 +90,17 @@ std::string Settings::getOutput() const {
return ""; return "";
} }
std::string Settings::getConfigPath() const {
if (configPath.has_value())
return configPath.value();
else
return std::string(getenv("HOME")) + "/.config/mlc.conf";
}
bool Settings::isConfigDefault() const {
return !configPath.has_value();
}
Loggable::Severity Settings::getLogLevel() const { Loggable::Severity Settings::getLogLevel() const {
if (logLevel.has_value()) if (logLevel.has_value())
return logLevel.value(); return logLevel.value();
@ -82,3 +117,101 @@ std::string_view Settings::stripFlags(const std::string_view& option) {
} }
return option; return option;
} }
void Settings::strip(std::string& line) {
line.erase(line.begin(), std::find_if(line.begin(), line.end(), std::not_fn(is_space)));
line.erase(std::find_if(line.rbegin(), line.rend(), std::not_fn(is_space)).base(), line.end());
}
void Settings::stripComment(std::string& line) {
std::string::size_type index = line.find('#');
if (index != std::string::npos)
line.erase(index);
strip(line);
}
std::string Settings::defaultConfig() const {
return ::defaultConfig + 1;
}
bool Settings::readConfigFile() {
std::ifstream file;
file.open(getConfigPath(), std::ios::in);
if (file.is_open()){
std::string tp;
while(getline(file, tp)) {
stripComment(tp);
if (!tp.empty())
readConfigLine(tp);
}
file.close();
return true;
}
return false;
}
void Settings::readConfigLine(const std::string& line) {
std::istringstream stream(line);
std::string key;
if (!(stream >> key))
return;
Option option = stringToOption(key);
if (option == _optionsSize)
return;
switch (option) {
case level: {
std::string lv;
if (stream >> lv) {
Loggable::Severity level = Loggable::stringToSeverity(lv);
if (level < Loggable::_sevetirySize)
logLevel = level;
}
} break;
case type: {
std::string lv;
if (stream >> lv) {
Type type = stringToType(lv);
if (type < _typesSize)
outputType = type;
}
} break;
default:
break;
}
}
Settings::Action Settings::stringToAction(const std::string& source) {
unsigned char dist = std::distance(actions.begin(), std::find(actions.begin(), actions.end(), source));
if (dist < _actionsSize)
return static_cast<Action>(dist);
return _actionsSize;
}
Settings::Action Settings::stringToAction(const std::string_view& source) {
unsigned char dist = std::distance(actions.begin(), std::find(actions.begin(), actions.end(), source));
if (dist < _actionsSize)
return static_cast<Action>(dist);
return _actionsSize;
}
Settings::Option Settings::stringToOption(const std::string& source) {
unsigned char dist = std::distance(options.begin(), std::find(options.begin(), options.end(), source));
if (dist < _optionsSize)
return static_cast<Option>(dist);
return _optionsSize;
}
Settings::Type Settings::stringToType(const std::string& source) {
unsigned char dist = std::distance(types.begin(), std::find(types.begin(), types.end(), source));
if (dist < _typesSize)
return static_cast<Type>(dist);
return _typesSize;
}

View File

@ -6,6 +6,11 @@
#include <vector> #include <vector>
#include <array> #include <array>
#include <algorithm> #include <algorithm>
#include <iostream>
#include <fstream>
#include <functional>
#include <cctype>
#include <sstream>
#include "loggable.h" #include "loggable.h"
@ -14,26 +19,53 @@ public:
enum Action { enum Action {
convert, convert,
help, help,
printConfig, config,
_actionsSize _actionsSize
}; };
enum Type {
mp3,
_typesSize
};
enum Option {
level,
type,
_optionsSize
};
Settings(int argc, char **argv); Settings(int argc, char **argv);
std::string getInput() const; std::string getInput() const;
std::string getOutput() const; std::string getOutput() const;
std::string getConfigPath() const;
bool isConfigDefault() const;
Loggable::Severity getLogLevel() const; Loggable::Severity getLogLevel() const;
Type getType() const;
Action getAction() const; Action getAction() const;
bool readConfigFile();
void readConfigLine(const std::string& line);
std::string defaultConfig() const;
static Action stringToAction(const std::string& source);
static Action stringToAction(const std::string_view& source);
static Type stringToType(const std::string& source);
static Option stringToOption(const std::string& source);
private: private:
void parseArguments(); void parseArguments();
static std::string_view stripFlags(const std::string_view& option); static std::string_view stripFlags(const std::string_view& option);
static void strip(std::string& line);
static void stripComment(std::string& line);
private: private:
std::vector<std::string_view> arguments; std::vector<std::string_view> arguments;
std::optional<Action> action; std::optional<Action> action;
std::optional<Type> outputType;
std::optional<std::string> input; std::optional<std::string> input;
std::optional<std::string> output; std::optional<std::string> output;
std::optional<Loggable::Severity> logLevel; std::optional<Loggable::Severity> logLevel;
std::optional<std::string> configPath;
}; };

View File

@ -22,7 +22,8 @@ constexpr const std::array<std::string_view, Loggable::fatal + 1> logHeaders({
/*fatal*/ "FATAL: " /*fatal*/ "FATAL: "
}); });
TaskManager::TaskManager(): TaskManager::TaskManager(const std::shared_ptr<Settings>& settings):
settings(settings),
busyThreads(0), busyThreads(0),
maxTasks(0), maxTasks(0),
completeTasks(0), completeTasks(0),
@ -42,7 +43,7 @@ TaskManager::TaskManager():
TaskManager::~TaskManager() { TaskManager::~TaskManager() {
} }
void TaskManager::queueJob(const std::string& source, const std::string& destination) { void TaskManager::queueJob(const std::filesystem::path& source, const std::filesystem::path& destination) {
{ {
std::unique_lock<std::mutex> lock(queueMutex); std::unique_lock<std::mutex> lock(queueMutex);
jobs.emplace(source, destination); jobs.emplace(source, destination);
@ -83,7 +84,13 @@ void TaskManager::loop() {
jobs.pop(); jobs.pop();
} }
JobResult result = job(pair.first, pair.second); JobResult result;
switch (settings->getType()) {
case Settings::mp3:
result = mp3Job(pair.first, pair.second + ".mp3");
break;
}
++completeTasks; ++completeTasks;
printProgress(result, pair.first, pair.second); printProgress(result, pair.first, pair.second);
--busyThreads; --busyThreads;
@ -117,7 +124,7 @@ void TaskManager::wait() {
waitConditional.wait(lock, boundWaitCondition); waitConditional.wait(lock, boundWaitCondition);
} }
TaskManager::JobResult TaskManager::job(const std::string& source, const std::string& destination) { TaskManager::JobResult TaskManager::mp3Job(const std::filesystem::path& source, const std::filesystem::path& destination) {
FLACtoMP3 convertor(Loggable::debug); FLACtoMP3 convertor(Loggable::debug);
convertor.setInputFile(source); convertor.setInputFile(source);
convertor.setOutputFile(destination); convertor.setOutputFile(destination);
@ -130,7 +137,11 @@ void TaskManager::printProgress() const {
std::cout << "\r" << completeTasks << "/" << maxTasks << std::flush; std::cout << "\r" << completeTasks << "/" << maxTasks << std::flush;
} }
void TaskManager::printProgress(const TaskManager::JobResult& result, const std::string& source, const std::string& destination) const { void TaskManager::printProgress(
const TaskManager::JobResult& result,
const std::filesystem::path& source,
const std::filesystem::path& destination) const
{
std::unique_lock<std::mutex> lock(printMutex); std::unique_lock<std::mutex> lock(printMutex);
if (result.first) { if (result.first) {
if (result.second.size() > 0) { if (result.second.size() > 0) {
@ -144,7 +155,11 @@ void TaskManager::printProgress(const TaskManager::JobResult& result, const std:
std::cout << "\r" << completeTasks << "/" << maxTasks << std::flush; std::cout << "\r" << completeTasks << "/" << maxTasks << std::flush;
} }
void TaskManager::printLog(const TaskManager::JobResult& result, const std::string& source, const std::string& destination) { void TaskManager::printLog(
const TaskManager::JobResult& result,
const std::filesystem::path& source,
const std::filesystem::path& destination)
{
std::cout << "Source: \t" << source << std::endl; std::cout << "Source: \t" << source << std::endl;
std::cout << "Destination: \t" << destination << std::endl; std::cout << "Destination: \t" << destination << std::endl;
for (const Loggable::Message& msg : result.second) { for (const Loggable::Message& msg : result.second) {

View File

@ -4,6 +4,7 @@
#include <condition_variable> #include <condition_variable>
#include <thread> #include <thread>
#include <functional> #include <functional>
#include <filesystem>
#include <vector> #include <vector>
#include <list> #include <list>
#include <queue> #include <queue>
@ -11,30 +12,33 @@
#include <atomic> #include <atomic>
#include <iostream> #include <iostream>
#include <array> #include <array>
#include <memory>
#include "loggable.h" #include "loggable.h"
#include "settings.h"
class TaskManager { class TaskManager {
typedef std::pair<bool, std::list<Loggable::Message>> JobResult; typedef std::pair<bool, std::list<Loggable::Message>> JobResult;
public: public:
TaskManager(); TaskManager(const std::shared_ptr<Settings>& settings);
~TaskManager(); ~TaskManager();
void start(); void start();
void queueJob(const std::string& source, const std::string& destination); void queueJob(const std::filesystem::path& source, const std::filesystem::path& destination);
void stop(); void stop();
bool busy() const; bool busy() const;
void wait(); void wait();
void printProgress() const; void printProgress() const;
void printProgress(const JobResult& result, const std::string& source, const std::string& destination) const; void printProgress(const JobResult& result, const std::filesystem::path& source, const std::filesystem::path& destination) const;
private: private:
void loop(); void loop();
bool loopCondition() const; bool loopCondition() const;
bool waitCondition() const; bool waitCondition() const;
static JobResult job(const std::string& source, const std::string& destination); static JobResult mp3Job(const std::filesystem::path& source, const std::filesystem::path& destination);
static void printLog(const JobResult& result, const std::string& source, const std::string& destination); static void printLog(const JobResult& result, const std::filesystem::path& source, const std::filesystem::path& destination);
private: private:
std::shared_ptr<Settings> settings;
std::atomic<uint32_t> busyThreads; std::atomic<uint32_t> busyThreads;
std::atomic<uint32_t> maxTasks; std::atomic<uint32_t> maxTasks;
std::atomic<uint32_t> completeTasks; std::atomic<uint32_t> completeTasks;
@ -45,7 +49,7 @@ private:
std::condition_variable loopConditional; std::condition_variable loopConditional;
std::condition_variable waitConditional; std::condition_variable waitConditional;
std::vector<std::thread> threads; std::vector<std::thread> threads;
std::queue<std::pair<std::string, std::string>> jobs; std::queue<std::pair<std::filesystem::path, std::filesystem::path>> jobs;
std::function<bool()> boundLoopCondition; std::function<bool()> boundLoopCondition;
std::function<bool()> boundWaitCondition; std::function<bool()> boundWaitCondition;