| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 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 | |||
| 17 | #include <climits> | ||
| 18 | #include <ctime> | ||
| 19 | #include <list> | ||
| 20 | #include <map> | ||
| 21 | #include <memory> | ||
| 22 | #include <mutex> | ||
| 23 | #include <stdio.h> | ||
| 24 | #include <stdlib.h> | ||
| 25 | #include <string.h> | ||
| 26 | #include <string> | ||
| 27 | #include <thread> | ||
| 28 | #include <chrono> | ||
| 29 | #include <condition_variable> | ||
| 30 | #include <time.h> | ||
| 31 | #include <utility> | ||
| 32 | #include <vector> | ||
| 33 | |||
| 34 | #include "Player.h" | ||
| 35 | #include "Warnings.h" | ||
| 36 | #include "common.h" | ||
| 37 | #include "log.h" | ||
| 38 | #include "settings.h" | ||
| 39 | #include "commands/Commands.h" | ||
| 40 | #include "mediaoutput/mediaoutput.h" | ||
| 41 | #include "playlist/Playlist.h" | ||
| 42 | |||
| 43 | #include "Scheduler.h" | ||
| 44 | |||
| 45 | Scheduler* scheduler = NULL; | ||
| 46 | |||
| 47 | ///////////////////////////////////////////////////////////////////////////// | ||
| 48 | |||
| 49 | // Test assumptions | ||
| 50 | static_assert(std::is_move_constructible_v<ScheduledItem>); | ||
| 51 | |||
| 52 | // Warning 23 is removed by an exact match on its text, so every site that adds | ||
| 53 | // or removes it for an entry has to produce the identical string. | ||
| 54 | // strftime() into a fixed buffer. The time and date formats come from | ||
| 55 | // settings; when a result doesn't fit, strftime() returns 0 and leaves the | ||
| 56 | // buffer's contents undefined, and the callers then read an unterminated | ||
| 57 | // string. Leave it empty instead. | ||
| 58 | 151247 | static void FormatTime(char* buf, size_t size, const char* fmt, const struct tm* t) { | |
| 59 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 151183 times.
|
151247 | if (strftime(buf, size, fmt, t) == 0) |
| 60 | 64 | buf[0] = '\0'; | |
| 61 | 151247 | } | |
| 62 | |||
| 63 | 671 | static std::string missingEntryWarning(const ScheduleEntry& entry) { | |
| 64 |
2/6✓ Branch 0 taken 671 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 671 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2013 | return std::string("Scheduled ") + (entry.sequence ? "Sequence" : "Playlist") + |
| 65 |
4/8✗ Branch 0 not taken.
✓ Branch 1 taken 671 times.
✓ Branch 2 taken 671 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 671 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 671 times.
✗ Branch 7 not taken.
|
2013 | " '" + entry.playlist + "' does not exist"; |
| 66 | } | ||
| 67 | |||
| 68 | ///////////////////////////////////////////////////////////////////////////// | ||
| 69 | |||
| 70 | // A schedule entry's args are stored as given, and the command layer accepts | ||
| 71 | // objects and arrays among them (an If command's condition), so asString() | ||
| 72 | // and operator[] can't be used on them blindly: either throws LogicError, and | ||
| 73 | // on the main loop nothing catches it - fppd aborted at every countdown to | ||
| 74 | // such an entry. | ||
| 75 | 1909 | static std::string ScheduleArgToString(const Json::Value& v) { | |
| 76 |
5/6✓ Branch 0 taken 1897 times.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1897 times.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 1897 times.
|
1909 | if (v.isObject() || v.isArray()) { |
| 77 |
2/4✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
|
24 | return SaveJsonToString(v); |
| 78 | } | ||
| 79 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1897 times.
|
1897 | if (v.isNull()) { |
| 80 | ✗ | return ""; | |
| 81 | } | ||
| 82 | 1897 | return v.asString(); | |
| 83 | } | ||
| 84 | |||
| 85 | // Whether two items carry the same first argument (how a ran item is matched). | ||
| 86 | 43 | static bool SameFirstScheduleArg(const Json::Value& a, const Json::Value& b) { | |
| 87 |
3/6✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 43 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 43 times.
|
43 | if (!a.isArray() || !b.isArray()) { |
| 88 | ✗ | return a == b; | |
| 89 | } | ||
| 90 |
3/6✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 43 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 43 times.
|
43 | if (a.empty() || b.empty()) { |
| 91 | ✗ | return a.empty() && b.empty(); | |
| 92 | } | ||
| 93 | 43 | return a[0] == b[0]; | |
| 94 | } | ||
| 95 | |||
| 96 | 1888 | Scheduler::Scheduler() : | |
| 97 | 1888 | m_schedulerDisabled(false), | |
| 98 | 1888 | m_loadSchedule(true), | |
| 99 | 1888 | m_lastLoadDate(0), | |
| 100 | 1888 | m_lastProcTime(0), | |
| 101 | 1888 | m_lastCommandCheckTime(0), | |
| 102 | 1888 | m_timeDelta(0), | |
| 103 | 1888 | m_timeDeltaThreshold(0), | |
| 104 | 1888 | m_forcedNextPlaylist(SCHEDULE_INDEX_INVALID), | |
| 105 | 1888 | m_lastBlockedTime(0), | |
| 106 | 1888 | m_schedulesExtendBeyondDistance(false) { | |
| 107 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | RegisterCommands(); |
| 108 | |||
| 109 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | m_schedulerDisabled = getSettingInt("DisableScheduler") == 1; |
| 110 | |||
| 111 |
6/8✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1882 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 1882 times.
|
1888 | if ((m_schedulerDisabled) && (getFPPmode() == PLAYER_MODE)) { |
| 112 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
18 | WarningHolder::AddWarning(19, "FPP Scheduler is disabled"); |
| 113 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | LogWarn(VB_SCHEDULE, "Scheduler is disabled!\n"); |
| 114 | } | ||
| 115 | |||
| 116 | 1888 | m_lastProcTime = time(NULL); | |
| 117 | // Seed the FPP-command catch-up window to now rather than leaving it at the | ||
| 118 | // epoch. CheckScheduledItems() fires any command whose second falls in | ||
| 119 | // (m_lastCommandCheckTime, now], which exists so a second this process | ||
| 120 | // MISSED -- a slow midnight reload, an NTP step -- still runs. A second | ||
| 121 | // that passed before this process existed was never missed by it, and the | ||
| 122 | // 60s catch-up cap is not a substitute: from 0 every start would replay the | ||
| 123 | // preceding minute of commands, so a scheduled "Outputs Off" or a restart | ||
| 124 | // would run a second time on every restart, and repeatedly in a restart | ||
| 125 | // loop. | ||
| 126 | 1888 | m_lastCommandCheckTime = m_lastProcTime; | |
| 127 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | LoadScheduleFromFile(); |
| 128 | |||
| 129 | // Sun-relative times and the coordinates warning come from these, and | ||
| 130 | // are only worked out when the schedule is loaded. | ||
| 131 |
2/2✓ Branch 0 taken 3776 times.
✓ Branch 1 taken 1888 times.
|
5664 | for (const char* s : { "Latitude", "Longitude" }) { |
| 132 |
3/6✓ Branch 0 taken 3776 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3776 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3776 times.
✗ Branch 5 not taken.
|
18560 | registerSettingsListener("Scheduler", s, [this](const std::string&) { |
| 133 | 2 | ReloadScheduleFile(); | |
| 134 | 2 | }); | |
| 135 | } | ||
| 136 | 1888 | } | |
| 137 | |||
| 138 | 1867 | Scheduler::~Scheduler() { | |
| 139 | 7468 | unregisterSettingsListener("Scheduler", "Latitude"); | |
| 140 | 5601 | unregisterSettingsListener("Scheduler", "Longitude"); | |
| 141 | 1867 | } | |
| 142 | |||
| 143 | 464648 | void Scheduler::ScheduleProc(void) { | |
| 144 | 464648 | time_t procTime = time(NULL); | |
| 145 | |||
| 146 | // Only check schedule once per second at most | ||
| 147 |
2/2✓ Branch 0 taken 454365 times.
✓ Branch 1 taken 10283 times.
|
464648 | if (m_lastProcTime == procTime) |
| 148 | 454365 | return; | |
| 149 | |||
| 150 | 10283 | int timeDelta = (int)(procTime - m_lastProcTime); | |
| 151 | 10283 | m_lastProcTime = procTime; | |
| 152 | |||
| 153 | // reload the schedule file once per day or if 5 seconds has elapsed | ||
| 154 | // since the last process time in case the time jumps | ||
| 155 |
4/6✓ Branch 0 taken 10283 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10283 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 44 times.
✓ Branch 5 taken 10239 times.
|
20566 | if ((m_lastLoadDate != GetCurrentDateInt()) || |
| 156 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 10239 times.
|
10283 | (abs(timeDelta) > 5)) { |
| 157 | 44 | m_loadSchedule = true; | |
| 158 | |||
| 159 | // Cleanup any ran items older than 2 days. SetItemRan mutates m_ranItems | ||
| 160 | // under m_scheduleLock from HTTP/MQTT/detached command threads, so this | ||
| 161 | // prune has to hold the lock too. | ||
| 162 | 44 | const time_t twoDaysAgo = time(NULL) - (2 * 24 * 60 * 60); | |
| 163 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 164 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | for (auto it = m_ranItems.begin(); it != m_ranItems.end(); /* in loop */) { |
| 165 | ✗ | if (it->first >= twoDaysAgo) | |
| 166 | ✗ | break; | |
| 167 | ✗ | it = m_ranItems.erase(it); | |
| 168 | } | ||
| 169 | |||
| 170 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 23 times.
|
44 | if (timeDelta < -SCHEDULE_COMMAND_CATCHUP_S) { |
| 171 | // The clock stepped back (an RTC that was ahead, corrected by | ||
| 172 | // NTP). Whatever "ran" at the wrong, later time hasn't really run | ||
| 173 | // yet: forget it so the reload below schedules it again, and pull | ||
| 174 | // the command check window back so commands in the stepped-over | ||
| 175 | // span fire when their second actually comes instead of being | ||
| 176 | // treated as already examined. | ||
| 177 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | LogWarn(VB_SCHEDULE, "Clock stepped back %d seconds; re-arming schedule items after now\n", -timeDelta); |
| 178 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
21 | m_ranItems.erase(m_ranItems.upper_bound(procTime), m_ranItems.end()); |
| 179 | 21 | m_lastCommandCheckTime = procTime; | |
| 180 | } | ||
| 181 | 44 | } | |
| 182 | |||
| 183 |
2/2✓ Branch 0 taken 76 times.
✓ Branch 1 taken 10207 times.
|
10283 | if (m_loadSchedule) |
| 184 |
1/2✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
|
76 | LoadScheduleFromFile(); |
| 185 | |||
| 186 |
1/2✓ Branch 0 taken 10283 times.
✗ Branch 1 not taken.
|
10283 | CheckScheduledItems(); |
| 187 | |||
| 188 | 10283 | return; | |
| 189 | } | ||
| 190 | |||
| 191 | 2402 | void Scheduler::CheckIfShouldBePlayingNow(int ignoreRepeat, int forceStopped) { | |
| 192 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2396 times.
|
2402 | if (m_schedulerDisabled) |
| 193 | 6 | return; | |
| 194 | |||
| 195 | 2396 | LogDebug(VB_SCHEDULE, "CheckIfShouldBePlayingNow(%d, %d)\n", ignoreRepeat, forceStopped); | |
| 196 | |||
| 197 | 2396 | std::time_t now = time(nullptr); | |
| 198 | { | ||
| 199 |
1/2✓ Branch 0 taken 2396 times.
✗ Branch 1 not taken.
|
2396 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 200 | |||
| 201 | // Idle in a switch is no stop (see Player::SwitchScope): re-armed | ||
| 202 | // then, the next pass pre-empts the playlist being switched to. | ||
| 203 | // Read after the status, as in the main loop. | ||
| 204 |
1/2✓ Branch 0 taken 2396 times.
✗ Branch 1 not taken.
|
2396 | bool idle = Player::INSTANCE.GetStatus() == FPP_STATUS_IDLE; |
| 205 |
1/2✓ Branch 0 taken 2396 times.
✗ Branch 1 not taken.
|
2396 | bool switching = Player::Switching(); |
| 206 | |||
| 207 |
2/2✓ Branch 0 taken 228 times.
✓ Branch 1 taken 2288 times.
|
2516 | for (auto& itemTime : m_scheduledItems) { |
| 208 |
2/2✓ Branch 0 taken 108 times.
✓ Branch 1 taken 120 times.
|
228 | if (itemTime.first > now) { |
| 209 | 108 | break; // no need to look at items that are further in the future | |
| 210 | } | ||
| 211 | |||
| 212 |
1/2✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
|
120 | LogExcess(VB_SCHEDULE, "Checking scheduled items:\n"); |
| 213 |
2/2✓ Branch 0 taken 126 times.
✓ Branch 1 taken 120 times.
|
246 | for (auto& item : itemTime.second) { |
| 214 |
2/4✓ Branch 0 taken 126 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 126 times.
|
126 | if (WillLog(LOG_EXCESSIVE, VB_SCHEDULE)) |
| 215 | ✗ | DumpScheduledItem(item.startTime, item); | |
| 216 |
2/2✓ Branch 0 taken 122 times.
✓ Branch 1 taken 4 times.
|
126 | if (item.isPlaylist) { |
| 217 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 115 times.
|
122 | if (item.endTime <= now) { |
| 218 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | LogExcess(VB_SCHEDULE, "End time is in the past, skipping item\n"); |
| 219 | 7 | continue; | |
| 220 | } | ||
| 221 | |||
| 222 | 115 | bool ir = ignoreRepeat; | |
| 223 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 112 times.
|
115 | if ((m_forcedNextPlaylist == item.entryIndex) && |
| 224 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | (m_forcedNextPlaylistStart == item.startTime)) { |
| 225 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | LogExcess(VB_SCHEDULE, "This item entry index == m_forcedNextPlaylist\n"); |
| 226 | 3 | ir = true; | |
| 227 | } | ||
| 228 | |||
| 229 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 115 times.
|
115 | if (!switching && |
| 230 |
2/8✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 87 times.
✓ Branch 5 taken 28 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
115 | (idle || (Player::INSTANCE.GetPlaylistName() != item.entry->playlist)) && |
| 231 |
2/2✓ Branch 0 taken 102 times.
✓ Branch 1 taken 13 times.
|
115 | (forceStopped != item.entryIndex) && |
| 232 |
5/6✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 85 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 15 times.
|
317 | (item.entry->repeat || ir) && |
| 233 |
3/6✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 87 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 115 times.
|
202 | !m_startsInFlight.count({ item.startTime, item.entryIndex })) { |
| 234 |
1/2✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
|
87 | LogExcess(VB_SCHEDULE, "Item run status reset\n"); |
| 235 | 87 | item.ran = false; | |
| 236 | } | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
| 240 | 2396 | } | |
| 241 | 2396 | LogExcess(VB_SCHEDULE, "Done checking list, proceeding to call CheckScheduledItems()\n"); | |
| 242 | |||
| 243 | 2396 | CheckScheduledItems(getSettingInt("restarted") == 1); | |
| 244 | } | ||
| 245 | |||
| 246 | 17 | std::string Scheduler::GetPlaylistThatShouldBePlaying(int& repeat) { | |
| 247 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | if (m_schedulerDisabled) |
| 248 | ✗ | return ""; | |
| 249 | |||
| 250 | 17 | std::time_t now = time(nullptr); | |
| 251 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 252 | |||
| 253 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | for (auto& itemTime : m_scheduledItems) { |
| 254 | ✗ | if (itemTime.first > now) { | |
| 255 | ✗ | break; // no need to look at items that are further in the future | |
| 256 | } | ||
| 257 | |||
| 258 | ✗ | for (auto& item : itemTime.second) { | |
| 259 | ✗ | if (item.isPlaylist) { | |
| 260 | // Skip things that should have ended in the past | ||
| 261 | ✗ | if (item.endTime <= now) { | |
| 262 | ✗ | continue; | |
| 263 | } | ||
| 264 | |||
| 265 | ✗ | repeat = item.entry->repeat; | |
| 266 | ✗ | return item.entry->playlist; | |
| 267 | } | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | 17 | repeat = 0; | |
| 272 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
34 | return ""; |
| 273 | 17 | } | |
| 274 | |||
| 275 | 249 | void Scheduler::ReloadScheduleFile(void) { | |
| 276 | 249 | m_loadSchedule = true; | |
| 277 | 249 | } | |
| 278 | |||
| 279 | 344 | void Scheduler::AddScheduledItems(ScheduleEntry* entry, int index, std::time_t loadTime) { | |
| 280 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 344 times.
|
344 | if (!entry->enabled) |
| 281 | 1 | return; | |
| 282 | |||
| 283 |
2/2✓ Branch 0 taken 327 times.
✓ Branch 1 taken 17 times.
|
344 | if (!entry->playlist.empty()) { |
| 284 | 327 | std::string playlistFile; | |
| 285 |
1/2✓ Branch 0 taken 327 times.
✗ Branch 1 not taken.
|
327 | std::string warning = missingEntryWarning(*entry); |
| 286 | |||
| 287 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 327 times.
|
327 | if (entry->sequence) { |
| 288 | ✗ | playlistFile = FPP_DIR_SEQUENCE("/" + entry->playlist); | |
| 289 | } else { | ||
| 290 |
5/10✓ Branch 0 taken 327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 327 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 327 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 327 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 327 times.
✗ Branch 9 not taken.
|
981 | playlistFile = FPP_DIR_PLAYLIST("/" + entry->playlist + ".json"); |
| 291 | } | ||
| 292 | |||
| 293 |
3/4✓ Branch 0 taken 327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 326 times.
✓ Branch 3 taken 1 times.
|
327 | if (FileExists(playlistFile)) { |
| 294 |
2/4✓ Branch 0 taken 326 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 326 times.
✗ Branch 3 not taken.
|
652 | WarningHolder::RemoveWarning(23, warning); |
| 295 | } else { | ||
| 296 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | ScheduleLoadWarning(23, warning); |
| 297 | 1 | entry->enabled = false; | |
| 298 | 1 | return; | |
| 299 | } | ||
| 300 |
4/4✓ Branch 0 taken 326 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 326 times.
✓ Branch 3 taken 1 times.
|
328 | } |
| 301 | |||
| 302 | 343 | int dayIndex = entry->dayIndex; | |
| 303 | 343 | std::time_t currTime = loadTime; | |
| 304 | struct tm now; | ||
| 305 | 343 | localtime_r(&currTime, &now); | |
| 306 |
1/2✓ Branch 0 taken 343 times.
✗ Branch 1 not taken.
|
343 | int scheduleDistance = getSettingInt("ScheduleDistance"); |
| 307 | |||
| 308 | // Check if this schedule's end date extends beyond the schedule distance | ||
| 309 | // Track this for warning display but respect the user's ScheduleDistance setting | ||
| 310 |
3/4✓ Branch 0 taken 343 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 112 times.
✓ Branch 3 taken 231 times.
|
343 | if (entry->endDate > 0 && entry->endDate < 10000000) { |
| 311 | // This is a yearless date (< 10000000), calculate actual end date | ||
| 312 | 112 | int currentDate = (now.tm_year + 1900) * 10000 + (now.tm_mon + 1) * 100 + now.tm_mday; | |
| 313 | 112 | int endMD = entry->endDate; | |
| 314 | |||
| 315 | // Reconstruct the full end date for the current or next year | ||
| 316 | 112 | int endYear = now.tm_year + 1900; | |
| 317 | 112 | int calculatedEndDate = endYear * 10000 + endMD; | |
| 318 | |||
| 319 | // If calculated end date is before today, use next year | ||
| 320 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 104 times.
|
112 | if (calculatedEndDate < currentDate) { |
| 321 | 8 | calculatedEndDate = (endYear + 1) * 10000 + endMD; | |
| 322 | } | ||
| 323 | |||
| 324 | // Calculate days from now to end date | ||
| 325 | 112 | int daysToEnd = (calculatedEndDate / 10000 - currentDate / 10000) * 365 + | |
| 326 | 112 | ((calculatedEndDate / 100 % 100) - (currentDate / 100 % 100)) * 30 + | |
| 327 | 112 | (calculatedEndDate % 100 - currentDate % 100); | |
| 328 | |||
| 329 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 103 times.
|
112 | if (daysToEnd > scheduleDistance) { |
| 330 | 9 | m_schedulesExtendBeyondDistance = true; | |
| 331 | } | ||
| 332 |
1/2✓ Branch 0 taken 231 times.
✗ Branch 1 not taken.
|
343 | } else if (entry->endDate >= 10000000) { |
| 333 | // Full date specified | ||
| 334 | 231 | int currentDate = (now.tm_year + 1900) * 10000 + (now.tm_mon + 1) * 100 + now.tm_mday; | |
| 335 | 231 | int daysDiff = ((entry->endDate / 10000) - (currentDate / 10000)) * 365 + | |
| 336 | 231 | ((entry->endDate / 100 % 100) - (currentDate / 100 % 100)) * 30 + | |
| 337 | 231 | (entry->endDate % 100 - currentDate % 100); | |
| 338 | |||
| 339 |
2/2✓ Branch 0 taken 118 times.
✓ Branch 1 taken 113 times.
|
231 | if (daysDiff > scheduleDistance) { |
| 340 | 118 | m_schedulesExtendBeyondDistance = true; | |
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | // Convert everything to a day mask to simplify code below. Odd/even is | ||
| 345 | // not a fixed set of weekdays, so it schedules its own occurrences here | ||
| 346 | // rather than through the mask; dayIndex is left as INX_ODD_DAY/EVEN_DAY. | ||
| 347 |
4/4✓ Branch 0 taken 337 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 332 times.
|
343 | if ((dayIndex == INX_ODD_DAY) || (dayIndex == INX_EVEN_DAY)) { |
| 348 | // dayOffset is 0 when today already matches this entry's parity, 1 | ||
| 349 | // when it does not (so the next - and previous - matching day is one | ||
| 350 | // day away, since odd/even parity alternates daily). | ||
| 351 |
3/4✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 5 times.
|
11 | int dayOffset = entry->IsOddEvenMatch(currTime) ? 0 : 1; |
| 352 | |||
| 353 | // Schedule yesterday if needed to handle midnight crossovers | ||
| 354 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
|
11 | if (dayOffset) |
| 355 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | entry->pushStartEndTimes(now.tm_wday - 1, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 356 | |||
| 357 |
2/2✓ Branch 0 taken 140 times.
✓ Branch 1 taken 11 times.
|
151 | for (int i = now.tm_wday + dayOffset; i <= scheduleDistance; i += 2) { |
| 358 |
1/2✓ Branch 0 taken 140 times.
✗ Branch 1 not taken.
|
140 | entry->pushStartEndTimes(i, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 359 | } | ||
| 360 | 11 | } else { | |
| 361 |
1/2✓ Branch 0 taken 332 times.
✗ Branch 1 not taken.
|
332 | dayIndex = ScheduleEntry::DayIndexToMask(dayIndex); |
| 362 | } | ||
| 363 | |||
| 364 | // Special case: if today is Sunday, a Saturday-night start that crosses | ||
| 365 | // midnight (e.g. 22:00->02:00) bleeds into Sunday morning. Push it only | ||
| 366 | // for entries that actually run on Saturday - hence the Saturday-mask | ||
| 367 | // test - so a Monday-only crossover entry no longer plays early Sunday. | ||
| 368 | // Odd/even entries are excluded because the block above already pushes | ||
| 369 | // yesterday (Saturday, when today is Sunday) whenever today's parity does | ||
| 370 | // not match; firing here too would double-push or push the wrong parity. | ||
| 371 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 332 times.
|
343 | if ((now.tm_wday == 0) && |
| 372 |
3/4✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
11 | (entry->dayIndex != INX_ODD_DAY) && (entry->dayIndex != INX_EVEN_DAY) && |
| 373 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | (dayIndex & INX_DAY_MASK_SATURDAY)) |
| 374 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | entry->pushStartEndTimes(-1, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 375 | |||
| 376 |
4/4✓ Branch 0 taken 337 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 332 times.
✓ Branch 3 taken 5 times.
|
343 | if ((entry->dayIndex != INX_ODD_DAY) && (entry->dayIndex != INX_EVEN_DAY)) { |
| 377 |
2/2✓ Branch 0 taken 4300 times.
✓ Branch 1 taken 332 times.
|
4632 | for (int weekOffset = 0; weekOffset <= scheduleDistance; weekOffset += 7) { |
| 378 |
2/2✓ Branch 0 taken 4140 times.
✓ Branch 1 taken 160 times.
|
4300 | if (dayIndex & INX_DAY_MASK_SUNDAY) |
| 379 |
1/2✓ Branch 0 taken 4140 times.
✗ Branch 1 not taken.
|
4140 | entry->pushStartEndTimes(INX_SUN + weekOffset, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 380 | |||
| 381 |
2/2✓ Branch 0 taken 4165 times.
✓ Branch 1 taken 135 times.
|
4300 | if (dayIndex & INX_DAY_MASK_MONDAY) |
| 382 |
1/2✓ Branch 0 taken 4165 times.
✗ Branch 1 not taken.
|
4165 | entry->pushStartEndTimes(INX_MON + weekOffset, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 383 | |||
| 384 |
2/2✓ Branch 0 taken 4185 times.
✓ Branch 1 taken 115 times.
|
4300 | if (dayIndex & INX_DAY_MASK_TUESDAY) |
| 385 |
1/2✓ Branch 0 taken 4185 times.
✗ Branch 1 not taken.
|
4185 | entry->pushStartEndTimes(INX_TUE + weekOffset, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 386 | |||
| 387 |
2/2✓ Branch 0 taken 4185 times.
✓ Branch 1 taken 115 times.
|
4300 | if (dayIndex & INX_DAY_MASK_WEDNESDAY) |
| 388 |
1/2✓ Branch 0 taken 4185 times.
✗ Branch 1 not taken.
|
4185 | entry->pushStartEndTimes(INX_WED + weekOffset, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 389 | |||
| 390 |
2/2✓ Branch 0 taken 4160 times.
✓ Branch 1 taken 140 times.
|
4300 | if (dayIndex & INX_DAY_MASK_THURSDAY) |
| 391 |
1/2✓ Branch 0 taken 4160 times.
✗ Branch 1 not taken.
|
4160 | entry->pushStartEndTimes(INX_THU + weekOffset, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 392 | |||
| 393 |
2/2✓ Branch 0 taken 4160 times.
✓ Branch 1 taken 140 times.
|
4300 | if (dayIndex & INX_DAY_MASK_FRIDAY) |
| 394 |
1/2✓ Branch 0 taken 4160 times.
✗ Branch 1 not taken.
|
4160 | entry->pushStartEndTimes(INX_FRI + weekOffset, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 395 | |||
| 396 |
2/2✓ Branch 0 taken 4165 times.
✓ Branch 1 taken 135 times.
|
4300 | if (dayIndex & INX_DAY_MASK_SATURDAY) |
| 397 |
1/2✓ Branch 0 taken 4165 times.
✗ Branch 1 not taken.
|
4165 | entry->pushStartEndTimes(INX_SAT + weekOffset, m_timeDelta, m_timeDeltaThreshold, currTime); |
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | // loop through entry->startEndTimes and add to m_scheduledItems | ||
| 402 | 343 | time_t startTime = 0; | |
| 403 | 343 | time_t endTime = 0; | |
| 404 |
2/2✓ Branch 0 taken 3207 times.
✓ Branch 1 taken 343 times.
|
3550 | for (auto& startEnd : entry->startEndTimes) { |
| 405 | 3207 | startTime = startEnd.first; | |
| 406 | 3207 | endTime = startEnd.second; | |
| 407 | |||
| 408 | // Check to see if this occurrence is within the date range | ||
| 409 | struct tm later; | ||
| 410 | 3207 | localtime_r(&startTime, &later); | |
| 411 | 3207 | int dateInt = 0; | |
| 412 | 3207 | dateInt += (later.tm_year + 1900) * 10000; | |
| 413 | 3207 | dateInt += (later.tm_mon + 1) * 100; | |
| 414 | 3207 | dateInt += (later.tm_mday); | |
| 415 | |||
| 416 | // Skip if occurrence is outside the date range | ||
| 417 |
2/4✓ Branch 0 taken 3207 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3207 times.
|
3207 | if (!DateInRange(dateInt, entry->startDate, entry->endDate)) { |
| 418 | ✗ | continue; | |
| 419 | } | ||
| 420 | |||
| 421 |
1/2✓ Branch 0 taken 3207 times.
✗ Branch 1 not taken.
|
3207 | auto& vec = m_scheduledItems[startTime]; |
| 422 | 3207 | vec.push_back({ .entry = entry, | |
| 423 | .priority = index, // change this if we add a priority field | ||
| 424 | .entryIndex = index, | ||
| 425 | .ran = false, | ||
| 426 | .startTime = startTime, | ||
| 427 | .endTime = startTime }); | ||
| 428 | 3207 | ScheduledItem& newItem = vec.back(); | |
| 429 | |||
| 430 |
2/2✓ Branch 0 taken 2317 times.
✓ Branch 1 taken 890 times.
|
3207 | if (!entry->playlist.empty()) { |
| 431 | // Old style schedule entry without a FPP Command | ||
| 432 |
1/2✓ Branch 0 taken 2317 times.
✗ Branch 1 not taken.
|
2317 | Json::Value args(Json::arrayValue); |
| 433 |
2/4✓ Branch 0 taken 2317 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2317 times.
✗ Branch 3 not taken.
|
2317 | args.append(entry->playlist); |
| 434 |
4/6✓ Branch 0 taken 1080 times.
✓ Branch 1 taken 1237 times.
✓ Branch 2 taken 2317 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2317 times.
✗ Branch 5 not taken.
|
2317 | args.append(entry->repeat ? "true" : "false"); |
| 435 |
2/4✓ Branch 0 taken 2317 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2317 times.
✗ Branch 3 not taken.
|
2317 | args.append("false"); |
| 436 | |||
| 437 |
1/2✓ Branch 0 taken 2317 times.
✗ Branch 1 not taken.
|
2317 | newItem.command = "Start Playlist"; |
| 438 | 2317 | newItem.isPlaylist = true; | |
| 439 | 2317 | newItem.args = std::move(args); | |
| 440 | 2317 | newItem.endTime = endTime; | |
| 441 | |||
| 442 | // Check to see if this item already ran | ||
| 443 |
3/4✓ Branch 0 taken 2317 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2316 times.
|
2317 | if (auto it = m_ranItems.find(newItem.startTime); it != m_ranItems.end()) { |
| 444 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (auto& item : it->second) { |
| 445 | 1 | if ((newItem.command == item.command) && | |
| 446 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | (newItem.isPlaylist == item.isPlaylist) && |
| 447 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | (newItem.startTime == item.startTime) && |
| 448 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | (newItem.endTime == item.endTime) && |
| 449 |
2/10✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ 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 taken 1 times.
|
2 | (newItem.args.size() == item.args.size()) && |
| 450 | ✗ | SameFirstScheduleArg(newItem.args, item.args)) { | |
| 451 | ✗ | LogDebug(VB_SCHEDULE, "Marking playlist item as already ran:\n"); | |
| 452 | ✗ | DumpScheduledItem(newItem.startTime, newItem); | |
| 453 | ✗ | newItem.ran = true; | |
| 454 | } | ||
| 455 | } | ||
| 456 | } | ||
| 457 | 2317 | } else { | |
| 458 | // New style schedule entry with a FPP Command | ||
| 459 |
1/2✓ Branch 0 taken 890 times.
✗ Branch 1 not taken.
|
890 | newItem.command = entry->command; |
| 460 |
1/2✓ Branch 0 taken 890 times.
✗ Branch 1 not taken.
|
890 | newItem.args = entry->args; |
| 461 | } | ||
| 462 | } | ||
| 463 |
2/8✓ Branch 0 taken 3207 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3207 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
3207 | } |
| 464 | |||
| 465 | 627 | void Scheduler::DumpScheduledItem(const std::time_t itemTime, const ScheduledItem& item) { | |
| 466 | char timeBuf[32]; | ||
| 467 |
1/2✓ Branch 0 taken 627 times.
✗ Branch 1 not taken.
|
627 | std::string timeStr = ctime_r(&itemTime, timeBuf); |
| 468 | |||
| 469 | 627 | std::string argStr; | |
| 470 |
2/4✓ Branch 0 taken 627 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 627 times.
✗ Branch 3 not taken.
|
627 | if (item.args.isArray()) { |
| 471 |
7/12✓ Branch 0 taken 627 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 627 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1909 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1909 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2536 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1909 times.
✓ Branch 11 taken 627 times.
|
2536 | for (const auto& a : item.args) { |
| 472 |
1/2✓ Branch 0 taken 1909 times.
✗ Branch 1 not taken.
|
1909 | argStr += "\""; |
| 473 |
2/4✓ Branch 0 taken 1909 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1909 times.
✗ Branch 3 not taken.
|
1909 | argStr += ScheduleArgToString(a); |
| 474 |
1/2✓ Branch 0 taken 1909 times.
✗ Branch 1 not taken.
|
1909 | argStr += "\" "; |
| 475 | } | ||
| 476 | } else { | ||
| 477 | ✗ | argStr = ScheduleArgToString(item.args); | |
| 478 | } | ||
| 479 | |||
| 480 |
2/4✓ Branch 0 taken 627 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 627 times.
✗ Branch 3 not taken.
|
627 | LogDebug(VB_SCHEDULE, "%s: %2d '%s' - '%s'\n", |
| 481 | timeStr.substr(0, timeStr.length() - 1).c_str(), | ||
| 482 | item.priority, | ||
| 483 | item.command.c_str(), | ||
| 484 | argStr.c_str()); | ||
| 485 | 627 | } | |
| 486 | |||
| 487 | ✗ | void Scheduler::DumpScheduledItems() { | |
| 488 | ✗ | LogDebug(VB_SCHEDULE, "DumpScheduledItems()\n"); | |
| 489 | |||
| 490 | ✗ | for (const auto& itemTime : m_scheduledItems) { | |
| 491 | ✗ | for (const auto& item : itemTime.second) { | |
| 492 | ✗ | DumpScheduledItem(itemTime.first, item); | |
| 493 | } | ||
| 494 | } | ||
| 495 | ✗ | } | |
| 496 | |||
| 497 | 464 | void Scheduler::doCountdown(const std::time_t now, const std::time_t itemTime, const std::vector<ScheduledItem>& items, | |
| 498 | std::vector<CountdownPreset>& presets) { | ||
| 499 | // Check to see if we should be counting down to the next item | ||
| 500 | 464 | bool logItems = false; | |
| 501 | 464 | int diff = itemTime - now; | |
| 502 | |||
| 503 | // Print the countdown more frequently as we get closer | ||
| 504 |
6/6✓ Branch 0 taken 176 times.
✓ Branch 1 taken 288 times.
✓ Branch 2 taken 97 times.
✓ Branch 3 taken 79 times.
✓ Branch 4 taken 128 times.
✓ Branch 5 taken 257 times.
|
464 | if (((diff > 300) && ((diff % 300) == 0)) || |
| 505 |
6/6✓ Branch 0 taken 31 times.
✓ Branch 1 taken 97 times.
✓ Branch 2 taken 27 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 256 times.
✓ Branch 5 taken 125 times.
|
385 | ((diff > 60) && (diff <= 300) && ((diff % 60) == 0)) || |
| 506 |
6/6✓ Branch 0 taken 132 times.
✓ Branch 1 taken 124 times.
✓ Branch 2 taken 115 times.
✓ Branch 3 taken 17 times.
✓ Branch 4 taken 125 times.
✓ Branch 5 taken 239 times.
|
381 | ((diff > 10) && (diff <= 60) && ((diff % 10) == 0)) || |
| 507 | ((diff <= 10))) { | ||
| 508 | 225 | logItems = true; | |
| 509 | } | ||
| 510 | |||
| 511 |
2/2✓ Branch 0 taken 225 times.
✓ Branch 1 taken 239 times.
|
464 | if (logItems) { |
| 512 |
4/4✓ Branch 0 taken 14 times.
✓ Branch 1 taken 211 times.
✓ Branch 2 taken 224 times.
✓ Branch 3 taken 1 times.
|
225 | LogDebug(VB_SCHEDULE, "Scheduled Item%s running in %d second%s:\n", |
| 513 | items.size() == 1 ? "" : "s", | ||
| 514 | diff, | ||
| 515 | diff == 1 ? "" : "s"); | ||
| 516 | |||
| 517 |
2/2✓ Branch 0 taken 232 times.
✓ Branch 1 taken 225 times.
|
457 | for (auto& item : items) { |
| 518 |
4/4✓ Branch 0 taken 172 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 94 times.
✓ Branch 3 taken 78 times.
|
232 | if (item.isPlaylist && |
| 519 | (diff < 1000)) { | ||
| 520 | char tmpStr[27]; | ||
| 521 | 94 | snprintf(tmpStr, 26, "PLAYLIST_START_TMINUS_%03d", diff); | |
| 522 | 188 | presets.push_back({ .name = tmpStr, .playlist = item.entry->playlist }); | |
| 523 | } | ||
| 524 | |||
| 525 |
1/2✓ Branch 0 taken 232 times.
✗ Branch 1 not taken.
|
232 | DumpScheduledItem(itemTime, item); |
| 526 | } | ||
| 527 | } | ||
| 528 |
3/8✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 94 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 94 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
558 | } |
| 529 | |||
| 530 | 12766 | void Scheduler::RunCountdownPresets(const std::vector<CountdownPreset>& presets) { | |
| 531 |
2/2✓ Branch 0 taken 94 times.
✓ Branch 1 taken 12766 times.
|
12860 | for (const auto& preset : presets) { |
| 532 | 94 | std::map<std::string, std::string> keywords; | |
| 533 |
3/6✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 94 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 94 times.
✗ Branch 5 not taken.
|
188 | keywords["PLAYLIST_NAME"] = preset.playlist; |
| 534 |
2/4✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 94 times.
✗ Branch 3 not taken.
|
94 | CommandManager::INSTANCE.TriggerPreset(preset.name, keywords); |
| 535 | 94 | } | |
| 536 | 12766 | } | |
| 537 | |||
| 538 | 12768 | void Scheduler::RunPlayerActions(const std::vector<PlayerAction>& actions) { | |
| 539 |
2/2✓ Branch 0 taken 93 times.
✓ Branch 1 taken 12768 times.
|
12861 | for (const auto& action : actions) { |
| 540 |
4/6✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 89 times.
✗ Branch 5 not taken.
|
93 | switch (action.type) { |
| 541 | 1 | case PlayerAction::Type::StopUntilIdle: | |
| 542 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | Player::INSTANCE.StopNowAllLevels(action.forceStop); |
| 543 | 1 | break; | |
| 544 | 1 | case PlayerAction::Type::StopNow: | |
| 545 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | Player::INSTANCE.StopNow(action.forceStop); |
| 546 | 1 | break; | |
| 547 | 2 | case PlayerAction::Type::StopGracefully: | |
| 548 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | Player::INSTANCE.StopGracefully(action.forceStop); |
| 549 | 2 | break; | |
| 550 | ✗ | case PlayerAction::Type::StopGracefullyAfterLoop: | |
| 551 | ✗ | Player::INSTANCE.StopGracefully(action.forceStop, 1); | |
| 552 | ✗ | break; | |
| 553 | 89 | case PlayerAction::Type::StartPlaylist: | |
| 554 | 89 | Player::INSTANCE.StartScheduledPlaylist(action.playlist, action.position, | |
| 555 | 89 | action.repeat, action.entryIndex, | |
| 556 | 89 | action.priority, | |
| 557 |
1/2✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
|
89 | action.startTime, action.endTime, action.stopType); |
| 558 | 89 | break; | |
| 559 | } | ||
| 560 | } | ||
| 561 | 12768 | } | |
| 562 | |||
| 563 | static std::mutex s_commandThreadsLock; | ||
| 564 | static std::condition_variable s_commandThreadsDone; | ||
| 565 | static int s_commandThreads = 0; | ||
| 566 | |||
| 567 | 1867 | void Scheduler::WaitForCommandThreads(int timeoutMS) { | |
| 568 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | std::unique_lock<std::mutex> l(s_commandThreadsLock); |
| 569 |
3/4✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1866 times.
|
3736 | if (!s_commandThreadsDone.wait_for(l, std::chrono::milliseconds(timeoutMS), []() { return s_commandThreads == 0; })) { |
| 570 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogWarn(VB_SCHEDULE, "%d scheduled command(s) still running at shutdown\n", s_commandThreads); |
| 571 | } | ||
| 572 | 1867 | } | |
| 573 | |||
| 574 | 7 | void Scheduler::doScheduledCommand(const std::time_t itemTime, const ScheduledItem& item) { | |
| 575 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | LogDebug(VB_SCHEDULE, "Running scheduled item:\n"); |
| 576 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | DumpScheduledItem(itemTime, item); |
| 577 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | Json::Value cmd; |
| 578 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | cmd["command"] = item.command; |
| 579 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | cmd["args"] = item.args; |
| 580 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | cmd["multisyncCommand"] = item.entry->multisyncCommand; |
| 581 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | cmd["multisyncHosts"] = item.entry->multisyncHosts; |
| 582 | |||
| 583 | 7 | int entryIndex = item.entryIndex; | |
| 584 | { | ||
| 585 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | std::lock_guard<std::mutex> l(s_commandThreadsLock); |
| 586 | 7 | s_commandThreads++; | |
| 587 | 7 | } | |
| 588 | 7 | std::thread th([entryIndex](Json::Value cmd) { | |
| 589 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | SetThreadName("FPP-RunCmd"); |
| 590 | // "id" rather than a row number: entryIndex is the 0-based index into | ||
| 591 | // the schedule vector, and that is exactly what the API reports as an | ||
| 592 | // entry's "id" (see BuildScheduleRangeItem / the scheduledItem["id"] | ||
| 593 | // below), so this is the value to cross-reference against. | ||
| 594 |
3/6✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
|
7 | LogDebug(VB_COMMAND, "Scheduler entry id %d running command \"%s\"\n", entryIndex, cmd["command"].asString().c_str()); |
| 595 | // This thread is detached, so an exception escaping the command is not | ||
| 596 | // a failed command - it is std::terminate and the whole daemon goes | ||
| 597 | // down at whatever second the schedule fires. Commands parse | ||
| 598 | // user-supplied arguments (and plugins supply their own), so catch | ||
| 599 | // everything here and let the show keep running. | ||
| 600 | try { | ||
| 601 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | CommandManager::INSTANCE.run(cmd); |
| 602 | ✗ | } catch (const std::exception& e) { | |
| 603 | ✗ | LogErr(VB_COMMAND, "Scheduler entry id %d command \"%s\" threw: %s\n", | |
| 604 | entryIndex, cmd["command"].asString().c_str(), e.what()); | ||
| 605 | ✗ | WarningHolder::AddWarningTimeout(60, 47, "Schedule: command '" + cmd["command"].asString() + "' failed: " + e.what()); | |
| 606 | ✗ | } catch (...) { | |
| 607 | ✗ | LogErr(VB_COMMAND, "Scheduler entry id %d command \"%s\" threw an unknown exception\n", | |
| 608 | entryIndex, cmd["command"].asString().c_str()); | ||
| 609 | ✗ | WarningHolder::AddWarningTimeout(60, 47, "Schedule: command '" + cmd["command"].asString() + "' failed"); | |
| 610 | ✗ | } | |
| 611 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | std::lock_guard<std::mutex> l(s_commandThreadsLock); |
| 612 | 7 | s_commandThreads--; | |
| 613 | 7 | s_commandThreadsDone.notify_all(); | |
| 614 | 7 | }, | |
| 615 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | std::move(cmd)); |
| 616 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | th.detach(); |
| 617 | 7 | } | |
| 618 | |||
| 619 | 299 | bool Scheduler::doScheduledPlaylist(const std::time_t now, const std::time_t itemTime, ScheduledItem& item, bool restarted, | |
| 620 | std::vector<PlayerAction>& actions) { | ||
| 621 | 299 | LogDebug(VB_SCHEDULE, "Checking scheduled 'Start Playlist'\n"); | |
| 622 | 299 | DumpScheduledItem(itemTime, item); | |
| 623 | |||
| 624 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 299 times.
|
299 | if (item.endTime <= now) { |
| 625 | ✗ | SetItemRan(item, true); | |
| 626 | |||
| 627 | // don't try to start if scheduled end has passed | ||
| 628 | ✗ | return false; | |
| 629 | } | ||
| 630 | |||
| 631 | // Don't restart a playlist if it was just force stopped | ||
| 632 | 938 | if ((Player::INSTANCE.GetForceStopped()) && | |
| 633 |
7/10✓ Branch 0 taken 299 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 258 times.
✓ Branch 4 taken 41 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 40 times.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 40 times.
|
339 | (Player::INSTANCE.GetOrigStartTime() == item.startTime) && |
| 634 |
4/8✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 259 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 299 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
339 | (Player::INSTANCE.GetForceStoppedPlaylist() == item.entry->playlist)) { |
| 635 | ✗ | SetItemRan(item, true); | |
| 636 | ✗ | return false; | |
| 637 | } | ||
| 638 | |||
| 639 |
2/2✓ Branch 0 taken 147 times.
✓ Branch 1 taken 152 times.
|
299 | if (Player::INSTANCE.GetStatus() == FPP_STATUS_PLAYLIST_PLAYING) { |
| 640 | // If we are playing, check to see if we should be playing something else | ||
| 641 | |||
| 642 | // Check to see if we are already playing this item, for | ||
| 643 | // instance if we reloaded the schedule while a scheduled | ||
| 644 | // playlist was playing. | ||
| 645 |
2/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 146 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
147 | if ((Player::INSTANCE.GetPlaylistName() == item.entry->playlist) && |
| 646 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | (Player::INSTANCE.WasScheduled()) && |
| 647 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | (Player::INSTANCE.GetRepeat() == item.entry->repeat) && |
| 648 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | ((Player::INSTANCE.GetOrigStopTime() == item.endTime) || |
| 649 |
6/10✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 146 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 147 times.
✗ Branch 9 not taken.
|
149 | (Player::INSTANCE.GetStopTime() == item.endTime)) && |
| 650 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | (Player::INSTANCE.GetStopMethod() == item.entry->stopType)) { |
| 651 | 1 | SetItemRan(item, true); | |
| 652 | 1 | return false; | |
| 653 | } | ||
| 654 | |||
| 655 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 138 times.
|
146 | if (!Player::INSTANCE.WasScheduled()) { |
| 656 | // Manually started playlist is running | ||
| 657 | |||
| 658 | // Check if playlist is protected from schedule override | ||
| 659 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
|
8 | if (!Player::INSTANCE.GetAllowScheduleOverride()) { |
| 660 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | LogInfo(VB_SCHEDULE, "Manual playlist '%s' is protected from schedule override, not starting scheduled playlist '%s'\n", |
| 661 | Player::INSTANCE.GetPlaylistName().c_str(), item.entry->playlist.c_str()); | ||
| 662 | // Track this blocked scheduled item | ||
| 663 | 3 | m_lastBlockedPlaylist = item.entry->playlist; | |
| 664 | 3 | m_lastBlockedTime = now; | |
| 665 | 3 | return false; | |
| 666 | } | ||
| 667 | |||
| 668 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | if (item.startTime < Player::INSTANCE.GetStartTime()) { |
| 669 | // Due before the playlist was started by hand: waiting behind | ||
| 670 | // the scheduled one it replaced, or a repeating entry owed a | ||
| 671 | // restart. It plays once that playlist ends. Only an entry | ||
| 672 | // that comes due while it plays can pre-empt it. | ||
| 673 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | LogDebug(VB_SCHEDULE, "Scheduled playlist '%s' was due before manual playlist '%s' started, waiting for it to end\n", |
| 674 | item.entry->playlist.c_str(), Player::INSTANCE.GetPlaylistName().c_str()); | ||
| 675 | 4 | return false; | |
| 676 | } | ||
| 677 | |||
| 678 | // Check priority | ||
| 679 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (Player::INSTANCE.GetPriority() > item.priority) { |
| 680 | // Scheduled playlist has higher priority, stop the manual one | ||
| 681 |
2/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
1 | LogInfo(VB_SCHEDULE, "Stopping manual playlist '%s' (priority %d) for scheduled playlist '%s' (priority %d)\n", |
| 682 | Player::INSTANCE.GetPlaylistName().c_str(), Player::INSTANCE.GetPriority(), | ||
| 683 | item.entry->playlist.c_str(), item.priority); | ||
| 684 | 1 | actions.push_back({ .type = PlayerAction::Type::StopUntilIdle, | |
| 685 | .forceStop = 1 }); | ||
| 686 | } else { | ||
| 687 | // Manual playlist has higher or equal priority, let it continue | ||
| 688 | ✗ | LogDebug(VB_SCHEDULE, "Manual playlist '%s' (priority %d) has higher priority than scheduled playlist '%s' (priority %d), not overriding\n", | |
| 689 | Player::INSTANCE.GetPlaylistName().c_str(), Player::INSTANCE.GetPriority(), | ||
| 690 | item.entry->playlist.c_str(), item.priority); | ||
| 691 | ✗ | return false; | |
| 692 | } | ||
| 693 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 136 times.
|
138 | } else if (Player::INSTANCE.GetPriority() > item.priority) { |
| 694 | // Lower priority (higher number) playlist is running | ||
| 695 | // Reset the ran status on the schedule entry for the | ||
| 696 | // running playlist to false so it shows as 'next' again | ||
| 697 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::time_t oldStartTime = Player::INSTANCE.GetOrigStartTime(); |
| 698 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::string playlistName = Player::INSTANCE.GetPlaylistName(); |
| 699 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | if (auto it = m_scheduledItems.find(oldStartTime); it != m_scheduledItems.end()) { |
| 700 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | for (auto& oldItem : it->second) { |
| 701 |
3/6✓ 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.
|
4 | if (oldItem.isPlaylist && |
| 702 | 2 | (oldItem.entry->playlist == playlistName)) { | |
| 703 | 2 | oldItem.ran = false; | |
| 704 | 2 | m_forcedNextPlaylist = oldItem.entryIndex; | |
| 705 | 2 | m_forcedNextPlaylistStart = oldItem.startTime; | |
| 706 | } | ||
| 707 | } | ||
| 708 | } | ||
| 709 | |||
| 710 | 2 | int forceStop = 0; | |
| 711 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
|
2 | if (Player::INSTANCE.GetStopTime() < Player::INSTANCE.GetOrigStopTime()) { |
| 712 | ✗ | forceStop = 1; | |
| 713 | } | ||
| 714 | |||
| 715 | // Stop whatever is playing the way its entry says to, and start | ||
| 716 | // this item once the player is idle: a hard stop is idle by the | ||
| 717 | // rescan this pass does after its actions, a graceful one when the | ||
| 718 | // running playlist finishes. This item is deliberately not marked | ||
| 719 | // ran. It used to queue its start straight after the stop, and | ||
| 720 | // the start cut the stopping playlist off - every pre-emption was a | ||
| 721 | // hard stop whatever stop type the running entry had. | ||
| 722 |
2/5✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
2 | switch (Player::INSTANCE.GetStopMethod()) { |
| 723 | 2 | case 0: | |
| 724 | 2 | actions.push_back({ .type = PlayerAction::Type::StopGracefully, | |
| 725 | .forceStop = forceStop }); | ||
| 726 | 2 | break; | |
| 727 | ✗ | case 2: | |
| 728 | ✗ | actions.push_back({ .type = PlayerAction::Type::StopGracefullyAfterLoop, | |
| 729 | .forceStop = forceStop }); | ||
| 730 | ✗ | break; | |
| 731 | ✗ | case 1: | |
| 732 | default: | ||
| 733 | ✗ | actions.push_back({ .type = PlayerAction::Type::StopNow, | |
| 734 | .forceStop = forceStop }); | ||
| 735 | ✗ | break; | |
| 736 | } | ||
| 737 | 2 | return false; | |
| 738 | 2 | } else { | |
| 739 | // Need to wait for current Scheduled Higher-Priority | ||
| 740 | // item to end until we can play multiple playlists | ||
| 741 | // concurrently | ||
| 742 | 136 | return false; | |
| 743 | } | ||
| 744 |
2/2✓ Branch 0 taken 66 times.
✓ Branch 1 taken 86 times.
|
152 | } else if (Player::INSTANCE.GetStatus() != FPP_STATUS_IDLE) { |
| 745 | // We are either paused or stopping already so do nothing | ||
| 746 | 66 | return false; | |
| 747 | } | ||
| 748 | |||
| 749 | 87 | LogDebug(VB_SCHEDULE, "Starting Scheduled Playlist:\n"); | |
| 750 | 87 | DumpScheduledItem(itemTime, item); | |
| 751 | |||
| 752 | 87 | int position = 0; | |
| 753 |
8/12✓ Branch 0 taken 4 times.
✓ Branch 1 taken 83 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 83 times.
✓ Branch 8 taken 4 times.
✓ Branch 9 taken 83 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
87 | if (restarted && (getSetting("resumePlaylist") == item.entry->playlist)) { |
| 754 | 4 | position = getSettingInt("resumePosition"); | |
| 755 | |||
| 756 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
16 | SetSetting("resumePlaylist", ""); |
| 757 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
8 | SetSetting("resumePosition", 0); |
| 758 | } | ||
| 759 | |||
| 760 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 86 times.
|
87 | if ((m_forcedNextPlaylist == item.entryIndex) && |
| 761 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | (m_forcedNextPlaylistStart == item.startTime)) { |
| 762 | // The pre-empted occurrence is getting its restart; that was the | ||
| 763 | // whole of what the force was for. | ||
| 764 | 1 | m_forcedNextPlaylist = SCHEDULE_INDEX_INVALID; | |
| 765 | } | ||
| 766 | |||
| 767 |
1/2✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
|
87 | m_startsInFlight.insert({ item.startTime, item.entryIndex }); |
| 768 | 87 | actions.push_back({ .type = PlayerAction::Type::StartPlaylist, | |
| 769 | 87 | .playlist = item.entry->playlist, | |
| 770 | .position = position, | ||
| 771 | 87 | .repeat = item.entry->repeat, | |
| 772 | 87 | .entryIndex = item.entryIndex, | |
| 773 | 87 | .priority = item.entryIndex, // priority is entry index for now | |
| 774 | 87 | .startTime = item.startTime, | |
| 775 | 87 | .endTime = item.endTime, | |
| 776 | 87 | .stopType = item.entry->stopType }); | |
| 777 | 87 | return true; | |
| 778 |
4/22✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 87 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 87 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
90 | } |
| 779 | |||
| 780 | // How far ahead of a scheduled playlist the audio output is brought up. | ||
| 781 | static constexpr std::time_t MEDIA_PREWARM_LEAD_S = 5; | ||
| 782 | |||
| 783 | 12679 | void Scheduler::CheckScheduledItems(bool restarted) { | |
| 784 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12677 times.
|
12679 | if (m_schedulerDisabled) |
| 785 | 2 | return; | |
| 786 | |||
| 787 | 12677 | std::time_t now = time(nullptr); | |
| 788 | 12677 | std::vector<PlayerAction> actions; | |
| 789 | 12677 | std::vector<CountdownPreset> presets; | |
| 790 | // How long to hold the audio output up for a playlist about to start (0 = | ||
| 791 | // none due). Opening it takes most of a second on slow boards, so do it | ||
| 792 | // ahead of time rather than when the first track starts. | ||
| 793 | 12677 | int prewarmMs = 0; | |
| 794 | |||
| 795 | // Longest a scheduled FPP command may fire after its second passed. Bounds | ||
| 796 | // the (m_lastCommandCheckTime, now] catch-up window so a forward clock step | ||
| 797 | // does not replay a batch of stale commands; anything older is logged and | ||
| 798 | // marked ran instead of run. | ||
| 799 | 12677 | constexpr std::time_t maxCommandCatchup = SCHEDULE_COMMAND_CATCHUP_S; | |
| 800 | |||
| 801 | // Each pass decides what to do while holding m_scheduleLock and runs it | ||
| 802 | // after releasing the lock, since starting or stopping a playlist takes | ||
| 803 | // m_playlistMutex in the opposite order (see Scheduler.h). A pass stops | ||
| 804 | // scanning as soon as it has an action so that the item after it is still | ||
| 805 | // judged against the player's real state rather than the state the player | ||
| 806 | // had before the action ran, which is what deciding for every item up | ||
| 807 | // front would do. | ||
| 808 | do { | ||
| 809 | 12766 | actions.clear(); | |
| 810 | 12766 | presets.clear(); | |
| 811 | |||
| 812 | { | ||
| 813 |
1/2✓ Branch 0 taken 12766 times.
✗ Branch 1 not taken.
|
12766 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 814 | |||
| 815 | // With the player idle and several playlists due, start the | ||
| 816 | // highest-priority one (lowest entry index) rather than whichever | ||
| 817 | // started earliest. Starting the earlier, lower-priority one | ||
| 818 | // played the wrong show until the next pass pre-empted it; and | ||
| 819 | // after a graceful pre-emption the stopped entry is due again (it | ||
| 820 | // is owed a restart) and would win again, pre-empting forever. | ||
| 821 | 12766 | int bestIdlePriority = INT_MAX; | |
| 822 |
3/4✓ Branch 0 taken 12766 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9576 times.
✓ Branch 3 taken 3190 times.
|
12766 | if (Player::INSTANCE.GetStatus() == FPP_STATUS_IDLE) { |
| 823 |
2/2✓ Branch 0 taken 426 times.
✓ Branch 1 taken 9341 times.
|
9767 | for (auto& due : m_scheduledItems) { |
| 824 |
2/2✓ Branch 0 taken 235 times.
✓ Branch 1 taken 191 times.
|
426 | if (due.first > now) |
| 825 | 235 | break; | |
| 826 |
2/2✓ Branch 0 taken 198 times.
✓ Branch 1 taken 191 times.
|
389 | for (auto& c : due.second) { |
| 827 |
3/4✓ Branch 0 taken 101 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 101 times.
✗ Branch 3 not taken.
|
110 | if (!c.ran && c.isPlaylist && (c.endTime > now) && |
| 828 |
4/4✓ Branch 0 taken 110 times.
✓ Branch 1 taken 88 times.
✓ Branch 2 taken 95 times.
✓ Branch 3 taken 6 times.
|
403 | (c.priority < bestIdlePriority) && |
| 829 |
4/6✓ Branch 0 taken 95 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 95 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 95 times.
✓ Branch 5 taken 103 times.
|
293 | !m_startsInFlight.count({ c.startTime, c.entryIndex })) { |
| 830 | 95 | bestIdlePriority = c.priority; | |
| 831 | } | ||
| 832 | } | ||
| 833 | } | ||
| 834 | } | ||
| 835 | |||
| 836 |
2/2✓ Branch 0 taken 1391 times.
✓ Branch 1 taken 12213 times.
|
13604 | for (auto& itemTime : m_scheduledItems) { |
| 837 |
2/2✓ Branch 0 taken 464 times.
✓ Branch 1 taken 927 times.
|
1391 | if (itemTime.first > now) { |
| 838 |
1/2✓ Branch 0 taken 464 times.
✗ Branch 1 not taken.
|
464 | doCountdown(now, itemTime.first, itemTime.second, presets); |
| 839 |
2/2✓ Branch 0 taken 72 times.
✓ Branch 1 taken 392 times.
|
464 | if (itemTime.first - now <= MEDIA_PREWARM_LEAD_S) { |
| 840 |
2/2✓ Branch 0 taken 72 times.
✓ Branch 1 taken 31 times.
|
103 | for (auto& item : itemTime.second) { |
| 841 |
3/4✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 31 times.
|
72 | if (!item.ran && item.isPlaylist) { |
| 842 | // Through the start, plus the usual linger in | ||
| 843 | // case the playlist opens with no media. | ||
| 844 | 41 | prewarmMs = (int)((itemTime.first - now + MEDIA_PREWARM_LEAD_S) * 1000); | |
| 845 | 41 | break; | |
| 846 | } | ||
| 847 | } | ||
| 848 | } | ||
| 849 | 464 | break; // no need to look at items that are further in the future | |
| 850 | } | ||
| 851 | |||
| 852 |
2/2✓ Branch 0 taken 948 times.
✓ Branch 1 taken 840 times.
|
1788 | for (auto& item : itemTime.second) { |
| 853 |
2/2✓ Branch 0 taken 630 times.
✓ Branch 1 taken 318 times.
|
948 | if (item.ran) |
| 854 | 630 | continue; // skip over any items that ran already | |
| 855 | |||
| 856 |
2/2✓ Branch 0 taken 308 times.
✓ Branch 1 taken 10 times.
|
318 | if (item.isPlaylist) { |
| 857 | // While any scheduled start is still being carried | ||
| 858 | // out the player is busy, whatever its status says: | ||
| 859 | // it reads idle until that playlist has loaded. | ||
| 860 | // Guarding only the in-flight item let a concurrent | ||
| 861 | // pass start the next, lower-priority item into the | ||
| 862 | // same player and lose the higher one. Skipped items | ||
| 863 | // keep their ran flag clear, and the pass doing the | ||
| 864 | // start rescans once it has finished. | ||
| 865 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 308 times.
|
308 | if (!m_startsInFlight.empty()) |
| 866 | ✗ | continue; | |
| 867 | // Nor while one playlist is being replaced by another | ||
| 868 | // (see Player::SwitchScope). | ||
| 869 |
3/4✓ Branch 0 taken 308 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 307 times.
|
308 | if (Player::Switching()) |
| 870 | 1 | continue; | |
| 871 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 299 times.
|
307 | if (item.priority > bestIdlePriority) |
| 872 | 8 | continue; // a higher-priority playlist is due; it goes first | |
| 873 |
3/4✓ Branch 0 taken 299 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 212 times.
✓ Branch 3 taken 87 times.
|
299 | if (!doScheduledPlaylist(now, itemTime.first, item, restarted, actions)) { |
| 874 | 212 | continue; | |
| 875 | } | ||
| 876 | } else { | ||
| 877 | // An FPP Command has no duration to catch up to, so a | ||
| 878 | // missed second would silently drop it. Fire any | ||
| 879 | // command whose second falls in the (lastCheck, now] | ||
| 880 | // window this run has not examined yet, but never one | ||
| 881 | // more than maxCommandCatchup seconds late so a large | ||
| 882 | // clock step cannot replay a batch of stale commands. | ||
| 883 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 7 times.
|
10 | if (itemTime.first <= m_lastCommandCheckTime) { |
| 884 | // A prior run already had its chance at this second. | ||
| 885 | // A reload can reset item.ran (FPP-command ran-state | ||
| 886 | // is not restored from m_ranItems), so gate on the | ||
| 887 | // check window, not on item.ran, to avoid re-firing. | ||
| 888 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | SetItemRan(item, true); |
| 889 | 3 | continue; | |
| 890 | } | ||
| 891 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if ((now - itemTime.first) > maxCommandCatchup) { |
| 892 | ✗ | LogWarn(VB_SCHEDULE, | |
| 893 | "Scheduled command '%s' skipped, %ld seconds late (catch-up cap is %ld s)\n", | ||
| 894 | item.command.c_str(), (long)(now - itemTime.first), | ||
| 895 | (long)maxCommandCatchup); | ||
| 896 | ✗ | SetItemRan(item, true); | |
| 897 | ✗ | continue; | |
| 898 | } | ||
| 899 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | doScheduledCommand(itemTime.first, item); |
| 900 | } | ||
| 901 | |||
| 902 |
1/2✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
|
94 | SetItemRan(item, true); |
| 903 | |||
| 904 |
2/2✓ Branch 0 taken 87 times.
✓ Branch 1 taken 7 times.
|
94 | if (!actions.empty()) |
| 905 | 87 | break; | |
| 906 | } | ||
| 907 | |||
| 908 |
2/2✓ Branch 0 taken 89 times.
✓ Branch 1 taken 838 times.
|
927 | if (!actions.empty()) |
| 909 | 89 | break; | |
| 910 | } | ||
| 911 | |||
| 912 | // A switch that began after the checks above read the player | ||
| 913 | // idle: a start now would replace the playlist being switched to. | ||
| 914 | // Drop this pass's actions, and leave its start to a later one. | ||
| 915 |
5/8✓ Branch 0 taken 89 times.
✓ Branch 1 taken 12677 times.
✓ Branch 2 taken 89 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 89 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12766 times.
|
12766 | if (!actions.empty() && Player::Switching()) { |
| 916 | ✗ | LogDebug(VB_SCHEDULE, "A playlist switch began, dropping this pass's scheduled actions\n"); | |
| 917 | ✗ | for (const auto& action : actions) { | |
| 918 | ✗ | if (action.type != PlayerAction::Type::StartPlaylist) | |
| 919 | ✗ | continue; | |
| 920 | ✗ | m_startsInFlight.erase({ action.startTime, action.entryIndex }); | |
| 921 | ✗ | if (auto it = m_scheduledItems.find(action.startTime); it != m_scheduledItems.end()) { | |
| 922 | ✗ | for (auto& item : it->second) { | |
| 923 | ✗ | if (item.isPlaylist && (item.entryIndex == action.entryIndex)) | |
| 924 | ✗ | SetItemRan(item, false); | |
| 925 | } | ||
| 926 | } | ||
| 927 | } | ||
| 928 | ✗ | actions.clear(); | |
| 929 | } | ||
| 930 | 12766 | } | |
| 931 | |||
| 932 | { | ||
| 933 | // Clear this pass's in-flight marks however RunPlayerActions() | ||
| 934 | // leaves: every scheduled playlist start waits while any mark is | ||
| 935 | // set, so one leaked by an exception would stop the schedule. | ||
| 936 | struct InFlightGuard { | ||
| 937 | Scheduler* s; | ||
| 938 | const std::vector<PlayerAction>& actions; | ||
| 939 | 12766 | ~InFlightGuard() { | |
| 940 | 12766 | std::unique_lock<std::recursive_mutex> lock(s->m_scheduleLock); | |
| 941 |
2/2✓ Branch 0 taken 90 times.
✓ Branch 1 taken 12766 times.
|
12856 | for (const auto& action : actions) { |
| 942 |
2/2✓ Branch 0 taken 87 times.
✓ Branch 1 taken 3 times.
|
90 | if (action.type == PlayerAction::Type::StartPlaylist) |
| 943 | 87 | s->m_startsInFlight.erase({ action.startTime, action.entryIndex }); | |
| 944 | } | ||
| 945 | 12766 | } | |
| 946 | 12766 | } inFlightGuard{ this, actions }; | |
| 947 |
1/2✓ Branch 0 taken 12766 times.
✗ Branch 1 not taken.
|
12766 | RunPlayerActions(actions); |
| 948 | 12766 | } | |
| 949 |
1/2✓ Branch 0 taken 12766 times.
✗ Branch 1 not taken.
|
12766 | RunCountdownPresets(presets); |
| 950 |
2/2✓ Branch 0 taken 89 times.
✓ Branch 1 taken 12677 times.
|
12766 | } while (!actions.empty()); |
| 951 | |||
| 952 |
2/2✓ Branch 0 taken 41 times.
✓ Branch 1 taken 12636 times.
|
12677 | if (prewarmMs > 0) { |
| 953 |
1/2✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
|
41 | PrewarmMediaOutput(prewarmMs); |
| 954 | } | ||
| 955 | |||
| 956 | // Advance the command catch-up window past every second this call examined. | ||
| 957 | // Done once, after all rescans, so each rescan judges commands against the | ||
| 958 | // same window; within a call the item.ran flag prevents a second firing. | ||
| 959 | // Left unchanged if the clock ran backwards so the window never moves ahead | ||
| 960 | // of the wall clock. | ||
| 961 | // Under the lock: this runs on the main loop and on API threads, and an | ||
| 962 | // unlocked read-compare-write could move the window backwards. | ||
| 963 |
1/2✓ Branch 0 taken 12677 times.
✗ Branch 1 not taken.
|
12677 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 964 |
2/2✓ Branch 0 taken 10271 times.
✓ Branch 1 taken 2406 times.
|
12677 | if (now > m_lastCommandCheckTime) |
| 965 | 10271 | m_lastCommandCheckTime = now; | |
| 966 | 12677 | } | |
| 967 | |||
| 968 | 1964 | void Scheduler::ClearScheduledItems() { | |
| 969 | 1964 | m_scheduledItems.clear(); | |
| 970 | 1964 | } | |
| 971 | |||
| 972 | 98 | void Scheduler::SetItemRan(ScheduledItem& item, bool ran) { | |
| 973 | 98 | item.ran = ran; | |
| 974 | |||
| 975 |
1/2✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
|
98 | auto& vec = m_ranItems[item.startTime]; |
| 976 |
1/2✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
|
98 | auto it = std::find_if(vec.begin(), vec.end(), [&](const ScheduledItem& ranItem) { |
| 977 | 44 | return item.command == ranItem.command && | |
| 978 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | item.isPlaylist == ranItem.isPlaylist && |
| 979 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | item.startTime == ranItem.startTime && |
| 980 |
2/2✓ Branch 0 taken 43 times.
✓ Branch 1 taken 1 times.
|
44 | item.endTime == ranItem.endTime && |
| 981 |
2/4✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 43 times.
✗ Branch 3 not taken.
|
131 | item.args.size() == ranItem.args.size() && |
| 982 |
1/2✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
|
87 | SameFirstScheduleArg(item.args, ranItem.args); |
| 983 | }); | ||
| 984 |
2/2✓ Branch 0 taken 43 times.
✓ Branch 1 taken 55 times.
|
98 | if (it != vec.end()) { |
| 985 | 43 | it->ran = ran; | |
| 986 | } else { | ||
| 987 | // Without the entry pointer: it points into m_Schedule, which the next | ||
| 988 | // reload frees, while ran-state outlives reloads. | ||
| 989 | 55 | vec.push_back({ .entry = nullptr, | |
| 990 | 55 | .priority = item.priority, | |
| 991 | 55 | .entryIndex = item.entryIndex, | |
| 992 | .ran = ran, | ||
| 993 | 55 | .startTime = item.startTime, | |
| 994 | 55 | .endTime = item.endTime, | |
| 995 | 55 | .command = item.command, | |
| 996 | 55 | .args = item.args, | |
| 997 | 55 | .isPlaylist = item.isPlaylist }); | |
| 998 | } | ||
| 999 |
3/10✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 55 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
153 | } |
| 1000 | |||
| 1001 | 1964 | void Scheduler::LoadScheduleFromFile(void) { | |
| 1002 |
3/6✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1964 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1964 times.
✗ Branch 5 not taken.
|
1964 | std::string SCHEDULE_FILE = FPP_DIR_CONFIG("/schedule.json"); |
| 1003 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | LogDebug(VB_SCHEDULE, "Loading Schedule from %s\n", SCHEDULE_FILE.c_str()); |
| 1004 | |||
| 1005 | 1964 | m_loadSchedule = false; | |
| 1006 | // One clock reading for the whole load: every entry and every occurrence | ||
| 1007 | // is placed relative to it, so a load that runs across midnight cannot | ||
| 1008 | // anchor some of them to the old day (or week) and some to the new one. | ||
| 1009 | // The load date comes from the same reading, so a load anchored to the | ||
| 1010 | // old day is redone once the new day is seen. | ||
| 1011 | 1964 | const std::time_t loadTime = std::time(nullptr); | |
| 1012 | { | ||
| 1013 | struct tm lt; | ||
| 1014 | 1964 | localtime_r(&loadTime, <); | |
| 1015 | 1964 | m_lastLoadDate = (lt.tm_year + 1900) * 10000 + (lt.tm_mon + 1) * 100 + lt.tm_mday; | |
| 1016 | } | ||
| 1017 | // Under the lock, like the AddScheduledItems() writes below and the read | ||
| 1018 | // in GetSchedule(), which an API thread makes while this reloads. | ||
| 1019 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1020 | 1964 | m_schedulesExtendBeyondDistance = false; | |
| 1021 | 1964 | m_Schedule.clear(); | |
| 1022 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | ClearScheduledItems(); |
| 1023 | |||
| 1024 | 1964 | std::string playlistFile; | |
| 1025 | |||
| 1026 | // A player that has never had a schedule has no schedule.json, which is a | ||
| 1027 | // normal state and not worth reading a file for -- an absent file and an | ||
| 1028 | // empty schedule are the same thing to everything below. | ||
| 1029 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | Json::Value sch; |
| 1030 |
3/4✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 171 times.
✓ Branch 3 taken 1793 times.
|
1964 | if (FileExists(SCHEDULE_FILE)) { |
| 1031 |
1/2✓ Branch 0 taken 171 times.
✗ Branch 1 not taken.
|
171 | sch = LoadJsonFromFile(SCHEDULE_FILE, JsonRoot::Array); |
| 1032 | } | ||
| 1033 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | ClearScheduleLoadWarnings(); |
| 1034 |
3/4✓ Branch 0 taken 2310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 346 times.
✓ Branch 3 taken 1964 times.
|
2310 | for (int i = 0; i < sch.size(); i++) { |
| 1035 |
1/2✓ Branch 0 taken 346 times.
✗ Branch 1 not taken.
|
346 | ScheduleEntry scheduleEntry; |
| 1036 | // jsoncpp throws on a wrong-typed field ("day":"7", an entry that is | ||
| 1037 | // not an object). This runs from the Scheduler constructor and from | ||
| 1038 | // the main loop's reloads with nothing above it to catch, so one bad | ||
| 1039 | // entry took fppd down -- and again on every restart. Skip the entry. | ||
| 1040 | try { | ||
| 1041 |
4/6✓ Branch 0 taken 346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 344 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 344 times.
|
346 | if (!scheduleEntry.LoadFromJson(sch[i])) |
| 1042 | ✗ | continue; | |
| 1043 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } catch (const std::exception& ex) { |
| 1044 |
3/6✓ 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.
|
2 | std::string warning = "Schedule entry " + std::to_string(i + 1) + " is invalid and was skipped: " + ex.what(); |
| 1045 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogErr(VB_SCHEDULE, "%s\n", warning.c_str()); |
| 1046 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | ScheduleLoadWarning(47, warning); |
| 1047 | 2 | continue; | |
| 1048 | 2 | } | |
| 1049 | |||
| 1050 |
1/2✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
|
344 | std::string warning = missingEntryWarning(scheduleEntry); |
| 1051 | |||
| 1052 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 344 times.
|
344 | if (scheduleEntry.sequence) { |
| 1053 | ✗ | playlistFile = FPP_DIR_SEQUENCE("/" + scheduleEntry.playlist); | |
| 1054 | } else { | ||
| 1055 |
5/10✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 344 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 344 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 344 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 344 times.
✗ Branch 9 not taken.
|
688 | playlistFile = FPP_DIR_PLAYLIST("/" + scheduleEntry.playlist + ".json"); |
| 1056 | } | ||
| 1057 | |||
| 1058 |
1/2✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
|
344 | if ((getFPPmode() != PLAYER_MODE) && |
| 1059 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 344 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 344 times.
|
344 | (scheduleEntry.enabled) && |
| 1060 | ✗ | (!scheduleEntry.playlist.empty())) { | |
| 1061 | ✗ | std::string warning = std::string("Playlist '") + scheduleEntry.playlist + | |
| 1062 | ✗ | "' was scheduled but system is running in Remote mode."; | |
| 1063 | ✗ | LogWarn(VB_SCHEDULE, "%s\n", warning.c_str()); | |
| 1064 | ✗ | ScheduleLoadWarning(29, warning); | |
| 1065 | ✗ | continue; | |
| 1066 | ✗ | } | |
| 1067 | |||
| 1068 | 1032 | if ((scheduleEntry.enabled) && | |
| 1069 |
4/6✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 327 times.
✓ Branch 3 taken 17 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 344 times.
|
671 | (!scheduleEntry.playlist.empty()) && |
| 1070 |
2/4✓ Branch 0 taken 327 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 327 times.
|
327 | (!FileExists(playlistFile))) { |
| 1071 | ✗ | LogErr(VB_SCHEDULE, "ERROR: Scheduled %s '%s' does not exist\n", | |
| 1072 | scheduleEntry.sequence ? "Sequence" : "Playlist", | ||
| 1073 | scheduleEntry.playlist.c_str()); | ||
| 1074 | ✗ | ScheduleLoadWarning(23, warning); | |
| 1075 | ✗ | continue; | |
| 1076 | } else { | ||
| 1077 |
2/4✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 344 times.
✗ Branch 3 not taken.
|
344 | WarningHolder::RemoveWarning(23, warning); |
| 1078 | } | ||
| 1079 | |||
| 1080 |
1/2✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
|
344 | m_Schedule.push_back(scheduleEntry); |
| 1081 |
3/4✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 344 times.
✓ Branch 3 taken 2 times.
|
346 | } |
| 1082 | |||
| 1083 |
2/2✓ Branch 0 taken 344 times.
✓ Branch 1 taken 1964 times.
|
2308 | for (int i = 0; i < m_Schedule.size(); i++) { |
| 1084 |
1/2✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
|
344 | AddScheduledItems(&m_Schedule[i], i, loadTime); |
| 1085 | } | ||
| 1086 | |||
| 1087 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | SchedulePrint(); |
| 1088 | |||
| 1089 |
2/4✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1964 times.
|
1964 | if (WillLog(LOG_EXCESSIVE, VB_SCHEDULE)) |
| 1090 | ✗ | DumpScheduledItems(); | |
| 1091 | |||
| 1092 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | lock.unlock(); |
| 1093 | |||
| 1094 | 3928 | return; | |
| 1095 | 1964 | } | |
| 1096 | |||
| 1097 | 1964 | void Scheduler::SchedulePrint(void) { | |
| 1098 | 1964 | int i = 0; | |
| 1099 | 1964 | char stopTypes[4] = "GHL"; | |
| 1100 | 1964 | std::string dayStr; | |
| 1101 | |||
| 1102 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1958 times.
|
1964 | if (m_schedulerDisabled) |
| 1103 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | LogInfo(VB_SCHEDULE, "WARNING: Scheduler is currently disabled\n"); |
| 1104 | |||
| 1105 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | LogInfo(VB_SCHEDULE, "Current Schedule: (Status: '+' = Enabled, '-' = Disabled, '!' = Outside Date Range, '*' = Repeat, Stop (G)raceful/(L)oop/(H)ard\n"); |
| 1106 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | LogInfo(VB_SCHEDULE, "Stat Start & End Dates Days Start & End Times Playlist/Command\n"); |
| 1107 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | LogInfo(VB_SCHEDULE, "---- ----------------------- ------------- ------------------- ---------------------------------------------\n"); |
| 1108 |
2/2✓ Branch 0 taken 344 times.
✓ Branch 1 taken 1964 times.
|
2308 | for (i = 0; i < m_Schedule.size(); i++) { |
| 1109 |
1/2✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
|
344 | dayStr = GetDayTextFromDayIndex(m_Schedule[i].dayIndex); |
| 1110 |
14/16✓ Branch 0 taken 17 times.
✓ Branch 1 taken 327 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 327 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 327 times.
✓ Branch 6 taken 211 times.
✓ Branch 7 taken 133 times.
✓ Branch 8 taken 344 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 172 times.
✓ Branch 11 taken 172 times.
✓ Branch 12 taken 343 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 344 times.
✗ Branch 15 not taken.
|
344 | LogInfo(VB_SCHEDULE, "%c%c%c%c %04d-%02d-%02d - %04d-%02d-%02d %-13.13s %-8.8s %c %-8.8s %s\n", |
| 1111 | m_Schedule[i].enabled ? '+' : '-', | ||
| 1112 | CurrentDateInRange(m_Schedule[i].startDate, m_Schedule[i].endDate) ? ' ' : '!', | ||
| 1113 | m_Schedule[i].repeat ? '*' : ' ', | ||
| 1114 | stopTypes[m_Schedule[i].stopType], | ||
| 1115 | (int)(m_Schedule[i].startDate / 10000), | ||
| 1116 | (int)(m_Schedule[i].startDate % 10000 / 100), | ||
| 1117 | (int)(m_Schedule[i].startDate % 100), | ||
| 1118 | (int)(m_Schedule[i].endDate / 10000), | ||
| 1119 | (int)(m_Schedule[i].endDate % 10000 / 100), | ||
| 1120 | (int)(m_Schedule[i].endDate % 100), | ||
| 1121 | dayStr.c_str(), | ||
| 1122 | m_Schedule[i].startTimeStr.c_str(), | ||
| 1123 | m_Schedule[i].playlist.empty() ? ' ' : '-', | ||
| 1124 | m_Schedule[i].playlist.empty() ? "" : m_Schedule[i].endTimeStr.c_str(), | ||
| 1125 | m_Schedule[i].playlist.empty() ? m_Schedule[i].command.c_str() : m_Schedule[i].playlist.c_str()); | ||
| 1126 | } | ||
| 1127 | |||
| 1128 |
1/2✓ Branch 0 taken 1964 times.
✗ Branch 1 not taken.
|
1964 | LogDebug(VB_SCHEDULE, "//////////////////////////////////////////////////\n"); |
| 1129 | 1964 | } | |
| 1130 | |||
| 1131 | 831910 | ScheduledItem* Scheduler::GetNextScheduledPlaylist() { | |
| 1132 | 831910 | std::time_t now = time(nullptr); | |
| 1133 |
1/2✓ Branch 0 taken 831910 times.
✗ Branch 1 not taken.
|
831910 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1134 | |||
| 1135 |
2/2✓ Branch 0 taken 43901 times.
✓ Branch 1 taken 830348 times.
|
874249 | for (auto& itemTime : m_scheduledItems) { |
| 1136 |
2/2✓ Branch 0 taken 44186 times.
✓ Branch 1 taken 42339 times.
|
86525 | for (auto& item : itemTime.second) { |
| 1137 |
2/2✓ Branch 0 taken 31904 times.
✓ Branch 1 taken 12282 times.
|
44186 | if (item.ran) |
| 1138 | 31904 | continue; // skip over any items that ran already | |
| 1139 | |||
| 1140 |
2/2✓ Branch 0 taken 1562 times.
✓ Branch 1 taken 10720 times.
|
12282 | if (item.isPlaylist) |
| 1141 | 1562 | return &item; | |
| 1142 | } | ||
| 1143 | } | ||
| 1144 | |||
| 1145 | 830348 | return nullptr; | |
| 1146 | 831910 | } | |
| 1147 | |||
| 1148 | 356759 | std::string Scheduler::GetNextPlaylistName() { | |
| 1149 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 356743 times.
|
356759 | if (m_schedulerDisabled) |
| 1150 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
32 | return "Scheduler is disabled."; |
| 1151 | |||
| 1152 |
1/2✓ Branch 0 taken 356743 times.
✗ Branch 1 not taken.
|
356743 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1153 |
1/2✓ Branch 0 taken 356743 times.
✗ Branch 1 not taken.
|
356743 | ScheduledItem* item = GetNextScheduledPlaylist(); |
| 1154 | |||
| 1155 |
2/2✓ Branch 0 taken 356119 times.
✓ Branch 1 taken 624 times.
|
356743 | if (!item) |
| 1156 |
1/2✓ Branch 0 taken 356119 times.
✗ Branch 1 not taken.
|
712238 | return "No playlist scheduled."; |
| 1157 | |||
| 1158 |
1/2✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
|
624 | return item->entry->playlist; |
| 1159 | 356743 | } | |
| 1160 | |||
| 1161 | 356759 | std::string Scheduler::GetNextPlaylistStartStr() { | |
| 1162 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 356743 times.
|
356759 | if (m_schedulerDisabled) |
| 1163 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
32 | return ""; |
| 1164 | |||
| 1165 |
4/8✓ Branch 0 taken 356743 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 356743 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 356743 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 356743 times.
✗ Branch 7 not taken.
|
356743 | std::string timeFmt = getSetting("DateFormat") + " @ " + getSetting("TimeFormat"); |
| 1166 | 356743 | std::string result; | |
| 1167 |
1/2✓ Branch 0 taken 356743 times.
✗ Branch 1 not taken.
|
356743 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1168 |
1/2✓ Branch 0 taken 356743 times.
✗ Branch 1 not taken.
|
356743 | ScheduledItem* item = GetNextScheduledPlaylist(); |
| 1169 | |||
| 1170 |
2/2✓ Branch 0 taken 356119 times.
✓ Branch 1 taken 624 times.
|
356743 | if (!item) |
| 1171 | 356119 | return result; | |
| 1172 | |||
| 1173 | char timeStr[32]; | ||
| 1174 | 624 | std::time_t timeT = 0; | |
| 1175 | struct tm timeStruct; | ||
| 1176 | 624 | time_t currTime = time(NULL); | |
| 1177 | |||
| 1178 | 624 | localtime_r(&item->startTime, &timeStruct); | |
| 1179 | 624 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1180 | |||
| 1181 | // Thursday @ 11:00:00 - (Everyday) | ||
| 1182 |
1/2✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
|
624 | result = timeStr; |
| 1183 |
1/2✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
|
624 | result += " - ("; |
| 1184 |
2/4✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 624 times.
✗ Branch 3 not taken.
|
624 | result += GetDayTextFromDayIndex(item->entry->dayIndex); |
| 1185 |
1/2✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
|
624 | result += ")"; |
| 1186 | |||
| 1187 | 624 | return result; | |
| 1188 | 356743 | } | |
| 1189 | |||
| 1190 | 36399 | std::string Scheduler::GetDayTextFromDayIndex(const int index) { | |
| 1191 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 36390 times.
|
36399 | if (index & INX_DAY_MASK) { |
| 1192 | char tmpStr[15]; | ||
| 1193 | 9 | strcpy(tmpStr, "Mask: "); | |
| 1194 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | strcat(tmpStr, (index & INX_DAY_MASK_SUNDAY) ? "S" : "-"); |
| 1195 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | strcat(tmpStr, (index & INX_DAY_MASK_MONDAY) ? "M" : "-"); |
| 1196 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | strcat(tmpStr, (index & INX_DAY_MASK_TUESDAY) ? "T" : "-"); |
| 1197 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | strcat(tmpStr, (index & INX_DAY_MASK_WEDNESDAY) ? "W" : "-"); |
| 1198 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | strcat(tmpStr, (index & INX_DAY_MASK_THURSDAY) ? "T" : "-"); |
| 1199 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | strcat(tmpStr, (index & INX_DAY_MASK_FRIDAY) ? "F" : "-"); |
| 1200 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | strcat(tmpStr, (index & INX_DAY_MASK_SATURDAY) ? "S" : "-"); |
| 1201 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return tmpStr; |
| 1202 | } | ||
| 1203 | |||
| 1204 |
13/17✓ Branch 0 taken 4 times.
✓ Branch 1 taken 69 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 36225 times.
✓ Branch 8 taken 9 times.
✓ Branch 9 taken 9 times.
✓ Branch 10 taken 9 times.
✓ Branch 11 taken 9 times.
✓ Branch 12 taken 9 times.
✓ Branch 13 taken 9 times.
✓ Branch 14 taken 13 times.
✓ Branch 15 taken 9 times.
✗ Branch 16 not taken.
|
36390 | switch (index) { |
| 1205 | 4 | case INX_SUN: | |
| 1206 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
8 | return "Sunday"; |
| 1207 | 69 | case INX_MON: | |
| 1208 |
1/2✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
|
138 | return "Monday"; |
| 1209 | ✗ | case INX_TUE: | |
| 1210 | ✗ | return "Tuesday"; | |
| 1211 | 9 | case INX_WED: | |
| 1212 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return "Wednesday"; |
| 1213 | ✗ | case INX_THU: | |
| 1214 | ✗ | return "Thursday"; | |
| 1215 | ✗ | case INX_FRI: | |
| 1216 | ✗ | return "Friday"; | |
| 1217 | 7 | case INX_SAT: | |
| 1218 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
14 | return "Saturday"; |
| 1219 | 36225 | case INX_EVERYDAY: | |
| 1220 |
1/2✓ Branch 0 taken 36225 times.
✗ Branch 1 not taken.
|
72450 | return "Everyday"; |
| 1221 | 9 | case INX_WKDAYS: | |
| 1222 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return "Weekdays"; |
| 1223 | 9 | case INX_WKEND: | |
| 1224 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return "Weekends"; |
| 1225 | 9 | case INX_M_W_F: | |
| 1226 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return "Mon/Wed/Fri"; |
| 1227 | 9 | case INX_T_TH: | |
| 1228 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return "Tues-Thurs"; |
| 1229 | 9 | case INX_SUN_TO_THURS: | |
| 1230 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return "Sun-Thurs"; |
| 1231 | 9 | case INX_FRI_SAT: | |
| 1232 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return "Fri/Sat"; |
| 1233 | 13 | case INX_ODD_DAY: | |
| 1234 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
26 | return "Odd Days"; |
| 1235 | 9 | case INX_EVEN_DAY: | |
| 1236 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | return "Even Days"; |
| 1237 | } | ||
| 1238 | |||
| 1239 | ✗ | return "Error"; | |
| 1240 | } | ||
| 1241 | |||
| 1242 | 118422 | Json::Value Scheduler::GetInfo(void) { | |
| 1243 |
1/2✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
|
118422 | Json::Value result; |
| 1244 | |||
| 1245 |
4/8✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 118422 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 118422 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 118422 times.
✗ Branch 7 not taken.
|
118422 | std::string timeFmt = getSetting("DateFormat") + " @ " + getSetting("TimeFormat"); |
| 1246 |
1/2✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
|
118422 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1247 |
1/2✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
|
118422 | Json::Value np; |
| 1248 |
1/2✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
|
118422 | ScheduledItem* nextItem = GetNextScheduledPlaylist(); |
| 1249 |
3/6✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 118422 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 118422 times.
✗ Branch 5 not taken.
|
118422 | np["playlistName"] = GetNextPlaylistName(); |
| 1250 |
4/6✓ Branch 0 taken 312 times.
✓ Branch 1 taken 118110 times.
✓ Branch 2 taken 118422 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 118422 times.
✗ Branch 5 not taken.
|
118422 | np["scheduledStartTime"] = nextItem ? (Json::UInt64)(nextItem->startTime) : 0; |
| 1251 |
3/6✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 118422 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 118422 times.
✗ Branch 5 not taken.
|
118422 | np["scheduledStartTimeStr"] = GetNextPlaylistStartStr(); |
| 1252 | |||
| 1253 |
4/6✓ Branch 0 taken 8 times.
✓ Branch 1 taken 118414 times.
✓ Branch 2 taken 118422 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 118422 times.
✗ Branch 5 not taken.
|
118422 | result["enabled"] = m_schedulerDisabled ? 0 : 1; |
| 1254 |
2/4✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 118422 times.
✗ Branch 3 not taken.
|
118422 | result["nextPlaylist"] = np; |
| 1255 | |||
| 1256 |
1/2✓ Branch 0 taken 118422 times.
✗ Branch 1 not taken.
|
118422 | if ((Player::INSTANCE.GetStatus() == FPP_STATUS_PLAYLIST_PLAYING) || |
| 1257 |
7/8✓ Branch 0 taken 43081 times.
✓ Branch 1 taken 75341 times.
✓ Branch 2 taken 43081 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42943 times.
✓ Branch 5 taken 138 times.
✓ Branch 6 taken 75520 times.
✓ Branch 7 taken 42902 times.
|
161365 | (Player::INSTANCE.GetStatus() == FPP_STATUS_STOPPING_GRACEFULLY) || |
| 1258 |
3/4✓ Branch 0 taken 42943 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 42902 times.
|
42943 | (Player::INSTANCE.GetStatus() == FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP)) { |
| 1259 |
1/2✓ Branch 0 taken 75520 times.
✗ Branch 1 not taken.
|
75520 | Json::Value cp; |
| 1260 | |||
| 1261 |
3/4✓ Branch 0 taken 75520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✓ Branch 3 taken 73069 times.
|
75520 | if (Player::INSTANCE.WasScheduled()) { |
| 1262 | char timeStr[32]; | ||
| 1263 | 2451 | std::time_t timeT = 0; | |
| 1264 | struct tm timeStruct; | ||
| 1265 | 2451 | time_t currTime = time(NULL); | |
| 1266 | |||
| 1267 | 2451 | localtime_r(&currTime, &timeStruct); | |
| 1268 | 2451 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1269 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["currentTime"] = (Json::UInt64)currTime; |
| 1270 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["currentTimeStr"] = timeStr; |
| 1271 | |||
| 1272 |
1/2✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
|
2451 | timeT = Player::INSTANCE.GetOrigStartTime(); |
| 1273 | 2451 | localtime_r(&timeT, &timeStruct); | |
| 1274 | 2451 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1275 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["scheduledStartTime"] = (Json::UInt64)timeT; |
| 1276 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["scheduledStartTimeStr"] = timeStr; |
| 1277 | |||
| 1278 |
1/2✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
|
2451 | timeT = Player::INSTANCE.GetStartTime(); |
| 1279 | 2451 | localtime_r(&timeT, &timeStruct); | |
| 1280 | 2451 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1281 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["actualStartTime"] = (Json::UInt64)timeT; |
| 1282 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["actualStartTimeStr"] = timeStr; |
| 1283 | |||
| 1284 |
1/2✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
|
2451 | timeT = Player::INSTANCE.GetOrigStopTime(); |
| 1285 | 2451 | localtime_r(&timeT, &timeStruct); | |
| 1286 | 2451 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1287 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["scheduledEndTime"] = (Json::UInt64)timeT; |
| 1288 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["scheduledEndTimeStr"] = timeStr; |
| 1289 | |||
| 1290 |
1/2✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
|
2451 | timeT = Player::INSTANCE.GetStopTime(); |
| 1291 | 2451 | localtime_r(&timeT, &timeStruct); | |
| 1292 | 2451 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1293 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["actualEndTime"] = (Json::UInt64)timeT; |
| 1294 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["actualEndTimeStr"] = timeStr; |
| 1295 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["secondsRemaining"] = (int)(timeT - currTime); |
| 1296 | |||
| 1297 |
1/2✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
|
2451 | int stopType = Player::INSTANCE.GetStopMethod(); |
| 1298 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | cp["stopType"] = stopType; |
| 1299 |
3/4✓ Branch 0 taken 138 times.
✓ Branch 1 taken 2312 times.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
4901 | cp["stopTypeStr"] = stopType == 2 ? "Graceful Loop" : stopType == 1 ? "Hard" |
| 1300 |
3/4✓ Branch 0 taken 2450 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
7352 | : "Graceful"; |
| 1301 | |||
| 1302 |
2/4✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2451 times.
✗ Branch 3 not taken.
|
2451 | result["status"] = "playing"; |
| 1303 | } else { | ||
| 1304 |
2/4✓ Branch 0 taken 73069 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 73069 times.
✗ Branch 3 not taken.
|
73069 | result["status"] = "manual"; |
| 1305 | } | ||
| 1306 | |||
| 1307 |
3/6✓ Branch 0 taken 75520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75520 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 75520 times.
✗ Branch 5 not taken.
|
75520 | cp["playlistName"] = Player::INSTANCE.GetPlaylistName(); |
| 1308 | |||
| 1309 |
2/4✓ Branch 0 taken 75520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75520 times.
✗ Branch 3 not taken.
|
75520 | result["currentPlaylist"] = cp; |
| 1310 | 75520 | } else { | |
| 1311 |
2/4✓ Branch 0 taken 42902 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42902 times.
✗ Branch 3 not taken.
|
42902 | result["status"] = "idle"; |
| 1312 | } | ||
| 1313 | |||
| 1314 | // Add blocked schedule information | ||
| 1315 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 118412 times.
|
118422 | if (m_lastBlockedTime > 0) { |
| 1316 | 10 | time_t now = time(NULL); | |
| 1317 | // Only report if blocked within last 5 minutes | ||
| 1318 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if ((now - m_lastBlockedTime) < 300) { |
| 1319 |
3/6✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
|
10 | result["blockedSchedule"]["playlistName"] = m_lastBlockedPlaylist; |
| 1320 |
3/6✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
|
10 | result["blockedSchedule"]["blockedTime"] = (Json::UInt64)m_lastBlockedTime; |
| 1321 | } | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | 118422 | return result; | |
| 1325 | 118422 | } | |
| 1326 | |||
| 1327 | 36327 | Json::Value Scheduler::GetSchedule() { | |
| 1328 |
4/8✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36327 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 36327 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 36327 times.
✗ Branch 7 not taken.
|
36327 | std::string timeFmt = getSetting("DateFormat") + " @ " + getSetting("TimeFormat"); |
| 1329 |
1/2✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
|
36327 | Json::Value result; |
| 1330 |
1/2✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
|
36327 | Json::Value entries(Json::arrayValue); |
| 1331 |
1/2✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
|
36327 | Json::Value entry; |
| 1332 |
1/2✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
|
36327 | Json::Value items(Json::arrayValue); |
| 1333 |
1/2✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
|
36327 | Json::Value scheduledItem; |
| 1334 | 36327 | std::time_t now = time(nullptr); | |
| 1335 |
1/2✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
|
36327 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1336 | |||
| 1337 |
3/6✗ Branch 0 not taken.
✓ Branch 1 taken 36327 times.
✓ Branch 2 taken 36327 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 36327 times.
✗ Branch 5 not taken.
|
36327 | result["enabled"] = m_schedulerDisabled ? 0 : 1; |
| 1338 | |||
| 1339 |
2/2✓ Branch 0 taken 35315 times.
✓ Branch 1 taken 36327 times.
|
71642 | for (int i = 0; i < m_Schedule.size(); i++) { |
| 1340 |
1/2✓ Branch 0 taken 35315 times.
✗ Branch 1 not taken.
|
35315 | entry = m_Schedule[i].GetJson(); |
| 1341 |
2/4✓ Branch 0 taken 35315 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35315 times.
✗ Branch 3 not taken.
|
35315 | entry["id"] = i; |
| 1342 |
4/6✓ Branch 0 taken 4 times.
✓ Branch 1 taken 35311 times.
✓ Branch 2 taken 35315 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35315 times.
✗ Branch 5 not taken.
|
35315 | entry["type"] = m_Schedule[i].playlist.empty() ? "command" : "playlist"; |
| 1343 |
3/6✓ Branch 0 taken 35315 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35315 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35315 times.
✗ Branch 5 not taken.
|
35315 | entry["dayStr"] = GetDayTextFromDayIndex(m_Schedule[i].dayIndex); |
| 1344 | |||
| 1345 |
1/2✓ Branch 0 taken 35315 times.
✗ Branch 1 not taken.
|
35315 | entries.append(entry); |
| 1346 | } | ||
| 1347 |
2/4✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36327 times.
✗ Branch 3 not taken.
|
36327 | result["entries"] = entries; |
| 1348 | |||
| 1349 | struct tm timeStruct; | ||
| 1350 | char timeStr[32]; | ||
| 1351 | time_t tmpTime; | ||
| 1352 | |||
| 1353 |
5/6✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4552 times.
✓ Branch 3 taken 31775 times.
✓ Branch 4 taken 2128 times.
✓ Branch 5 taken 34199 times.
|
40879 | if ((Player::INSTANCE.GetStatus() != FPP_STATUS_IDLE) && |
| 1354 |
3/4✓ Branch 0 taken 4552 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✓ Branch 3 taken 2424 times.
|
4552 | (Player::INSTANCE.WasScheduled())) { |
| 1355 | // If we are playing a scheduled playlist, then insert an item for | ||
| 1356 | // it since it is already marked as ran and will get skipped by. | ||
| 1357 | // We do this here instead of just using a more complicated if continue | ||
| 1358 | // statement below because the player may have more up to date info | ||
| 1359 | // on the end time than our old schedule entry if the end time has | ||
| 1360 | // been extended or shortened. | ||
| 1361 |
3/6✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2128 times.
✗ Branch 5 not taken.
|
2128 | scheduledItem["priority"] = Player::INSTANCE.GetPriority(); |
| 1362 |
3/6✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2128 times.
✗ Branch 5 not taken.
|
2128 | scheduledItem["id"] = Player::INSTANCE.GetScheduleEntry(); |
| 1363 | |||
| 1364 |
1/2✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
|
2128 | tmpTime = Player::INSTANCE.GetOrigStartTime(); |
| 1365 | 2128 | localtime_r(&tmpTime, &timeStruct); | |
| 1366 | 2128 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1367 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["startTimeStr"] = timeStr; |
| 1368 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["startTime"] = (Json::UInt64)tmpTime; |
| 1369 | |||
| 1370 | 2128 | FormatTime(timeStr, sizeof(timeStr), "%Y%m%d", &timeStruct); | |
| 1371 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["startDateStr"] = timeStr; |
| 1372 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["startDateInt"] = atoi(timeStr); |
| 1373 | |||
| 1374 |
1/2✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
|
2128 | tmpTime = Player::INSTANCE.GetStopTime(); |
| 1375 | 2128 | localtime_r(&tmpTime, &timeStruct); | |
| 1376 | 2128 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1377 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["endTimeStr"] = timeStr; |
| 1378 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["endTime"] = (Json::UInt64)tmpTime; |
| 1379 | |||
| 1380 | 2128 | FormatTime(timeStr, sizeof(timeStr), "%Y%m%d", &timeStruct); | |
| 1381 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["endDateStr"] = timeStr; |
| 1382 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["endDateInt"] = atoi(timeStr); |
| 1383 | |||
| 1384 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["command"] = "Start Playlist"; |
| 1385 | |||
| 1386 |
1/2✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
|
2128 | Json::Value args(Json::arrayValue); |
| 1387 |
3/6✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2128 times.
✗ Branch 5 not taken.
|
2128 | args.append(Player::INSTANCE.GetPlaylistName()); |
| 1388 |
2/4✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
|
2128 | scheduledItem["args"] = args; |
| 1389 | |||
| 1390 |
1/2✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
|
2128 | items.append(scheduledItem); |
| 1391 | 2128 | } | |
| 1392 | |||
| 1393 |
2/2✓ Branch 0 taken 37881 times.
✓ Branch 1 taken 36327 times.
|
74208 | for (auto& itemTime : m_scheduledItems) { |
| 1394 |
2/2✓ Branch 0 taken 37949 times.
✓ Branch 1 taken 37881 times.
|
75830 | for (auto& item : itemTime.second) { |
| 1395 |
2/2✓ Branch 0 taken 5675 times.
✓ Branch 1 taken 32274 times.
|
37949 | if (item.ran) |
| 1396 | 5675 | continue; | |
| 1397 | |||
| 1398 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["priority"] = item.priority; |
| 1399 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["id"] = item.entryIndex; |
| 1400 | |||
| 1401 | 32274 | localtime_r(&item.startTime, &timeStruct); | |
| 1402 | 32274 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1403 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["startTimeStr"] = timeStr; |
| 1404 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["startTime"] = (Json::UInt64)item.startTime; |
| 1405 | |||
| 1406 | 32274 | FormatTime(timeStr, sizeof(timeStr), "%Y%m%d", &timeStruct); | |
| 1407 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["startDateStr"] = timeStr; |
| 1408 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["startDateInt"] = atoi(timeStr); |
| 1409 | |||
| 1410 | 32274 | localtime_r(&item.endTime, &timeStruct); | |
| 1411 | 32274 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1412 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["endTimeStr"] = timeStr; |
| 1413 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["endTime"] = (Json::UInt64)item.endTime; |
| 1414 | |||
| 1415 | 32274 | FormatTime(timeStr, sizeof(timeStr), "%Y%m%d", &timeStruct); | |
| 1416 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["endDateStr"] = timeStr; |
| 1417 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["endDateInt"] = atoi(timeStr); |
| 1418 | |||
| 1419 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["command"] = item.command; |
| 1420 | |||
| 1421 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["args"] = item.args; |
| 1422 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["multisyncCommand"] = item.entry->multisyncCommand; |
| 1423 |
2/4✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32274 times.
✗ Branch 3 not taken.
|
32274 | scheduledItem["multisyncHosts"] = item.entry->multisyncHosts; |
| 1424 | |||
| 1425 |
1/2✓ Branch 0 taken 32274 times.
✗ Branch 1 not taken.
|
32274 | items.append(scheduledItem); |
| 1426 | } | ||
| 1427 | } | ||
| 1428 |
2/4✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36327 times.
✗ Branch 3 not taken.
|
36327 | result["items"] = items; |
| 1429 |
2/4✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36327 times.
✗ Branch 3 not taken.
|
36327 | result["schedulesExtendBeyondDistance"] = m_schedulesExtendBeyondDistance; |
| 1430 |
3/6✓ Branch 0 taken 36327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36327 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 36327 times.
✗ Branch 5 not taken.
|
36327 | result["scheduleDistance"] = getSettingInt("ScheduleDistance"); |
| 1431 | |||
| 1432 | 36327 | return result; | |
| 1433 | 36327 | } | |
| 1434 | |||
| 1435 | /* | ||
| 1436 | * Expand the configured schedule across an arbitrary date range. | ||
| 1437 | * | ||
| 1438 | * GetSchedule() can only report the rolling window fppd actually schedules out | ||
| 1439 | * (ScheduleDistance days, minus anything already run), which is enough for the | ||
| 1440 | * list preview but not for a calendar the user can page through. This walks | ||
| 1441 | * the range a day at a time and asks each entry which occurrences land on it, | ||
| 1442 | * so any month - past or future - can be rendered from the real schedule rules. | ||
| 1443 | * | ||
| 1444 | * Occurrences are advisory: they describe what the schedule says should happen, | ||
| 1445 | * not what fppd has committed to running. | ||
| 1446 | */ | ||
| 1447 | 190 | Json::Value Scheduler::BuildScheduleRangeItem(const ScheduleEntry& entry, | |
| 1448 | const Occurrence& occurrence, | ||
| 1449 | const std::string& timeFmt) { | ||
| 1450 | struct tm timeStruct; | ||
| 1451 | char timeStr[32]; | ||
| 1452 |
1/2✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
|
190 | Json::Value item; |
| 1453 | |||
| 1454 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["id"] = occurrence.entryIndex; |
| 1455 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["priority"] = occurrence.entryIndex; // priority is entry index for now |
| 1456 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["enabled"] = entry.enabled; |
| 1457 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["overridden"] = occurrence.overridden; |
| 1458 |
5/8✓ Branch 0 taken 38 times.
✓ Branch 1 taken 152 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 152 times.
✓ Branch 4 taken 190 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 190 times.
✗ Branch 7 not taken.
|
190 | item["type"] = entry.playlist.empty() ? "command" : (entry.sequence ? "sequence" : "playlist"); |
| 1459 | |||
| 1460 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["startTime"] = (Json::UInt64)occurrence.start; |
| 1461 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["endTime"] = (Json::UInt64)occurrence.end; |
| 1462 | |||
| 1463 | 190 | localtime_r(&occurrence.start, &timeStruct); | |
| 1464 | 190 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1465 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["startTimeStr"] = timeStr; |
| 1466 | 190 | FormatTime(timeStr, sizeof(timeStr), "%Y%m%d", &timeStruct); | |
| 1467 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["startDateStr"] = timeStr; |
| 1468 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["startDateInt"] = atoi(timeStr); |
| 1469 | |||
| 1470 | 190 | localtime_r(&occurrence.end, &timeStruct); | |
| 1471 | 190 | FormatTime(timeStr, sizeof(timeStr), timeFmt.c_str(), &timeStruct); | |
| 1472 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["endTimeStr"] = timeStr; |
| 1473 | 190 | FormatTime(timeStr, sizeof(timeStr), "%Y%m%d", &timeStruct); | |
| 1474 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["endDateStr"] = timeStr; |
| 1475 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["endDateInt"] = atoi(timeStr); |
| 1476 | |||
| 1477 |
2/2✓ Branch 0 taken 38 times.
✓ Branch 1 taken 152 times.
|
190 | if (entry.playlist.empty()) { |
| 1478 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
|
38 | item["command"] = entry.command; |
| 1479 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
|
38 | item["args"] = entry.args; |
| 1480 | } else { | ||
| 1481 |
1/2✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
|
152 | Json::Value args(Json::arrayValue); |
| 1482 |
2/4✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 152 times.
✗ Branch 3 not taken.
|
152 | args.append(entry.playlist); |
| 1483 |
4/6✓ Branch 0 taken 78 times.
✓ Branch 1 taken 74 times.
✓ Branch 2 taken 152 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 152 times.
✗ Branch 5 not taken.
|
152 | args.append(entry.repeat ? "true" : "false"); |
| 1484 |
2/4✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 152 times.
✗ Branch 3 not taken.
|
152 | args.append("false"); |
| 1485 | |||
| 1486 |
2/4✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 152 times.
✗ Branch 3 not taken.
|
152 | item["command"] = "Start Playlist"; |
| 1487 |
2/4✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 152 times.
✗ Branch 3 not taken.
|
152 | item["args"] = args; |
| 1488 |
2/4✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 152 times.
✗ Branch 3 not taken.
|
152 | item["playlist"] = entry.playlist; |
| 1489 | 152 | } | |
| 1490 | |||
| 1491 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["multisyncCommand"] = entry.multisyncCommand; |
| 1492 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["multisyncHosts"] = entry.multisyncHosts; |
| 1493 |
3/8✗ Branch 0 not taken.
✓ Branch 1 taken 190 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 190 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 190 times.
✗ Branch 7 not taken.
|
190 | item["stopTypeStr"] = entry.stopType == 0 ? "Graceful" : (entry.stopType == 1 ? "Hard" : "Graceful Loop"); |
| 1494 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["repeat"] = entry.repeat; |
| 1495 |
2/4✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
|
190 | item["repeatInterval"] = entry.repeatInterval; |
| 1496 | |||
| 1497 | 190 | return item; | |
| 1498 | ✗ | } | |
| 1499 | |||
| 1500 | 18 | Json::Value Scheduler::GetScheduleRange(time_t rangeStart, time_t rangeEnd, | |
| 1501 | bool includeDisabled, bool daySummary) { | ||
| 1502 |
4/8✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 18 times.
✗ Branch 7 not taken.
|
18 | std::string timeFmt = getSetting("DateFormat") + " @ " + getSetting("TimeFormat"); |
| 1503 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | Json::Value result; |
| 1504 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | Json::Value entries(Json::arrayValue); |
| 1505 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | Json::Value items(Json::arrayValue); |
| 1506 | 18 | std::vector<Occurrence> expanded; | |
| 1507 | 18 | std::vector<Occurrence> playlistWindows; | |
| 1508 | |||
| 1509 | // The walk below steps a day at a time from rangeStart; a start whose | ||
| 1510 | // year doesn't fit in a struct tm makes localtime_r fail and leaves the | ||
| 1511 | // cursor at -1, i.e. trillions of steps. Keep both ends in 1970..9999 | ||
| 1512 | // here rather than trusting every caller to. | ||
| 1513 | static constexpr time_t MAX_RANGE_TIME = 253402300799; // 9999-12-31 23:59:59 UTC | ||
| 1514 |
4/6✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 16 times.
|
18 | if (rangeStart < 0 || rangeEnd < rangeStart || rangeEnd > MAX_RANGE_TIME) { |
| 1515 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | result["entries"] = entries; |
| 1516 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | result["items"] = items; |
| 1517 | 2 | return result; | |
| 1518 | } | ||
| 1519 | |||
| 1520 | // Work from a copy of the schedule so the expansion below never holds the | ||
| 1521 | // scheduler lock. ScheduleProc() takes the same lock to start and stop | ||
| 1522 | // playlists, and expanding a large range takes long enough that holding it | ||
| 1523 | // throughout would delay a show. Copying a handful of entries is cheap. | ||
| 1524 | 16 | std::vector<ScheduleEntry> schedule; | |
| 1525 | { | ||
| 1526 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1527 | |||
| 1528 |
3/6✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
|
16 | result["enabled"] = m_schedulerDisabled ? 0 : 1; |
| 1529 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | schedule = m_Schedule; |
| 1530 | 16 | } | |
| 1531 | |||
| 1532 |
2/2✓ Branch 0 taken 116 times.
✓ Branch 1 taken 16 times.
|
132 | for (int i = 0; i < schedule.size(); i++) { |
| 1533 |
1/2✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
|
116 | Json::Value entry = schedule[i].GetJson(); |
| 1534 |
2/4✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 116 times.
✗ Branch 3 not taken.
|
116 | entry["id"] = i; |
| 1535 |
4/6✓ Branch 0 taken 4 times.
✓ Branch 1 taken 112 times.
✓ Branch 2 taken 116 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 116 times.
✗ Branch 5 not taken.
|
116 | entry["type"] = schedule[i].playlist.empty() ? "command" : "playlist"; |
| 1536 |
3/6✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 116 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 116 times.
✗ Branch 5 not taken.
|
116 | entry["dayStr"] = GetDayTextFromDayIndex(schedule[i].dayIndex); |
| 1537 |
1/2✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
|
116 | entries.append(entry); |
| 1538 | 116 | } | |
| 1539 |
2/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
16 | result["entries"] = entries; |
| 1540 | |||
| 1541 | struct tm timeStruct; | ||
| 1542 | char timeStr[32]; | ||
| 1543 | |||
| 1544 | // An entry starting late in the day can run past midnight, so start a day | ||
| 1545 | // early to catch occurrences that overlap into the requested range. Walk | ||
| 1546 | // the range from midday to midday: a 23 or 25 hour DST day would otherwise | ||
| 1547 | // let a fixed 86400s step skip or repeat a calendar date. | ||
| 1548 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (!localtime_r(&rangeStart, &timeStruct)) { |
| 1549 | ✗ | result["items"] = items; | |
| 1550 | ✗ | return result; | |
| 1551 | } | ||
| 1552 | 16 | timeStruct.tm_hour = 12; | |
| 1553 | 16 | timeStruct.tm_min = 0; | |
| 1554 | 16 | timeStruct.tm_sec = 0; | |
| 1555 | 16 | timeStruct.tm_isdst = -1; | |
| 1556 | 16 | timeStruct.tm_mday -= 1; | |
| 1557 | 16 | time_t cursor = mktime(&timeStruct); | |
| 1558 | |||
| 1559 | // This runs on an API thread for whatever range the calendar asks for; | ||
| 1560 | // what the expansion finds wrong (a year past a lunar table) belongs in | ||
| 1561 | // this response, not in the scheduler's load warnings. | ||
| 1562 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | SchedulePreviewScope previewScope; |
| 1563 |
2/2✓ Branch 0 taken 2362 times.
✓ Branch 1 taken 16 times.
|
2378 | for (; cursor <= (rangeEnd + SECONDS_PER_DAY); cursor += SECONDS_PER_DAY) { |
| 1564 | 2362 | localtime_r(&cursor, &timeStruct); | |
| 1565 | 2362 | int year = timeStruct.tm_year + 1900; | |
| 1566 | 2362 | int month = timeStruct.tm_mon + 1; | |
| 1567 | 2362 | int mday = timeStruct.tm_mday; | |
| 1568 | |||
| 1569 |
2/2✓ Branch 0 taken 23868 times.
✓ Branch 1 taken 2362 times.
|
26230 | for (int i = 0; i < schedule.size(); i++) { |
| 1570 | 23868 | ScheduleEntry& entry = schedule[i]; | |
| 1571 | |||
| 1572 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 23868 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
23868 | if (!entry.enabled && !includeDisabled) |
| 1573 | ✗ | continue; | |
| 1574 | |||
| 1575 | 23868 | std::vector<std::pair<time_t, time_t>> occurrences; | |
| 1576 |
1/2✓ Branch 0 taken 23868 times.
✗ Branch 1 not taken.
|
23868 | entry.GetOccurrencesOnDate(year, month, mday, occurrences); |
| 1577 | |||
| 1578 | 23868 | int dayKey = (year * 10000) + (month * 100) + mday; | |
| 1579 | |||
| 1580 |
2/2✓ Branch 0 taken 294 times.
✓ Branch 1 taken 23868 times.
|
24162 | for (auto& occurrence : occurrences) { |
| 1581 | // A command fires at a point in time - only playlists occupy the | ||
| 1582 | // window up to their end time. AddScheduledItems() makes the | ||
| 1583 | // same distinction, so match it or the calendar would draw a | ||
| 1584 | // repeating command as a block instead of a marker. | ||
| 1585 |
2/2✓ Branch 0 taken 67 times.
✓ Branch 1 taken 227 times.
|
294 | if (entry.playlist.empty()) |
| 1586 | 67 | occurrence.second = occurrence.first; | |
| 1587 | |||
| 1588 | // A playlist's end is exclusive, as everywhere else: one that | ||
| 1589 | // ends at the start of the range (an entry ending at midnight) | ||
| 1590 | // belongs to the previous day, not this range. | ||
| 1591 | 294 | bool span = occurrence.second > occurrence.first; | |
| 1592 | 294 | Occurrence o{ .entryIndex = i, | |
| 1593 | .dayKey = dayKey, | ||
| 1594 | 294 | .start = occurrence.first, | |
| 1595 | 294 | .end = occurrence.second, | |
| 1596 | 294 | .overridden = false }; | |
| 1597 | |||
| 1598 | // Only playlists can override, and a schedule of repeating | ||
| 1599 | // commands can run to tens of thousands of occurrences, so | ||
| 1600 | // collect the playlist windows once rather than rescanning | ||
| 1601 | // everything for every occurrence. Collect them before the | ||
| 1602 | // range test: one from before the range (the day-early walk | ||
| 1603 | // finds it) can still override an occurrence inside it. | ||
| 1604 |
2/2✓ Branch 0 taken 227 times.
✓ Branch 1 taken 67 times.
|
294 | if (!entry.playlist.empty()) |
| 1605 |
1/2✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
|
227 | playlistWindows.push_back(o); |
| 1606 | |||
| 1607 |
6/6✓ Branch 0 taken 227 times.
✓ Branch 1 taken 67 times.
✓ Branch 2 taken 192 times.
✓ Branch 3 taken 35 times.
✓ Branch 4 taken 54 times.
✓ Branch 5 taken 13 times.
|
294 | if ((span ? (occurrence.second <= rangeStart) : (occurrence.second < rangeStart)) || |
| 1608 |
2/2✓ Branch 0 taken 53 times.
✓ Branch 1 taken 193 times.
|
246 | (occurrence.first > rangeEnd)) |
| 1609 | 101 | continue; | |
| 1610 | |||
| 1611 |
1/2✓ Branch 0 taken 193 times.
✗ Branch 1 not taken.
|
193 | expanded.push_back(o); |
| 1612 | } | ||
| 1613 | 23868 | } | |
| 1614 | } | ||
| 1615 | |||
| 1616 | // Flag playlist occurrences the scheduler would skip because a | ||
| 1617 | // higher-priority (lower index) playlist is already running at that point. | ||
| 1618 | |||
| 1619 |
2/2✓ Branch 0 taken 193 times.
✓ Branch 1 taken 16 times.
|
209 | for (auto& occurrence : expanded) { |
| 1620 |
2/2✓ Branch 0 taken 41 times.
✓ Branch 1 taken 152 times.
|
193 | if (schedule[occurrence.entryIndex].playlist.empty()) |
| 1621 | 41 | continue; | |
| 1622 | |||
| 1623 |
2/2✓ Branch 0 taken 3830 times.
✓ Branch 1 taken 143 times.
|
3973 | for (const auto& window : playlistWindows) { |
| 1624 |
2/2✓ Branch 0 taken 2174 times.
✓ Branch 1 taken 1656 times.
|
3830 | if (window.entryIndex >= occurrence.entryIndex) |
| 1625 | 2174 | continue; | |
| 1626 | |||
| 1627 |
4/4✓ Branch 0 taken 862 times.
✓ Branch 1 taken 794 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 853 times.
|
1656 | if ((window.start <= occurrence.start) && (window.end > occurrence.start)) { |
| 1628 | 9 | occurrence.overridden = true; | |
| 1629 | 9 | break; | |
| 1630 | } | ||
| 1631 | } | ||
| 1632 | } | ||
| 1633 | |||
| 1634 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 15 times.
|
16 | if (daySummary) { |
| 1635 | // Coarse views cannot show hundreds of occurrences per cell and do not | ||
| 1636 | // need to: what matters there is which entries are active on a day. | ||
| 1637 | // Collapse to one item per entry per day and carry the occurrence count | ||
| 1638 | // so the caller can label it; the detail views ask for the full list. | ||
| 1639 | 1 | std::map<std::pair<int, int>, Occurrence> byDay; | |
| 1640 | 1 | std::map<std::pair<int, int>, int> counts; | |
| 1641 | |||
| 1642 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
|
13 | for (const auto& occurrence : expanded) { |
| 1643 | 12 | auto key = std::make_pair(occurrence.dayKey, occurrence.entryIndex); | |
| 1644 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | counts[key]++; |
| 1645 | |||
| 1646 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | auto it = byDay.find(key); |
| 1647 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
|
12 | if (it == byDay.end()) { |
| 1648 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | byDay[key] = occurrence; |
| 1649 | } else { | ||
| 1650 | // Span the whole day's activity for this entry | ||
| 1651 | 3 | it->second.start = std::min(it->second.start, occurrence.start); | |
| 1652 | 3 | it->second.end = std::max(it->second.end, occurrence.end); | |
| 1653 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3 | it->second.overridden = it->second.overridden && occurrence.overridden; |
| 1654 | } | ||
| 1655 | } | ||
| 1656 | |||
| 1657 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
|
10 | for (const auto& [key, occurrence] : byDay) { |
| 1658 | 9 | Json::Value item = BuildScheduleRangeItem(schedule[occurrence.entryIndex], | |
| 1659 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | occurrence, timeFmt); |
| 1660 |
3/6✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
|
9 | item["count"] = counts[key]; |
| 1661 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
9 | item["summary"] = true; |
| 1662 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | items.append(item); |
| 1663 | 9 | } | |
| 1664 | 1 | } else { | |
| 1665 |
2/2✓ Branch 0 taken 181 times.
✓ Branch 1 taken 15 times.
|
196 | for (const auto& occurrence : expanded) { |
| 1666 | 181 | Json::Value item = BuildScheduleRangeItem(schedule[occurrence.entryIndex], | |
| 1667 |
1/2✓ Branch 0 taken 181 times.
✗ Branch 1 not taken.
|
181 | occurrence, timeFmt); |
| 1668 |
2/4✓ Branch 0 taken 181 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 181 times.
✗ Branch 3 not taken.
|
181 | item["count"] = 1; |
| 1669 |
2/4✓ Branch 0 taken 181 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 181 times.
✗ Branch 3 not taken.
|
181 | item["summary"] = false; |
| 1670 |
1/2✓ Branch 0 taken 181 times.
✗ Branch 1 not taken.
|
181 | items.append(item); |
| 1671 | 181 | } | |
| 1672 | } | ||
| 1673 | |||
| 1674 |
2/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
16 | result["items"] = items; |
| 1675 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (!previewScope.warnings().empty()) { |
| 1676 | ✗ | Json::Value warnings(Json::arrayValue); | |
| 1677 | ✗ | for (const auto& w : previewScope.warnings()) | |
| 1678 | ✗ | warnings.append(w.second); | |
| 1679 | ✗ | result["warnings"] = warnings; | |
| 1680 | ✗ | } | |
| 1681 |
2/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
16 | result["rangeStart"] = (Json::UInt64)rangeStart; |
| 1682 |
2/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
16 | result["rangeEnd"] = (Json::UInt64)rangeEnd; |
| 1683 |
2/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
16 | result["daySummary"] = daySummary; |
| 1684 |
3/6✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
|
16 | result["scheduleDistance"] = getSettingInt("ScheduleDistance"); |
| 1685 | |||
| 1686 | 16 | return result; | |
| 1687 | 18 | } | |
| 1688 | |||
| 1689 | 1 | void Scheduler::SetTimeDelta(int delta, int timeLimit) { | |
| 1690 | // Under the schedule lock: the load path reads and resets m_timeDelta | ||
| 1691 | // there, and this runs on the command's thread. | ||
| 1692 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1693 | 1 | m_timeDelta = m_timeDelta + delta; | |
| 1694 | 1 | m_timeDeltaThreshold = time(NULL) + timeLimit; | |
| 1695 | // Trigger a reload the next time Scheduler::ScheduleProc() is called | ||
| 1696 | 1 | m_loadSchedule = true; | |
| 1697 | 1 | } | |
| 1698 | |||
| 1699 | 2 | bool Scheduler::StartNextScheduledItemNow() { | |
| 1700 | // Decide under the lock and copy everything the player calls need, then | ||
| 1701 | // release it before calling the player. See Scheduler.h for why. | ||
| 1702 | 2 | std::vector<PlayerAction> actions; | |
| 1703 | |||
| 1704 | { | ||
| 1705 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::unique_lock<std::recursive_mutex> lock(m_scheduleLock); |
| 1706 | |||
| 1707 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | ScheduledItem* item = GetNextScheduledPlaylist(); |
| 1708 | |||
| 1709 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!item) |
| 1710 | ✗ | return false; | |
| 1711 | |||
| 1712 |
3/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
2 | if (Player::INSTANCE.GetStatus() != FPP_STATUS_IDLE) { |
| 1713 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogDebug(VB_SCHEDULE, "Force-Stopping running playlist\n"); |
| 1714 | 1 | actions.push_back({ .type = PlayerAction::Type::StopNow, | |
| 1715 | .forceStop = 1 }); | ||
| 1716 | } | ||
| 1717 | |||
| 1718 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogDebug(VB_SCHEDULE, "Force-Starting next scheduled playlist now:\n"); |
| 1719 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | DumpScheduledItem(item->startTime, *item); |
| 1720 | |||
| 1721 | 2 | actions.push_back({ .type = PlayerAction::Type::StartPlaylist, | |
| 1722 | 2 | .playlist = item->entry->playlist, | |
| 1723 | .position = 0, | ||
| 1724 | 2 | .repeat = item->entry->repeat, | |
| 1725 | 2 | .entryIndex = item->entryIndex, | |
| 1726 | 2 | .priority = item->entryIndex, // priority is entry index for now | |
| 1727 | 2 | .startTime = time(NULL), | |
| 1728 | 2 | .endTime = item->endTime, | |
| 1729 | 2 | .stopType = item->entry->stopType }); | |
| 1730 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | } |
| 1731 | |||
| 1732 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | RunPlayerActions(actions); |
| 1733 | |||
| 1734 | 2 | return true; | |
| 1735 |
3/10✓ Branch 0 taken 1 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 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
5 | } |
| 1736 | |||
| 1737 | class ScheduleCommand : public Command { | ||
| 1738 | public: | ||
| 1739 | 3776 | ScheduleCommand(const std::string& str, Scheduler* s) : | |
| 1740 | Command(str), | ||
| 1741 | 3776 | sched(s) {} | |
| 1742 | 7468 | virtual ~ScheduleCommand() {} | |
| 1743 | |||
| 1744 | Scheduler* sched; | ||
| 1745 | }; | ||
| 1746 | |||
| 1747 | class StartNextItemCommand : public ScheduleCommand { | ||
| 1748 | public: | ||
| 1749 | 1888 | StartNextItemCommand(Scheduler* s) : | |
| 1750 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | ScheduleCommand("Start Next Scheduled Item", s) { |
| 1751 | 1888 | } | |
| 1752 | |||
| 1753 | 2 | virtual std::unique_ptr<Command::Result> run(const std::vector<std::string>& args) override { | |
| 1754 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (sched->StartNextScheduledItemNow()) |
| 1755 | 2 | return std::make_unique<Command::Result>("Next scheduled item started"); | |
| 1756 | else | ||
| 1757 | ✗ | return std::make_unique<Command::Result>("Unable to start next scheduled item"); | |
| 1758 | } | ||
| 1759 | }; | ||
| 1760 | |||
| 1761 | // Command arguments arrive as strings after %VAR% replacement, so an unset | ||
| 1762 | // variable hands us "" and a typo hands us anything at all. std::stoi() throws | ||
| 1763 | // on both, and these commands run on a detached scheduler thread. | ||
| 1764 | 10 | static bool ParseIntArg(const std::string& str, int& out) { | |
| 1765 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
|
10 | if (str.empty()) { |
| 1766 | 1 | return false; | |
| 1767 | } | ||
| 1768 | |||
| 1769 | 9 | const char* start = str.c_str(); | |
| 1770 | 9 | char* end = nullptr; | |
| 1771 | 9 | errno = 0; | |
| 1772 | 9 | long v = strtol(start, &end, 10); | |
| 1773 | |||
| 1774 | // "Consumed nothing" has to be decided BEFORE the trailing-whitespace skip | ||
| 1775 | // below moves `end`: strtol() skips leading whitespace itself, so an | ||
| 1776 | // all-whitespace argument leaves end == start with v == 0, and skipping | ||
| 1777 | // first walks end to the terminator and makes the test pass -- reporting a | ||
| 1778 | // successful parse of 0 for an argument that held no number at all. | ||
| 1779 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
|
9 | if (end == start) { |
| 1780 | 2 | return false; | |
| 1781 | } | ||
| 1782 | |||
| 1783 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
|
7 | while ((*end == ' ') || (*end == '\t')) { |
| 1784 | ✗ | end++; | |
| 1785 | } | ||
| 1786 | |||
| 1787 |
4/6✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
7 | if ((*end != '\0') || (errno == ERANGE) || |
| 1788 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | (v < INT_MIN) || (v > INT_MAX)) { |
| 1789 | 1 | return false; | |
| 1790 | } | ||
| 1791 | |||
| 1792 | 6 | out = (int)v; | |
| 1793 | 6 | return true; | |
| 1794 | } | ||
| 1795 | |||
| 1796 | class ExtendScheduleCommand : public ScheduleCommand { | ||
| 1797 | public: | ||
| 1798 | 1888 | ExtendScheduleCommand(Scheduler* s) : | |
| 1799 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | ScheduleCommand("Extend Schedule", s) { |
| 1800 |
6/12✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1888 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1888 times.
✗ Branch 11 not taken.
|
11328 | args.push_back(CommandArg("Seconds", "int", "Extend for Seconds").setRange(-12 * 60 * 60, 12 * 60 * 60).setDefaultValue("300").setAdjustable()); |
| 1801 |
6/12✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1888 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1888 times.
✗ Branch 11 not taken.
|
11328 | args.push_back(CommandArg("Limit", "int", "For schedule events occurring in the next x Seconds").setRange(-12 * 60 * 60, 12 * 60 * 60).setDefaultValue("0").setAdjustable()); |
| 1802 | 1888 | } | |
| 1803 | |||
| 1804 | 7 | virtual std::unique_ptr<Command::Result> run(const std::vector<std::string>& args) override { | |
| 1805 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (args.size() < 1) { |
| 1806 | ✗ | return std::make_unique<Command::ErrorResult>("Command needs at least 1 argument, found " + std::to_string(args.size())); | |
| 1807 | } | ||
| 1808 | 7 | int seconds = 0; | |
| 1809 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
|
7 | if (!ParseIntArg(args[0], seconds)) { |
| 1810 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | return std::make_unique<Command::ErrorResult>("Seconds argument '" + args[0] + "' is not a number"); |
| 1811 | } | ||
| 1812 | |||
| 1813 | 3 | int limit = 0; | |
| 1814 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
|
3 | if ((args.size() > 1) && !args[1].empty() && !ParseIntArg(args[1], limit)) { |
| 1815 | ✗ | return std::make_unique<Command::ErrorResult>("Limit argument '" + args[1] + "' is not a number"); | |
| 1816 | } | ||
| 1817 | |||
| 1818 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | if (Player::INSTANCE.AdjustPlaylistStopTime(seconds)) { |
| 1819 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | if (limit != 0) { |
| 1820 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | scheduler->SetTimeDelta(seconds, limit); |
| 1821 | } | ||
| 1822 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | return std::make_unique<Command::Result>("Schedule Updated"); |
| 1823 | } | ||
| 1824 | ✗ | return std::make_unique<Command::Result>("Error extending Schedule"); | |
| 1825 | } | ||
| 1826 | }; | ||
| 1827 | |||
| 1828 | 1888 | void Scheduler::RegisterCommands() { | |
| 1829 |
6/12✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1728 times.
✓ Branch 7 taken 160 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 1728 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
3936 | CommandManager::INSTANCE.addCategorizedCommand(new StartNextItemCommand(this), "Playlist", 1); |
| 1830 |
6/12✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1728 times.
✓ Branch 7 taken 160 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 1728 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
2208 | CommandManager::INSTANCE.addCategorizedCommand(new ExtendScheduleCommand(this), "Playlist", 1); |
| 1831 | 1888 | } | |
| 1832 |