diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 0acf2e5f77..10d5599729 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1265,6 +1265,15 @@ // This allows hosts to request long names for files and folders with M33 //#define LONG_FILENAME_HOST_SUPPORT + // Default M20 File Listing behavior + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + //#define M20_DEFER_DOS_FILENAMES // Hide DOS 8.3 filenames in listings by default + //#define M20_REPORT_LONG_FILENAMES // Include long filenames in listings by default + #endif + + // List folders ahead of the .gcode file listing + //#define M20_REPORT_DIRECTORY_NAMES + // Enable this option to scroll long filenames in the SD card menu //#define SCROLL_LONG_FILENAMES diff --git a/Marlin/src/core/language.h b/Marlin/src/core/language.h index 60d9aa6b72..f021f3dffb 100644 --- a/Marlin/src/core/language.h +++ b/Marlin/src/core/language.h @@ -121,6 +121,8 @@ #define STR_NO_MEDIA "No media" #define STR_BEGIN_FILE_LIST "Begin file list" #define STR_END_FILE_LIST "End file list" +#define STR_BEGIN_DIR_LIST "Begin dir list" +#define STR_END_DIR_LIST "End dir list" #define STR_INVALID_EXTRUDER "Invalid extruder" #define STR_INVALID_E_STEPPER "Invalid E stepper" #define STR_E_STEPPER_NOT_SPECIFIED "E stepper not specified" diff --git a/Marlin/src/gcode/sd/M20.cpp b/Marlin/src/gcode/sd/M20.cpp index 7ac4affdae..207b541e3e 100644 --- a/Marlin/src/gcode/sd/M20.cpp +++ b/Marlin/src/gcode/sd/M20.cpp @@ -32,9 +32,12 @@ */ void GcodeSuite::M20() { if (card.flag.mounted) { - SERIAL_ECHOLNPGM(STR_BEGIN_FILE_LIST); - card.ls(); - SERIAL_ECHOLNPGM(STR_END_FILE_LIST); + card.ls( + parser.boolval('S', DISABLED(M20_DEFER_DOS_FILENAMES)), + parser.boolval('L', ENABLED(M20_REPORT_LONG_FILENAMES)) + ); + if (DISABLED(LONG_FILENAME_HOST_SUPPORT) && parser.boolval('L')) + SERIAL_ECHOLNPGM("ERROR: LONG_FILENAME_HOST_SUPPORT Not Enabled!"); } else SERIAL_ECHO_MSG(STR_NO_MEDIA); diff --git a/Marlin/src/gcode/sd/M33.cpp b/Marlin/src/gcode/sd/M33.cpp index b611c8bc08..deb0047ce7 100644 --- a/Marlin/src/gcode/sd/M33.cpp +++ b/Marlin/src/gcode/sd/M33.cpp @@ -41,7 +41,7 @@ */ void GcodeSuite::M33() { - card.printLongPath(parser.string_arg); + card.printLongPath(parser.string_arg, true); } diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp index 1be5f4f2f6..ee70c5aed7 100644 --- a/Marlin/src/sd/cardreader.cpp +++ b/Marlin/src/sd/cardreader.cpp @@ -157,6 +157,7 @@ CardReader::CardReader() { // // Get a DOS 8.3 filename in its useful form +// Requires a file specified // char *createFilename(char * const buffer, const dir_t &p) { char *pos = buffer; @@ -225,20 +226,110 @@ void CardReader::selectByIndex(SdFile dir, const uint8_t index) { // // Get file/folder info for an item by name // -void CardReader::selectByName(SdFile dir, const char * const match) { +void CardReader::selectByName(SdFile dir, const char * const match, const bool debug/*=false*/) { dir_t p; for (uint8_t cnt = 0; dir.readDir(&p, longFilename) > 0; cnt++) { if (is_dir_or_gcode(p)) { createFilename(filename, p); - if (strcasecmp(match, filename) == 0) return; + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + // Check for match using Long_File_Name or short filename + if (strcasecmp(match, longFilename) == 0 || strcasecmp(match, filename) == 0) { + #if ENABLED(DEBUG_CARDREADER) + //debug = true; + if (debug) { + DEBUG_ECHOLNPAIR("\n" + " DEBUG -SelectByName- LONG FileName: ", longFilename, + "-- Created FileName: ", filename, + " -- Search Term: ", match, + " -- Match Success: TRUE" + ); + } + #endif + + // Workaround for a DOS8.3 name identical to long name. + if (strncasecmp(filename, longFilename, 3) != 0) { + //debug = true; + if (TERN0(DEBUG_CARDREADER, debug)) { + DEBUG_ECHOLNPAIR("DEBUG: Mismatch! Copying filename to longFilename"); + DEBUG_ECHOLNPAIR("DEBUG: filename = ", filename); + DEBUG_ECHOLNPAIR("DEBUG: longFilename = ", longFilename); + } + strcpy(longFilename, filename); + } + return; + } + else { + // Match Not Found -> Wipe out the filenames to avoid returning a path to a different file + //if (debug) DEBUG_ECHOLNPGM("FALSE"); + filename[0] = longFilename[0] = '\0'; + } + #else + if (strcasecmp(match, filename) == 0) { + if (debug) DEBUG_ECHOLNPGM(" DEBUG SelectByName - MatchFound"); + return; + } + else if (debug) + DEBUG_ECHOLNPGM("FALSE"); + #endif } } } +#if ENABLED(M20_REPORT_DIRECTORY_NAMES) + // + // Recursive method to list all directories within a folder + // + void CardReader::printDirListing(SdFile parent, const bool print_dos_names, const bool print_long_names, const char * const prepend/*=nullptr*/) { + dir_t p; + while (parent.readDir(&p, longFilename) > 0) { + if (DIR_IS_SUBDIR(&p)) { + + // Get the short name for the item, which we know is a folder + char dosFilename[FILENAME_LENGTH]; + createFilename(dosFilename, p); + + // Allocate enough stack space to do the work ( full path to a folder, trailing slash, and nul ) + const bool prepend_is_empty = (!prepend || prepend[0] == '\0'); + const size_t lenPrePend = prepend_is_empty ? 1 : strlen(prepend), + len = lenPrePend + strlen(dosFilename) + 1 + 1; + char path[len]; + + // Append the FOLDERNAME12/ to the passed string. + // It contains the full path to the "parent" argument. + // We now have the full path to the item in this folder. + strcpy(path, prepend_is_empty ? "/" : prepend); // root slash if prepend is empty + strcat(path, dosFilename); // FILENAME_LENGTH characters maximum + strcat(path, "/"); // 1 character + + #if DISABLED(LONG_FILENAME_HOST_SUPPORT) + SERIAL_ECHOLN(path); // All this work is already done! + #else + const size_t plen = lenPrePend + strlen(longFilename) + 2; + // Generate the name of the file path then display + char Fpath[plen]; + strcpy(Fpath, prepend_is_empty ? "/" : prepend); // root slash if prepend is empty + strcat(Fpath, longFilename); + strcat(Fpath, "/"); + SERIAL_ECHOLN(Fpath); // Echo Result + #endif + + // Get a new directory object using the full path + // and dive recursively into it. + SdFile child; + if (!child.open(&parent, dosFilename, O_READ)) { + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR(STR_SD_CANT_OPEN_SUBDIR, dosFilename); + } + printDirListing(child, print_dos_names, print_long_names, path); + } + } + } +#endif + // // Recursive method to list all files within a folder // -void CardReader::printListing(SdFile parent, const char * const prepend/*=nullptr*/) { +void CardReader::printListing(SdFile parent, const bool print_dos_names, const bool print_long_names, const char * const prepend/*=nullptr*/) { dir_t p; while (parent.readDir(&p, longFilename) > 0) { if (DIR_IS_SUBDIR(&p)) { @@ -249,7 +340,7 @@ void CardReader::printListing(SdFile parent, const char * const prepend/*=nullpt // Allocate enough stack space for the full path to a folder, trailing slash, and nul const bool prepend_is_empty = (!prepend || prepend[0] == '\0'); - const int len = (prepend_is_empty ? 1 : strlen(prepend)) + strlen(dosFilename) + 1 + 1; + const size_t len = (prepend_is_empty ? 1 : strlen(prepend)) + strlen(dosFilename) + 1 + 1; char path[len]; // Append the FOLDERNAME12/ to the passed string. @@ -268,26 +359,49 @@ void CardReader::printListing(SdFile parent, const char * const prepend/*=nullpt SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR(STR_SD_CANT_OPEN_SUBDIR, dosFilename); } - printListing(child, path); + printListing(child, print_dos_names, print_long_names, path); // close() is done automatically by destructor of SdFile } else if (is_dir_or_gcode(p)) { - createFilename(filename, p); - if (prepend) SERIAL_ECHO(prepend); - SERIAL_ECHO(filename); - SERIAL_CHAR(' '); - SERIAL_ECHOLN(p.fileSize); - } - } -} + createFilename(filename, p); // Parse the string and retrieve a DOS 8.3 FileName+ -// -// List all files on the SD card -// -void CardReader::ls() { - if (flag.mounted) { - root.rewind(); - printListing(root); + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + + if (print_dos_names) { // Print the path element and DOS 8.3 filename + if (prepend) SERIAL_ECHO(prepend); + SERIAL_ECHO(filename); + } + + if (print_long_names) { // Print the Long FileName + if (print_dos_names) SERIAL_ECHOPGM(" --> "); + if (prepend) { + // Allocate stack to join strings before passing to printLongPath + const size_t plen = strlen(prepend), + len = plen + strlen(filename) + 1; + // Generate the name of the file path then display + char Fpath[len]; + strcpy(Fpath, prepend); + strcpy(&Fpath[plen], filename); + printLongPath(Fpath); + } + else { + // File is in the root directory + const int len = 1 + strlen(filename) + 1; // slash, filename, terninating nul + char Fpath[len]; + Fpath[0] = '/'; + strcpy(&Fpath[1], filename); + printLongPath(Fpath); + } + } + #else + // If Long_FileName_Host_Support is disabled -> Print the DOS8.3 filenames + if (prepend) SERIAL_ECHO(prepend); + SERIAL_ECHO(filename); + #endif + + // Print the FileSize + SERIAL_ECHOLNPAIR(" ", p.fileSize, " bytes"); + } } } @@ -296,7 +410,7 @@ void CardReader::ls() { // // Get a long pretty path based on a DOS 8.3 path // - void CardReader::printLongPath(char * const path) { + void CardReader::printLongPath(char * const path, const bool print_eol/*=false*/) { int i, pathLen = strlen(path); @@ -331,7 +445,7 @@ void CardReader::ls() { // If the filename was printed then that's it if (!flag.filenameIsDir) break; - // SERIAL_ECHOPGM("Opening dir: "); SERIAL_ECHOLN(segment); + //SERIAL_ECHOPGM("Opening dir: "); SERIAL_ECHOLN(segment); // Open the sub-item as the new dive parent SdFile dir; @@ -347,11 +461,30 @@ void CardReader::ls() { } // while irewind(); // Redundant rewind, but no harm item_name_adr++; DEBUG_ECHOLNPAIR("diveToFile: CWD to root: ", hex_address((void*)diveDir)); if (update_cwd) workDirDepth = 0; // The cwd can be updated for the benefit of sub-programs } else + DEBUG_ECHOLNPGM("Starting from WorkingDirectory"); diveDir = &workDir; // Dive from workDir (as set by the UI) startDir = diveDir; @@ -839,12 +975,19 @@ const char* CardReader::diveToFile(const bool update_cwd, SdFile*& diveDir, cons strncpy(dosSubdirname, item_name_adr, len); dosSubdirname[len] = 0; - if (echo) SERIAL_ECHOLN(dosSubdirname); - DEBUG_ECHOLNPAIR("diveToFile: sub = ", hex_address((void*)sub)); - // Open diveDir (closing first) + // Close before dive or select sub->close(); + + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + // Select the folder by name and ensure the DOS8.3 name is used + DEBUG_ECHOLNPAIR("Search for folder: ", dosSubdirname); + selectByName(*diveDir, dosSubdirname); + if (filename[0]) strcpy(dosSubdirname, filename); + #endif + + // Open diveDir (closing first, above) if (!sub->open(diveDir, dosSubdirname, O_READ)) { openFailed(dosSubdirname); item_name_adr = nullptr; @@ -874,7 +1017,7 @@ const char* CardReader::diveToFile(const bool update_cwd, SdFile*& diveDir, cons // Next path atom address item_name_adr = name_end + 1; - } + } // end while loop if (update_cwd) { workDir = *diveDir; @@ -883,6 +1026,18 @@ const char* CardReader::diveToFile(const bool update_cwd, SdFile*& diveDir, cons TERN_(SDCARD_SORT_ALPHA, presort()); } + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + if (!is_new_file) { + // Expecting a file that already exists - Search and select + diveDir->rewind(); + DEBUG_ECHOLNPAIR("Search for file: ", item_name_adr); + selectByName(*diveDir, item_name_adr); + DEBUG_ECHOLNPAIR("diveToFile: Return String = ", filename); + return filename; + } + #endif + + DEBUG_ECHOLNPAIR("diveToFile: Return String = ", item_name_adr); return item_name_adr; } diff --git a/Marlin/src/sd/cardreader.h b/Marlin/src/sd/cardreader.h index c6fe37400c..0c7619daa0 100644 --- a/Marlin/src/sd/cardreader.h +++ b/Marlin/src/sd/cardreader.h @@ -81,7 +81,7 @@ public: static void mount(); static void release(); static inline bool isMounted() { return flag.mounted; } - static void ls(); + static void ls(const bool print_dos_names, const bool print_long_names); // Handle media insert/remove static void manage_media(); @@ -103,8 +103,9 @@ public: static void removeFile(const char * const name); static inline char* longest_filename() { return longFilename[0] ? longFilename : filename; } + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) - static void printLongPath(char * const path); // Used by M33 + static void printLongPath(char * const path, const bool print_eol=false); // Used by M33 #endif // Working Directory for SD card menu @@ -135,7 +136,7 @@ public: static inline uint8_t percentDone() { return (isFileOpen() && filesize) ? sdpos / ((filesize + 99) / 100) : 0; } // Helper for open and remove - static const char* diveToFile(const bool update_cwd, SdFile*& curDir, const char * const path, const bool echo=false); + static const char* diveToFile(const bool update_cwd, SdFile*& curDir, const char * const path, const bool is_new_file=false); #if ENABLED(SDCARD_SORT_ALPHA) static void presort(); @@ -272,8 +273,9 @@ private: static bool is_dir_or_gcode(const dir_t &p); static int countItems(SdFile dir); static void selectByIndex(SdFile dir, const uint8_t index); - static void selectByName(SdFile dir, const char * const match); - static void printListing(SdFile parent, const char * const prepend=nullptr); + static void selectByName(SdFile dir, const char * const match, const bool debug=false); + static void printListing(SdFile parent, const bool print_dos_names, const bool print_long_names, const char * const prepend=nullptr); + static void printDirListing(SdFile parent, const bool print_dos_names, const bool print_long_names, const char * const prepend=nullptr); #if ENABLED(SDCARD_SORT_ALPHA) static void flush_presort();