✨ Long filename open/create/write (#23526)
Co-authored-by: Scott Lahteine <github@thinkyhead.com>
This commit is contained in:
parent
8695f462b7
commit
e704de9bb0
|
@ -1525,33 +1525,23 @@
|
|||
// LCD's font must contain the characters. Check your selected LCD language.
|
||||
//#define UTF_FILENAME_SUPPORT
|
||||
|
||||
// This allows hosts to request long names for files and folders with M33
|
||||
//#define LONG_FILENAME_HOST_SUPPORT
|
||||
//#define LONG_FILENAME_HOST_SUPPORT // Get the long filename of a file/folder with 'M33 <dosname>' and list long filenames with 'M20 L'
|
||||
//#define LONG_FILENAME_WRITE_SUPPORT // Create / delete files with long filenames via M28, M30, and Binary Transfer Protocol
|
||||
|
||||
// Enable this option to scroll long filenames in the SD card menu
|
||||
//#define SCROLL_LONG_FILENAMES
|
||||
//#define SCROLL_LONG_FILENAMES // Scroll long filenames in the SD card menu
|
||||
|
||||
// Leave the heaters on after Stop Print (not recommended!)
|
||||
//#define SD_ABORT_NO_COOLDOWN
|
||||
//#define SD_ABORT_NO_COOLDOWN // Leave the heaters on after Stop Print (not recommended!)
|
||||
|
||||
/**
|
||||
* This option allows you to abort SD printing when any endstop is triggered.
|
||||
* This feature must be enabled with "M540 S1" or from the LCD menu.
|
||||
* To have any effect, endstops must be enabled during SD printing.
|
||||
* Abort SD printing when any endstop is triggered.
|
||||
* This feature is enabled with 'M540 S1' or from the LCD menu.
|
||||
* Endstops must be activated for this option to work.
|
||||
*/
|
||||
//#define SD_ABORT_ON_ENDSTOP_HIT
|
||||
|
||||
/**
|
||||
* This option makes it easier to print the same SD Card file again.
|
||||
* On print completion the LCD Menu will open with the file selected.
|
||||
* You can just click to start the print, or navigate elsewhere.
|
||||
*/
|
||||
//#define SD_REPRINT_LAST_SELECTED_FILE
|
||||
//#define SD_REPRINT_LAST_SELECTED_FILE // On print completion open the LCD Menu and select the same file
|
||||
|
||||
/**
|
||||
* Auto-report SdCard status with M27 S<seconds>
|
||||
*/
|
||||
//#define AUTO_REPORT_SD_STATUS
|
||||
//#define AUTO_REPORT_SD_STATUS // Auto-report media status with 'M27 S<seconds>'
|
||||
|
||||
/**
|
||||
* Support for USB thumb drives using an Arduino USB Host Shield or
|
||||
|
|
|
@ -56,6 +56,8 @@ void GcodeSuite::M502() {
|
|||
/**
|
||||
* M503: print settings currently in memory
|
||||
*
|
||||
* S<bool> : Include / exclude header comments in the output. (Default: S1)
|
||||
*
|
||||
* With CONFIGURATION_EMBEDDING:
|
||||
* C<flag> : Save the full Marlin configuration to SD Card as "mc.zip"
|
||||
*/
|
||||
|
|
|
@ -154,6 +154,12 @@ void GcodeSuite::M115() {
|
|||
// LONG_FILENAME_HOST_SUPPORT (M33)
|
||||
cap_line(F("LONG_FILENAME"), ENABLED(LONG_FILENAME_HOST_SUPPORT));
|
||||
|
||||
// LONG_FILENAME_WRITE_SUPPORT (M23, M28, M30...)
|
||||
cap_line(F("LFN_WRITE"), ENABLED(LONG_FILENAME_WRITE_SUPPORT));
|
||||
|
||||
// CUSTOM_FIRMWARE_UPLOAD (M20 F)
|
||||
cap_line(F("CUSTOM_FIRMWARE_UPLOAD"), ENABLED(CUSTOM_FIRMWARE_UPLOAD));
|
||||
|
||||
// EXTENDED_M20 (M20 L)
|
||||
cap_line(F("EXTENDED_M20"), ENABLED(LONG_FILENAME_HOST_SUPPORT));
|
||||
|
||||
|
@ -179,7 +185,7 @@ void GcodeSuite::M115() {
|
|||
cap_line(F("MEATPACK"), SERIAL_IMPL.has_feature(port, SerialFeature::MeatPack));
|
||||
|
||||
// CONFIG_EXPORT
|
||||
cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIG_EMBED_AND_SAVE_TO_SD));
|
||||
cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIGURATION_EMBEDDING));
|
||||
|
||||
// Machine Geometry
|
||||
#if ENABLED(M115_GEOMETRY_REPORT)
|
||||
|
|
|
@ -89,6 +89,7 @@ bool SdBaseFile::addDirCluster() {
|
|||
}
|
||||
|
||||
// cache a file's directory entry
|
||||
// cache the current "dirBlock_" and return the entry at index "dirIndex_"
|
||||
// return pointer to cached entry or null for failure
|
||||
dir_t* SdBaseFile::cacheDirEntry(uint8_t action) {
|
||||
if (!vol_->cacheRawBlock(dirBlock_, action)) return nullptr;
|
||||
|
@ -384,6 +385,20 @@ int8_t SdBaseFile::lsPrintNext(uint8_t flags, uint8_t indent) {
|
|||
return DIR_IS_FILE(&dir) ? 1 : 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a checksum for an 8.3 filename
|
||||
*
|
||||
* \param name The 8.3 file name to calculate
|
||||
*
|
||||
* \return The checksum byte
|
||||
*/
|
||||
uint8_t lfn_checksum(const uint8_t *name) {
|
||||
uint8_t sum = 0;
|
||||
for (uint8_t i = 11; i; i--)
|
||||
sum = ((sum & 1) << 7) + (sum >> 1) + *name++;
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Format directory name field from a 8.3 name string
|
||||
bool SdBaseFile::make83Name(const char *str, uint8_t *name, const char **ptr) {
|
||||
uint8_t n = 7, // Max index until a dot is found
|
||||
|
@ -430,6 +445,10 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) {
|
|||
SdBaseFile *sub = &dir1;
|
||||
SdBaseFile *start = parent;
|
||||
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
uint8_t dlname[LONG_FILENAME_LENGTH];
|
||||
#endif
|
||||
|
||||
if (!parent || isOpen()) return false;
|
||||
|
||||
if (*path == '/') {
|
||||
|
@ -439,28 +458,31 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) {
|
|||
parent = &dir2;
|
||||
}
|
||||
}
|
||||
while (1) {
|
||||
if (!make83Name(path, dname, &path)) return false;
|
||||
|
||||
for (;;) {
|
||||
if (!TERN(LONG_FILENAME_WRITE_SUPPORT, parsePath(path, dname, dlname, &path), make83Name(path, dname, &path))) return false;
|
||||
while (*path == '/') path++;
|
||||
if (!*path) break;
|
||||
if (!sub->open(parent, dname, O_READ)) {
|
||||
if (!pFlag || !sub->mkdir(parent, dname))
|
||||
if (!sub->open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), O_READ)) {
|
||||
if (!pFlag || !sub->mkdir(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname)))
|
||||
return false;
|
||||
}
|
||||
if (parent != start) parent->close();
|
||||
parent = sub;
|
||||
sub = parent != &dir1 ? &dir1 : &dir2;
|
||||
}
|
||||
return mkdir(parent, dname);
|
||||
return mkdir(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname));
|
||||
}
|
||||
|
||||
bool SdBaseFile::mkdir(SdBaseFile *parent, const uint8_t dname[11]) {
|
||||
bool SdBaseFile::mkdir(SdBaseFile *parent, const uint8_t dname[11]
|
||||
OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH])
|
||||
) {
|
||||
if (ENABLED(SDCARD_READONLY)) return false;
|
||||
|
||||
if (!parent->isDir()) return false;
|
||||
|
||||
// create a normal file
|
||||
if (!open(parent, dname, O_CREAT | O_EXCL | O_RDWR)) return false;
|
||||
if (!open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), O_CREAT | O_EXCL | O_RDWR)) return false;
|
||||
|
||||
// convert file to directory
|
||||
flags_ = O_READ;
|
||||
|
@ -578,6 +600,10 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) {
|
|||
SdBaseFile dir1, dir2;
|
||||
SdBaseFile *parent = dirFile, *sub = &dir1;
|
||||
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
uint8_t dlname[LONG_FILENAME_LENGTH];
|
||||
#endif
|
||||
|
||||
if (!dirFile || isOpen()) return false;
|
||||
|
||||
if (*path == '/') { // Path starts with '/'
|
||||
|
@ -589,70 +615,178 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) {
|
|||
}
|
||||
|
||||
for (;;) {
|
||||
if (!make83Name(path, dname, &path)) return false;
|
||||
if (!TERN(LONG_FILENAME_WRITE_SUPPORT, parsePath(path, dname, dlname, &path), make83Name(path, dname, &path))) return false;
|
||||
while (*path == '/') path++;
|
||||
if (!*path) break;
|
||||
if (!sub->open(parent, dname, O_READ)) return false;
|
||||
if (TERN0(LONG_FILENAME_WRITE_SUPPORT, !sub->open(parent, dname, dlname, O_READ))) return false;
|
||||
if (parent != dirFile) parent->close();
|
||||
parent = sub;
|
||||
sub = parent != &dir1 ? &dir1 : &dir2;
|
||||
}
|
||||
return open(parent, dname, oflag);
|
||||
return open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), oflag);
|
||||
}
|
||||
|
||||
// open with filename in dname
|
||||
bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t oflag) {
|
||||
// open with filename in dname and long filename in dlname
|
||||
bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11]
|
||||
OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH])
|
||||
, uint8_t oflag
|
||||
) {
|
||||
bool emptyFound = false, fileFound = false;
|
||||
uint8_t index;
|
||||
uint8_t index = 0;
|
||||
dir_t *p;
|
||||
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
// LFN - Long File Name support
|
||||
const bool useLFN = dlname[0] != 0;
|
||||
bool lfnFileFound = false;
|
||||
vfat_t *pvFat;
|
||||
uint8_t emptyCount = 0,
|
||||
emptyIndex = 0,
|
||||
reqEntriesNum = useLFN ? getLFNEntriesNum((char*)dlname) + 1 : 1,
|
||||
lfnNameLength = useLFN ? strlen((char*)dlname) : 0,
|
||||
lfnName[LONG_FILENAME_LENGTH],
|
||||
lfnSequenceNumber = 0,
|
||||
lfnChecksum = 0;
|
||||
#endif
|
||||
|
||||
// Rewind this dir
|
||||
vol_ = dirFile->vol_;
|
||||
|
||||
dirFile->rewind();
|
||||
|
||||
// search for file
|
||||
|
||||
while (dirFile->curPosition_ < dirFile->fileSize_) {
|
||||
index = 0xF & (dirFile->curPosition_ >> 5);
|
||||
p = dirFile->readDirCache();
|
||||
if (!p) return false;
|
||||
// Get absolute index position
|
||||
index = (dirFile->curPosition_ >> 5) IF_DISABLED(LONG_FILENAME_WRITE_SUPPORT, & 0x0F);
|
||||
|
||||
// Get next entry
|
||||
if (!(p = dirFile->readDirCache())) return false;
|
||||
|
||||
// Check empty status: Is entry empty?
|
||||
if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) {
|
||||
// remember first empty slot
|
||||
// Count the contiguous available entries in which (eventually) fit the new dir entry, if it's a write operation
|
||||
if (!emptyFound) {
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
if (emptyCount == 0) emptyIndex = index;
|
||||
// Incr empty entries counter
|
||||
// If found the required empty entries, mark it
|
||||
if (++emptyCount == reqEntriesNum) {
|
||||
dirBlock_ = dirFile->vol_->cacheBlockNumber();
|
||||
dirIndex_ = index & 0xF;
|
||||
emptyFound = true;
|
||||
}
|
||||
#else
|
||||
dirBlock_ = dirFile->vol_->cacheBlockNumber();
|
||||
dirIndex_ = index;
|
||||
emptyFound = true;
|
||||
#endif
|
||||
}
|
||||
// done if no entries follow
|
||||
// Done if no entries follow
|
||||
if (p->name[0] == DIR_NAME_FREE) break;
|
||||
}
|
||||
else if (!memcmp(dname, p->name, 11)) {
|
||||
else { // Entry not empty
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
// Reset empty counter
|
||||
if (!emptyFound) emptyCount = 0;
|
||||
// Search for SFN or LFN?
|
||||
if (!useLFN) {
|
||||
// Check using SFN: file found?
|
||||
if (!memcmp(dname, p->name, 11)) {
|
||||
fileFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Check using LFN: LFN not found? continue search for LFN
|
||||
if (!lfnFileFound) {
|
||||
// Is this dir a LFN?
|
||||
if (isDirLFN(p)) {
|
||||
// Get VFat dir entry
|
||||
pvFat = (vfat_t *) p;
|
||||
// Get checksum from the last entry of the sequence
|
||||
if (pvFat->sequenceNumber & 0x40) lfnChecksum = pvFat->checksum;
|
||||
// Get LFN sequence number
|
||||
lfnSequenceNumber = pvFat->sequenceNumber & 0x1F;
|
||||
if WITHIN(lfnSequenceNumber, 1, reqEntriesNum) {
|
||||
// Check checksum for all other entries with the starting checksum fetched before
|
||||
if (lfnChecksum == pvFat->checksum) {
|
||||
// Set chunk of LFN from VFAT entry into lfnName
|
||||
getLFNName(pvFat, (char *)lfnName, lfnSequenceNumber);
|
||||
// LFN found?
|
||||
if (!strncasecmp((char*)dlname, (char*)lfnName, lfnNameLength)) lfnFileFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Complete LFN found, check for related SFN
|
||||
// Check if only the SFN checksum match because the filename may be different due to different truncation methods
|
||||
if (!isDirLFN(p) && (lfnChecksum == lfn_checksum(p->name))) {
|
||||
fileFound = true;
|
||||
break;
|
||||
}
|
||||
else lfnFileFound = false; // SFN not valid for the LFN found, reset LFN FileFound
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
if (!memcmp(dname, p->name, 11)) {
|
||||
fileFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif // LONG_FILENAME_WRITE_SUPPORT
|
||||
}
|
||||
}
|
||||
|
||||
if (fileFound) {
|
||||
// don't open existing file if O_EXCL
|
||||
if (oflag & O_EXCL) return false;
|
||||
TERN_(LONG_FILENAME_WRITE_SUPPORT, index &= 0xF);
|
||||
}
|
||||
else {
|
||||
// don't create unless O_CREAT and O_WRITE
|
||||
if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) return false;
|
||||
if (emptyFound) {
|
||||
index = dirIndex_;
|
||||
p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||
if (!p) return false;
|
||||
}
|
||||
else {
|
||||
if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false;
|
||||
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
|
||||
// Use bookmark index if found empty entries
|
||||
if (emptyFound) index = emptyIndex;
|
||||
|
||||
// Make room for needed entries
|
||||
while (emptyCount < reqEntriesNum) {
|
||||
p = dirFile->readDirCache();
|
||||
if (!p) break;
|
||||
emptyCount++;
|
||||
}
|
||||
while (emptyCount < reqEntriesNum) {
|
||||
if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false;
|
||||
// add and zero cluster for dirFile - first cluster is in cache for write
|
||||
if (!dirFile->addDirCluster()) return false;
|
||||
|
||||
// use first entry in cluster
|
||||
p = dirFile->vol_->cache()->dir;
|
||||
index = 0;
|
||||
emptyCount += dirFile->vol_->blocksPerCluster() * 16;
|
||||
}
|
||||
|
||||
// Move to 1st entry to write
|
||||
if (!dirFile->seekSet(32 * index)) return false;
|
||||
|
||||
// Dir entries write loop: [LFN] + SFN(1)
|
||||
LOOP_L_N(dirWriteIdx, reqEntriesNum) {
|
||||
index = (dirFile->curPosition_ / 32) & 0xF;
|
||||
p = dirFile->readDirCache();
|
||||
// LFN or SFN Entry?
|
||||
if (dirWriteIdx < reqEntriesNum - 1) {
|
||||
// Write LFN Entries
|
||||
pvFat = (vfat_t *) p;
|
||||
// initialize as empty file
|
||||
memset(pvFat, 0, sizeof(*pvFat));
|
||||
lfnSequenceNumber = (reqEntriesNum - dirWriteIdx - 1) & 0x1F;
|
||||
pvFat->attributes = DIR_ATT_LONG_NAME;
|
||||
pvFat->checksum = lfn_checksum(dname);
|
||||
// Set sequence number and mark as last LFN entry if it's the 1st loop
|
||||
pvFat->sequenceNumber = lfnSequenceNumber | (dirWriteIdx == 0 ? 0x40 : 0);
|
||||
// Set LFN name block
|
||||
setLFNName(pvFat, (char*)dlname, lfnSequenceNumber);
|
||||
}
|
||||
else {
|
||||
// Write SFN Entry
|
||||
// initialize as empty file
|
||||
memset(p, 0, sizeof(*p));
|
||||
memcpy(p->name, dname, 11);
|
||||
|
@ -670,9 +804,55 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t ofla
|
|||
p->lastAccessDate = p->creationDate;
|
||||
p->lastWriteDate = p->creationDate;
|
||||
p->lastWriteTime = p->creationTime;
|
||||
}
|
||||
|
||||
// write entry to SD
|
||||
dirFile->vol_->cacheSetDirty();
|
||||
if (!dirFile->vol_->cacheFlush()) return false;
|
||||
}
|
||||
|
||||
#else // !LONG_FILENAME_WRITE_SUPPORT
|
||||
|
||||
if (emptyFound) {
|
||||
index = dirIndex_;
|
||||
p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||
if (!p) return false;
|
||||
}
|
||||
else {
|
||||
if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false;
|
||||
|
||||
// add and zero cluster for dirFile - first cluster is in cache for write
|
||||
if (!dirFile->addDirCluster()) return false;
|
||||
|
||||
// use first entry in cluster
|
||||
p = dirFile->vol_->cache()->dir;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// initialize as empty file
|
||||
memset(p, 0, sizeof(*p));
|
||||
memcpy(p->name, dname, 11);
|
||||
|
||||
// set timestamps
|
||||
if (dateTime_) {
|
||||
// call user date/time function
|
||||
dateTime_(&p->creationDate, &p->creationTime);
|
||||
}
|
||||
else {
|
||||
// use default date/time
|
||||
p->creationDate = FAT_DEFAULT_DATE;
|
||||
p->creationTime = FAT_DEFAULT_TIME;
|
||||
}
|
||||
|
||||
p->lastAccessDate = p->creationDate;
|
||||
p->lastWriteDate = p->creationDate;
|
||||
p->lastWriteTime = p->creationTime;
|
||||
|
||||
// write entry to SD
|
||||
if (!dirFile->vol_->cacheFlush()) return false;
|
||||
|
||||
#endif // !LONG_FILENAME_WRITE_SUPPORT
|
||||
|
||||
}
|
||||
// open entry in cache
|
||||
return openCachedEntry(index, oflag);
|
||||
|
@ -808,6 +988,191 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) {
|
|||
return false;
|
||||
}
|
||||
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
|
||||
/**
|
||||
* Check if dir is a long file name entry (LFN)
|
||||
*
|
||||
* \param[in] dir Parent of this directory will be opened. Must not be root.
|
||||
* \return true if the dir is a long file name entry (LFN)
|
||||
*/
|
||||
bool SdBaseFile::isDirLFN(const dir_t* dir) {
|
||||
if (DIR_IS_LONG_NAME(dir)) {
|
||||
vfat_t *VFAT = (vfat_t*)dir;
|
||||
// Sanity-check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher than 0
|
||||
if ((VFAT->firstClusterLow == 0) && WITHIN((VFAT->sequenceNumber & 0x1F), 1, MAX_VFAT_ENTRIES)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if dirname string is a long file name (LFN)
|
||||
*
|
||||
* \param[in] dirname The string to check
|
||||
* \return true if the dirname is a long file name (LFN)
|
||||
* \return false if the dirname is a short file name 8.3 (SFN)
|
||||
*/
|
||||
bool SdBaseFile::isDirNameLFN(const char *dirname) {
|
||||
uint8_t length = strlen(dirname);
|
||||
uint8_t idx = length;
|
||||
bool dotFound = false;
|
||||
if (idx > 12) return true; // LFN due to filename length > 12 ("filename.ext")
|
||||
// Check dot(s) position
|
||||
while (idx) {
|
||||
if (dirname[--idx] == '.') {
|
||||
if (!dotFound) {
|
||||
// Last dot (extension) is allowed only
|
||||
// in position [1..8] from start or [0..3] from end for SFN else it's a LFN
|
||||
// A filename starting with "." is a LFN (eg. ".file" ->in SFN-> "file~1 ")
|
||||
// A filename ending with "." is a SFN (if length <= 9) (eg. "file." ->in SFN-> "file ")
|
||||
if (idx > 8 || idx == 0 || (length - idx - 1) > 3) return true; // LFN due to dot extension position
|
||||
dotFound = true;
|
||||
}
|
||||
else {
|
||||
// Found another dot, is a LFN
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no dots found, the filename must be of max 8 characters
|
||||
if ((!dotFound) && length > 8) return true; // LFN due to max filename (without extension) length
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse path and return 8.3 format and LFN filenames (if the parsed path is a LFN)
|
||||
* The SFN is without dot ("FILENAMEEXT")
|
||||
* The LFN is complete ("Filename.ext")
|
||||
*/
|
||||
bool SdBaseFile::parsePath(const char *path, uint8_t *name, uint8_t *lname, const char **ptrNextPath) {
|
||||
// Init randomizer for SFN generation
|
||||
randomSeed(millis());
|
||||
// Parse the LFN
|
||||
uint8_t ilfn = 0;
|
||||
bool lastDotFound = false;
|
||||
const char *pLastDot = 0;
|
||||
const char *lfnpath = path;
|
||||
uint8_t c;
|
||||
|
||||
while (*lfnpath && *lfnpath != '/') {
|
||||
if (ilfn == LONG_FILENAME_LENGTH - 1) return false; // Name too long
|
||||
c = *lfnpath++; // Get char and advance
|
||||
// Fail for illegal characters
|
||||
PGM_P p = PSTR("|<>^+=?/[];:,*\"\\");
|
||||
while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; // Check reserved characters
|
||||
if (c < 0x20 || c == 0x7F) return false; // Check non-printable characters
|
||||
if (c == '.' && (lfnpath - 1) > path) { // Skip dot '.' check in 1st position
|
||||
// Save last dot pointer (skip if starts with '.')
|
||||
pLastDot = lfnpath - 1;
|
||||
lastDotFound = true;
|
||||
}
|
||||
lname[ilfn++] = c; // Set LFN character
|
||||
}
|
||||
// Terminate LFN
|
||||
lname[ilfn] = 0;
|
||||
|
||||
// Parse/generate 8.3 SFN. Will take
|
||||
// until 8 characters for the filename part
|
||||
// until 3 characters for the extension part (if exists)
|
||||
// Add 4 more characters if name part < 3
|
||||
// Add '~cnt' characters if it's a LFN
|
||||
const bool isLFN = isDirNameLFN((char*)lname);
|
||||
|
||||
uint8_t n = isLFN ? 5 : 7, // Max index for each component of the file:
|
||||
// starting with 7 or 5 (if LFN)
|
||||
// switch to 10 for extension if the last dot is found
|
||||
i = 11;
|
||||
while (i) name[--i] = ' '; // Set whole FILENAMEEXT to spaces
|
||||
while (*path && *path != '/') {
|
||||
c = *path++; // Get char and advance
|
||||
// Skip spaces and dots (if it's not the last dot)
|
||||
if (c == ' ') continue;
|
||||
if (c == '.' && (!lastDotFound || (lastDotFound && path < pLastDot))) continue;
|
||||
// Fail for illegal characters
|
||||
PGM_P p = PSTR("|<>^+=?/[];:,*\"\\");
|
||||
while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; // Check reserved characters
|
||||
if (c < 0x21 || c == 0x7F) return false; // Check non-printable characters
|
||||
// Is last dot?
|
||||
if (c == '.') {
|
||||
// Switch to extension part
|
||||
n = 10;
|
||||
i = 8;
|
||||
}
|
||||
// If in valid range add the character
|
||||
else if (i <= n) // Check size for 8.3 format
|
||||
name[i++] = c + (WITHIN(c, 'a', 'z') ? 'A' - 'a' : 0); // Uppercase required for 8.3 name
|
||||
}
|
||||
// If it's a LFN then the SFN always need:
|
||||
// - A minimal of 3 characters (otherwise 4 chars are added)
|
||||
// - The '~cnt' at the end
|
||||
if (isLFN) {
|
||||
// Get the 1st free character
|
||||
uint8_t iFree = 0;
|
||||
while (1) if (name[iFree++] == ' ' || iFree == 11) break;
|
||||
iFree--;
|
||||
// Check minimal length
|
||||
if (iFree < 3) {
|
||||
// Append 4 extra characters
|
||||
name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(0,24) + 'A';
|
||||
name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(0,24) + 'A';
|
||||
}
|
||||
// Append '~cnt' characters
|
||||
if (iFree > 5) iFree = 5; // Force the append in the last 3 characters of name part
|
||||
name[iFree++] = '~';
|
||||
name[iFree++] = random(1,9) + '0';
|
||||
name[iFree++] = random(1,9) + '0';
|
||||
}
|
||||
|
||||
// Check if LFN is needed
|
||||
if (!isLFN) lname[0] = 0; // Zero LFN
|
||||
*ptrNextPath = path; // Set passed pointer to the end
|
||||
return name[0] != ' '; // Return true if any name was set
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LFN filename block from a dir. Get the block in lname at startOffset
|
||||
*/
|
||||
void SdBaseFile::getLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) {
|
||||
uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
|
||||
LOOP_L_N(i, FILENAME_LENGTH) {
|
||||
const uint16_t utf16_ch = (i >= 11) ? pFatDir->name3[i - 11] : (i >= 5) ? pFatDir->name2[i - 5] : pFatDir->name1[i];
|
||||
#if ENABLED(UTF_FILENAME_SUPPORT)
|
||||
// We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks
|
||||
// needs static bytes addressing. So here just store full UTF-16LE words to re-convert later.
|
||||
uint16_t idx = (startOffset + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding
|
||||
longFilename[idx] = utf16_ch & 0xFF;
|
||||
longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF;
|
||||
#else
|
||||
// Replace all multibyte characters to '_'
|
||||
lname[startOffset + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the LFN filename block lname to a dir. Put the block based on sequence number
|
||||
*/
|
||||
void SdBaseFile::setLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) {
|
||||
uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
|
||||
uint8_t nameLength = strlen(lname);
|
||||
LOOP_L_N(i, FILENAME_LENGTH) {
|
||||
uint16_t ch = 0;
|
||||
if ((startOffset + i) < nameLength)
|
||||
ch = lname[startOffset + i];
|
||||
else if ((startOffset + i) > nameLength)
|
||||
ch = 0xFFFF;
|
||||
// Set char
|
||||
if (i < 5)
|
||||
pFatDir->name1[i] = ch;
|
||||
else if (i < 11)
|
||||
pFatDir->name2[i - 5] = ch;
|
||||
else
|
||||
pFatDir->name3[i - 11] = ch;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // LONG_FILENAME_WRITE_SUPPORT
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* Open a directory's parent directory.
|
||||
|
@ -1049,20 +1414,6 @@ int16_t SdBaseFile::read(void *buf, uint16_t nbyte) {
|
|||
return nbyte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a checksum for an 8.3 filename
|
||||
*
|
||||
* \param name The 8.3 file name to calculate
|
||||
*
|
||||
* \return The checksum byte
|
||||
*/
|
||||
uint8_t lfn_checksum(const uint8_t *name) {
|
||||
uint8_t sum = 0;
|
||||
for (uint8_t i = 11; i; i--)
|
||||
sum = ((sum & 1) << 7) + (sum >> 1) + *name++;
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next entry in a directory.
|
||||
*
|
||||
|
@ -1110,14 +1461,21 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
|
|||
if (VFAT->firstClusterLow == 0) {
|
||||
const uint8_t seq = VFAT->sequenceNumber & 0x1F;
|
||||
if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) {
|
||||
n = (seq - 1) * (FILENAME_LENGTH);
|
||||
if (n == 0) {
|
||||
if (seq == 1) {
|
||||
checksum = VFAT->checksum;
|
||||
checksum_error = 0;
|
||||
}
|
||||
else if (checksum != VFAT->checksum) // orphan detected
|
||||
checksum_error = 1;
|
||||
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
|
||||
getLFNName(VFAT, longFilename, seq); // Get chunk of LFN from VFAT entry
|
||||
|
||||
#else // !LONG_FILENAME_WRITE_SUPPORT
|
||||
|
||||
n = (seq - 1) * (FILENAME_LENGTH);
|
||||
|
||||
LOOP_L_N(i, FILENAME_LENGTH) {
|
||||
const uint16_t utf16_ch = (i >= 11) ? VFAT->name3[i - 11] : (i >= 5) ? VFAT->name2[i - 5] : VFAT->name1[i];
|
||||
#if ENABLED(UTF_FILENAME_SUPPORT)
|
||||
|
@ -1131,9 +1489,12 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
|
|||
longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // !LONG_FILENAME_WRITE_SUPPORT
|
||||
|
||||
// If this VFAT entry is the last one, add a NUL terminator at the end of the string
|
||||
if (VFAT->sequenceNumber & 0x40)
|
||||
longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0';
|
||||
longFilename[LONG_FILENAME_CHARSIZE * TERN(LONG_FILENAME_WRITE_SUPPORT, seq * FILENAME_LENGTH, (n + FILENAME_LENGTH))] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1227,6 +1588,11 @@ bool SdBaseFile::remove() {
|
|||
dir_t *d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||
if (!d) return false;
|
||||
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
// get SFN checksum before name rewrite (needed for LFN deletion)
|
||||
const uint8_t sfn_checksum = lfn_checksum(d->name);
|
||||
#endif
|
||||
|
||||
// mark entry deleted
|
||||
d->name[0] = DIR_NAME_DELETED;
|
||||
|
||||
|
@ -1234,8 +1600,48 @@ bool SdBaseFile::remove() {
|
|||
type_ = FAT_FILE_TYPE_CLOSED;
|
||||
|
||||
// write entry to SD
|
||||
#if DISABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
|
||||
return vol_->cacheFlush();
|
||||
|
||||
#else // LONG_FILENAME_WRITE_SUPPORT
|
||||
|
||||
flags_ = 0;
|
||||
|
||||
if (!vol_->cacheFlush()) return false;
|
||||
|
||||
// Check if the entry has a LFN
|
||||
bool lastEntry = false;
|
||||
// loop back to search for any LFN entries related to this file
|
||||
LOOP_S_LE_N(sequenceNumber, 1, MAX_VFAT_ENTRIES) {
|
||||
dirIndex_ = (dirIndex_ - 1) & 0xF;
|
||||
if (dirBlock_ == 0) break;
|
||||
if (dirIndex_ == 0xF) dirBlock_--;
|
||||
dir_t *dir = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||
if (!dir) return false;
|
||||
|
||||
// check for valid LFN: not deleted, not top dirs (".", ".."), must be a LFN
|
||||
if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.' || !isDirLFN(dir)) break;
|
||||
// check coherent LFN: checksum and sequenceNumber must match
|
||||
vfat_t* dirlfn = (vfat_t*) dir;
|
||||
if (dirlfn->checksum != sfn_checksum || (dirlfn->sequenceNumber & 0x1F) != sequenceNumber) break; // orphan entry
|
||||
// is last entry of LFN ?
|
||||
lastEntry = (dirlfn->sequenceNumber & 0x40);
|
||||
// mark as deleted
|
||||
dirlfn->sequenceNumber = DIR_NAME_DELETED;
|
||||
// Flush to SD
|
||||
if (!vol_->cacheFlush()) return false;
|
||||
// exit on last entry of LFN deleted
|
||||
if (lastEntry) break;
|
||||
}
|
||||
|
||||
// Restore current index
|
||||
//if (!seekSet(32UL * dirIndex_)) return false;
|
||||
//dirIndex_ += prevDirIndex;
|
||||
|
||||
return true;
|
||||
|
||||
#endif // LONG_FILENAME_WRITE_SUPPORT
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -377,8 +377,26 @@ class SdBaseFile {
|
|||
dir_t* cacheDirEntry(uint8_t action);
|
||||
int8_t lsPrintNext(uint8_t flags, uint8_t indent);
|
||||
static bool make83Name(const char *str, uint8_t *name, const char **ptr);
|
||||
bool mkdir(SdBaseFile *parent, const uint8_t dname[11]);
|
||||
bool open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t oflag);
|
||||
bool mkdir(SdBaseFile *parent, const uint8_t dname[11]
|
||||
OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH])
|
||||
);
|
||||
bool open(SdBaseFile *dirFile, const uint8_t dname[11]
|
||||
OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH])
|
||||
, uint8_t oflag
|
||||
);
|
||||
bool openCachedEntry(uint8_t cacheIndex, uint8_t oflags);
|
||||
dir_t* readDirCache();
|
||||
|
||||
// Long Filename create/write support
|
||||
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||
static bool isDirLFN(const dir_t* dir);
|
||||
static bool isDirNameLFN(const char *dirname);
|
||||
static bool parsePath(const char *str, uint8_t *name, uint8_t *lname, const char **ptr);
|
||||
/**
|
||||
* Return the number of entries needed in the FAT for this LFN
|
||||
*/
|
||||
static inline uint8_t getLFNEntriesNum(const char *lname) { return (strlen(lname) + 12) / 13; }
|
||||
static void getLFNName(vfat_t *vFatDir, char *lname, uint8_t startOffset);
|
||||
static void setLFNName(vfat_t *vFatDir, char *lname, uint8_t lfnSequenceNumber);
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -328,7 +328,7 @@ void CardReader::printListing(
|
|||
if (includeLongNames) {
|
||||
SERIAL_CHAR(' ');
|
||||
if (prependLong) { SERIAL_ECHO(prependLong); SERIAL_CHAR('/'); }
|
||||
SERIAL_ECHO(longFilename[0] ? longFilename : "???");
|
||||
SERIAL_ECHO(longFilename[0] ? longFilename : filename);
|
||||
}
|
||||
#endif
|
||||
SERIAL_EOL();
|
||||
|
@ -385,9 +385,9 @@ void CardReader::ls(
|
|||
diveDir.rewind();
|
||||
selectByName(diveDir, segment);
|
||||
|
||||
// Print /LongNamePart to serial output
|
||||
// Print /LongNamePart to serial output or the short name if not available
|
||||
SERIAL_CHAR('/');
|
||||
SERIAL_ECHO(longFilename[0] ? longFilename : "???");
|
||||
SERIAL_ECHO(longFilename[0] ? longFilename : filename);
|
||||
|
||||
// If the filename was printed then that's it
|
||||
if (!flag.filenameIsDir) break;
|
||||
|
|
|
@ -84,21 +84,26 @@ def Upload(source, target, env):
|
|||
#----------------#
|
||||
# File functions #
|
||||
#----------------#
|
||||
def _GetFirmwareFiles():
|
||||
def _GetFirmwareFiles(UseLongFilenames):
|
||||
if Debug: print('Get firmware files...')
|
||||
_Send('M20 F')
|
||||
_Send(f"M20 F{'L' if UseLongFilenames else ''}")
|
||||
Responses = _Recv()
|
||||
if len(Responses) < 3 or not any('file list' in r for r in Responses):
|
||||
raise Exception('Error getting firmware files')
|
||||
if Debug: print('OK')
|
||||
return Responses
|
||||
|
||||
def _FilterFirmwareFiles(FirmwareList):
|
||||
def _FilterFirmwareFiles(FirmwareList, UseLongFilenames):
|
||||
Firmwares = []
|
||||
for FWFile in FirmwareList:
|
||||
if not '/' in FWFile and '.BIN' in FWFile:
|
||||
idx = FWFile.index('.BIN')
|
||||
Firmwares.append(FWFile[:idx+4])
|
||||
# For long filenames take the 3rd column of the firmwares list
|
||||
if UseLongFilenames:
|
||||
Space = 0
|
||||
Space = FWFile.find(' ')
|
||||
if Space >= 0: Space = FWFile.find(' ', Space + 1)
|
||||
if Space >= 0: FWFile = FWFile[Space + 1:]
|
||||
if not '/' in FWFile and '.BIN' in FWFile.upper():
|
||||
Firmwares.append(FWFile[:FWFile.upper().index('.BIN') + 4])
|
||||
return Firmwares
|
||||
|
||||
def _RemoveFirmwareFile(FirmwareFile):
|
||||
|
@ -124,6 +129,8 @@ def Upload(source, target, env):
|
|||
marlin_board_info_name = _GetMarlinEnv(MarlinEnv, 'BOARD_INFO_NAME')
|
||||
marlin_board_custom_build_flags = _GetMarlinEnv(MarlinEnv, 'BOARD_CUSTOM_BUILD_FLAGS')
|
||||
marlin_firmware_bin = _GetMarlinEnv(MarlinEnv, 'FIRMWARE_BIN')
|
||||
marlin_long_filename_host_support = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_HOST_SUPPORT') is not None
|
||||
marlin_longname_write = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_WRITE_SUPPORT') is not None
|
||||
marlin_custom_firmware_upload = _GetMarlinEnv(MarlinEnv, 'CUSTOM_FIRMWARE_UPLOAD') is not None
|
||||
marlin_short_build_version = _GetMarlinEnv(MarlinEnv, 'SHORT_BUILD_VERSION')
|
||||
marlin_string_config_h_author = _GetMarlinEnv(MarlinEnv, 'STRING_CONFIG_H_AUTHOR')
|
||||
|
@ -148,6 +155,10 @@ def Upload(source, target, env):
|
|||
# "upload_delete_old_bins": delete all *.bin files in the root of SD Card
|
||||
upload_delete_old_bins = marlin_motherboard in ['BOARD_CREALITY_V4', 'BOARD_CREALITY_V4210', 'BOARD_CREALITY_V423', 'BOARD_CREALITY_V427',
|
||||
'BOARD_CREALITY_V431', 'BOARD_CREALITY_V452', 'BOARD_CREALITY_V453', 'BOARD_CREALITY_V24S1']
|
||||
# "upload_random_name": generate a random 8.3 firmware filename to upload
|
||||
upload_random_filename = marlin_motherboard in ['BOARD_CREALITY_V4', 'BOARD_CREALITY_V4210', 'BOARD_CREALITY_V423', 'BOARD_CREALITY_V427',
|
||||
'BOARD_CREALITY_V431', 'BOARD_CREALITY_V452', 'BOARD_CREALITY_V453', 'BOARD_CREALITY_V24S1'] and not marlin_long_filename_host_support
|
||||
|
||||
try:
|
||||
|
||||
# Start upload job
|
||||
|
@ -156,7 +167,7 @@ def Upload(source, target, env):
|
|||
# Dump some debug info
|
||||
if Debug:
|
||||
print('Upload using:')
|
||||
print('---- Marlin --------------------')
|
||||
print('---- Marlin -----------------------------------')
|
||||
print(f' PIOENV : {marlin_pioenv}')
|
||||
print(f' SHORT_BUILD_VERSION : {marlin_short_build_version}')
|
||||
print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}')
|
||||
|
@ -164,8 +175,10 @@ def Upload(source, target, env):
|
|||
print(f' BOARD_INFO_NAME : {marlin_board_info_name}')
|
||||
print(f' CUSTOM_BUILD_FLAGS : {marlin_board_custom_build_flags}')
|
||||
print(f' FIRMWARE_BIN : {marlin_firmware_bin}')
|
||||
print(f' LONG_FILENAME_HOST_SUPPORT : {marlin_long_filename_host_support}')
|
||||
print(f' LONG_FILENAME_WRITE_SUPPORT : {marlin_longname_write}')
|
||||
print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}')
|
||||
print('---- Upload parameters ---------')
|
||||
print('---- Upload parameters ------------------------')
|
||||
print(f' Source : {upload_firmware_source_name}')
|
||||
print(f' Target : {upload_firmware_target_name}')
|
||||
print(f' Port : {upload_port} @ {upload_speed} baudrate')
|
||||
|
@ -175,9 +188,13 @@ def Upload(source, target, env):
|
|||
print(f' Error ratio : {upload_error_ratio}')
|
||||
print(f' Test : {upload_test}')
|
||||
print(f' Reset : {upload_reset}')
|
||||
print('--------------------------------')
|
||||
print('-----------------------------------------------')
|
||||
|
||||
# Custom implementations based on board parameters
|
||||
# Generate a new 8.3 random filename
|
||||
if upload_random_filename:
|
||||
upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN"
|
||||
print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'")
|
||||
|
||||
# Delete all *.bin files on the root of SD Card (if flagged)
|
||||
if upload_delete_old_bins:
|
||||
|
@ -185,11 +202,6 @@ def Upload(source, target, env):
|
|||
if not marlin_custom_firmware_upload:
|
||||
raise Exception(f"CUSTOM_FIRMWARE_UPLOAD must be enabled in 'Configuration_adv.h' for '{marlin_motherboard}'")
|
||||
|
||||
# Generate a new 8.3 random filename
|
||||
# This board remember the last firmware filename and doesn't allow to flash from that filename
|
||||
upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN"
|
||||
print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'")
|
||||
|
||||
# Init serial port
|
||||
port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1)
|
||||
port.reset_input_buffer()
|
||||
|
@ -198,13 +210,13 @@ def Upload(source, target, env):
|
|||
_CheckSDCard()
|
||||
|
||||
# Get firmware files
|
||||
FirmwareFiles = _GetFirmwareFiles()
|
||||
FirmwareFiles = _GetFirmwareFiles(marlin_long_filename_host_support)
|
||||
if Debug:
|
||||
for FirmwareFile in FirmwareFiles:
|
||||
print(f'Found: {FirmwareFile}')
|
||||
|
||||
# Get all 1st level firmware files (to remove)
|
||||
OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2]) # Skip header and footers of list
|
||||
OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2], marlin_long_filename_host_support) # Skip header and footers of list
|
||||
if len(OldFirmwareFiles) == 0:
|
||||
print('No old firmware files to delete')
|
||||
else:
|
||||
|
|
|
@ -35,7 +35,7 @@ opt_set MOTHERBOARD BOARD_AZTEEG_X3_PRO LCD_LANGUAGE jp_kana DEFAULT_EJERK 10 \
|
|||
EXTRUDERS 5 TEMP_SENSOR_1 1 TEMP_SENSOR_2 5 TEMP_SENSOR_3 20 TEMP_SENSOR_4 1000 TEMP_SENSOR_BED 1
|
||||
opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER LIGHTWEIGHT_UI SHOW_CUSTOM_BOOTSCREEN BOOT_MARLIN_LOGO_SMALL \
|
||||
LCD_SET_PROGRESS_MANUALLY PRINT_PROGRESS_SHOW_DECIMALS SHOW_REMAINING_TIME STATUS_MESSAGE_SCROLLING SCROLL_LONG_FILENAMES \
|
||||
SDSUPPORT SDCARD_SORT_ALPHA NO_SD_AUTOSTART USB_FLASH_DRIVE_SUPPORT CANCEL_OBJECTS \
|
||||
SDSUPPORT LONG_FILENAME_WRITE_SUPPORT SDCARD_SORT_ALPHA NO_SD_AUTOSTART USB_FLASH_DRIVE_SUPPORT CANCEL_OBJECTS \
|
||||
Z_PROBE_SLED AUTO_BED_LEVELING_UBL UBL_HILBERT_CURVE RESTORE_LEVELING_AFTER_G28 DEBUG_LEVELING_FEATURE G26_MESH_VALIDATION ENABLE_LEVELING_FADE_HEIGHT \
|
||||
EEPROM_SETTINGS EEPROM_CHITCHAT GCODE_MACROS CUSTOM_MENU_MAIN \
|
||||
MULTI_NOZZLE_DUPLICATION CLASSIC_JERK LIN_ADVANCE QUICK_HOME \
|
||||
|
|
Loading…
Reference in a new issue