[ai] refactoring, fixed crash

This commit is contained in:
Stefan Ostermann 2025-11-02 00:22:02 +01:00
parent c32eabf464
commit 7f120ae62d
1 changed files with 66 additions and 159 deletions

View File

@ -693,12 +693,12 @@ String getState()
jsonState["playing"] = audio.isRunning();
if (currentNode != nullptr)
if (currentNode != nullptr && currentNode->getCurrentPlaying() != nullptr)
jsonState["title"] = *currentNode->getCurrentPlaying();
else
jsonState["title"] = "Stopped";
if (currentNode != nullptr)
if (currentNode != nullptr && currentNode->getCurrentPlaying() != nullptr)
jsonState["filepath"] = currentNode->getCurrentPlayingFilePath();
else
jsonState["filepath"] = "";
@ -1105,191 +1105,98 @@ void readRFID()
lastInteraction = millis();
}
static void serveStaticFile(AsyncWebServerRequest *request,
const String &plainPath,
const String &gzPath,
const char *contentType,
const char *cacheControl,
const __FlashStringHelper *notFoundMsg,
bool allowGzip = true)
{
webreq_enter();
// Ensure SD is active and RFID is deactivated while serving files.
deactivateRFID();
activateSD();
// Prefer gz if present
bool useGz = allowGzip && SD.exists(gzPath);
const String &sendPath = useGz ? gzPath : plainPath;
if (SD.exists(sendPath))
{
#ifdef DEBUG
Serial.printf("Serving %s heap=%u webreq_cnt=%u\n", sendPath.c_str(), (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
#endif
// Chunked streaming with short SD lock per read
struct FileCtx { File f; };
FileCtx *ctx = new FileCtx();
ctx->f = SD.open(sendPath);
if (!ctx->f) { delete ctx; request->send(500, txt_plain, F("Open failed")); webreq_exit(); return; }
auto resp = request->beginChunkedResponse(contentType,
[ctx](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
size_t toRead = maxLen;
if (toRead > 512) toRead = 512;
sd_lock_acquire();
size_t n = ctx->f.read(buffer, toRead);
sd_lock_release();
if (n == 0) { ctx->f.close(); }
return n;
});
resp->addHeader(hdr_cache_control_key, cacheControl);
resp->addHeader(hdr_connection_key, hdr_connection_val);
if (useGz) {
resp->addHeader(F("Content-Encoding"), F("gzip"));
}
// Ensure FileCtx cleanup even on aborted connections
request->onDisconnect([ctx](){
sd_lock_acquire();
if (ctx->f) ctx->f.close();
sd_lock_release();
delete ctx;
webreq_exit();
});
request->send(resp);
}
else
{
// Fallback: 404 for missing asset
request->send(404, txt_plain, notFoundMsg);
webreq_exit();
}
}
void init_webserver() {
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{
webreq_enter();
/* onDisconnect set later with cleanup */
deactivateRFID();
activateSD();
static String htmlPath = "";
static String htmlPathGz = "";
if (htmlPath.isEmpty()) {
htmlPath = getSysDir(index_file);
htmlPathGz = htmlPath + F(".gz");
}
// Prefer gz if present
bool useGz = SD.exists(htmlPathGz);
const String &sendPath = useGz ? htmlPathGz : htmlPath;
if (SD.exists(sendPath))
{
#ifdef DEBUG
Serial.printf("Serving %s heap=%u webreq_cnt=%u\n", sendPath.c_str(), (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
#endif
// Chunked streaming with short SD lock per read
struct FileCtx { File f; };
FileCtx* ctx = new FileCtx();
ctx->f = SD.open(sendPath);
if (!ctx->f) { delete ctx; request->send(500, txt_plain, F("Open failed")); return; }
auto response = request->beginChunkedResponse(String(txt_html_charset),
[ctx](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
size_t toRead = maxLen;
if (toRead > 512) toRead = 512;
sd_lock_acquire();
size_t n = ctx->f.read(buffer, toRead);
sd_lock_release();
if (n == 0) { ctx->f.close(); }
return n;
});
response->addHeader(hdr_cache_control_key, hdr_cache_control_val);
response->addHeader(hdr_connection_key, hdr_connection_val);
if (useGz) {
response->addHeader(F("Content-Encoding"), F("gzip"));
}
// Ensure FileCtx cleanup even on aborted connections
request->onDisconnect([ctx](){
sd_lock_acquire();
if (ctx->f) ctx->f.close();
sd_lock_release();
delete ctx;
webreq_exit();
});
request->send(response);
}
else
{
// Fallback: serve minimal error if file not found
request->send(404, txt_plain, F("ERROR: /system/index.html(.gz) not found!"));
}
serveStaticFile(request, htmlPath, htmlPathGz, txt_html_charset, hdr_cache_control_val, F("ERROR: /system/index.html(.gz) not found!"), true);
});
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
{
webreq_enter();
/* onDisconnect set later with cleanup */
deactivateRFID();
activateSD();
// Ensure SD is active and RFID is deactivated while serving files.
static String cssPath = "";
static String cssPathGz = "";
if (cssPath.isEmpty()) {
cssPath = getSysDir(style_file);
cssPathGz = cssPath + F(".gz");
}
// Prefer gz if present
bool useGz = SD.exists(cssPathGz);
const String &sendPath = useGz ? cssPathGz : cssPath;
if (SD.exists(sendPath))
{
#ifdef DEBUG
Serial.printf("Serving %s heap=%u webreq_cnt=%u\n", sendPath.c_str(), (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
#endif
{
// Chunked streaming with short SD lock per read
struct FileCtx { File f; };
FileCtx* ctx = new FileCtx();
ctx->f = SD.open(sendPath);
if (!ctx->f) { delete ctx; request->send(500, txt_plain, F("Open failed")); return; }
auto resp = request->beginChunkedResponse(F("text/css"),
[ctx](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
size_t toRead = maxLen;
if (toRead > 512) toRead = 512;
sd_lock_acquire();
size_t n = ctx->f.read(buffer, toRead);
sd_lock_release();
if (n == 0) { ctx->f.close(); }
return n;
});
resp->addHeader(hdr_cache_control_key, F("public, max-age=300"));
resp->addHeader(hdr_connection_key, hdr_connection_val);
if (useGz) {
resp->addHeader(F("Content-Encoding"), F("gzip"));
}
// Ensure FileCtx cleanup even on aborted connections
request->onDisconnect([ctx](){
sd_lock_acquire();
if (ctx->f) ctx->f.close();
sd_lock_release();
delete ctx;
webreq_exit();
});
request->send(resp);
}
}
else
{
// Fallback: serve minimal CSS if file not found
request->send(404, txt_plain, F("ERROR: /system/style.css(.gz) not found!"));
}
serveStaticFile(request, cssPath, cssPathGz, "text/css", "public, max-age=300", F("ERROR: /system/style.css(.gz) not found!"), true);
});
server.on("/script.js", HTTP_GET, [](AsyncWebServerRequest *request)
{
webreq_enter();
/* onDisconnect set later with cleanup */
deactivateRFID();
activateSD();
static String jsPath = "";
static String jsPathGz = "";
if (jsPath.isEmpty()) {
jsPath = getSysDir(script_file);
jsPathGz = jsPath + F(".gz");
}
// Prefer gz if present
bool useGz = SD.exists(jsPathGz);
const String &sendPath = useGz ? jsPathGz : jsPath;
if (SD.exists(sendPath))
{
#ifdef DEBUG
Serial.printf("Serving %s heap=%u webreq_cnt=%u\n", sendPath.c_str(), (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
#endif
{
// Chunked streaming with short SD lock per read
struct FileCtx { File f; };
FileCtx* ctx = new FileCtx();
ctx->f = SD.open(sendPath);
if (!ctx->f) { delete ctx; request->send(500, txt_plain, F("Open failed")); return; }
auto resp = request->beginChunkedResponse(F("application/javascript"),
[ctx](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
size_t toRead = maxLen;
if (toRead > 512) toRead = 512;
sd_lock_acquire();
size_t n = ctx->f.read(buffer, toRead);
sd_lock_release();
if (n == 0) { ctx->f.close(); }
return n;
});
resp->addHeader(hdr_cache_control_key, F("public, max-age=300"));
resp->addHeader(hdr_connection_key, hdr_connection_val);
if (useGz) {
resp->addHeader(F("Content-Encoding"), F("gzip"));
}
// Ensure FileCtx cleanup even on aborted connections
request->onDisconnect([ctx](){
sd_lock_acquire();
if (ctx->f) ctx->f.close();
sd_lock_release();
delete ctx;
webreq_exit();
});
request->send(resp);
}
}
else
{
// Fallback: serve minimal JS if file not found
request->send(404, txt_plain, F("ERROR: /system/script.js(.gz) not found!"));
}
serveStaticFile(request, jsPath, jsPathGz, "application/javascript", "public, max-age=300", F("ERROR: /system/script.js(.gz) not found!"), true);
});
// Dynamic endpoints to avoid template processing heap spikes