diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b409c7a..b5d0965 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -150,6 +150,7 @@ set(headers webserver_displaycontrol.h webserver_ota.h webserver_settings.h + webserver_stringsettings.h wifitexthelpers.h wifi_bobbycar.h ) diff --git a/main/displays/updatedisplay.h b/main/displays/updatedisplay.h index 258ab39..378f51f 100644 --- a/main/displays/updatedisplay.h +++ b/main/displays/updatedisplay.h @@ -93,7 +93,7 @@ void UpdateDisplay::redraw() m_statusLabel.redraw(toString(asyncOta->status())); const auto progress = asyncOta->progress(); m_progressLabel.redraw(std::to_string(progress)); - if (const auto totalSize = asyncOta->totalSize()) + if (const auto totalSize = asyncOta->totalSize(); totalSize && *totalSize > 0) { m_totalLabel.redraw(std::to_string(*totalSize)); m_progressBar.redraw(float(progress) / *totalSize * 100); diff --git a/main/settingspersister.h b/main/settingspersister.h index 6dcd314..1702a85 100644 --- a/main/settingspersister.h +++ b/main/settingspersister.h @@ -303,7 +303,7 @@ bool SettingsPersister::load(T &settings) { settings.executeForEveryCommonSetting([&](const char *key, auto &value) { - if (esp_err_t result = nvsGetterHelper>::nvs_get(m_handle, key, &value); result != ESP_OK) + if (esp_err_t result = nvsGetterHelper>::nvs_get(m_handle, key, &value); result != ESP_OK) { if (result != ESP_ERR_NVS_NOT_FOUND) ESP_LOGE("BOBBY", "nvs_get() COMMON %s failed with %s", key, esp_err_to_name(result)); @@ -321,7 +321,7 @@ bool SettingsPersister::load(T &settings) { settings.executeForEveryProfileSetting([&](const char *key, auto &value) { - if (esp_err_t result = nvsGetterHelper>::nvs_get(m_profile->handle, key, &value); result != ESP_OK) + if (esp_err_t result = nvsGetterHelper>::nvs_get(m_profile->handle, key, &value); result != ESP_OK) { if (result != ESP_ERR_NVS_NOT_FOUND) ESP_LOGE("BOBBY", "nvs_get() PROFILE %s failed with %s", key, esp_err_to_name(result)); @@ -418,9 +418,9 @@ bool SettingsPersister::save(T &settings) if (m_handle) { - settings.executeForEveryCommonSetting([&](const char *key, auto value) + settings.executeForEveryCommonSetting([&](const char *key, const auto &value) { - if (esp_err_t result = nvsSetterHelper::nvs_set(m_handle, key, value); result != ESP_OK) + if (esp_err_t result = nvsSetterHelper>::nvs_set(m_handle, key, value); result != ESP_OK) { ESP_LOGE("BOBBY", "nvs_set() COMMON %s failed with %s", key, esp_err_to_name(result)); result = false; @@ -435,9 +435,9 @@ bool SettingsPersister::save(T &settings) if (m_profile) { - settings.executeForEveryProfileSetting([&](const char *key, auto value) + settings.executeForEveryProfileSetting([&](const char *key, const auto &value) { - if (esp_err_t result = nvsSetterHelper::nvs_set(m_profile->handle, key, value); result != ESP_OK) + if (esp_err_t result = nvsSetterHelper>::nvs_set(m_profile->handle, key, value); result != ESP_OK) { ESP_LOGE("BOBBY", "nvs_set() PROFILE %s failed with %s", key, esp_err_to_name(result)); result = false; diff --git a/main/webserver.h b/main/webserver.h index 99b7d81..5ad9382 100644 --- a/main/webserver.h +++ b/main/webserver.h @@ -20,6 +20,7 @@ #include "webserver_ota.h" #endif #include "webserver_settings.h" +#include "webserver_stringsettings.h" #ifdef FEATURE_WEBSERVER namespace { @@ -49,17 +50,19 @@ void initWebserver() } for (const httpd_uri_t &uri : { - httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/triggerButton", .method = HTTP_GET, .handler = webserver_triggerButton_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/triggerItem", .method = HTTP_GET, .handler = webserver_triggerItem_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/setValue", .method = HTTP_GET, .handler = webserver_setValue_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/reboot", .method = HTTP_GET, .handler = webserver_reboot_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/triggerButton", .method = HTTP_GET, .handler = webserver_triggerButton_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/triggerItem", .method = HTTP_GET, .handler = webserver_triggerItem_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/setValue", .method = HTTP_GET, .handler = webserver_setValue_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/reboot", .method = HTTP_GET, .handler = webserver_reboot_handler, .user_ctx = NULL }, #ifdef FEATURE_OTA - httpd_uri_t { .uri = "/ota", .method = HTTP_GET, .handler = webserver_ota_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/triggerOta", .method = HTTP_GET, .handler = webserver_trigger_ota_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/ota", .method = HTTP_GET, .handler = webserver_ota_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/triggerOta", .method = HTTP_GET, .handler = webserver_trigger_ota_handler, .user_ctx = NULL }, #endif - httpd_uri_t { .uri = "/settings", .method = HTTP_GET, .handler = webserver_settings_handler, .user_ctx = NULL }, - httpd_uri_t { .uri = "/saveSettings", .method = HTTP_GET, .handler = webserver_save_settings_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/settings", .method = HTTP_GET, .handler = webserver_settings_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/saveSettings", .method = HTTP_GET, .handler = webserver_saveSettings_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/stringSettings", .method = HTTP_GET, .handler = webserver_stringSettings_handler, .user_ctx = NULL }, + httpd_uri_t { .uri = "/saveStringSettings", .method = HTTP_GET, .handler = webserver_saveStringSettings_handler, .user_ctx = NULL }, }) { const auto result = httpd_register_uri_handler(httpdHandle, &uri); diff --git a/main/webserver_displaycontrol.h b/main/webserver_displaycontrol.h index b8f5a8a..0ed43a7 100644 --- a/main/webserver_displaycontrol.h +++ b/main/webserver_displaycontrol.h @@ -59,12 +59,13 @@ esp_err_t webserver_root_handler(httpd_req_t *req) { HtmlTag pTag{"p", body}; - body += "Display control " + body += "Display control - " #ifdef FEATURE_OTA - "Update " + "Update - " #endif - "Settings"; + "Settings - " + "String Settings"; } { diff --git a/main/webserver_ota.h b/main/webserver_ota.h index ff57bea..8035e42 100644 --- a/main/webserver_ota.h +++ b/main/webserver_ota.h @@ -54,9 +54,133 @@ esp_err_t webserver_ota_handler(httpd_req_t *req) { HtmlTag pTag{"p", body}; - body += "Display control " - "Update " - "Settings"; + body += "Display control - " + "Update - " + "Settings - " + "String Settings"; + } + + if (const esp_app_desc_t *app_desc = esp_ota_get_app_description()) + { + HtmlTag tableTag{"table", "border=\"1\"", body}; + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current project_name"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->project_name); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current version"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->version); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current secure_version"; } + { HtmlTag tdTag{"td", body}; body += std::to_string(app_desc->secure_version); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current timestamp"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(fmt::format("{} {}", app_desc->date, app_desc->time)); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current idf_ver"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->idf_ver); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Current sha256"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(espcpputils::toHexString({app_desc->app_elf_sha256, 8})); } + } + } + else + { + constexpr const std::string_view msg = "esp_ota_get_app_description() failed"; + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + HtmlTag pTag{"p", "style=\"color: red;\"", body}; + body += esphttpdutils::htmlentities(msg); + } + + if (asyncOta) + { + HtmlTag tableTag{"table", "border=\"1\"", body}; + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Update status"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(toString(asyncOta->status())); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Update progress"; } + { + HtmlTag tdTag{"td", body}; + const auto progress = asyncOta->progress(); + const auto totalSize = asyncOta->totalSize(); + body += fmt::format("{} / {}{}", + progress, + totalSize ? std::to_string(*totalSize) : "?", + (totalSize && *totalSize > 0) ? fmt::format(" ({:.02f}%)", float(progress) / *totalSize * 100) : ""); + } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "Update message"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(asyncOta->message()); } + } + + if (const auto &appDesc = asyncOta->appDesc()) + { + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New project_name"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->project_name); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New version"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->version); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New secure_version"; } + { HtmlTag tdTag{"td", body}; body += std::to_string(appDesc->secure_version); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New timestamp"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(fmt::format("{} {}", appDesc->date, appDesc->time)); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New idf_ver"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->idf_ver); } + } + + { + HtmlTag trTag{"tr", body}; + { HtmlTag tdTag{"td", body}; body += "New sha256"; } + { HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(espcpputils::toHexString({appDesc->app_elf_sha256, 8})); } + } + } + } + else + { + HtmlTag pTag{"p", body}; + body += "Updater is sleeping (not constructed)"; } { @@ -73,6 +197,8 @@ esp_err_t webserver_ota_handler(httpd_req_t *req) HtmlTag buttonTag{"button", "type=\"submit\"", body}; body += "Go"; } + + body += "url is only used temporarely and not persisted in flash"; } } } @@ -82,7 +208,50 @@ esp_err_t webserver_ota_handler(httpd_req_t *req) esp_err_t webserver_trigger_ota_handler(httpd_req_t *req) { - CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "not yet implemented") + std::string query; + if (auto result = esphttpdutils::webserver_get_query(req)) + query = *result; + else + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + std::string url; + constexpr const std::string_view urlParamName{"url"}; + + { + char valueBufEncoded[256]; + if (const auto result = httpd_query_key_value(query.data(), urlParamName.data(), valueBufEncoded, 256); result != ESP_OK) + { + if (result == ESP_ERR_NOT_FOUND) + { + const auto msg = fmt::format("{} not set", urlParamName); + ESP_LOGW(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + else + { + const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", urlParamName, esp_err_to_name(result)); + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg); + } + } + + char valueBuf[257]; + esphttpdutils::urldecode(valueBuf, valueBufEncoded); + + url = valueBuf; + } + + if (const auto result = triggerOta(url); !result) + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/ota") + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at /") } } #endif diff --git a/main/webserver_settings.h b/main/webserver_settings.h index 5498c35..3fe3dd0 100644 --- a/main/webserver_settings.h +++ b/main/webserver_settings.h @@ -1,5 +1,8 @@ #pragma once +// system includes +#include + // esp-idf includes #ifdef FEATURE_WEBSERVER #include @@ -11,6 +14,7 @@ #include #include #include +#include // local includes #include "globals.h" @@ -18,12 +22,45 @@ #ifdef FEATURE_WEBSERVER namespace { esp_err_t webserver_settings_handler(httpd_req_t *req); -esp_err_t webserver_save_settings_handler(httpd_req_t *req); +esp_err_t webserver_saveSettings_handler(httpd_req_t *req); } // namespace using esphttpdutils::HtmlTag; namespace { +template +typename std::enable_if::value && !std::is_integral::value, bool>::type +showInputForSetting(std::string_view key, T value, std::string &body) +{ + HtmlTag spanTag{"span", "style=\"color: red;\"", body}; + body += "Unsupported config type"; + return false; +} + +template +typename std::enable_if::value, bool>::type +showInputForSetting(std::string_view key, T value, std::string &body) +{ + body += fmt::format("" + "", + esphttpdutils::htmlentities(key), + esphttpdutils::htmlentities(key), + value ? "checked " : ""); + return true; +} + +template +typename std::enable_if::value && std::is_integral::value, bool>::type +showInputForSetting(std::string_view key, T value, std::string &body) +{ + body += fmt::format("", + esphttpdutils::htmlentities(key), + value, + std::numeric_limits::min(), + std::numeric_limits::max()); + return true; +} + esp_err_t webserver_settings_handler(httpd_req_t *req) { std::string body; @@ -40,6 +77,22 @@ esp_err_t webserver_settings_handler(httpd_req_t *req) } body += ""; + + HtmlTag styleTag{"style", "type=\"text/css\"", body}; + body += + ".form-table {" + "display: table;" + "border-collapse: separate;" + "border-spacing: 10px 0;" + "}" + + ".form-table .form-table-row {" + "display: table-row;" + "}" + + ".form-table .form-table-row .form-table-cell {" + "display: table-cell;" + "}"; } { @@ -52,26 +105,31 @@ esp_err_t webserver_settings_handler(httpd_req_t *req) { HtmlTag pTag{"p", body}; - body += "Display control " + body += "Display control - " #ifdef FEATURE_OTA - "Update " + "Update - " #endif - "Settings"; + "Settings - " + "String Settings"; } - stringSettings.executeForEveryCommonSetting([&](const char *key, const auto &value){ - HtmlTag formTag{"form", "action=\"/saveSettings\" method=\"GET\"", body}; - HtmlTag fieldsetTag{"fieldset", body}; + HtmlTag divTag{"div", "class=\"form-table\"", body}; + settings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ + HtmlTag formTag{"form", "class=\"form-table-row\" action=\"/saveSettings\" method=\"GET\"", body}; + { - HtmlTag legendTag{"legend", body}; + HtmlTag divTag{"div", "class=\"form-table\"", body}; + HtmlTag bTag{"b", body}; body += esphttpdutils::htmlentities(key); } - body += fmt::format("", - esphttpdutils::htmlentities(key), - esphttpdutils::htmlentities(value)); + { + HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; + showInputForSetting(key, value, body); + } { + HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; HtmlTag buttonTag{"button", "type=\"submit\"", body}; body += "Save"; } @@ -82,7 +140,55 @@ esp_err_t webserver_settings_handler(httpd_req_t *req) CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) } -esp_err_t webserver_save_settings_handler(httpd_req_t *req) +template +typename std::enable_if::value && !std::is_integral::value, bool>::type +saveSetting(T &value, std::string_view newValue, std::string &body) +{ + body += "Unsupported config type"; + return false; +} + +template +typename std::enable_if::value, bool>::type +saveSetting(T &value, std::string_view newValue, std::string &body) +{ + if (newValue == "true") + { + value = true; + body += "applied"; + return true; + } + else if (newValue == "false") + { + value = false; + body += "applied"; + return true; + } + else + { + body += fmt::format("only true and false allowed, not {}", newValue); + return false; + } +} + +template +typename std::enable_if::value && std::is_integral::value, bool>::type +saveSetting(T &value, std::string_view newValue, std::string &body) +{ + if (auto parsed = cpputils::fromString(newValue)) + { + value = *parsed; + body += "applied"; + return true; + } + else + { + body += fmt::format("could not parse {}", newValue); + return false; + } +} + +esp_err_t webserver_saveSettings_handler(httpd_req_t *req) { std::string query; if (auto result = esphttpdutils::webserver_get_query(req)) @@ -96,9 +202,9 @@ esp_err_t webserver_save_settings_handler(httpd_req_t *req) std::string body; bool success{true}; - stringSettings.executeForEveryCommonSetting([&](const char *key, auto &value){ + settings.executeForEveryCommonSetting([&](std::string_view key, auto &value){ char valueBufEncoded[256]; - if (const auto result = httpd_query_key_value(query.data(), key, valueBufEncoded, 256); result != ESP_OK && result != ESP_ERR_NOT_FOUND) + if (const auto result = httpd_query_key_value(query.data(), key.data(), valueBufEncoded, 256); result != ESP_OK && result != ESP_ERR_NOT_FOUND) { const auto msg = fmt::format("{}: httpd_query_key_value() failed with {}", key, esp_err_to_name(result)); ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); @@ -111,15 +217,16 @@ esp_err_t webserver_save_settings_handler(httpd_req_t *req) char valueBuf[257]; esphttpdutils::urldecode(valueBuf, valueBufEncoded); - value = valueBuf; - body += fmt::format("{}: applied", key); + body += key; + if (!saveSetting(value, valueBuf, body)) + success = false; body += '\n'; }); if (body.empty()) CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "nothing changed?!") - if (settingsPersister.save(stringSettings)) + if (settingsPersister.save(settings)) body += "settings persisted successfully"; else { @@ -128,7 +235,10 @@ esp_err_t webserver_save_settings_handler(httpd_req_t *req) } if (success) - CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/") + { + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/settings") + body += "\nOk, continue at /settings"; + } CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, @@ -139,4 +249,3 @@ esp_err_t webserver_save_settings_handler(httpd_req_t *req) } // namespace #endif - diff --git a/main/webserver_stringsettings.h b/main/webserver_stringsettings.h new file mode 100644 index 0000000..e4bacc6 --- /dev/null +++ b/main/webserver_stringsettings.h @@ -0,0 +1,168 @@ +#pragma once + +// esp-idf includes +#ifdef FEATURE_WEBSERVER +#include +#endif +#include + +// 3rdparty lib includes +#include +#include +#include +#include + +// local includes +#include "globals.h" + +#ifdef FEATURE_WEBSERVER +namespace { +esp_err_t webserver_stringSettings_handler(httpd_req_t *req); +esp_err_t webserver_saveStringSettings_handler(httpd_req_t *req); +} // namespace + +using esphttpdutils::HtmlTag; + +namespace { +esp_err_t webserver_stringSettings_handler(httpd_req_t *req) +{ + std::string body; + + { + HtmlTag htmlTag{"html", body}; + + { + HtmlTag headTag{"head", body}; + + { + HtmlTag titleTag{"title", body}; + body += "String Settings"; + } + + body += ""; + + HtmlTag styleTag{"style", "type=\"text/css\"", body}; + body += + ".form-table {" + "display: table;" + "border-collapse: separate;" + "border-spacing: 10px 0;" + "}" + + ".form-table .form-table-row {" + "display: table-row;" + "}" + + ".form-table .form-table-row .form-table-cell {" + "display: table-cell;" + "}"; + } + + { + HtmlTag bodyTag{"body", body}; + + { + HtmlTag h1Tag{"h1", body}; + body += "String Settings"; + } + + { + HtmlTag pTag{"p", body}; + body += "Display control - " +#ifdef FEATURE_OTA + "Update - " +#endif + "Settings - " + "String Settings"; + } + + HtmlTag divTag{"div", "class=\"form-table\"", body}; + stringSettings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){ + HtmlTag formTag{"form", "class=\"form-table-row\" action=\"/saveStringSettings\" method=\"GET\"", body}; + + { + HtmlTag divTag{"div", "class=\"form-table\"", body}; + HtmlTag bTag{"b", body}; + body += esphttpdutils::htmlentities(key); + } + + { + HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; + body += fmt::format("", + esphttpdutils::htmlentities(key), + esphttpdutils::htmlentities(value)); + } + + { + HtmlTag divTag{"div", "class=\"form-table-cell\"", body}; + HtmlTag buttonTag{"button", "type=\"submit\"", body}; + body += "Save"; + } + }); + } + } + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body) +} + +esp_err_t webserver_saveStringSettings_handler(httpd_req_t *req) +{ + std::string query; + if (auto result = esphttpdutils::webserver_get_query(req)) + query = *result; + else + { + ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data()); + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error()); + } + + std::string body; + bool success{true}; + + stringSettings.executeForEveryCommonSetting([&](std::string_view key, auto &value){ + char valueBufEncoded[256]; + if (const auto result = httpd_query_key_value(query.data(), key.data(), valueBufEncoded, 256); result != ESP_OK && result != ESP_ERR_NOT_FOUND) + { + const auto msg = fmt::format("{}: httpd_query_key_value() failed with {}", key, esp_err_to_name(result)); + ESP_LOGE(TAG, "%.*s", msg.size(), msg.data()); + body += msg; + body += '\n'; + success = false; + return; + } + + char valueBuf[257]; + esphttpdutils::urldecode(valueBuf, valueBufEncoded); + + value = valueBuf; + body += fmt::format("{}: applied", key); + body += '\n'; + }); + + if (body.empty()) + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "nothing changed?!") + + if (settingsPersister.save(stringSettings)) + body += "string settings persisted successfully"; + else + { + body += "error while persisting string settings"; + success = false; + } + + if (success) + { + CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/stringSettings") + body += "\nOk, continue at /stringSettings"; + } + + CALL_AND_EXIT(esphttpdutils::webserver_resp_send, + req, + success ? esphttpdutils::ResponseStatus::TemporaryRedirect : esphttpdutils::ResponseStatus::BadRequest, + "text/plain", + body) +} +} // namespace + +#endif +