🩹 Fix up SD card sorting (#28218)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
ellensp
2025-12-16 19:10:40 +13:00
committed by GitHub
parent 54dfe6237c
commit ec752068a5
10 changed files with 107 additions and 91 deletions

View File

@@ -1887,17 +1887,21 @@
// SD Card Sorting options
#if ENABLED(SDCARD_SORT_ALPHA)
#define SDSORT_REVERSE false // Default to sorting file names in reverse order.
#define SDSORT_LIMIT 40 // Maximum number of sorted items (10-256). Costs 27 bytes each.
#define SDSORT_FOLDERS -1 // -1=above 0=none 1=below
#define SDSORT_GCODE false // Enable G-code M34 to set sorting behaviors: M34 S<-1|0|1> F<-1|0|1>
#define SDSORT_USES_RAM false // Pre-allocate a static array for faster pre-sorting.
#define SDSORT_USES_STACK false // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.)
#define SDSORT_CACHE_NAMES false // Keep sorted items in RAM longer for speedy performance. Most expensive option.
#define SDSORT_DYNAMIC_RAM false // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use!
#define SDSORT_CACHE_VFATS 2 // Maximum number of 13-byte VFAT entries to use for sorting.
// Note: Only affects SCROLL_LONG_FILENAMES with SDSORT_CACHE_NAMES but not SDSORT_DYNAMIC_RAM.
#define SDSORT_QUICK true // Use Quick Sort as a sorting algorithm. Otherwise use Bubble Sort.
#define SDSORT_QUICK true // Use Quick Sort as a sorting algorithm. Otherwise use Bubble Sort.
#define SDSORT_REVERSE false // Default to sorting file names in reverse order.
#define SDSORT_LIMIT 40 // Maximum number of sorted items (10-256). Costs 27 bytes each.
#define SDSORT_FOLDERS -1 // -1=above 0=none 1=below
#define SDSORT_GCODE false // Enable G-code M34 to set sorting behaviors: M34 S<-1|0|1> F<-1|0|1>
#define SDSORT_USES_STACK false // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.)
#define SDSORT_USES_RAM false // Pre-allocate a static array for faster pre-sorting.
#if ENABLED(SDSORT_USES_RAM)
#define SDSORT_CACHE_NAMES false // Keep sorted items in RAM longer for speedy performance. Most expensive option.
#if ENABLED(SDSORT_CACHE_NAMES)
#define SDSORT_DYNAMIC_RAM false // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use!
#define SDSORT_CACHE_VFATS 2 // Maximum number of 13-byte VFAT entries to use for sorting.
// Note: Only affects SCROLL_LONG_FILENAMES with SDSORT_CACHE_NAMES but not SDSORT_DYNAMIC_RAM.
#endif
#endif
#endif
// Allow international symbols in long filenames. To display correctly, the

View File

@@ -26,6 +26,7 @@
#include "../gcode.h"
#include "../../sd/cardreader.h"
#include "../../lcd/marlinui.h"
/**
* M34: Media Sorting
@@ -33,7 +34,7 @@
* Set Media Sorting Options
*
* Parameters:
* S<inr> Sorting Order:
* S<int> Sorting Order:
* S Default sorting (i.e., SDSORT_REVERSE)
* S-1 Reverse alpha sorting
* S0 FID Order (not always newest)
@@ -46,11 +47,16 @@
* F1 Folders after files
*/
void GcodeSuite::M34() {
if (parser.seen('S')) card.setSortOn(SortFlag(parser.ushortval('S', TERN(SDSORT_REVERSE, AS_REV, AS_FWD))));
if (parser.seen('S'))
card.setSortOn(SortFlag(parser.ushortval('S', TERN(SDSORT_REVERSE, AS_REV, AS_FWD))));
if (parser.seenval('F')) {
const int v = parser.value_long();
card.setSortFolders(v < 0 ? -1 : v > 0 ? 1 : 0);
}
ui.refresh();
//if (parser.seen('R')) card.setSortReverse(parser.value_bool());
}

View File

@@ -3612,6 +3612,14 @@
#endif
#endif
#if ALL(SDCARD_SORT_ALPHA, SDSORT_CACHE_NAMES) && DISABLED(SDSORT_DYNAMIC_RAM)
#if SDSORT_CACHE_VFATS > VFAT_ENTRIES_LIMIT
#undef SDSORT_CACHE_VFATS
#define SDSORT_CACHE_VFATS VFAT_ENTRIES_LIMIT
#define SDSORT_CACHE_VFATS_WARNING 1
#endif
#endif
// Fallback SPI Speed for SD
#if HAS_MEDIA && !defined(SD_SPI_SPEED)
#define SD_SPI_SPEED SPI_FULL_SPEED

View File

@@ -429,14 +429,8 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
#error "SDSORT_DYNAMIC_RAM requires SDSORT_CACHE_NAMES."
#endif
#if ENABLED(SDSORT_CACHE_NAMES) && DISABLED(SDSORT_DYNAMIC_RAM)
#if SDSORT_CACHE_VFATS < 2
#error "SDSORT_CACHE_VFATS must be 2 or greater!"
#elif SDSORT_CACHE_VFATS > VFAT_ENTRIES_LIMIT
#undef SDSORT_CACHE_VFATS
#define SDSORT_CACHE_VFATS VFAT_ENTRIES_LIMIT
#define SDSORT_CACHE_VFATS_WARNING 1
#endif
#if ENABLED(SDSORT_CACHE_NAMES) && DISABLED(SDSORT_DYNAMIC_RAM) && SDSORT_CACHE_VFATS < 2
#error "SDSORT_CACHE_VFATS must be 2 or greater!"
#endif
#endif

View File

@@ -349,6 +349,7 @@ void menu_main() {
SUBMENU(MSG_MEDIA_MENU_SD, MEDIA_MENU_GATEWAY);
else if (TERN0(SHOW_UNMOUNTED_DRIVES, card.isSDCardInserted()))
SUBMENU(MSG_MEDIA_MENU_SD, MEDIA_MENU_GATEWAY_SD);
if (card.isFlashDriveMounted())
SUBMENU(MSG_MEDIA_MENU_USB, MEDIA_MENU_GATEWAY);
else if (TERN0(SHOW_UNMOUNTED_DRIVES, card.isFlashDriveInserted()))

View File

@@ -101,7 +101,10 @@ class MenuItem_sdfolder : public MenuItem_sdbase {
}
};
//
// Shortcut menu items to go directly to inserted — not necessarily mounted — drives
//
void menu_file_selector_sd() {
if (!card.isSDCardSelected()) {
card.release();
@@ -111,7 +114,6 @@ void menu_file_selector_sd() {
ui.goto_screen(menu_file_selector);
}
// Shortcut menu items to go directly to inserted — not necessarily mounted — drives
void menu_file_selector_usb() {
if (!card.isFlashDriveSelected()) {
card.release();
@@ -121,7 +123,6 @@ void menu_file_selector_usb() {
ui.goto_screen(menu_file_selector);
}
// Shortcut menu items to go directly to inserted — not necessarily mounted — drives
void menu_file_selector() {
ui.encoder_direction_menus();

View File

@@ -102,23 +102,15 @@ int16_t CardReader::nrItems = -1;
//bool CardReader::sort_reverse;
#endif
#if ENABLED(SDSORT_DYNAMIC_RAM)
uint8_t *CardReader::sort_order;
#else
uint8_t CardReader::sort_order[SDSORT_LIMIT];
#endif
uint8_t *CardReader::sort_order;
#if ENABLED(SDSORT_USES_RAM)
#if ENABLED(SDSORT_CACHE_NAMES)
#if ENABLED(SDSORT_DYNAMIC_RAM)
char **CardReader::sortshort, **CardReader::sortnames;
#else
char CardReader::sortshort[SDSORT_LIMIT][FILENAME_LENGTH];
char CardReader::sortnames[SDSORT_LIMIT][SORTED_LONGNAME_STORAGE];
#endif
#elif DISABLED(SDSORT_USES_STACK)
char CardReader::sortnames[SDSORT_LIMIT][SORTED_LONGNAME_STORAGE];
char (*CardReader::sortshort)[FILENAME_LENGTH];
#endif
#if ENABLED(SDSORT_CACHE_NAMES) || DISABLED(SDSORT_USES_STACK)
char (*CardReader::sortnames)[SORTED_LONGNAME_STORAGE];
#endif
#if HAS_FOLDER_SORTING
@@ -163,6 +155,19 @@ uint32_t CardReader::filesize, CardReader::sdpos;
CardReader::CardReader() {
#if ENABLED(SDCARD_SORT_ALPHA)
#if DISABLED(SDSORT_DYNAMIC_RAM)
static uint8_t sort_order_static[SDSORT_LIMIT];
sort_order = sort_order_static;
#endif
#if ENABLED(SDSORT_CACHE_NAMES) && DISABLED(SDSORT_DYNAMIC_RAM)
static char sortshort_static[SDSORT_LIMIT][FILENAME_LENGTH];
sortshort = sortshort_static;
#endif
#if ENABLED(SDSORT_CACHE_NAMES) && !ALL(SDSORT_DYNAMIC_RAM, SDSORT_USES_STACK)
static char sortnames_static[SDSORT_LIMIT][SORTED_LONGNAME_STORAGE];
sortnames = sortnames_static;
#endif
sort_count = 0;
#if ENABLED(SDSORT_GCODE)
sort_alpha = TERN(SDSORT_REVERSE, AS_REV, AS_FWD);
@@ -204,8 +209,8 @@ char *createFilename(char * const buffer, const dir_t &p) {
return buffer;
}
inline bool extIsBIN(char *ext) {
return ext[0] == 'B' && ext[1] == 'I' && ext[2] == 'N';
inline bool extIsBIN(char * const ext) {
return ext && ext[0] == 'B' && ext[1] == 'I' && ext[2] == 'N';
}
//
@@ -1122,7 +1127,8 @@ void CardReader::selectFileByIndex(const int16_t nr) {
strcpy(filename, sortshort[nr]);
strcpy(longFilename, sortnames[nr]);
TERN_(HAS_FOLDER_SORTING, flag.filenameIsDir = IS_DIR(nr));
setBinFlag(extIsBIN(strrchr(filename, '.') + 1));
char *ext = strrchr(filename, '.');
setBinFlag(extIsBIN(ext ? ext + 1 : nullptr));
return;
}
#endif
@@ -1136,14 +1142,17 @@ void CardReader::selectFileByIndex(const int16_t nr) {
//
void CardReader::selectFileByName(const char * const match) {
#if ENABLED(SDSORT_CACHE_NAMES)
for (int16_t nr = 0; nr < sort_count; nr++)
if (strcasecmp(match, sortshort[nr]) == 0) {
strcpy(filename, sortshort[nr]);
for (int16_t nr = 0; nr < sort_count; nr++) {
const char *name = sortshort[nr];
if (strcasecmp(match, name) == 0) {
strcpy(filename, name);
strcpy(longFilename, sortnames[nr]);
TERN_(HAS_FOLDER_SORTING, flag.filenameIsDir = IS_DIR(nr));
setBinFlag(extIsBIN(strrchr(filename, '.') + 1));
char *ext = strrchr(filename, '.');
setBinFlag(extIsBIN(ext ? ext + 1 : nullptr));
return;
}
}
#endif
workDir.rewind();
selectByName(workDir, match);
@@ -1306,11 +1315,11 @@ void CardReader::cdroot() {
#if ENABLED(SDSORT_USES_RAM)
#if ENABLED(SDSORT_DYNAMIC_RAM)
// Use dynamic method to copy long filename
#define SET_SORTNAME(I) (sortnames[I] = strdup(longest_filename()))
#define SET_SORTNAME(I) strlcpy(sortnames[I], longest_filename(), SORTED_LONGNAME_STORAGE)
#if ENABLED(SDSORT_CACHE_NAMES)
// When caching also store the short name, since
// we're replacing the selectFileByIndex() behavior.
#define SET_SORTSHORT(I) (sortshort[I] = strdup(filename))
#define SET_SORTSHORT(I) strlcpy(sortshort[I], filename, SORTED_SHORTNAME_STORAGE)
#else
#define SET_SORTSHORT(I) NOOP
#endif
@@ -1366,8 +1375,8 @@ void CardReader::cdroot() {
// If using dynamic ram for names, allocate on the heap.
#if ENABLED(SDSORT_CACHE_NAMES)
#if ENABLED(SDSORT_DYNAMIC_RAM)
sortshort = new char*[fileCnt];
sortnames = new char*[fileCnt];
sortshort = new char[fileCnt][SORTED_SHORTNAME_STORAGE];
sortnames = new char[fileCnt][SORTED_LONGNAME_STORAGE];
#endif
#elif ENABLED(SDSORT_USES_STACK)
char sortnames[fileCnt][SORTED_LONGNAME_STORAGE];
@@ -1388,7 +1397,7 @@ void CardReader::cdroot() {
// Init sort order.
for (int16_t i = 0; i < fileCnt; i++) {
sort_order[i] = i;
sort_order[i] = uint8_t(i);
// If using RAM then read all filenames now.
#if ENABLED(SDSORT_USES_RAM)
selectFileByIndex(i);
@@ -1427,18 +1436,18 @@ void CardReader::cdroot() {
const char *name1 = sortnames[o1], *name2 = sortnames[o2];
#endif
#if HAS_FOLDER_SORTING
#if ENABLED(SDSORT_GCODE)
if (sort_folders && dir1 != dir2)
return (sort_folders > 0) ? dir1 : !dir1;
#else
if (dir1 != dir2)
return (SDSORT_FOLDERS > 0) ? dir1 : !dir1;
#endif
#if ENABLED(SDSORT_GCODE)
if (sort_folders && dir1 != dir2)
return (sort_folders > 0) ? !dir1 : dir1;
#elif SDSORT_FOLDERS
if (dir1 != dir2)
return (SDSORT_FOLDERS > 0) ? !dir1 : dir1;
#endif
const bool sort = strcasecmp(name1, name2) < 0;
return (TERN(SDSORT_GCODE, sort_alpha == AS_REV, ENABLED(SDSORT_REVERSE))) ? !sort : sort;
const bool sort = strcasecmp(name1, name2) < 0,
reversed = TERN(SDSORT_GCODE, sort_alpha == AS_REV, ENABLED(SDSORT_REVERSE));
return reversed ? !sort : sort;
};
auto partition = [&](uint8_t* arr, int16_t low, int16_t high) -> int16_t {
@@ -1523,7 +1532,11 @@ void CardReader::cdroot() {
const bool sort = strcasecmp(n1, n2) > 0;
return (TERN(SDSORT_GCODE, sort_alpha == AS_REV, ENABLED(SDSORT_REVERSE))) ? !sort : sort;
};
#define _SORT_CMP_FILE() _sort_cmp_file(TERN(SDSORT_USES_RAM, sortnames[o1], name1), TERN(SDSORT_USES_RAM, sortnames[o2], name2))
#if ENABLED(SDSORT_USES_RAM)
#define _SORT_CMP_FILE() _sort_cmp_file(sortnames[o1], sortnames[o2])
#else
#define _SORT_CMP_FILE() _sort_cmp_file(name1, name2)
#endif
#if HAS_FOLDER_SORTING
#if ENABLED(SDSORT_USES_RAM)
@@ -1576,19 +1589,16 @@ void CardReader::cdroot() {
#endif // Bubble Sort
// Using RAM but not keeping names around
#if ENABLED(SDSORT_USES_RAM) && DISABLED(SDSORT_CACHE_NAMES)
#if ENABLED(SDSORT_DYNAMIC_RAM)
for (int16_t i = 0; i < fileCnt; ++i) free(sortnames[i]);
TERN_(HAS_FOLDER_SORTING, delete [] isDir);
#endif
#if ALL(HAS_FOLDER_SORTING, SDSORT_DYNAMIC_RAM) && DISABLED(SDSORT_CACHE_NAMES)
delete [] isDir;
#endif
}
else {
sort_order[0] = 0;
sort_order[0] = uint8_t(0);
#if ALL(SDSORT_USES_RAM, SDSORT_CACHE_NAMES)
#if ENABLED(SDSORT_DYNAMIC_RAM)
sortnames = new char*[1];
sortshort = new char*[1];
sortnames = new char[1][SORTED_LONGNAME_STORAGE];
sortshort = new char[1][SORTED_SHORTNAME_STORAGE];
#endif
selectFileByIndex(0);
SET_SORTNAME(0);
@@ -1609,10 +1619,6 @@ void CardReader::cdroot() {
#if ENABLED(SDSORT_DYNAMIC_RAM)
delete [] sort_order;
#if ENABLED(SDSORT_CACHE_NAMES)
for (uint8_t i = 0; i < sort_count; ++i) {
free(sortshort[i]); // strdup
free(sortnames[i]); // strdup
}
delete [] sortshort;
delete [] sortnames;
#endif

View File

@@ -360,12 +360,8 @@ private:
//static bool sort_reverse; // Flag to enable / disable reverse sorting
#endif
// By default the sort index is statically allocated
#if ENABLED(SDSORT_DYNAMIC_RAM)
static uint8_t *sort_order;
#else
static uint8_t sort_order[SDSORT_LIMIT];
#endif
// Pointer to the static or dynamic sort index
static uint8_t *sort_order;
#if ALL(SDSORT_USES_RAM, SDSORT_CACHE_NAMES) && DISABLED(SDSORT_DYNAMIC_RAM)
#define SORTED_LONGNAME_MAXLEN (SDSORT_CACHE_VFATS) * (FILENAME_LENGTH)
@@ -375,20 +371,17 @@ private:
#define SORTED_LONGNAME_STORAGE SORTED_LONGNAME_MAXLEN
#endif
#define SORTED_SHORTNAME_STORAGE FILENAME_LENGTH
// Cache filenames to speed up SD menus.
#if ENABLED(SDSORT_USES_RAM)
// If using dynamic ram for names, allocate on the heap.
// Pointers to static or dynamic arrays of sorted names
#if ENABLED(SDSORT_CACHE_NAMES)
#if ENABLED(SDSORT_DYNAMIC_RAM)
static char **sortshort, **sortnames;
#else
static char sortshort[SDSORT_LIMIT][FILENAME_LENGTH];
#endif
static char (*sortshort)[SORTED_SHORTNAME_STORAGE];
#endif
#if (ENABLED(SDSORT_CACHE_NAMES) && DISABLED(SDSORT_DYNAMIC_RAM)) || NONE(SDSORT_CACHE_NAMES, SDSORT_USES_STACK)
static char sortnames[SDSORT_LIMIT][SORTED_LONGNAME_STORAGE];
#if ENABLED(SDSORT_CACHE_NAMES) || DISABLED(SDSORT_USES_STACK)
static char (*sortnames)[SORTED_LONGNAME_STORAGE];
#endif
// Folder sorting uses an isDir array when caching items.

View File

@@ -17,14 +17,16 @@ opt_set MOTHERBOARD BOARD_SMOOTHIEBOARD \
EXTRUDERS 2 TEMP_SENSOR_0 -5 TEMP_SENSOR_1 -4 TEMP_SENSOR_BED 5 TEMP_0_CS_PIN P1_29 \
MAG_MOUNTED_PROBE_SERVO_NR 0 GRID_MAX_POINTS_X 16 \
NOZZLE_CLEAN_START_POINT "{ { 10, 10, 3 }, { 10, 10, 3 } }" \
NOZZLE_CLEAN_END_POINT "{ { 10, 20, 3 }, { 10, 20, 3 } }"
opt_enable TFTGLCD_PANEL_SPI SDSUPPORT ADAPTIVE_FAN_SLOWING REPORT_ADAPTIVE_FAN_SLOWING TEMP_TUNING_MAINTAIN_FAN \
NOZZLE_CLEAN_END_POINT "{ { 10, 20, 3 }, { 10, 20, 3 } }" \
SDSORT_GCODE true SDSORT_USES_RAM true SDSORT_CACHE_NAMES true SDSORT_DYNAMIC_RAM true
opt_enable TFTGLCD_PANEL_SPI SDSUPPORT SDCARD_SORT_ALPHA \
ADAPTIVE_FAN_SLOWING REPORT_ADAPTIVE_FAN_SLOWING TEMP_TUNING_MAINTAIN_FAN \
MAX31865_SENSOR_OHMS_0 MAX31865_CALIBRATION_OHMS_0 \
MAG_MOUNTED_PROBE AUTO_BED_LEVELING_BILINEAR G29_RETRY_AND_RECOVER Z_MIN_PROBE_REPEATABILITY_TEST DEBUG_LEVELING_FEATURE \
BABYSTEPPING BABYSTEP_XY BABYSTEP_ZPROBE_OFFSET BED_TRAMMING_USE_PROBE BED_TRAMMING_VERIFY_RAISED \
PRINTCOUNTER NOZZLE_PARK_FEATURE NOZZLE_CLEAN_FEATURE SLOW_PWM_HEATERS PIDTEMPBED EEPROM_SETTINGS INCH_MODE_SUPPORT TEMPERATURE_UNITS_SUPPORT \
Z_SAFE_HOMING ADVANCED_PAUSE_FEATURE PARK_HEAD_ON_PAUSE \
LCD_INFO_MENU ARC_SUPPORT BEZIER_CURVE_SUPPORT EXTENDED_CAPABILITIES_REPORT AUTO_REPORT_TEMPERATURES SDCARD_SORT_ALPHA EMERGENCY_PARSER
LCD_INFO_MENU ARC_SUPPORT BEZIER_CURVE_SUPPORT EXTENDED_CAPABILITIES_REPORT AUTO_REPORT_TEMPERATURES EMERGENCY_PARSER
exec_test $1 $2 "Smoothieboard with TFTGLCD_PANEL_SPI and many features" "$3"
#restore_configs
@@ -80,6 +82,7 @@ opt_set MOTHERBOARD BOARD_BTT_SKR_V1_4_TURBO SERIAL_PORT -1 \
Z_STEPPER_ALIGN_ITERATIONS 10 DEFAULT_STEPPER_TIMEOUT_SEC 0 \
SLOWDOWN_DIVISOR 16 SDCARD_CONNECTION ONBOARD BLOCK_BUFFER_SIZE 64 \
CHOPPER_TIMING CHOPPER_DEFAULT_24V MMU_SERIAL_PORT 0 \
SDSORT_GCODE true SDSORT_USES_RAM true SDSORT_CACHE_NAMES true \
Z_MIN_ENDSTOP_HIT_STATE HIGH
opt_enable PIDTEMPBED \
FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE \

View File

@@ -21,8 +21,8 @@ opt_set MOTHERBOARD BOARD_TEENSY35_36 \
NOZZLE_CLEAN_START_POINT "{ { 10, 10, 3 }, { 10, 10, 3 } }" \
NOZZLE_CLEAN_END_POINT "{ { 10, 20, 3 }, { 10, 20, 3 } }" \
MANUAL_MOVE_DISTANCE_MM "100, 50, 10, 10, 0.1" \
MANUAL_MOVE_DISTANCE_IN "1.000, 0.500, 0.100, 0.010, 0.001"
MANUAL_MOVE_DISTANCE_IN "1.000, 0.500, 0.100, 0.010, 0.001" \
SDSORT_FOLDERS 0 SDSORT_USES_RAM true SDSORT_CACHE_NAMES true SDSORT_DYNAMIC_RAM true
opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT SDCARD_SORT_ALPHA \
LCD_INFO_MENU LCD_PRINTER_INFO_IS_BOOTSCREEN TURBO_BACK_MENU_ITEM PREHEAT_SHORTCUT_MENU_ITEM \
FILAMENT_WIDTH_SENSOR FILAMENT_LCD_DISPLAY CALIBRATION_GCODE BAUD_RATE_GCODE SOUND_MENU_ITEM \