rebase master

This commit is contained in:
pablohc
2026-03-20 15:27:03 +01:00
parent 53beeeed2b
commit a2ba5dba5c
5 changed files with 3788 additions and 70 deletions
+32 -13
View File
@@ -32,21 +32,41 @@ def minify_html(html: str) -> str:
return html.strip()
def sanitize_identifier(name: str) -> str:
"""Sanitize a filename to create a valid C identifier.
C identifiers must:
- Start with a letter or underscore
- Contain only letters, digits, and underscores
"""
# Replace non-alphanumeric characters (including hyphens) with underscores
sanitized = re.sub(r'[^a-zA-Z0-9_]', '_', name)
# Prefix with underscore if starts with a digit
if sanitized and sanitized[0].isdigit():
sanitized = f"_{sanitized}"
return sanitized
for root, _, files in os.walk(SRC_DIR):
for file in files:
if file.endswith(".html"):
html_path = os.path.join(root, file)
with open(html_path, "r", encoding="utf-8") as f:
html_content = f.read()
if file.endswith(".html") or file.endswith(".js"):
file_path = os.path.join(root, file)
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
# minified = regex.sub("\g<1>", html_content)
minified = minify_html(html_content)
# Only minify HTML files; JS files are typically pre-minified (e.g., jszip.min.js)
if file.endswith(".html"):
processed = minify_html(content)
else:
processed = content
# Compress with gzip (compresslevel 9 is maximum compression)
# IMPORTANT: we don't use brotli because Firefox doesn't support brotli with insecured context (only supported on HTTPS)
compressed = gzip.compress(minified.encode('utf-8'), compresslevel=9)
compressed = gzip.compress(processed.encode('utf-8'), compresslevel=9)
base_name = f"{os.path.splitext(file)[0]}Html"
# Create valid C identifier from filename
# Use appropriate suffix based on file type
suffix = "Html" if file.endswith(".html") else "Js"
base_name = sanitize_identifier(f"{os.path.splitext(file)[0]}{suffix}")
header_path = os.path.join(root, f"{base_name}.generated.h")
with open(header_path, "w", encoding="utf-8") as h:
@@ -65,10 +85,9 @@ for root, _, files in os.walk(SRC_DIR):
h.write(f"}};\n\n")
h.write(f"constexpr size_t {base_name}CompressedSize = {len(compressed)};\n")
h.write(f"constexpr size_t {base_name}OriginalSize = {len(minified)};\n")
h.write(f"constexpr size_t {base_name}OriginalSize = {len(processed)};\n")
print(f"Generated: {header_path}")
print(f" Original: {len(html_content)} bytes")
print(f" Minified: {len(minified)} bytes ({100*len(minified)/len(html_content):.1f}%)")
print(f" Compressed: {len(compressed)} bytes ({100*len(compressed)/len(html_content):.1f}%)")
print(f" Original: {len(content)} bytes")
print(f" Minified: {len(processed)} bytes ({100*len(processed)/len(content):.1f}%)")
print(f" Compressed: {len(compressed)} bytes ({100*len(compressed)/len(content):.1f}%)")
+40 -11
View File
@@ -16,6 +16,7 @@
#include "html/FilesPageHtml.generated.h"
#include "html/HomePageHtml.generated.h"
#include "html/SettingsPageHtml.generated.h"
#include "html/js/jszip_minJs.generated.h"
namespace {
// Folders/files to hide from the web interface file browser
@@ -36,6 +37,7 @@ size_t wsUploadSize = 0;
size_t wsUploadReceived = 0;
unsigned long wsUploadStartTime = 0;
bool wsUploadInProgress = false;
uint8_t wsUploadClientNum = 255; // 255 = no active upload client
String wsLastCompleteName;
size_t wsLastCompleteSize = 0;
unsigned long wsLastCompleteAt = 0;
@@ -43,7 +45,7 @@ unsigned long wsLastCompleteAt = 0;
// Helper function to clear epub cache after upload
void clearEpubCacheIfNeeded(const String& filePath) {
// Only clear cache for .epub files
if (FsHelpers::hasEpubExtension(filePath)) {
if (FsHelpers::checkFileExtension(filePath, ".epub")) {
Epub(filePath.c_str(), "/.crosspoint").clearCache();
LOG_DBG("WEB", "Cleared epub cache for: %s", filePath.c_str());
}
@@ -131,6 +133,7 @@ void CrossPointWebServer::begin() {
LOG_DBG("WEB", "Setting up routes...");
server->on("/", HTTP_GET, [this] { handleRoot(); });
server->on("/files", HTTP_GET, [this] { handleFileList(); });
server->on("/js/jszip.min.js", HTTP_GET, [this] { handleJszip(); });
server->on("/api/status", HTTP_GET, [this] { handleStatus(); });
server->on("/api/files", HTTP_GET, [this] { handleFileListData(); });
@@ -203,6 +206,7 @@ void CrossPointWebServer::stop() {
if (wsUploadInProgress && wsUploadFile) {
wsUploadFile.close();
wsUploadInProgress = false;
wsUploadClientNum = 255;
}
// Stop WebSocket server
@@ -309,6 +313,12 @@ void CrossPointWebServer::handleRoot() const {
LOG_DBG("WEB", "Served root page");
}
void CrossPointWebServer::handleJszip() const {
server->sendHeader("Content-Encoding", "gzip");
server->send_P(200, "application/javascript", jszip_minJs, jszip_minJsCompressedSize);
LOG_DBG("WEB", "Served jszip.min.js");
}
void CrossPointWebServer::handleNotFound() const {
String message = "404 Not Found\n\n";
message += "URI: " + server->uri() + "\n";
@@ -390,7 +400,11 @@ void CrossPointWebServer::scanFiles(const char* path, const std::function<void(F
root.close();
}
bool CrossPointWebServer::isEpubFile(const String& filename) const { return FsHelpers::hasEpubExtension(filename); }
bool CrossPointWebServer::isEpubFile(const String& filename) const {
String lower = filename;
lower.toLowerCase();
return lower.endsWith(".epub");
}
void CrossPointWebServer::handleFileList() const {
sendHtmlContent(server.get(), FilesPageHtml, sizeof(FilesPageHtml));
@@ -505,7 +519,16 @@ void CrossPointWebServer::handleDownload() const {
server->send(200, contentType.c_str(), "");
NetworkClient client = server->client();
client.write(file);
const size_t chunkSize = 4096;
uint8_t buffer[chunkSize];
while (file.available()) {
size_t bytesRead = file.read(buffer, chunkSize);
if (bytesRead > 0) {
client.write(buffer, bytesRead);
}
}
client.clear();
file.close();
}
@@ -1028,7 +1051,7 @@ void CrossPointWebServer::handleSettingsPage() const {
}
void CrossPointWebServer::handleGetSettings() const {
const auto& settings = getSettingsList();
auto settings = getSettingsList();
server->setContentLength(CONTENT_LENGTH_UNKNOWN);
server->send(200, "application/json", "");
@@ -1082,7 +1105,7 @@ void CrossPointWebServer::handleGetSettings() const {
doc["type"] = "string";
if (s.stringGetter) {
doc["value"] = s.stringGetter();
} else if (s.stringOffset > 0) {
} else if (s.stringOffset && s.stringMaxLen > 0) {
doc["value"] = reinterpret_cast<const char*>(&SETTINGS) + s.stringOffset;
}
break;
@@ -1124,10 +1147,10 @@ void CrossPointWebServer::handlePostSettings() {
return;
}
const auto& settings = getSettingsList();
auto settings = getSettingsList();
int applied = 0;
for (const auto& s : settings) {
for (auto& s : settings) {
if (!s.key) continue;
if (!doc[s.key].is<JsonVariant>()) continue;
@@ -1166,7 +1189,7 @@ void CrossPointWebServer::handlePostSettings() {
const std::string val = doc[s.key].as<std::string>();
if (s.stringSetter) {
s.stringSetter(val);
} else if (s.stringOffset > 0 && s.stringMaxLen > 0) {
} else if (s.stringOffset && s.stringMaxLen > 0) {
char* ptr = reinterpret_cast<char*>(&SETTINGS) + s.stringOffset;
strncpy(ptr, val.c_str(), s.stringMaxLen - 1);
ptr[s.stringMaxLen - 1] = '\0';
@@ -1202,8 +1225,10 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
switch (type) {
case WStype_DISCONNECTED:
LOG_DBG("WS", "Client %u disconnected", num);
// Clean up any in-progress upload
if (wsUploadInProgress && wsUploadFile) {
// Only clean up if this is the client that owns the active upload.
// A new client may have already started a fresh upload before this
// DISCONNECTED event fires (race condition on quick cancel + retry).
if (num == wsUploadClientNum && wsUploadInProgress && wsUploadFile) {
wsUploadFile.close();
// Delete incomplete file
String filePath = wsUploadPath;
@@ -1211,8 +1236,9 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
filePath += wsUploadFileName;
Storage.remove(filePath.c_str());
LOG_DBG("WS", "Deleted incomplete upload: %s", filePath.c_str());
wsUploadInProgress = false;
}
wsUploadInProgress = false;
wsUploadClientNum = 255;
break;
case WStype_CONNECTED: {
@@ -1262,10 +1288,12 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
if (!Storage.openFileForWrite("WS", filePath, wsUploadFile)) {
wsServer->sendTXT(num, "ERROR:Failed to create file");
wsUploadInProgress = false;
wsUploadClientNum = 255;
return;
}
esp_task_wdt_reset();
wsUploadClientNum = num;
wsUploadInProgress = true;
wsServer->sendTXT(num, "READY");
} else {
@@ -1307,6 +1335,7 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
if (wsUploadReceived >= wsUploadSize) {
wsUploadFile.close();
wsUploadInProgress = false;
wsUploadClientNum = 255;
wsLastCompleteName = wsUploadFileName;
wsLastCompleteSize = wsUploadSize;
+1
View File
@@ -89,6 +89,7 @@ class CrossPointWebServer {
// Request handlers
void handleRoot() const;
void handleJszip() const;
void handleNotFound() const;
void handleStatus() const;
void handleFileList() const;
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long