arduino-audio-tools
Loading...
Searching...
No Matches
SDIndex.h
1#pragma once
2
3#include "AudioTools/CoreAudio/AudioBasic/Str.h"
4#include "AudioTools/CoreAudio/AudioBasic/Collections/List.h"
5
6#define MAX_FILE_LEN 256
7
8// Special logic for SDTFAT
9#ifdef SDT_FAT_VERSION
10# define USE_SDFAT
11#endif
12
13namespace audio_tools {
14
19template <class SDT, class FileT>
20class SDIndex {
21 public:
22 SDIndex(SDT &sd) { p_sd = &sd; };
23 void begin(const char *startDir, const char *extension,
24 const char *file_name_pattern, bool setupIndex = true) {
25 TRACED();
26 this->start_dir = startDir;
27 this->ext = extension;
28 this->file_name_pattern = file_name_pattern;
29 idx_path = filePathString(startDir, "idx.txt");
30 idx_defpath = filePathString(startDir, "idx-def.txt");
31 int idx_file_size = indexFileTSize();
32 LOGI("Index file size: %d", idx_file_size);
33 String keyNew =
34 String(startDir) + "|" + extension + "|" + file_name_pattern;
35 String keyOld = getIndexDef();
36 if (setupIndex && (keyNew != keyOld || idx_file_size == 0)) {
37 FileT idxfile = p_sd->open(idx_path.c_str(), FILE_WRITE);
38 LOGW("Creating index file");
39 listDir(idxfile, startDir);
40 LOGI("Indexing completed");
41 idxfile.close();
42 // update index definition file
43 saveIndexDef(keyNew);
44 }
45 }
46
47 void ls(Print &p, const char *startDir, const char *extension,
48 const char *file_name_pattern = "*") {
49 TRACED();
50 this->ext = extension;
51 this->file_name_pattern = file_name_pattern;
52 listDir(p, startDir);
53 file_path_stack.clear();
54 file_path_str.clear();
55 }
56
58 const char *operator[](int idx) {
59 // return null when idx is negative
60 if (idx < 0) {
61 LOGE("idx %d is negative", idx);
62 return nullptr;
63 }
64
65 // return null when idx too big
66 if (max_idx >= 0 && idx >= max_idx) {
67 LOGE("idx %d >= size %d", idx, max_idx);
68 return nullptr;
69 }
70
71 // find record
72 FileT idxfile = p_sd->open(idx_path.c_str());
73
74 // Check if file was successfully opened
75 if (!idxfile) {
76 LOGE("Failed to open index file: %s", idx_path.c_str());
77 return nullptr;
78 }
79
80 int count = 0;
81
82 if (idxfile.available() == 0) {
83 LOGE("Index file is empty");
84 idxfile.close();
85 return nullptr;
86 }
87
88 bool found = false;
89 while (idxfile.available() > 0 && !found) {
90 result = idxfile.readStringUntil('\n');
91
92 // result c-string
93 char *c_str = (char *)result.c_str();
94 // remove potential cr character - hax to avoid any allocations
95 int lastPos = result.length() - 1;
96 if (c_str[lastPos] == 13) {
97 c_str[lastPos] = 0;
98 }
99
100 LOGD("%d -> %s", count, c_str);
101 if (count == idx) {
102 found = true;
103 }
104 count++;
105 }
106 if (!found) {
107 max_idx = count - 1; // Fix: count represents total entries, max valid index is count-1
108 }
109 idxfile.close();
110
111 return found ? result.c_str() : nullptr;
112 }
113
115 int indexOf(const char* filename) {
116 if (filename == nullptr) {
117 LOGE("filename is null");
118 return -1;
119 }
120
121 FileT idxfile = p_sd->open(idx_path.c_str());
122 if (idxfile.available() == 0) {
123 LOGE("Index file is empty");
124 idxfile.close();
125 return -1;
126 }
127
128 int count = 0;
129 String searchName(filename);
130
131 while (idxfile.available() > 0) {
132 result = idxfile.readStringUntil('\n');
133
134 // result c-string
135 char *c_str = (char *)result.c_str();
136 // remove potential cr character
137 int lastPos = result.length() - 1;
138 if (lastPos >= 0 && c_str[lastPos] == 13) {
139 c_str[lastPos] = 0;
140 }
141
142 LOGD("Comparing %d: '%s' with '%s'", count, c_str, filename);
143
144 // Check if the filename matches (with or without path)
145 String currentFile(c_str);
146 if (currentFile == searchName ||
147 currentFile.endsWith("/" + searchName) ||
148 currentFile.endsWith(searchName)) {
149 LOGD("Found '%s' at index %d", filename, count);
150 idxfile.close();
151 return count;
152 }
153
154 count++;
155 }
156
157 // Update max_idx if we've counted all entries
158 if (max_idx == -1) {
159 max_idx = count;
160 }
161
162 idxfile.close();
163 LOGD("File '%s' not found in index", filename);
164 return -1;
165 }
166
167 long size() {
168 if (max_idx == -1) {
169 FileT idxfile = p_sd->open(idx_path.c_str());
170 int count = 0;
171
172 while (idxfile.available() > 0) {
173 result = idxfile.readStringUntil('\n');
174 // result c-string
175 char *c_str = (char *)result.c_str();
176 count++;
177 }
178 idxfile.close();
179 max_idx = count;
180 }
181 return max_idx;
182 }
183
184 protected:
185 String result;
186 String idx_path;
187 String idx_defpath;
188 SDT *p_sd = nullptr;
189 List<String> file_path_stack;
190 String file_path_str;
191 const char *start_dir;
192
193 const char *ext = nullptr;
194 const char *file_name_pattern = nullptr;
195 long max_idx = -1;
196
197 String filePathString(const char *name, const char *suffix) {
198 String result = name;
199 return result.endsWith("/") ? result + suffix : result + "/" + suffix;
200 }
201
203 void listDir(Print &idxfile, const char *dirname) {
204 LOGD("listDir: %s", dirname);
205 FileT root = open(dirname);
206 if (!root) {
207 LOGE("Open failed: %s", dirname);
208 popPath();
209 return;
210 }
211 if (!isDirectory(root)) {
212 LOGD("Is not directory: %s", dirname);
213 popPath();
214 return;
215 }
216 if (StrView(dirname).startsWith(".")) {
217 LOGD("Invalid file: %s", dirname);
218 popPath();
219 return;
220 }
221
222 rewind(root);
223 FileT file = openNext(root);
224 while (file) {
225 if (isDirectory(file)) {
226 String name = String(fileNamePath(file));
227 LOGD("name: %s", name.c_str());
228 pushPath(fileName(file));
229 listDir(idxfile, name.c_str());
230 } else {
231 const char *fn = fileNamePath(file);
232 if (isValidAudioFile(file)) {
233 LOGD("Adding file to index: %s", fn);
234 idxfile.println(fn);
235 } else {
236 LOGD("Ignoring %s", fn);
237 }
238 }
239 file = openNext(root);
240 }
241 popPath();
242 }
243
244 bool isDirectory(FileT &f) {
245 bool result;
246#ifdef USE_SDFAT
247 result = f.isDir();
248#else
249 result = f.isDirectory();
250#endif
251 LOGD("isDirectory %s: %d", fileName(f), result);
252 return result;
253 }
254
255 FileT openNext(FileT &dir) {
256 TRACED();
257#ifdef USE_SDFAT
258 FileT result;
259 if (!result.openNext(&dir, O_READ)) {
260 LOGD("No next file");
261 }
262 return result;
263#else
264 return dir.openNextFile();
265#endif
266 }
267
268 void pushPath(const char *name) {
269 LOGD("pushPath: %s", name);
270 LOGD("pushPath: %s", name);
271 String nameStr(name);
272 file_path_stack.push_back(nameStr);
273 }
274
275 void popPath() {
276 TRACED();
277 String str;
278 file_path_stack.pop_back(str);
279 LOGD("popPath: %s", str.c_str());
280 }
281
283 bool isValidAudioFile(FileT &file) {
284 TRACED();
285 const char *file_name = fileName(file);
286 if (file.isDirectory()) {
287 LOGD("-> isValidAudioFile: '%s': %d", file_name, false);
288 return false;
289 }
290 StrView strFileTName(file_name);
291 bool result = strFileTName.endsWithIgnoreCase(ext) &&
292 strFileTName.matches(file_name_pattern) && !isHidden(file);
293 LOGD("-> isValidAudioFile: '%s': %d", file_name, result);
294 return result;
295 }
296
297 String getIndexDef() {
298 FileT idxdef = p_sd->open(idx_defpath.c_str());
299 String key1 = idxdef.readString();
300 idxdef.close();
301 return key1;
302 }
303 void saveIndexDef(String keyNew) {
304 FileT idxdef = p_sd->open(idx_defpath.c_str(), FILE_WRITE);
305 idxdef.write((const uint8_t *)keyNew.c_str(), keyNew.length());
306 idxdef.close();
307 }
308
309 size_t indexFileTSize() {
310 FileT idxfile = p_sd->open(idx_path.c_str());
311 size_t result = idxfile.size();
312 idxfile.close();
313 return result;
314 }
315
316 void rewind(FileT &f) {
317 TRACED();
318#ifdef USE_SDFAT
319 f.rewind();
320#else
321 f.rewindDirectory();
322#endif
323 }
324
326 const char *fileName(FileT &file) {
327#ifdef USE_SDFAT
328 // add name
329 static char name[MAX_FILE_LEN];
330 file.getName(name, MAX_FILE_LEN);
331 return name;
332#else
333 StrView tmp(file.name());
334 int pos = 0;
335 // remove directories
336 if (tmp.contains("/")) {
337 pos = tmp.lastIndexOf("/") + 1;
338 }
339 return file.name() + pos;
340#endif
341 }
342
344 const char *fileNamePath(FileT &file) {
345#if defined(USE_SDFAT) || ESP_IDF_VERSION_MAJOR >= 4
346 LOGD("-> fileNamePath: %s", fileName(file));
347 file_path_str = start_dir;
348 if (!file_path_str.endsWith("/")) {
349 file_path_str += "/";
350 }
351 for (int j = 0; j < file_path_stack.size(); j++) {
352 file_path_str += file_path_stack[j] + "/";
353 }
354 // add name
355 static char name[MAX_FILE_LEN];
356 strncpy(name, fileName(file), MAX_FILE_LEN);
357 file_path_str += name;
358 const char *result = file_path_str.c_str();
359 LOGD("<- fileNamePath: %s", result);
360 return result;
361#else
362 return file.name();
363#endif
364 }
365
366 bool isHidden(FileT &f) {
367#ifdef USE_SDFAT
368 return f.isHidden();
369#else
370 return StrView(fileNamePath(f)).contains("/.");
371#endif
372 }
373
374 FileT open(const char *name) {
375 TRACED();
376#ifdef USE_SDFAT
377 FileT result;
378 if (!result.open(name)) {
379 LOGE("FileT open error: %s", name);
380 }
381 return result;
382#else
383 return p_sd->open(name, "r");
384#endif
385 }
386};
387
388} // namespace audio_tools
Definition NoArduino.h:62
We store all the relevant file names in an sequential index file. Form there we can access them via a...
Definition SDIndex.h:20
void listDir(Print &idxfile, const char *dirname)
Writes the index file.
Definition SDIndex.h:203
const char * fileNamePath(FileT &file)
Returns the filename including the path.
Definition SDIndex.h:344
int indexOf(const char *filename)
Find the index of a filename.
Definition SDIndex.h:115
bool isValidAudioFile(FileT &file)
checks if the file is a valid audio file
Definition SDIndex.h:283
const char * fileName(FileT &file)
Returns the filename w/o the path.
Definition SDIndex.h:326
const char * operator[](int idx)
Access file name by index.
Definition SDIndex.h:58
A simple wrapper to provide string functions on existing allocated char*. If the underlying char* is ...
Definition StrView.h:28
virtual bool contains(const char *str)
checks if the string contains a substring
Definition StrView.h:280
virtual int lastIndexOf(const char *cont)
provides the position of the last occurrence of the indicated substring
Definition StrView.h:297
virtual bool matches(const char *pattern)
Definition StrView.h:193
virtual bool endsWithIgnoreCase(const char *str)
checks if the string ends with the indicated substring
Definition StrView.h:185
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10