#include "TimbreService.h" #include 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& patterns) { return transact([&patterns](AppState& candidate) { candidate.patterns = patterns; return true; }); } } // namespace timbre_core