arduino-audio-tools
SDIndex.h
1 #pragma once
2 
3 #include "AudioTools/CoreAudio/AudioBasic/Str.h"
4 
5 #define MAX_FILE_LEN 256
6 
7 // Special logic for SDTFAT
8 #ifdef SDT_FAT_VERSION
9 # define USE_SDFAT
10 #endif
11 
12 namespace audio_tools {
13 
18 template <class SDT, class FileT>
19 class SDIndex {
20  public:
21  SDIndex(SDT &sd) { p_sd = &sd; };
22  void begin(const char *startDir, const char *extension,
23  const char *file_name_pattern, bool setupIndex = true) {
24  TRACED();
25  this->start_dir = startDir;
26  this->ext = extension;
27  this->file_name_pattern = file_name_pattern;
28  idx_path = filePathString(startDir, "idx.txt");
29  idx_defpath = filePathString(startDir, "idx-def.txt");
30  int idx_file_size = indexFileTSize();
31  LOGI("Index file size: %d", idx_file_size);
32  String keyNew =
33  String(startDir) + "|" + extension + "|" + file_name_pattern;
34  String keyOld = getIndexDef();
35  if (setupIndex && (keyNew != keyOld || idx_file_size == 0)) {
36  FileT idxfile = p_sd->open(idx_path.c_str(), FILE_WRITE);
37  LOGW("Creating index file");
38  listDir(idxfile, startDir);
39  LOGI("Indexing completed");
40  idxfile.close();
41  // update index definition file
42  saveIndexDef(keyNew);
43  }
44  }
45 
46  void ls(Print &p, const char *startDir, const char *extension,
47  const char *file_name_pattern = "*") {
48  TRACED();
49  this->ext = extension;
50  this->file_name_pattern = file_name_pattern;
51  listDir(p, startDir);
52  file_path_stack.clear();
53  file_path_str.clear();
54  }
55 
57  const char *operator[](int idx) {
58  // return null when inx too big
59  if (max_idx >= 0 && idx > max_idx) {
60  LOGE("idx %d > size %d", idx, max_idx);
61  return nullptr;
62  }
63  // find record
64  FileT idxfile = p_sd->open(idx_path.c_str());
65  int count = 0;
66 
67  if (idxfile.available() == 0) {
68  LOGE("Index file is empty");
69  }
70 
71  bool found = false;
72  while (idxfile.available() > 0 && !found) {
73  result = idxfile.readStringUntil('\n');
74 
75  // result c-string
76  char *c_str = (char *)result.c_str();
77  // remove potential cr character - hax to avoid any allocations
78  int lastPos = result.length() - 1;
79  if (c_str[lastPos] == 13) {
80  c_str[lastPos] = 0;
81  }
82 
83  LOGD("%d -> %s", count, c_str);
84  if (count == idx) {
85  found = true;
86  }
87  count++;
88  }
89  if (!found) {
90  max_idx = count;
91  }
92  idxfile.close();
93 
94  return found ? result.c_str() : nullptr;
95  }
96 
97  long size() {
98  if (max_idx == -1) {
99  FileT idxfile = p_sd->open(idx_path.c_str());
100  int count = 0;
101 
102  while (idxfile.available() > 0) {
103  result = idxfile.readStringUntil('\n');
104  // result c-string
105  char *c_str = (char *)result.c_str();
106  count++;
107  }
108  idxfile.close();
109  max_idx = count;
110  }
111  return max_idx;
112  }
113 
114  protected:
115  String result;
116  String idx_path;
117  String idx_defpath;
118  SDT *p_sd = nullptr;
119  List<String> file_path_stack;
120  String file_path_str;
121  const char *start_dir;
122 
123  const char *ext = nullptr;
124  const char *file_name_pattern = nullptr;
125  long max_idx = -1;
126 
127  String filePathString(const char *name, const char *suffix) {
128  String result = name;
129  return result.endsWith("/") ? result + suffix : result + "/" + suffix;
130  }
131 
133  void listDir(Print &idxfile, const char *dirname) {
134  LOGD("listDir: %s", dirname);
135  FileT root = open(dirname);
136  if (!root) {
137  LOGE("Open failed: %s", dirname);
138  popPath();
139  return;
140  }
141  if (!isDirectory(root)) {
142  LOGD("Is not directory: %s", dirname);
143  popPath();
144  return;
145  }
146  if (StrView(dirname).startsWith(".")) {
147  LOGD("Invalid file: %s", dirname);
148  popPath();
149  return;
150  }
151 
152  rewind(root);
153  FileT file = openNext(root);
154  while (file) {
155  if (isDirectory(file)) {
156  String name = String(fileNamePath(file));
157  LOGD("name: %s", name.c_str());
158  pushPath(fileName(file));
159  listDir(idxfile, name.c_str());
160  } else {
161  const char *fn = fileNamePath(file);
162  if (isValidAudioFile(file)) {
163  LOGD("Adding file to index: %s", fn);
164  idxfile.println(fn);
165  } else {
166  LOGD("Ignoring %s", fn);
167  }
168  }
169  file = openNext(root);
170  }
171  popPath();
172  }
173 
174  bool isDirectory(FileT &f) {
175  bool result;
176 #ifdef USE_SDFAT
177  result = f.isDir();
178 #else
179  result = f.isDirectory();
180 #endif
181  LOGD("isDirectory %s: %d", fileName(f), result);
182  return result;
183  }
184 
185  FileT openNext(FileT &dir) {
186  TRACED();
187 #ifdef USE_SDFAT
188  FileT result;
189  if (!result.openNext(&dir, O_READ)) {
190  LOGD("No next file");
191  }
192  return result;
193 #else
194  return dir.openNextFile();
195 #endif
196  }
197 
198  void pushPath(const char *name) {
199  LOGD("pushPath: %s", name);
200  LOGD("pushPath: %s", name);
201  String nameStr(name);
202  file_path_stack.push_back(nameStr);
203  }
204 
205  void popPath() {
206  TRACED();
207  String str;
208  file_path_stack.pop_back(str);
209  LOGD("popPath: %s", str.c_str());
210  }
211 
213  bool isValidAudioFile(FileT &file) {
214  TRACED();
215  const char *file_name = fileName(file);
216  if (file.isDirectory()) {
217  LOGD("-> isValidAudioFile: '%s': %d", file_name, false);
218  return false;
219  }
220  StrView strFileTName(file_name);
221  bool result = strFileTName.endsWithIgnoreCase(ext) &&
222  strFileTName.matches(file_name_pattern) && !isHidden(file);
223  LOGD("-> isValidAudioFile: '%s': %d", file_name, result);
224  return result;
225  }
226 
227  String getIndexDef() {
228  FileT idxdef = p_sd->open(idx_defpath.c_str());
229  String key1 = idxdef.readString();
230  idxdef.close();
231  return key1;
232  }
233  void saveIndexDef(String keyNew) {
234  FileT idxdef = p_sd->open(idx_defpath.c_str(), FILE_WRITE);
235  idxdef.write((const uint8_t *)keyNew.c_str(), keyNew.length());
236  idxdef.close();
237  }
238 
239  size_t indexFileTSize() {
240  FileT idxfile = p_sd->open(idx_path.c_str());
241  size_t result = idxfile.size();
242  idxfile.close();
243  return result;
244  }
245 
246  void rewind(FileT &f) {
247  TRACED();
248 #ifdef USE_SDFAT
249  f.rewind();
250 #else
251  f.rewindDirectory();
252 #endif
253  }
254 
256  const char *fileName(FileT &file) {
257 #ifdef USE_SDFAT
258  // add name
259  static char name[MAX_FILE_LEN];
260  file.getName(name, MAX_FILE_LEN);
261  return name;
262 #else
263  StrView tmp(file.name());
264  int pos = 0;
265  // remove directories
266  if (tmp.contains("/")) {
267  pos = tmp.lastIndexOf("/") + 1;
268  }
269  return file.name() + pos;
270 #endif
271  }
272 
274  const char *fileNamePath(FileT &file) {
275 #if defined(USE_SDFAT) || ESP_IDF_VERSION_MAJOR >= 4
276  LOGD("-> fileNamePath: %s", fileName(file));
277  file_path_str = start_dir;
278  if (!file_path_str.endsWith("/")) {
279  file_path_str += "/";
280  }
281  for (int j = 0; j < file_path_stack.size(); j++) {
282  file_path_str += file_path_stack[j] + "/";
283  }
284  // add name
285  static char name[MAX_FILE_LEN];
286  strncpy(name, fileName(file), MAX_FILE_LEN);
287  file_path_str += name;
288  const char *result = file_path_str.c_str();
289  LOGD("<- fileNamePath: %s", result);
290  return result;
291 #else
292  return file.name();
293 #endif
294  }
295 
296  bool isHidden(FileT &f) {
297 #ifdef USE_SDFAT
298  return f.isHidden();
299 #else
300  return StrView(fileNamePath(f)).contains("/.");
301 #endif
302  }
303 
304  FileT open(const char *name) {
305  TRACED();
306 #ifdef USE_SDFAT
307  FileT result;
308  if (!result.open(name)) {
309  LOGE("FileT open error: %s", name);
310  }
311  return result;
312 #else
313  return p_sd->open(name);
314 #endif
315  }
316 };
317 
318 } // namespace audio_tools
Definition: NoArduino.h:58
We store all the relevant file names in an sequential index file. Form there we can access them via a...
Definition: SDIndex.h:19
const char * operator[](int idx)
Access file name by index.
Definition: SDIndex.h:57
const char * fileNamePath(FileT &file)
Returns the filename including the path.
Definition: SDIndex.h:274
const char * fileName(FileT &file)
Returns the filename w/o the path.
Definition: SDIndex.h:256
void listDir(Print &idxfile, const char *dirname)
Writes the index file.
Definition: SDIndex.h:133
bool isValidAudioFile(FileT &file)
checks if the file is a valid audio file
Definition: SDIndex.h:213
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: AudioConfig.h:868