You've already forked crosspoint-reader
mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-04-29 10:26:52 -07:00
rebase master
This commit is contained in:
+32
-13
@@ -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}%)")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -89,6 +89,7 @@ class CrossPointWebServer {
|
||||
|
||||
// Request handlers
|
||||
void handleRoot() const;
|
||||
void handleJszip() const;
|
||||
void handleNotFound() const;
|
||||
void handleStatus() const;
|
||||
void handleFileList() const;
|
||||
|
||||
+3702
-46
File diff suppressed because it is too large
Load Diff
Vendored
+13
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user