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.
94 lines
2.9 KiB
C++
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
|