🧽 Refactors stateful service
This commit is contained in:
@@ -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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user