Files
radiantwave/src/schedule/schedule_manager.h
T
NeilClack 509c925480 Kiosk: schedule client (conditional GET + atomic cache) and engine
Adds api_client support for GET /licenses/{key}/schedule with If-None-Match
and target-payload parsing (version/type/daily/weekly + legacy blocks), an
atomic on-disk schedule cache, a pure schedule engine (half-open blocks,
midnight self-wrap for daily, Sunday->Monday spillover for weekly, invalid
blocks skipped with a warning), and a background ScheduleManager that loads
the cache offline and pulls on boot and every 5 minutes. Adds engine/cache
tests against the shared docs/fixtures, including 304, corrupt cache, and
offline cases.
2026-09-29 10:34:40 -05:00

94 lines
2.9 KiB
C++

#pragma once
#include "db/database_manager.h"
#include "network/api_client.h"
#include "schedule/schedule_engine.h"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
namespace radiantwave::schedule {
// Fetches the Schedule for the stored License Key with conditional GET, caches
// the last payload to disk (atomic write), and exposes the active Block for
// playback. Never blocks the UI: a background worker pulls on boot, on
// network-up, and every 5 minutes.
class ScheduleManager {
public:
ScheduleManager();
~ScheduleManager();
ScheduleManager(const ScheduleManager&) = delete;
ScheduleManager& operator=(const ScheduleManager&) = delete;
// Returns the active Block for the stored device state at `minutes`
// (wall-clock) on `weekday`. Honors local enable/disable: when schedules
// are off, always returns no block.
Decision activeBlock(int minutes, Weekday weekday) const;
// True when a payload with an active type is loaded (from network or cache).
bool hasSchedule() const;
// True while schedules are locally enabled (System Settings toggle).
bool enabled() const;
// Background lifecycle.
void start(db::DatabaseManager& db, const std::string& timezone,
std::function<void()> onChanged = nullptr);
// Update the local enable flag (from config.yaml). When off, activeBlock()
// returns none but the cache keeps updating.
void setEnabled(bool enabled);
// Test/inspection hook: replace the loaded payload directly.
void setPayloadForTesting(api::SchedulePayload payload, const std::string& etag);
void shutdown();
// Request an immediate refresh (e.g. after a settings change). Non-blocking.
void requestRefresh();
// Pull interval (5 minutes).
static constexpr std::chrono::seconds REFRESH_INTERVAL{300};
// Injectable for tests: performs one fetch+cache cycle. The production
// implementation uses api::fetchScheduleForLicense + schedule_cache.
using FetchFn = std::function<api::ScheduleFetchStatus(
const std::string& licenseKey, const std::string& etag,
api::SchedulePayload& out, std::string& outEtag)>;
void setFetchForTesting(FetchFn fetch);
// Run one fetch cycle synchronously (used by tests and boot).
void refreshOnce();
private:
void run();
void loadCache();
void applyPayload(api::SchedulePayload payload, const std::string& etag);
mutable std::mutex mutex_;
api::SchedulePayload payload_;
std::string etag_;
bool enabled_ = true;
std::string licenseKey_;
std::string timezone_;
FetchFn fetch_;
db::DatabaseManager* db_ = nullptr;
std::function<void()> onChanged_;
std::thread worker_;
std::condition_variable cv_;
std::atomic<bool> stop_{false};
std::atomic<bool> requested_{false};
bool started_ = false;
};
} // namespace radiantwave::schedule