fixes webserver uploads and general stability
This commit is contained in:
parent
c171813045
commit
bab374a675
@ -52,6 +52,13 @@ void RecentBooksStore::clearAll() {
|
|||||||
Serial.printf("[%lu] [RBS] Cleared all recent books\n", millis());
|
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 {
|
bool RecentBooksStore::saveToFile() const {
|
||||||
// Make sure the directory exists
|
// Make sure the directory exists
|
||||||
SdMan.mkdir("/.crosspoint");
|
SdMan.mkdir("/.crosspoint");
|
||||||
|
|||||||
@ -29,9 +29,14 @@ class RecentBooksStore {
|
|||||||
// Returns true if the book was found and removed
|
// Returns true if the book was found and removed
|
||||||
bool removeBook(const std::string& path);
|
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();
|
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)
|
// Get the list of recent books (most recent first)
|
||||||
const std::vector<RecentBook>& getBooks() const { return recentBooks; }
|
const std::vector<RecentBook>& getBooks() const { return recentBooks; }
|
||||||
|
|
||||||
|
|||||||
27
src/main.cpp
27
src/main.cpp
@ -381,6 +381,15 @@ void onGoToListsOrPinned() {
|
|||||||
|
|
||||||
void onGoToFileTransfer() {
|
void onGoToFileTransfer() {
|
||||||
exitActivity();
|
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));
|
enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,12 +405,24 @@ void onGoToClearCache() {
|
|||||||
|
|
||||||
void onGoToMyLibrary() {
|
void onGoToMyLibrary() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
|
|
||||||
|
// Reload recent books if they were cleared (e.g., when exiting File Transfer mode)
|
||||||
|
if (RECENT_BOOKS.getCount() == 0) {
|
||||||
|
RECENT_BOOKS.loadFromFile();
|
||||||
|
}
|
||||||
|
|
||||||
enterNewActivity(
|
enterNewActivity(
|
||||||
new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader, onGoToListView, onGoToBookmarkList));
|
new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader, onGoToListView, onGoToBookmarkList));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoToMyLibraryWithTab(const std::string& path, MyLibraryActivity::Tab tab) {
|
void onGoToMyLibraryWithTab(const std::string& path, MyLibraryActivity::Tab tab) {
|
||||||
exitActivity();
|
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,
|
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader, onGoToListView,
|
||||||
onGoToBookmarkList, tab, path));
|
onGoToBookmarkList, tab, path));
|
||||||
}
|
}
|
||||||
@ -413,6 +434,12 @@ void onGoToBrowser() {
|
|||||||
|
|
||||||
void onGoHome() {
|
void onGoHome() {
|
||||||
exitActivity();
|
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,
|
enterNewActivity(new HomeActivity(renderer, mappedInputManager, onContinueReading, onGoToListsOrPinned,
|
||||||
onGoToMyLibrary, onGoToSettings, onGoToFileTransfer, onGoToBrowser));
|
onGoToMyLibrary, onGoToSettings, onGoToFileTransfer, onGoToBrowser));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -441,17 +441,33 @@ void CrossPointWebServer::handleFileListData() const {
|
|||||||
showHidden = server->arg("showHidden") == "true";
|
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->setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||||
server->send(200, "application/json", "");
|
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];
|
char output[512];
|
||||||
constexpr size_t outputSize = sizeof(output);
|
constexpr size_t outputSize = sizeof(output);
|
||||||
bool seenFirst = false;
|
bool seenFirst = false;
|
||||||
|
bool clientDisconnected = false;
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
|
|
||||||
scanFiles(
|
scanFiles(
|
||||||
currentPath.c_str(),
|
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.clear();
|
||||||
doc["name"] = info.name;
|
doc["name"] = info.name;
|
||||||
doc["size"] = info.size;
|
doc["size"] = info.size;
|
||||||
@ -475,18 +491,33 @@ void CrossPointWebServer::handleFileListData() const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send comma separator before all entries except the first
|
||||||
if (seenFirst) {
|
if (seenFirst) {
|
||||||
server->sendContent(",");
|
if (!sendContentSafe(",")) {
|
||||||
|
clientDisconnected = true;
|
||||||
|
Serial.printf("[%lu] [WEB] Client disconnected during file list\n", millis());
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
seenFirst = true;
|
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);
|
showHidden);
|
||||||
server->sendContent("]");
|
|
||||||
|
// Only send closing bracket if client is still connected
|
||||||
|
if (!clientDisconnected) {
|
||||||
|
sendContentSafe("]");
|
||||||
// End of streamed response, empty chunk to signal client
|
// End of streamed response, empty chunk to signal client
|
||||||
server->sendContent("");
|
server->sendContent("");
|
||||||
Serial.printf("[%lu] [WEB] Served file listing page for path: %s\n", millis(), currentPath.c_str());
|
Serial.printf("[%lu] [WEB] Served file listing page for path: %s\n", millis(), currentPath.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static variables for upload handling
|
// 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 {
|
bool CrossPointWebServer::copyFile(const String& srcPath, const String& destPath) const {
|
||||||
FsFile srcFile;
|
FsFile srcFile;
|
||||||
FsFile destFile;
|
FsFile destFile;
|
||||||
|
|||||||
@ -108,6 +108,11 @@ class CrossPointWebServer {
|
|||||||
bool copyFile(const String& srcPath, const String& destPath) const;
|
bool copyFile(const String& srcPath, const String& destPath) const;
|
||||||
bool copyFolder(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
|
// List management handlers
|
||||||
void handleListGet() const;
|
void handleListGet() const;
|
||||||
void handleListPost() const;
|
void handleListPost() const;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user