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.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user