fixes webserver uploads and general stability

This commit is contained in:
cottongin 2026-01-29 17:57:56 -05:00
parent c171813045
commit bab374a675
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
5 changed files with 118 additions and 9 deletions

View File

@ -52,6 +52,13 @@ void RecentBooksStore::clearAll() {
Serial.printf("[%lu] [RBS] Cleared all recent books\n", millis());
}
void RecentBooksStore::clearFromMemory() {
const size_t count = recentBooks.size();
recentBooks.clear();
recentBooks.shrink_to_fit(); // Actually free the vector capacity
Serial.printf("[%lu] [RBS] Cleared %d recent books from memory (not saved)\n", millis(), count);
}
bool RecentBooksStore::saveToFile() const {
// Make sure the directory exists
SdMan.mkdir("/.crosspoint");

View File

@ -29,9 +29,14 @@ class RecentBooksStore {
// Returns true if the book was found and removed
bool removeBook(const std::string& path);
// Clear all recent books from the list
// Clear all recent books from the list (and save to file)
void clearAll();
// Clear recent books from memory without saving to file
// Used to free memory when entering modes that don't need this data (e.g., File Transfer)
// Call loadFromFile() to restore the data when needed again
void clearFromMemory();
// Get the list of recent books (most recent first)
const std::vector<RecentBook>& getBooks() const { return recentBooks; }

View File

@ -381,6 +381,15 @@ void onGoToListsOrPinned() {
void onGoToFileTransfer() {
exitActivity();
// Free memory not needed during file transfer to maximize heap for webserver
RECENT_BOOKS.clearFromMemory();
APP_STATE.openBookTitle.clear();
APP_STATE.openBookTitle.shrink_to_fit();
APP_STATE.openBookAuthor.clear();
APP_STATE.openBookAuthor.shrink_to_fit();
Serial.printf("[%lu] [FT] Cleared non-essential memory before File Transfer\n", millis());
enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
}
@ -396,12 +405,24 @@ void onGoToClearCache() {
void onGoToMyLibrary() {
exitActivity();
// Reload recent books if they were cleared (e.g., when exiting File Transfer mode)
if (RECENT_BOOKS.getCount() == 0) {
RECENT_BOOKS.loadFromFile();
}
enterNewActivity(
new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader, onGoToListView, onGoToBookmarkList));
}
void onGoToMyLibraryWithTab(const std::string& path, MyLibraryActivity::Tab tab) {
exitActivity();
// Reload recent books if they were cleared (e.g., when exiting File Transfer mode)
if (RECENT_BOOKS.getCount() == 0) {
RECENT_BOOKS.loadFromFile();
}
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader, onGoToListView,
onGoToBookmarkList, tab, path));
}
@ -413,6 +434,12 @@ void onGoToBrowser() {
void onGoHome() {
exitActivity();
// Reload recent books if they were cleared (e.g., when exiting File Transfer mode)
if (RECENT_BOOKS.getCount() == 0) {
RECENT_BOOKS.loadFromFile();
}
enterNewActivity(new HomeActivity(renderer, mappedInputManager, onContinueReading, onGoToListsOrPinned,
onGoToMyLibrary, onGoToSettings, onGoToFileTransfer, onGoToBrowser));
}

View File

@ -441,17 +441,33 @@ void CrossPointWebServer::handleFileListData() const {
showHidden = server->arg("showHidden") == "true";
}
// Check client connection before starting
if (!server->client().connected()) {
Serial.printf("[%lu] [WEB] Client disconnected before file list could start\n", millis());
return;
}
server->setContentLength(CONTENT_LENGTH_UNKNOWN);
server->send(200, "application/json", "");
server->sendContent("[");
if (!sendContentSafe("[")) {
Serial.printf("[%lu] [WEB] Client disconnected at start of file list\n", millis());
return;
}
char output[512];
constexpr size_t outputSize = sizeof(output);
bool seenFirst = false;
bool clientDisconnected = false;
JsonDocument doc;
scanFiles(
currentPath.c_str(),
[this, &output, &doc, seenFirst](const FileInfo& info) mutable {
[this, &output, &doc, &seenFirst, &clientDisconnected](const FileInfo& info) mutable {
// Skip remaining files if client already disconnected
if (clientDisconnected) {
return;
}
doc.clear();
doc["name"] = info.name;
doc["size"] = info.size;
@ -475,18 +491,33 @@ void CrossPointWebServer::handleFileListData() const {
return;
}
// Send comma separator before all entries except the first
if (seenFirst) {
server->sendContent(",");
if (!sendContentSafe(",")) {
clientDisconnected = true;
Serial.printf("[%lu] [WEB] Client disconnected during file list\n", millis());
return;
}
} else {
seenFirst = true;
}
server->sendContent(output);
// Send the JSON entry with flow control
if (!sendContentSafe(output)) {
clientDisconnected = true;
Serial.printf("[%lu] [WEB] Client disconnected during file list\n", millis());
return;
}
},
showHidden);
server->sendContent("]");
// Only send closing bracket if client is still connected
if (!clientDisconnected) {
sendContentSafe("]");
// End of streamed response, empty chunk to signal client
server->sendContent("");
Serial.printf("[%lu] [WEB] Served file listing page for path: %s\n", millis(), currentPath.c_str());
}
}
// Static variables for upload handling
@ -1264,6 +1295,40 @@ void CrossPointWebServer::handleRename() const {
}
}
// Counter for flow control pacing
static uint8_t sendContentCounter = 0;
bool CrossPointWebServer::sendContentSafe(const char* content) const {
if (!server || !server->client().connected()) {
return false;
}
// Send the content
server->sendContent(content);
// Flow control: give TCP stack time to transmit data and drain the send buffer
// The ESP32 TCP buffer is limited and fills quickly when streaming many small chunks.
// We use progressive delays:
// - yield() after every send to allow WiFi processing
// - delay(5ms) every send to allow buffer draining
// - delay(50ms) every 10 sends to allow larger buffer flush
yield();
sendContentCounter++;
if (sendContentCounter >= 10) {
sendContentCounter = 0;
delay(50); // Longer pause every 10 sends for buffer catchup
} else {
delay(5); // Short pause each send
}
return server->client().connected();
}
bool CrossPointWebServer::sendContentSafe(const String& content) const {
return sendContentSafe(content.c_str());
}
bool CrossPointWebServer::copyFile(const String& srcPath, const String& destPath) const {
FsFile srcFile;
FsFile destFile;

View File

@ -108,6 +108,11 @@ class CrossPointWebServer {
bool copyFile(const String& srcPath, const String& destPath) const;
bool copyFolder(const String& srcPath, const String& destPath) const;
// Helper for safe content sending with flow control
// Returns false if client disconnected, true otherwise
bool sendContentSafe(const char* content) const;
bool sendContentSafe(const String& content) const;
// List management handlers
void handleListGet() const;
void handleListPost() const;