[SOLVED] List all directory and file with levels using sdfat.h

I want to list all filles and directorys an subdirectorys on the sd card with an mcu.

The problem I have is that it forget what he was previous doing when I call the same function in that function. The SdFat library doesn't have a namespace so I can not use that like the example I use with the SD library.

The library I want to use

listDir(audio_SD, file.name(), levels - 1); //"can not be referenced -- it is a deleting function"

The complete void I try modify ...

//void listDir(fs::FS& fs, const char* dirname, uint8_t levels) {
void listDir(SdFat audio_SD, const char* dirname, uint8_t levels) {
	Serial.printf("Listing directory: %s\n", dirname);

	File root = audio_SD.open(dirname);
	if (!root) {
		Serial.println("Failed to open directory");
		return;
	}
	if (!root.isDirectory()) {
		Serial.println("Not a directory");
		return;
	}

	File file = root.openNextFile();

	while (file) {
		if (file.isDirectory()) {
			Serial.print("  DIR : ");
			Serial.println(file.name());
			if (levels) {
				listDir(audio_SD, file.name(), levels - 1);
			}
		}
		else {
			Serial.print("  FILE: ");
			Serial.print(file.name());
			Serial.print("  SIZE: ");
			Serial.print(file.size());
		}
		file = root.openNextFile();
	}
}

Note: I know that the memory will be an issue with larger amount of data on the SD card. But the data I have is not that big soo I will suppress that.

not sure what this means.

I have this old code that might be of interest - totally unsure if it still works.

#include <SdFat.h>
SdFat sd;

void displayDirectoryContent(File& aDirectory, byte tabulation) {
  File file;
  char fileName[20];

  if (!aDirectory.isDir()) return;
  aDirectory.rewind();

  while (file.openNext(&aDirectory, O_READ)) {
    if (!file.isHidden()) {
      file.getName(fileName, sizeof(fileName));
      for (uint8_t i = 0; i < tabulation; i++) Serial.write('\t');
      Serial.print(fileName);

      if (file.isDir()) {
        Serial.println(F("/"));
        displayDirectoryContent(file, tabulation + 1);
      } else {
        Serial.write('\t'); Serial.print(file.fileSize()); Serial.println(F(" bytes"));
      }
    }
    file.close();
  }
}

void setup() {
  File dir;

  Serial.begin(115200);
  if (!sd.begin(SdSpiConfig(SS, DEDICATED_SPI))) {
    sd.initErrorHalt(&Serial);
  }

  // Open root directory
  if (!dir.open("/")) {
    sd.errorHalt(&Serial, F("dir.open failed"));
  }
  displayDirectoryContent(dir, 0);
}

void loop() {}

Old but not forgotten. Just had to added

const int SD_CS_PIN = SS;
#define SPI_SPEED SD_SCK_MHZ(4)

	if (!sd.begin(SD_CS_PIN, SPI_SPEED)) {
		Serial.println("SD card error!");
		sd.initErrorHalt(&Serial);
		while (true);
	}

for my board to get it work.

I diden't know at the moment I made this topic, but there is a methode that could called within the library.

 #include <SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
// **************** SD CARD
const int SD_CS_PIN = SS;
#define SPI_SPEED SD_SCK_MHZ(4)
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 0

#if SD_FAT_TYPE == 0
SdFat sd;
File dir;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 dir;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile dir;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile dir;
FsFile file;
#else  // SD_FAT_TYPE
#error invalid SD_FAT_TYPE
#endif  // SD_FAT_TYPE
void setup() {
	Serial.begin(9600);
	while (!Serial);
	Serial.println("SD Card directory sorting...");
	if (!sd.begin(SD_CS_PIN, SPI_SPEED)) {
		Serial.println("SD card error!");
		while (true);
	}
	sd.ls("/", LS_R);
}
void loop() {
  
}

The formating is and that beautifull as yours, but I does also longer file names. Thanks for helping me out in such a short time.

just change the buffer size here

  char fileName[20];

but indeed, the ls function of the library is appropriate if you don't need to perform some sort of process on each file

You got me there, I do want do some sorting!

This is how I do it for now. It can be done better, but It will do the job for files that only contain numbers. Even 01 files when the are mixed together with 002's... what I have added to the function. It's not complete debugged but yeah, that's what I got for now.

String* sortArrayNumber(String* a, int n)
{
	int i, j;
	for (i = 0; i < n - 1; i++) {
		// Last i elements are already in place
		for (j = 0; j < n - i - 1; j++) {
			if (RemoveExtension(GetFileName(a[j])).toInt() > RemoveExtension(GetFileName(a[j+1])).toInt()) {
				String temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}	
		}
	}
	return a;
}
/*FOLDER AND FILES*/
String GetFileName(String path) {
	path = path.substring(path.lastIndexOf("/") + 1, path.length());
	return path;
}
String RemoveExtension(String path) {
	//String Filename = path;
	if (path.lastIndexOf(".") > 0 && path.lastIndexOf(".") < 5) {
		path = path.substring(0, path.lastIndexOf("."));
		return path;
	}
	return "";
}

if that works :slight_smile:
(I would not use the String class unless you are sure about the memory available)

So that's why evrybody use const char*. The can be set equal to each other but the memorie is the reason.

1 to 2 bytes up to 4 bytes for special characters depending to the amount of characters I have.

Soo I converted the strings to characters, but now I am outputting two pointers. Can I get rid of it?

char** sortArrayNumber(char* a[40], int n)
{
        char** char2dArray = NULL;
	char2dArray = new char* [40];

	int i, j, k;
	for (i = 0; i < n - 1; i++) {
		// Last i elements are already in place
		for (j = 0; j < n - i - 1; j++) {
			int size = sizeof(a) / sizeof(a[0]);
			char* charArray = NULL;
			charArray = new char[size];
			charArray = a[j];
			GetFileName(charArray);
			if (atoi(RemoveExtension(a[j])) > atoi(RemoveExtension(a[j + 1]))) {//string to number
				char* temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
				Serial.println("array");
				Serial.println(a[j + 1]);
			}
		}
	}
	return a;
}

Ok. I think I know now. I just to remove the procedure and place it where I am calling it. If I would use 2D fix arrays it would be even beter for memorie perpeces.

when you do this

you create space for 40 pointers, but no space for the content

the content space is indeed created with the

charArray = new char[size];

but the size is not calculated right. It should be the size of the filename you want to store.

going fixed size will (at the cost of wasted memory possibly) solve the issue. if memory becomes an issue later on, you could then revert to some dynamic allocation.

I just finished it. This is how it looks

void SortPlaylist(char* dirPath, char* Dbfilename) {
	if (!openDbFile(dirPath, Dbfilename)) {
		error("opening file in folder failed");
	}
	else
	{
		Serial.println("read db lines for sorting csv file");

		DbFile.rewind();
		int cnt = 0;
		//char line[MaxTitleLength];
		//size_t len = 0;
		//char* str;
		//char* StrTitleArray[MaxTitleLength];
		char chr;
		byte byt;
		char Title[TitleIndex][MaxTitleLength];//TitleIndex//MaxTitleLength
		uint16_t intCelIndex = 0;
		uint16_t intEndIndex = MaxTitleLength;
		uint16_t cntLetter = 0;
		uint16_t cntTitle = 0;
		while (DbFile.available()) {
			byt = DbFile.read();
			Serial.println(byt);
			//byte = 59 = ;
			//Serial.println(cnt);
			if (byt == 59) intCelIndex = cnt;//Serial.println("test");//
			if (byt == 10) intEndIndex = cnt;
			if ((cnt > intCelIndex) && (cnt < intEndIndex-2)) {
				chr = byt;
				Title[cntTitle][cntLetter] = chr;
				Serial.println(chr);
				cntLetter++;
			}
			if (byt == 10) {
				cnt = 0;
				Title[cntTitle][cntLetter] = '\0';
				cntTitle++;
				cntLetter = 0;
				intCelIndex = 100;
				intEndIndex = MaxTitleLength;
			}
			else{
				cnt++;
			}
		}
		//print values
		//for (int i = 0; i < cntTitle;i ++) {
		//	Serial.println(Title[i]);
		//}
		Serial.println("sort lines csv file");
		int i, j;
		for (i = 0; i < cntTitle- 1; i++) {
			for (j = 0; j < cntTitle- i - 1; j++) {
				if (atoi(RemoveExtension(Title[j])) > atoi(RemoveExtension(Title[j + 1]))) {//string to number
					char temp[MaxTitleLength];
					for (int x = 0; x < MaxTitleLength; x++) {
						temp[x] = Title[j][x];
					}
					for (int x = 0; x < MaxTitleLength; x++) {
						//Title[j] = Title[j + 1];
						Title[j][x] = Title[j + 1][x];
					}
					for (int x = 0; x < MaxTitleLength; x++) {
						//Title[j + 1] = temp;
						Title[j + 1][x] = temp[x];
					}
				}
			}
		}
		//for (int i = 0; i < cntTitle; i++) {
		//	Serial.println(Title[i]);
		//}
		Serial.println("write sorted lines to csv file");
		DbFile.rewind();
		for (int i = 0; i < cntTitle;i ++) {
			//Serial.println(StrTitleArray[i]);
			WriteSongToPlaylist(i, dirPath, Title[i], Dbfilename);
		}
		DbFile.close();
	}
}

It will save you a day. It will cost you a minut to read this topic complety to understand how come to this. I have made also an other topic to understand 2D array's and how to use them in a small ram environment.

Char* to Char**

And your csv has too look like this without modify

1;001.mp3\n

1 is the first field (id number)
001.mp3 is field 2 (filename)

\n is a new line

You can code , as well as ; to delimit fields. Then you have a Comma Separated Fields format.

Since before I've been here the big lesson is Do Many Things At Once that starts with leds and buttons and using millis() or micros() to avoid blocking code like delay.
This allows writing cooperative tasks and IPO, Input, Process, Output modular tasks.
Nick Gammon's clear tutorial with breakfast-making explanation.

This tutorial has a simple State Machine example.

State Machines ROCK! They allow the state of a process to select which code runs, and that code may change the state. I have put state machines inside of state machines to do text hacking in game files before, to find certain lines of original file and change them in a mod file else copy input.

Thanks.

Can be different.. I thinking of defining it.

When I set const int TitleIndex = 200; I get an overflow. Not sure why, because I have 160kb ram and the title array is only 40 characters long.

If I do...

if (Title[cntTitle] != "") or if (Title[cntTitle] != nullptr) or Serial.println(Title[i]) when it is set to 200 it get stuck. I am loosing my mind over this. When I print evry byt I store nothing seems wrong.

I moved all varables on top still having that issue.

Don't post snippets (Snippets R Us!)

could you write a small piece of code to test what you want to achieve and we could work on that?

1 Like

On what Arduino, compatible, or add-on device?

Why use an int to count to less than 200? Oh right, 160kb RAM.
On memory devices a 32kb device is 32768 bits, 8192 bytes, 8KB, cheap SPI RAM.

File on SD, I would make a file with title0title0title0.... and another of offsets to each title that ends with 0.
And then I can make a file of sorted offsets into the data. Keep a bit array table to mark already selected titles (8 bits per byte) and through successive reads of unselected titles the sorted index order can be written directly to file. The index tells which offset to use, index 1 may be anywhere but the end of file.

Sorting indexes is faster than sorting text. One data file can be index-sorted 12 ways and still may be no bigger than twice the data. Take this to heart with SD, never sort -on- SD!

#include <SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
#include <stdio.h>
#include <string.h>
//#include <CSVFile.h>
// **************** SD CARD
const int SD_CS_PIN = SS;
#define SPI_SPEED SD_SCK_MHZ(4)
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 1

#if SD_FAT_TYPE == 0
SdFat sd;
File dir;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 dir;
File32 file;
File32 DbFile;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile dir;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile dir;
FsFile file;
#else  // SD_FAT_TYPE
#error invalid SD_FAT_TYPE
#endif  // SD_FAT_TYPE
// Store error strings in flash to save RAM.
#define error(s) sd.errorHalt(&Serial, F(s))
const uint32_t MAX_FILE_SIZE_MiB = 100;  // 100 MiB file.
const uint32_t MAX_FILE_SIZE = MAX_FILE_SIZE_MiB << 20;
char seperator[2] = ";";
const int MaxTitleLength = 40;
const int TitleIndex = 200;
static char Title[TitleIndex][MaxTitleLength];
static char temp[MaxTitleLength];
void setup() {
	Serial.begin(115200);
	if (!sd.begin(SD_CS_PIN, SPI_SPEED)) {
		Serial.println("SD card error!");
		sd.initErrorHalt(&Serial);
		while (true);
	}
	test();
}
void test() {
	if (!dir.open("/")) {
		//sd.errorHalt(&Serial, F("dir.open failed"));
	}
	else
	{
		ReadAndSortFolderPlaylist("/", "test.csv");
	}
}

char* RemoveExtension(char* path) {
	if (lastIndex(path, '.'), sizeof(path) / sizeof(path[0]) > 0 && lastIndex(path, '.'), sizeof(path) / sizeof(path[0]) < 5) {
		path = substr(path,0 , lastIndex(path, '.'));
		return path;
	}
	return "";
}
char* subString(char* s, int index, int n) {
	char* res = new char[n + 1];
	sprintf(res, "%.*s", n, s + index);
	//Serial.println(res);
	return res;
}
int lastIndex(char* arr, char chFind) {
	if (arr == nullptr) return -1;
	size_t length = strlen(arr); //length
	int pos = 0;
	for (size_t i = length - 1; i > 0; i--) {
		if (arr[i] == chFind) {
			return i;
		}
	}
	return -1;
}
bool openDbFile(char* dirPath, char* Dbfilename) {
	if (!sd.exists(CombineStrings(dirPath, Dbfilename))) {
		//Serial.println(name);
		//DbFile.close();
		Serial.println(F("File does not exist"));
		//return false;
		if (!DbFile.open(CombineStrings(dirPath, Dbfilename),  O_CREAT | O_RDWR)) {
			//Serial.println(name);
			Serial.println(F("Db failed to create"));
			return false;
		}
		else
		{
			Serial.println(F("Created Db File opened"));
			return true;
		}
	}
	else
	{
		if (!DbFile.open(CombineStrings(dirPath, Dbfilename), O_RDWR)) {
			//Serial.println(name);
			Serial.println(F("Db failed to open"));
			return false;
		}
		else
		{
			Serial.println(F("Db File opened"));
			return true;
		}
	}
}
void ReadAndSortFolderPlaylist(char* dirPath, char* Dbfilename) {
	if (!openDbFile(dirPath, Dbfilename)) {
		error("opening file in folder failed");
	}
	else
	{
		Serial.println("read db lines for sorting csv file");

		DbFile.rewind();
		int cnt = 0;
		char chr;
		byte byt;

		uint16_t intCelIndex = 0;
		uint16_t intEndIndex = MaxTitleLength;
		uint16_t cntLetter = 0;
		uint16_t cntTitle = 0;
		while (DbFile.available()) {
			byt = DbFile.read();
			Serial.println(byt);
			if (byt == 13)break;//empty lines
			//byte = 59 = ;
			//Serial.println(cnt);
			if (byt == 59) intCelIndex = cnt;//Serial.println("test");//
			if (byt == 10) intEndIndex = cnt;
			if ((cnt > intCelIndex) && (cnt < intEndIndex - 2)) {
				if (byt != 10) {
					chr = byt;
					Title[cntTitle][cntLetter] = chr;
					Serial.println(chr);
					cntLetter++;
				}
			}
			if (byt == 10) {
				cnt = 0;
				//if (Title[cntTitle] != "") {
					Title[cntTitle][cntLetter] = '\0';
					//Serial.println(Title[cntTitle]);
				//}
				
				cntTitle++;
				cntLetter = 0;
				intCelIndex = 100;
				intEndIndex = MaxTitleLength;
			}
			else {
				cnt++;
			}
		}
		//////print values
		//for (int i = 0; i < cntTitle;i ++) {
		//	if (Title[i] != "") {
		//		Serial.println(Title[i]);
		//	}
		//}
		//Serial.println("sort lines csv file");
		//int i, j,x;

		//for (i = 0; i < cntTitle - 1; i++) {
		//	for (j = 0; j < cntTitle - i - 1; j++) {
		//		//Serial.println(RemoveExtension(Title[j]));
		//		if (atoi(RemoveExtension(Title[j])) > atoi(RemoveExtension(Title[j + 1]))) {//string to number

		//			for (x = 0; x < MaxTitleLength; x++) {
		//				temp[x] = Title[j][x];

		//			}
		//			for (x = 0; x < MaxTitleLength; x++) {
		//				//Title[j] = Title[j + 1];
		//				Title[j][x] = Title[j + 1][x];
		//			}
		//			for (x = 0; x < MaxTitleLength; x++) {
		//				//Title[j + 1] = temp;
		//				Title[j + 1][x] = temp[x];
		//			}
		//		}
		//	}
		//}
		//for (int i = 0; i < cntTitle; i++) {
		//	Serial.println(Title[i]);
		//}
		//Serial.println("write sorted lines to csv file");
		//DbFile.rewind();
		//for (int i = 0; i < cntTitle; i++) {
		//	//Serial.println(StrTitleArray[i]);
		//	WriteSongToPlaylist(i, dirPath, Title[i], Dbfilename);
		//}
		DbFile.close();
	}
}

test.zip (161 Bytes)

You will see when you try to print the values it get stuck.