GCC Code Coverage Report


Directory: ./
File: Scheduler.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 1 1 100.0%
Functions: 1 1 100.0%
Branches: 0 0 -%

Line Branch Exec Source
1 #pragma once
2 /*
3 * This file is part of the Falcon Player (FPP) and is Copyright (C)
4 * 2013-2022 by the Falcon Player Developers.
5 *
6 * The Falcon Player (FPP) is free software, and is covered under
7 * multiple Open Source licenses. Please see the included 'LICENSES'
8 * file for descriptions of what files are covered by each license.
9 *
10 * This source file is covered under the LGPL v2.1 as described in the
11 * included LICENSE.LGPL file.
12 */
13
14 #include <atomic>
15 #include <map>
16 #include "fpp-json.h"
17 #include <mutex>
18 #include <set>
19 #include <vector>
20
21 #include <pthread.h>
22 #include <time.h>
23
24 #include "ScheduleEntry.h"
25
26 #define SCHEDULE_INDEX_INVALID -1
27
28 // The INX_* day index and day mask defines now live in ScheduleEntry.h, which
29 // is included above, since they describe a ScheduleEntry's dayIndex field.
30 // They remain visible to anything including this header.
31
32 class ScheduledItem {
33 public:
34 ScheduleEntry* const entry;
35 int const priority;
36 int const entryIndex;
37 bool ran;
38 time_t const startTime;
39 time_t endTime;
40 std::string command;
41 Json::Value args;
42 // A playlist entry's start (as opposed to an FPP command entry). The
43 // command string can't tell them apart: a command entry can run the
44 // "Start Playlist" command, and was then taken for a playlist entry with
45 // no playlist -- marked ran, never run.
46 bool isPlaylist = false;
47 };
48
49 class Scheduler {
50 public:
51 Scheduler();
52 ~Scheduler();
53
54 void ScheduleProc(void);
55 void CheckIfShouldBePlayingNow(int ignoreRepeat = 0, int forceStopped = -1);
56 void ReloadScheduleFile(void);
57 void SetTimeDelta(int delta, int timeLimit);
58 bool StartNextScheduledItemNow();
59
60 // Scheduled FPP commands run on their own detached threads. At shutdown,
61 // wait (up to timeoutMS) for any still running, before the media, outputs
62 // and command registry they use are torn down.
63 static void WaitForCommandThreads(int timeoutMS);
64
65 std::string GetNextPlaylistName();
66 std::string GetNextPlaylistStartStr();
67 119915 bool IsEnabled() const { return !m_schedulerDisabled; }
68
69 std::string GetPlaylistThatShouldBePlaying(int& repeat);
70 Json::Value GetInfo(void);
71 Json::Value GetSchedule(void);
72
73 // One expanded occurrence of a schedule entry, used while building the
74 // calendar range response. Kept lightweight so a range holding tens of
75 // thousands of occurrences can be collected and aggregated before any of
76 // the much more expensive JSON is built.
77 struct Occurrence {
78 int entryIndex;
79 int dayKey; // YYYYMMDD of the local day the occurrence starts on
80 time_t start;
81 time_t end;
82 bool overridden;
83 };
84
85 Json::Value GetScheduleRange(time_t rangeStart, time_t rangeEnd,
86 bool includeDisabled, bool daySummary);
87
88 private:
89 Json::Value BuildScheduleRangeItem(const ScheduleEntry& entry,
90 const Occurrence& occurrence,
91 const std::string& timeFmt);
92
93 // A player start/stop the scheduler decided on while holding
94 // m_scheduleLock, carried by value so it can be run after the lock is
95 // released. See the m_scheduleLock declaration below.
96 struct PlayerAction {
97 enum class Type {
98 StopUntilIdle,
99 StopNow,
100 StopGracefully,
101 StopGracefullyAfterLoop,
102 StartPlaylist
103 };
104
105 Type type;
106 int forceStop = 0;
107 std::string playlist;
108 int position = 0;
109 int repeat = 0;
110 int entryIndex = 0;
111 int priority = 0;
112 time_t startTime = 0;
113 time_t endTime = 0;
114 int stopType = 0;
115 };
116
117 // A countdown preset to trigger once the lock is released. Presets run
118 // their commands synchronously, so they are subject to the same rule as
119 // PlayerAction.
120 struct CountdownPreset {
121 std::string name;
122 std::string playlist;
123 };
124
125 void RunPlayerActions(const std::vector<PlayerAction>& actions);
126 void RunCountdownPresets(const std::vector<CountdownPreset>& presets);
127
128 void AddScheduledItems(ScheduleEntry* entry, int index, std::time_t loadTime);
129 void DumpScheduledItem(const std::time_t itemTime, const ScheduledItem& item);
130 void DumpScheduledItems();
131 void CheckScheduledItems(bool restarted = false);
132 void ClearScheduledItems();
133 void SetItemRan(ScheduledItem& item, bool ran);
134
135 ScheduledItem* GetNextScheduledPlaylist();
136 void LoadScheduleFromFile(void);
137 void SchedulePrint(void);
138 std::string GetDayTextFromDayIndex(const int index);
139
140 void RegisterCommands();
141
142 void doCountdown(const std::time_t now, const std::time_t itemTime, const std::vector<ScheduledItem>& items,
143 std::vector<CountdownPreset>& presets);
144 void doScheduledCommand(const std::time_t itemTime, const ScheduledItem& item);
145 bool doScheduledPlaylist(const std::time_t now, const std::time_t itemTime, ScheduledItem& item, bool restarted,
146 std::vector<PlayerAction>& actions);
147
148 bool m_schedulerDisabled;
149 // Set lock-free from ScheduleProc, ReloadScheduleFile and SetTimeDelta on
150 // different threads; atomic so those unsynchronized writes/reads are defined.
151 std::atomic<bool> m_loadSchedule;
152 int m_lastLoadDate;
153 int m_timeDelta;
154 time_t m_timeDeltaThreshold;
155
156 time_t m_lastProcTime;
157
158 // The wall-clock second CheckScheduledItems last finished examining for
159 // scheduled FPP commands. A command fires when its second falls in the
160 // (m_lastCommandCheckTime, now] window not yet examined, so a missed second
161 // (a slow midnight reload, an NTP step, a stalled main loop) still fires it
162 // on the next pass instead of dropping it silently. A per-pass catch-up
163 // cap keeps a large forward clock step from replaying a batch of stale
164 // commands. Playlists need no such window - they already tolerate a late
165 // start by checking their own end time.
166 time_t m_lastCommandCheckTime;
167
168 // Lock ordering: m_scheduleLock may be taken while holding no other lock,
169 // or from inside Playlist's m_playlistMutex (a playlist entry can run an
170 // FPP Command inline, and several of those call back into the scheduler).
171 // That makes m_playlistMutex -> m_scheduleLock the established order, so
172 // scheduler code must never start or stop the Player/Playlist while
173 // holding m_scheduleLock - doing so takes the two locks in the opposite
174 // order and deadlocks against a playlist thread. Both mutexes are
175 // recursive, which does not help across threads. Decide under the lock,
176 // copy out what the call needs, release, then call the Player (see
177 // CheckScheduledItems). The read-only Player/Playlist getters do not take
178 // m_playlistMutex and are safe to call under this lock.
179 std::recursive_mutex m_scheduleLock;
180 std::vector<ScheduleEntry> m_Schedule;
181 std::map<std::time_t, std::vector<ScheduledItem>> m_scheduledItems;
182
183 // Scheduled starts decided under m_scheduleLock but not yet carried out
184 // (RunPlayerActions runs unlocked), keyed by start time and entry index.
185 // The player still reads idle while such a start loads, and a concurrent
186 // CheckIfShouldBePlayingNow() -- an API "Start Playlist At Item" runs one
187 // on its own thread -- used to reset the item's ran flag in that window
188 // and start it a second time. Guarded by m_scheduleLock.
189 std::set<std::pair<std::time_t, int>> m_startsInFlight;
190 std::map<std::time_t, std::vector<ScheduledItem>> m_oldItems;
191 std::map<std::time_t, std::vector<ScheduledItem>> m_ranItems;
192
193 // The scheduled occurrence a higher-priority entry pre-empted: allowed to
194 // start again (despite not repeating) once the player goes idle, then
195 // consumed. Keyed by occurrence -- entry index alone matched every later
196 // run of that entry forever, restarting it each time it ended.
197 int m_forcedNextPlaylist;
198 std::time_t m_forcedNextPlaylistStart = 0;
199
200 std::string m_lastBlockedPlaylist;
201 std::time_t m_lastBlockedTime;
202
203 bool m_schedulesExtendBeyondDistance;
204 };
205
206 extern Scheduler* scheduler;
207