From 3e7b7e0ecfbdeb6fd03be9ea018bcf0f4938b675 Mon Sep 17 00:00:00 2001 From: Hieromon Ikasamo Date: Thu, 4 Jun 2020 17:33:41 +0900 Subject: [PATCH] Support LittleFS --- examples/Elements/Elements.ino | 39 +- examples/FSBrowser/FSBrowser.ino | 827 +++++++++----- examples/FSBrowser/README.md | 152 ++- examples/FSBrowser/data/edit.htm.gz | Bin 4116 -> 0 bytes examples/FSBrowser/data/edit/index.htm | 1128 ++++++++++++++++++++ examples/FSBrowser/data/graphs.js.gz | Bin 1971 -> 0 bytes examples/FSBrowser/data/index.htm | 103 +- examples/FSBrowser/data/pins.png | Bin 0 -> 55578 bytes examples/FSBrowser/extras/feathericons.png | Bin 0 -> 1558 bytes examples/FSBrowser/extras/index.htm.gz | Bin 0 -> 6261 bytes examples/FSBrowser/extras/index_htm.h | 529 +++++++++ examples/FSBrowser/extras/reduce_index.sh | 60 ++ examples/FileUpload/FileUpload.ino | 21 +- 13 files changed, 2489 insertions(+), 370 deletions(-) delete mode 100644 examples/FSBrowser/data/edit.htm.gz create mode 100644 examples/FSBrowser/data/edit/index.htm delete mode 100644 examples/FSBrowser/data/graphs.js.gz create mode 100644 examples/FSBrowser/data/pins.png create mode 100644 examples/FSBrowser/extras/feathericons.png create mode 100644 examples/FSBrowser/extras/index.htm.gz create mode 100644 examples/FSBrowser/extras/index_htm.h create mode 100644 examples/FSBrowser/extras/reduce_index.sh diff --git a/examples/Elements/Elements.ino b/examples/Elements/Elements.ino index 45e3d9f..9aba95d 100644 --- a/examples/Elements/Elements.ino +++ b/examples/Elements/Elements.ino @@ -20,10 +20,25 @@ using WebServerClass = ESP8266WebServer; #include using WebServerClass = WebServer; #endif -#include #include +/* + AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that + will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng + the valid file system. After including AutoConnect.h, the Sketch can determine + whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition. +*/ +#ifdef AUTOCONNECT_USE_SPIFFS +#include +FS& FlashFS = SPIFFS; +#else +#include +FS& FlashFS = LittleFS; +#endif + #define PARAM_FILE "/elements.json" +#define USERNAME "username_you_wish" // For HTTP authentication +#define PASSWORD "password_you_wish" // For HTTP authentication static const char PAGE_ELEMENTS[] PROGMEM = R"( { @@ -156,13 +171,13 @@ void setup() { // Since this handler only supports AutoConnectSubmit called the // Load, it uses the uri of the custom web page placed to // determine whether the Load was called me or not. - SPIFFS.begin(); - File param = SPIFFS.open(PARAM_FILE, "r"); + FlashFS.begin(); + File param = FlashFS.open(PARAM_FILE, "r"); if (param) { aux.loadElement(param, { "text", "check", "input", "radio", "select" } ); param.close(); } - SPIFFS.end(); + FlashFS.end(); } return String(); }); @@ -181,28 +196,32 @@ void setup() { aux["caption"].value = PARAM_FILE; #if defined(ARDUINO_ARCH_ESP8266) - SPIFFS.begin(); + FlashFS.begin(); #elif defined(ARDUINO_ARCH_ESP32) - SPIFFS.begin(true); + FlashFS.begin(true); #endif - File param = SPIFFS.open(PARAM_FILE, "w"); + File param = FlashFS.open(PARAM_FILE, "w"); if (param) { // Save as a loadable set for parameters. elementsAux.saveElement(param, { "text", "check", "input", "radio", "select" }); param.close(); // Read the saved elements again to display. - param = SPIFFS.open(PARAM_FILE, "r"); + param = FlashFS.open(PARAM_FILE, "r"); aux["echo"].value = param.readString(); param.close(); } else { - aux["echo"].value = "SPIFFS failed to open."; + aux["echo"].value = "Filesystem failed to open."; } - SPIFFS.end(); + FlashFS.end(); return String(); }); portal.join({ elementsAux, saveAux }); + config.auth = AC_AUTH_DIGEST; + config.authScope = AC_AUTHSCOPE_AUX; + config.username = USERNAME; + config.password = PASSWORD; config.ticker = true; portal.config(config); portal.begin(); diff --git a/examples/FSBrowser/FSBrowser.ino b/examples/FSBrowser/FSBrowser.ino index a8e13ed..d01e12b 100644 --- a/examples/FSBrowser/FSBrowser.ino +++ b/examples/FSBrowser/FSBrowser.ino @@ -1,355 +1,668 @@ -/* - FSWebServer - Example WebServer with SPIFFS backend for esp8266 +/* + FSBrowser - A web-based FileSystem Browser for ESP8266 filesystems + Copyright (c) 2015 Hristo Gochkov. All rights reserved. This file is part of the ESP8266WebServer library for Arduino environment. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - - upload the contents of the data folder with MkSPIFFS Tool ("ESP8266 Sketch Data Upload" in Tools menu in Arduino IDE) - or you can upload the contents of a folder if you CD in that folder and run the following command: - for file in `\ls -A1`; do curl -F "file=@$PWD/$file" esp8266fs.local/edit; done - - access the sample web page at http://esp8266fs.local - edit the page by going to http://esp8266fs.local/edit + + See readme.md for more information. */ + +//////////////////////////////// + +// Select the FileSystem by uncommenting one of the lines below + +//#define USE_SPIFFS +#define USE_LITTLEFS +//#define USE_SDFS + +// Uncomment the following line to embed a version of the web page in the code +// (program code will be larger, but no file will have to be written to the filesystem). +// Note: the source file "extras/index_htm.h" must have been generated by "extras/reduce_index.sh" + +//#define INCLUDE_FALLBACK_INDEX_HTM + +//////////////////////////////// + #if defined(ARDUINO_ARCH_ESP8266) #include -#include #include #include -#include #elif defined(ARDUINO_ARCH_ESP32) #include -#include #include #include -#include #endif -//Add a below line for AutoConnect. +#include +#include #include +#ifdef INCLUDE_FALLBACK_INDEX_HTM +#include "extras/index_htm.h" +#endif + +#if defined USE_SPIFFS +#include +const char* fsName = "SPIFFS"; +FS* fileSystem = &SPIFFS; +SPIFFSConfig fileSystemConfig = SPIFFSConfig(); +#elif defined USE_LITTLEFS +#include +const char* fsName = "LittleFS"; +FS* fileSystem = &LittleFS; +LittleFSConfig fileSystemConfig = LittleFSConfig(); +#elif defined USE_SDFS +#include +const char* fsName = "SDFS"; +FS* fileSystem = &SDFS; +SDFSConfig fileSystemConfig = SDFSConfig(); +// fileSystemConfig.setCSPin(chipSelectPin); +#else +#error Please select a filesystem first by uncommenting one of the "#define USE_xxx" lines at the beginning of the sketch. +#endif + + #define DBG_OUTPUT_PORT Serial -const char* ssid = "wifi-ssid"; -const char* password = "wifi-password"; -#if defined(ARDUINO_ARCH_ESP8266) -const char* host = "esp8266fs"; +// Exclude unnecessary declarations due to applying AutoConnect +// #ifndef STASSID +// #define STASSID "SHAP-G" +// #define STAPSK "A0309T0312#" +// #endif + +// const char* ssid = STASSID; +// const char* password = STAPSK; +const char* host = "fsbrowser"; ESP8266WebServer server(80); -#elif defined(ARDUINO_ARCH_ESP32) -const char* host = "esp32fs"; -WebServer server(80); -#endif -//Add a below line for AutoConnect. +static bool fsOK; +String unsupportedFiles = String(); + +File uploadFile; + +// Additional lines as the below to apply AutoConnect AutoConnect portal(server); AutoConnectConfig config; -//holds the current upload -File fsUploadFile; - -//format bytes -String formatBytes(size_t bytes){ - if (bytes < 1024) { - return String(bytes) + "B"; - } else if (bytes < (1024 * 1024)) { - return String(bytes / 1024.0) + "KB"; - } else if (bytes < (1024 * 1024 * 1024)) { - return String(bytes / 1024.0 / 1024.0) + "MB"; + +static const char TEXT_PLAIN[] PROGMEM = "text/plain"; +static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR"; +static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound"; + +//////////////////////////////// +// Utils to return HTTP codes, and determine content-type + +void replyOK() { + server.send(200, FPSTR(TEXT_PLAIN), ""); +} + +void replyOKWithMsg(String msg) { + server.send(200, FPSTR(TEXT_PLAIN), msg); +} + +void replyNotFound(String msg) { + server.send(404, FPSTR(TEXT_PLAIN), msg); +} + +void replyBadRequest(String msg) { + DBG_OUTPUT_PORT.println(msg); + server.send(400, FPSTR(TEXT_PLAIN), msg + "\r\n"); +} + +void replyServerError(String msg) { + DBG_OUTPUT_PORT.println(msg); + server.send(500, FPSTR(TEXT_PLAIN), msg + "\r\n"); +} + +#ifdef USE_SPIFFS +/* + Checks filename for character combinations that are not supported by FSBrowser (alhtough valid on SPIFFS). + Returns an empty String if supported, or detail of error(s) if unsupported +*/ +String checkForUnsupportedPath(String filename) { + String error = String(); + if (!filename.startsWith("/")) { + error += F("!NO_LEADING_SLASH! "); + } + if (filename.indexOf("//") != -1) { + error += F("!DOUBLE_SLASH! "); + } + if (filename.endsWith("/")) { + error += F("!TRAILING_SLASH! "); + } + return error; +} +#endif + + +//////////////////////////////// +// Request handlers + +/* + Return the FS type, status and size info +*/ +void handleStatus() { + DBG_OUTPUT_PORT.println("handleStatus"); + FSInfo fs_info; + String json; + json.reserve(128); + + json = "{\"type\":\""; + json += fsName; + json += "\", \"isOk\":"; + if (fsOK) { + fileSystem->info(fs_info); + json += F("\"true\", \"totalBytes\":\""); + json += fs_info.totalBytes; + json += F("\", \"usedBytes\":\""); + json += fs_info.usedBytes; + json += "\""; } else { - return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; + json += "\"false\""; } + json += F(",\"unsupportedFiles\":\""); + json += unsupportedFiles; + json += "\"}"; + + server.send(200, "application/json", json); } -String getContentType(String filename) { - if (server.hasArg("download")) { - return "application/octet-stream"; - } else if (filename.endsWith(".htm")) { - return "text/html"; - } else if (filename.endsWith(".html")) { - return "text/html"; - } else if (filename.endsWith(".css")) { - return "text/css"; - } else if (filename.endsWith(".js")) { - return "application/javascript"; - } else if (filename.endsWith(".png")) { - return "image/png"; - } else if (filename.endsWith(".gif")) { - return "image/gif"; - } else if (filename.endsWith(".jpg")) { - return "image/jpeg"; - } else if (filename.endsWith(".ico")) { - return "image/x-icon"; - } else if (filename.endsWith(".xml")) { - return "text/xml"; - } else if (filename.endsWith(".pdf")) { - return "application/x-pdf"; - } else if (filename.endsWith(".zip")) { - return "application/x-zip"; - } else if (filename.endsWith(".gz")) { - return "application/x-gzip"; - } - return "text/plain"; + +/* + Return the list of files in the directory specified by the "dir" query string parameter. + Also demonstrates the use of chuncked responses. +*/ +void handleFileList() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } + + if (!server.hasArg("dir")) { + return replyBadRequest(F("DIR ARG MISSING")); + } + + String path = server.arg("dir"); + if (path != "/" && !fileSystem->exists(path)) { + return replyBadRequest("BAD PATH"); + } + + DBG_OUTPUT_PORT.println(String("handleFileList: ") + path); + Dir dir = fileSystem->openDir(path); + path.clear(); + + // use HTTP/1.1 Chunked response to avoid building a huge temporary string + if (!server.chunkedResponseModeStart(200, "text/json")) { + server.send(505, F("text/html"), F("HTTP1.1 required")); + return; + } + + // use the same string for every line + String output; + output.reserve(64); + while (dir.next()) { +#ifdef USE_SPIFFS + String error = checkForUnsupportedPath(dir.fileName()); + if (error.length() > 0) { + DBG_OUTPUT_PORT.println(String("Ignoring ") + error + dir.fileName()); + continue; + } +#endif + if (output.length()) { + // send string from previous iteration + // as an HTTP chunk + server.sendContent(output); + output = ','; + } else { + output = '['; + } + + output += "{\"type\":\""; + if (dir.isDirectory()) { + output += "dir"; + } else { + output += F("file\",\"size\":\""); + output += dir.fileSize(); + } + + output += F("\",\"name\":\""); + // Always return names without leading "/" + if (dir.fileName()[0] == '/') { + output += &(dir.fileName()[1]); + } else { + output += dir.fileName(); + } + + output += "\"}"; + } + + // send last string + output += "]"; + server.sendContent(output); + server.chunkedResponseFinalize(); } + +/* + Read the given file from the filesystem and stream it back to the client +*/ bool handleFileRead(String path) { - DBG_OUTPUT_PORT.println("handleFileRead: " + path); + DBG_OUTPUT_PORT.println(String("handleFileRead: ") + path); + if (!fsOK) { + replyServerError(FPSTR(FS_INIT_ERROR)); + return true; + } + if (path.endsWith("/")) { path += "index.htm"; } - String contentType = getContentType(path); - String pathWithGz = path + ".gz"; - if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { - if (SPIFFS.exists(pathWithGz)) { - path += ".gz"; + + String contentType; + if (server.hasArg("download")) { + contentType = F("application/octet-stream"); + } else { + contentType = mime::getContentType(path); + } + + if (!fileSystem->exists(path)) { + // File not found, try gzip version + path = path + ".gz"; + } + if (fileSystem->exists(path)) { + File file = fileSystem->open(path, "r"); + if (server.streamFile(file, contentType) != file.size()) { + DBG_OUTPUT_PORT.println("Sent less data than expected!"); } - File file = SPIFFS.open(path, "r"); - server.streamFile(file, contentType); file.close(); return true; } + return false; } + +/* + As some FS (e.g. LittleFS) delete the parent folder when the last child has been removed, + return the path of the closest parent still existing +*/ +String lastExistingParent(String path) { + while (!path.isEmpty() && !fileSystem->exists(path)) { + if (path.lastIndexOf('/') > 0) { + path = path.substring(0, path.lastIndexOf('/')); + } else { + path = String(); // No slash => the top folder does not exist + } + } + DBG_OUTPUT_PORT.println(String("Last existing parent: ") + path); + return path; +} + +/* + Handle the creation/rename of a new file + Operation | req.responseText + ---------------+-------------------------------------------------------------- + Create file | parent of created file + Create folder | parent of created folder + Rename file | parent of source file + Move file | parent of source file, or remaining ancestor + Rename folder | parent of source folder + Move folder | parent of source folder, or remaining ancestor +*/ +void handleFileCreate() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } + + String path = server.arg("path"); + if (path.isEmpty()) { + return replyBadRequest(F("PATH ARG MISSING")); + } + +#ifdef USE_SPIFFS + if (checkForUnsupportedPath(path).length() > 0) { + return replyServerError(F("INVALID FILENAME")); + } +#endif + + if (path == "/") { + return replyBadRequest("BAD PATH"); + } + if (fileSystem->exists(path)) { + return replyBadRequest(F("PATH FILE EXISTS")); + } + + String src = server.arg("src"); + if (src.isEmpty()) { + // No source specified: creation + DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path); + if (path.endsWith("/")) { + // Create a folder + path.remove(path.length() - 1); + if (!fileSystem->mkdir(path)) { + return replyServerError(F("MKDIR FAILED")); + } + } else { + // Create a file + File file = fileSystem->open(path, "w"); + if (file) { + file.write((const char *)0); + file.close(); + } else { + return replyServerError(F("CREATE FAILED")); + } + } + if (path.lastIndexOf('/') > -1) { + path = path.substring(0, path.lastIndexOf('/')); + } + replyOKWithMsg(path); + } else { + // Source specified: rename + if (src == "/") { + return replyBadRequest("BAD SRC"); + } + if (!fileSystem->exists(src)) { + return replyBadRequest(F("SRC FILE NOT FOUND")); + } + + DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path + " from " + src); + + if (path.endsWith("/")) { + path.remove(path.length() - 1); + } + if (src.endsWith("/")) { + src.remove(src.length() - 1); + } + if (!fileSystem->rename(src, path)) { + return replyServerError(F("RENAME FAILED")); + } + replyOKWithMsg(lastExistingParent(src)); + } +} + + +/* + Delete the file or folder designed by the given path. + If it's a file, delete it. + If it's a folder, delete all nested contents first then the folder itself + + IMPORTANT NOTE: using recursion is generally not recommended on embedded devices and can lead to crashes (stack overflow errors). + This use is just for demonstration purpose, and FSBrowser might crash in case of deeply nested filesystems. + Please don't do this on a production system. +*/ +void deleteRecursive(String path) { + File file = fileSystem->open(path, "r"); + bool isDir = file.isDirectory(); + file.close(); + + // If it's a plain file, delete it + if (!isDir) { + fileSystem->remove(path); + return; + } + + // Otherwise delete its contents first + Dir dir = fileSystem->openDir(path); + + while (dir.next()) { + deleteRecursive(path + '/' + dir.fileName()); + } + + // Then delete the folder itself + fileSystem->rmdir(path); +} + + +/* + Handle a file deletion request + Operation | req.responseText + ---------------+-------------------------------------------------------------- + Delete file | parent of deleted file, or remaining ancestor + Delete folder | parent of deleted folder, or remaining ancestor +*/ +void handleFileDelete() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } + + String path = server.arg(0); + if (path.isEmpty() || path == "/") { + return replyBadRequest("BAD PATH"); + } + + DBG_OUTPUT_PORT.println(String("handleFileDelete: ") + path); + if (!fileSystem->exists(path)) { + return replyNotFound(FPSTR(FILE_NOT_FOUND)); + } + deleteRecursive(path); + + replyOKWithMsg(lastExistingParent(path)); +} + +/* + Handle a file upload request +*/ void handleFileUpload() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } if (server.uri() != "/edit") { return; } HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { String filename = upload.filename; + // Make sure paths always start with "/" if (!filename.startsWith("/")) { filename = "/" + filename; } - DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename); - fsUploadFile = SPIFFS.open(filename, "w"); - filename = String(); + DBG_OUTPUT_PORT.println(String("handleFileUpload Name: ") + filename); + uploadFile = fileSystem->open(filename, "w"); + if (!uploadFile) { + return replyServerError(F("CREATE FAILED")); + } + DBG_OUTPUT_PORT.println(String("Upload: START, filename: ") + filename); } else if (upload.status == UPLOAD_FILE_WRITE) { - //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize); - if (fsUploadFile) { - fsUploadFile.write(upload.buf, upload.currentSize); + if (uploadFile) { + size_t bytesWritten = uploadFile.write(upload.buf, upload.currentSize); + if (bytesWritten != upload.currentSize) { + return replyServerError(F("WRITE FAILED")); + } } + DBG_OUTPUT_PORT.println(String("Upload: WRITE, Bytes: ") + upload.currentSize); } else if (upload.status == UPLOAD_FILE_END) { - if (fsUploadFile) { - fsUploadFile.close(); + if (uploadFile) { + uploadFile.close(); } - DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); + DBG_OUTPUT_PORT.println(String("Upload: END, Size: ") + upload.totalSize); } } -void handleFileDelete() { - if (server.args() == 0) { - return server.send(500, "text/plain", "BAD ARGS"); - } - String path = server.arg(0); - DBG_OUTPUT_PORT.println("handleFileDelete: " + path); - if (path == "/") { - return server.send(500, "text/plain", "BAD PATH"); - } - if (!SPIFFS.exists(path)) { - return server.send(404, "text/plain", "FileNotFound"); - } - SPIFFS.remove(path); - server.send(200, "text/plain", ""); - path = String(); -} -void handleFileCreate() { - if (server.args() == 0) { - return server.send(500, "text/plain", "BAD ARGS"); - } - String path = server.arg(0); - DBG_OUTPUT_PORT.println("handleFileCreate: " + path); - if (path == "/") { - return server.send(500, "text/plain", "BAD PATH"); +/* + The "Not Found" handler catches all URI not explicitely declared in code + First try to find and return the requested file from the filesystem, + and if it fails, return a 404 page with debug information +*/ +void handleNotFound() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); } - if (SPIFFS.exists(path)) { - return server.send(500, "text/plain", "FILE EXISTS"); + + String uri = ESP8266WebServer::urlDecode(server.uri()); // required to read paths with blanks + + if (handleFileRead(uri)) { + return; } - File file = SPIFFS.open(path, "w"); - if (file) { - file.close(); - } else { - return server.send(500, "text/plain", "CREATE FAILED"); + + // Dump debug data + String message; + message.reserve(100); + message = F("Error: File not found\n\nURI: "); + message += uri; + message += F("\nMethod: "); + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += F("\nArguments: "); + message += server.args(); + message += '\n'; + for (uint8_t i = 0; i < server.args(); i++) { + message += F(" NAME:"); + message += server.argName(i); + message += F("\n VALUE:"); + message += server.arg(i); + message += '\n'; } - server.send(200, "text/plain", ""); - path = String(); + message += "path="; + message += server.arg("path"); + message += '\n'; + DBG_OUTPUT_PORT.print(message); + + return replyNotFound(message); } -void handleFileList() { - if (!server.hasArg("dir")) { - server.send(500, "text/plain", "BAD ARGS"); +/* + This specific handler returns the index.htm (or a gzipped version) from the /edit folder. + If the file is not present but the flag INCLUDE_FALLBACK_INDEX_HTM has been set, falls back to the version + embedded in the program code. + Otherwise, fails with a 404 page with debug information +*/ +void handleGetEdit() { + if (handleFileRead(F("/edit/index.htm"))) { return; } - - String path = server.arg("dir"); - DBG_OUTPUT_PORT.println("handleFileList: " + path); -#if defined(ARDUINO_ARCH_ESP8266) - Dir dir = SPIFFS.openDir(path); -#elif defined(ARDUINO_ARCH_ESP32) - File root = SPIFFS.open(path); -#endif - path = String(); - String output = "["; -#if defined(ARDUINO_ARCH_ESP8266) - while (dir.next()) { - File entry = dir.openFile("r"); - if (output != "[") { - output += ','; - } - bool isDir = false; - output += "{\"type\":\""; - output += (isDir) ? "dir" : "file"; - output += "\",\"name\":\""; - output += String(entry.name()).substring(1); - output += "\"}"; - entry.close(); - } -#elif defined(ARDUINO_ARCH_ESP32) - if(root.isDirectory()){ - File file = root.openNextFile(); - while(file){ - if (output != "[") { - output += ','; - } - output += "{\"type\":\""; - output += (file.isDirectory()) ? "dir" : "file"; - output += "\",\"name\":\""; - output += String(file.name()).substring(1); - output += "\"}"; - file = root.openNextFile(); - } - } +#ifdef INCLUDE_FALLBACK_INDEX_HTM + server.sendHeader(F("Content-Encoding"), "gzip"); + server.send(200, "text/html", index_htm_gz, index_htm_gz_len); +#else + replyNotFound(FPSTR(FILE_NOT_FOUND)); #endif - - output += "]"; - server.send(200, "text/json", output); + } -void setup(void){ +void setup(void) { + //////////////////////////////// + // SERIAL INIT DBG_OUTPUT_PORT.begin(115200); - DBG_OUTPUT_PORT.print("\n"); DBG_OUTPUT_PORT.setDebugOutput(true); - SPIFFS.begin(); - { -#if defined(ARDUINO_ARCH_ESP8266) - Dir dir = SPIFFS.openDir("/"); - while (dir.next()) { - String fileName = dir.fileName(); - size_t fileSize = dir.fileSize(); - DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); - } -#elif defined(ARDUINO_ARCH_ESP32) - File root = SPIFFS.open("/"); - File file = root.openNextFile(); - while(file){ - String fileName = file.name(); - size_t fileSize = file.size(); - DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); - file = root.openNextFile(); + DBG_OUTPUT_PORT.print('\n'); + + //////////////////////////////// + // FILESYSTEM INIT + + fileSystemConfig.setAutoFormat(false); + fileSystem->setConfig(fileSystemConfig); + fsOK = fileSystem->begin(); + DBG_OUTPUT_PORT.println(fsOK ? F("Filesystem initialized.") : F("Filesystem init failed!")); + +#ifdef USE_SPIFFS + // Debug: dump on console contents of filessytem with no filter and check filenames validity + Dir dir = fileSystem->openDir(""); + DBG_OUTPUT_PORT.println(F("List of files at root of filesystem:")); + while (dir.next()) { + String error = checkForUnsupportedPath(dir.fileName()); + String fileInfo = dir.fileName() + (dir.isDirectory() ? " [DIR]" : String(" (") + dir.fileSize() + "b)"); + DBG_OUTPUT_PORT.println(error + fileInfo); + if (error.length() > 0) { + unsupportedFiles += error + fileInfo + '\n'; } + } + DBG_OUTPUT_PORT.println(); + + // Keep the "unsupportedFiles" variable to show it, but clean it up + unsupportedFiles.replace("\n", "
"); + unsupportedFiles = unsupportedFiles.substring(0, unsupportedFiles.length() - 5); #endif - DBG_OUTPUT_PORT.printf("\n"); - } - - - //WIFI INIT - DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid); - //Comment out as follows to make AutoConnect recognition. - //if (String(WiFi.SSID()) != String(ssid)) { - // WiFi.mode(WIFI_STA); - // WiFi.begin(ssid, password); - //} - - //while (WiFi.status() != WL_CONNECTED) { - // delay(500); - // DBG_OUTPUT_PORT.print("."); - //} - - //SERVER INIT - //list directory + + // With applying AutoConnect, making WiFi connection is not necessary. + // WI-FI INIT + // DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid); + // WiFi.mode(WIFI_STA); + // WiFi.begin(ssid, password); + // // Wait for connection + // while (WiFi.status() != WL_CONNECTED) { + // delay(500); + // DBG_OUTPUT_PORT.print("."); + // } + // DBG_OUTPUT_PORT.println(""); + // DBG_OUTPUT_PORT.print(F("Connected! IP address: ")); + // DBG_OUTPUT_PORT.println(WiFi.localIP()); + + //////////////////////////////// + // WEB SERVER INIT + + // Filesystem status + server.on("/status", HTTP_GET, handleStatus); + + // List directory server.on("/list", HTTP_GET, handleFileList); - //load editor - server.on("/edit", HTTP_GET, []() { - if (!handleFileRead("/edit.htm")) { - server.send(404, "text/plain", "FileNotFound"); - } - }); - //create file - server.on("/edit", HTTP_PUT, handleFileCreate); - //delete file - server.on("/edit", HTTP_DELETE, handleFileDelete); - //first callback is called after the request has ended with all parsed arguments - //second callback handles file uploads at that location - server.on("/edit", HTTP_POST, []() { - server.send(200, "text/plain", ""); - }, handleFileUpload); - - //called when the url is not defined here - //use it to load content from SPIFFS - //Replacement as follows to make AutoConnect recognition. - //server.onNotFound([](){ - portal.onNotFound([](){ - if(!handleFileRead(server.uri())) - server.send(404, "text/plain", "FileNotFound"); - }); - - //get heap status, analog input value and all GPIO statuses in one json call - server.on("/all", HTTP_GET, [](){ - String json = "{"; - json += "\"heap\":"+String(ESP.getFreeHeap()); - json += ", \"analog\":"+String(analogRead(A0)); -#if defined(ARDUINO_ARCH_ESP8266) - json += ", \"gpio\":"+String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16))); -#elif defined(ARDUINO_ARCH_ESP32) - json += ", \"gpio\":" + String((uint32_t)(0)); -#endif - json += "}"; - server.send(200, "text/json", json); - json = String(); - }); - //Set menu title + // Load editor + server.on("/edit", HTTP_GET, handleGetEdit); + + // Create file + server.on("/edit", HTTP_PUT, handleFileCreate); + + // Delete file + server.on("/edit", HTTP_DELETE, handleFileDelete); + + // Upload file + // - first callback is called after the request has ended with all parsed arguments + // - second callback handles file upload at that location + server.on("/edit", HTTP_POST, replyOK, handleFileUpload); + + // Default handler for all URIs not defined above + // Use it to read files from filesystem + // To make AutoConnect recognize the 404 handler, replace it with: + //server.onNotFound(handleNotFound); + portal.onNotFound(handleNotFound); + + // Using AutoConnect does not require the HTTP server to be started + // intentionally. It is launched inside AutoConnect.begin. + // Start server + // server.begin(); + // DBG_OUTPUT_PORT.println("HTTP server started"); + + // Start AutoConnect config.title = "FSBrowser"; portal.config(config); - //Register AutoConnect menu portal.append("/edit", "Edit"); portal.append("/list?dir=\"/\"", "List"); - //Replacement as follows to make AutoConnect recognition. - //server.begin(); - portal.begin(); + if (portal.begin()) { + DBG_OUTPUT_PORT.print(F("Connected! IP address: ")); + DBG_OUTPUT_PORT.println(WiFi.localIP()); + } DBG_OUTPUT_PORT.println("HTTP server started"); - //Relocation as follows to make AutoConnect recognition. - DBG_OUTPUT_PORT.println(""); - DBG_OUTPUT_PORT.print("Connected! IP address: "); - DBG_OUTPUT_PORT.println(WiFi.localIP()); - - //Relocation as follows to make AutoConnect recognition. + // With applying AutoConnect, the MDNS service must be started after + // establishing a WiFi connection. + // MDNS INIT if (MDNS.begin(host)) { MDNS.addService("http", "tcp", 80); - DBG_OUTPUT_PORT.print("Open http://"); + DBG_OUTPUT_PORT.print(F("Open http://")); DBG_OUTPUT_PORT.print(host); - DBG_OUTPUT_PORT.println(".local/edit to see the file browser"); - } - else { - DBG_OUTPUT_PORT.print("mDNS start failed"); + DBG_OUTPUT_PORT.println(F(".local/edit to open the FileSystem Browser")); + DBG_OUTPUT_PORT.print(F("Open http://")); + DBG_OUTPUT_PORT.print(host); + DBG_OUTPUT_PORT.println(F(".local/_ac to AutoConnect statistics")); } } - -void loop(void){ - //Replacement as follows to make AutoConnect recognition. - //server.handleClient(); + + +void loop(void) { + // To make AutoConnect recognize the client handling, replace it with: + // server.handleClient(); portal.handleClient(); #ifdef ARDUINO_ARCH_ESP8266 MDNS.update(); diff --git a/examples/FSBrowser/README.md b/examples/FSBrowser/README.md index fe19492..5d5c8a2 100644 --- a/examples/FSBrowser/README.md +++ b/examples/FSBrowser/README.md @@ -1,13 +1,149 @@ -### FSBrowser from the example of ESP8266WebServer +# FSBrowser with AutoConnect readme -This example sketch is in ESP8266WebServer library for Arduino environment. The [FSWebServer](https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer/examples/FSBrowser) is cited as an example of combining AutoConnect library. +It is a concrete implementation that applies AutoConnect to **FSBrowser.ino** which is an example of ESP8266WebServer library. A description of the specific changes can be found in the [AutoConnect documentation](https://hieromon.github.io/AutoConnect/menuize.html). -The modification describes in the source code. +Modifying just a few lines of the Sketch you have and applying AutoConnect has the following advantages: +1. Make a wifi connection without pre-coded SSID and password. +2. Integrate the FSBrowser utility separated for each URI into the AutoConnect menu. +3. It does not interfere with the functions of the original FSBrowser. -#### License +The following description is a quote of readme.md that the FSBrowser distribution contains. -FSWebServer - Example WebServer with SPIFFS backend for esp8266 -Copyright (c) 2015 Hristo Gochkov. All rights reserved. -This file is part of the ESP8266WebServer library for Arduino environment. +---- + +## What is this sketch about ? + +This example is a FileSystem Browser for the ESP8266 using http requests and a html/javascript frontend, +working for all of SPIFFS, LittleFS and SDFS. +This unified version is based on the previous examples named FSWebServer, FSBrowser and SDWebServer, Copyright (c) 2015 Hristo Gochkov. All rights reserved. + +## How to use it ? +1. Uncomment one of the `#define USE_xxx` directives in the sketch +2. Add the credentials of your WiFi network (search for `STASSID`) +3. Compile and upload the sketch to your ESP8266 device +4. For normal use, copy the contents of the `data` folder to the filesystem. To do so: +- for SDFS, copy that contents (not the data folder itself, just its contents) to the root of a FAT/FAT32-formated SD card connected to the SPI port of the ESP8266 +- for SPIFFS or LittleFS, please follow the instructions at https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#uploading-files-to-file-system +5. Once the data and sketch have been uploaded, access the editor by pointing your browser to http://fsbrowser.local/edit + +## Options +If you need to free some space on the ESP filesystem, you can delete all the sample files at the root but also replace the `index.htm` file in the `data/edit` subfolder by the `index.htm.gz` file from the `extras` folder. That compressed version is not suited for learning or debugging, but will bring the total FS usage under 7KB. +If you want to use the browser on a an existing filesystem or don't want to perform step 4 above, you have two possibilities : +- either upload the `index.htm` file to the filesystem by opening a command shell in the `data` folder and running the following cURL command: +`curl -F file=@edit/index.htm;filename=/edit/index.htm fsbrowser.local/edit` +- or embed a version of the html page in the source code itself by uncommenting the following line in the sketch and rebuilding: +`#define INCLUDE_FALLBACK_INDEX_HTM` +That embedded version is functionally equivalent and will be returned if no `/edit/index.htm` or `/edit/index.htm.gz` file can be found on the filesystem, at the expense of a higher binary size. + +If you use the gzipped or `INCLUDE_FALLBACK_INDEX_HTM` options, please remember to rerun the `reduce_index.sh` script located in the `extras` subfolder and recompile the sketch after each change to the `index.html` file. + +## Dependency +The html page uses the [Ace.js](https://ace.c9.io/) (v1.4.9 at the time of writing) text editor which is loaded from a CDN. Consequently, internet access from your web browser is required for the FSBrowser editing feature to work as-is. + +If your browser has no web access (e.g. if you are connected to the ESP8266 as an access-point), you can copy the `ace.js` file to the `edit` subfolder of the ESP filesystem, along with optional plugins etc. according to your needs. A typical set might be: +``` +ace.js +ext-keybinding_menu.js +ext-searchbox.js +mode-html.js +worker-html.js +worker-css.js +worker-javascript.js +mode-xml.js +worker-xml.js +mode-json.js +worker-json.js +``` +(see https://github.com/ajaxorg/ace-builds for a full list). + +If `ace.js` cannot be found on the ESP filesystem either, the page will default to a plain text viewer, with a warning message. + +## Notes +- See https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html for more information on FileSystems supported by the ESP8266. +- For SDFS, if your card's CS pin is not connected to the default pin (4), uncomment the `fileSystemConfig.setCSPin(chipSelectPin);` line, specifying the GPIO the CS pin is connected to +- `index.htm` is the default index returned if your URL does not end with a filename (works on subfolders as well) +- Filesystem limitations apply. For example, FAT16 is limited to 8.3 filenames - see https://en.wikipedia.org/wiki/8.3_filename - SPIFFS and LittleFS also have limitations, please see https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#spiffs-file-system-limitations +- Directories are supported on SDFS and LittleFS. On SPIFFS, all files are at the root, although their names may contain the "/" character. +- The convention here is that the root of the filesystem is "/". On SPIFFS, paths not started with a slash are not supported +- For creation, the convention is that a path ending with a "/" means create a folder, while without a "/" we create a file. Having an extension or not does not matter. + +## Changelog since original FSBrowser + +### Fixes to work on LittleFS based on SDFS +- #define logic to select FS +- switched from SD to SDFS +- begin() does not support parameters > removed SS and added optional config +- LittleFS.open() second parametsr is mandatory > specified "r" where needed +- 'FILE_WRITE' was not declared in this scope > replaced by "w" + +### UI/usability improvements +- Never format filesystem, just return "FS INIT ERROR" when FS cannot be mounted +- Tree panel width is now proportional (20%) to see long names on big screens +- Added icons for files, and indented them to the same level as folders +- Changed file/folder icon set to use a lighter and more neutral one, and added specific "text" and "image" icons for formats recognized as such +- Items are now sorted (folders first, then plain files, each in alphabetic order) +- Added file size after each file name +- Added FS status information at the top right +- Made clear that an async operation is in progress by dimming screen and showing operation status +- Filled filename box in header with the name of the last clicked file +- Selecting a file for upload defaults to putting it in the same folder as the last clicked file +- Removed limitation to 8.3 lowercase filenames +- Support Filenames without extension, Dirnames with extension +- Improved recursive refresh of parts of the tree (e.g. refresh folder upon file delete, show last folder upon creating nested file) +- Added Save/Discard/Help buttons to ACE editor, discard confirmation on leave, and refresh tree and status upon save +- Removed "Upload" from context menu (which didn't work anyway) +- Added "Rename/Move" feature to context menu +- Sketch can be used on a preexisting filesystem by embedding the index.htm file in the program + +## TODO (maybe) +- ? How can we query the fatType of the SDFS (FAT16 or FAT32) to limit filenames to 8.3 on FAT16 ? +- ? Add a visible root node "/" (with no delete option) + add the FS type next to it, like LittleFS +- ? move "Mkdir" and "MkFile" to context menu, with prompt like for Rename/Move +- ? implement drag/drop for move + make "rename" only a local rename operation (no move) +- ? Optionally present SPIFFS as a hierarchical FS too +- ? Optionally mount several filesystems at the same time (SPIFFS + SDFS or LittleFS + SDFS) + +## Test suite +These tests are a checklist of operations to verify the FSBrowser behaviour. +### On SPIFFS +#### 8.3 filenames +- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image +- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image +- Create nested file '/a/b.txt' and delete it +- Attempt creation of unsupported filenames +#### Long filenames +- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image +- In subdir : MkFile '/My Directory/My text 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image 2.png' / View image +- Create nested file '/My folder/My test file.txt' and delete it + +### On LittleFS +#### 8.3 filenames +- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image / Mkdir '/dir' +- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image / Mkdir '/dir/sub' +- Delete root folder '/dir' +- Create nested file '/a/b.txt' and delete file 'b.txt' +#### Long filenames +- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory' +- In subdir : MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory' +- Delete root folder '/My Directory' +- Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt' + +### On SDFS +#### 8.3 filenames +- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image / Mkdir '/dir' +- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image / Mkdir '/dir/sub' +- Delete root folder '/dir' +- Create nested file '/a/b.txt' and delete file 'b.txt', then delete '/a' +#### Long filenames +- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory' +- In subdir : MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory' +- Delete root folder '/My Directory' +- Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt' + +## Credits +- Original version of FSBrowser written by me-no-dev, contributions over time by various contributors +- Icons are from https://feathericons.com/ . The resulting PNG is passed first through https://compresspng.com/ before being converted to base64 using https://www.base64-image.de/ +- The spinner is based on https://github.com/jlong/css-spinners +- Minifiying of index.htm is done using the command line version of https://kangax.github.io/html-minifier/ +- Idea of embedding webpage in code borrowed from https://github.com/me-no-dev/ESPAsyncWebServer -License under the LGPL-2.1. diff --git a/examples/FSBrowser/data/edit.htm.gz b/examples/FSBrowser/data/edit.htm.gz deleted file mode 100644 index 69ce414f47f4b25a70160fc4985b2bd320c9b90a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4116 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 + + + + File manager + + + + + + + +
+
+ +

Loading...
+ + + diff --git a/examples/FSBrowser/data/graphs.js.gz b/examples/FSBrowser/data/graphs.js.gz deleted file mode 100644 index 72435445a7ef86e00fe6c66b3b71c47fdbd53a93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1971 zcmV;k2Tb@MiwFo@s$5k717~t!aAw-*8%!ep`yEM!aI@XpTRzN;M>CH`Bk93rb1y+*nfgVXr6{*O z+qWk+vpIfdbmRB&GiOD~!8-BI8jWLya*xm0`?Ns*u7@$U5b~-2FAtyY?VsHiJWiul z$g}Ci$m17Tz>sgbNBG!p6`3AIdmW5d-8e)$jYf;JJn?*ncK^L=>v4>q7o#|V#AXxo zd(ilSPVW5&A*9`%(ECk!Y+arKfe)*P?pEpRp~jzoF096?>r=Aoz97T=5)|h&-xQIu zg>l28ew8E08a6CBp`}c_5jC)_GP?DY`3f}-jd!TP&)0@aTI6>ySBJ(C^6Vjg_FCi? z4W)y8K|{N+7xu_b?5Q2tp*_aW9rTMaq?DSsrVu+rd~J}hE1ki{8Q_4DpSuf+7N=y0 z&lbmI*d0+cIw7C&8TouX>b8vuC7%rP5n-@XYJN3AXB2Qkjq~PBh|f&&!8{IOB&X(2 zH9yyb7aQ|uSdryo%OvNrl!(4VCh<(%jlKg>o+p(VLEZu+SU49}D`X?;9hV2J6#b@5xoQzr_H2o5@^1L}hMUELzT^@ZjjKOo0;;PSc-R=y16 zZ?GMY#vgK8*szEP4+cZTEn{%f2k_E z27Gde+C~f)Fg^jqZfsd{i2;6w@jVpRk!@);Z(!Q^0V_`j7DaAW9TdZ@cIt4a4szZaa^^aU29qDr;ABm46G?-=na2}Ferp=!isDL5 zZsRR-8*h`__!TcbYL6zj@e8?){|UK0{(#)d@5!y&&_(q^Zq+tjEH`wq+|b2RliLz5 zFqaQCU2wY8dG2R6eWA(3ua&TB6J>0#%pV>3Z zdyofc0!I`ut`SP$kiJ3WHi#-0<433_` z)YOv0bAAFn?dFnNRNv(ebZf$J9m~5X4;HB}|KgQLUj9wtTU0ot`o1rAx!kOPE}Fw8 zS~Q^pvJt_F1Mf4=n%)ASw`5Ik|GTcX!Dzk1YQ^WUvs*ob65M#*8Pl}n#sj~2z;Kp3 z#wt>!wBemdQe1%Fa@<6G*Tw3BO6(?)=)j$C-mo1S$t$oENe=yhLb-k<8_;yI4OzB} zRnJ39Vd(ZadQJ$E_Fo6+T+OyM?w0=_W4@|s1;9o}-pE(zujhQpQ&}rb!wtIwq}*=X z4FSKo$AySPF)Nk{6YIBX;rZfke?Eikq=c+ENp6^52>r$^5|&2ANW^K%_#LYXv7c8e z=vGa#hyxfcmO0B{UVkpiG+<$zvOo;WqJY`6)K6l6B8Kp~K2ezpCd|@8%tl_2EiSSo zFA^|v1uq>WUSe{a+%Cnisy&!<^nU5?i#QLL2@C+6O z9mvZ6hkhC(4R=nr5iF#GH6&3&)`F6DcbF}Sh6I^7IhzV|9hOnAbbP~0)_aTc?G-T zUOp0`CmZu63}>tmJ`@(Gu)SWMGnfEAsi4IU)bR<5*jr~_#!;-Z^EjQ<&VItC4B8MT z8B`^L%rDim_gH$+=1MruU~n)H8CPxfm(bCv7eR<84hDk()HUc0oPGxaFZ!1Rx^{U0 z&VK*U?e~RBheyXbhhXsb4|_d`A?>3*L0l+t&uz4z32}iM0zT**`+0@&e*in8G#?BP F000|7$0Ps% diff --git a/examples/FSBrowser/data/index.htm b/examples/FSBrowser/data/index.htm index 26ff605..55fe5a6 100644 --- a/examples/FSBrowser/data/index.htm +++ b/examples/FSBrowser/data/index.htm @@ -1,101 +1,22 @@ - - - ESP Monitor - - - -
- - - - -
-
-
-
-

- AutoConnect menu -

+ +

ESP8266 Pin Functions

+ - \ No newline at end of file + diff --git a/examples/FSBrowser/data/pins.png b/examples/FSBrowser/data/pins.png new file mode 100644 index 0000000000000000000000000000000000000000..f988bf45e3eff7380cf899affb33f7bb17e23cd5 GIT binary patch literal 55578 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 literal 0 HcmV?d00001 diff --git a/examples/FSBrowser/extras/feathericons.png b/examples/FSBrowser/extras/feathericons.png new file mode 100644 index 0000000000000000000000000000000000000000..5bb2cf63dcdc6f9c0a5309c5823652117e5bdd73 GIT binary patch literal 1558 zcmeAS@N?(olHy`uVBq!ia0vp^0zkZggBeH~J(p1fQY`6?zK#qG8~eHcB(gFvFf#=B zgt!95^-OK_&1{V<9W3l!tsOnAoje>I96UWey}iACeSQ7?{at;6JpDs`f+K>0g2KYW zf+FG~A|hg9V&mfC;^X5(qZ7ko5+mbMlQMFXGxD;5ASWj`H#Z*$N-7IVD{C5CYn$3y zT3XtGpsTO5cVhpP8PjIZn>lml+_`fXFI%}}`KomrH?7~WdBf&yo3?D-vUS^*ZQFP5 z+_?t`_U%7(o;%RxOwaD-Mjbi-+%Dn!PBSDo<4j2?AeQ#FJHcY z|Nhgb&)>d%`~Lm=j~_pN|Ni~&-@pI={{uZZ3PwW!R|qJ6jdWmOU}Pu>@(Tv0mXQn? zg1@t?0>g>3z$3Dlfr0NJ2s7@OnEe(gC{^MbQ4*Y=R#Ki=l*$m0n3-3i=jR%tP-d)W zs%L2E{@KYK7>re(E{-7{-fyqm3_4^W!j{l+X-B?C_Uj;rgIXHvc!NYkT%P{Dudb%< zJ^Narmh$7)KjqJ7tJt2Lli9c|Xu*5 z)J$8z=fSk=_)!O^{ZVhfKTu%ZcVB$9P;`aKfufcwskTKI6M14icxLK;@+r+&srtHb zHH*Q)>L)Y%`^$^%^iFxJAFFwDZ%?S)q|Y16ZEw~2oI9SH`ccHn^qrBFjKqhkhVAoA z@5t|cliYK9yU@C8ADLDg-kY$2N3@^8J4|Qi^TQ{2&YU@LhUd(m>;+$ht2DpedRzP6 Q4V2J5UHx3vIVCg!0JbYzga7~l literal 0 HcmV?d00001 diff --git a/examples/FSBrowser/extras/index.htm.gz b/examples/FSBrowser/extras/index.htm.gz new file mode 100644 index 0000000000000000000000000000000000000000..34797b0589617ca9bfd7e2dec3cabe824efe45c0 GIT binary patch literal 6261 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 fM1-xRarv_#hz^ctf_C~R#J!_`xuIwdI7 index_htm.h +echo '// This file is an embeddable version of the gzipped index.htm file.' >> index_htm.h +echo '// To update it, please rerun the `reduce_index.sh` script located in the `extras` subfolder' >> index_htm.h +echo '// then recompile the sketch after each change to the `index.html` file.' >> index_htm.h +xxd -i index.htm.gz >> index_htm.h +if [ $? -ne 0 ] +then + echo "Error creating include file from index.htm.gz" + exit -1 +fi + +echo Reduce complete. + diff --git a/examples/FileUpload/FileUpload.ino b/examples/FileUpload/FileUpload.ino index fc40a3a..dcee026 100644 --- a/examples/FileUpload/FileUpload.ino +++ b/examples/FileUpload/FileUpload.ino @@ -26,9 +26,22 @@ #include #include #endif -#include #include +/* + AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that + will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng + the valid file system. After including AutoConnect.h, the Sketch can determine + whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition. +*/ +#ifdef AUTOCONNECT_USE_SPIFFS +#include +FS& FlashFS = SPIFFS; +#else +#include +FS& FlashFS = LittleFS; +#endif + // Upload request custom Web page static const char PAGE_UPLOAD[] PROGMEM = R"( { @@ -126,8 +139,8 @@ String postUpload(AutoConnectAux& aux, PageArgument& args) { // The file saved by the AutoConnect upload handler is read from // the EEPROM and echoed to a custom web page. if (upload.mimeType.indexOf("text/") >= 0) { - SPIFFS.begin(); - File uploadFile = SPIFFS.open(String("/" + upload.value).c_str(), FILE_MODE_R); + FlashFS.begin(); + File uploadFile = FlashFS.open(String("/" + upload.value).c_str(), FILE_MODE_R); if (uploadFile) { while (uploadFile.available()) { char c = uploadFile.read(); @@ -140,7 +153,7 @@ String postUpload(AutoConnectAux& aux, PageArgument& args) { } else content = "Not saved"; - SPIFFS.end(); + FlashFS.end(); } return content; }