Centralized default path for config file, fixes #525

This commit is contained in:
emeric
2024-10-04 10:07:59 +02:00
parent 55c7dea8d8
commit 0f626f7716
5 changed files with 193 additions and 147 deletions
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 Emeric Poupon
*
* This file is part of LMS.
*
* LMS 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.
*
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <filesystem>
namespace lms::core
{
static inline const std::filesystem::path sysconfDirectory{ "/etc" };
}
+160 -144
View File
@@ -30,6 +30,7 @@
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "core/SystemPaths.hpp"
#include "core/WtLogger.hpp"
#include "database/Db.hpp"
#include "database/Session.hpp"
@@ -50,180 +51,195 @@
namespace lms
{
std::size_t getThreadCount()
namespace
{
const unsigned long configHttpServerThreadCount{ core::Service<core::IConfig>::get()->getULong("http-server-thread-count", 0) };
// Reserve at least 2 threads since we still have some blocking IO (for example when reading from ffmpeg)
return configHttpServerThreadCount ? configHttpServerThreadCount : std::max<unsigned long>(2, std::thread::hardware_concurrency());
}
core::logging::Severity getLogMinSeverity()
{
std::string_view minSeverity{ core::Service<core::IConfig>::get()->getString("log-min-severity", "info") };
if (minSeverity == "debug")
return core::logging::Severity::DEBUG;
else if (minSeverity == "info")
return core::logging::Severity::INFO;
else if (minSeverity == "warning")
return core::logging::Severity::WARNING;
else if (minSeverity == "error")
return core::logging::Severity::ERROR;
else if (minSeverity == "fatal")
return core::logging::Severity::FATAL;
throw core::LmsException{ "Invalid config value for 'log-min-severity'" };
}
std::optional<core::tracing::Level> getTracingLevel()
{
std::string_view tracingLevel{ core::Service<core::IConfig>::get()->getString("tracing-level", "disabled") };
if (tracingLevel == "disabled")
return std::nullopt;
else if (tracingLevel == "overview")
return core::tracing::Level::Overview;
else if (tracingLevel == "detailed")
return core::tracing::Level::Detailed;
throw core::LmsException{ "Invalid config value for 'tracing-level'" };
}
std::vector<std::string> generateWtConfig(std::string execPath, core::logging::Severity minSeverity)
{
std::vector<std::string> args;
const std::filesystem::path wtConfigPath{ core::Service<core::IConfig>::get()->getPath("working-dir") / "wt_config.xml" };
const std::filesystem::path wtLogFilePath{ core::Service<core::IConfig>::get()->getPath("log-file", "/var/log/lms.log") };
const std::filesystem::path wtAccessLogFilePath{ core::Service<core::IConfig>::get()->getPath("access-log-file", "/var/log/lms.access.log") };
const std::filesystem::path wtResourcesPath{ core::Service<core::IConfig>::get()->getPath("wt-resources", "/usr/share/Wt/resources") };
args.push_back(execPath);
args.push_back("--config=" + wtConfigPath.string());
args.push_back("--docroot=" + std::string{ core::Service<core::IConfig>::get()->getString("docroot") });
args.push_back("--approot=" + std::string{ core::Service<core::IConfig>::get()->getString("approot") });
args.push_back("--deploy-path=" + std::string{ core::Service<core::IConfig>::get()->getString("deploy-path", "/") });
if (!wtResourcesPath.empty())
args.push_back("--resources-dir=" + wtResourcesPath.string());
if (core::Service<core::IConfig>::get()->getBool("tls-enable", false))
std::size_t getThreadCount()
{
args.push_back("--https-port=" + std::to_string(core::Service<core::IConfig>::get()->getULong("listen-port", 5082)));
args.push_back("--https-address=" + std::string{ core::Service<core::IConfig>::get()->getString("listen-addr", "0.0.0.0") });
args.push_back("--ssl-certificate=" + std::string{ core::Service<core::IConfig>::get()->getString("tls-cert") });
args.push_back("--ssl-private-key=" + std::string{ core::Service<core::IConfig>::get()->getString("tls-key") });
args.push_back("--ssl-tmp-dh=" + std::string{ core::Service<core::IConfig>::get()->getString("tls-dh") });
}
else
{
args.push_back("--http-port=" + std::to_string(core::Service<core::IConfig>::get()->getULong("listen-port", 5082)));
args.push_back("--http-address=" + std::string{ core::Service<core::IConfig>::get()->getString("listen-addr", "0.0.0.0") });
const unsigned long configHttpServerThreadCount{ core::Service<core::IConfig>::get()->getULong("http-server-thread-count", 0) };
// Reserve at least 2 threads since we still have some blocking IO (for example when reading from ffmpeg)
return configHttpServerThreadCount ? configHttpServerThreadCount : std::max<unsigned long>(2, std::thread::hardware_concurrency());
}
if (!wtAccessLogFilePath.empty())
args.push_back("--accesslog=" + wtAccessLogFilePath.string());
args.push_back("--threads=" + std::to_string(getThreadCount()));
// Generate the wt_config.xml file
boost::property_tree::ptree pt;
pt.put("server.application-settings.<xmlattr>.location", "*");
pt.put("server.application-settings.log-file", wtLogFilePath.string());
// log-config
pt.put("server.application-settings.log-config", core::logging::WtLogger::computeLogConfig(minSeverity));
pt.put("server.application-settings.behind-reverse-proxy", core::Service<core::IConfig>::get()->getBool("behind-reverse-proxy", false));
core::logging::Severity getLogMinSeverity()
{
boost::property_tree::ptree viewport;
viewport.put("<xmlattr>.name", "viewport");
viewport.put("<xmlattr>.content", "width=device-width, initial-scale=1, user-scalable=no");
pt.add_child("server.application-settings.head-matter.meta", viewport);
}
{
boost::property_tree::ptree themeColor;
themeColor.put("<xmlattr>.name", "theme-color");
themeColor.put("<xmlattr>.content", "#303030");
pt.add_child("server.application-settings.head-matter.meta", themeColor);
std::string_view minSeverity{ core::Service<core::IConfig>::get()->getString("log-min-severity", "info") };
if (minSeverity == "debug")
return core::logging::Severity::DEBUG;
else if (minSeverity == "info")
return core::logging::Severity::INFO;
else if (minSeverity == "warning")
return core::logging::Severity::WARNING;
else if (minSeverity == "error")
return core::logging::Severity::ERROR;
else if (minSeverity == "fatal")
return core::logging::Severity::FATAL;
throw core::LmsException{ "Invalid config value for 'log-min-severity'" };
}
std::optional<core::tracing::Level> getTracingLevel()
{
std::ofstream oss{ wtConfigPath.string().c_str(), std::ios::out };
if (!oss)
throw core::LmsException{ "Can't open '" + wtConfigPath.string() + "' for writing!" };
std::string_view tracingLevel{ core::Service<core::IConfig>::get()->getString("tracing-level", "disabled") };
boost::property_tree::xml_parser::write_xml(oss, pt);
if (tracingLevel == "disabled")
return std::nullopt;
else if (tracingLevel == "overview")
return core::tracing::Level::Overview;
else if (tracingLevel == "detailed")
return core::tracing::Level::Detailed;
if (!oss)
throw core::LmsException{ "Can't write in file '" + wtConfigPath.string() + "', no space left?" };
throw core::LmsException{ "Invalid config value for 'tracing-level'" };
}
return args;
}
std::vector<std::string> generateWtConfig(std::string execPath, core::logging::Severity minSeverity)
{
std::vector<std::string> args;
void proxyScannerEventsToApplication(scanner::IScannerService& scanner, Wt::WServer& server)
{
auto postAll{ [](Wt::WServer& server, std::function<void()> cb) {
server.postAll([cb = std::move(cb)] {
// may be nullptr, see https://redmine.webtoolkit.eu/issues/8202
if (LmsApp)
cb();
});
} };
const std::filesystem::path wtConfigPath{ core::Service<core::IConfig>::get()->getPath("working-dir") / "wt_config.xml" };
const std::filesystem::path wtLogFilePath{ core::Service<core::IConfig>::get()->getPath("log-file", "/var/log/lms.log") };
const std::filesystem::path wtAccessLogFilePath{ core::Service<core::IConfig>::get()->getPath("access-log-file", "/var/log/lms.access.log") };
const std::filesystem::path wtResourcesPath{ core::Service<core::IConfig>::get()->getPath("wt-resources", "/usr/share/Wt/resources") };
scanner.getEvents().scanAborted.connect([&] {
postAll(server, [] {
LmsApp->getScannerEvents().scanAborted.emit();
LmsApp->triggerUpdate();
});
});
args.push_back(execPath);
args.push_back("--config=" + wtConfigPath.string());
args.push_back("--docroot=" + std::string{ core::Service<core::IConfig>::get()->getString("docroot") });
args.push_back("--approot=" + std::string{ core::Service<core::IConfig>::get()->getString("approot") });
args.push_back("--deploy-path=" + std::string{ core::Service<core::IConfig>::get()->getString("deploy-path", "/") });
if (!wtResourcesPath.empty())
args.push_back("--resources-dir=" + wtResourcesPath.string());
scanner.getEvents().scanStarted.connect([&] {
postAll(server, [] {
LmsApp->getScannerEvents().scanStarted.emit();
LmsApp->triggerUpdate();
});
});
if (core::Service<core::IConfig>::get()->getBool("tls-enable", false))
{
args.push_back("--https-port=" + std::to_string(core::Service<core::IConfig>::get()->getULong("listen-port", 5082)));
args.push_back("--https-address=" + std::string{ core::Service<core::IConfig>::get()->getString("listen-addr", "0.0.0.0") });
args.push_back("--ssl-certificate=" + std::string{ core::Service<core::IConfig>::get()->getString("tls-cert") });
args.push_back("--ssl-private-key=" + std::string{ core::Service<core::IConfig>::get()->getString("tls-key") });
args.push_back("--ssl-tmp-dh=" + std::string{ core::Service<core::IConfig>::get()->getString("tls-dh") });
}
else
{
args.push_back("--http-port=" + std::to_string(core::Service<core::IConfig>::get()->getULong("listen-port", 5082)));
args.push_back("--http-address=" + std::string{ core::Service<core::IConfig>::get()->getString("listen-addr", "0.0.0.0") });
}
scanner.getEvents().scanComplete.connect([&](const scanner::ScanStats& stats) {
postAll(server, [=] {
LmsApp->getScannerEvents().scanComplete.emit(stats);
LmsApp->triggerUpdate();
});
});
if (!wtAccessLogFilePath.empty())
args.push_back("--accesslog=" + wtAccessLogFilePath.string());
scanner.getEvents().scanInProgress.connect([&](const scanner::ScanStepStats& stats) {
postAll(server, [=] {
LmsApp->getScannerEvents().scanInProgress.emit(stats);
LmsApp->triggerUpdate();
});
});
args.push_back("--threads=" + std::to_string(getThreadCount()));
scanner.getEvents().scanScheduled.connect([&](const Wt::WDateTime dateTime) {
postAll(server, [=] {
LmsApp->getScannerEvents().scanScheduled.emit(dateTime);
LmsApp->triggerUpdate();
// Generate the wt_config.xml file
boost::property_tree::ptree pt;
pt.put("server.application-settings.<xmlattr>.location", "*");
pt.put("server.application-settings.log-file", wtLogFilePath.string());
// log-config
pt.put("server.application-settings.log-config", core::logging::WtLogger::computeLogConfig(minSeverity));
pt.put("server.application-settings.behind-reverse-proxy", core::Service<core::IConfig>::get()->getBool("behind-reverse-proxy", false));
{
boost::property_tree::ptree viewport;
viewport.put("<xmlattr>.name", "viewport");
viewport.put("<xmlattr>.content", "width=device-width, initial-scale=1, user-scalable=no");
pt.add_child("server.application-settings.head-matter.meta", viewport);
}
{
boost::property_tree::ptree themeColor;
themeColor.put("<xmlattr>.name", "theme-color");
themeColor.put("<xmlattr>.content", "#303030");
pt.add_child("server.application-settings.head-matter.meta", themeColor);
}
{
std::ofstream oss{ wtConfigPath.string().c_str(), std::ios::out };
if (!oss)
throw core::LmsException{ "Can't open '" + wtConfigPath.string() + "' for writing!" };
boost::property_tree::xml_parser::write_xml(oss, pt);
if (!oss)
throw core::LmsException{ "Can't write in file '" + wtConfigPath.string() + "', no space left?" };
}
return args;
}
void proxyScannerEventsToApplication(scanner::IScannerService& scanner, Wt::WServer& server)
{
auto postAll{ [](Wt::WServer& server, std::function<void()> cb) {
server.postAll([cb = std::move(cb)] {
// may be nullptr, see https://redmine.webtoolkit.eu/issues/8202
if (LmsApp)
cb();
});
} };
scanner.getEvents().scanAborted.connect([&] {
postAll(server, [] {
LmsApp->getScannerEvents().scanAborted.emit();
LmsApp->triggerUpdate();
});
});
});
}
scanner.getEvents().scanStarted.connect([&] {
postAll(server, [] {
LmsApp->getScannerEvents().scanStarted.emit();
LmsApp->triggerUpdate();
});
});
scanner.getEvents().scanComplete.connect([&](const scanner::ScanStats& stats) {
postAll(server, [=] {
LmsApp->getScannerEvents().scanComplete.emit(stats);
LmsApp->triggerUpdate();
});
});
scanner.getEvents().scanInProgress.connect([&](const scanner::ScanStepStats& stats) {
postAll(server, [=] {
LmsApp->getScannerEvents().scanInProgress.emit(stats);
LmsApp->triggerUpdate();
});
});
scanner.getEvents().scanScheduled.connect([&](const Wt::WDateTime dateTime) {
postAll(server, [=] {
LmsApp->getScannerEvents().scanScheduled.emit(dateTime);
LmsApp->triggerUpdate();
});
});
}
} // namespace
int main(int argc, char* argv[])
{
std::filesystem::path configFilePath{ "/etc/lms.conf" };
std::filesystem::path configFilePath{ core::sysconfDirectory / "lms.conf" };
int res{ EXIT_FAILURE };
assert(argc > 0);
assert(argv[0] != NULL);
auto displayUsage{ [&](std::ostream& os) {
os << "Usage:\t" << argv[0] << "\t[conf_file]\n\n"
<< "Options:\n"
<< "\tconf_file:\t path to the LMS configuration file (defaults to " << configFilePath << ")\n\n";
} };
if (argc == 2)
configFilePath = std::string(argv[1], 0, 256);
{
const std::string_view arg{ argv[1] };
if (arg == "-h" || arg == "--help")
{
displayUsage(std::cout);
return EXIT_SUCCESS;
}
configFilePath = std::string(arg, 0, 256);
}
else if (argc > 2)
{
std::cerr << "Usage:\t" << argv[0] << "\t[conf_file]\n\n"
<< "Options:\n"
<< "\tconf_file:\t path to the LMS configuration file (defaults to " << configFilePath << ")\n\n";
displayUsage(std::cerr);
return EXIT_FAILURE;
}
+2 -1
View File
@@ -28,6 +28,7 @@
#include "core/ILogger.hpp"
#include "core/Service.hpp"
#include "core/StreamLogger.hpp"
#include "core/SystemPaths.hpp"
#include "database/Db.hpp"
#include "database/Release.hpp"
#include "database/Session.hpp"
@@ -66,7 +67,7 @@ int main(int argc, char* argv[])
core::Service<core::logging::ILogger> logger{ std::make_unique<core::logging::StreamLogger>(std::cout) };
po::options_description desc{ "Allowed options" };
desc.add_options()("help,h", "print usage message")("conf,c", po::value<std::string>()->default_value("/etc/lms.conf"), "LMS config file")("default-cover,d", po::value<std::string>(), "Default cover path")("tracks,t", "dump covers for tracks")("size,s", po::value<unsigned>()->default_value(512), "Requested cover size")("quality,q", po::value<unsigned>()->default_value(75), "JPEG quality (1-100)");
desc.add_options()("help,h", "print usage message")("conf,c", po::value<std::string>()->default_value(core::sysconfDirectory / "lms.conf"), "LMS config file")("default-cover,d", po::value<std::string>(), "Default cover path")("tracks,t", "dump covers for tracks")("size,s", po::value<unsigned>()->default_value(512), "Requested cover size")("quality,q", po::value<unsigned>()->default_value(75), "JPEG quality (1-100)");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
+2 -1
View File
@@ -32,6 +32,7 @@
#include "core/Random.hpp"
#include "core/Service.hpp"
#include "core/StreamLogger.hpp"
#include "core/SystemPaths.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Db.hpp"
@@ -177,7 +178,7 @@ int main(int argc, char* argv[])
const GeneratorParameters defaultParams;
program_options::options_description options{ "Options" };
options.add_options()("conf,c", program_options::value<std::string>()->default_value("/etc/lms.conf"), "lms config file")("media-library-count", program_options::value<unsigned>()->default_value(defaultParams.mediaLibraryCount), "Number of media libraries to use")("release-count-per-batch", program_options::value<unsigned>()->default_value(defaultParams.releaseCountPerBatch), "Number of releases to generate before committing transaction")("release-count", program_options::value<unsigned>()->default_value(defaultParams.releaseCount), "Number of releases to generate")("track-count-per-release", program_options::value<unsigned>()->default_value(defaultParams.trackCountPerRelease), "Number of tracks per release")("compilation-ratio", program_options::value<float>()->default_value(defaultParams.compilationRatio), "Compilation ratio (compilation means all tracks have a different artist)")("track-path", program_options::value<std::string>()->required(), "Path of a valid track file, that will be used for all generated tracks")("genre-count", program_options::value<unsigned>()->default_value(defaultParams.genreCount), "Number of genres to generate")("genre-count-per-track", program_options::value<unsigned>()->default_value(defaultParams.genreCountPerTrack), "Number of genres to assign to each track")("mood-count", program_options::value<unsigned>()->default_value(defaultParams.moodCount), "Number of moods to generate")("mood-count-per-track", program_options::value<unsigned>()->default_value(defaultParams.moodCountPerTrack), "Number of moods to assign to each track")("help,h", "produce help message");
options.add_options()("conf,c", program_options::value<std::string>()->default_value(core::sysconfDirectory / "lms.conf"), "lms config file")("media-library-count", program_options::value<unsigned>()->default_value(defaultParams.mediaLibraryCount), "Number of media libraries to use")("release-count-per-batch", program_options::value<unsigned>()->default_value(defaultParams.releaseCountPerBatch), "Number of releases to generate before committing transaction")("release-count", program_options::value<unsigned>()->default_value(defaultParams.releaseCount), "Number of releases to generate")("track-count-per-release", program_options::value<unsigned>()->default_value(defaultParams.trackCountPerRelease), "Number of tracks per release")("compilation-ratio", program_options::value<float>()->default_value(defaultParams.compilationRatio), "Compilation ratio (compilation means all tracks have a different artist)")("track-path", program_options::value<std::string>()->required(), "Path of a valid track file, that will be used for all generated tracks")("genre-count", program_options::value<unsigned>()->default_value(defaultParams.genreCount), "Number of genres to generate")("genre-count-per-track", program_options::value<unsigned>()->default_value(defaultParams.genreCountPerTrack), "Number of genres to assign to each track")("mood-count", program_options::value<unsigned>()->default_value(defaultParams.moodCount), "Number of moods to generate")("mood-count-per-track", program_options::value<unsigned>()->default_value(defaultParams.moodCountPerTrack), "Number of moods to assign to each track")("help,h", "produce help message");
program_options::variables_map vm;
program_options::store(program_options::parse_command_line(argc, argv, options), vm);
@@ -27,6 +27,7 @@
#include "core/IConfig.hpp"
#include "core/Service.hpp"
#include "core/StreamLogger.hpp"
#include "core/SystemPaths.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Db.hpp"
@@ -132,7 +133,7 @@ int main(int argc, char* argv[])
core::Service<core::logging::ILogger> logger{ std::make_unique<core::logging::StreamLogger>(std::cout) };
po::options_description desc{ "Allowed options" };
desc.add_options()("help,h", "print usage message")("conf,c", po::value<std::string>()->default_value("/etc/lms.conf"), "LMS config file")("artists,a", "Display recommendation for artists")("releases,r", "Display recommendation for releases")("tracks,t", "Display recommendation for tracks")("max,m", po::value<unsigned>()->default_value(3), "Max similarity result count");
desc.add_options()("help,h", "print usage message")("conf,c", po::value<std::string>()->default_value(core::sysconfDirectory / "lms.conf"), "LMS config file")("artists,a", "Display recommendation for artists")("releases,r", "Display recommendation for releases")("tracks,t", "Display recommendation for tracks")("max,m", po::value<unsigned>()->default_value(3), "Max similarity result count");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);