306 lines
9.1 KiB
C++
Executable File
306 lines
9.1 KiB
C++
Executable File
#include "TimbreService.h"
|
|
|
|
#include <limits>
|
|
|
|
namespace timbre_core {
|
|
namespace {
|
|
|
|
bool containsProfile(const AppState& state, ProfileId id) noexcept {
|
|
return state.profile(id) != nullptr;
|
|
}
|
|
|
|
bool containsSchedule(const AppState& state, ScheduleId id) noexcept {
|
|
return state.schedule(id) != nullptr;
|
|
}
|
|
|
|
bool containsHoliday(const AppState& state, HolidayId id) noexcept {
|
|
return state.holiday(id) != nullptr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool TimbreService::fail(ServiceCode code, const ValidationResult& validation) {
|
|
lastResult_ = ServiceResult{false, code, validation};
|
|
return false;
|
|
}
|
|
|
|
void TimbreService::observeIds(const AppState& state) noexcept {
|
|
nextProfileId_.reset();
|
|
nextScheduleId_.reset();
|
|
nextHolidayId_.reset();
|
|
for (std::size_t i = 0; i < state.profiles.size(); ++i) {
|
|
nextProfileId_.observe(state.profiles[i].id);
|
|
}
|
|
for (std::size_t i = 0; i < state.schedules.size(); ++i) {
|
|
nextScheduleId_.observe(state.schedules[i].id);
|
|
}
|
|
for (std::size_t i = 0; i < state.holidays.size(); ++i) {
|
|
nextHolidayId_.observe(state.holidays[i].id);
|
|
}
|
|
}
|
|
|
|
Id TimbreService::nextAvailableProfileId(const AppState& state) {
|
|
for (;;) {
|
|
const Id id = nextProfileId_.next();
|
|
if (id != kInvalidId && !containsProfile(state, id)) {
|
|
return id;
|
|
}
|
|
}
|
|
}
|
|
|
|
Id TimbreService::nextAvailableScheduleId(const AppState& state) {
|
|
for (;;) {
|
|
const Id id = nextScheduleId_.next();
|
|
if (id != kInvalidId && !containsSchedule(state, id)) {
|
|
return id;
|
|
}
|
|
}
|
|
}
|
|
|
|
Id TimbreService::nextAvailableHolidayId(const AppState& state) {
|
|
for (;;) {
|
|
const Id id = nextHolidayId_.next();
|
|
if (id != kInvalidId && !containsHoliday(state, id)) {
|
|
return id;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TimbreService::load() {
|
|
AppState candidate;
|
|
if (repository_ == nullptr || !repository_->load(candidate)) {
|
|
return fail(ServiceCode::LoadFailed, ValidationResult{});
|
|
}
|
|
const ValidationResult validation = ScheduleValidator::validate(candidate);
|
|
if (!validation.valid) {
|
|
return fail(ServiceCode::InvalidRepositoryState, validation);
|
|
}
|
|
ram_ = candidate;
|
|
observeIds(ram_);
|
|
loaded_ = true;
|
|
lastResult_ = ServiceResult{true, ServiceCode::Ok, ValidationResult{}};
|
|
return true;
|
|
}
|
|
|
|
bool TimbreService::commitCurrent() {
|
|
const ValidationResult validation = ScheduleValidator::validate(ram_);
|
|
if (!validation.valid) {
|
|
return fail(ServiceCode::ValidationFailed, validation);
|
|
}
|
|
if (repository_ == nullptr || !repository_->commit(ram_)) {
|
|
return fail(ServiceCode::CommitFailed, ValidationResult{});
|
|
}
|
|
lastResult_ = ServiceResult{true, ServiceCode::Ok, ValidationResult{}};
|
|
return true;
|
|
}
|
|
|
|
bool TimbreService::addProfile(const Profile& profile) {
|
|
if (ram_.profiles.full()) {
|
|
return fail(ServiceCode::LimitExceeded, ValidationResult{});
|
|
}
|
|
const ProfileId id = profile.id == kInvalidId ? nextAvailableProfileId(ram_) : profile.id;
|
|
if (containsProfile(ram_, id)) {
|
|
return fail(ServiceCode::DuplicateId, ValidationResult{});
|
|
}
|
|
Profile copy = profile;
|
|
copy.id = id;
|
|
const StableIdAllocator savedAllocator = nextProfileId_;
|
|
const bool committed = transact([©](AppState& candidate) {
|
|
if (!candidate.profiles.push_back(copy)) {
|
|
return false;
|
|
}
|
|
if (candidate.profiles.size() == 1) {
|
|
candidate.activeProfileId = copy.id;
|
|
}
|
|
return true;
|
|
});
|
|
if (!committed) {
|
|
nextProfileId_ = savedAllocator;
|
|
}
|
|
return committed;
|
|
}
|
|
|
|
bool TimbreService::updateProfile(ProfileId id, const Profile& profile) {
|
|
if (!containsProfile(ram_, id)) {
|
|
return fail(ServiceCode::NotFound, ValidationResult{});
|
|
}
|
|
Profile copy = profile;
|
|
copy.id = id;
|
|
return transact([©](AppState& candidate) {
|
|
Profile* target = candidate.profile(copy.id);
|
|
if (target == nullptr) {
|
|
return false;
|
|
}
|
|
*target = copy;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool TimbreService::removeProfile(ProfileId id) {
|
|
if (!containsProfile(ram_, id)) {
|
|
return fail(ServiceCode::NotFound, ValidationResult{});
|
|
}
|
|
return transact([id](AppState& candidate) {
|
|
std::size_t index = 0;
|
|
for (; index < candidate.profiles.size(); ++index) {
|
|
if (candidate.profiles[index].id == id) {
|
|
break;
|
|
}
|
|
}
|
|
if (index >= candidate.profiles.size() || !candidate.profiles.erase(index)) {
|
|
return false;
|
|
}
|
|
if (candidate.activeProfileId == id) {
|
|
candidate.activeProfileId = candidate.profiles.empty()
|
|
? 0
|
|
: candidate.profiles[0].id;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool TimbreService::addSchedule(const Schedule& schedule) {
|
|
if (ram_.schedules.full()) {
|
|
return fail(ServiceCode::LimitExceeded, ValidationResult{});
|
|
}
|
|
const ScheduleId id = schedule.id == kInvalidId
|
|
? nextAvailableScheduleId(ram_)
|
|
: schedule.id;
|
|
if (containsSchedule(ram_, id)) {
|
|
return fail(ServiceCode::DuplicateId, ValidationResult{});
|
|
}
|
|
Schedule copy = schedule;
|
|
copy.id = id;
|
|
const StableIdAllocator savedAllocator = nextScheduleId_;
|
|
const bool committed = transact([©](AppState& candidate) {
|
|
if (!candidate.schedules.push_back(copy)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
if (!committed) {
|
|
nextScheduleId_ = savedAllocator;
|
|
}
|
|
return committed;
|
|
}
|
|
|
|
bool TimbreService::updateSchedule(ScheduleId id, const Schedule& schedule) {
|
|
if (!containsSchedule(ram_, id)) {
|
|
return fail(ServiceCode::NotFound, ValidationResult{});
|
|
}
|
|
Schedule copy = schedule;
|
|
copy.id = id;
|
|
return transact([©](AppState& candidate) {
|
|
Schedule* target = candidate.schedule(copy.id);
|
|
if (target == nullptr) {
|
|
return false;
|
|
}
|
|
*target = copy;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool TimbreService::removeSchedule(ScheduleId id) {
|
|
if (!containsSchedule(ram_, id)) {
|
|
return fail(ServiceCode::NotFound, ValidationResult{});
|
|
}
|
|
return transact([id](AppState& candidate) {
|
|
for (std::size_t i = 0; i < candidate.schedules.size(); ++i) {
|
|
if (candidate.schedules[i].id == id) {
|
|
return candidate.schedules.erase(i);
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
bool TimbreService::addHoliday(const Holiday& holiday) {
|
|
if (ram_.holidays.full()) {
|
|
return fail(ServiceCode::LimitExceeded, ValidationResult{});
|
|
}
|
|
const HolidayId id = holiday.id == kInvalidId
|
|
? nextAvailableHolidayId(ram_)
|
|
: holiday.id;
|
|
if (containsHoliday(ram_, id)) {
|
|
return fail(ServiceCode::DuplicateId, ValidationResult{});
|
|
}
|
|
Holiday copy = holiday;
|
|
copy.id = id;
|
|
const StableIdAllocator savedAllocator = nextHolidayId_;
|
|
const bool committed = transact([©](AppState& candidate) {
|
|
if (!candidate.holidays.push_back(copy)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
if (!committed) {
|
|
nextHolidayId_ = savedAllocator;
|
|
}
|
|
return committed;
|
|
}
|
|
|
|
bool TimbreService::updateHoliday(HolidayId id, const Holiday& holiday) {
|
|
if (!containsHoliday(ram_, id)) {
|
|
return fail(ServiceCode::NotFound, ValidationResult{});
|
|
}
|
|
Holiday copy = holiday;
|
|
copy.id = id;
|
|
return transact([©](AppState& candidate) {
|
|
Holiday* target = candidate.holiday(copy.id);
|
|
if (target == nullptr) {
|
|
return false;
|
|
}
|
|
*target = copy;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool TimbreService::removeHoliday(HolidayId id) {
|
|
if (!containsHoliday(ram_, id)) {
|
|
return fail(ServiceCode::NotFound, ValidationResult{});
|
|
}
|
|
return transact([id](AppState& candidate) {
|
|
for (std::size_t i = 0; i < candidate.holidays.size(); ++i) {
|
|
if (candidate.holidays[i].id == id) {
|
|
return candidate.holidays.erase(i);
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
bool TimbreService::setActiveProfile(ProfileId id) {
|
|
if (!containsProfile(ram_, id)) {
|
|
return fail(ServiceCode::NotFound, ValidationResult{});
|
|
}
|
|
return transact([id](AppState& candidate) {
|
|
candidate.activeProfileId = id;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool TimbreService::setPattern(PatternId patternIdentifier, const Pattern& pattern) {
|
|
if (!isPatternId(patternIdentifier) ||
|
|
patternIdentifier != pattern.id ||
|
|
!ScheduleValidator::validatePattern(pattern).valid) {
|
|
return fail(ServiceCode::ValidationFailed, ValidationResult{});
|
|
}
|
|
return transact([patternIdentifier, pattern](AppState& candidate) {
|
|
const std::size_t index = patternIndex(patternIdentifier);
|
|
if (index >= candidate.patterns.size()) {
|
|
return false;
|
|
}
|
|
candidate.patterns[index] = pattern;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool TimbreService::setPatterns(const std::array<Pattern, kMaxPatterns>& patterns) {
|
|
return transact([&patterns](AppState& candidate) {
|
|
candidate.patterns = patterns;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
} // namespace timbre_core
|