Prusa-Firmware/Firmware/cardreader.cpp

1077 lines
29 KiB
C++
Raw Normal View History

2016-07-22 13:28:01 +00:00
#include "Marlin.h"
#include "cmdqueue.h"
2016-07-22 13:28:01 +00:00
#include "cardreader.h"
#include "ultralcd.h"
#include "conv2str.h"
#include "menu.h"
2016-07-22 13:28:01 +00:00
#include "stepper.h"
#include "temperature.h"
#include "language.h"
#ifdef SDSUPPORT
#define LONGEST_FILENAME (longFilename[0] ? longFilename : filename)
2016-07-22 13:28:01 +00:00
CardReader::CardReader()
{
#ifdef SDCARD_SORT_ALPHA
sort_count = 0;
#endif
2016-07-22 13:28:01 +00:00
filesize = 0;
sdpos = 0;
sdprinting = false;
cardOK = false;
saving = false;
logging = false;
workDirDepth = 0;
file_subcall_ctr=0;
memset(workDirParents, 0, sizeof(workDirParents));
presort_flag = false;
2016-07-22 13:28:01 +00:00
lastnr=0;
//power to SD reader
#if SDPOWER > -1
SET_OUTPUT(SDPOWER);
WRITE(SDPOWER,HIGH);
#endif //SDPOWER
autostart_atmillis.start(); // reset timer
2016-07-22 13:28:01 +00:00
}
char *createFilename(char *buffer,const dir_t &p) //buffer>12characters
{
char *pos=buffer;
for (uint8_t i = 0; i < 11; i++)
{
if (p.name[i] == ' ')continue;
if (i == 8)
{
*pos++='.';
}
*pos++=p.name[i];
}
*pos++=0;
return buffer;
}
/**
+* Dive into a folder and recurse depth-first to perform a pre-set operation lsAction:
2021-01-22 09:13:44 +00:00
+* LS_Count - Add +1 to nrFiles for every file within the parent
+* LS_GetFilename - Get the filename of the file indexed by nrFiles
+* LS_SerialPrint - Print the full path and size of each file to serial output
+*/
void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/, LsAction lsAction, ls_param lsParams) {
static uint8_t recursionCnt = 0;
// RAII incrementer for the recursionCnt
class _incrementer
{
public:
_incrementer() {recursionCnt++;}
~_incrementer() {recursionCnt--;}
} recursionCntIncrementer;
dir_t p;
uint8_t cnt = 0;
// Read the next entry from a directory
2021-02-09 18:29:06 +00:00
for (position = parent.curPosition(); parent.readDir(p, longFilename) > 0; position = parent.curPosition()) {
if (recursionCnt > MAX_DIR_DEPTH)
return;
uint8_t pn0 = p.name[0];
if (pn0 == DIR_NAME_FREE) break;
if (pn0 == DIR_NAME_DELETED || pn0 == '.') continue;
if (longFilename[0] == '.') continue;
if (!DIR_IS_FILE_OR_SUBDIR(&p) || (p.attributes & DIR_ATT_HIDDEN)) continue;
2021-04-19 11:48:50 +00:00
if (DIR_IS_SUBDIR(&p) && lsAction == LS_SerialPrint) { // If the entry is a directory and the action is LS_SerialPrint
// Get the short name for the item, which we know is a folder
char lfilename[FILENAME_LENGTH];
createFilename(lfilename, p);
// Allocate enough stack space for the full path to a folder, trailing slash, and nul
bool prepend_is_empty = (prepend[0] == '\0');
int len = (prepend_is_empty ? 1 : strlen(prepend)) + strlen(lfilename) + 1 + 1;
char path[len];
// Append the FOLDERNAME12/ to the passed string.
// It contains the full path to the "parent" argument.
// We now have the full path to the item in this folder.
strcpy(path, prepend_is_empty ? "/" : prepend); // root slash if prepend is empty
strcat(path, lfilename); // FILENAME_LENGTH-1 characters maximum
strcat(path, "/"); // 1 character
// Serial.print(path);
// Get a new directory object using the full path
// and dive recursively into it.
2021-01-22 09:13:44 +00:00
if (lsParams.LFN)
2021-01-22 09:13:44 +00:00
printf_P(PSTR("DIR_ENTER: %s \"%s\"\n"), path, longFilename[0] ? longFilename : lfilename);
SdFile dir;
if (!dir.open(parent, lfilename, O_READ)) {
2021-04-19 11:48:50 +00:00
//SERIAL_ECHO_START();
//SERIAL_ECHOPGM(_i("Cannot open subdir"));////MSG_SD_CANT_OPEN_SUBDIR
//SERIAL_ECHOLN(lfilename);
}
2021-04-19 11:48:50 +00:00
lsDive(path, dir, NULL, lsAction, lsParams);
// close() is done automatically by destructor of SdFile
2021-01-22 09:13:44 +00:00
if (lsParams.LFN)
2021-01-22 09:13:44 +00:00
puts_P(PSTR("DIR_EXIT"));
}
else {
filenameIsDir = DIR_IS_SUBDIR(&p);
if (!filenameIsDir && (p.name[8] != 'G' || p.name[9] == '~')) continue;
switch (lsAction) {
case LS_Count:
nrFiles++;
break;
case LS_SerialPrint:
createFilename(filename, p);
SERIAL_PROTOCOL(prepend);
SERIAL_PROTOCOL(filename);
2021-04-19 10:54:43 +00:00
2017-12-11 10:30:49 +00:00
MYSERIAL.write(' ');
2021-04-19 10:54:43 +00:00
SERIAL_PROTOCOL(p.fileSize);
2021-01-22 09:13:44 +00:00
if (lsParams.timestamp)
2021-04-19 11:48:50 +00:00
{
crmodDate = p.lastWriteDate;
crmodTime = p.lastWriteTime;
if( crmodDate < p.creationDate || ( crmodDate == p.creationDate && crmodTime < p.creationTime ) ){
crmodDate = p.creationDate;
crmodTime = p.creationTime;
}
printf_P(PSTR(" %#lx"), ((uint32_t)crmodDate << 16) | crmodTime);
}
if (lsParams.LFN)
2021-04-19 10:54:43 +00:00
printf_P(PSTR(" \"%s\""), LONGEST_FILENAME);
2021-01-22 09:13:44 +00:00
2021-04-19 10:54:43 +00:00
SERIAL_PROTOCOLLN();
2021-01-25 09:46:51 +00:00
manage_heater();
break;
case LS_GetFilename:
2021-02-09 18:29:06 +00:00
//SERIAL_ECHOPGM("File: ");
createFilename(filename, p);
2021-02-09 18:29:06 +00:00
// cluster = parent.curCluster();
// position = parent.curPosition();
/*MYSERIAL.println(filename);
SERIAL_ECHOPGM("Write date: ");
writeDate = p.lastWriteDate;
MYSERIAL.println(writeDate);
writeTime = p.lastWriteTime;
SERIAL_ECHOPGM("Creation date: ");
MYSERIAL.println(p.creationDate);
SERIAL_ECHOPGM("Access date: ");
MYSERIAL.println(p.lastAccessDate);
SERIAL_ECHOLNPGM("");*/
crmodDate = p.lastWriteDate;
crmodTime = p.lastWriteTime;
// There are scenarios when simple modification time is not enough (on MS Windows)
// For example - extract an old g-code from an archive onto the SD card.
// In such case the creation time is current time (which is correct), but the modification time
// stays the same - i.e. old.
// Therefore let's pick the most recent timestamp from both creation and modification timestamps
if( crmodDate < p.creationDate || ( crmodDate == p.creationDate && crmodTime < p.creationTime ) ){
crmodDate = p.creationDate;
crmodTime = p.creationTime;
}
//writeDate = p.lastAccessDate;
if (match != NULL) {
if (strcasecmp(match, filename) == 0) return;
}
else if (cnt == nrFiles) return;
cnt++;
break;
}
}
} // while readDir
2016-07-22 13:28:01 +00:00
}
void CardReader::ls(ls_param params)
2016-07-22 13:28:01 +00:00
{
root.rewind();
lsDive("",root, NULL, LS_SerialPrint, params);
2016-07-22 13:28:01 +00:00
}
void CardReader::initsd(bool doPresort/* = true*/)
2016-07-22 13:28:01 +00:00
{
cardOK = false;
if(root.isOpen())
root.close();
#ifdef SDSLOW
if (!card.init(SPI_HALF_SPEED)
2016-07-22 13:28:01 +00:00
)
#else
if (!card.init(SPI_FULL_SPEED)
2016-07-22 13:28:01 +00:00
)
#endif
{
SERIAL_ECHO_START;
SERIAL_ECHOLNRPGM(_n("SD init fail"));////MSG_SD_INIT_FAIL
2016-07-22 13:28:01 +00:00
}
else if (!volume.init(&card))
{
SERIAL_ERROR_START;
SERIAL_ERRORLNRPGM(_n("volume.init failed"));////MSG_SD_VOL_INIT_FAIL
2016-07-22 13:28:01 +00:00
}
else if (!root.openRoot(&volume))
{
SERIAL_ERROR_START;
SERIAL_ERRORLNRPGM(_n("openRoot failed"));////MSG_SD_OPENROOT_FAIL
2016-07-22 13:28:01 +00:00
}
else
{
cardOK = true;
SERIAL_ECHO_START;
SERIAL_ECHOLNRPGM(_n("SD card ok"));////MSG_SD_CARD_OK
2016-07-22 13:28:01 +00:00
}
workDir=root;
curDir=&root;
workDirDepth = 0;
#ifdef SDCARD_SORT_ALPHA
if (doPresort)
presort();
#endif
2016-07-22 13:28:01 +00:00
/*
if(!workDir.openRoot(&volume))
{
SERIAL_ECHOLNPGM(MSG_SD_WORKDIR_FAIL);
2016-07-22 13:28:01 +00:00
}
*/
}
void CardReader::setroot(bool doPresort)
2016-07-22 13:28:01 +00:00
{
workDir=root;
workDirDepth = 0;
2016-07-22 13:28:01 +00:00
curDir=&workDir;
#ifdef SDCARD_SORT_ALPHA
if (doPresort)
presort();
else
presort_flag = true;
#endif
2016-07-22 13:28:01 +00:00
}
void CardReader::release()
{
sdprinting = false;
cardOK = false;
2020-08-28 14:32:07 +00:00
SERIAL_ECHO_START;
SERIAL_ECHOLNRPGM(_n("SD card released"));////MSG_SD_CARD_RELEASED
2016-07-22 13:28:01 +00:00
}
void CardReader::startFileprint()
{
if(cardOK)
{
sdprinting = true;
Stopped = false;
#ifdef SDCARD_SORT_ALPHA
2017-12-11 10:30:49 +00:00
//flush_presort();
#endif
2016-07-22 13:28:01 +00:00
}
}
void CardReader::openLogFile(const char* name)
2016-07-22 13:28:01 +00:00
{
logging = true;
openFileWrite(name);
2016-07-22 13:28:01 +00:00
}
2017-11-27 05:20:51 +00:00
void CardReader::getDirName(char* name, uint8_t level)
{
workDirParents[level].getFilename(name);
}
uint16_t CardReader::getWorkDirDepth() {
return workDirDepth;
}
2016-07-22 13:28:01 +00:00
void CardReader::getAbsFilename(char *t)
{
uint8_t cnt=0;
*t='/';t++;cnt++;
for(uint8_t i=0;i<workDirDepth;i++)
{
workDirParents[i].getFilename(t); //SDBaseFile.getfilename!
while(*t!=0 && cnt< MAXPATHNAMELENGTH)
{t++;cnt++;} //crawl counter forward.
}
if(cnt<MAXPATHNAMELENGTH-13)
file.getFilename(t);
else
t[0]=0;
}
2021-02-07 19:51:44 +00:00
void CardReader::printAbsFilenameFast()
{
SERIAL_PROTOCOL('/');
for (uint8_t i = 0; i < getWorkDirDepth(); i++)
{
SERIAL_PROTOCOL(dir_names[i]);
SERIAL_PROTOCOL('/');
}
SERIAL_PROTOCOL(LONGEST_FILENAME);
}
/**
* @brief Dive into subfolder
*
* Method sets curDir to point to root, in case fileName is null.
* Method sets curDir to point to workDir, in case fileName path is relative
* (doesn't start with '/')
* Method sets curDir to point to dir, which is specified by absolute path
* specified by fileName. In such case fileName is updated so it points to
* file name without the path.
*
* @param[in,out] fileName
* expects file name including path
* in case of absolute path, file name without path is returned
*/
bool CardReader::diveSubfolder (const char *&fileName)
{
curDir=&root;
if (!fileName)
return 1;
const char *dirname_start, *dirname_end;
if (fileName[0] == '/') // absolute path
{
setroot(false);
dirname_start = fileName + 1;
while (*dirname_start)
{
dirname_end = strchr(dirname_start, '/');
//SERIAL_ECHO("start:");SERIAL_ECHOLN((int)(dirname_start-name));
//SERIAL_ECHO("end :");SERIAL_ECHOLN((int)(dirname_end-name));
if (dirname_end && dirname_end > dirname_start)
{
const size_t maxLen = 12;
char subdirname[maxLen+1];
const size_t len = ((static_cast<size_t>(dirname_end-dirname_start))>maxLen) ? maxLen : (dirname_end-dirname_start);
strncpy(subdirname, dirname_start, len);
2021-01-31 14:42:01 +00:00
subdirname[len] = 0;
if (!chdir(subdirname, false))
return 0;
curDir = &workDir;
dirname_start = dirname_end + 1;
}
else // the reminder after all /fsa/fdsa/ is the filename
{
fileName = dirname_start;
//SERIAL_ECHOLN("remaider");
//SERIAL_ECHOLN(fname);
break;
}
}
}
else //relative path
{
curDir = &workDir;
}
return 1;
}
2016-07-22 13:28:01 +00:00
static const char ofKill[] PROGMEM = "trying to call sub-gcode files with too many levels.";
static const char ofSubroutineCallTgt[] PROGMEM = "SUBROUTINE CALL target:\"";
static const char ofParent[] PROGMEM = "\" parent:\"";
static const char ofPos[] PROGMEM = "\" pos";
static const char ofNowDoingFile[] PROGMEM = "Now doing file: ";
static const char ofNowFreshFile[] PROGMEM = "Now fresh file: ";
static const char ofFileOpened[] PROGMEM = "File opened: ";
static const char ofSize[] PROGMEM = " Size: ";
static const char ofFileSelected[] PROGMEM = "File selected";
static const char ofSDPrinting[] PROGMEM = "SD-PRINTING";
static const char ofWritingToFile[] PROGMEM = "Writing to file: ";
void CardReader::openFileReadFilteredGcode(const char* name, bool replace_current/* = false*/){
if(!cardOK)
return;
if(file.isOpen()){ //replacing current file by new file, or subfile call
if(!replace_current){
if((int)file_subcall_ctr>(int)SD_PROCEDURE_DEPTH-1){
// SERIAL_ERROR_START;
// SERIAL_ERRORPGM("trying to call sub-gcode files with too many levels. MAX level is:");
// SERIAL_ERRORLN(SD_PROCEDURE_DEPTH);
kill(ofKill, 1);
return;
}
SERIAL_ECHO_START;
SERIAL_ECHORPGM(ofSubroutineCallTgt);
SERIAL_ECHO(name);
SERIAL_ECHORPGM(ofParent);
//store current filename and position
getAbsFilename(filenames[file_subcall_ctr]);
SERIAL_ECHO(filenames[file_subcall_ctr]);
SERIAL_ECHORPGM(ofPos);
SERIAL_ECHOLN(sdpos);
filespos[file_subcall_ctr]=sdpos;
file_subcall_ctr++;
} else {
SERIAL_ECHO_START;
SERIAL_ECHORPGM(ofNowDoingFile);
SERIAL_ECHOLN(name);
}
file.close();
} else { //opening fresh file
file_subcall_ctr=0; //resetting procedure depth in case user cancels print while in procedure
SERIAL_ECHO_START;
SERIAL_ECHORPGM(ofNowFreshFile);
SERIAL_ECHOLN(name);
2016-07-22 13:28:01 +00:00
}
sdprinting = false;
const char *fname=name;
2021-02-10 11:15:57 +00:00
if (!diveSubfolder(fname))
return;
if (file.openFilteredGcode(curDir, fname)) {
getfilename(0, fname);
filesize = file.fileSize();
SERIAL_PROTOCOLRPGM(ofFileOpened);////MSG_SD_FILE_OPENED
printAbsFilenameFast();
SERIAL_PROTOCOLRPGM(ofSize);////MSG_SD_SIZE
SERIAL_PROTOCOLLN(filesize);
sdpos = 0;
SERIAL_PROTOCOLLNRPGM(ofFileSelected);////MSG_SD_FILE_SELECTED
lcd_setstatuspgm(ofFileSelected);
scrollstuff = 0;
} else {
SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL);
SERIAL_PROTOCOL(fname);
SERIAL_PROTOCOLLN('.');
}
}
void CardReader::openFileWrite(const char* name)
2016-07-22 13:28:01 +00:00
{
if(!cardOK)
return;
if(file.isOpen()){ //replacing current file by new file, or subfile call
#if 0
// I doubt chained files support is necessary for file saving:
// Intentionally disabled because it takes a lot of code size while being not used
if((int)file_subcall_ctr>(int)SD_PROCEDURE_DEPTH-1){
// SERIAL_ERROR_START;
// SERIAL_ERRORPGM("trying to call sub-gcode files with too many levels. MAX level is:");
// SERIAL_ERRORLN(SD_PROCEDURE_DEPTH);
kill(ofKill, 1);
return;
}
SERIAL_ECHO_START;
SERIAL_ECHORPGM(ofSubroutineCallTgt);
SERIAL_ECHO(name);
SERIAL_ECHORPGM(ofParent);
//store current filename and position
getAbsFilename(filenames[file_subcall_ctr]);
SERIAL_ECHO(filenames[file_subcall_ctr]);
SERIAL_ECHORPGM(ofPos);
SERIAL_ECHOLN(sdpos);
filespos[file_subcall_ctr]=sdpos;
file_subcall_ctr++;
file.close();
#else
SERIAL_ECHOLNPGM("File already opened");
#endif
} else { //opening fresh file
file_subcall_ctr=0; //resetting procedure depth in case user cancels print while in procedure
SERIAL_ECHO_START;
SERIAL_ECHORPGM(ofNowFreshFile);
SERIAL_ECHOLN(name);
2016-07-22 13:28:01 +00:00
}
sdprinting = false;
const char *fname=name;
if (!diveSubfolder(fname))
return;
//write
if (!file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)){
SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL);
SERIAL_PROTOCOL(fname);
SERIAL_PROTOCOLLN('.');
} else {
saving = true;
getfilename(0, fname);
SERIAL_PROTOCOLRPGM(ofWritingToFile);////MSG_SD_WRITE_TO_FILE
printAbsFilenameFast();
SERIAL_PROTOCOLLN();
SERIAL_PROTOCOLLNRPGM(ofFileSelected);////MSG_SD_FILE_SELECTED
lcd_setstatuspgm(ofFileSelected);
scrollstuff = 0;
2016-07-22 13:28:01 +00:00
}
}
void CardReader::removeFile(const char* name)
2016-07-22 13:28:01 +00:00
{
if(!cardOK) return;
file.close();
sdprinting = false;
const char *fname=name;
if (!diveSubfolder(fname))
return;
2016-07-22 13:28:01 +00:00
if (file.remove(curDir, fname))
{
SERIAL_PROTOCOLPGM("File deleted:");
SERIAL_PROTOCOLLN(fname);
sdpos = 0;
#ifdef SDCARD_SORT_ALPHA
presort();
#endif
2016-07-22 13:28:01 +00:00
}
else
{
SERIAL_PROTOCOLPGM("Deletion failed, File: ");
SERIAL_PROTOCOL(fname);
SERIAL_PROTOCOLLN('.');
2016-07-22 13:28:01 +00:00
}
}
uint32_t CardReader::getFileSize()
{
return filesize;
}
void CardReader::getStatus(bool arg_P)
2016-07-22 13:28:01 +00:00
{
if (isPrintPaused)
{
if (saved_printing && (saved_printing_type == PRINTING_TYPE_SD))
SERIAL_PROTOCOLLNPGM("SD print paused");
else
SERIAL_PROTOCOLLNPGM("Print saved");
}
else if (sdprinting)
{
if (arg_P)
{
2021-02-07 19:51:44 +00:00
printAbsFilenameFast();
SERIAL_PROTOCOLLN();
}
else
SERIAL_PROTOCOLLN(LONGEST_FILENAME);
SERIAL_PROTOCOLRPGM(_N("SD printing byte "));////MSG_SD_PRINTING_BYTE
SERIAL_PROTOCOL(sdpos);
SERIAL_PROTOCOL('/');
SERIAL_PROTOCOLLN(filesize);
uint16_t time = ( _millis() - starttime ) / 60000U;
SERIAL_PROTOCOL(itostr2(time/60));
SERIAL_PROTOCOL(':');
SERIAL_PROTOCOLLN(itostr2(time%60));
}
else
SERIAL_PROTOCOLLNPGM("Not SD printing");
2016-07-22 13:28:01 +00:00
}
void CardReader::write_command(char *buf)
{
file.writeError = false;
file.write(buf); //write command
file.write("\r\n"); //write line termination
2016-07-22 13:28:01 +00:00
if (file.writeError)
{
SERIAL_ERROR_START;
SERIAL_ERRORLNRPGM(MSG_SD_ERR_WRITE_TO_FILE);
2016-07-22 13:28:01 +00:00
}
}
2017-03-24 18:47:50 +00:00
#define CHUNK_SIZE 64
void CardReader::write_command_no_newline(char *buf)
{
file.write(buf, CHUNK_SIZE);
if (file.writeError)
{
SERIAL_ERROR_START;
SERIAL_ERRORLNRPGM(MSG_SD_ERR_WRITE_TO_FILE);
SERIAL_PROTOCOLLNPGM("An error while writing to the SD Card.");
2017-03-24 18:47:50 +00:00
}
}
2016-07-22 13:28:01 +00:00
void CardReader::checkautostart(bool force)
{
// The SD start is delayed because otherwise the serial cannot answer
// fast enough to make contact with the host software.
static bool autostart_stilltocheck = true;
2016-07-22 13:28:01 +00:00
if(!force)
{
if(!autostart_stilltocheck)
return;
if(autostart_atmillis.expired(5000))
2016-07-22 13:28:01 +00:00
return;
}
autostart_stilltocheck = false;
2016-07-22 13:28:01 +00:00
if(!cardOK)
{
initsd();
if(!cardOK) //fail
return;
}
char autoname[30];
sprintf_P(autoname, PSTR("auto%i.g"), lastnr);
for(int8_t i=0;i<(int8_t)strlen(autoname);i++)
autoname[i]=tolower(autoname[i]);
dir_t p;
root.rewind();
bool found=false;
while (root.readDir(p, NULL) > 0)
{
for(int8_t i=0;i<(int8_t)strlen((char*)p.name);i++)
p.name[i]=tolower(p.name[i]);
//Serial.print((char*)p.name);
//Serial.print(" ");
//Serial.println(autoname);
if(p.name[9]!='~') //skip safety copies
if(strncmp((char*)p.name,autoname,5)==0)
{
char cmd[30];
// M23: Select SD file
sprintf_P(cmd, PSTR("M23 %s"), autoname);
enquecommand(cmd);
// M24: Start/resume SD print
enquecommand_P(PSTR("M24"));
found=true;
}
}
if(!found)
lastnr=-1;
else
lastnr++;
}
void CardReader::closefile(bool store_location)
{
file.sync();
file.close();
saving = false;
logging = false;
if(store_location)
{
//future: store printer state, filename and position for continuing a stopped print
// so one can unplug the printer and continue printing the next day.
}
}
void CardReader::getfilename(uint16_t nr, const char * const match/*=NULL*/)
{
curDir=&workDir;
nrFiles=nr;
curDir->rewind();
2021-04-19 11:48:50 +00:00
lsDive("",*curDir,match, LS_GetFilename);
2016-07-22 13:28:01 +00:00
}
void CardReader::getfilename_simple(uint16_t entry, const char * const match/*=NULL*/)
2017-12-11 10:30:49 +00:00
{
curDir = &workDir;
nrFiles = 0;
curDir->seekSet((uint32_t)entry << 5);
2021-04-19 11:48:50 +00:00
lsDive("", *curDir, match, LS_GetFilename);
2017-12-11 10:30:49 +00:00
}
2021-02-09 18:31:02 +00:00
void CardReader::getfilename_next(uint32_t position, const char * const match/*=NULL*/)
{
curDir = &workDir;
nrFiles = 1;
curDir->seekSet(position);
2021-04-19 11:48:50 +00:00
lsDive("", *curDir, match, LS_GetFilename);
2021-02-09 18:31:02 +00:00
}
2016-07-22 13:28:01 +00:00
uint16_t CardReader::getnrfilenames()
{
curDir=&workDir;
nrFiles=0;
curDir->rewind();
2021-04-19 11:48:50 +00:00
lsDive("",*curDir, NULL, LS_Count);
2016-07-22 13:28:01 +00:00
//SERIAL_ECHOLN(nrFiles);
return nrFiles;
}
bool CardReader::chdir(const char * relpath, bool doPresort)
2016-07-22 13:28:01 +00:00
{
SdFile newfile;
SdFile *parent=&root;
if(workDir.isOpen())
parent=&workDir;
if(!newfile.open(*parent,relpath, O_READ) || ((workDirDepth + 1) >= MAX_DIR_DEPTH))
2016-07-22 13:28:01 +00:00
{
SERIAL_ECHO_START;
SERIAL_ECHORPGM(_n("Cannot enter subdir: "));////MSG_SD_CANT_ENTER_SUBDIR
2016-07-22 13:28:01 +00:00
SERIAL_ECHOLN(relpath);
return 0;
2016-07-22 13:28:01 +00:00
}
else
{
strcpy(dir_names[workDirDepth], relpath);
puts(relpath);
2016-07-22 13:28:01 +00:00
if (workDirDepth < MAX_DIR_DEPTH) {
for (int d = ++workDirDepth; d--;)
workDirParents[d+1] = workDirParents[d];
workDirParents[0]=*parent;
}
workDir=newfile;
#ifdef SDCARD_SORT_ALPHA
if (doPresort)
2017-12-11 10:30:49 +00:00
presort();
else
presort_flag = true;
#endif
return 1;
2016-07-22 13:28:01 +00:00
}
}
void CardReader::updir()
{
if(workDirDepth > 0)
{
--workDirDepth;
workDir = workDirParents[0];
for (unsigned int d = 0; d < workDirDepth; d++)
{
workDirParents[d] = workDirParents[d+1];
}
2017-12-11 10:30:49 +00:00
#ifdef SDCARD_SORT_ALPHA
presort();
2017-12-11 10:30:49 +00:00
#endif
2016-07-22 13:28:01 +00:00
}
}
#ifdef SDCARD_SORT_ALPHA
/**
* Get the name of a file in the current directory by sort-index
*/
void CardReader::getfilename_sorted(const uint16_t nr, uint8_t sdSort) {
2021-02-03 17:18:13 +00:00
if (nr < sort_count)
getfilename_simple(sort_entries[(sdSort == SD_SORT_ALPHA) ? (sort_count - nr - 1) : nr]);
else
2021-02-03 17:18:13 +00:00
getfilename(nr);
}
/**
* Read all the files and produce a sort key
*
* We can do this in 3 ways...
* - Minimal RAM: Read two filenames at a time sorting along...
* - Some RAM: Buffer the directory just for this sort
* - Most RAM: Buffer the directory and return filenames from RAM
*/
void CardReader::presort() {
2017-12-11 10:30:49 +00:00
if (farm_mode || IS_SD_INSERTED == false) return; //sorting is not used in farm mode
uint8_t sdSort = eeprom_read_byte((uint8_t*)EEPROM_SD_SORT);
if (sdSort == SD_SORT_NONE) return; //sd sort is turned off
2017-12-11 10:30:49 +00:00
KEEPALIVE_STATE(IN_HANDLER);
// Throw away old sort index
flush_presort();
// If there are files, sort up to the limit
uint16_t fileCnt = getnrfilenames();
if (fileCnt > 0) {
// Never sort more than the max allowed
// If you use folders to organize, 20 may be enough
2017-12-11 10:30:49 +00:00
if (fileCnt > SDSORT_LIMIT) {
MK3 3 9 0 missing translations (#2646) * Add and update missing translations - updated in Firmware/ files the missing `c=xx` column and `r=yy` rows. - added missing translations to lang/lang_en*.txt Everyone is developing and adding messages to serial and especially to LCD PLEASE add `//// c=xx` or `//// c=xx r=yy` comments. Preparing translations files without that information is a pain in the ... and takes way more time for somebody else to review to code as it would take you. * No need to have `MSG_abcde` again in comments `////` in `messages.c` * German translation * Missed a space * Use the same format as somewhere else * French translation. I am not a native French speaking person, so please excuse my mistakes I may have done. * Spanish translation. I am not a native Spanish speaking person, so please excuse my mistakes I may have done. * CZ translation * Fix typos * Another fix It is Dimmwert and not Dim Wert * Fix issues reported by `lang-check.py` * Add "difficult" messages containing `%` * Updated MSG and German translation * removed a translation as it breaks the language selection * No need to wait until any-key is pressed * No need to wait any-key is pressed * Fixed two LF issues * Updated PO files ready to be send to translators * Add missing italian translations * Improve some existing italian translations * More italian fixes * More italian fixes * Add exceptions in editorconfig for po files to avoid recoding * Fix typo Thanks @DRracer for pointing out * Italian translation by @wavexx * Update po/new/*.po files * Update after merging MK3 branch * Update French translation and some c=xx comments Big thanks to @awenelo @carlin57 for helping with the french translations and their comments. * Update po files after French translation * Fixed most `lang-check.py` reported translation errors for Czech and German. Two Czech have to be reviewed as these are too long. One German is correct as it is shown in c=20 r=2 but is 1 char longer than this to split the message. One German translation seams to be to long but have to review the actual max length * Fix `lang-check.py` Spanish translation errors There have been quite lot TOO long messages, Can't imagine that nobody every complained about that. * Fix `lang-check.py` Italian translations errors * Update not_tran and not_used files after fixing several translations * Some more error fixes and update of `po` files * Polish translation * Czech updated * Fix typo * no need to translate `\x00` if it is the same * Polish: Runouts->Koniec * Polish: Runouts->Konce f ... hopefully the last change * Added MK2.5/s auto power mode to eeprom doxygen * Final updates. - Compiled all versions with multi-languages - Compiled all versions with EN_ONLY - updated all /lang/po/Firmware*.* files * Add crlf attributes for po files As done for editorconfig, this similarly forces git to handle po files consistently in DOS format. * Further improvent of IT translations * Updated translation Added cleanup to PF-build.sh * remove lang/not_tran* and lang/not_used mistakenly added into the PR Co-authored-by: DRracer <drracer@seznam.cz> Co-authored-by: Yuri D'Elia <wavexx@thregr.org> Co-authored-by: D.R.racer <drracer@drracer.eu>
2020-05-12 20:23:40 +00:00
lcd_show_fullscreen_message_and_wait_P(_i("Some files will not be sorted. Max. No. of files in 1 folder for sorting is 100."));////MSG_FILE_CNT c=20 r=6
2017-12-11 10:30:49 +00:00
fileCnt = SDSORT_LIMIT;
}
// By default re-read the names from SD for every compare
// retaining only two filenames at a time. This is very
// slow but is safest and uses minimal RAM.
char name1[LONG_FILENAME_LENGTH];
uint16_t crmod_time_bckp;
uint16_t crmod_date_bckp;
2020-01-08 19:16:08 +00:00
#if HAS_FOLDER_SORTING
uint16_t dirCnt = 0;
#endif
if (fileCnt > 1) {
// Init sort order.
uint8_t sort_order[fileCnt];
for (uint16_t i = 0; i < fileCnt; i++) {
2017-12-11 10:30:49 +00:00
if (!IS_SD_INSERTED) return;
manage_heater();
2021-02-09 18:31:02 +00:00
if (i == 0)
getfilename(0);
else
getfilename_next(position);
sort_order[i] = i;
sort_entries[i] = position >> 5;
2020-01-08 19:16:08 +00:00
#if HAS_FOLDER_SORTING
if (filenameIsDir) dirCnt++;
#endif
}
2017-12-11 10:30:49 +00:00
#ifdef QUICKSORT
quicksort(0, fileCnt - 1);
#elif defined(SHELLSORT)
2021-02-26 06:57:49 +00:00
#define _SORT_CMP_NODIR() (strcasecmp(name1, name2) < 0) //true if lowercase(name1) < lowercase(name2)
#define _SORT_CMP_TIME_NODIR() (((crmod_date_bckp == crmodDate) && (crmod_time_bckp < crmodTime)) || (crmod_date_bckp < crmodDate))
2019-11-26 09:29:57 +00:00
2019-11-26 13:49:45 +00:00
#if HAS_FOLDER_SORTING
2021-02-26 06:57:49 +00:00
#define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs < 0 ? dir1 : !dir1))
#define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs < 0 ? dir1 : !dir1))
2019-11-26 13:49:45 +00:00
#endif
2021-02-26 06:57:49 +00:00
2020-01-08 19:16:08 +00:00
for (uint8_t runs = 0; runs < 2; runs++)
2019-11-26 09:29:57 +00:00
{
2020-01-08 19:16:08 +00:00
//run=0: sorts all files and moves folders to the beginning
//run=1: assumes all folders are at the beginning of the list and sorts them
uint16_t sortCountFiles = 0;
if (runs == 0)
2019-11-26 09:29:57 +00:00
{
2020-01-08 19:16:08 +00:00
sortCountFiles = fileCnt;
}
#if HAS_FOLDER_SORTING
else
{
sortCountFiles = dirCnt;
}
#endif
uint16_t counter = 0;
uint16_t total = 0;
for (uint16_t i = sortCountFiles/2; i > 0; i /= 2) total += sortCountFiles - i; //total runs for progress bar
New PO-based language translation support (#3471) * lang: Add a PO language extractor with FW metadata support Implement a straight-to-po language extractor which supports our custom language requirements: - _i/_I/ISTR for text string definitions - _T for catalog translations (with back-reference support) - //// EOL comments with: - MSG_ catalog entry name identifiers - c=X r=Y annotations for screen dimensioning checks - Crude support for commented lines All source locations are correctly referenced in the PO, with the metadata colleted in the comment for further processing. Several checks are implemented already during extraction: - Correct catalog name assignment (no duplicates) - Metadata checks for each entry Further checks will be implemented by directly checking the translated PO file. Requires "polib" and "regex" python modules. * lang: Adapt lang-check to work directly on PO/POT files * lang: Allow lang-extract to generate stable (pre-sorted) output directly * lang: Further extend lang-extract consistency/error checking - Do not parse inside preprocessor conditionals - Distinguish between references and definitions - Warn about missing references and definitions * lang: lang-extract: warn about incorrect PROGMEM assignments Check that ISTR is used along with PROGMEM_I1 in an attempt to spot useless translated catalogs. * lang: lang-extract: Improved handling of same-line translations Correctly reference metadata on same-line translations. * lang: lang-extract: Handle _O as a cat-ref https://github.com/prusa3d/Prusa-Firmware/pull/3434 * lang: lang-extract: Warn about unused catalog definitions * lang: lang-extract: Allow propagating translation comments via // The definition: code //// definition [// comment] will check [definition] as before, but blindly accumulate // comment. The comment is then re-appended back into the PO files for translators with the form: definition comment comment... * lang: Fix incorrect display definitions * lang: lang-extract: Check source encoding/charmap * lang: Translate the degree symbol * lang: Unbreak/cleanup DEBUG_SEC_LANG * lang: Improve meaning of comment * lang: Split charset conversions into an aux lib for future use * lang: Implement lang-map.py to extract the translation symbol map - Extracts the translatable symbol map for further use - Computes a stable "language signature" from the map itself - Optionally patches the binary update the symbols * lang: Check for translation recoding problems * lang: Implement a transliteration map to post-process translations TRANS_CHARS is now used to replace unavailable symbols to the source encoding, only while producing the language catalog. * lang: Handle/check character replacements in lang-check Filter the translation through TRANS_CHARS, so that the preview and length check are performed correctly for expanding replacements such as 'ß' to 'ss'. * lang: Implement lang-build.py to generate the final language catalog * Cleanup .gitignore * lang: Drop txt language files * lang: Remove outdated translation scripts and obsolete docs * lang: Update build scripts for new infrastructure * lang: [no] Integrate accents from po/new/no.po We now support accents natively * lang: Remove redundant directory po/new/ * lang: Fix encoding of LCD characters in PO files * lang: [hr] Fix wrapping in MSG_CRASH_DET_ONLY_IN_NORMAL * lang: Sort and reformat PO files for further massaging * lang: Switch to developer (dot) comments for PO metadata * lang: Allow the IGNORE annotation to skip extraction * lang: Fix missing/broken language metadata in sources * lang: Add update-pot.sh and regenerate po/Firmware.pot * lang: Add update-po.sh and refresh all PO files * lang: Add summary documentation about the new translation workflow * Add more ignored files * CI: Add new required dependencies to travis * lang: lang-build: Improve warning message "referenced" was really meaning that data is being duplicated. * lang: Respect the language order as defined in config.sh This correctly splits normal and community-made entries during language selection. * lang: More typos in the documentation * lang: Check for the maximum size of each language Each table needs to fit within LANG_SIZE_RESERVED * lang: Properly align _SEC_LANG to page boundaries ... instead of relying on _SEC_LANG_TABLE to calculate the offset * lang: Build support for dual-language hex files Detect the printer type by checking the current variant type. On printers with no xflash (MK2*), generate one hex file for each additional language file by patching the built-in secondary language table during the build process * lang: Mention lang-patchsec.py * lang: Use color() instead of tput for clarity * lang: Allow disabling terminal colors with NO_COLOR/TERM=dumb * lang: Consistent use of redirection in config.sh * lang: Stricter variant-type check for xflash support * lang: Output size stats when building double-language hex files * lang: Respect NO_COLOR in lang-check.py * lang: Check for repeated/incorrect annotations Catch errors such as "c=1 c=2" * lang: Correct MSG_SLIGHT_SKEW/MSG_SEVERE_SKEW annotations * lang: [it] Improve MSG_*_SKEW translation * lang: Use INTLHEX instead of OUTHEX_P/S for configuration We already have OUTHEX which is the compiled firmware. Use INTLHEX for the final internationalized firmware, which is less confusing. Also, assume it being a prefix for all generated hex files, which reduces the number of variables set. * lang: Move lang_map to lib.io for further use * lang: lang-check: Accept a firmware map file to suppress unused string warnings * lang: Use the map file to reduce useless warnings during fw-build * lang: lang-check: Also suppress unused empty annotations * lang: Fix MSG_MOVE_CARRIAGE_TO_THE_TOP_Z annotation Refresh pot file * lang: lang-check: Do not warn about same-word translations by default Do not warn when one-word translations such as "No" result in "No" also in other languages, since this is common in latin languages. Allow to re-enable the warning with --warn-same * lang: lang-build: Handle same-source/translation efficiently * lang: [it] Explicitly add On/Off/Reset/Wizard to suppress warnings Instead of displaying a warning, supress the warning and explicitly translate each entry using english (which is the common/acceptable word in these cases). * lang: [it] Suppress more warnings * lang: lang-check: Add intermediate "suggest" warning category Warnings in the "suggest" category as shown as [S] as based on pure speculation from the checking tool, such as the translation being significantly shorter than the original. As a result, they can be suppressed with --no-suggest * lang: Return translation status from lang-check - 0 if the translation only contains suggestions - 1 if the translation contains warnings or errors Check for the exit status in fw-build.sh, but do nothing at the moment except printing a non-fatal error. * lang: Remove "trim_trailing_whitespace=false" for po files PO files got cleaned up/rewritten. We can now ensure they stay consistent. * lang: [sv] Re-integrate changes from 70c73cb * lang: [no] Reintegrate changes from @pkg2000
2022-06-16 13:03:30 +00:00
menu_progressbar_init(
total, (runs == 0)
? _i("Sorting files") ////MSG_SORTING_FILES c=20
: _i("Sorting folders")); ////MSG_SORTING_FOLDERS c=20
2020-01-08 19:16:08 +00:00
for (uint16_t gap = sortCountFiles/2; gap > 0; gap /= 2)
{
for (uint16_t i = gap; i < sortCountFiles; i++)
2019-11-26 09:29:57 +00:00
{
2020-01-08 19:16:08 +00:00
if (!IS_SD_INSERTED) return;
2021-02-25 18:25:32 +00:00
menu_progressbar_update(counter);
2020-01-08 19:16:08 +00:00
counter++;
manage_heater();
2021-02-26 06:57:49 +00:00
uint8_t orderBckp = sort_order[i];
getfilename_simple(sort_entries[orderBckp]);
2020-01-08 19:16:08 +00:00
strcpy(name1, LONGEST_FILENAME); // save (or getfilename below will trounce it)
crmod_date_bckp = crmodDate;
crmod_time_bckp = crmodTime;
2020-01-08 19:16:08 +00:00
#if HAS_FOLDER_SORTING
bool dir1 = filenameIsDir;
#endif
uint16_t j = i;
getfilename_simple(sort_entries[sort_order[j - gap]]);
2020-01-08 19:16:08 +00:00
char *name2 = LONGEST_FILENAME; // use the string in-place
#if HAS_FOLDER_SORTING
while (j >= gap && ((sdSort == SD_SORT_TIME)?_SORT_CMP_TIME_DIR(FOLDER_SORTING):_SORT_CMP_DIR(FOLDER_SORTING)))
#else
while (j >= gap && ((sdSort == SD_SORT_TIME)?_SORT_CMP_TIME_NODIR():_SORT_CMP_NODIR()))
2019-11-26 10:18:11 +00:00
#endif
2020-01-08 19:16:08 +00:00
{
2021-02-26 06:57:49 +00:00
sort_order[j] = sort_order[j - gap];
2020-01-08 19:16:08 +00:00
j -= gap;
#ifdef SORTING_DUMP
for (uint16_t z = 0; z < sortCountFiles; z++)
{
2021-02-26 06:57:49 +00:00
printf_P(PSTR("%2u "), sort_order[z]);
2020-01-08 19:16:08 +00:00
}
printf_P(PSTR("i%2d j%2d gap%2d orderBckp%2d\n"), i, j, gap, orderBckp);
#endif
if (j < gap) break;
getfilename_simple(sort_entries[sort_order[j - gap]]);
2020-01-08 19:16:08 +00:00
name2 = LONGEST_FILENAME; // use the string in-place
}
2021-02-26 06:57:49 +00:00
sort_order[j] = orderBckp;
2019-11-26 09:29:57 +00:00
}
}
}
#else //Bubble Sort
#define _SORT_CMP_NODIR() (strcasecmp(name1, name2) < 0) //true if lowercase(name1) < lowercase(name2)
#define _SORT_CMP_TIME_NODIR() (((crmod_date_bckp == crmodDate) && (crmod_time_bckp > crmodTime)) || (crmod_date_bckp > crmodDate))
#if HAS_FOLDER_SORTING
#define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs < 0 ? dir1 : !dir1))
#define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs < 0 ? dir1 : !dir1))
#endif
2021-02-25 18:25:32 +00:00
uint16_t counter = 0;
menu_progressbar_init(0.5*(fileCnt - 1)*(fileCnt), _i("Sorting files"));
2017-12-11 10:30:49 +00:00
for (uint16_t i = fileCnt; --i;) {
2017-12-11 10:30:49 +00:00
if (!IS_SD_INSERTED) return;
bool didSwap = false;
2021-02-25 18:25:32 +00:00
menu_progressbar_update(counter);
counter++;
for (uint16_t j = 0; j < i; ++j) {
2017-12-11 10:30:49 +00:00
if (!IS_SD_INSERTED) return;
2019-11-26 10:18:11 +00:00
#ifdef SORTING_DUMP
for (uint16_t z = 0; z < fileCnt; z++)
{
printf_P(PSTR("%2u "), sort_order[z]);
}
MYSERIAL.println();
#endif
2017-12-11 10:30:49 +00:00
manage_heater();
const uint16_t o1 = sort_order[j], o2 = sort_order[j + 1];
2017-12-11 10:30:49 +00:00
counter++;
getfilename_simple(sort_entries[o1]);
strcpy(name1, LONGEST_FILENAME); // save (or getfilename below will trounce it)
crmod_date_bckp = crmodDate;
crmod_time_bckp = crmodTime;
#if HAS_FOLDER_SORTING
bool dir1 = filenameIsDir;
#endif
getfilename_simple(sort_entries[o2]);
char *name2 = LONGEST_FILENAME; // use the string in-place
// Sort the current pair according to settings.
if (
#if HAS_FOLDER_SORTING
(sdSort == SD_SORT_TIME && _SORT_CMP_TIME_DIR(FOLDER_SORTING)) || (sdSort == SD_SORT_ALPHA && !_SORT_CMP_DIR(FOLDER_SORTING))
#else
(sdSort == SD_SORT_TIME && _SORT_CMP_TIME_NODIR()) || (sdSort == SD_SORT_ALPHA && !_SORT_CMP_NODIR())
#endif
)
{
2021-02-26 06:57:49 +00:00
#ifdef SORTING_DUMP
puts_P(PSTR("swap"));
#endif
sort_order[j] = o2;
sort_order[j + 1] = o1;
didSwap = true;
}
}
if (!didSwap) break;
} //end of bubble sort loop
2017-12-11 10:30:49 +00:00
#endif
2021-02-26 06:57:49 +00:00
#ifdef SORTING_DUMP
for (uint16_t z = 0; z < fileCnt; z++)
printf_P(PSTR("%2u "), sort_order[z]);
SERIAL_PROTOCOLLN();
#endif
uint8_t sort_order_reverse_index[fileCnt];
for (uint8_t i = 0; i < fileCnt; i++)
sort_order_reverse_index[sort_order[i]] = i;
for (uint8_t i = 0; i < fileCnt; i++)
{
if (sort_order_reverse_index[i] != i)
{
uint32_t el = sort_entries[i];
uint8_t idx = sort_order_reverse_index[i];
while (idx != i)
{
uint32_t el1 = sort_entries[idx];
uint8_t idx1 = sort_order_reverse_index[idx];
sort_order_reverse_index[idx] = idx;
sort_entries[idx] = el;
idx = idx1;
el = el1;
}
sort_order_reverse_index[idx] = idx;
sort_entries[idx] = el;
}
}
2021-02-25 18:25:32 +00:00
menu_progressbar_finish();
}
else {
2021-02-09 18:31:02 +00:00
getfilename(0);
sort_entries[0] = position >> 5;
}
sort_count = fileCnt;
}
2021-02-04 09:35:15 +00:00
2017-12-11 10:30:49 +00:00
lcd_update(2);
KEEPALIVE_STATE(NOT_BUSY);
}
void CardReader::flush_presort() {
if (sort_count > 0) {
sort_count = 0;
}
}
#endif // SDCARD_SORT_ALPHA
2016-07-22 13:28:01 +00:00
void CardReader::printingHasFinished()
{
st_synchronize();
if(file_subcall_ctr>0) //heading up to a parent file that called current as a procedure.
{
file.close();
file_subcall_ctr--;
openFileReadFilteredGcode(filenames[file_subcall_ctr],true);
2016-07-22 13:28:01 +00:00
setIndex(filespos[file_subcall_ctr]);
startFileprint();
}
else
{
quickStop();
file.close();
sdprinting = false;
if(SD_FINISHED_STEPPERRELEASE)
{
finishAndDisableSteppers();
//enquecommand_P(PSTR(SD_FINISHED_RELEASECOMMAND));
2016-07-22 13:28:01 +00:00
}
autotempShutdown();
#ifdef SDCARD_SORT_ALPHA
2017-12-12 14:26:48 +00:00
//presort();
#endif
2016-07-22 13:28:01 +00:00
}
}
bool CardReader::ToshibaFlashAir_GetIP(uint8_t *ip)
{
memset(ip, 0, 4);
return card.readExtMemory(1, 1, 0x400+0x150, 4, ip);
}
#endif //SDSUPPORT