arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
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
98 long size() {
99 if (max_idx == -1) {
100 FileT idxfile = p_sd->open(idx_path.c_str());
101 int count = 0;
102
103 while (idxfile.available() > 0) {
104 result = idxfile.readStringUntil('\n');
105 // result c-string
106 char *c_str = (char *)result.c_str();
107 count++;
108 }
109 idxfile.close();
110 max_idx = count;
111 }
112 return max_idx;
113 }
114
115 protected:
116 String result;
117 String idx_path;
118 String idx_defpath;
119 SDT *p_sd = nullptr;
120 List<String> file_path_stack;
121 String file_path_str;
122 const char *start_dir;
123
124 const char *ext = nullptr;
125 const char *file_name_pattern = nullptr;
126 long max_idx = -1;
127
128 String filePathString(const char *name, const char *suffix) {
129 String result = name;
130 return result.endsWith("/") ? result + suffix : result + "/" + suffix;
131 }
132
134 void listDir(Print &idxfile, const char *dirname) {
135 LOGD("listDir: %s", dirname);
136 FileT root = open(dirname);
137 if (!root) {
138 LOGE("Open failed: %s", dirname);
139 popPath();
140 return;
141 }
142 if (!isDirectory(root)) {
143 LOGD("Is not directory: %s", dirname);
144 popPath();
145 return;
146 }
147 if (StrView(dirname).startsWith(".")) {
148 LOGD("Invalid file: %s", dirname);
149 popPath();
150 return;
151 }
152
153 rewind(root);
154 FileT file = openNext(root);
155 while (file) {
156 if (isDirectory(file)) {
157 String name = String(fileNamePath(file));
158 LOGD("name: %s", name.c_str());
159 pushPath(fileName(file));
160 listDir(idxfile, name.c_str());
161 } else {
162 const char *fn = fileNamePath(file);
163 if (isValidAudioFile(file)) {
164 LOGD("Adding file to index: %s", fn);
165 idxfile.println(fn);
166 } else {
167 LOGD("Ignoring %s", fn);
168 }
169 }
170 file = openNext(root);
171 }
172 popPath();
173 }
174
175 bool isDirectory(FileT &f) {
176 bool result;
177#ifdef USE_SDFAT
178 result = f.isDir();
179#else
180 result = f.isDirectory();
181#endif
182 LOGD("isDirectory %s: %d", fileName(f), result);
183 return result;
184 }
185
186 FileT openNext(FileT &dir) {
187 TRACED();
188#ifdef USE_SDFAT
189 FileT result;
190 if (!result.openNext(&dir, O_READ)) {
191 LOGD("No next file");
192 }
193 return result;
194#else
195 return dir.openNextFile();
196#endif
197 }
198
199 void pushPath(const char *name) {
200 LOGD("pushPath: %s", name);
201 LOGD("pushPath: %s", name);
202 String nameStr(name);
203 file_path_stack.push_back(nameStr);
204 }
205
206 void popPath() {
207 TRACED();
208 String str;
209 file_path_stack.pop_back(str);
210 LOGD("popPath: %s", str.c_str());
211 }
212
214 bool isValidAudioFile(FileT &file) {
215 TRACED();
216 const char *file_name = fileName(file);
217 if (file.isDirectory()) {
218 LOGD("-> isValidAudioFile: '%s': %d", file_name, false);
219 return false;
220 }
221 StrView strFileTName(file_name);
222 bool result = strFileTName.endsWithIgnoreCase(ext) &&
223 strFileTName.matches(file_name_pattern) && !isHidden(file);
224 LOGD("-> isValidAudioFile: '%s': %d", file_name, result);
225 return result;
226 }
227
228 String getIndexDef() {
229 FileT idxdef = p_sd->open(idx_defpath.c_str());
230 String key1 = idxdef.readString();
231 idxdef.close();
232 return key1;
233 }
234 void saveIndexDef(String keyNew) {
235 FileT idxdef = p_sd->open(idx_defpath.c_str(), FILE_WRITE);
236 idxdef.write((const uint8_t *)keyNew.c_str(), keyNew.length());
237 idxdef.close();
238 }
239
240 size_t indexFileTSize() {
241 FileT idxfile = p_sd->open(idx_path.c_str());
242 size_t result = idxfile.size();
243 idxfile.close();
244 return result;
245 }
246
247 void rewind(FileT &f) {
248 TRACED();
249#ifdef USE_SDFAT
250 f.rewind();
251#else
252 f.rewindDirectory();
253#endif
254 }
255
257 const char *fileName(FileT &file) {
258#ifdef USE_SDFAT
259 // add name
260 static char name[MAX_FILE_LEN];
261 file.getName(name, MAX_FILE_LEN);
262 return name;
263#else
264 StrView tmp(file.name());
265 int pos = 0;
266 // remove directories
267 if (tmp.contains("/")) {
268 pos = tmp.lastIndexOf("/") + 1;
269 }
270 return file.name() + pos;
271#endif
272 }
273
275 const char *fileNamePath(FileT &file) {
276#if defined(USE_SDFAT) || ESP_IDF_VERSION_MAJOR >= 4
277 LOGD("-> fileNamePath: %s", fileName(file));
278 file_path_str = start_dir;
279 if (!file_path_str.endsWith("/")) {
280 file_path_str += "/";
281 }
282 for (int j = 0; j < file_path_stack.size(); j++) {
283 file_path_str += file_path_stack[j] + "/";
284 }
285 // add name
286 static char name[MAX_FILE_LEN];
287 strncpy(name, fileName(file), MAX_FILE_LEN);
288 file_path_str += name;
289 const char *result = file_path_str.c_str();
290 LOGD("<- fileNamePath: %s", result);
291 return result;
292#else
293 return file.name();
294#endif
295 }
296
297 bool isHidden(FileT &f) {
298#ifdef USE_SDFAT
299 return f.isHidden();
300#else
301 return StrView(fileNamePath(f)).contains("/.");
302#endif
303 }
304
305 FileT open(const char *name) {
306 TRACED();
307#ifdef USE_SDFAT
308 FileT result;
309 if (!result.open(name)) {
310 LOGE("FileT open error: %s", name);
311 }
312 return result;
313#else
314 return p_sd->open(name, "r");
315#endif
316 }
317};
318
319} // 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:134
const char * fileNamePath(FileT &file)
Returns the filename including the path.
Definition SDIndex.h:275
bool isValidAudioFile(FileT &file)
checks if the file is a valid audio file
Definition SDIndex.h:214
const char * fileName(FileT &file)
Returns the filename w/o the path.
Definition SDIndex.h:257
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