#pragma once #include "ScheduleValidator.h" #include "StateRepository.h" #include #include #include namespace timbre_core { enum class ServiceCode : std::uint8_t { Ok = 0, NotLoaded, LoadFailed, InvalidRepositoryState, MutationRejected, ValidationFailed, CommitFailed, NotFound, LimitExceeded, DuplicateId }; struct ServiceResult { bool ok = true; ServiceCode code = ServiceCode::Ok; ValidationResult validation{}; constexpr bool succeeded() const noexcept { return ok; } constexpr explicit operator bool() const noexcept { return ok; } }; class TimbreService { public: explicit TimbreService(StateRepository& repository) : repository_(&repository) {} bool load(); bool loaded() const noexcept { return loaded_; } const AppState& state() const noexcept { return ram_; } const AppState& ram() const noexcept { return ram_; } const ServiceResult& lastResult() const noexcept { return lastResult_; } ServiceCode lastError() const noexcept { return lastResult_.code; } // Copy, mutate, validate, persist, and only then publish to ram_. The // lambda may return bool (false rejects the transaction) or void. template bool transact(Mutator&& mutator) { AppState candidate = ram_; bool accepted = true; if constexpr (std::is_void_v>) { std::forward(mutator)(candidate); } else { accepted = static_cast( std::forward(mutator)(candidate)); } if (!accepted) { return fail(ServiceCode::MutationRejected, ValidationResult{}); } if (candidate.revision == std::numeric_limits::max()) { candidate.revision = 0; } else { ++candidate.revision; } const ValidationResult validation = ScheduleValidator::validate(candidate); if (!validation.valid) { return fail(ServiceCode::ValidationFailed, validation); } if (!repository_->commit(candidate)) { return fail(ServiceCode::CommitFailed, ValidationResult{}); } // This is the only assignment to the live model in a transaction. ram_ = candidate; loaded_ = true; lastResult_ = ServiceResult{true, ServiceCode::Ok, ValidationResult{}}; return true; } template bool mutate(Mutator&& mutator) { return transact(std::forward(mutator)); } template bool apply(Mutator&& mutator) { return transact(std::forward(mutator)); } bool replace(const AppState& replacement) { return transact([&replacement](AppState& candidate) { candidate = replacement; return true; }); } bool commitCurrent(); bool commit() { return commitCurrent(); } bool addProfile(const Profile& profile); bool updateProfile(ProfileId id, const Profile& profile); bool removeProfile(ProfileId id); bool deleteProfile(ProfileId id) { return removeProfile(id); } bool addSchedule(const Schedule& schedule); bool updateSchedule(ScheduleId id, const Schedule& schedule); bool removeSchedule(ScheduleId id); bool deleteSchedule(ScheduleId id) { return removeSchedule(id); } bool addHoliday(const Holiday& holiday); bool updateHoliday(HolidayId id, const Holiday& holiday); bool removeHoliday(HolidayId id); bool deleteHoliday(HolidayId id) { return removeHoliday(id); } bool setActiveProfile(ProfileId id); bool setPattern(PatternId id, const Pattern& pattern); bool setPatterns(const std::array& patterns); private: bool fail(ServiceCode code, const ValidationResult& validation); void observeIds(const AppState& state) noexcept; Id nextAvailableProfileId(const AppState& state); Id nextAvailableScheduleId(const AppState& state); Id nextAvailableHolidayId(const AppState& state); StateRepository* repository_ = nullptr; AppState ram_{}; StableIdAllocator nextProfileId_{}; StableIdAllocator nextScheduleId_{}; StableIdAllocator nextHolidayId_{}; bool loaded_ = false; ServiceResult lastResult_{}; }; } // namespace timbre_core