137 lines
4.3 KiB
C++
Executable File
137 lines
4.3 KiB
C++
Executable File
#pragma once
|
|
|
|
#include "ScheduleValidator.h"
|
|
#include "StateRepository.h"
|
|
|
|
#include <limits>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
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 <typename Mutator>
|
|
bool transact(Mutator&& mutator) {
|
|
AppState candidate = ram_;
|
|
bool accepted = true;
|
|
if constexpr (std::is_void_v<std::invoke_result_t<Mutator, AppState&>>) {
|
|
std::forward<Mutator>(mutator)(candidate);
|
|
} else {
|
|
accepted = static_cast<bool>(
|
|
std::forward<Mutator>(mutator)(candidate));
|
|
}
|
|
if (!accepted) {
|
|
return fail(ServiceCode::MutationRejected, ValidationResult{});
|
|
}
|
|
|
|
if (candidate.revision == std::numeric_limits<std::uint32_t>::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 <typename Mutator>
|
|
bool mutate(Mutator&& mutator) {
|
|
return transact(std::forward<Mutator>(mutator));
|
|
}
|
|
|
|
template <typename Mutator>
|
|
bool apply(Mutator&& mutator) {
|
|
return transact(std::forward<Mutator>(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<Pattern, kMaxPatterns>& 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
|