| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2025 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 10 | * included LICENSE.LGPL file. | ||
| 11 | */ | ||
| 12 | #include "fpp-json.h" | ||
| 13 | |||
| 14 | #include <atomic> | ||
| 15 | #include <cerrno> | ||
| 16 | #include <condition_variable> | ||
| 17 | #include <cstring> | ||
| 18 | #include <ctype.h> | ||
| 19 | #include <fcntl.h> | ||
| 20 | #include <map> | ||
| 21 | #include <memory> | ||
| 22 | #include <mutex> | ||
| 23 | #include <pwd.h> | ||
| 24 | #include <shared_mutex> | ||
| 25 | #include <stdio.h> | ||
| 26 | #include <stdlib.h> | ||
| 27 | #include <string> | ||
| 28 | #include <sys/file.h> | ||
| 29 | #include <unistd.h> | ||
| 30 | #include <utility> | ||
| 31 | #include <vector> | ||
| 32 | |||
| 33 | #include "common.h" | ||
| 34 | #include "log.h" | ||
| 35 | #include "settings.h" | ||
| 36 | |||
| 37 | #ifdef PLATFORM_OSX | ||
| 38 | #include <mach-o/dyld.h> | ||
| 39 | #endif | ||
| 40 | #include <sys/fcntl.h> | ||
| 41 | |||
| 42 | // Built on first use and never destroyed. Both are reached from other static | ||
| 43 | // objects: AES67Manager's constructor asks for the media directory during | ||
| 44 | // static initialization, and ~PluginManager -> CommandManager::Cleanup() asks | ||
| 45 | // again when fppd leaves through exit(). File-scope strings could be used | ||
| 46 | // there before they were constructed, or after they were destroyed. | ||
| 47 | 23105 | static std::string& FPPBinDir() { | |
| 48 |
5/10✓ Branch 0 taken 2385 times.
✓ Branch 1 taken 20720 times.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2385 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2385 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
23105 | static std::string* dir = new std::string(); |
| 49 | 23105 | return *dir; | |
| 50 | } | ||
| 51 | 1436103 | static std::string& FPPMediaDir() { | |
| 52 |
5/10✓ Branch 0 taken 2385 times.
✓ Branch 1 taken 1433718 times.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2385 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2385 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
1436103 | static std::string* dir = new std::string(); |
| 53 | 1436103 | return *dir; | |
| 54 | } | ||
| 55 | |||
| 56 | 23105 | std::string getFPPDDir(const std::string& path) { | |
| 57 | 23105 | std::string& FPP_BIN_DIR = FPPBinDir(); | |
| 58 |
2/2✓ Branch 0 taken 2385 times.
✓ Branch 1 taken 20720 times.
|
23105 | if (FPP_BIN_DIR == "") { |
| 59 | char exePath[2048]; | ||
| 60 | 2385 | exePath[0] = 0; | |
| 61 | #ifdef PLATFORM_OSX | ||
| 62 | uint32_t len = sizeof(exePath); | ||
| 63 | _NSGetExecutablePath(exePath, &len); | ||
| 64 | #else | ||
| 65 | 2385 | ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath) - 1); | |
| 66 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2385 times.
|
2385 | if (len == -1) { |
| 67 | ✗ | LogWarn(VB_GENERAL, "readlink /proc/self/exe failed: %s\n", strerror(errno)); | |
| 68 | ✗ | exePath[0] = '\0'; | |
| 69 | } else { | ||
| 70 | 2385 | exePath[len] = '\0'; | |
| 71 | } | ||
| 72 | #endif | ||
| 73 |
1/2✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
|
2385 | FPP_BIN_DIR = exePath; |
| 74 |
2/4✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
|
2385 | FPP_BIN_DIR = FPP_BIN_DIR.substr(0, FPP_BIN_DIR.rfind("/")); |
| 75 |
2/4✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
|
2385 | FPP_BIN_DIR = FPP_BIN_DIR.substr(0, FPP_BIN_DIR.rfind("/")); |
| 76 | } | ||
| 77 | 23105 | return FPP_BIN_DIR + path; | |
| 78 | } | ||
| 79 | 1436103 | std::string getFPPMediaDir(const std::string& path) { | |
| 80 | 1436103 | std::string& FPP_MEDIA_DIR = FPPMediaDir(); | |
| 81 |
2/2✓ Branch 0 taken 2385 times.
✓ Branch 1 taken 1433718 times.
|
1436103 | if (FPP_MEDIA_DIR == "") { |
| 82 |
2/4✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
|
4770 | std::string filename = getFPPDDir("/www/media_root.txt"); |
| 83 |
1/2✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
|
2385 | std::string fc = ""; |
| 84 |
3/4✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1927 times.
✓ Branch 3 taken 458 times.
|
2385 | if (FileExists(filename)) { |
| 85 |
1/2✓ Branch 0 taken 1927 times.
✗ Branch 1 not taken.
|
1927 | fc = GetFileContents(filename); |
| 86 |
1/2✓ Branch 0 taken 1927 times.
✗ Branch 1 not taken.
|
1927 | FPP_MEDIA_DIR = fc; |
| 87 |
1/2✓ Branch 0 taken 1927 times.
✗ Branch 1 not taken.
|
1927 | TrimWhiteSpace(FPP_MEDIA_DIR); |
| 88 | } | ||
| 89 |
3/4✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 458 times.
✓ Branch 3 taken 1927 times.
|
2385 | if (FPP_MEDIA_DIR == "") { |
| 90 | #ifdef PLATFORM_OSX | ||
| 91 | // getpwuid() is not thread-safe; use getpwuid_r(). Also guards | ||
| 92 | // against a NULL deref when neither $HOME nor the passwd entry exist. | ||
| 93 | const char* homedir = getenv("HOME"); | ||
| 94 | struct passwd pwd; | ||
| 95 | struct passwd* pwres = nullptr; | ||
| 96 | char pbuf[16384]; | ||
| 97 | if (homedir == NULL && | ||
| 98 | getpwuid_r(getuid(), &pwd, pbuf, sizeof(pbuf), &pwres) == 0 && pwres) { | ||
| 99 | homedir = pwres->pw_dir; | ||
| 100 | } | ||
| 101 | if (homedir) { | ||
| 102 | FPP_MEDIA_DIR = homedir; | ||
| 103 | FPP_MEDIA_DIR += "/Documents/fpp"; | ||
| 104 | } | ||
| 105 | #else | ||
| 106 |
1/2✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
|
458 | FPP_MEDIA_DIR = "/home/fpp/media"; |
| 107 | #endif | ||
| 108 | } | ||
| 109 |
2/2✓ Branch 0 taken 458 times.
✓ Branch 1 taken 1927 times.
|
2385 | if (FPP_MEDIA_DIR != fc) { |
| 110 |
1/2✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
|
458 | PutFileContents(filename, FPP_MEDIA_DIR); |
| 111 | } | ||
| 112 | 2385 | } | |
| 113 | 1436103 | return FPP_MEDIA_DIR + path; | |
| 114 | } | ||
| 115 | |||
| 116 | class SettingListener { | ||
| 117 | public: | ||
| 118 | std::string id; | ||
| 119 | std::function<void(const std::string&)> callback; | ||
| 120 | }; | ||
| 121 | class SettingsConfig { | ||
| 122 | public: | ||
| 123 | SettingsConfig(); | ||
| 124 | ~SettingsConfig(); | ||
| 125 | |||
| 126 | void Init(); | ||
| 127 | |||
| 128 | // written by the settings-file reload, read unlocked by getFPPmode() | ||
| 129 | std::atomic<FPPMode> fppMode; | ||
| 130 | |||
| 131 | std::string getSetting(const std::string& setting, const std::string& defaultVal); | ||
| 132 | int getSettingInt(const std::string& setting, int defaultVal); | ||
| 133 | |||
| 134 | void setSetting(const std::string& key, const std::string& value, bool persist); | ||
| 135 | |||
| 136 | int LoadSettings(const std::string& base, bool logChanges = false); | ||
| 137 | |||
| 138 | void registerSettingsListener(const std::string& id, const std::string& setting, std::function<void(const std::string&)>&& cb); | ||
| 139 | void unregisterSettingsListener(const std::string& id, const std::string& setting); | ||
| 140 | |||
| 141 | private: | ||
| 142 | bool setSettingLocked(const std::string& key, const std::string& value, bool logInfo); | ||
| 143 | bool persistSetting(const std::string& key, const std::string& value); | ||
| 144 | void notifyListeners(const std::map<std::string, std::string>& changed); | ||
| 145 | void LoadSettingsInfo(); | ||
| 146 | |||
| 147 | Json::Value settingsInfo; | ||
| 148 | Json::Value settings; | ||
| 149 | std::shared_mutex settingsMutex; | ||
| 150 | // Serializes setSetting(persist) so the file is written in the same order | ||
| 151 | // the in-memory values changed. Taken before settingsMutex, never after. | ||
| 152 | std::mutex persistMutex; | ||
| 153 | |||
| 154 | std::map<std::string, std::list<SettingListener>> settingsListeners; | ||
| 155 | std::shared_mutex settingsListenersMutex; | ||
| 156 | // Listener calls copied out by notifyListeners() and not yet returned, | ||
| 157 | // per (setting, id): unregisterSettingsListener() waits for its own. | ||
| 158 | // listenerCallsLock is a leaf lock; no callback runs under it. | ||
| 159 | std::map<std::pair<std::string, std::string>, int> listenerCallsInFlight; | ||
| 160 | std::mutex listenerCallsLock; | ||
| 161 | std::condition_variable listenerCallFinished; | ||
| 162 | void retireListenerCalls(const std::vector<std::pair<SettingListener, const std::string*>>& calls, | ||
| 163 | size_t from, size_t to); | ||
| 164 | }; | ||
| 165 | // Nonzero while this thread is inside a settings listener: a listener that | ||
| 166 | // unregisters (itself or another) must not wait on a call that may be its | ||
| 167 | // own, or one waiting on it. | ||
| 168 | static thread_local int t_inSettingsListener = 0; | ||
| 169 | static SettingsConfig settings; | ||
| 170 | |||
| 171 |
1/2✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
|
2385 | SettingsConfig::SettingsConfig() { |
| 172 | 2385 | } | |
| 173 | |||
| 174 | 497 | SettingsConfig::~SettingsConfig() { | |
| 175 | 497 | } | |
| 176 | |||
| 177 | 2211 | void SettingsConfig::Init() { | |
| 178 |
2/2✓ Branch 0 taken 1923 times.
✓ Branch 1 taken 288 times.
|
2211 | if (settingsInfo.empty()) { |
| 179 | 1923 | LoadSettingsInfo(); | |
| 180 | 1923 | fppMode = PLAYER_MODE; | |
| 181 | |||
| 182 | // load UUID File | ||
| 183 |
3/6✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1923 times.
✗ Branch 5 not taken.
|
3846 | if (access(getFPPDDir("/scripts/get_uuid").c_str(), F_OK) == 0) { |
| 184 | FILE* uuid_fp; | ||
| 185 | char temp_uuid[500]; | ||
| 186 |
3/6✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1923 times.
✗ Branch 5 not taken.
|
1923 | uuid_fp = popen(getFPPDDir("/scripts/get_uuid").c_str(), "r"); |
| 187 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1922 times.
|
1923 | if (uuid_fp == NULL) { |
| 188 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogWarn(VB_SETTING, "Couldn't execute get_uuid\n"); |
| 189 | } else { | ||
| 190 |
3/4✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1921 times.
✓ Branch 3 taken 1 times.
|
1922 | if (fgets(temp_uuid, sizeof(temp_uuid), uuid_fp) != NULL) { |
| 191 | 1921 | int pos = strlen(temp_uuid) - 1; | |
| 192 | // Strip newline | ||
| 193 |
2/4✓ Branch 0 taken 1921 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1921 times.
✗ Branch 3 not taken.
|
1921 | if (pos >= 0 && '\n' == temp_uuid[pos]) { |
| 194 | 1921 | temp_uuid[pos] = '\0'; | |
| 195 | } | ||
| 196 |
3/6✓ Branch 0 taken 1921 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1921 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1921 times.
✗ Branch 5 not taken.
|
3842 | settings["SystemUUID"] = std::string(temp_uuid); |
| 197 | } | ||
| 198 |
1/2✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
|
1922 | pclose(uuid_fp); |
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | // Set to unknown if not found | ||
| 203 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1921 times.
|
1923 | if (!settings.isMember("SystemUUID")) { |
| 204 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | settings["SystemUUID"] = "Unknown"; |
| 205 | 2 | LogWarn(VB_SETTING, "Couldn't find UUID\n"); | |
| 206 | } | ||
| 207 | } | ||
| 208 | 2211 | } | |
| 209 | 41522 | void SettingsConfig::registerSettingsListener(const std::string& id, const std::string& setting, std::function<void(const std::string&)>&& cb) { | |
| 210 |
1/2✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
|
41522 | std::unique_lock<std::shared_mutex> lock(settingsListenersMutex); |
| 211 | 41522 | SettingListener listener; | |
| 212 |
1/2✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
|
41522 | listener.id = id; |
| 213 | 41522 | listener.callback = std::move(cb); | |
| 214 | |||
| 215 |
3/4✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41509 times.
✓ Branch 3 taken 13 times.
|
41522 | if (settingsListeners.find(setting) == settingsListeners.end()) { |
| 216 |
1/2✓ Branch 0 taken 41509 times.
✗ Branch 1 not taken.
|
41509 | settingsListeners[setting] = std::list<SettingListener>(); |
| 217 | } | ||
| 218 |
2/4✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41522 times.
✗ Branch 3 not taken.
|
41522 | settingsListeners[setting].push_back(listener); |
| 219 |
1/2✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
|
41522 | LogDebug(VB_SETTING, "Registered settings listener for %s setting with id %s\n", setting.c_str(), id.c_str()); |
| 220 | 41522 | } | |
| 221 | 29830 | void SettingsConfig::unregisterSettingsListener(const std::string& id, const std::string& setting) { | |
| 222 | { | ||
| 223 |
1/2✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
|
29830 | std::unique_lock<std::shared_mutex> lock(settingsListenersMutex); |
| 224 |
3/4✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26165 times.
✓ Branch 3 taken 3665 times.
|
29830 | if (settingsListeners.find(setting) != settingsListeners.end()) { |
| 225 |
1/2✓ Branch 0 taken 26165 times.
✗ Branch 1 not taken.
|
26165 | auto& listeners = settingsListeners[setting]; |
| 226 |
2/2✓ Branch 0 taken 26178 times.
✓ Branch 1 taken 26165 times.
|
52343 | for (auto it = listeners.begin(); it != listeners.end();) { |
| 227 |
2/2✓ Branch 0 taken 26152 times.
✓ Branch 1 taken 26 times.
|
26178 | if (it->id == id) { |
| 228 | 26152 | it = listeners.erase(it); | |
| 229 | } else { | ||
| 230 | 26 | ++it; | |
| 231 | } | ||
| 232 | } | ||
| 233 |
2/2✓ Branch 0 taken 26139 times.
✓ Branch 1 taken 26 times.
|
26165 | if (listeners.empty()) { |
| 234 |
1/2✓ Branch 0 taken 26139 times.
✗ Branch 1 not taken.
|
26139 | settingsListeners.erase(setting); |
| 235 | } | ||
| 236 | } | ||
| 237 | 29830 | } | |
| 238 | // Listeners are called from a copy with no lock held, so one copied out | ||
| 239 | // before the erase above can still be running, or about to run. Its | ||
| 240 | // owner frees what it captured once this returns (an output deleted by a | ||
| 241 | // reload), so wait for those calls to finish. Not under | ||
| 242 | // settingsListenersMutex: a running listener may register or unregister. | ||
| 243 |
1/2✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
|
29830 | if (t_inSettingsListener == 0) { |
| 244 |
1/2✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
|
29830 | std::unique_lock<std::mutex> lock(listenerCallsLock); |
| 245 |
1/2✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
|
29830 | auto key = std::make_pair(setting, id); |
| 246 |
1/2✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
|
59661 | listenerCallFinished.wait(lock, [this, &key]() { return listenerCallsInFlight.count(key) == 0; }); |
| 247 | 29830 | } | |
| 248 | 29830 | } | |
| 249 | 57 | void SettingsConfig::retireListenerCalls(const std::vector<std::pair<SettingListener, const std::string*>>& calls, | |
| 250 | size_t from, size_t to) { | ||
| 251 |
2/2✓ Branch 0 taken 27 times.
✓ Branch 1 taken 30 times.
|
57 | if (from >= to) { |
| 252 | 27 | return; | |
| 253 | } | ||
| 254 | { | ||
| 255 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | std::lock_guard<std::mutex> lock(listenerCallsLock); |
| 256 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 30 times.
|
60 | for (size_t i = from; i < to; i++) { |
| 257 |
2/4✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
|
30 | auto f = listenerCallsInFlight.find(std::make_pair(*calls[i].second, calls[i].first.id)); |
| 258 |
3/6✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
|
30 | if (f != listenerCallsInFlight.end() && --f->second <= 0) { |
| 259 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | listenerCallsInFlight.erase(f); |
| 260 | } | ||
| 261 | } | ||
| 262 | 30 | } | |
| 263 | 30 | listenerCallFinished.notify_all(); | |
| 264 | } | ||
| 265 | |||
| 266 | 1923 | void SettingsConfig::LoadSettingsInfo() { | |
| 267 |
2/4✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
|
1923 | std::string settingsJson = getFPPDDir("/www/settings.json"); |
| 268 |
2/4✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
|
1923 | if (FileExists(settingsJson)) { |
| 269 |
1/2✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
|
1923 | settingsInfo = LoadJsonFromFile(settingsJson, JsonRoot::Object); |
| 270 | |||
| 271 |
2/4✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
|
1923 | Json::Value s = settingsInfo["settings"]; |
| 272 |
1/2✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
|
1923 | Json::Value::Members memberNames = s.getMemberNames(); |
| 273 | |||
| 274 |
2/2✓ Branch 0 taken 484596 times.
✓ Branch 1 taken 1923 times.
|
486519 | for (int i = 0; i < memberNames.size(); i++) { |
| 275 |
3/6✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 484596 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 484596 times.
✗ Branch 5 not taken.
|
484596 | std::string type = s[memberNames[i]]["type"].asString(); |
| 276 |
1/2✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
|
484596 | std::string def = ""; |
| 277 | |||
| 278 |
4/6✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 484596 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 248067 times.
✓ Branch 5 taken 236529 times.
|
484596 | if (s[memberNames[i]].isMember("default")) { |
| 279 |
3/6✓ Branch 0 taken 248067 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248067 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 248067 times.
✗ Branch 5 not taken.
|
248067 | def = s[memberNames[i]]["default"].asString(); |
| 280 | } else { | ||
| 281 |
5/6✓ Branch 0 taken 236529 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 188454 times.
✓ Branch 3 taken 48075 times.
✓ Branch 4 taken 49998 times.
✓ Branch 5 taken 186531 times.
|
424983 | if ((type == "checkbox") || |
| 282 |
3/4✓ Branch 0 taken 188454 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✓ Branch 3 taken 186531 times.
|
188454 | (type == "number")) { |
| 283 |
1/2✓ Branch 0 taken 49998 times.
✗ Branch 1 not taken.
|
99996 | def = std::string("0"); |
| 284 | } else { | ||
| 285 |
1/2✓ Branch 0 taken 186531 times.
✗ Branch 1 not taken.
|
373062 | def = std::string(""); |
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 |
2/4✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 484596 times.
✗ Branch 3 not taken.
|
484596 | settings[memberNames[i]] = def; |
| 290 | // The following will never be logged due to startup order | ||
| 291 |
1/2✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
|
484596 | LogExcess(VB_SETTING, "Setting default for '%s' setting to '%s'\n", |
| 292 | memberNames[i].c_str(), def.c_str()); | ||
| 293 | 484596 | } | |
| 294 | 1923 | } | |
| 295 | #ifdef PLATFORM_OSX | ||
| 296 | char hostname[256]; | ||
| 297 | gethostname(hostname, 256); | ||
| 298 | std::string hn = hostname; | ||
| 299 | int idx = hn.find("."); | ||
| 300 | if (idx != std::string::npos) { | ||
| 301 | hn = hn.substr(0, idx); | ||
| 302 | } | ||
| 303 | settings["HostName"] = hn; | ||
| 304 | #endif | ||
| 305 | 1923 | } | |
| 306 | 24128 | void SettingsConfig::setSetting(const std::string& key, const std::string& value, bool persist) { | |
| 307 | 24128 | std::unique_lock<std::mutex> persistLock(persistMutex, std::defer_lock); | |
| 308 |
2/2✓ Branch 0 taken 304 times.
✓ Branch 1 taken 23824 times.
|
24128 | if (persist) { |
| 309 | // The file is line based and nothing escapes a value, so a line break | ||
| 310 | // would start a new setting. Same rule as WriteSettingToFile(). | ||
| 311 |
2/4✓ Branch 0 taken 304 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 304 times.
|
608 | if (value.find_first_of(std::string("\r\n\0", 3)) != std::string::npos) { |
| 312 | ✗ | LogWarn(VB_SETTING, "Not saving setting %s: value contains a line break or NUL\n", key.c_str()); | |
| 313 | ✗ | return; | |
| 314 | } | ||
| 315 |
1/2✓ Branch 0 taken 304 times.
✗ Branch 1 not taken.
|
304 | persistLock.lock(); |
| 316 | } | ||
| 317 |
1/2✓ Branch 0 taken 24128 times.
✗ Branch 1 not taken.
|
24128 | std::unique_lock<std::shared_mutex> lock(settingsMutex); |
| 318 |
3/4✓ Branch 0 taken 24128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4083 times.
✓ Branch 3 taken 20045 times.
|
24128 | if (setSettingLocked(key, value, false)) { |
| 319 |
1/2✓ Branch 0 taken 4083 times.
✗ Branch 1 not taken.
|
4083 | lock.unlock(); |
| 320 |
2/2✓ Branch 0 taken 259 times.
✓ Branch 1 taken 3824 times.
|
4083 | if (persist) { |
| 321 |
1/2✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
|
259 | persistSetting(key, value); |
| 322 |
1/2✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
|
259 | persistLock.unlock(); |
| 323 | } | ||
| 324 |
4/8✓ Branch 0 taken 4083 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4083 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4083 times.
✓ Branch 5 taken 4083 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
12249 | notifyListeners({ { key, value } }); |
| 325 | } | ||
| 326 |
2/8✓ Branch 0 taken 4083 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24128 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
28211 | } |
| 327 | // Rewrites one key in the settings file under a single LOCK_EX held across | ||
| 328 | // the read and the write, so a concurrent writer (the PHP side holds the | ||
| 329 | // same lock) can't slip in between and have its change lost. Called without | ||
| 330 | // settingsMutex: LoadSettings() takes settingsMutex and then the file lock. | ||
| 331 | 259 | bool SettingsConfig::persistSetting(const std::string& key, const std::string& value) { | |
| 332 |
3/6✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 259 times.
✗ Branch 5 not taken.
|
259 | int fd = open(FPP_FILE_SETTINGS.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666); |
| 333 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
|
259 | if (fd == -1) { |
| 334 | ✗ | LogWarn(VB_SETTING, "Could not open %s to save %s: %s\n", FPP_FILE_SETTINGS.c_str(), key.c_str(), strerror(errno)); | |
| 335 | ✗ | return false; | |
| 336 | } | ||
| 337 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 259 times.
|
259 | while (flock(fd, LOCK_EX) == -1 && errno == EINTR) { |
| 338 | } | ||
| 339 | 259 | std::string contents; | |
| 340 | char buf[4096]; | ||
| 341 | ssize_t n; | ||
| 342 |
3/4✓ Branch 0 taken 518 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✓ Branch 3 taken 259 times.
|
518 | while ((n = read(fd, buf, sizeof(buf))) != 0) { |
| 343 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
|
259 | if (n < 0) { |
| 344 | ✗ | if (errno == EINTR) { | |
| 345 | ✗ | continue; | |
| 346 | } | ||
| 347 | ✗ | LogWarn(VB_SETTING, "Could not read %s to save %s: %s\n", FPP_FILE_SETTINGS.c_str(), key.c_str(), strerror(errno)); | |
| 348 | ✗ | flock(fd, LOCK_UN); | |
| 349 | ✗ | close(fd); | |
| 350 | ✗ | return false; | |
| 351 | } | ||
| 352 |
1/2✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
|
259 | contents.append(buf, n); |
| 353 | } | ||
| 354 | |||
| 355 | // Match the key the way LoadSettings() reads it (the text before the | ||
| 356 | // first '=', trimmed) rather than searching for "key = " anywhere, which | ||
| 357 | // also hit a key that ends with this one or a value containing that text. | ||
| 358 | // The first line for the key is replaced and any later duplicates are | ||
| 359 | // dropped, since the last one would win on the next load. | ||
| 360 |
3/6✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 259 times.
✗ Branch 5 not taken.
|
259 | const std::string newLine = key + " = \"" + value + "\"\n"; |
| 361 | 259 | std::string out; | |
| 362 |
1/2✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
|
259 | out.reserve(contents.size() + newLine.size()); |
| 363 | 259 | bool found = false; | |
| 364 | 259 | size_t pos = 0; | |
| 365 |
2/2✓ Branch 0 taken 3434 times.
✓ Branch 1 taken 259 times.
|
3693 | while (pos < contents.size()) { |
| 366 | 3434 | size_t end = contents.find('\n', pos); | |
| 367 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3434 times.
|
3434 | end = (end == std::string::npos) ? contents.size() : end + 1; |
| 368 |
1/2✓ Branch 0 taken 3434 times.
✗ Branch 1 not taken.
|
3434 | std::string line = contents.substr(pos, end - pos); |
| 369 | 3434 | pos = end; | |
| 370 | 3434 | size_t eq = line.find('='); | |
| 371 |
1/2✓ Branch 0 taken 3434 times.
✗ Branch 1 not taken.
|
3434 | if (eq != std::string::npos) { |
| 372 |
2/4✓ Branch 0 taken 3434 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3434 times.
✗ Branch 3 not taken.
|
3434 | char* k = trimwhitespace(line.substr(0, eq).c_str()); |
| 373 |
1/2✓ Branch 0 taken 3434 times.
✗ Branch 1 not taken.
|
3434 | bool match = (key == k); |
| 374 | 3434 | free(k); | |
| 375 |
2/2✓ Branch 0 taken 147 times.
✓ Branch 1 taken 3287 times.
|
3434 | if (match) { |
| 376 |
1/2✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
|
147 | if (!found) { |
| 377 |
1/2✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
|
147 | out += newLine; |
| 378 | 147 | found = true; | |
| 379 | } | ||
| 380 | 147 | continue; | |
| 381 | } | ||
| 382 | } | ||
| 383 |
1/2✓ Branch 0 taken 3287 times.
✗ Branch 1 not taken.
|
3287 | out += line; |
| 384 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3287 times.
|
3287 | if (out.back() != '\n') { |
| 385 | ✗ | out += '\n'; | |
| 386 | } | ||
| 387 |
2/2✓ Branch 0 taken 3287 times.
✓ Branch 1 taken 147 times.
|
3434 | } |
| 388 |
2/2✓ Branch 0 taken 112 times.
✓ Branch 1 taken 147 times.
|
259 | if (!found) { |
| 389 |
1/2✓ Branch 0 taken 112 times.
✗ Branch 1 not taken.
|
112 | out += newLine; |
| 390 | } | ||
| 391 | |||
| 392 | 259 | bool ok = ftruncate(fd, 0) == 0; | |
| 393 | 259 | const char* p = out.data(); | |
| 394 | 259 | size_t left = out.size(); | |
| 395 | 259 | off_t off = 0; | |
| 396 |
3/4✓ Branch 0 taken 518 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✓ Branch 3 taken 259 times.
|
518 | while (ok && left > 0) { |
| 397 |
1/2✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
|
259 | ssize_t w = pwrite(fd, p, left, off); |
| 398 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
259 | if (w < 0 && errno == EINTR) { |
| 399 | ✗ | continue; | |
| 400 | } | ||
| 401 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
|
259 | if (w <= 0) { |
| 402 | ✗ | ok = false; | |
| 403 | ✗ | break; | |
| 404 | } | ||
| 405 | 259 | p += w; | |
| 406 | 259 | left -= w; | |
| 407 | 259 | off += w; | |
| 408 | } | ||
| 409 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
|
259 | if (!ok) { |
| 410 | ✗ | LogWarn(VB_SETTING, "Could not write %s to save %s: %s\n", FPP_FILE_SETTINGS.c_str(), key.c_str(), strerror(errno)); | |
| 411 | } | ||
| 412 | 259 | flock(fd, LOCK_UN); | |
| 413 |
1/2✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
|
259 | close(fd); |
| 414 |
3/6✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 259 times.
✗ Branch 5 not taken.
|
259 | SetFilePerms(FPP_FILE_SETTINGS); |
| 415 | 259 | return ok; | |
| 416 | 259 | } | |
| 417 | // Listeners run with no settings lock held. They take their own locks and | ||
| 418 | // read settings back (MultiSync holds its lock and calls getSetting, which | ||
| 419 | // takes settingsMutex), and settingsMutex is held while settingsListenersMutex | ||
| 420 | // is taken, so calling them under either lock closes a cycle. It also lets a | ||
| 421 | // listener register or unregister listeners. Each call is counted until it | ||
| 422 | // returns, for unregisterSettingsListener() to wait on. | ||
| 423 | 6283 | void SettingsConfig::notifyListeners(const std::map<std::string, std::string>& changed) { | |
| 424 | 6283 | std::vector<std::pair<SettingListener, const std::string*>> toCall; | |
| 425 | { | ||
| 426 |
1/2✓ Branch 0 taken 6283 times.
✗ Branch 1 not taken.
|
6283 | std::shared_lock<std::shared_mutex> listenersLock(settingsListenersMutex); |
| 427 |
1/2✓ Branch 0 taken 6283 times.
✗ Branch 1 not taken.
|
6283 | std::lock_guard<std::mutex> callsLock(listenerCallsLock); |
| 428 |
2/2✓ Branch 0 taken 8006 times.
✓ Branch 1 taken 6283 times.
|
14289 | for (const auto& change : changed) { |
| 429 |
1/2✓ Branch 0 taken 8006 times.
✗ Branch 1 not taken.
|
8006 | auto it = settingsListeners.find(change.first); |
| 430 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 7977 times.
|
8006 | if (it != settingsListeners.end()) { |
| 431 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 29 times.
|
59 | for (const auto& listener : it->second) { |
| 432 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | toCall.emplace_back(listener, &change.first); |
| 433 |
2/4✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
|
30 | ++listenerCallsInFlight[std::make_pair(change.first, listener.id)]; |
| 434 | } | ||
| 435 | } | ||
| 436 | } | ||
| 437 | 6283 | } | |
| 438 |
2/2✓ Branch 0 taken 6256 times.
✓ Branch 1 taken 27 times.
|
6283 | if (toCall.empty()) { |
| 439 | 6256 | return; | |
| 440 | } | ||
| 441 | // Each call is retired as it returns; a throw retires the rest. | ||
| 442 | 27 | size_t next = 0; | |
| 443 | struct InFlight { | ||
| 444 | SettingsConfig* self; | ||
| 445 | const std::vector<std::pair<SettingListener, const std::string*>>& calls; | ||
| 446 | const size_t& next; | ||
| 447 | 27 | ~InFlight() { | |
| 448 | 27 | --t_inSettingsListener; | |
| 449 | 27 | self->retireListenerCalls(calls, next, calls.size()); | |
| 450 | 27 | } | |
| 451 | }; | ||
| 452 | 27 | ++t_inSettingsListener; | |
| 453 | 27 | InFlight inFlight{ this, toCall, next }; | |
| 454 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 27 times.
|
57 | while (next < toCall.size()) { |
| 455 | 30 | const auto& [listener, key] = toCall[next]; | |
| 456 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | const std::string& value = changed.at(*key); |
| 457 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | LogDebug(VB_SETTING, "Notifying listener %s for setting %s with value '%s'\n", |
| 458 | listener.id.c_str(), key->c_str(), value.c_str()); | ||
| 459 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | listener.callback(value); |
| 460 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | retireListenerCalls(toCall, next, next + 1); |
| 461 | 30 | ++next; | |
| 462 | } | ||
| 463 |
2/2✓ Branch 0 taken 27 times.
✓ Branch 1 taken 6256 times.
|
6283 | } |
| 464 | 32059 | bool SettingsConfig::setSettingLocked(const std::string& key, const std::string& value, bool logInfo) { | |
| 465 |
2/4✓ Branch 0 taken 32059 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32059 times.
✗ Branch 3 not taken.
|
32059 | std::string val = settings[key].asString(); |
| 466 |
2/2✓ Branch 0 taken 8066 times.
✓ Branch 1 taken 23993 times.
|
32059 | if (val != value) { |
| 467 |
2/2✓ Branch 0 taken 66 times.
✓ Branch 1 taken 8000 times.
|
8066 | if (logInfo) { |
| 468 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | std::shared_lock<std::shared_mutex> listenersLock(settingsListenersMutex); |
| 469 |
3/4✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✓ Branch 3 taken 3 times.
|
66 | if (settingsListeners.find(key) != settingsListeners.end()) { |
| 470 |
1/2✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
|
63 | LogInfo(VB_SETTING, "Setting %s changed from %s to %s\n", key.c_str(), val.c_str(), value.c_str()); |
| 471 | } else { | ||
| 472 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | LogDebug(VB_SETTING, "Setting %s: from %s to %s\n", key.c_str(), val.c_str(), value.c_str()); |
| 473 | } | ||
| 474 | 66 | } else { | |
| 475 |
1/2✓ Branch 0 taken 8000 times.
✗ Branch 1 not taken.
|
8000 | LogDebug(VB_SETTING, "Setting %s: from %s to %s\n", key.c_str(), val.c_str(), value.c_str()); |
| 476 | } | ||
| 477 |
2/4✓ Branch 0 taken 8066 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8066 times.
✗ Branch 3 not taken.
|
8066 | settings[key] = value; |
| 478 |
3/4✓ Branch 0 taken 8066 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 8025 times.
|
8066 | if (key == "fppMode") { |
| 479 |
6/8✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 38 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 38 times.
|
41 | if (value == "player" || value.empty()) { |
| 480 | 3 | fppMode = PLAYER_MODE; | |
| 481 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
|
38 | } else if (value == "bridge") { |
| 482 | // legacy, remap to PLAYER | ||
| 483 | ✗ | fppMode = PLAYER_MODE; | |
| 484 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
|
38 | } else if (value == "master") { |
| 485 | // legacy, remap to player, but turn on MultiSync setting | ||
| 486 | ✗ | fppMode = PLAYER_MODE; | |
| 487 | ✗ | settings[key] = "player"; | |
| 488 | ✗ | settings["MultiSyncEnabled"] = "1"; | |
| 489 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
|
38 | } else if (value == "remote") { |
| 490 | 38 | fppMode = REMOTE_MODE; | |
| 491 | } else { | ||
| 492 | // default to playmode if string is invalid | ||
| 493 | ✗ | fppMode = PLAYER_MODE; | |
| 494 | ✗ | settings[key] = "player"; | |
| 495 | } | ||
| 496 |
4/6✓ Branch 0 taken 8025 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8025 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 306 times.
✓ Branch 5 taken 7719 times.
|
16050 | } else if (startsWith(key, "LogLevel_")) { |
| 497 | // Starts with LogLevel_ | ||
| 498 |
2/4✓ Branch 0 taken 306 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 306 times.
✗ Branch 3 not taken.
|
306 | if (value != "") { |
| 499 |
1/2✓ Branch 0 taken 306 times.
✗ Branch 1 not taken.
|
306 | FPPLogger::INSTANCE.SetLevel(key.c_str(), value.c_str()); |
| 500 | } else { | ||
| 501 | ✗ | FPPLogger::INSTANCE.SetLevel(key.c_str(), "warn"); | |
| 502 | } | ||
| 503 | } | ||
| 504 | 8066 | return true; | |
| 505 | } | ||
| 506 | 23993 | return false; | |
| 507 | 32059 | } | |
| 508 | 2386311 | std::string SettingsConfig::getSetting(const std::string& setting, const std::string& defaultVal) { | |
| 509 |
1/2✓ Branch 0 taken 2386311 times.
✗ Branch 1 not taken.
|
2386311 | std::shared_lock<std::shared_mutex> lock(settingsMutex); |
| 510 |
1/2✓ Branch 0 taken 2386311 times.
✗ Branch 1 not taken.
|
2386311 | std::string result = defaultVal; |
| 511 |
3/4✓ Branch 0 taken 2386311 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2368362 times.
✓ Branch 3 taken 17949 times.
|
2386311 | if (settings.isMember(setting)) { |
| 512 |
2/4✓ Branch 0 taken 2368362 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2368362 times.
✗ Branch 3 not taken.
|
2368362 | result = settings[setting].asString(); |
| 513 | } | ||
| 514 |
1/2✓ Branch 0 taken 2386311 times.
✗ Branch 1 not taken.
|
2386311 | LogExcess(VB_SETTING, "getSetting(%s) returning '%s'\n", setting.c_str(), result.c_str()); |
| 515 | 2386311 | return result; | |
| 516 | 2386311 | } | |
| 517 | 340733 | int SettingsConfig::getSettingInt(const std::string& setting, int defaultVal) { | |
| 518 |
1/2✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
|
340733 | std::shared_lock<std::shared_mutex> lock(settingsMutex); |
| 519 | 340733 | int result = defaultVal; | |
| 520 |
3/4✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 311532 times.
✓ Branch 3 taken 29201 times.
|
340733 | if (settings.isMember(setting)) { |
| 521 |
2/4✓ Branch 0 taken 311532 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 311532 times.
✗ Branch 3 not taken.
|
311532 | result = atoi(settings[setting].asString().c_str()); |
| 522 | } | ||
| 523 |
1/2✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
|
340733 | LogExcess(VB_SETTING, "getSettingInt(%s) returning %d\n", setting.c_str(), result); |
| 524 | 340733 | return result; | |
| 525 | 340733 | } | |
| 526 | |||
| 527 | // Returns a string that's the white-space trimmed version | ||
| 528 | // of the input string. Also trim double quotes now that the | ||
| 529 | // settings file will have double quotes in it. | ||
| 530 | 11374 | char* trimwhitespace(const char* str, int quotesAlso) { | |
| 531 | const char* end; | ||
| 532 | size_t out_size; | ||
| 533 | char* out; | ||
| 534 | |||
| 535 | // Trim leading space | ||
| 536 |
3/6✗ Branch 0 not taken.
✓ Branch 1 taken 11374 times.
✓ Branch 2 taken 11374 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 11374 times.
|
11374 | while ((isspace(*str)) || (quotesAlso && (*str == '"'))) |
| 537 | ✗ | str++; | |
| 538 | |||
| 539 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 11373 times.
|
11374 | if (*str == 0) // All spaces? |
| 540 | 1 | return strdup(""); | |
| 541 | |||
| 542 | // Trim trailing space | ||
| 543 | 11373 | end = str + strlen(str) - 1; | |
| 544 |
5/8✓ Branch 0 taken 22746 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11373 times.
✓ Branch 3 taken 11373 times.
✓ Branch 4 taken 11373 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 11373 times.
|
22746 | while (end > str && ((isspace(*end)) || (quotesAlso && (*end == '"')))) |
| 545 | 11373 | end--; | |
| 546 | 11373 | end++; | |
| 547 | |||
| 548 | // Set output size to minimum of trimmed string length | ||
| 549 | 11373 | out_size = end - str; | |
| 550 | |||
| 551 | // All whitespace | ||
| 552 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11373 times.
|
11373 | if (out_size == 0) |
| 553 | ✗ | return strdup(""); | |
| 554 | |||
| 555 | 11373 | out = (char*)malloc(out_size + 1); | |
| 556 | // If we didn't allocate anything, die! | ||
| 557 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11373 times.
|
11373 | if (!out) { |
| 558 | ✗ | fprintf(stderr, "Failed to allocate memory for string trimming\n"); | |
| 559 | ✗ | exit(EXIT_FAILURE); | |
| 560 | } | ||
| 561 | |||
| 562 | // Copy trimmed string and add null terminator | ||
| 563 | 11373 | memcpy(out, str, out_size); | |
| 564 | 11373 | out[out_size] = 0; | |
| 565 | |||
| 566 | 11373 | return out; | |
| 567 | } | ||
| 568 | |||
| 569 | // Caller must free the char array we give them | ||
| 570 | 333252 | char* modeToString(int mode) { | |
| 571 |
2/2✓ Branch 0 taken 313797 times.
✓ Branch 1 taken 19455 times.
|
333252 | if (mode == PLAYER_MODE) |
| 572 | 313797 | return strdup("player"); | |
| 573 |
2/2✓ Branch 0 taken 19317 times.
✓ Branch 1 taken 138 times.
|
19455 | else if (mode == REMOTE_MODE) |
| 574 | 19317 | return strdup("remote"); | |
| 575 |
2/2✓ Branch 0 taken 112 times.
✓ Branch 1 taken 26 times.
|
138 | else if (mode == BRIDGE_MODE) |
| 576 | 112 | return strdup("bridge"); | |
| 577 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
|
26 | else if (mode == MASTER_MODE) |
| 578 | ✗ | return strdup("master"); | |
| 579 | |||
| 580 | 26 | return strdup("unknown"); | |
| 581 | } | ||
| 582 | |||
| 583 | 2211 | int SettingsConfig::LoadSettings(const std::string& base, bool logChanges) { | |
| 584 |
1/2✓ Branch 0 taken 2211 times.
✗ Branch 1 not taken.
|
2211 | std::unique_lock<std::shared_mutex> lock(settingsMutex); |
| 585 |
1/2✓ Branch 0 taken 2211 times.
✗ Branch 1 not taken.
|
2211 | Init(); |
| 586 | |||
| 587 |
5/8✓ Branch 0 taken 2211 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2211 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2211 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 2200 times.
|
4422 | if (!FileExists(FPP_FILE_SETTINGS)) { |
| 588 |
3/6✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
|
11 | LogWarn(VB_SETTING, |
| 589 | "Attempted to load settings file %s which does not exist!\n", FPP_FILE_SETTINGS.c_str()); | ||
| 590 | 11 | return -1; | |
| 591 | } | ||
| 592 | |||
| 593 |
3/6✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2200 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2200 times.
✗ Branch 5 not taken.
|
2200 | FILE* file = fopen(FPP_FILE_SETTINGS.c_str(), "r"); |
| 594 | 2200 | std::map<std::string, std::string> changed; | |
| 595 |
1/2✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
|
2200 | if (file != NULL) { |
| 596 | 2200 | flock(fileno(file), LOCK_EX); // Lock the file for exclusive access | |
| 597 | 2200 | char* line = (char*)calloc(256, 1); | |
| 598 | 2200 | size_t len = 256; | |
| 599 | ssize_t read; | ||
| 600 | 2200 | int sIndex = 0; | |
| 601 | |||
| 602 |
3/4✓ Branch 0 taken 10132 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7932 times.
✓ Branch 3 taken 2200 times.
|
10132 | while ((read = getline(&line, &len, file)) != -1) { |
| 603 |
3/6✓ Branch 0 taken 7932 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7932 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7932 times.
|
7932 | if ((!line) || (!read) || (read == 1)) |
| 604 | 1 | continue; | |
| 605 | |||
| 606 | // Split on the first '=' only and drop exactly one pair of | ||
| 607 | // surrounding quotes, the way the PHP side and getRawSetting() | ||
| 608 | // read this file. Splitting on every '=' cut base64 passwords | ||
| 609 | // and JSON values short, and trimming every quote ate quotes | ||
| 610 | // that belong to the value. | ||
| 611 |
1/2✓ Branch 0 taken 7932 times.
✗ Branch 1 not taken.
|
8279 | std::string l(line, read); |
| 612 | 7932 | size_t eq = l.find('='); | |
| 613 |
2/4✓ Branch 0 taken 7932 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7932 times.
✗ Branch 3 not taken.
|
7932 | char* key = trimwhitespace(l.substr(0, eq).c_str()); |
| 614 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7931 times.
|
7932 | if (!strlen(key)) { |
| 615 | 1 | free(key); | |
| 616 | 1 | continue; | |
| 617 | } | ||
| 618 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7931 times.
|
7931 | if (eq == std::string::npos) { |
| 619 | ✗ | fprintf(stderr, "Error tokenizing value for %s setting\n", key); | |
| 620 | ✗ | free(key); | |
| 621 | ✗ | continue; | |
| 622 | } | ||
| 623 |
1/2✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
|
7931 | std::string value = l.substr(eq + 1); |
| 624 |
1/2✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
|
7931 | TrimWhiteSpace(value); |
| 625 |
4/8✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7931 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7931 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7931 times.
✗ Branch 7 not taken.
|
7931 | if (value.size() >= 2 && value.front() == '"' && value.back() == '"') { |
| 626 |
1/2✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
|
7931 | value = value.substr(1, value.size() - 2); |
| 627 | } | ||
| 628 |
4/6✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7931 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3983 times.
✓ Branch 5 taken 3948 times.
|
23446 | if (setSettingLocked(key, value, logChanges)) { |
| 629 |
3/6✓ Branch 0 taken 3983 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3983 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3983 times.
✗ Branch 5 not taken.
|
7966 | changed[key] = value; // Store the changed setting |
| 630 | } | ||
| 631 | 7931 | free(key); | |
| 632 |
2/2✓ Branch 0 taken 7931 times.
✓ Branch 1 taken 1 times.
|
7932 | } |
| 633 | |||
| 634 |
1/2✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
|
2200 | if (line) { |
| 635 | 2200 | free(line); | |
| 636 | } | ||
| 637 | 2200 | flock(fileno(file), LOCK_UN); // Lock the file for exclusive access | |
| 638 |
1/2✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
|
2200 | fclose(file); |
| 639 | } else { | ||
| 640 | ✗ | LogWarn(VB_SETTING, "Warning: couldn't open settings file: '%s'!\n", FPP_FILE_SETTINGS.c_str()); | |
| 641 | ✗ | return -1; | |
| 642 | } | ||
| 643 |
1/2✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
|
2200 | lock.unlock(); |
| 644 |
1/2✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
|
2200 | notifyListeners(changed); |
| 645 | 2200 | return 0; | |
| 646 | 2211 | } | |
| 647 | |||
| 648 | 2211 | int LoadSettings(const char* base, bool logChanges) { | |
| 649 |
2/4✓ Branch 0 taken 2211 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2211 times.
✗ Branch 3 not taken.
|
6461 | return settings.LoadSettings(base, logChanges); |
| 650 | } | ||
| 651 | |||
| 652 | 1915 | int setSetting(const std::string& key, const int value) { | |
| 653 |
1/2✓ Branch 0 taken 1915 times.
✗ Branch 1 not taken.
|
1915 | return setSetting(key, std::to_string(value)); |
| 654 | } | ||
| 655 | 24128 | int setSetting(const std::string& key, const std::string& value, bool persist) { | |
| 656 | 24128 | settings.setSetting(key, value, persist); | |
| 657 | 24128 | return 1; | |
| 658 | } | ||
| 659 | 1915 | int SetSetting(const std::string& key, const int value) { | |
| 660 | 1915 | return setSetting(key, value); | |
| 661 | } | ||
| 662 | 21909 | int SetSetting(const std::string& key, const std::string& value) { | |
| 663 | 21909 | return setSetting(key, value); | |
| 664 | } | ||
| 665 | 2365272 | std::string getSetting(const char* setting, const char* defaultVal) { | |
| 666 |
2/4✓ Branch 0 taken 2365272 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2365272 times.
|
2365272 | if ((!setting) || (setting[0] == 0x00)) { |
| 667 | ✗ | LogErr(VB_SETTING, "getSetting() called with NULL or empty value\n"); | |
| 668 | ✗ | std::string result = defaultVal; | |
| 669 | ✗ | return result; | |
| 670 | ✗ | } | |
| 671 |
3/6✓ Branch 0 taken 2365272 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2365272 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2365272 times.
✗ Branch 5 not taken.
|
9461088 | return settings.getSetting(setting, defaultVal); |
| 672 | } | ||
| 673 | |||
| 674 | 340733 | int getSettingInt(const char* setting, int defaultVal) { | |
| 675 |
2/4✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 340733 times.
|
340733 | if ((!setting) || (setting[0] == 0x00)) { |
| 676 | ✗ | LogErr(VB_SETTING, "getSetting() called with NULL or empty value\n"); | |
| 677 | ✗ | return defaultVal; | |
| 678 | } | ||
| 679 |
2/4✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 340733 times.
✗ Branch 3 not taken.
|
681466 | return settings.getSettingInt(setting, defaultVal); |
| 680 | } | ||
| 681 | |||
| 682 | 21039 | bool isPipeWireBackend() { | |
| 683 | #ifdef PLATFORM_OSX | ||
| 684 | // There is no PipeWire on macOS, whatever MediaBackend says (its default is | ||
| 685 | // a PipeWire mode). Media plays through GStreamer's non-PipeWire path there - | ||
| 686 | // autoaudiosink, i.e. osxaudiosink - so every PipeWire-only feature has to | ||
| 687 | // see "no" from here rather than build pipewiresink/pipewiresrc pipelines | ||
| 688 | // that cannot be created. | ||
| 689 | return false; | ||
| 690 | #endif | ||
| 691 | // Default to the settings.json default rather than "". ALSA is retired -- | ||
| 692 | // the UI offers only the two PipeWire modes and FPPINIT migrates a stored | ||
| 693 | // "alsa" away -- so a box that simply has no MediaBackend key must not fall | ||
| 694 | // through to the retired path. | ||
| 695 |
3/6✓ Branch 0 taken 21039 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21039 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 21039 times.
✗ Branch 5 not taken.
|
63117 | std::string mb = settings.getSetting("MediaBackend", "pipewire-simple"); |
| 696 | // Lowercase compare without pulling in toLowerCopy here | ||
| 697 |
2/2✓ Branch 0 taken 311438 times.
✓ Branch 1 taken 21039 times.
|
332477 | for (char& c : mb) { |
| 698 |
3/4✓ Branch 0 taken 290964 times.
✓ Branch 1 taken 20474 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 290964 times.
|
311438 | if (c >= 'A' && c <= 'Z') |
| 699 | ✗ | c = c + ('a' - 'A'); | |
| 700 | } | ||
| 701 |
6/8✓ Branch 0 taken 21039 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20522 times.
✓ Branch 3 taken 517 times.
✓ Branch 4 taken 20522 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 20474 times.
✓ Branch 7 taken 48 times.
|
42078 | return (mb == "pipewire" || mb == "pipewire-simple"); |
| 702 | 21039 | } | |
| 703 | |||
| 704 | 1951432 | FPPMode getFPPmode(void) { | |
| 705 | 1951432 | return settings.fppMode; | |
| 706 | } | ||
| 707 | |||
| 708 | 24 | const std::string getFPPmodeStr(FPPMode mode) { | |
| 709 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (mode == UNKNOWN_MODE) |
| 710 | ✗ | mode = settings.fppMode; | |
| 711 | |||
| 712 | 24 | std::string result; | |
| 713 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | char* modePtr = modeToString((int)mode); |
| 714 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | if (modePtr) { |
| 715 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | result = modePtr; |
| 716 | 24 | free(modePtr); | |
| 717 | } else { | ||
| 718 | ✗ | result = "unknown"; | |
| 719 | } | ||
| 720 | |||
| 721 | 24 | return result; | |
| 722 | ✗ | } | |
| 723 | |||
| 724 | 41522 | void registerSettingsListener(const std::string& id, const std::string& key, std::function<void(const std::string&)>&& cb) { | |
| 725 | 41522 | settings.registerSettingsListener(id, key, std::move(cb)); | |
| 726 | 41522 | } | |
| 727 | 29830 | void unregisterSettingsListener(const std::string& id, const std::string& key) { | |
| 728 | 29830 | settings.unregisterSettingsListener(id, key); | |
| 729 | 29830 | } | |
| 730 |