| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2026 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 | |||
| 13 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include "fpp-json.h" | ||
| 16 | #include "fpphttp.h" | ||
| 17 | #include "fpphttp_clientip.h" // getEffectiveClientIP() - FPP-internal, not in the plugin API | ||
| 18 | |||
| 19 | #include "MultiSync.h" | ||
| 20 | #include "Player.h" | ||
| 21 | #include "Scheduler.h" | ||
| 22 | #include "Variables.h" | ||
| 23 | #include "Warnings.h" | ||
| 24 | #include "commands/Commands.h" | ||
| 25 | #include "commands/Condition.h" | ||
| 26 | #include "common.h" | ||
| 27 | #include "log.h" | ||
| 28 | #include "mqtt.h" | ||
| 29 | #include "settings.h" | ||
| 30 | #include "mediaoutput/mediaoutput.h" | ||
| 31 | #include "util/ExpressionProcessor.h" | ||
| 32 | |||
| 33 | #include <condition_variable> | ||
| 34 | #include <ctime> | ||
| 35 | #include <map> | ||
| 36 | #include <utility> | ||
| 37 | |||
| 38 | // std::to_string(double) always pads to 6 decimals ("45.000000"); use a | ||
| 39 | // format that drops trailing zeros so integer-valued results stay readable. | ||
| 40 | 96 | static std::string DoubleToCleanString(double v) { | |
| 41 | char buf[64]; | ||
| 42 | 96 | snprintf(buf, sizeof(buf), "%g", v); | |
| 43 |
1/2✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
|
192 | return std::string(buf); |
| 44 | } | ||
| 45 | |||
| 46 | // Same "static initialized at process start" convention as httpAPI.cpp's own | ||
| 47 | // (private) startupTime - duplicated rather than shared across translation | ||
| 48 | // units for one time_t. | ||
| 49 | // On the monotonic clock, as httpAPI.cpp's: a wall-clock step made the | ||
| 50 | // uptime negative or hours off. | ||
| 51 | static long long FPP_STARTUP_MS = GetMonotonicTimeMS(); | ||
| 52 | |||
| 53 | // FPP_STATUS_* -> name, mirroring GetCurrentFPPDStatus()'s switch | ||
| 54 | // (httpAPI.cpp) - not shared code since it's a small, stable 6-case mapping | ||
| 55 | // and pulling in httpAPI.cpp's own dependencies here isn't worth it. | ||
| 56 | 119915 | static std::string PlaylistStatusName(int status) { | |
| 57 |
5/7✓ Branch 0 taken 6244 times.
✓ Branch 1 taken 104571 times.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 56 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 9016 times.
✗ Branch 6 not taken.
|
119915 | switch (status) { |
| 58 | 6244 | case 0: | |
| 59 |
1/2✓ Branch 0 taken 6244 times.
✗ Branch 1 not taken.
|
12488 | return "idle"; |
| 60 | 104571 | case 1: | |
| 61 |
1/2✓ Branch 0 taken 104571 times.
✗ Branch 1 not taken.
|
209142 | return "playing"; |
| 62 | 28 | case 2: | |
| 63 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
56 | return "stopping gracefully"; |
| 64 | 56 | case 3: | |
| 65 |
1/2✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
|
112 | return "stopping gracefully after loop"; |
| 66 | ✗ | case 4: | |
| 67 | ✗ | return "stopping now"; | |
| 68 | 9016 | case 5: | |
| 69 |
1/2✓ Branch 0 taken 9016 times.
✗ Branch 1 not taken.
|
18032 | return "paused"; |
| 70 | ✗ | default: | |
| 71 | ✗ | return "unknown"; | |
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | // Playlist::GetCurrentStatus() sets some fields (e.g. "repeat_mode", | ||
| 76 | // "random") as a JSON int in its active-playlist branch but as a JSON | ||
| 77 | // string in its idle-state default branch - Json::Value::asString() throws | ||
| 78 | // if the value isn't actually a string, so a plain .asString() here would | ||
| 79 | // throw depending on player state. Coerce defensively regardless of type. | ||
| 80 | 1319065 | static std::string JsonToStr(const Json::Value& v) { | |
| 81 |
2/2✓ Branch 0 taken 1085479 times.
✓ Branch 1 taken 233586 times.
|
1319065 | if (v.isString()) { |
| 82 | 1085479 | return v.asString(); | |
| 83 | } | ||
| 84 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 233586 times.
|
233586 | if (v.isBool()) { |
| 85 | ✗ | return v.asBool() ? "1" : "0"; | |
| 86 | } | ||
| 87 |
1/2✓ Branch 0 taken 233586 times.
✗ Branch 1 not taken.
|
233586 | if (v.isIntegral()) { |
| 88 | 233586 | return std::to_string(v.asInt64()); | |
| 89 | } | ||
| 90 | ✗ | if (v.isDouble()) { | |
| 91 | ✗ | return DoubleToCleanString(v.asDouble()); | |
| 92 | } | ||
| 93 | ✗ | return ""; | |
| 94 | } | ||
| 95 | |||
| 96 | // Computes the current value of every "fpp_" read-only status variable. | ||
| 97 | // Cheap (a handful of in-memory reads, no I/O) so recomputed fresh on every | ||
| 98 | // call rather than cached - avoids any staleness question. | ||
| 99 | // | ||
| 100 | // Underscore, not the old hyphen ("fpp_") this family used previously: a | ||
| 101 | // bare "fpp_next_playlist_start" is directly usable in pure-math mode | ||
| 102 | // (lowercase-led, a-z0-9_ only - see ExpressionProcessor's | ||
| 103 | // isValidExprIdentifier()) with no aliasing needed, unlike the old | ||
| 104 | // hyphenated form. Renamed outright, not kept as a dual-registered alias - | ||
| 105 | // per explicit direction, no old "fpp-*" references remain anywhere. | ||
| 106 | 119915 | static std::map<std::string, std::string> ComputeFppStatusVariables() { | |
| 107 | 119915 | std::map<std::string, std::string> vars; | |
| 108 | |||
| 109 |
1/2✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
|
119915 | int status = (int)Player::INSTANCE.GetStatus(); |
| 110 |
2/4✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
|
359745 | vars["fpp_status"] = std::to_string(status); |
| 111 |
3/6✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
|
359745 | vars["fpp_status_name"] = PlaylistStatusName(status); |
| 112 |
5/10✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 119915 times.
✗ Branch 9 not taken.
|
359745 | vars["fpp_mode_name"] = toStdStringAndFree(modeToString(getFPPmode())); |
| 113 |
3/6✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
|
359745 | vars["fpp_volume"] = std::to_string(getVolume()); |
| 114 |
5/10✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 119915 times.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 119915 times.
✗ Branch 9 not taken.
|
239830 | vars["fpp_multisync"] = (multiSync && multiSync->isMultiSyncEnabled()) ? "1" : "0"; |
| 115 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_uptime_seconds"] = std::to_string((GetMonotonicTimeMS() - FPP_STARTUP_MS) / 1000); |
| 116 |
6/10✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 113671 times.
✓ Branch 3 taken 6244 times.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 119915 times.
✗ Branch 9 not taken.
|
239830 | vars["fpp_is_playing"] = Player::INSTANCE.IsPlaying() ? "1" : "0"; |
| 117 |
6/10✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 112 times.
✓ Branch 3 taken 119803 times.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 119915 times.
✗ Branch 9 not taken.
|
239830 | vars["fpp_was_scheduled"] = Player::INSTANCE.WasScheduled() ? "1" : "0"; |
| 118 |
5/10✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 119915 times.
✗ Branch 9 not taken.
|
239830 | vars["fpp_scheduler_enabled"] = (scheduler && scheduler->IsEnabled()) ? "1" : "0"; |
| 119 | { | ||
| 120 | // Same "HH:MM" convention as the "Time" Condition source | ||
| 121 | // (Condition.cpp's CurrentTimeHHMM()) so a plain fpp_current_time | ||
| 122 | // comparison lines up with what that source's Value examples show. | ||
| 123 | // Date is ISO 8601 (YYYY-MM-DD) - unambiguous and sorts/compares | ||
| 124 | // correctly as a plain string. Day name matches strftime's locale- | ||
| 125 | // independent full weekday name (English), same as the rest of this | ||
| 126 | // list rather than the system locale. | ||
| 127 | static const char* kDayNames[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; | ||
| 128 | static const char* kMonthNames[12] = { "January", "February", "March", "April", "May", "June", | ||
| 129 | "July", "August", "September", "October", "November", "December" }; | ||
| 130 | 119915 | std::time_t now = std::time(nullptr); | |
| 131 | struct tm local; | ||
| 132 | 119915 | localtime_r(&now, &local); | |
| 133 | char buf[16]; | ||
| 134 | 119915 | snprintf(buf, sizeof(buf), "%02d:%02d", local.tm_hour, local.tm_min); | |
| 135 |
3/6✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
|
119915 | vars["fpp_current_time"] = buf; |
| 136 | 119915 | snprintf(buf, sizeof(buf), "%04d-%02d-%02d", local.tm_year + 1900, local.tm_mon + 1, local.tm_mday); | |
| 137 |
3/6✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
|
119915 | vars["fpp_current_date"] = buf; |
| 138 |
3/6✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
|
239830 | vars["fpp_day_of_week"] = kDayNames[local.tm_wday]; |
| 139 |
3/6✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
|
239830 | vars["fpp_current_month"] = kMonthNames[local.tm_mon]; |
| 140 | } | ||
| 141 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_warning_count"] = std::to_string(WarningHolder::GetWarnings().size()); |
| 142 |
1/2✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
|
119915 | if (scheduler) { |
| 143 |
3/6✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
|
359745 | vars["fpp_next_playlist"] = scheduler->GetNextPlaylistName(); |
| 144 |
3/6✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
|
359745 | vars["fpp_next_playlist_start"] = scheduler->GetNextPlaylistStartStr(); |
| 145 | } else { | ||
| 146 | ✗ | vars["fpp_next_playlist"] = ""; | |
| 147 | ✗ | vars["fpp_next_playlist_start"] = ""; | |
| 148 | } | ||
| 149 | |||
| 150 |
1/2✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
|
119915 | Json::Value pl; |
| 151 |
1/2✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
|
119915 | Player::INSTANCE.GetCurrentStatus(pl); |
| 152 |
5/10✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 119915 times.
✗ Branch 9 not taken.
|
359745 | vars["fpp_current_playlist"] = JsonToStr(pl["current_playlist"]["playlist"]); |
| 153 |
5/10✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 119915 times.
✗ Branch 9 not taken.
|
359745 | vars["fpp_current_playlist_count"] = JsonToStr(pl["current_playlist"]["count"]); |
| 154 |
5/10✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 119915 times.
✗ Branch 9 not taken.
|
359745 | vars["fpp_current_playlist_index"] = JsonToStr(pl["current_playlist"]["index"]); |
| 155 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_current_sequence"] = JsonToStr(pl["current_sequence"]); |
| 156 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_current_song"] = JsonToStr(pl["current_song"]); |
| 157 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_seconds_played"] = JsonToStr(pl["seconds_played"]); |
| 158 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_seconds_remaining"] = JsonToStr(pl["seconds_remaining"]); |
| 159 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_time_elapsed"] = JsonToStr(pl["time_elapsed"]); |
| 160 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_time_remaining"] = JsonToStr(pl["time_remaining"]); |
| 161 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_repeat_mode"] = JsonToStr(pl["repeat_mode"]); |
| 162 |
4/8✓ Branch 0 taken 119915 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119915 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119915 times.
✗ Branch 7 not taken.
|
359745 | vars["fpp_random"] = JsonToStr(pl["random"]); |
| 163 | |||
| 164 | 119915 | return vars; | |
| 165 | 119915 | } | |
| 166 | |||
| 167 | Variables Variables::INSTANCE; | ||
| 168 | |||
| 169 | 2346 | Variables::Variables() { | |
| 170 | 2346 | } | |
| 171 | 458 | Variables::~Variables() { | |
| 172 | 458 | } | |
| 173 | |||
| 174 | 1885 | void Variables::Init() { | |
| 175 | 1885 | load(); | |
| 176 | 1885 | } | |
| 177 | |||
| 178 | 1867 | void Variables::Close() { | |
| 179 | 1867 | save(); | |
| 180 | 1867 | } | |
| 181 | |||
| 182 | 1885 | void Variables::load() { | |
| 183 |
3/6✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1885 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1885 times.
✗ Branch 5 not taken.
|
1885 | std::string file = FPP_DIR_CONFIG("/variables.json"); |
| 184 |
3/4✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1865 times.
✓ Branch 3 taken 20 times.
|
1885 | if (!FileExists(file)) { |
| 185 | 1865 | return; | |
| 186 | } | ||
| 187 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | Json::Value root = LoadJsonFromFile(file, JsonRoot::Object); |
| 188 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | std::unique_lock<std::mutex> lock(m_lock); |
| 189 |
3/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 20 times.
|
22 | for (auto const& name : root.getMemberNames()) { |
| 190 | // A hand-edited variable of the wrong JSON type threw at startup, to | ||
| 191 | // std::terminate; skip that one variable instead. | ||
| 192 | try { | ||
| 193 | 2 | VariableEntry e; | |
| 194 |
4/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
|
2 | e.value = root[name]["value"].asString(); |
| 195 | ✗ | e.persist = root[name]["persist"].asBool(); | |
| 196 | ✗ | e.lastUpdated = (time_t)root[name]["lastUpdated"].asInt64(); | |
| 197 | ✗ | m_variables[name] = e; | |
| 198 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
4 | } catch (const std::exception& ex) { |
| 199 |
4/8✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
|
6 | WarningHolder::InvalidConfigEntry("variables.json", name + ": " + ex.what()); |
| 200 | 2 | } | |
| 201 | 20 | } | |
| 202 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1865 times.
|
1885 | } |
| 203 | |||
| 204 | // Held across a save's snapshot *and* its write. With only m_lock around | ||
| 205 | // the snapshot, two threads saving at once could write in the opposite | ||
| 206 | // order to their snapshots, leaving the older values on disk. Always taken | ||
| 207 | // before m_lock, never inside it. | ||
| 208 | static std::mutex s_saveLock; | ||
| 209 | |||
| 210 | 2171 | void Variables::save() { | |
| 211 |
1/2✓ Branch 0 taken 2171 times.
✗ Branch 1 not taken.
|
2171 | std::unique_lock<std::mutex> saveLock(s_saveLock); |
| 212 |
1/2✓ Branch 0 taken 2171 times.
✗ Branch 1 not taken.
|
2171 | Json::Value root; |
| 213 |
1/2✓ Branch 0 taken 2171 times.
✗ Branch 1 not taken.
|
2171 | std::unique_lock<std::mutex> lock(m_lock); |
| 214 |
2/2✓ Branch 0 taken 722 times.
✓ Branch 1 taken 2171 times.
|
2893 | for (auto const& kv : m_variables) { |
| 215 |
2/2✓ Branch 0 taken 307 times.
✓ Branch 1 taken 415 times.
|
722 | if (kv.second.persist) { |
| 216 |
3/6✓ Branch 0 taken 307 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 307 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 307 times.
✗ Branch 5 not taken.
|
307 | root[kv.first]["value"] = kv.second.value; |
| 217 |
3/6✓ Branch 0 taken 307 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 307 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 307 times.
✗ Branch 5 not taken.
|
307 | root[kv.first]["persist"] = true; |
| 218 |
3/6✓ Branch 0 taken 307 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 307 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 307 times.
✗ Branch 5 not taken.
|
307 | root[kv.first]["lastUpdated"] = (Json::Int64)kv.second.lastUpdated; |
| 219 | } | ||
| 220 | } | ||
| 221 |
1/2✓ Branch 0 taken 2171 times.
✗ Branch 1 not taken.
|
2171 | lock.unlock(); |
| 222 |
5/10✓ Branch 0 taken 2171 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2171 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2171 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2171 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2171 times.
✗ Branch 9 not taken.
|
6513 | SaveJsonToFile(root, FPP_DIR_CONFIG("/variables.json"), "\t"); |
| 223 | 2171 | } | |
| 224 | |||
| 225 | 99192 | bool Variables::IsFppStatusVariableName(const std::string& name) { | |
| 226 | // "fpp_" (underscore) - a bare "fpp_next_playlist_start" is directly | ||
| 227 | // usable in pure-math mode with no aliasing needed (lowercase-led, | ||
| 228 | // a-z0-9_ only - see ExpressionProcessor's isValidExprIdentifier()), | ||
| 229 | // unlike the old hyphenated "fpp-" form this replaced. | ||
| 230 | 99192 | return name.rfind("fpp_", 0) == 0; | |
| 231 | } | ||
| 232 | |||
| 233 | // "mqtt-<topic>" is a live, read-only variable backed directly by | ||
| 234 | // MosquittoClient's own last-message-per-topic cache (mqtt.h's | ||
| 235 | // messageCache) rather than the persisted m_variables map - same shape as | ||
| 236 | // the "fpp_" branch above. <topic> is the raw MQTT topic string (may | ||
| 237 | // contain '/'), so it's only usable where the value is looked up by name | ||
| 238 | // (variable condition leaf, %VAR:name% substitution) - it won't parse as | ||
| 239 | // an ExpressionProcessor/tinyexpr identifier, which requires alnum/underscore. | ||
| 240 | 11529 | bool Variables::IsMqttVariableName(const std::string& name) { | |
| 241 | 11529 | return name.rfind("mqtt-", 0) == 0; | |
| 242 | } | ||
| 243 | |||
| 244 | 93810 | std::string Variables::getVariable(const std::string& name, const std::string& def) { | |
| 245 |
3/4✓ Branch 0 taken 93810 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 87408 times.
✓ Branch 3 taken 6402 times.
|
93810 | if (IsFppStatusVariableName(name)) { |
| 246 |
1/2✓ Branch 0 taken 87408 times.
✗ Branch 1 not taken.
|
87408 | auto vars = ComputeFppStatusVariables(); |
| 247 |
1/2✓ Branch 0 taken 87408 times.
✗ Branch 1 not taken.
|
87408 | auto it = vars.find(name); |
| 248 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 87408 times.
✓ Branch 2 taken 87408 times.
✗ Branch 3 not taken.
|
87408 | return it == vars.end() ? def : it->second; |
| 249 | 87408 | } | |
| 250 |
3/4✓ Branch 0 taken 6402 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 477 times.
✓ Branch 3 taken 5925 times.
|
6402 | if (IsMqttVariableName(name)) { |
| 251 |
1/2✓ Branch 0 taken 477 times.
✗ Branch 1 not taken.
|
477 | std::string topic = name.substr(5); |
| 252 | 477 | std::string value; | |
| 253 |
4/8✓ Branch 0 taken 477 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 477 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 477 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 477 times.
✗ Branch 7 not taken.
|
477 | if (mqtt && mqtt->GetCachedMessage(topic, value)) { |
| 254 | 477 | return value; | |
| 255 | } | ||
| 256 | ✗ | return def; | |
| 257 | 477 | } | |
| 258 |
1/2✓ Branch 0 taken 5925 times.
✗ Branch 1 not taken.
|
5925 | std::unique_lock<std::mutex> lock(m_lock); |
| 259 |
1/2✓ Branch 0 taken 5925 times.
✗ Branch 1 not taken.
|
5925 | auto it = m_variables.find(name); |
| 260 |
2/2✓ Branch 0 taken 137 times.
✓ Branch 1 taken 5788 times.
|
5925 | if (it == m_variables.end()) { |
| 261 |
1/2✓ Branch 0 taken 137 times.
✗ Branch 1 not taken.
|
137 | return def; |
| 262 | } | ||
| 263 |
1/2✓ Branch 0 taken 5788 times.
✗ Branch 1 not taken.
|
5788 | return it->second.value; |
| 264 | 5925 | } | |
| 265 | ✗ | int Variables::getVariableInt(const std::string& name, int def) { | |
| 266 | ✗ | std::string v = getVariable(name, ""); | |
| 267 | ✗ | if (v.empty()) { | |
| 268 | ✗ | return def; | |
| 269 | } | ||
| 270 | try { | ||
| 271 | ✗ | return std::stoi(v); | |
| 272 | ✗ | } catch (...) { | |
| 273 | ✗ | return def; | |
| 274 | ✗ | } | |
| 275 | ✗ | } | |
| 276 | ✗ | double Variables::getVariableDouble(const std::string& name, double def) { | |
| 277 | ✗ | std::string v = getVariable(name, ""); | |
| 278 | ✗ | if (v.empty()) { | |
| 279 | ✗ | return def; | |
| 280 | } | ||
| 281 | try { | ||
| 282 | ✗ | return std::stod(v); | |
| 283 | ✗ | } catch (...) { | |
| 284 | ✗ | return def; | |
| 285 | ✗ | } | |
| 286 | ✗ | } | |
| 287 | ✗ | bool Variables::getVariableBool(const std::string& name, bool def) { | |
| 288 | ✗ | std::string v = getVariable(name, ""); | |
| 289 | ✗ | if (v.empty()) { | |
| 290 | ✗ | return def; | |
| 291 | } | ||
| 292 | ✗ | return v == "true" || v == "1"; | |
| 293 | ✗ | } | |
| 294 | 7227 | std::vector<std::string> Variables::getVariableNames() { | |
| 295 | 7227 | std::vector<std::string> names; | |
| 296 |
1/2✓ Branch 0 taken 7227 times.
✗ Branch 1 not taken.
|
7227 | std::unique_lock<std::mutex> lock(m_lock); |
| 297 |
1/2✓ Branch 0 taken 7227 times.
✗ Branch 1 not taken.
|
7227 | names.reserve(m_variables.size()); |
| 298 |
2/2✓ Branch 0 taken 12070 times.
✓ Branch 1 taken 7227 times.
|
19297 | for (auto const& kv : m_variables) { |
| 299 |
1/2✓ Branch 0 taken 12070 times.
✗ Branch 1 not taken.
|
12070 | names.push_back(kv.first); |
| 300 | } | ||
| 301 | 7227 | return names; | |
| 302 | 7227 | } | |
| 303 | |||
| 304 | 7227 | std::vector<std::string> Variables::getAllVariableNames() { | |
| 305 |
1/2✓ Branch 0 taken 7227 times.
✗ Branch 1 not taken.
|
7227 | std::vector<std::string> names = getVariableNames(); |
| 306 | |||
| 307 | // Names only: no need for the change tracking reportFppVariables() does. | ||
| 308 |
3/4✓ Branch 0 taken 7227 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195129 times.
✓ Branch 3 taken 7227 times.
|
202356 | for (auto const& kv : ComputeFppStatusVariables()) { |
| 309 |
1/2✓ Branch 0 taken 195129 times.
✗ Branch 1 not taken.
|
195129 | names.push_back(kv.first); |
| 310 | 7227 | } | |
| 311 | |||
| 312 |
1/2✓ Branch 0 taken 7227 times.
✗ Branch 1 not taken.
|
7227 | Json::Value mqttVars; |
| 313 |
1/2✓ Branch 0 taken 7227 times.
✗ Branch 1 not taken.
|
7227 | reportMqttVariables(mqttVars); |
| 314 |
3/4✓ Branch 0 taken 7227 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 477 times.
✓ Branch 3 taken 7227 times.
|
7704 | for (auto const& name : mqttVars.getMemberNames()) { |
| 315 |
1/2✓ Branch 0 taken 477 times.
✗ Branch 1 not taken.
|
477 | names.push_back(name); |
| 316 | 7227 | } | |
| 317 | |||
| 318 | 7227 | return names; | |
| 319 | 7227 | } | |
| 320 | |||
| 321 | 4872 | void Variables::setVariable(const std::string& name, const std::string& value, bool persist) { | |
| 322 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4872 times.
|
4872 | if (IsFppStatusVariableName(name)) { |
| 323 | ✗ | LogWarn(VB_GENERAL, "Variables::setVariable(): \"%s\" starts with the reserved \"fpp_\" prefix " | |
| 324 | "(read-only computed status) and cannot be set\n", | ||
| 325 | name.c_str()); | ||
| 326 | ✗ | return; | |
| 327 | } | ||
| 328 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4872 times.
|
4872 | if (IsMqttVariableName(name)) { |
| 329 | ✗ | LogWarn(VB_GENERAL, "Variables::setVariable(): \"%s\" starts with the reserved \"mqtt-\" prefix " | |
| 330 | "(read-only, backed by the MQTT message cache) and cannot be set\n", | ||
| 331 | name.c_str()); | ||
| 332 | ✗ | return; | |
| 333 | } | ||
| 334 | 4872 | bool wasPersisted = false; | |
| 335 | { | ||
| 336 |
1/2✓ Branch 0 taken 4872 times.
✗ Branch 1 not taken.
|
4872 | std::unique_lock<std::mutex> lock(m_lock); |
| 337 |
1/2✓ Branch 0 taken 4872 times.
✗ Branch 1 not taken.
|
4872 | VariableEntry& e = m_variables[name]; |
| 338 | 4872 | wasPersisted = e.persist; | |
| 339 |
1/2✓ Branch 0 taken 4872 times.
✗ Branch 1 not taken.
|
4872 | e.value = value; |
| 340 | 4872 | e.persist = persist; | |
| 341 | 4872 | e.lastUpdated = time(nullptr); | |
| 342 | 4872 | } | |
| 343 | 4872 | fireListeners(name, value); | |
| 344 | // Save whenever the variable is (or was) persisted, so demoting persist | ||
| 345 | // true->false actually removes the stale entry from variables.json | ||
| 346 | // instead of leaving it there until the next persisted write elsewhere. | ||
| 347 |
3/4✓ Branch 0 taken 4568 times.
✓ Branch 1 taken 304 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4568 times.
|
4872 | if (persist || wasPersisted) { |
| 348 | 304 | save(); | |
| 349 | } | ||
| 350 | } | ||
| 351 | 5 | void Variables::setVariable(const std::string& name, int value, bool persist) { | |
| 352 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | setVariable(name, std::to_string(value), persist); |
| 353 | 5 | } | |
| 354 | |||
| 355 | 255 | bool Variables::deleteVariable(const std::string& name) { | |
| 356 | 255 | bool existed = false; | |
| 357 | 255 | bool wasPersisted = false; | |
| 358 | { | ||
| 359 |
1/2✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
|
255 | std::unique_lock<std::mutex> lock(m_lock); |
| 360 |
1/2✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
|
255 | auto it = m_variables.find(name); |
| 361 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 255 times.
|
255 | if (it != m_variables.end()) { |
| 362 | ✗ | existed = true; | |
| 363 | ✗ | wasPersisted = it->second.persist; | |
| 364 | ✗ | m_variables.erase(it); | |
| 365 | } | ||
| 366 | 255 | } | |
| 367 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 255 times.
|
255 | if (wasPersisted) { |
| 368 | ✗ | save(); | |
| 369 | } | ||
| 370 | 255 | return existed; | |
| 371 | } | ||
| 372 | 48 | double Variables::incrementVariable(const std::string& name, double delta, bool persist) { | |
| 373 | 48 | double newValue = 0.0; | |
| 374 | 48 | bool wasPersisted = false; | |
| 375 | { | ||
| 376 |
1/2✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
|
48 | std::unique_lock<std::mutex> lock(m_lock); |
| 377 |
1/2✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
|
48 | VariableEntry& e = m_variables[name]; |
| 378 | 48 | wasPersisted = e.persist; | |
| 379 | 48 | double cur = 0.0; | |
| 380 | try { | ||
| 381 |
3/4✓ Branch 0 taken 24 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
48 | cur = e.value.empty() ? 0.0 : std::stod(e.value); |
| 382 | ✗ | } catch (...) { | |
| 383 | ✗ | cur = 0.0; | |
| 384 | ✗ | } | |
| 385 | 48 | newValue = cur + delta; | |
| 386 |
1/2✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
|
48 | e.value = DoubleToCleanString(newValue); |
| 387 | 48 | e.persist = persist; | |
| 388 | 48 | e.lastUpdated = time(nullptr); | |
| 389 | 48 | } | |
| 390 |
2/4✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
|
48 | fireListeners(name, DoubleToCleanString(newValue)); |
| 391 |
2/4✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
|
48 | if (persist || wasPersisted) { |
| 392 | ✗ | save(); | |
| 393 | } | ||
| 394 | 48 | return newValue; | |
| 395 | } | ||
| 396 | |||
| 397 | // fireListeners() runs copies of the callbacks outside m_lock, so a listener | ||
| 398 | // could still be running after unregisterListener() returned, and its owner | ||
| 399 | // then freed what it uses. Count the calls in flight per (name, id), under | ||
| 400 | // m_lock, and have unregisterListener() wait for its listener's to finish. | ||
| 401 | static std::map<std::pair<std::string, std::string>, int> s_listenersInFlight; | ||
| 402 | static std::condition_variable s_listenerFinished; | ||
| 403 | // Nonzero while this thread is inside a listener: a listener that | ||
| 404 | // unregisters (itself or another) must not wait on a call that may be its | ||
| 405 | // own, or one waiting on it. | ||
| 406 | static thread_local int t_inListener = 0; | ||
| 407 | |||
| 408 | 1 | void Variables::registerListener(const std::string& id, const std::string& name, | |
| 409 | std::function<void(const std::string&, const std::string&)>&& cb) { | ||
| 410 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::unique_lock<std::mutex> lock(m_lock); |
| 411 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | m_listeners[name][id] = std::move(cb); |
| 412 | 1 | } | |
| 413 | 1 | void Variables::unregisterListener(const std::string& id, const std::string& name) { | |
| 414 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::unique_lock<std::mutex> lock(m_lock); |
| 415 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | auto it = m_listeners.find(name); |
| 416 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (it != m_listeners.end()) { |
| 417 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | it->second.erase(id); |
| 418 | } | ||
| 419 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (t_inListener == 0) { |
| 420 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | auto key = std::make_pair(name, id); |
| 421 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
3 | s_listenerFinished.wait(lock, [&key]() { return s_listenersInFlight.count(key) == 0; }); |
| 422 | 1 | } | |
| 423 | 1 | } | |
| 424 | 4920 | void Variables::fireListeners(const std::string& name, const std::string& value) { | |
| 425 | using CallList = std::vector<std::pair<std::string, std::function<void(const std::string&, const std::string&)>>>; | ||
| 426 | 4920 | CallList cbs; | |
| 427 | { | ||
| 428 |
1/2✓ Branch 0 taken 4920 times.
✗ Branch 1 not taken.
|
4920 | std::unique_lock<std::mutex> lock(m_lock); |
| 429 |
1/2✓ Branch 0 taken 4920 times.
✗ Branch 1 not taken.
|
4920 | auto it = m_listeners.find(name); |
| 430 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4919 times.
|
4920 | if (it != m_listeners.end()) { |
| 431 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (auto const& kv : it->second) { |
| 432 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | cbs.emplace_back(kv.first, kv.second); |
| 433 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | ++s_listenersInFlight[std::make_pair(name, kv.first)]; |
| 434 | } | ||
| 435 | } | ||
| 436 | 4920 | } | |
| 437 |
2/2✓ Branch 0 taken 4919 times.
✓ Branch 1 taken 1 times.
|
4920 | if (cbs.empty()) { |
| 438 | 4919 | return; | |
| 439 | } | ||
| 440 | // Retires every call counted above when this returns or throws. | ||
| 441 | struct InFlight { | ||
| 442 | Variables* self; | ||
| 443 | const std::string& name; | ||
| 444 | const CallList& calls; | ||
| 445 | 1 | ~InFlight() { | |
| 446 | 1 | --t_inListener; | |
| 447 | 1 | std::unique_lock<std::mutex> lock(self->m_lock); | |
| 448 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (auto const& c : calls) { |
| 449 | 1 | auto f = s_listenersInFlight.find(std::make_pair(name, c.first)); | |
| 450 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | if (f != s_listenersInFlight.end() && --f->second <= 0) { |
| 451 | 1 | s_listenersInFlight.erase(f); | |
| 452 | } | ||
| 453 | } | ||
| 454 | 1 | s_listenerFinished.notify_all(); | |
| 455 | 1 | } | |
| 456 | }; | ||
| 457 | 1 | ++t_inListener; | |
| 458 | 1 | InFlight inFlight{ this, name, cbs }; | |
| 459 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (auto& cb : cbs) { |
| 460 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | cb.second(name, value); |
| 461 | } | ||
| 462 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4919 times.
|
4920 | } |
| 463 | |||
| 464 | 1745 | std::set<std::string> Variables::getReferencedNamesInConfig() { | |
| 465 | 1745 | std::set<std::string> referenced; | |
| 466 | // The presets as CommandManager loaded them, not the file: this read the | ||
| 467 | // file again and indexed whatever it held, so one entry of the wrong shape | ||
| 468 | // (a hand edit, an upload) threw, and GET /api/variables answered 500. | ||
| 469 |
1/2✓ Branch 0 taken 1745 times.
✗ Branch 1 not taken.
|
1745 | Json::Value presets = CommandManager::INSTANCE.GetPresets(); |
| 470 |
3/4✓ Branch 0 taken 1745 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 191 times.
✓ Branch 3 taken 1745 times.
|
1936 | for (auto const& presetName : presets.getMemberNames()) { |
| 471 |
8/14✓ Branch 0 taken 191 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 191 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 191 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 210 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 210 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 401 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 210 times.
✓ Branch 13 taken 191 times.
|
401 | for (auto const& cmd : presets[presetName]) { |
| 472 |
2/4✓ Branch 0 taken 210 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 210 times.
✗ Branch 3 not taken.
|
210 | std::string commandName = cmd["command"].asString(); |
| 473 |
1/2✓ Branch 0 taken 210 times.
✗ Branch 1 not taken.
|
210 | const Json::Value& args = cmd["args"]; |
| 474 |
3/4✓ Branch 0 taken 1511 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1301 times.
✓ Branch 3 taken 210 times.
|
1511 | for (Json::ArrayIndex i = 0; i < args.size(); i++) { |
| 475 |
3/6✓ Branch 0 taken 1301 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1301 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1301 times.
|
1301 | if (args[i].isNull()) { |
| 476 | ✗ | continue; | |
| 477 | } | ||
| 478 | // An argument can be any JSON value. A list argument stored | ||
| 479 | // as JSON (an If's commands) is searched as its text. | ||
| 480 |
3/6✓ Branch 0 taken 1301 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1301 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1301 times.
✗ Branch 5 not taken.
|
2602 | std::string arg = args[i].isConvertibleTo(Json::stringValue) ? args[i].asString() |
| 481 |
4/18✓ Branch 0 taken 1301 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1301 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 1301 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 1301 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
2602 | : SaveJsonToString(args[i]); |
| 482 | // "Set Variable" writes to whatever's in its first arg. | ||
| 483 |
8/10✓ Branch 0 taken 1301 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1298 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 203 times.
✓ Branch 5 taken 1095 times.
✓ Branch 6 taken 203 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 203 times.
✓ Branch 9 taken 1098 times.
|
1301 | if (commandName == "Set Variable" && i == 0 && !arg.empty()) { |
| 484 |
1/2✓ Branch 0 taken 203 times.
✗ Branch 1 not taken.
|
203 | referenced.insert(arg); |
| 485 | } | ||
| 486 | // %VAR:name% substitution reference, anywhere in any arg. | ||
| 487 | 1301 | std::size_t pos = 0; | |
| 488 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1301 times.
|
1301 | while ((pos = arg.find("%VAR:", pos)) != std::string::npos) { |
| 489 | ✗ | std::size_t end = arg.find('%', pos + 5); | |
| 490 | ✗ | if (end == std::string::npos) { | |
| 491 | ✗ | break; | |
| 492 | } | ||
| 493 | ✗ | referenced.insert(arg.substr(pos + 5, end - pos - 5)); | |
| 494 | ✗ | pos = end + 1; | |
| 495 | } | ||
| 496 | 1301 | } | |
| 497 | 210 | } | |
| 498 | 1745 | } | |
| 499 | 1745 | return referenced; | |
| 500 | 1745 | } | |
| 501 | |||
| 502 | // Values at or under this size are reported inline in the bulk listing | ||
| 503 | // endpoints below. Bigger ones are reported as a truncated preview plus | ||
| 504 | // "truncated"/"size" so the listing page's default (non-View) load stays | ||
| 505 | // small even when a Variable holds a full fetched web page or similar - | ||
| 506 | // the full value is still one GET /api/variables/{name} away. Kept small | ||
| 507 | // (not just "under some KB") since these values also render directly in a | ||
| 508 | // table cell - variables.php additionally CSS-truncates the cell itself, | ||
| 509 | // but there's no point shipping bytes the row can't usefully show anyway. | ||
| 510 | static const size_t kInlineValueMaxBytes = 200; | ||
| 511 | |||
| 512 | 684760 | static void reportValueField(Json::Value& entry, const std::string& value) { | |
| 513 |
1/2✓ Branch 0 taken 684760 times.
✗ Branch 1 not taken.
|
684760 | if (value.size() <= kInlineValueMaxBytes) { |
| 514 |
2/4✓ Branch 0 taken 684760 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 684760 times.
✗ Branch 3 not taken.
|
684760 | entry["value"] = value; |
| 515 |
2/4✓ Branch 0 taken 684760 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 684760 times.
✗ Branch 3 not taken.
|
684760 | entry["truncated"] = false; |
| 516 | } else { | ||
| 517 | ✗ | entry["value"] = value.substr(0, kInlineValueMaxBytes); | |
| 518 | ✗ | entry["truncated"] = true; | |
| 519 | ✗ | entry["size"] = (Json::UInt64)value.size(); | |
| 520 | } | ||
| 521 | 684760 | } | |
| 522 | |||
| 523 | 1745 | void Variables::reportVariables(Json::Value& root) { | |
| 524 |
1/2✓ Branch 0 taken 1745 times.
✗ Branch 1 not taken.
|
1745 | std::set<std::string> referenced = getReferencedNamesInConfig(); |
| 525 |
1/2✓ Branch 0 taken 1745 times.
✗ Branch 1 not taken.
|
1745 | std::unique_lock<std::mutex> lock(m_lock); |
| 526 |
2/2✓ Branch 0 taken 1723 times.
✓ Branch 1 taken 1745 times.
|
3468 | for (auto const& kv : m_variables) { |
| 527 |
2/4✓ Branch 0 taken 1723 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1723 times.
✗ Branch 3 not taken.
|
1723 | reportValueField(root[kv.first], kv.second.value); |
| 528 |
3/6✓ Branch 0 taken 1723 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1723 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1723 times.
✗ Branch 5 not taken.
|
1723 | root[kv.first]["persist"] = kv.second.persist; |
| 529 |
3/6✓ Branch 0 taken 1723 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1723 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1723 times.
✗ Branch 5 not taken.
|
1723 | root[kv.first]["lastUpdated"] = (Json::Int64)kv.second.lastUpdated; |
| 530 |
4/8✓ Branch 0 taken 1723 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1723 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1723 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1723 times.
✗ Branch 7 not taken.
|
1723 | root[kv.first]["used"] = referenced.count(kv.first) > 0; |
| 531 | } | ||
| 532 | 1745 | } | |
| 533 | |||
| 534 | // fpp_* status variables are computed fresh from live Player/system state on | ||
| 535 | // every call (ComputeFppStatusVariables() above) - there's no persisted | ||
| 536 | // "last updated" the way User/MQTT Variables have. Approximate one by | ||
| 537 | // tracking each name's last-seen value here and only stamping a new time | ||
| 538 | // when it actually changes, scoped to this bulk-listing report so it doesn't | ||
| 539 | // add tracking overhead to the getVariable()/If-condition hot path. The | ||
| 540 | // first observation after an fppd (re)start always stamps "now" - there's no | ||
| 541 | // way to know when a value last changed before we started watching it. | ||
| 542 | static std::mutex fppVarChangeLock; | ||
| 543 | static std::map<std::string, std::pair<std::string, time_t>> fppVarLastChange; | ||
| 544 | |||
| 545 | 25280 | void Variables::reportFppVariables(Json::Value& root) { | |
| 546 | // Computed before taking fppVarChangeLock, never under it: computing them | ||
| 547 | // takes the playlist's lock (Player::GetCurrentStatus), and the main loop | ||
| 548 | // holds that lock while a playlist's If / Set Variable command entry runs | ||
| 549 | // and lists the variables. Holding this lock across it was the other | ||
| 550 | // half of a lock-order deadlock that hung fppd. | ||
| 551 |
1/2✓ Branch 0 taken 25280 times.
✗ Branch 1 not taken.
|
25280 | auto vars = ComputeFppStatusVariables(); |
| 552 |
1/2✓ Branch 0 taken 25280 times.
✗ Branch 1 not taken.
|
25280 | std::unique_lock<std::mutex> lock(fppVarChangeLock); |
| 553 | 25280 | time_t now = time(nullptr); | |
| 554 |
2/2✓ Branch 0 taken 682560 times.
✓ Branch 1 taken 25280 times.
|
707840 | for (auto const& kv : vars) { |
| 555 |
1/2✓ Branch 0 taken 682560 times.
✗ Branch 1 not taken.
|
682560 | auto it = fppVarLastChange.find(kv.first); |
| 556 |
6/6✓ Branch 0 taken 682533 times.
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 930 times.
✓ Branch 3 taken 681603 times.
✓ Branch 4 taken 957 times.
✓ Branch 5 taken 681603 times.
|
682560 | if (it == fppVarLastChange.end() || it->second.first != kv.second) { |
| 557 |
2/4✓ Branch 0 taken 957 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 957 times.
✗ Branch 3 not taken.
|
957 | fppVarLastChange[kv.first] = { kv.second, now }; |
| 558 | } | ||
| 559 |
2/4✓ Branch 0 taken 682560 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 682560 times.
✗ Branch 3 not taken.
|
682560 | reportValueField(root[kv.first], kv.second); |
| 560 |
4/8✓ Branch 0 taken 682560 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 682560 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 682560 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 682560 times.
✗ Branch 7 not taken.
|
682560 | root[kv.first]["lastUpdated"] = (Json::Int64)fppVarLastChange[kv.first].second; |
| 561 | } | ||
| 562 | 25280 | } | |
| 563 | |||
| 564 | 7227 | void Variables::reportMqttVariables(Json::Value& root) { | |
| 565 |
2/2✓ Branch 0 taken 7059 times.
✓ Branch 1 taken 168 times.
|
7227 | if (!mqtt) { |
| 566 | 7059 | return; | |
| 567 | } | ||
| 568 |
1/2✓ Branch 0 taken 168 times.
✗ Branch 1 not taken.
|
168 | Json::Value cache; |
| 569 |
1/2✓ Branch 0 taken 168 times.
✗ Branch 1 not taken.
|
168 | mqtt->dumpMessageCache(cache, true); |
| 570 |
3/4✓ Branch 0 taken 168 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 477 times.
✓ Branch 3 taken 168 times.
|
645 | for (auto const& topic : cache.getMemberNames()) { |
| 571 |
1/2✓ Branch 0 taken 477 times.
✗ Branch 1 not taken.
|
477 | std::string name = "mqtt-" + topic; |
| 572 |
5/10✓ Branch 0 taken 477 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 477 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 477 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 477 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 477 times.
✗ Branch 9 not taken.
|
477 | reportValueField(root[name], cache[topic]["value"].asString()); |
| 573 |
5/10✓ Branch 0 taken 477 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 477 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 477 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 477 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 477 times.
✗ Branch 9 not taken.
|
477 | root[name]["lastUpdated"] = cache[topic]["lastUpdated"]; |
| 574 | 645 | } | |
| 575 | 168 | } | |
| 576 | |||
| 577 | // -------------------------------------------------------------------------- | ||
| 578 | // OpenAPI docs for the /variables/* endpoints handled below. | ||
| 579 | // -------------------------------------------------------------------------- | ||
| 580 | |||
| 581 | /** | ||
| 582 | * List all User Variables and their current values. Pass ?validateExpression | ||
| 583 | * instead to syntax-check an expression (for the Set Variable "Expression" | ||
| 584 | * field) against currently-known variables, without setting anything. Pass | ||
| 585 | * ?fpp=true instead to list the read-only "fpp_" status variables (current | ||
| 586 | * playlist/sequence, play state, volume, etc.) instead of User Variables. | ||
| 587 | * | ||
| 588 | * @route GET /api/variables | ||
| 589 | * @param string validateExpression If set, validate this expression instead of listing variables. | ||
| 590 | * @param boolean conditionExpr If "true" alongside validateExpression, classify it the way the If | ||
| 591 | * Check editor's Name/Value fields actually evaluate it (exact variable match / formula / inert | ||
| 592 | * literal text) instead of a bare compile check. | ||
| 593 | * @param boolean fpp If "true", list the read-only fpp- status variables instead of User Variables. | ||
| 594 | * @param boolean mqtt If "true", list the read-only mqtt-<topic> variables (MQTT's own last-message- | ||
| 595 | * per-topic cache) instead of User Variables. | ||
| 596 | * @response 200 Object keyed by variable name, each with `value`, `truncated`, | ||
| 597 | * `persist`, `lastUpdated` (unix timestamp) and `used` (true if referenced | ||
| 598 | * anywhere in config/commandPresets.json, either as a Set Variable target or | ||
| 599 | * via %VAR:name%). If `validateExpression` was passed, `{"valid": true|false, | ||
| 600 | * "kind": "formula"|"template"|"literal"}` (or, with `conditionExpr=true`, | ||
| 601 | * `{"valid": true|false, "kind": "variable"|"formula"|"literal"}`) | ||
| 602 | * instead. If `fpp=true`, each entry has `value`/`truncated`/`lastUpdated` | ||
| 603 | * (no persist/used - these are live-computed, not stored; `lastUpdated` is | ||
| 604 | * approximated as the last time this endpoint observed the value actually | ||
| 605 | * change, not a true change time, and resets on an fppd restart). If | ||
| 606 | * `mqtt=true`, each entry has `value`/`truncated`/`lastUpdated` (the time | ||
| 607 | * the topic's last MQTT message was received; no persist/used). Values over | ||
| 608 | * 200 bytes are reported truncated (`truncated: true`, plus `size` = the | ||
| 609 | * real byte count) to keep this listing endpoint cheap - fetch GET | ||
| 610 | * /api/variables/{name} for the full value. | ||
| 611 | */ | ||
| 612 | |||
| 613 | /** | ||
| 614 | * Read a single User Variable's current value. | ||
| 615 | * | ||
| 616 | * @route GET /api/variables/{name} | ||
| 617 | * @response 200 The variable's value as plain text (empty string if unset). | ||
| 618 | */ | ||
| 619 | // getPathPieces() splits the whole path on '/', so a name containing '/' | ||
| 620 | // (e.g. an mqtt-<topic> Variable) lands across multiple pieces instead of | ||
| 621 | // just parts[1]. Rejoin everything after the resource segment (parts[0], | ||
| 622 | // "variables") back into one name. | ||
| 623 | 865 | static std::string joinNameFromPathPieces(const std::vector<std::string>& parts) { | |
| 624 | 865 | std::string name; | |
| 625 |
2/2✓ Branch 0 taken 1040 times.
✓ Branch 1 taken 865 times.
|
1905 | for (size_t i = 1; i < parts.size(); ++i) { |
| 626 |
2/2✓ Branch 0 taken 175 times.
✓ Branch 1 taken 865 times.
|
1040 | if (i > 1) { |
| 627 |
1/2✓ Branch 0 taken 175 times.
✗ Branch 1 not taken.
|
175 | name += "/"; |
| 628 | } | ||
| 629 |
1/2✓ Branch 0 taken 1040 times.
✗ Branch 1 not taken.
|
1040 | name += parts[i]; |
| 630 | } | ||
| 631 | 865 | return name; | |
| 632 | ✗ | } | |
| 633 | |||
| 634 | 27393 | HttpResponsePtr Variables::render_GET(const HttpRequestPtr& req) { | |
| 635 |
2/4✓ Branch 0 taken 27393 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27393 times.
✗ Branch 3 not taken.
|
27393 | auto parts = getPathPieces(req->path()); |
| 636 | 27393 | int plen = parts.size(); | |
| 637 | |||
| 638 | // ?validateExpression=<expr> - live syntax check for the Set Variable | ||
| 639 | // "Expression" field, without setting anything. Binds every currently | ||
| 640 | // known Variable (User Variables plus the read-only fpp-/mqtt- ones) | ||
| 641 | // first, same as SetVariableCommand::run(), so a reference to an | ||
| 642 | // existing variable name validates correctly and a reference to a | ||
| 643 | // nonexistent one is correctly flagged invalid. | ||
| 644 |
2/4✓ Branch 0 taken 27393 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27393 times.
✗ Branch 3 not taken.
|
27393 | std::string exprArg = getRequestArg(req, "validateExpression"); |
| 645 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 27380 times.
|
27393 | if (!exprArg.empty()) { |
| 646 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | Json::Value result; |
| 647 | // conditionExpr=true: the If Check editor's Name/Value fields, which | ||
| 648 | // go through ConditionNode::ClassifyNameOrExpression's auto-detect at | ||
| 649 | // runtime (Condition.cpp's EvaluateNameOrExpression) rather than a | ||
| 650 | // bare ExpressionProcessor::compile() - a raw compile() call always | ||
| 651 | // "succeeds" on ordinary unmatched text (it's just treated as inert | ||
| 652 | // literal, per that function's own documented behavior), so it can't | ||
| 653 | // tell a real variable match, an actual formula, and a typo'd/ | ||
| 654 | // unmatched name apart the way the Check editor's icon needs to. | ||
| 655 |
5/8✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 6 times.
|
26 | if (getRequestArg(req, "conditionExpr") == "true") { |
| 656 | 7 | bool compileOk = true; | |
| 657 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | auto kind = ConditionNode::ClassifyNameOrExpression(exprArg, compileOk); |
| 658 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | result["valid"] = compileOk; |
| 659 |
6/8✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
|
7 | result["kind"] = kind == ConditionNode::ExpressionKind::Variable ? "variable" : (kind == ConditionNode::ExpressionKind::Formula ? "formula" : "literal"); |
| 660 | } else { | ||
| 661 | // Set Variable's "Expression" field (VariableCommands.cpp's | ||
| 662 | // SetVariableCommand::run(), type=="Expression" branch) really is | ||
| 663 | // just a bare ExpressionProcessor::compile() call with no | ||
| 664 | // auto-detect - so validating it the same way here is correct, | ||
| 665 | // not a shortcut. | ||
| 666 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | ExpressionProcessor proc; |
| 667 | 6 | std::map<std::string, ExpressionProcessor::ExpressionVariable> boundVars; | |
| 668 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 168 times.
✓ Branch 3 taken 6 times.
|
174 | for (auto const& varName : getAllVariableNames()) { |
| 669 |
1/2✓ Branch 0 taken 168 times.
✗ Branch 1 not taken.
|
168 | auto inserted = boundVars.try_emplace(varName, varName); |
| 670 |
3/6✓ Branch 0 taken 168 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 168 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 168 times.
✗ Branch 5 not taken.
|
336 | inserted.first->second.setValue(getVariable(varName)); |
| 671 |
1/2✓ Branch 0 taken 168 times.
✗ Branch 1 not taken.
|
168 | proc.bindVariable(&inserted.first->second); |
| 672 | 6 | } | |
| 673 |
3/6✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
6 | result["valid"] = proc.compile(exprArg); |
| 674 | // ExpressionProcessor::compile() treats anything not starting with | ||
| 675 | // '=' (and with no embedded "==...=="/"%%...%%" markers) as inert | ||
| 676 | // literal text to store verbatim - it always "compiles" fine, so a | ||
| 677 | // bare variable name like "outsideTemp" (meant to reference that | ||
| 678 | // variable's value) silently becomes the literal string | ||
| 679 | // "outsideTemp" instead. A plain valid:true can't distinguish that | ||
| 680 | // mistake from an intentional formula, so flag it here for the | ||
| 681 | // frontend to warn about instead of showing a bare green check. | ||
| 682 |
4/6✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
6 | bool looksLikeFormula = exprArg.size() > 1 && exprArg[0] == '=' && exprArg[1] != '='; |
| 683 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
|
6 | bool hasEmbeddedMarker = exprArg.find("==") != std::string::npos || exprArg.find("%%") != std::string::npos; |
| 684 |
6/8✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
|
6 | result["kind"] = looksLikeFormula ? "formula" : (hasEmbeddedMarker ? "template" : "literal"); |
| 685 | 6 | } | |
| 686 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
26 | std::string resultStr = SaveJsonToString(result); |
| 687 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
13 | return makeStringResponse(resultStr, 200, "application/json"); |
| 688 | 13 | } | |
| 689 | |||
| 690 |
2/2✓ Branch 0 taken 355 times.
✓ Branch 1 taken 27025 times.
|
27380 | if (plen > 1) { |
| 691 |
1/2✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
|
355 | std::string name = joinNameFromPathPieces(parts); |
| 692 |
2/4✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 355 times.
✗ Branch 3 not taken.
|
710 | std::string v = getVariable(name, ""); |
| 693 |
2/4✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 355 times.
✗ Branch 3 not taken.
|
355 | return makeStringResponse(v, 200, "text/plain"); |
| 694 | 355 | } | |
| 695 | |||
| 696 |
1/2✓ Branch 0 taken 27025 times.
✗ Branch 1 not taken.
|
27025 | Json::Value result; |
| 697 |
5/8✓ Branch 0 taken 27025 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27025 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 27025 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 25280 times.
✓ Branch 7 taken 1745 times.
|
54050 | if (getRequestArg(req, "fpp") == "true") { |
| 698 |
1/2✓ Branch 0 taken 25280 times.
✗ Branch 1 not taken.
|
25280 | reportFppVariables(result); |
| 699 |
4/8✓ Branch 0 taken 1745 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1745 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1745 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1745 times.
|
3490 | } else if (getRequestArg(req, "mqtt") == "true") { |
| 700 | ✗ | reportMqttVariables(result); | |
| 701 | } else { | ||
| 702 |
1/2✓ Branch 0 taken 1745 times.
✗ Branch 1 not taken.
|
1745 | reportVariables(result); |
| 703 | } | ||
| 704 |
2/4✓ Branch 0 taken 27025 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27025 times.
✗ Branch 3 not taken.
|
54050 | std::string resultStr = SaveJsonToString(result, " "); |
| 705 |
2/4✓ Branch 0 taken 27025 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27025 times.
✗ Branch 3 not taken.
|
27025 | return makeStringResponse(resultStr, 200, "application/json"); |
| 706 | 27393 | } | |
| 707 | |||
| 708 | /** | ||
| 709 | * Set a User Variable's value. Body is the raw new value (plain text, not JSON). | ||
| 710 | * Add ?persist=true to save it to config/variables.json so it survives an | ||
| 711 | * fppd restart; omitted or any other value means the variable is in-memory only. | ||
| 712 | * | ||
| 713 | * @route POST /api/variables/{name} | ||
| 714 | * @param boolean persist Persist the value to disk so it survives a restart. | ||
| 715 | * @body new-value-goes-here | ||
| 716 | * @response 200 "OK" | ||
| 717 | * @response 400 Missing variable name in the path. | ||
| 718 | */ | ||
| 719 | 270 | HttpResponsePtr Variables::render_POST(const HttpRequestPtr& req) { | |
| 720 |
2/4✓ Branch 0 taken 270 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 270 times.
✗ Branch 3 not taken.
|
270 | auto parts = getPathPieces(req->path()); |
| 721 | 270 | int plen = parts.size(); | |
| 722 | |||
| 723 |
2/2✓ Branch 0 taken 255 times.
✓ Branch 1 taken 15 times.
|
270 | if (plen > 1) { |
| 724 |
1/2✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
|
255 | std::string name = joinNameFromPathPieces(parts); |
| 725 |
2/4✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 255 times.
|
255 | if (IsFppStatusVariableName(name)) { |
| 726 | ✗ | return makeStringResponse("\"" + name + "\" is a read-only fpp- status variable and cannot be set", 400, "text/plain"); | |
| 727 | } | ||
| 728 |
1/2✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
|
255 | std::string value = std::string(getRequestContent(req)); |
| 729 |
3/6✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 255 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 255 times.
✗ Branch 5 not taken.
|
255 | bool persist = getRequestArg(req, "persist") == "true"; |
| 730 |
4/8✗ Branch 0 not taken.
✓ Branch 1 taken 255 times.
✓ Branch 2 taken 255 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 255 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 255 times.
✗ Branch 7 not taken.
|
255 | LogDebug(VB_COMMAND, "POST /api/variables/%s from %s: \"%s\"%s\n", name.c_str(), |
| 731 | getEffectiveClientIP(req).c_str(), TruncateForLog(value).c_str(), persist ? " (persist)" : ""); | ||
| 732 |
1/2✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
|
255 | setVariable(name, value, persist); |
| 733 |
3/6✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 255 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 255 times.
✗ Branch 5 not taken.
|
765 | return makeStringResponse("OK", 200, "text/plain"); |
| 734 | 255 | } | |
| 735 |
3/6✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
|
60 | return makeStringResponse("Bad Request", 400, "text/plain"); |
| 736 | 270 | } | |
| 737 | |||
| 738 | /** | ||
| 739 | * Delete a User Variable entirely - unlike POSTing an empty body (the UI's | ||
| 740 | * "Clear"), which only resets its value/persist flag and leaves the row | ||
| 741 | * behind, this removes the name from the list altogether. | ||
| 742 | * | ||
| 743 | * @route DELETE /api/variables/{name} | ||
| 744 | * @response 200 "OK" | ||
| 745 | * @response 400 Missing variable name in the path, or name is a read-only | ||
| 746 | * fpp-/mqtt- variable (nothing to delete - those aren't stored). | ||
| 747 | */ | ||
| 748 | 270 | HttpResponsePtr Variables::render_DELETE(const HttpRequestPtr& req) { | |
| 749 |
2/4✓ Branch 0 taken 270 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 270 times.
✗ Branch 3 not taken.
|
270 | auto parts = getPathPieces(req->path()); |
| 750 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 255 times.
|
270 | if (parts.size() <= 1) { |
| 751 |
3/6✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
|
60 | return makeStringResponse("Bad Request", 400, "text/plain"); |
| 752 | } | ||
| 753 |
1/2✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
|
255 | std::string name = joinNameFromPathPieces(parts); |
| 754 |
5/10✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 255 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 255 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 255 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 255 times.
|
255 | if (IsFppStatusVariableName(name) || IsMqttVariableName(name)) { |
| 755 | ✗ | return makeStringResponse("\"" + name + "\" is read-only and cannot be deleted", 400, "text/plain"); | |
| 756 | } | ||
| 757 |
2/4✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 255 times.
✗ Branch 3 not taken.
|
255 | LogDebug(VB_COMMAND, "DELETE /api/variables/%s from %s\n", name.c_str(), getEffectiveClientIP(req).c_str()); |
| 758 |
1/2✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
|
255 | deleteVariable(name); |
| 759 |
3/6✓ Branch 0 taken 255 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 255 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 255 times.
✗ Branch 5 not taken.
|
1020 | return makeStringResponse("OK", 200, "text/plain"); |
| 760 | 270 | } | |
| 761 |