✨ 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.
|
// LCD's font must contain the characters. Check your selected LCD language.
|
||||||
//#define UTF_FILENAME_SUPPORT
|
//#define UTF_FILENAME_SUPPORT
|
||||||
|
|
||||||
// This allows hosts to request long names for files and folders with M33
|
//#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_HOST_SUPPORT
|
//#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 // Scroll long filenames in the SD card menu
|
||||||
//#define SCROLL_LONG_FILENAMES
|
|
||||||
|
|
||||||
// Leave the heaters on after Stop Print (not recommended!)
|
//#define SD_ABORT_NO_COOLDOWN // Leave the heaters on after Stop Print (not recommended!)
|
||||||
//#define SD_ABORT_NO_COOLDOWN
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This option allows you to abort SD printing when any endstop is triggered.
|
* Abort SD printing when any endstop is triggered.
|
||||||
* This feature must be enabled with "M540 S1" or from the LCD menu.
|
* This feature is enabled with 'M540 S1' or from the LCD menu.
|
||||||
* To have any effect, endstops must be enabled during SD printing.
|
* Endstops must be activated for this option to work.
|
||||||
*/
|
*/
|
||||||
//#define SD_ABORT_ON_ENDSTOP_HIT
|
//#define SD_ABORT_ON_ENDSTOP_HIT
|
||||||
|
|
||||||
/**
|
//#define SD_REPRINT_LAST_SELECTED_FILE // On print completion open the LCD Menu and select the same file
|
||||||
* 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 AUTO_REPORT_SD_STATUS // Auto-report media status with 'M27 S<seconds>'
|
||||||
* Auto-report SdCard status with M27 S<seconds>
|
|
||||||
*/
|
|
||||||
//#define AUTO_REPORT_SD_STATUS
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for USB thumb drives using an Arduino USB Host Shield or
|
* 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
|
* M503: print settings currently in memory
|
||||||
*
|
*
|
||||||
|
* S<bool> : Include / exclude header comments in the output. (Default: S1)
|
||||||
|
*
|
||||||
* With CONFIGURATION_EMBEDDING:
|
* With CONFIGURATION_EMBEDDING:
|
||||||
* C<flag> : Save the full Marlin configuration to SD Card as "mc.zip"
|
* 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)
|
// LONG_FILENAME_HOST_SUPPORT (M33)
|
||||||
cap_line(F("LONG_FILENAME"), ENABLED(LONG_FILENAME_HOST_SUPPORT));
|
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)
|
// EXTENDED_M20 (M20 L)
|
||||||
cap_line(F("EXTENDED_M20"), ENABLED(LONG_FILENAME_HOST_SUPPORT));
|
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));
|
cap_line(F("MEATPACK"), SERIAL_IMPL.has_feature(port, SerialFeature::MeatPack));
|
||||||
|
|
||||||
// CONFIG_EXPORT
|
// CONFIG_EXPORT
|
||||||
cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIG_EMBED_AND_SAVE_TO_SD));
|
cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIGURATION_EMBEDDING));
|
||||||
|
|
||||||
// Machine Geometry
|
// Machine Geometry
|
||||||
#if ENABLED(M115_GEOMETRY_REPORT)
|
#if ENABLED(M115_GEOMETRY_REPORT)
|
||||||
|
@ -89,6 +89,7 @@ bool SdBaseFile::addDirCluster() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cache a file's directory entry
|
// 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
|
// return pointer to cached entry or null for failure
|
||||||
dir_t* SdBaseFile::cacheDirEntry(uint8_t action) {
|
dir_t* SdBaseFile::cacheDirEntry(uint8_t action) {
|
||||||
if (!vol_->cacheRawBlock(dirBlock_, action)) return nullptr;
|
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;
|
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
|
// Format directory name field from a 8.3 name string
|
||||||
bool SdBaseFile::make83Name(const char *str, uint8_t *name, const char **ptr) {
|
bool SdBaseFile::make83Name(const char *str, uint8_t *name, const char **ptr) {
|
||||||
uint8_t n = 7, // Max index until a dot is found
|
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 *sub = &dir1;
|
||||||
SdBaseFile *start = parent;
|
SdBaseFile *start = parent;
|
||||||
|
|
||||||
|
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||||
|
uint8_t dlname[LONG_FILENAME_LENGTH];
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!parent || isOpen()) return false;
|
if (!parent || isOpen()) return false;
|
||||||
|
|
||||||
if (*path == '/') {
|
if (*path == '/') {
|
||||||
@ -439,28 +458,31 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) {
|
|||||||
parent = &dir2;
|
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++;
|
while (*path == '/') path++;
|
||||||
if (!*path) break;
|
if (!*path) break;
|
||||||
if (!sub->open(parent, dname, O_READ)) {
|
if (!sub->open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), O_READ)) {
|
||||||
if (!pFlag || !sub->mkdir(parent, dname))
|
if (!pFlag || !sub->mkdir(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname)))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (parent != start) parent->close();
|
if (parent != start) parent->close();
|
||||||
parent = sub;
|
parent = sub;
|
||||||
sub = parent != &dir1 ? &dir1 : &dir2;
|
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 (ENABLED(SDCARD_READONLY)) return false;
|
||||||
|
|
||||||
if (!parent->isDir()) return false;
|
if (!parent->isDir()) return false;
|
||||||
|
|
||||||
// create a normal file
|
// 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
|
// convert file to directory
|
||||||
flags_ = O_READ;
|
flags_ = O_READ;
|
||||||
@ -578,6 +600,10 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) {
|
|||||||
SdBaseFile dir1, dir2;
|
SdBaseFile dir1, dir2;
|
||||||
SdBaseFile *parent = dirFile, *sub = &dir1;
|
SdBaseFile *parent = dirFile, *sub = &dir1;
|
||||||
|
|
||||||
|
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||||
|
uint8_t dlname[LONG_FILENAME_LENGTH];
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!dirFile || isOpen()) return false;
|
if (!dirFile || isOpen()) return false;
|
||||||
|
|
||||||
if (*path == '/') { // Path starts with '/'
|
if (*path == '/') { // Path starts with '/'
|
||||||
@ -589,90 +615,244 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
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++;
|
while (*path == '/') path++;
|
||||||
if (!*path) break;
|
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();
|
if (parent != dirFile) parent->close();
|
||||||
parent = sub;
|
parent = sub;
|
||||||
sub = parent != &dir1 ? &dir1 : &dir2;
|
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
|
// open with filename in dname and long filename in dlname
|
||||||
bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t oflag) {
|
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;
|
bool emptyFound = false, fileFound = false;
|
||||||
uint8_t index;
|
uint8_t index = 0;
|
||||||
dir_t *p;
|
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_;
|
vol_ = dirFile->vol_;
|
||||||
|
|
||||||
dirFile->rewind();
|
dirFile->rewind();
|
||||||
|
|
||||||
// search for file
|
// search for file
|
||||||
|
|
||||||
while (dirFile->curPosition_ < dirFile->fileSize_) {
|
while (dirFile->curPosition_ < dirFile->fileSize_) {
|
||||||
index = 0xF & (dirFile->curPosition_ >> 5);
|
// Get absolute index position
|
||||||
p = dirFile->readDirCache();
|
index = (dirFile->curPosition_ >> 5) IF_DISABLED(LONG_FILENAME_WRITE_SUPPORT, & 0x0F);
|
||||||
if (!p) return false;
|
|
||||||
|
|
||||||
|
// 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) {
|
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 (!emptyFound) {
|
||||||
dirBlock_ = dirFile->vol_->cacheBlockNumber();
|
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||||
dirIndex_ = index;
|
if (emptyCount == 0) emptyIndex = index;
|
||||||
emptyFound = true;
|
// 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;
|
if (p->name[0] == DIR_NAME_FREE) break;
|
||||||
}
|
}
|
||||||
else if (!memcmp(dname, p->name, 11)) {
|
else { // Entry not empty
|
||||||
fileFound = true;
|
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||||
break;
|
// 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) {
|
if (fileFound) {
|
||||||
// don't open existing file if O_EXCL
|
// don't open existing file if O_EXCL
|
||||||
if (oflag & O_EXCL) return false;
|
if (oflag & O_EXCL) return false;
|
||||||
|
TERN_(LONG_FILENAME_WRITE_SUPPORT, index &= 0xF);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// don't create unless O_CREAT and O_WRITE
|
// don't create unless O_CREAT and O_WRITE
|
||||||
if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) return false;
|
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;
|
|
||||||
|
|
||||||
// add and zero cluster for dirFile - first cluster is in cache for write
|
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||||
if (!dirFile->addDirCluster()) return false;
|
|
||||||
|
|
||||||
// use first entry in cluster
|
// Use bookmark index if found empty entries
|
||||||
p = dirFile->vol_->cache()->dir;
|
if (emptyFound) index = emptyIndex;
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
// initialize as empty file
|
|
||||||
memset(p, 0, sizeof(*p));
|
|
||||||
memcpy(p->name, dname, 11);
|
|
||||||
|
|
||||||
// set timestamps
|
// Make room for needed entries
|
||||||
if (dateTime_) {
|
while (emptyCount < reqEntriesNum) {
|
||||||
// call user date/time function
|
p = dirFile->readDirCache();
|
||||||
dateTime_(&p->creationDate, &p->creationTime);
|
if (!p) break;
|
||||||
}
|
emptyCount++;
|
||||||
else {
|
}
|
||||||
// use default date/time
|
while (emptyCount < reqEntriesNum) {
|
||||||
p->creationDate = FAT_DEFAULT_DATE;
|
if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false;
|
||||||
p->creationTime = FAT_DEFAULT_TIME;
|
// add and zero cluster for dirFile - first cluster is in cache for write
|
||||||
}
|
if (!dirFile->addDirCluster()) return false;
|
||||||
p->lastAccessDate = p->creationDate;
|
emptyCount += dirFile->vol_->blocksPerCluster() * 16;
|
||||||
p->lastWriteDate = p->creationDate;
|
}
|
||||||
p->lastWriteTime = p->creationTime;
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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
|
||||||
|
|
||||||
// write entry to SD
|
|
||||||
if (!dirFile->vol_->cacheFlush()) return false;
|
|
||||||
}
|
}
|
||||||
// open entry in cache
|
// open entry in cache
|
||||||
return openCachedEntry(index, oflag);
|
return openCachedEntry(index, oflag);
|
||||||
@ -808,6 +988,191 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) {
|
|||||||
return false;
|
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
|
#if 0
|
||||||
/**
|
/**
|
||||||
* Open a directory's parent directory.
|
* Open a directory's parent directory.
|
||||||
@ -1049,20 +1414,6 @@ int16_t SdBaseFile::read(void *buf, uint16_t nbyte) {
|
|||||||
return 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.
|
* Read the next entry in a directory.
|
||||||
*
|
*
|
||||||
@ -1110,30 +1461,40 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
|
|||||||
if (VFAT->firstClusterLow == 0) {
|
if (VFAT->firstClusterLow == 0) {
|
||||||
const uint8_t seq = VFAT->sequenceNumber & 0x1F;
|
const uint8_t seq = VFAT->sequenceNumber & 0x1F;
|
||||||
if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) {
|
if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) {
|
||||||
n = (seq - 1) * (FILENAME_LENGTH);
|
if (seq == 1) {
|
||||||
if (n == 0) {
|
|
||||||
checksum = VFAT->checksum;
|
checksum = VFAT->checksum;
|
||||||
checksum_error = 0;
|
checksum_error = 0;
|
||||||
}
|
}
|
||||||
else if (checksum != VFAT->checksum) // orphan detected
|
else if (checksum != VFAT->checksum) // orphan detected
|
||||||
checksum_error = 1;
|
checksum_error = 1;
|
||||||
|
|
||||||
LOOP_L_N(i, FILENAME_LENGTH) {
|
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||||
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)
|
getLFNName(VFAT, longFilename, seq); // Get chunk of LFN from VFAT entry
|
||||||
// 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.
|
#else // !LONG_FILENAME_WRITE_SUPPORT
|
||||||
uint16_t idx = (n + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding
|
|
||||||
longFilename[idx] = utf16_ch & 0xFF;
|
n = (seq - 1) * (FILENAME_LENGTH);
|
||||||
longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF;
|
|
||||||
#else
|
LOOP_L_N(i, FILENAME_LENGTH) {
|
||||||
// Replace all multibyte characters to '_'
|
const uint16_t utf16_ch = (i >= 11) ? VFAT->name3[i - 11] : (i >= 5) ? VFAT->name2[i - 5] : VFAT->name1[i];
|
||||||
longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF);
|
#if ENABLED(UTF_FILENAME_SUPPORT)
|
||||||
#endif
|
// 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 = (n + 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 '_'
|
||||||
|
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 this VFAT entry is the last one, add a NUL terminator at the end of the string
|
||||||
if (VFAT->sequenceNumber & 0x40)
|
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);
|
dir_t *d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
if (!d) return false;
|
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
|
// mark entry deleted
|
||||||
d->name[0] = DIR_NAME_DELETED;
|
d->name[0] = DIR_NAME_DELETED;
|
||||||
|
|
||||||
@ -1234,8 +1600,48 @@ bool SdBaseFile::remove() {
|
|||||||
type_ = FAT_FILE_TYPE_CLOSED;
|
type_ = FAT_FILE_TYPE_CLOSED;
|
||||||
|
|
||||||
// write entry to SD
|
// write entry to SD
|
||||||
return vol_->cacheFlush();
|
#if DISABLED(LONG_FILENAME_WRITE_SUPPORT)
|
||||||
return true;
|
|
||||||
|
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);
|
dir_t* cacheDirEntry(uint8_t action);
|
||||||
int8_t lsPrintNext(uint8_t flags, uint8_t indent);
|
int8_t lsPrintNext(uint8_t flags, uint8_t indent);
|
||||||
static bool make83Name(const char *str, uint8_t *name, const char **ptr);
|
static bool make83Name(const char *str, uint8_t *name, const char **ptr);
|
||||||
bool mkdir(SdBaseFile *parent, const uint8_t dname[11]);
|
bool mkdir(SdBaseFile *parent, const uint8_t dname[11]
|
||||||
bool open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t oflag);
|
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);
|
bool openCachedEntry(uint8_t cacheIndex, uint8_t oflags);
|
||||||
dir_t* readDirCache();
|
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) {
|
if (includeLongNames) {
|
||||||
SERIAL_CHAR(' ');
|
SERIAL_CHAR(' ');
|
||||||
if (prependLong) { SERIAL_ECHO(prependLong); SERIAL_CHAR('/'); }
|
if (prependLong) { SERIAL_ECHO(prependLong); SERIAL_CHAR('/'); }
|
||||||
SERIAL_ECHO(longFilename[0] ? longFilename : "???");
|
SERIAL_ECHO(longFilename[0] ? longFilename : filename);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
SERIAL_EOL();
|
SERIAL_EOL();
|
||||||
@ -385,9 +385,9 @@ void CardReader::ls(
|
|||||||
diveDir.rewind();
|
diveDir.rewind();
|
||||||
selectByName(diveDir, segment);
|
selectByName(diveDir, segment);
|
||||||
|
|
||||||
// Print /LongNamePart to serial output
|
// Print /LongNamePart to serial output or the short name if not available
|
||||||
SERIAL_CHAR('/');
|
SERIAL_CHAR('/');
|
||||||
SERIAL_ECHO(longFilename[0] ? longFilename : "???");
|
SERIAL_ECHO(longFilename[0] ? longFilename : filename);
|
||||||
|
|
||||||
// If the filename was printed then that's it
|
// If the filename was printed then that's it
|
||||||
if (!flag.filenameIsDir) break;
|
if (!flag.filenameIsDir) break;
|
||||||
|
@ -84,21 +84,26 @@ def Upload(source, target, env):
|
|||||||
#----------------#
|
#----------------#
|
||||||
# File functions #
|
# File functions #
|
||||||
#----------------#
|
#----------------#
|
||||||
def _GetFirmwareFiles():
|
def _GetFirmwareFiles(UseLongFilenames):
|
||||||
if Debug: print('Get firmware files...')
|
if Debug: print('Get firmware files...')
|
||||||
_Send('M20 F')
|
_Send(f"M20 F{'L' if UseLongFilenames else ''}")
|
||||||
Responses = _Recv()
|
Responses = _Recv()
|
||||||
if len(Responses) < 3 or not any('file list' in r for r in Responses):
|
if len(Responses) < 3 or not any('file list' in r for r in Responses):
|
||||||
raise Exception('Error getting firmware files')
|
raise Exception('Error getting firmware files')
|
||||||
if Debug: print('OK')
|
if Debug: print('OK')
|
||||||
return Responses
|
return Responses
|
||||||
|
|
||||||
def _FilterFirmwareFiles(FirmwareList):
|
def _FilterFirmwareFiles(FirmwareList, UseLongFilenames):
|
||||||
Firmwares = []
|
Firmwares = []
|
||||||
for FWFile in FirmwareList:
|
for FWFile in FirmwareList:
|
||||||
if not '/' in FWFile and '.BIN' in FWFile:
|
# For long filenames take the 3rd column of the firmwares list
|
||||||
idx = FWFile.index('.BIN')
|
if UseLongFilenames:
|
||||||
Firmwares.append(FWFile[:idx+4])
|
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
|
return Firmwares
|
||||||
|
|
||||||
def _RemoveFirmwareFile(FirmwareFile):
|
def _RemoveFirmwareFile(FirmwareFile):
|
||||||
@ -124,6 +129,8 @@ def Upload(source, target, env):
|
|||||||
marlin_board_info_name = _GetMarlinEnv(MarlinEnv, 'BOARD_INFO_NAME')
|
marlin_board_info_name = _GetMarlinEnv(MarlinEnv, 'BOARD_INFO_NAME')
|
||||||
marlin_board_custom_build_flags = _GetMarlinEnv(MarlinEnv, 'BOARD_CUSTOM_BUILD_FLAGS')
|
marlin_board_custom_build_flags = _GetMarlinEnv(MarlinEnv, 'BOARD_CUSTOM_BUILD_FLAGS')
|
||||||
marlin_firmware_bin = _GetMarlinEnv(MarlinEnv, 'FIRMWARE_BIN')
|
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_custom_firmware_upload = _GetMarlinEnv(MarlinEnv, 'CUSTOM_FIRMWARE_UPLOAD') is not None
|
||||||
marlin_short_build_version = _GetMarlinEnv(MarlinEnv, 'SHORT_BUILD_VERSION')
|
marlin_short_build_version = _GetMarlinEnv(MarlinEnv, 'SHORT_BUILD_VERSION')
|
||||||
marlin_string_config_h_author = _GetMarlinEnv(MarlinEnv, 'STRING_CONFIG_H_AUTHOR')
|
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": 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',
|
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']
|
'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:
|
try:
|
||||||
|
|
||||||
# Start upload job
|
# Start upload job
|
||||||
@ -156,28 +167,34 @@ def Upload(source, target, env):
|
|||||||
# Dump some debug info
|
# Dump some debug info
|
||||||
if Debug:
|
if Debug:
|
||||||
print('Upload using:')
|
print('Upload using:')
|
||||||
print('---- Marlin --------------------')
|
print('---- Marlin -----------------------------------')
|
||||||
print(f' PIOENV : {marlin_pioenv}')
|
print(f' PIOENV : {marlin_pioenv}')
|
||||||
print(f' SHORT_BUILD_VERSION : {marlin_short_build_version}')
|
print(f' SHORT_BUILD_VERSION : {marlin_short_build_version}')
|
||||||
print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}')
|
print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}')
|
||||||
print(f' MOTHERBOARD : {marlin_motherboard}')
|
print(f' MOTHERBOARD : {marlin_motherboard}')
|
||||||
print(f' BOARD_INFO_NAME : {marlin_board_info_name}')
|
print(f' BOARD_INFO_NAME : {marlin_board_info_name}')
|
||||||
print(f' CUSTOM_BUILD_FLAGS : {marlin_board_custom_build_flags}')
|
print(f' CUSTOM_BUILD_FLAGS : {marlin_board_custom_build_flags}')
|
||||||
print(f' FIRMWARE_BIN : {marlin_firmware_bin}')
|
print(f' FIRMWARE_BIN : {marlin_firmware_bin}')
|
||||||
print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}')
|
print(f' LONG_FILENAME_HOST_SUPPORT : {marlin_long_filename_host_support}')
|
||||||
print('---- Upload parameters ---------')
|
print(f' LONG_FILENAME_WRITE_SUPPORT : {marlin_longname_write}')
|
||||||
print(f' Source : {upload_firmware_source_name}')
|
print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}')
|
||||||
print(f' Target : {upload_firmware_target_name}')
|
print('---- Upload parameters ------------------------')
|
||||||
print(f' Port : {upload_port} @ {upload_speed} baudrate')
|
print(f' Source : {upload_firmware_source_name}')
|
||||||
print(f' Timeout : {upload_timeout}')
|
print(f' Target : {upload_firmware_target_name}')
|
||||||
print(f' Block size : {upload_blocksize}')
|
print(f' Port : {upload_port} @ {upload_speed} baudrate')
|
||||||
print(f' Compression : {upload_compression}')
|
print(f' Timeout : {upload_timeout}')
|
||||||
print(f' Error ratio : {upload_error_ratio}')
|
print(f' Block size : {upload_blocksize}')
|
||||||
print(f' Test : {upload_test}')
|
print(f' Compression : {upload_compression}')
|
||||||
print(f' Reset : {upload_reset}')
|
print(f' Error ratio : {upload_error_ratio}')
|
||||||
print('--------------------------------')
|
print(f' Test : {upload_test}')
|
||||||
|
print(f' Reset : {upload_reset}')
|
||||||
|
print('-----------------------------------------------')
|
||||||
|
|
||||||
# Custom implementations based on board parameters
|
# 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)
|
# Delete all *.bin files on the root of SD Card (if flagged)
|
||||||
if upload_delete_old_bins:
|
if upload_delete_old_bins:
|
||||||
@ -185,11 +202,6 @@ def Upload(source, target, env):
|
|||||||
if not marlin_custom_firmware_upload:
|
if not marlin_custom_firmware_upload:
|
||||||
raise Exception(f"CUSTOM_FIRMWARE_UPLOAD must be enabled in 'Configuration_adv.h' for '{marlin_motherboard}'")
|
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
|
# Init serial port
|
||||||
port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1)
|
port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1)
|
||||||
port.reset_input_buffer()
|
port.reset_input_buffer()
|
||||||
@ -198,13 +210,13 @@ def Upload(source, target, env):
|
|||||||
_CheckSDCard()
|
_CheckSDCard()
|
||||||
|
|
||||||
# Get firmware files
|
# Get firmware files
|
||||||
FirmwareFiles = _GetFirmwareFiles()
|
FirmwareFiles = _GetFirmwareFiles(marlin_long_filename_host_support)
|
||||||
if Debug:
|
if Debug:
|
||||||
for FirmwareFile in FirmwareFiles:
|
for FirmwareFile in FirmwareFiles:
|
||||||
print(f'Found: {FirmwareFile}')
|
print(f'Found: {FirmwareFile}')
|
||||||
|
|
||||||
# Get all 1st level firmware files (to remove)
|
# 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:
|
if len(OldFirmwareFiles) == 0:
|
||||||
print('No old firmware files to delete')
|
print('No old firmware files to delete')
|
||||||
else:
|
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
|
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 \
|
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 \
|
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 \
|
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 \
|
EEPROM_SETTINGS EEPROM_CHITCHAT GCODE_MACROS CUSTOM_MENU_MAIN \
|
||||||
MULTI_NOZZLE_DUPLICATION CLASSIC_JERK LIN_ADVANCE QUICK_HOME \
|
MULTI_NOZZLE_DUPLICATION CLASSIC_JERK LIN_ADVANCE QUICK_HOME \
|
||||||
|
Loading…
Reference in New Issue
Block a user