🧽 Refactors stateful service

This commit is contained in:
Rune Harlyk
2024-11-23 13:54:10 +01:00
parent a7c5a5f1cf
commit abdf763215
3 changed files with 76 additions and 103 deletions
@@ -21,16 +21,14 @@ class StatefulHttpEndpoint {
: _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {} : _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {}
esp_err_t handleStateUpdate(PsychicRequest *request, JsonVariant &json) { esp_err_t handleStateUpdate(PsychicRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>()) { if (!json.is<JsonObject>()) return request->reply(400);
return request->reply(400);
}
JsonObject jsonObject = json.as<JsonObject>(); JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) { if (outcome == StateUpdateResult::ERROR)
return request->reply(400); return request->reply(400);
} else if ((outcome == StateUpdateResult::CHANGED)) { else if ((outcome == StateUpdateResult::CHANGED)) {
// persist the changes to the FS // persist the changes to the FS
_statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID);
} }
@@ -58,25 +58,18 @@ class FSPersistence {
} }
bool writeToFS() { bool writeToFS() {
// create and populate a new json object
JsonDocument jsonDocument; JsonDocument jsonDocument;
JsonObject jsonObject = jsonDocument.to<JsonObject>(); JsonObject jsonObject = jsonDocument.to<JsonObject>();
_statefulService->read(jsonObject, _stateReader); _statefulService->read(jsonObject, _stateReader);
// make directories if required
mkdirs(); mkdirs();
// serialize it to filesystem File file = _fs->open(_filePath, "w");
File settingsFile = _fs->open(_filePath, "w");
// failed to open file, return false if (!file) return false;
if (!settingsFile) {
return false;
}
// serialize the data to the file serializeJson(jsonDocument, file);
serializeJson(jsonDocument, settingsFile); file.close();
settingsFile.close();
return true; return true;
} }
@@ -100,7 +93,7 @@ class FSPersistence {
FS *_fs {&ESPFS}; FS *_fs {&ESPFS};
const char *_filePath; const char *_filePath;
size_t _bufferSize; size_t _bufferSize;
update_handler_id_t _updateHandlerId; HandlerId _updateHandlerId;
// We assume we have a _filePath with format // We assume we have a _filePath with format
// "/directory1/directory2/filename" We create a directory for each missing // "/directory1/directory2/filename" We create a directory for each missing
@@ -110,9 +103,7 @@ class FSPersistence {
int index = 0; int index = 0;
while ((index = path.indexOf('/', index + 1)) != -1) { while ((index = path.indexOf('/', index + 1)) != -1) {
String segment = path.substring(0, index); String segment = path.substring(0, index);
if (!_fs->exists(segment)) { if (!_fs->exists(segment)) _fs->mkdir(segment);
_fs->mkdir(segment);
}
} }
} }
@@ -1,20 +1,4 @@
#ifndef StatefulService_h #pragma once
#define StatefulService_h
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2018 - 2023 rjwats
* Copyright (C) 2023 theelims
* Copyright (C) 2024 runeharlyk
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
@@ -32,28 +16,42 @@ using JsonStateUpdater = std::function<StateUpdateResult(JsonObject &root, T &se
template <typename T> template <typename T>
using JsonStateReader = std::function<void(T &settings, JsonObject &root)>; using JsonStateReader = std::function<void(T &settings, JsonObject &root)>;
typedef size_t update_handler_id_t; using HandlerId = size_t;
typedef size_t hook_handler_id_t; using StateUpdateCallback = std::function<void(const String &originId)>;
typedef std::function<void(const String &originId)> StateUpdateCallback; using StateHookCallback = std::function<void(const String &originId, StateUpdateResult &result)>;
typedef std::function<void(const String &originId, StateUpdateResult &result)> StateHookCallback;
typedef struct StateUpdateHandlerInfo { class HandlerBase {
static inline update_handler_id_t currentUpdatedHandlerId = 0; protected:
update_handler_id_t _id; static inline HandlerId nextId_ = 1; // Start from 1, 0 is invalid
StateUpdateCallback _callback; HandlerId id_;
bool _allowRemove; bool allowRemove_;
StateUpdateHandlerInfo(StateUpdateCallback callback, bool allowRemove)
: _id(++currentUpdatedHandlerId), _callback(callback), _allowRemove(allowRemove) {};
} StateUpdateHandlerInfo_t;
typedef struct StateHookHandlerInfo { HandlerBase(bool allowRemove) : id_(nextId_++), allowRemove_(allowRemove) {}
static inline hook_handler_id_t currentHookHandlerId = 0;
hook_handler_id_t _id; public:
StateHookCallback _callback; HandlerId getId() const { return id_; }
bool _allowRemove; bool isRemovable() const { return allowRemove_; }
StateHookHandlerInfo(StateHookCallback callback, bool allowRemove) };
: _id(++currentHookHandlerId), _callback(callback), _allowRemove(allowRemove) {};
} StateHookHandlerInfo_t; class UpdateHandler : public HandlerBase {
StateUpdateCallback callback_;
public:
UpdateHandler(StateUpdateCallback callback, bool allowRemove)
: HandlerBase(allowRemove), callback_(std::move(callback)) {}
void invoke(const String &originId) const { callback_(originId); }
};
class HookHandler : public HandlerBase {
StateHookCallback callback_;
public:
HookHandler(StateHookCallback callback, bool allowRemove)
: HandlerBase(allowRemove), callback_(std::move(callback)) {}
void invoke(const String &originId, StateUpdateResult &result) const { callback_(originId, result); }
};
template <class T> template <class T>
class StatefulService { class StatefulService {
@@ -61,93 +59,81 @@ class StatefulService {
template <typename... Args> template <typename... Args>
StatefulService(Args &&...args) : state_(std::forward<Args>(args)...), mutex_(xSemaphoreCreateRecursiveMutex()) {} StatefulService(Args &&...args) : state_(std::forward<Args>(args)...), mutex_(xSemaphoreCreateRecursiveMutex()) {}
update_handler_id_t addUpdateHandler(StateUpdateCallback callback, bool allowRemove = true) { HandlerId addUpdateHandler(StateUpdateCallback callback, bool allowRemove = true) {
if (!callback) return 0; if (!callback) return 0;
StateUpdateHandlerInfo_t updateHandler(callback, allowRemove); updateHandlers_.emplace_back(std::move(callback), allowRemove);
updateHandlers_.push_back(updateHandler); return updateHandlers_.back().getId();
return updateHandler._id;
} }
void removeUpdateHandler(update_handler_id_t id) { void removeUpdateHandler(HandlerId id) {
for (auto i = updateHandlers_.begin(); i != updateHandlers_.end();) { updateHandlers_.remove_if(
if ((*i)._allowRemove && (*i)._id == id) { [id](const UpdateHandler &handler) { return handler.isRemovable() && handler.getId() == id; });
i = updateHandlers_.erase(i);
} else {
++i;
}
}
} }
hook_handler_id_t addHookHandler(StateHookCallback callback, bool allowRemove = true) { HandlerId addHookHandler(StateHookCallback callback, bool allowRemove = true) {
if (!callback) return 0; if (!callback) return 0;
StateHookHandlerInfo_t hookHandler(callback, allowRemove); hookHandlers_.emplace_back(std::move(callback), allowRemove);
hookHandlers_.push_back(hookHandler); return hookHandlers_.back().getId();
return hookHandler._id;
} }
void removeHookHandler(hook_handler_id_t id) { void removeHookHandler(HandlerId id) {
for (auto i = hookHandlers_.begin(); i != hookHandlers_.end();) { hookHandlers_.remove_if(
if ((*i)._allowRemove && (*i)._id == id) { [id](const HookHandler &handler) { return handler.isRemovable() && handler.getId() == id; });
i = hookHandlers_.erase(i);
} else {
++i;
}
}
} }
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const String &originId) { StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const String &originId) {
beginTransaction(); lock();
StateUpdateResult result = stateUpdater(state_); StateUpdateResult result = stateUpdater(state_);
endTransaction(); unlock();
notifyStateChange(originId, result); notifyStateChange(originId, result);
return result; return result;
} }
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T &)> stateUpdater) { StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T &)> stateUpdater) {
beginTransaction(); lock();
StateUpdateResult result = stateUpdater(state_); StateUpdateResult result = stateUpdater(state_);
endTransaction(); unlock();
return result; return result;
} }
StateUpdateResult update(JsonObject &jsonObject, JsonStateUpdater<T> stateUpdater, const String &originId) { StateUpdateResult update(JsonObject &jsonObject, JsonStateUpdater<T> stateUpdater, const String &originId) {
beginTransaction(); lock();
StateUpdateResult result = stateUpdater(jsonObject, state_); StateUpdateResult result = stateUpdater(jsonObject, state_);
endTransaction(); unlock();
notifyStateChange(originId, result); notifyStateChange(originId, result);
return result; return result;
} }
StateUpdateResult updateWithoutPropagation(JsonObject &jsonObject, JsonStateUpdater<T> stateUpdater) { StateUpdateResult updateWithoutPropagation(JsonObject &jsonObject, JsonStateUpdater<T> stateUpdater) {
beginTransaction(); lock();
StateUpdateResult result = stateUpdater(jsonObject, state_); StateUpdateResult result = stateUpdater(jsonObject, state_);
endTransaction(); unlock();
return result; return result;
} }
void read(std::function<void(T &)> stateReader) { void read(std::function<void(T &)> stateReader) {
beginTransaction(); lock();
stateReader(state_); stateReader(state_);
endTransaction(); unlock();
} }
void read(JsonObject &jsonObject, JsonStateReader<T> stateReader) { void read(JsonObject &jsonObject, JsonStateReader<T> stateReader) {
beginTransaction(); lock();
stateReader(state_, jsonObject); stateReader(state_, jsonObject);
endTransaction(); unlock();
} }
void callUpdateHandlers(const String &originId) { void callUpdateHandlers(const String &originId) {
for (const StateUpdateHandlerInfo_t &updateHandler : updateHandlers_) { for (const UpdateHandler &updateHandler : updateHandlers_) {
updateHandler._callback(originId); updateHandler.invoke(originId);
} }
} }
void callHookHandlers(const String &originId, StateUpdateResult &result) { void callHookHandlers(const String &originId, StateUpdateResult &result) {
for (const StateHookHandlerInfo_t &hookHandler : hookHandlers_) { for (const HookHandler &hookHandler : hookHandlers_) {
hookHandler._callback(originId, result); hookHandler.invoke(originId, result);
} }
} }
@@ -156,8 +142,8 @@ class StatefulService {
private: private:
T state_; T state_;
inline void beginTransaction() { xSemaphoreTakeRecursive(mutex_, portMAX_DELAY); } inline void lock() { xSemaphoreTakeRecursive(mutex_, portMAX_DELAY); }
inline void endTransaction() { xSemaphoreGiveRecursive(mutex_); } inline void unlock() { xSemaphoreGiveRecursive(mutex_); }
void notifyStateChange(const String &originId, StateUpdateResult &result) { void notifyStateChange(const String &originId, StateUpdateResult &result) {
callHookHandlers(originId, result); callHookHandlers(originId, result);
@@ -167,8 +153,6 @@ class StatefulService {
} }
SemaphoreHandle_t mutex_; SemaphoreHandle_t mutex_;
std::list<StateUpdateHandlerInfo_t> updateHandlers_; std::list<UpdateHandler> updateHandlers_;
std::list<StateHookHandlerInfo_t> hookHandlers_; std::list<HookHandler> hookHandlers_;
}; };
#endif // end StatefulService_h