#pragma once #include "db/database_manager.h" #include "network/api_client.h" #include "schedule/schedule_engine.h" #include #include #include #include #include #include #include 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 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; 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 onChanged_; std::thread worker_; std::condition_variable cv_; std::atomic stop_{false}; std::atomic requested_{false}; bool started_ = false; }; } // namespace radiantwave::schedule