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 inx too big
60 if (max_idx >= 0 && idx > max_idx) {
61 LOGE("idx %d > size %d", idx, max_idx);
62 return nullptr;
63 }
64 // find record
65 FileT idxfile = p_sd->open(idx_path.c_str());
66 int count = 0;
67
68 if (idxfile.available() == 0) {
69 LOGE("Index file is empty");
70 }
71
72 bool found = false;
73 while (idxfile.available() > 0 && !found) {
74 result = idxfile.readStringUntil('\n');
75
76 // result c-string
77 char *c_str = (char *)result.c_str();
78 // remove potential cr character - hax to avoid any allocations
79 int lastPos = result.length() - 1;
80 if (c_str[lastPos] == 13) {
81 c_str[lastPos] = 0;
82 }
83
84 LOGD("%d -> %s", count, c_str);
85 if (count == idx) {
86 found = true;
87 }
88 count++;
89 }
90 if (!found) {
91 max_idx = count;
92 }
93 idxfile.close();
94
95 return found ? result.c_str() : nullptr;
96 }
97
99 int indexOf(const char* filename) {
100 if (filename == nullptr) {
101 LOGE("filename is null");
102 return -1;
103 }
104
105 FileT idxfile = p_sd->open(idx_path.c_str());
106 if (idxfile.available() == 0) {
107 LOGE("Index file is empty");
108 idxfile.close();
109 return -1;
110 }
111
112 int count = 0;
113 String searchName(filename);
114
115 while (idxfile.available() > 0) {
116 result = idxfile.readStringUntil('\n');
117
118 // result c-string
119 char *c_str = (char *)result.c_str();
120 // remove potential cr character
121 int lastPos = result.length() - 1;
122 if (lastPos >= 0 && c_str[lastPos] == 13) {
123 c_str[lastPos] = 0;
124 }
125
126 LOGD("Comparing %d: '%s' with '%s'", count, c_str, filename);
127
128 // Check if the filename matches (with or without path)
129 String currentFile(c_str);
130 if (currentFile == searchName ||
131 currentFile.endsWith("/" + searchName) ||
132 currentFile.endsWith(searchName)) {
133 LOGD("Found '%s' at index %d", filename, count);
134 idxfile.close();
135 return count;
136 }
137
138 count++;
139 }
140
141 // Update max_idx if we've counted all entries
142 if (max_idx == -1) {
143 max_idx = count;
144 }
145
146 idxfile.close();
147 LOGD("File '%s' not found in index", filename);
148 return -1;
149 }
150
151 long size() {
152 if (max_idx == -1) {
153 FileT idxfile = p_sd->open(idx_path.c_str());
154 int count = 0;
155
156 while (idxfile.available() > 0) {
157 result = idxfile.readStringUntil('\n');
158 // result c-string
159 char *c_str = (char *)result.c_str();
160 count++;
161 }
162 idxfile.close();
163 max_idx = count;
164 }
165 return max_idx;
166 }
167
168 protected:
169 String result;
170 String idx_path;
171 String idx_defpath;
172 SDT *p_sd = nullptr;
173 List<String> file_path_stack;
174 String file_path_str;
175 const char *start_dir;
176
177 const char *ext = nullptr;
178 const char *file_name_pattern = nullptr;
179 long max_idx = -1;
180
181 String filePathString(const char *name, const char *suffix) {
182 String result = name;
183 return result.endsWith("/") ? result + suffix : result + "/" + suffix;
184 }
185
187 void listDir(Print &idxfile, const char *dirname) {
188 LOGD("listDir: %s", dirname);
189 FileT root = open(dirname);
190 if (!root) {
191 LOGE("Open failed: %s", dirname);
192 popPath();
193 return;
194 }
195 if (!isDirectory(root)) {
196 LOGD("Is not directory: %s", dirname);
197 popPath();
198 return;
199 }
200 if (StrView(dirname).startsWith(".")) {
201 LOGD("Invalid file: %s", dirname);
202 popPath();
203 return;
204 }
205
206 rewind(root);
207 FileT file = openNext(root);
208 while (file) {
209 if (isDirectory(file)) {
210 String name = String(fileNamePath(file));
211 LOGD("name: %s", name.c_str());
212 pushPath(fileName(file));
213 listDir(idxfile, name.c_str());
214 } else {
215 const char *fn = fileNamePath(file);
216 if (isValidAudioFile(file)) {
217 LOGD("Adding file to index: %s", fn);
218 idxfile.println(fn);
219 } else {
220 LOGD("Ignoring %s", fn);
221 }
222 }
223 file = openNext(root);
224 }
225 popPath();
226 }
227
228 bool isDirectory(FileT &f) {
229 bool result;
230#ifdef USE_SDFAT
231 result = f.isDir();
232#else
233 result = f.isDirectory();
234#endif
235 LOGD("isDirectory %s: %d", fileName(f), result);
236 return result;
237 }
238
239 FileT openNext(FileT &dir) {
240 TRACED();
241#ifdef USE_SDFAT
242 FileT result;
243 if (!result.openNext(&dir, O_READ)) {
244 LOGD("No next file");
245 }
246 return result;
247#else
248 return dir.openNextFile();
249#endif
250 }
251
252 void pushPath(const char *name) {
253 LOGD("pushPath: %s", name);
254 LOGD("pushPath: %s", name);
255 String nameStr(name);
256 file_path_stack.push_back(nameStr);
257 }
258
259 void popPath() {
260 TRACED();
261 String str;
262 file_path_stack.pop_back(str);
263 LOGD("popPath: %s", str.c_str());
264 }
265
267 bool isValidAudioFile(FileT &file) {
268 TRACED();
269 const char *file_name = fileName(file);
270 if (file.isDirectory()) {
271 LOGD("-> isValidAudioFile: '%s': %d", file_name, false);
272 return false;
273 }
274 StrView strFileTName(file_name);
275 bool result = strFileTName.endsWithIgnoreCase(ext) &&
276 strFileTName.matches(file_name_pattern) && !isHidden(file);
277 LOGD("-> isValidAudioFile: '%s': %d", file_name, result);
278 return result;
279 }
280
281 String getIndexDef() {
282 FileT idxdef = p_sd->open(idx_defpath.c_str());
283 String key1 = idxdef.readString();
284 idxdef.close();
285 return key1;
286 }
287 void saveIndexDef(String keyNew) {
288 FileT idxdef = p_sd->open(idx_defpath.c_str(), FILE_WRITE);
289 idxdef.write((const uint8_t *)keyNew.c_str(), keyNew.length());
290 idxdef.close();
291 }
292
293 size_t indexFileTSize() {
294 FileT idxfile = p_sd->open(idx_path.c_str());
295 size_t result = idxfile.size();
296 idxfile.close();
297 return result;
298 }
299
300 void rewind(FileT &f) {
301 TRACED();
302#ifdef USE_SDFAT
303 f.rewind();
304#else
305 f.rewindDirectory();
306#endif
307 }
308
310 const char *fileName(FileT &file) {
311#ifdef USE_SDFAT
312 // add name
313 static char name[MAX_FILE_LEN];
314 file.getName(name, MAX_FILE_LEN);
315 return name;
316#else
317 StrView tmp(file.name());
318 int pos = 0;
319 // remove directories
320 if (tmp.contains("/")) {
321 pos = tmp.lastIndexOf("/") + 1;
322 }
323 return file.name() + pos;
324#endif
325 }
326
328 const char *fileNamePath(FileT &file) {
329#if defined(USE_SDFAT) || ESP_IDF_VERSION_MAJOR >= 4
330 LOGD("-> fileNamePath: %s", fileName(file));
331 file_path_str = start_dir;
332 if (!file_path_str.endsWith("/")) {
333 file_path_str += "/";
334 }
335 for (int j = 0; j < file_path_stack.size(); j++) {
336 file_path_str += file_path_stack[j] + "/";
337 }
338 // add name
339 static char name[MAX_FILE_LEN];
340 strncpy(name, fileName(file), MAX_FILE_LEN);
341 file_path_str += name;
342 const char *result = file_path_str.c_str();
343 LOGD("<- fileNamePath: %s", result);
344 return result;
345#else
346 return file.name();
347#endif
348 }
349
350 bool isHidden(FileT &f) {
351#ifdef USE_SDFAT
352 return f.isHidden();
353#else
354 return StrView(fileNamePath(f)).contains("/.");
355#endif
356 }
357
358 FileT open(const char *name) {
359 TRACED();
360#ifdef USE_SDFAT
361 FileT result;
362 if (!result.open(name)) {
363 LOGE("FileT open error: %s", name);
364 }
365 return result;
366#else
367 return p_sd->open(name, "r");
368#endif
369 }
370};
371
372} // 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:187
const char * fileNamePath(FileT &file)
Returns the filename including the path.
Definition SDIndex.h:328
int indexOf(const char *filename)
Find the index of a filename.
Definition SDIndex.h:99
bool isValidAudioFile(FileT &file)
checks if the file is a valid audio file
Definition SDIndex.h:267
const char * fileName(FileT &file)
Returns the filename w/o the path.
Definition SDIndex.h:310
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