Has anyone shared SdFat example code for sorting files on SD alphanumerically? I understand the FAT standard is based on indexes, and that the sorting would require an struct/array and a hefty amount of memory, but there are Arduino(-compatible) microcontrollers that can certainly handle it. I've looked at length for such an example and can't find any. I'm new enough to C that, while I might eventually figure it out, I would love some example code to get me started.
For my purposes, I need something like file.openNextAlphaNum() to open the next file in the sorted array, and loop back to the start at the end of the alphabet. Specifically, I actually need to collect the indexes of every folder in the root, and move into them sequentially in alphabetical order. The number of folders is unknown, but is likely to be around 100. I'm running on Teensy LC.
I understand there are tools that sort SD indexes, but I need a real-time solution so my customers can drop folders on an SD card without worrying about the index order.
Browse to my Arduino web server home automation application at http://www.2wg.co.nz/ and select the SD Card web page option on the left hand side menu.
What you immediately see is an alphabetically sorted list of the folders and files in the root directory of the application's SD card. (Directories/folders first, then files.)
Click on the PUBLIC folder name and the system will open up an alphabetical sorted list of files in the PUBLIC folder.
From the root directory/folder select the ACTIVITY folder name and the system will automatically drill down through several folder levels to the current month's daily activity log files. Also in alphabetical (numerical) order.
The full source code for my application (not including the last month of development that has not been released) is available in the SD Card \PUBLIC\ folder.
Those two procedures are the implementation of the alphabetical SD Card directory/folder browsing web page sub-system.
In the SDCardDirectoryBrowse() procedure this code defines two small arrays used to sort folders and files for alphabetical listing on web pages.
const int C_SortMax = 5;
String l_SortList[C_SortMax]; //The sort array can be as small as one array element
unsigned long l_FileSize[C_SortMax];
This implementation finds the first five folders/files alphabetically, then the next five, etc., etc. For every five folders/files the procedure is parsing the SD card directory/folder - so for example it does four parses of an SD card directory/folder if there are between 16 and 20 folders/files in the directory/folder.
C_SortMax can be as low as 1 in you have limited memory - the sorted listing would just take longer because more parses of the current directory/folder would be required.
My code is available for you to re-use as you wish. Note - I am using the <SD.h> library.
//----------------------------------------------------------------------------------------------
void SDCardListWebPage(const String &p_start_folder) { //Minimise Stack size
const byte c_proc_num = 30;
Push(c_proc_num);
CheckRAM();
ProcessCache(false); //Check cache size before SD file operations
//See the SD card files /PUBLIC/INITWBPG.TXT and /PUBLIC/ACTNMENU.TXT
//for the program code for these two common procedures.
InitialiseWebPage(EPSR(E_SD_CARD_78)); //will install some javascript
if (HTMLParmRead(E_HEAD_315) == "T") {
CheckRAM();
Pop(c_proc_num);
return;
}
InsertActionMenu(600, EPSR(E_SD_CARD_78), St_SD_Card_List_WPE);
G_EthernetClient.println(F("<tr style=\"background-color:Khaki;\">"));
G_EthernetClient.println(F("<td><b>PAGE ACTIONS</b></td>"));
G_EthernetClient.println(F("</tr>"));
G_EthernetClient.print(F("<tr>"));
G_EthernetClient.print(F("<td>"));
InsertLogFileLinks(0); //Hack (public) or all five links
if ((G_SDCardOK) &&
(UserLoggedIn())) {
G_EthernetClient.print(F("
<input type='checkbox' name='FileDelete' value='Delete'>File Delete
"));
G_EthernetClient.print(F("<input type='submit' formaction='/"));
G_EthernetClient.print(G_PageNames[43]);
G_EthernetClient.println(F("/' style='width:150px;height:30px;font-size:20px' value='File Upload'>"));
G_EthernetClient.print(F("
"));
}
G_EthernetClient.print(F("</td>"));
G_EthernetClient.print(F("</tr>"));
CheckRAM();
//END OF MASTER TABLE, ROW 1, CELL 1 on LHS
//Time, Page Hits, Page Menu, Action Menu all on LHS
G_EthernetClient.print(F("</table>")); //LHS
G_EthernetClient.print(F("</td>"));
//START OF RHS PAGE DATA
//A NEW CELL ON ROW 1 OF THE MASTER TABLE
G_EthernetClient.println(F("<td style=\"vertical-align:top;\">"));
G_EthernetClient.println(F("<table id='SDCardListTable' border=1>"));
G_EthernetClient.print(F("<tr style=\"font-family:Courier;background-color:White;\">"));
String l_parent_dir = p_start_folder;
if (!G_SDCardOK) {
ActivityWrite(EPSR(E_SD_Card_Failure_16));
G_EthernetClient.print(F("<td>SD Card Failure</td>"));
}
else {
if (l_parent_dir == "")
l_parent_dir = "/";
//
//display the file if a local LAN user or if user logged in or is one of the public directories
boolean l_SD_file_links = CheckFileAccessSecurity(l_parent_dir);
String l_last_directory = "";
int l_dir_count; //reset to zero within the sub procedure
int l_file_count; //reset to zero within the sub procedure
int l_panel_count = 0;
while (true) {
G_EthernetClient.println(F("<td style=\"vertical-align:top;\" align=\"left\">"));
SDCardDirectoryBrowse(&l_parent_dir[0], l_SD_file_links, l_last_directory, l_dir_count, l_file_count);
G_EthernetClient.println(F("</td>"));
l_panel_count++;
//Exit now if this is the root folder or there are no sub directories or files or if we already have four panels
if ((p_start_folder == "/") || (l_dir_count == 0) || (l_file_count != 0) || (l_panel_count == 4)) {
break;
}
//Otherwise we iterate to the last sub ddirectory/folder within this sub directory or folder.
l_parent_dir = l_parent_dir + l_last_directory + '/';
}
} //SD Card OK - extract the file listing
G_EthernetClient.println(F("</tr>"));
G_EthernetClient.println(F("<tr><td style='background-color:sandybrown;'><b>CURRENT FOLDER:</b></td></tr>"));
G_EthernetClient.print(F("<tr><td colspan=\"0\"><input type='text' name='FOLDER' style='font-family:Courier;font-size:20px;background-color:White;text-align:left;width:100%' "));
G_EthernetClient.print(F("readonly value='"));
G_EthernetClient.print(l_parent_dir);
G_EthernetClient.print(F("'></td></tr>"));
G_EthernetClient.println(F("<tr><td style='background-color:sandybrown;'><b>FILENAME:</b></td></tr>"));
G_EthernetClient.print(F("<tr><td colspan=\"0\"><input type='text' id='FILENAME' name='FNAME' style='font-family:Courier;font-size:20px;background-color:White;text-align:left;width:100%' "));
G_EthernetClient.print(F("value=''"));
//G_EthernetClient.print(F("NULL"));
G_EthernetClient.println(F("></td></tr>"));
G_EthernetClient.println(F("</table>")); //RHS SDCardListTable
G_EthernetClient.println(F("</td>"));
G_EthernetClient.println(F("</tr>"));
G_EthernetClient.print(F("</table>")); //form
G_EthernetClient.println(F("</form>"));
G_EthernetClient.println(F("</body></html>"));
CheckRAM();
Pop(c_proc_num);
} //SDCardListWebPage
void SDCardDirectoryBrowse(const String &p_parent_dir, boolean p_file_links, String &p_last_directory, int &p_dir_count, int &p_file_count) {
//This procedure is called iteratively to auto traverse to the latest log files.
const byte c_proc_num = 31;
Push(c_proc_num);
CheckRAM();
SPIDeviceSelect(DC_SDCardSSPin);
const char C_FileType_Dir = '1';
const char C_FileType_File = '2';
File l_SD_root = SD.open(p_parent_dir.c_str(), FILE_READ);
if (!l_SD_root) {
ActivityWrite(Message(M_Unable_to_Open_SD_Card_Root_19));
SPIDeviceSelect(DC_EthernetSSPin);
Pop(c_proc_num);
return;
}
//Using arrays we output a sorted directory listing
//We only use 13 char strings so hopefully we get no String memory fragmentation
const String C_StringMax = EPSR(E_13_tildes_109);
const int C_SortMax = 5;
String l_SortList[C_SortMax]; //The sort array can be as small as one array element
unsigned long l_FileSize[C_SortMax];
String l_PrevMax = "";
int l_entry_count = 2; //Add one for the heading
//Initialise the sort arrays
for (byte l_idx = 0; l_idx < C_SortMax; l_idx++) {
l_SortList[l_idx] = C_StringMax; //Reserves the filename space
l_FileSize[l_idx] = 0;
}
CheckRAM();
SPIDeviceSelect(DC_EthernetSSPin);
G_EthernetClient.print(F("<table id='FileListTable' style=\"font-family:Courier;background-color:White;text-align:left;\">"));
G_EthernetClient.println(F("<tr><td colspan=\"2\">"));
G_EthernetClient.println(F("<b>DIR: "));
G_EthernetClient.print(p_parent_dir);
G_EthernetClient.print(F("</b></td>"));
G_EthernetClient.print(F("</tr>"));
G_EthernetClient.println(F("<tr><td><b>ENTRY NAME</b></td><td style=\"text-align:right;\"><b>SIZE</b></td></tr>"));
SPIDeviceSelect(DC_SDCardSSPin);
//We extract the SD card file list multiple times outputting a small sorted subset each time
//SPIDeviceSelect(DC_SDCardSSPin);
String l_SD_filename = "";
boolean l_finished = false;
boolean l_first = true;
File l_SD_file;
p_last_directory = "";
p_dir_count = 0;
p_file_count = 0;
CheckRAM();
while (true) { //iterate until complete
//In this first section we parse all the files in the directory and extract
//the next C_SortMax set that is immediately greater that l_PrevMax.
l_SD_root.rewindDirectory();
while (true) {
l_SD_file = l_SD_root.openNextFile();
if (!l_SD_file) {
break;
}
if (l_SD_file.isDirectory())
l_SD_filename = String(C_FileType_Dir);
else
l_SD_filename = String(C_FileType_File);
//
l_SD_filename += l_SD_file.name();
//Serial.println(l_SD_filename);
if ((l_SD_filename > l_PrevMax) && (l_SD_filename < l_SortList[C_SortMax - 1])) {
boolean l_inserted = false;
//We are in range so we have to insert
for (byte l_idx = C_SortMax - 1; l_idx > 0; l_idx--) {
//discard the final record (we can get it again on the next parse)
l_SortList[l_idx] = l_SortList[l_idx - 1];
l_FileSize[l_idx] = l_FileSize[l_idx - 1];
//Now see if down one record is where to insert
if (l_SD_filename > l_SortList[l_idx - 1]) {
l_SortList[l_idx] = l_SD_filename;
if (l_SD_file.isDirectory())
l_FileSize[l_idx] = 0;
else
l_FileSize[l_idx] = l_SD_file.size();
//
l_inserted = true;
break;
} //if
} //for
if (!l_inserted) {
//insert as first item - prev 1st iten was copied to be 2nd item
l_SortList[0] = l_SD_filename;
if (l_SD_file.isDirectory())
l_FileSize[0] = 0;
else
l_FileSize[0] = l_SD_file.size();
//
} //if l_inserted
} //if skip files already processed or beyond range
l_SD_file.close();
} //for each SD card file
//We now have our sorted subset of SD card files
CheckRAM();
//Now print the array and reset its value
//If the last cell of the array is C_SortMax we have finished
SPIDeviceSelect(DC_EthernetSSPin);
for (byte l_idx = 0; l_idx < C_SortMax; l_idx++) {
if (l_SortList[l_idx] == C_StringMax) {
l_finished = true;
break; //we are done
}
if ((l_entry_count % 20) == 0) { //rollover to new column every 20 entries
G_EthernetClient.print(F("</table>"));
G_EthernetClient.print(F("</td>"));
G_EthernetClient.println(F("<td style=\"vertical-align:top;\" align=\"left\">"));
G_EthernetClient.print(F("<table style=\"font-family:Courier;background-color:White;text-align:left;\">"));
G_EthernetClient.println(F("<tr><td><b>ENTRY NAME</b></td><td><b>SIZE</b></td>"));
l_entry_count++; //for this heading
}
l_entry_count++; //for the file entry below
G_EthernetClient.print(F("<tr><td>"));
if ((p_file_links) || (l_SortList[l_idx][0] == C_FileType_Dir)) { //always show links for sub directories
//Part I - Set up the html link
G_EthernetClient.print(F("<a href=\""));
G_EthernetClient.print(p_parent_dir);
G_EthernetClient.print(l_SortList[l_idx].substring(1)); //file link pagename
if (l_SortList[l_idx][0] == C_FileType_Dir) { //Directory
//G_EthernetClient.print(F(".DIR/\">"));
G_EthernetClient.print(F("/\">"));
} //submit() never applies to directories
else { //File
//G_EthernetClient.print(F("/\" onclick='fnamesubmit(\""));
G_EthernetClient.print(F("\" onclick='fnamesubmit(\""));
G_EthernetClient.print(l_SortList[l_idx].substring(1));
G_EthernetClient.print(F("\"); return false;'>"));
}
}
//Part II - output the dir/file name
G_EthernetClient.print(l_SortList[l_idx].substring(1)); //displayed file/dir name
if ((p_file_links) || (l_SortList[l_idx][0] == C_FileType_Dir)) {//always show links for sub directories
//Part III - terminate the html link
G_EthernetClient.print(F("</a>"));
}
G_EthernetClient.print(F("</td>"));
// files have sizes, directories do not
G_EthernetClient.print(F("<td style=\"text-align:right;\">"));
if (l_SortList[l_idx][0] == C_FileType_Dir) { //DIR
p_dir_count++;
p_last_directory = l_SortList[l_idx].substring(1);
G_EthernetClient.print(F("[DIR]"));
}
else {
p_file_count++;
G_EthernetClient.print(l_FileSize[l_idx], DEC);
}
//
G_EthernetClient.println(F("</td></tr>"));
l_PrevMax = l_SortList[l_idx]; //record the updated prevMax
l_SortList[l_idx] = C_StringMax; //Reset the array
l_FileSize[l_idx] = 0; //Reset the array
} //Print and reset each sort array record
l_first = false;
//We are finished if we encountered a C_StringMax record anywhere
if (l_finished)
break; //DC_EthernetSSPin already selected
//
SPIDeviceSelect(DC_SDCardSSPin);
} //iterate until complete
SPIDeviceSelect(DC_SDCardSSPin); //Just in case
l_SD_root.close();
SPIDeviceSelect(DC_EthernetSSPin);
G_EthernetClient.println(F("<tr><td>"));
G_EthernetClient.println(F("Subdirectories"));
G_EthernetClient.println(F("</td><td style=\"text-align:right;\">"));
G_EthernetClient.println(p_dir_count);
G_EthernetClient.println(F("</td></tr>"));
G_EthernetClient.println(F("<tr><td>"));
G_EthernetClient.println(F("Files"));
G_EthernetClient.println(F("</td><td style=\"text-align:right;\">"));
G_EthernetClient.println(p_file_count);
G_EthernetClient.println(F("</td></tr>"));
G_EthernetClient.print(F("</table>")); //SDFileListTable
CheckRAM();
Pop(c_proc_num);
} //SDCardDirectoryBrowse