Update: 2020-05-25

Der Esp32 Spiffs Datei Manager alphabetisch Sortiert als Arduino Tab.

Spiffs.ino

// ****************************************************************
// Sketch Esp32 Datei Manager alphabetisch Sortiert Modular(Tab)
// created: Jens Fleischer, 2020-03-26
// last mod: Jens Fleischer, 2020-03-26
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Geprüft: mit 4MB Flash
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file 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 file 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.
*******************************************************************/
// Diese Version von Spiffs sollte als Tab eingebunden werden.
// #include <SPIFFS.h> #include <WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// "server.onNotFound()" darf nicht im Setup des ESP8266 Webserver stehen.
// Die Funktion "spiffs();" muss im Setup aufgerufen werden.
/**************************************************************************************/

#include <detail/RequestHandlersImpl.h>
#include <list>

const char HELPER[] PROGMEM = R"(<form method="POST" action="/upload" enctype="multipart/form-data"><input type="file" name="upload">
<input type="submit" value="Upload"></form>Lade die spiffs.html hoch.)";
const char HEADER[] PROGMEM = "HTTP/1.1 303 OK\r\nLocation:spiffs.html\r\nCache-Control: no-cache\r\n";

void spiffs() {       // Funktionsaufruf "spiffs();" muss im Setup eingebunden werden
  SPIFFS.begin(true);
  server.on("/json", handleList);
  server.on("/format", formatSpiffs);
  server.on("/upload", HTTP_POST, []() {}, handleFileUpload);
  server.onNotFound([]() {
    if (!handleFile(server.urlDecode(server.uri())))
      server.send(404, "text/plain", "FileNotFound");
  });
}

void handleList() {               // Senden aller Daten an den Client
  std::list<String> dirList;                                                   // Liste anlegen
  File root = SPIFFS.open("/");
  while (File f = root.openNextFile()) {
    dirList.push_back(static_cast<String>(f.name()) + ":" + f.size());         // Liste füllen
  }
  dirList.sort( [](const String & first, const String & second) {              // Liste sortieren
    uint8_t i {0};
    while (true) {
      if (tolower(first[i]) < tolower(second[i])) return true;
      else if (tolower(first[i]) > tolower(second[i])) return false;
      i++;
    }
  });
  String temp = "[";
  for (auto& d : dirList) {
    if (temp != "[") temp += ',';
    temp += R"({"name":")" + d.substring(1, d.indexOf (":")) + R"(","size":")" + formatBytes(d.substring((d.indexOf (":")) + 1).toInt()) + R"("})";
  }
  temp += R"(,{"usedBytes":")" + formatBytes(SPIFFS.usedBytes() * 1.05) + R"(",)" +              // Berechnet den verwendeten Speicherplatz + 5% Sicherheitsaufschlag
          R"("totalBytes":")" + formatBytes(SPIFFS.totalBytes()) + R"(","freeBytes":")" +        // Zeigt die Größe des Speichers
          (SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05)) + R"("}])";                        // Berechnet den freien Speicherplatz + 5% Sicherheitsaufschlag
  server.send(200, "application/json", temp);
}

bool handleFile(String&& path) {
  if (server.hasArg("delete")) {
    SPIFFS.remove(server.arg("delete"));        // Datei löschen
    server.sendContent(HEADER);
    return true;
  }
  if (!SPIFFS.exists("/spiffs.html"))server.send(200, "text/html", HELPER);     // ermöglicht das hochladen der spiffs.html
  if (path.endsWith("/")) path += "index.html";
  return SPIFFS.exists(path) ? ({File f = SPIFFS.open(path, "r"); server.streamFile(f, StaticRequestHandler::getContentType(path)); f.close(); true;}) : false;
}

void handleFileUpload() {                                  // Dateien vom Rechnenknecht oder Klingelkasten ins SPIFFS schreiben
  static File fsUploadFile;
  HTTPUpload& upload = server.upload();
  if (upload.status == UPLOAD_FILE_START) {
    if (upload.filename.length() > 30) {
      upload.filename = upload.filename.substring(upload.filename.length() - 30, upload.filename.length());  // Dateinamen auf 30 Zeichen kürzen
    }
    DEBUG_F("handleFileUpload Name: /%s\n", upload.filename.c_str());
    fsUploadFile = SPIFFS.open("/" + server.urlDecode(upload.filename), "w");
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    DEBUG_F("handleFileUpload Data: %u\n", upload.currentSize);
    if (fsUploadFile)
      fsUploadFile.write(upload.buf, upload.currentSize);
  } else if (upload.status == UPLOAD_FILE_END) {
    if (fsUploadFile)
      fsUploadFile.close();
    DEBUG_F("handleFileUpload Size: %u\n", upload.totalSize);
    server.sendContent(HEADER);
  }
}

void formatSpiffs() {       //Formatiert den Speicher
  SPIFFS.format();
  server.sendContent(HEADER);
}

const String formatBytes(size_t const& bytes) {            // lesbare Anzeige der Speichergrößen
  return bytes < 1024 ? static_cast<String>(bytes) + " Byte" : bytes < 1048576 ? static_cast<String>(bytes / 1024.0) + " KB" : static_cast<String>(bytes / 1048576.0) + " MB";
}

bool freeSpace(uint16_t const& printsize) {            // Funktion zum genügend freier Platzbeim speichern in Logdateien zu prüfen ob noch  verfügbar ist.
  //DEBUG_F("Funktion: %s meldet in Zeile: %d FreeSpace: %s\n", __PRETTY_FUNCTION__, __LINE__, formatBytes(SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05)).c_str());
  return (SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05) > printsize) ? true : false;
}
Wer Logdateien anlegt sollte auf den noch zur Verfügung stehenden Speicher achten. Dafür kannst du die Funktion "freeSpace();" in deinem Sketch nutzen.

Aufruf der Logdatei um das überfüllen des Spiffs zu verhindern.


  File f = SPIFFS.open("/logdatei.txt", "a");
  if (f && freeSpace(200)) {      // Anpassen an die zu schreibende Anzahl Byte
    f.print("Fehler im Ablauf in die Logdatei schreiben\n");
  }
  f.close();

Das Webinterface zum Esp32 Spiffs Datei Manager.

spiffs.html

<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
   <head>
      <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="stylesheet" href="style32.css">
      <title>Esp32 Datei Manager</title>
      <script>
         document.addEventListener('DOMContentLoaded', () => {
		   let span = document.querySelector('span');
           let main = document.querySelector('main');
		   let elem = document.querySelectorAll('input');
           fetch('json').then(function (response) {
             return response.json();
           }).then(function (json) {
             for (var i = 0; i < json.length - 1; i++) {
               let dir = `<li><a href ="${json[i].name}">${json[i].name}</a><small> ${json[i].size}</small><a href ="${json[i].name}"download="${json[i].name}"> Download </a>`;
               if (json[i].name != 'spiffs.html') {
                 dir += `or <a href ="${json[i].name}?delete=/${json[i].name}">Delete </a>`;
               }
               main.insertAdjacentHTML('beforeend', dir);
             }
             main.insertAdjacentHTML('beforeend', `<li><b>SPIFFS</b> belegt ${json[i].usedBytes} von ${json[i].totalBytes}`);
			 free = json[i].freeBytes;
           });
		   elem[0].addEventListener('change', () => {
			 let nBytes = elem[0].files[0].size, output = `${nBytes} Byte`;
             for (var aMultiples = [
               ' KB',
               ' MB'
               ], i = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, i++) {
               output = nApprox.toFixed(2) + aMultiples[i];
             }
             if (nBytes > free) {
               span.innerHTML = `<li><small> Dateigröße: ${output}</small><strong style="color: red;"> Ungenügend Speicher frei </strong></li>`;
               elem[1].setAttribute('disabled', 'disabled');
             } 
             else {
               span.innerHTML = `<li><b>Dateigröße:</b> ${output}</li>`;
               elem[1].removeAttribute('disabled');
             }
		   });
		   elem[2].addEventListener('click', () => {
	         if (!confirm(`Wirklich formatieren? Alle Daten gehen verloren.\nDu musst anschließend spiffs.html wieder laden.`)) event.preventDefault();
	       }); 
         });
      </script>
   </head>
   <body>
      <h2>ESP32 Datei Manager</h2>
      <form action="/upload" method="POST" enctype="multipart/form-data"><input type="file" name="upload">
         <input type="submit" value="Upload" disabled>
      </form>
      <div>
         <span></span>
         <main></main>
      </div>
      <form action="/format" method="POST"><input type="submit" value="Format SPIFFS"></form>
   </body>
</html>

Das Webinterface mit zusätzlicher Sicherheitsabfrage vor dem löschen.

spiffs.html

<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
   <head>
      <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="stylesheet" href="style32.css">
      <title>Esp32 Datei Manager</title>
      <script>
         document.addEventListener('DOMContentLoaded', () => {
		   let span = document.querySelector('span');
           let main = document.querySelector('main');
		   let elem = document.querySelectorAll('input');
           fetch('json').then(function (response) {
             return response.json();
           }).then(function (json) {
             for (var i = 0; i < json.length - 1; i++) {
               let dir = `<li><a href ="${json[i].name}">${json[i].name}</a><small> ${json[i].size}</small>`;
			   dir += `<a href ="${json[i].name}"download="${json[i].name}"> Download </a>or <a href ="${json[i].name}?delete=/${json[i].name}">Delete </a>`;
               main.insertAdjacentHTML('beforeend', dir);
             }
             main.insertAdjacentHTML('beforeend', `<li><b>SPIFFS</b> belegt ${json[i].usedBytes} von ${json[i].totalBytes}`);
			 document.querySelectorAll('a:last-child').forEach((node) => {
			   node.addEventListener('click', () => {
	             if (!confirm('Bist du sicher!')) event.preventDefault();
	           });
		     });
			 free = json[i].freeBytes;
           });
		   elem[0].addEventListener('change', () => {
			 let nBytes = elem[0].files[0].size, output = `${nBytes} Byte`;
             for (var aMultiples = [
               ' KB',
               ' MB'
               ], i = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, i++) {
               output = nApprox.toFixed(2) + aMultiples[i];
             }
             if (nBytes > free) {
               span.innerHTML = `<li><small> Dateigröße: ${output}</small><strong style="color: red;"> Ungenügend Speicher frei </strong></li>`;
               elem[1].setAttribute('disabled', 'disabled');
             } 
             else {
               span.innerHTML = `<li><b>Dateigröße:</b> ${output}</li>`;
               elem[1].removeAttribute('disabled');
             }
		   });
		   elem[2].addEventListener('click', () => {
	         if (!confirm(`Wirklich formatieren? Alle Daten gehen verloren.\nDu musst anschließend spiffs.html wieder laden.`)) event.preventDefault();
	       }); 
         });
      </script>
   </head>
   <body>
      <h2>ESP32 Datei Manager</h2>
      <form action="/upload" method="POST" enctype="multipart/form-data"><input type="file" name="upload">
         <input type="submit" value="Upload" disabled>
      </form>
      <div>
         <span></span>
         <main></main>
      </div>
      <form action="/format" method="POST"><input type="submit" value="Format SPIFFS"></form>
   </body>
</html>

Die CSS Datei für den Esp32 Webserver.

style32.css

/*For more information visit: https://fipsok.de*/
body {
font-family: sans-serif;
background-color: #a9a9a9;
display: flex;
flex-flow: column;
align-items: center;
}
h1,h2 {
color: #e1e1e1;
text-shadow: 2px 2px 2px black;
}
li {
background-color: #7cfc00;
list-style-type: none;
margin-bottom: 10px;
padding-right: 5px;
border-top: 3px solid #7cfc00;
border-bottom: 3px solid #7cfc00;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.7);
}
b,a:nth-child(1) {
background-color: #ff4500;
font-weight: bold;
color: white;
text-decoration: none;
padding: 0 5px;
border-top: 3.2px solid #ff4500;
border-bottom: 3.6px solid #ff4500;
text-shadow: 2px 2px 1px black;
}
input {
height: 35px;
font-size: 13px;
}
h1+main {
display: flex;
}
section {
display: flex;
flex-direction: column;
padding: 0.2em;
}
#left {
align-items: flex-end;
text-shadow: 1px 1px 2px #757474;
}
.note {
background-color: salmon;
padding: 0.5em;
margin-top: 1em;
text-align: center;
max-width: 320px;
border-radius: 0.5em;
}
.button {
width: 130px;
height: 40px;
font-size: 16px;
margin-top: 1em;
cursor: pointer;
background-color: #adff2f;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.7);
}
[type=submit] {
height: 40px;
min-width: 70px;
}
[value^=Format] {
background-color: #ddd;
}
[title] {
background-color: silver;
font-size: 16px;
width: 125px;
}