diff --git a/Grbl_Esp32/src/WebUI/JSONEncoder.cpp b/Grbl_Esp32/src/WebUI/JSONEncoder.cpp index 77a539a6d..eeee80118 100644 --- a/Grbl_Esp32/src/WebUI/JSONEncoder.cpp +++ b/Grbl_Esp32/src/WebUI/JSONEncoder.cpp @@ -154,4 +154,20 @@ namespace WebUI { member("S", max); member("M", min); } + + void JSONencoder::filesystemStats(uint64_t total, uint64_t used) { + member("total", total ? ESPResponseStream::formatBytes(total) : "-1"); + member("used", ESPResponseStream::formatBytes(used + 1)); + int occupied_percent; + if (total) { + occupied_percent = (used * 100) / total; + //minimum if even one byte is used is 1% + if (occupied_percent <= 1) { + occupied_percent = 1; + } + } else { + occupied_percent = -1; + } + member("occupation", occupied_percent); + } } diff --git a/Grbl_Esp32/src/WebUI/JSONEncoder.h b/Grbl_Esp32/src/WebUI/JSONEncoder.h index 03140015c..746723f4e 100644 --- a/Grbl_Esp32/src/WebUI/JSONEncoder.h +++ b/Grbl_Esp32/src/WebUI/JSONEncoder.h @@ -78,5 +78,7 @@ namespace WebUI { void begin_webui(const char* p, const char* help, const char* type, const char* val); void begin_webui(const char* p, const char* help, const char* type, const int val); void begin_webui(const char* p, const char* help, const char* type, const char* val, int min, int max); + + void filesystemStats(uint64_t total, uint64_t used); }; } diff --git a/Grbl_Esp32/src/WebUI/WebServer.cpp b/Grbl_Esp32/src/WebUI/WebServer.cpp index b4a1cef55..983c6b396 100644 --- a/Grbl_Esp32/src/WebUI/WebServer.cpp +++ b/Grbl_Esp32/src/WebUI/WebServer.cpp @@ -101,6 +101,20 @@ namespace WebUI { long Web_Server::get_client_ID() { return _id_connection; } + void Web_Server::dontCache() { _webserver->sendHeader("Cache-Control", "no-cache"); } + void Web_Server::sendJSON(int code, JSONencoder j) { _webserver->send(code, "application/json", j.end()); } + void Web_Server::sendStatus(int code, const char* status, const char* auth, const char* user) { + JSONencoder j(false); + j.begin(); + j.member("status", status); + if (auth) { + j.member("authentication_lvl", auth); + } + if (user) { + j.member("user", user); + } + sendJSON(code, j); + } bool Web_Server::begin() { bool no_error = true; _setupdone = false; @@ -484,16 +498,17 @@ namespace WebUI { } else { espresponse->flush(); } - if(espresponse) delete(espresponse); + if (espresponse) + delete (espresponse); } else { //execute GCODE if (auth_level == AuthenticationLevel::LEVEL_GUEST) { _webserver->send(401, "text/plain", "Authentication failed!\n"); return; } //Instead of send several commands one by one by web / send full set and split here - String scmd; - bool hasError =false; - uint8_t sindex = 0; + String scmd; + bool hasError = false; + uint8_t sindex = 0; // TODO Settings - this is very inefficient. get_Splited_Value() is O(n^2) // when it could easily be O(n). Also, it would be just as easy to push // the entire string into Serial2Socket and pull off lines from there. @@ -516,16 +531,15 @@ namespace WebUI { hasError = true; } } - _webserver->send(200, "text/plain", hasError?"Error":""); + _webserver->send(200, "text/plain", hasError ? "Error" : ""); } } //login status check void Web_Server::handle_login() { # ifdef ENABLE_AUTHENTICATION - String smsg; + char * smsg, auths; String sUser, sPassword; - String auths; int code = 200; bool msg_alert_error = false; //disconnect can be done anytime no need to check credential @@ -539,22 +553,25 @@ namespace WebUI { } ClearAuthIP(_webserver->client().remoteIP(), sessionID.c_str()); _webserver->sendHeader("Set-Cookie", "ESPSESSIONID=0"); - _webserver->sendHeader("Cache-Control", "no-cache"); - String buffer2send = "{\"status\":\"Ok\",\"authentication_lvl\":\"guest\"}"; - _webserver->send(code, "application/json", buffer2send); + dontCache(); + sendStatus(code, "Ok", "guest", NULL); //_webserver->client().stop(); return; } AuthenticationLevel auth_level = is_authenticated(); - if (auth_level == AuthenticationLevel::LEVEL_GUEST) { - auths = "guest"; - } else if (auth_level == AuthenticationLevel::LEVEL_USER) { - auths = "user"; - } else if (auth_level == AuthenticationLevel::LEVEL_ADMIN) { - auths = "admin"; - } else { - auths = "???"; + switch (auth_level) { + case AuthenticationLevel::LEVEL_GUEST: + auths = "guest"; + break; + case AuthenticationLevel::LEVEL_USER: + auths = "user"; + break; + case AuthenticationLevel::LEVEL_ADMIN: + auths = "admin"; + break; + default: + auths = "???"; } //check is it is a submission or a query @@ -635,7 +652,7 @@ namespace WebUI { String tmps = "ESPSESSIONID="; tmps += current_auth->sessionID; _webserver->sendHeader("Set-Cookie", tmps); - _webserver->sendHeader("Cache-Control", "no-cache"); + dontCache(); switch (current_auth->level) { case AuthenticationLevel::LEVEL_ADMIN: auths = "admin"; @@ -659,11 +676,7 @@ namespace WebUI { smsg = "Ok"; } - //build JSON - String buffer2send = "{\"status\":\"" + smsg + "\",\"authentication_lvl\":\""; - buffer2send += auths; - buffer2send += "\"}"; - _webserver->send(code, "application/json", buffer2send); + sendStatus(code, smsg, auths, NULL); } else { if (auth_level != AuthenticationLevel::LEVEL_GUEST) { String cookie = _webserver->header("Cookie"); @@ -678,16 +691,11 @@ namespace WebUI { } } } - String buffer2send = "{\"status\":\"200\",\"authentication_lvl\":\""; - buffer2send += auths; - buffer2send += "\",\"user\":\""; - buffer2send += sUser; - buffer2send += "\"}"; - _webserver->send(code, "application/json", buffer2send); + sendStatus(code, "200", auths, sUser.c_str()); } # else - _webserver->sendHeader("Cache-Control", "no-cache"); - _webserver->send(200, "application/json", "{\"status\":\"Ok\",\"authentication_lvl\":\"admin\"}"); + dontCache(); + sendStatus(200, "Ok", "admin", NULL); # endif } //SPIFFS @@ -814,15 +822,15 @@ namespace WebUI { } } - String jsonfile = "{"; - String ptmp = path; + JSONencoder j(false); + j.begin(); + String ptmp = path; if ((path != "/") && (path[path.length() - 1] = '/')) { ptmp = path.substring(0, path.length() - 1); } File dir = SPIFFS.open(ptmp); - jsonfile += "\"files\":["; - bool firstentry = true; + j.begin_array("files"); String subdirlist = ""; File fileparsed = dir.openNextFile(); while (fileparsed) { @@ -857,37 +865,19 @@ namespace WebUI { } } if (addtolist) { - if (!firstentry) { - jsonfile += ","; - } else { - firstentry = false; - } - jsonfile += "{"; - jsonfile += "\"name\":\""; - jsonfile += filename; - jsonfile += "\",\"size\":\""; - jsonfile += size; - jsonfile += "\""; - jsonfile += "}"; + j.begin_object(); + j.member("name", filename); + j.member("size", size); + j.end_object(); } fileparsed = dir.openNextFile(); } - jsonfile += "],"; - jsonfile += "\"path\":\"" + path + "\","; - jsonfile += "\"status\":\"" + status + "\","; - size_t totalBytes; - size_t usedBytes; - totalBytes = SPIFFS.totalBytes(); - usedBytes = SPIFFS.usedBytes(); - jsonfile += "\"total\":\"" + ESPResponseStream::formatBytes(totalBytes) + "\","; - jsonfile += "\"used\":\"" + ESPResponseStream::formatBytes(usedBytes) + "\","; - jsonfile.concat(F("\"occupation\":\"")); - jsonfile += String(100 * usedBytes / totalBytes); - jsonfile += "\""; - jsonfile += "}"; - path = ""; - _webserver->sendHeader("Cache-Control", "no-cache"); - _webserver->send(200, "application/json", jsonfile); + j.end_array(); + j.member("path", path); + j.member("status", status); + j.filesystemStats(uint64_t(SPIFFS.totalBytes()), uint64_t(SPIFFS.usedBytes())); + dontCache(); + sendJSON(200, j); _upload_status = UploadStatusType::NONE; } @@ -1058,13 +1048,8 @@ namespace WebUI { return; } - String jsonfile = "{\"status\":\""; - jsonfile += String(int32_t(uint8_t(_upload_status))); - jsonfile += "\"}"; - - //send status - _webserver->sendHeader("Cache-Control", "no-cache"); - _webserver->send(200, "application/json", jsonfile); + dontCache(); + sendStatus(200, String(int32_t(uint8_t(_upload_status))).c_str(), NULL, NULL); //if success restart if (_upload_status == UploadStatusType::SUCCESSFUL) { @@ -1218,7 +1203,7 @@ namespace WebUI { //this is only for admin and user if (is_authenticated() == AuthenticationLevel::LEVEL_GUEST) { _upload_status = UploadStatusType::NONE; - _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); + sendStatus(401, "Authentication failed!", NULL, NULL); return; } @@ -1228,15 +1213,10 @@ namespace WebUI { sstatus = "Upload failed"; _upload_status = UploadStatusType::NONE; } - bool list_files = true; - uint64_t totalspace = 0; - uint64_t usedspace = 0; - SDState state = get_sd_state(true); + SDState state = get_sd_state(true); if (state != SDState::Idle) { - String status = "{\"status\":\""; - status += state == SDState::NotPresent ? "No SD Card\"}" : "Busy\"}"; - _webserver->sendHeader("Cache-Control", "no-cache"); - _webserver->send(200, "application/json", status); + dontCache(); + sendStatus(200, state == SDState::NotPresent ? "No SD Card" : "Busy", NULL, NULL); return; } set_sd_state(SDState::BusyParsing); @@ -1314,100 +1294,52 @@ namespace WebUI { } } } - //check if no need build file list - if (_webserver->hasArg("dontlist") && _webserver->arg("dontlist") == "yes") { - list_files = false; - } - // TODO Settings - consider using the JSONEncoder class - String jsonfile = "{"; - jsonfile += "\"files\":["; + JSONencoder j(false); + j.begin(); if (path != "/") { path = path.substring(0, path.length() - 1); } if (path != "/" && !SD.exists(path)) { - String s = "{\"status\":\" "; - s += path; - s += " does not exist on SD Card\"}"; - _webserver->send(200, "application/json", s); - SD.end(); - return; - } - if (list_files) { - File dir = SD.open(path); - if (!dir.isDirectory()) { - dir.close(); - } - dir.rewindDirectory(); - File entry = dir.openNextFile(); - int i = 0; - while (entry) { - COMMANDS::wait(1); - if (i > 0) { - jsonfile += ","; + j.member("status", path + " does not exist on SD Card"); + } else { + j.begin_array("files"); + if (!_webserver->hasArg("dontlist") || !(_webserver->arg("dontlist") == "yes")) { + File dir = SD.open(path); + if (!dir.isDirectory()) { + dir.close(); } - jsonfile += "{\"name\":\""; - String tmpname = entry.name(); - int pos = tmpname.lastIndexOf("/"); - tmpname = tmpname.substring(pos + 1); - jsonfile += tmpname; - jsonfile += "\",\"shortname\":\""; //No need here - jsonfile += tmpname; - jsonfile += "\",\"size\":\""; - if (entry.isDirectory()) { - jsonfile += "-1"; - } else { - // files have sizes, directories do not - jsonfile += ESPResponseStream::formatBytes(entry.size()); + dir.rewindDirectory(); + File entry = dir.openNextFile(); + while (entry) { + COMMANDS::wait(1); + j.begin_object(); + String tmpname = entry.name(); + int pos = tmpname.lastIndexOf("/"); + tmpname = tmpname.substring(pos + 1); + j.member("name", tmpname); + j.member("shortname", tmpname); + j.member("size", entry.isDirectory() ? "-1" : ESPResponseStream::formatBytes(entry.size())); + // Omit the timestamp because Grbl_ESP32 systems rarely have + // reliable knowledge of the current date and time, so timestamps + // on uploaded files are unlikely to be useful. + j.member("datetime", ""); + j.end_object(); + entry.close(); + entry = dir.openNextFile(); } - jsonfile += "\",\"datetime\":\""; - //TODO - can be done later - jsonfile += "\"}"; - i++; - entry.close(); - entry = dir.openNextFile(); + dir.close(); } - dir.close(); - } - jsonfile += "],\"path\":\""; - jsonfile += path + "\","; - jsonfile += "\"total\":\""; - String stotalspace, susedspace; - //SDCard are in GB or MB but no less - totalspace = SD.totalBytes(); - usedspace = SD.usedBytes(); - stotalspace = ESPResponseStream::formatBytes(totalspace); - susedspace = ESPResponseStream::formatBytes(usedspace + 1); - - uint32_t occupedspace = 1; - uint32_t usedspace2 = usedspace / (1024 * 1024); - uint32_t totalspace2 = totalspace / (1024 * 1024); - occupedspace = (usedspace2 * 100) / totalspace2; - //minimum if even one byte is used is 1% - if (occupedspace <= 1) { - occupedspace = 1; - } - if (totalspace) { - jsonfile += stotalspace; - } else { - jsonfile += "-1"; - } - jsonfile += "\",\"used\":\""; - jsonfile += susedspace; - jsonfile += "\",\"occupation\":\""; - if (totalspace) { - jsonfile += String(occupedspace); - } else { - jsonfile += "-1"; + j.end_array(); + + j.member("path", path); + j.member("status", sstatus); + j.filesystemStats(SD.totalBytes(), SD.usedBytes()); + j.member("mode", "direct"); } - jsonfile += "\","; - jsonfile += "\"mode\":\"direct\","; - jsonfile += "\"status\":\""; - jsonfile += sstatus + "\""; - jsonfile += "}"; - _webserver->sendHeader("Cache-Control", "no-cache"); - _webserver->send(200, "application/json", jsonfile); + dontCache(); + sendJSON(200, j); _upload_status = UploadStatusType::NONE; set_sd_state(SDState::Idle); SD.end(); @@ -1420,7 +1352,7 @@ namespace WebUI { //this is only for admin and user if (is_authenticated() == AuthenticationLevel::LEVEL_GUEST) { _upload_status = UploadStatusType::FAILED; - _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); + sendStatus(401, "Authentication failed!", NULL, NULL); pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); } else { //retrieve current file id diff --git a/Grbl_Esp32/src/WebUI/WebServer.h b/Grbl_Esp32/src/WebUI/WebServer.h index c4bd87451..87d8b465f 100644 --- a/Grbl_Esp32/src/WebUI/WebServer.h +++ b/Grbl_Esp32/src/WebUI/WebServer.h @@ -94,6 +94,9 @@ namespace WebUI { static void SDFile_direct_upload(); static bool deleteRecursive(String path); #endif + static void dontCache(); + static void sendJSON(int code, JSONencoder j); + static void sendStatus(int code, const char* status, const char* auth, const char* user); }; extern Web_Server web_server; diff --git a/Grbl_Esp32/src/WebUI/WebSettings.cpp b/Grbl_Esp32/src/WebUI/WebSettings.cpp index f2a312f23..975cfb918 100644 --- a/Grbl_Esp32/src/WebUI/WebSettings.cpp +++ b/Grbl_Esp32/src/WebUI/WebSettings.cpp @@ -869,9 +869,7 @@ namespace WebUI { j.begin_array("files"); listDirJSON(SPIFFS, "/", 4, &j); j.end_array(); - j.member("total", SPIFFS.totalBytes()); - j.member("used", SPIFFS.usedBytes()); - j.member("occupation", String(100 * SPIFFS.usedBytes() / SPIFFS.totalBytes())); + j.filesystemStats(SPIFFS.totalBytes(), SPIFFS.usedBytes()); webPrint(j.end()); if (espresponse->client() != CLIENT_WEBUI) { webPrintln("");