diff --git a/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino b/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino
index d3d0acbd72cc..ee99a4f801ff 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino
@@ -18,6 +18,17 @@
*/
#ifdef USE_UFILESYS
+
+// saves 80 bytes of flash, makes the UI cleaner for folders containing lots of files.
+// disables recursive folder listing in file UI
+//#define UFILESYS_NO_RECURSE_GUI
+
+// Enables serving of static files on /fs/
+// costs 1844 bytes of flash and 40 bytes of RAM
+// probably not useful on esp8266, but useful on esp32
+// You could serve a whole webapp from Tas itself.
+//#define UFILESYS_STATIC_SERVING
+
/*********************************************************************************************\
This driver adds universal file system support for
- ESP8266 (sd card or littlefs on > 1 M devices with special linker file e.g. eagle.flash.4m2m.ld)
@@ -135,6 +146,36 @@ void UfsInit(void) {
}
}
+// simple put a zero at last '/'
+// modifies input string
+char *folderOnly(char *fname){
+ for (int i = strlen(fname)-1; i >= 0; i--){
+ if (fname[i] == '/'){
+ fname[i] = 0;
+ break;
+ }
+ fname[i] = 0;
+ }
+ if (!fname[0]){
+ fname[0] = '/';
+ fname[1] = 0;
+ }
+ return fname;
+}
+
+// returns everything after last '/' of whiole input if no '/'
+char *fileOnly(char *fname){
+ char *cp = fname;
+ for (uint32_t cnt = strlen(fname); cnt >= 0; cnt--) {
+ if (fname[cnt] == '/') {
+ cp = &fname[cnt + 1];
+ break;
+ }
+ }
+ return cp;
+}
+
+
#ifdef USE_SDCARD
void UfsCheckSDCardInit(void) {
// Try SPI mode first
@@ -503,10 +544,18 @@ char* UfsFilename(char* fname, char* fname_in) {
}
const char kUFSCommands[] PROGMEM = "Ufs|" // Prefix
- "|Type|Size|Free|Delete|Rename|Run";
+ "|Type|Size|Free|Delete|Rename|Run"
+#ifdef UFILESYS_STATIC_SERVING
+ "|Serve"
+#endif
+ ;
void (* const kUFSCommand[])(void) PROGMEM = {
- &UFSInfo, &UFSType, &UFSSize, &UFSFree, &UFSDelete, &UFSRename, &UFSRun};
+ &UFSInfo, &UFSType, &UFSSize, &UFSFree, &UFSDelete, &UFSRename, &UFSRun
+#ifdef UFILESYS_STATIC_SERVING
+ ,&UFSServe
+#endif
+ };
void UFSInfo(void) {
Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}"), ufs_type, UfsInfo(0, 0), UfsInfo(1, 0));
@@ -586,6 +635,115 @@ void UFSRename(void) {
}
}
+#ifdef UFILESYS_STATIC_SERVING
+/*
+* Serves a filesystem folder at a web url.
+* NOTE - this is expensive on flash -> +2.5kbytes.
+* like "UFSServe
%s: %s
"; + +#ifndef D_CURR_DIR + #define D_CURR_DIR "Folder" +#endif const char UFS_FORM_FILE_UPLOAD[] PROGMEM = "%s"; const char UFS_FORM_SDC_DIRb[] PROGMEM = - "
%s %s %8d %s %s"; + "
%s %19s %8d %s %s"; const char UFS_FORM_SDC_HREF[] PROGMEM = "ufsd?download=%s/%s"; #ifdef GUI_TRASH_FILE const char UFS_FORM_SDC_HREFdel[] PROGMEM = //"🗑"; // 🗑️ - "🔥"; // 🔥 + "🔥"; // 🔥 #endif // GUI_TRASH_FILE #ifdef GUI_EDIT_FILE @@ -684,36 +851,48 @@ void UfsDirectory(void) { AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_MANAGE_FILE_SYSTEM)); uint8_t depth = 0; + uint8_t isdir = 0; strcpy(ufs_path, "/"); - if (Webserver->hasArg(F("download"))) { - String stmp = Webserver->arg(F("download")); - char *cp = (char*)stmp.c_str(); - if (UfsDownloadFile(cp)) { - // is directory - strcpy(ufs_path, cp); - } else { - return; - } - } - if (Webserver->hasArg(F("dir"))) { String stmp = Webserver->arg(F("dir")); ufs_dir = atoi(stmp.c_str()); - if (ufs_dir == 1) { - dfsp = ufsp; - } else { - if (ffsp) { - dfsp = ffsp; - } + } + + if (ufs_dir == 1) { + dfsp = ufsp; + } else { + if (ffsp) { + dfsp = ffsp; } } if (Webserver->hasArg(F("delete"))) { String stmp = Webserver->arg(F("delete")); char *cp = (char*)stmp.c_str(); - dfsp->remove(cp); + File download_file = dfsp->open(cp, UFS_FILE_READ); + if (download_file) { + if (download_file.isDirectory()) { + download_file.close(); + dfsp->rmdir(cp); + } else { + download_file.close(); + dfsp->remove(cp); + } + } + } + + if (Webserver->hasArg(F("download"))) { + String stmp = Webserver->arg(F("download")); + char *cp = (char*)stmp.c_str(); + if (UfsDownloadFile(cp)) { + // is directory + strcpy(ufs_path, cp); + isdir = 1; + } else { + return; + } } WSContentStart_P(PSTR(D_MANAGE_FILE_SYSTEM)); @@ -733,13 +912,20 @@ void UfsDirectory(void) { WSContentSend_P(UFS_FORM_FILE_UPG, PSTR(D_SCRIPT_UPLOAD)); + if (isdir){ + // if a folder, show 'folder: xxx' if not '/' + if (ufs_path[0] != '/' || strlen(ufs_path) != 1){ + WSContentSend_P(UFS_CURRDIR, PSTR(D_CURR_DIR), ufs_path); + } + } + WSContentSend_P(UFS_FORM_SDC_DIRa); if (ufs_type) { UfsListDir(ufs_path, depth); } WSContentSend_P(UFS_FORM_SDC_DIRc); #ifdef GUI_EDIT_FILE - WSContentSend_P(UFS_FORM_FILE_UPGb); + WSContentSend_P(UFS_FORM_FILE_UPGb, ufs_path); #endif if (!isSDC()) { WSContentSend_P(UFS_FORM_FILE_UPGb1); @@ -788,6 +974,13 @@ void UfsListDir(char *path, uint8_t depth) { } char *ep; while (true) { + WiFiClient client = Webserver->client(); + // abort if the client disconnected + // if there is a huge folder, then this gives a way out by refresh of browser + if (!client.connected()){ + break; + } + File entry = dir.openNextFile(); if (!entry) { break; @@ -820,9 +1013,19 @@ void UfsListDir(char *path, uint8_t depth) { const char* ppe = pp_escaped_string.c_str(); // this can't be merged on a single line otherwise the String object can be freed const char* epe = ep_escaped_string.c_str(); sprintf(cp, format, ep); +#ifdef GUI_TRASH_FILE + char delpath[128+UFS_FILENAME_SIZE]; + ext_snprintf_P(delpath, sizeof(delpath), UFS_FORM_SDC_HREFdel, ppe, epe, ppe[0]?ppe:"/"); +#else + char delpath[2] = " "; +#endif // GUI_TRASH_FILE if (entry.isDirectory()) { ext_snprintf_P(npath, sizeof(npath), UFS_FORM_SDC_HREF, ppe, epe); - WSContentSend_P(UFS_FORM_SDC_DIRd, npath, ep, name); + + WSContentSend_P(UFS_FORM_SDC_DIRb, hiddable ? UFS_FORM_SDC_DIR_HIDDABLE : UFS_FORM_SDC_DIR_NORMAL, npath, epe, + HtmlEscape(name).c_str(), "", 0, delpath, " "); + //WSContentSend_P(UFS_FORM_SDC_DIRd, npath, ep, name); +#ifdef UFILESYS_RECURSEFOLDERS_GUI uint8_t plen = strlen(path); if (plen > 1) { strcat(path, "/"); @@ -830,14 +1033,8 @@ void UfsListDir(char *path, uint8_t depth) { strcat(path, ep); UfsListDir(path, depth + 4); path[plen] = 0; +#endif } else { - #ifdef GUI_TRASH_FILE - char delpath[128]; - ext_snprintf_P(delpath, sizeof(delpath), UFS_FORM_SDC_HREFdel, ppe, epe); - #else - char delpath[2]; - delpath[0]=0; - #endif // GUI_TRASH_FILE #ifdef GUI_EDIT_FILE char editpath[128]; ext_snprintf_P(editpath, sizeof(editpath), UFS_FORM_SDC_HREFedit, ppe, epe); @@ -857,12 +1054,16 @@ void UfsListDir(char *path, uint8_t depth) { } #ifdef ESP32 -#define ESP32_DOWNLOAD_TASK +// this actually does not work reliably, as the +// webserver can close the connection before the download task completes +//#define ESP32_DOWNLOAD_TASK #endif // ESP32 uint8_t UfsDownloadFile(char *file) { File download_file; + AddLog(LOG_LEVEL_INFO, PSTR("UFS: File '%s' download"), file); + if (!dfsp->exists(file)) { AddLog(LOG_LEVEL_INFO, PSTR("UFS: File '%s' not found"), file); return 0; @@ -875,6 +1076,7 @@ uint8_t UfsDownloadFile(char *file) { } if (download_file.isDirectory()) { + AddLog(LOG_LEVEL_DEBUG, PSTR("UFS: File '%s' to download is directory"), file); download_file.close(); return 1; } @@ -887,13 +1089,7 @@ uint8_t UfsDownloadFile(char *file) { Webserver->setContentLength(flen); char attachment[100]; - char *cp; - for (uint32_t cnt = strlen(file); cnt >= 0; cnt--) { - if (file[cnt] == '/') { - cp = &file[cnt + 1]; - break; - } - } + char *cp = fileOnly(file); snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"), cp); Webserver->sendHeader(F("Content-Disposition"), attachment); WSSend(200, CT_APP_STREAM, ""); @@ -922,6 +1118,10 @@ uint8_t UfsDownloadFile(char *file) { #endif // ESP32_DOWNLOAD_TASK + +// to make this work I thing you wouold need to duplicate the client +// BEFORE starting the task, so that the webserver does not close it's +// copy of the client. #ifdef ESP32_DOWNLOAD_TASK download_file.close(); @@ -952,20 +1152,18 @@ void download_task(void *path) { WiFiClient download_Client; char *file = (char*) path; + AddLog(LOG_LEVEL_DEBUG, PSTR("UFS: ESP32 File '%s' to download"), file); + download_file = dfsp->open(file, UFS_FILE_READ); uint32_t flen = download_file.size(); + AddLog(LOG_LEVEL_DEBUG, PSTR("UFS: len %d to download"), flen); + download_Client = Webserver->client(); Webserver->setContentLength(flen); char attachment[100]; - char *cp; - for (uint32_t cnt = strlen(file); cnt >= 0; cnt--) { - if (file[cnt] == '/') { - cp = &file[cnt + 1]; - break; - } - } + char *cp = fileOnly(file); //snprintf_P(attachment, sizeof(attachment), PSTR("download file '%s' as '%s'"), file, cp); //Webserver->sendHeader(F("X-Tasmota-Debug"), attachment); snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"), cp); @@ -987,6 +1185,7 @@ void download_task(void *path) { UfsData.download_busy = false; vTaskDelete( NULL ); free(path); + AddLog(LOG_LEVEL_DEBUG, PSTR("UFS: esp32 sent file")); } #endif // ESP32_DOWNLOAD_TASK @@ -1065,7 +1264,8 @@ void UfsEditor(void) { } WSContentSend_P(HTTP_EDITOR_FORM_END); - WSContentSend_P(UFS_WEB_DIR, PSTR(D_MANAGE_FILE_SYSTEM)); + folderOnly(fname); + WSContentSend_P(UFS_WEB_DIR, fname, PSTR(D_MANAGE_FILE_SYSTEM)); WSContentStop(); } @@ -1100,6 +1300,23 @@ void UfsEditorUpload(void) { return; } + // recursively create folder(s) + char tmp[UFS_FILENAME_SIZE]; + char folder[UFS_FILENAME_SIZE] = ""; + strcpy(tmp, fname); + // zap file name off the end + folderOnly(tmp); + char *tf = strtok(tmp, "/"); + while(tf){ + if (*tf){ + strcat(folder, "/"); + strcat(folder, tf); + } + // we don;t care if it fails - it may already exist. + dfsp->mkdir(folder); + tf = strtok(nullptr, "/"); + } + File fp = dfsp->open(fname, "w"); if (!fp) { Web.upload_error = 1; @@ -1119,7 +1336,11 @@ void UfsEditorUpload(void) { fp.close(); - Webserver->sendHeader(F("Location"),F("/ufsu")); + // zap file name off the end + folderOnly(fname); + char t[20+UFS_FILENAME_SIZE] = "/ufsu?download="; + strcat(t, fname); + Webserver->sendHeader(F("Location"), t); Webserver->send(303); } @@ -1160,7 +1381,7 @@ bool Xdrv50(uint32_t function) { if (XdrvMailbox.index) { XdrvMailbox.index++; } else { - WSContentSend_PD(UFS_WEB_DIR, PSTR(D_MANAGE_FILE_SYSTEM)); + WSContentSend_PD(UFS_WEB_DIR, "/", PSTR(D_MANAGE_FILE_SYSTEM)); } } break;