diff --git a/include/lsl/common.h b/include/lsl/common.h index 3c81c798..8eb25e24 100644 --- a/include/lsl/common.h +++ b/include/lsl/common.h @@ -227,3 +227,23 @@ extern LIBLSL_C_API double lsl_local_clock(); * no free() method is available (e.g., in some scripting languages). */ extern LIBLSL_C_API void lsl_destroy_string(char *s); + +/** + * Set the name of the configuration file to be used. + * + * This is a global setting that will be used by all LSL + * after this function is called. If, and only if, this function + * is called before the first call to any other LSL function. + */ +extern LIBLSL_C_API void lsl_set_config_filename(const char *filename); + +/** + * Set the content of the configuration file to be used. + * + * This is a global setting that will be used by all LSL + * after this function is called. If, and only if, this function + * is called before the first call to any other LSL function. + * + * @note the configuration content is wiped after LSL has initialized. + */ +extern LIBLSL_C_API void lsl_set_config_content(const char *content); diff --git a/src/api_config.cpp b/src/api_config.cpp index fc84a474..7b088573 100644 --- a/src/api_config.cpp +++ b/src/api_config.cpp @@ -1,7 +1,6 @@ #include "api_config.h" #include "common.h" #include "util/cast.hpp" -#include "util/inireader.hpp" #include "util/strfuns.hpp" #include #include @@ -10,6 +9,7 @@ #include #include #include +#include using namespace lsl; @@ -52,9 +52,36 @@ bool file_is_readable(const std::string &filename) { } api_config::api_config() { + // first check to see if a config content was provided + if (!api_config_content_.empty()) { + try { + // if so, load it from the content + load_from_content(api_config_content_); + // free the content this can only be called once + api_config_content_.clear(); + // config loaded successfully, so return + return; + } catch (std::exception &e) { + LOG_F(ERROR, "Error parsing config content: '%s', rolling back to defaults", e.what()); + // clear the content, it was invalid anyway + api_config_content_.clear(); + } + } + // otherwise, load the config from a file + // for each config file location under consideration... std::vector filenames; + // NOLINTNEXTLINE(concurrency-mt-unsafe) + if (!api_config_filename_.empty()) { + // if a config file name was set, use it if it is readable + if (file_is_readable(api_config_filename_)) { + filenames.insert(filenames.begin(), api_config_filename_); + } else { + LOG_F(ERROR, "Config file %s not found", api_config_filename_.c_str()); + } + } + // NOLINTNEXTLINE(concurrency-mt-unsafe) if (auto *cfgpath = getenv("LSLAPICFG")) { std::string envcfg(cfgpath); @@ -82,7 +109,6 @@ api_config::api_config() { load_from_file(); } - void api_config::load_from_file(const std::string &filename) { try { INI pt; @@ -92,7 +118,35 @@ void api_config::load_from_file(const std::string &filename) { pt.load(infile); } } + api_config::load(pt); + // log config filename only after setting the verbosity level and all config has been read + if (!filename.empty()) + LOG_F(INFO, "Configuration loaded from %s", filename.c_str()); + else + LOG_F(INFO, "Loaded default config"); + + } catch (std::exception &e) { + LOG_F(ERROR, "Error parsing config file '%s': '%s', rolling back to defaults", + filename.c_str(), e.what()); + // any error: assign defaults + load_from_file(); + // and rethrow + throw e; + } +} +void api_config::load_from_content(const std::string &content) { + // load the content into an INI object + INI pt; + if (!content.empty()) { + std::istringstream content_stream(content); + pt.load(content_stream); + } + api_config::load(pt); + LOG_F(INFO, "Configuration loaded from content"); +} + +void api_config::load(INI &pt) { // read the [log] settings int log_level = pt.get("log.level", (int)loguru::Verbosity_INFO); if (log_level < -3 || log_level > 9) @@ -264,20 +318,7 @@ void api_config::load_from_file(const std::string &filename) { smoothing_halftime_ = pt.get("tuning.SmoothingHalftime", 90.0F); force_default_timestamps_ = pt.get("tuning.ForceDefaultTimestamps", false); - // log config filename only after setting the verbosity level and all config has been read - if (!filename.empty()) - LOG_F(INFO, "Configuration loaded from %s", filename.c_str()); - else - LOG_F(INFO, "Loaded default config"); - - } catch (std::exception &e) { - LOG_F(ERROR, "Error parsing config file '%s': '%s', rolling back to defaults", - filename.c_str(), e.what()); - // any error: assign defaults - load_from_file(); - // and rethrow - throw e; - } + } static std::once_flag api_config_once_flag; diff --git a/src/api_config.h b/src/api_config.h index 1c9d3c16..6cb987cd 100644 --- a/src/api_config.h +++ b/src/api_config.h @@ -2,6 +2,7 @@ #define API_CONFIG_H #include "netinterfaces.h" +#include "util/inireader.hpp" #include #include #include @@ -14,9 +15,11 @@ namespace lsl { * A configuration object: holds all the configurable settings of liblsl. * These settings can be set via a configuration file that is automatically searched * by stream providers and recipients in a series of locations: - * - lsl_api.cfg - * - ~/lsl_api/lsl_api.cfg - * - /etc/lsl_api/lsl_api.cfg + * - First, the content set via `lsl_set_config_content()` + * - Second, the file set via `lsl_set_config_filename()` + * - Third, the file `lsl_api.cfg` in the current working directory + * - Fourth, the file `lsl_api.cfg` in the home directory (e.g., `~/lsl_api/lsl_api.cfg`) + * - Fifth, the file `lsl_api.cfg` in the system configuration directory (e.g., `/etc/lsl_api/lsl_api.cfg`) * * Note that, while in some cases it might seem sufficient to override configurations * only for a subset of machines involved in a recording session (e.g., the servers), @@ -76,6 +79,33 @@ class api_config { bool allow_ipv6() const { return allow_ipv6_; } bool allow_ipv4() const { return allow_ipv4_; } + + + /** + * @brief Set the configuration directly from a string. + * + * This allows passing in configuration content directly rather than from a file. + * This MUST be called before the first call to get_instance() to have any effect. + */ + static void set_api_config_content(const std::string &content) { + api_config_content_ = content; + } + + /** + * @brief An additional settings path to load configuration from. + */ + const std::string &api_config_filename() const { return api_config_filename_; } + + /** + * @brief Set the config file name used to load the settings. + * + * This MUST be called before the first call to get_instance() to have any effect. + */ + static void set_api_config_filename(const std::string &filename) { + api_config_filename_ = filename; + } + + /** * @brief The range or scope of stream lookup when using multicast-based discovery * @@ -221,6 +251,22 @@ class api_config { */ void load_from_file(const std::string &filename = std::string()); + /** + * @brief Load a configuration from a string. + * @param content The configuration content to parse + */ + void load_from_content(const std::string &content); + + /** + * @brief Load the configuration from an INI object. + * @param pt The INI object to load the configuration from + */ + void load(INI &pt); + + // config overrides + static std::string api_config_filename_; + static std::string api_config_content_; + // core parameters bool allow_ipv6_, allow_ipv4_; uint16_t base_port_; @@ -258,6 +304,11 @@ class api_config { float smoothing_halftime_; bool force_default_timestamps_; }; + +// initialize configuration file name +inline std::string api_config::api_config_filename_ = ""; +inline std::string api_config::api_config_content_ = ""; + } // namespace lsl #endif diff --git a/src/common.cpp b/src/common.cpp index c8307f20..9914dd37 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -26,6 +26,18 @@ LIBLSL_C_API int32_t lsl_protocol_version() { return lsl::api_config::get_instance()->use_protocol_version(); } +LIBLSL_C_API void lsl_set_config_filename(const char *filename) { + if (filename) { + lsl::api_config::set_api_config_filename(filename); + } +} + +LIBLSL_C_API void lsl_set_config_content(const char *content) { + if (content) { + lsl::api_config::set_api_config_content(content); + } +} + LIBLSL_C_API int32_t lsl_library_version() { return LSL_LIBRARY_VERSION; } LIBLSL_C_API double lsl_local_clock() {