arduino-audio-tools
SDIndex.h
1 #pragma once
2 
3 #define MAX_FILE_LEN 256
4 
5 // Special logic for SDTFAT
6 #ifdef SDT_FAT_VERSION
7 #define USE_SDFAT
8 #endif
9 
10 namespace audio_tools {
11 
12 
17 template<class SDT, class FileT>
18 class SDIndex {
19  public:
20  SDIndex(SDT &sd) {
21  p_sd = &sd;
22  };
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 = 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  }
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 (Str(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  Str strFileTName(file_name);
222  bool result = strFileTName.endsWithIgnoreCase(ext)
223  && strFileTName.matches(file_name_pattern)
224  && !isHidden(file);
225  LOGD("-> isValidAudioFile: '%s': %d", file_name, result);
226  return result;
227  }
228 
229  String getIndexDef() {
230  FileT idxdef = p_sd->open(idx_defpath.c_str());
231  String key1 = idxdef.readString();
232  idxdef.close();
233  return key1;
234  }
235  void saveIndexDef(String keyNew){
236  FileT idxdef = p_sd->open(idx_defpath.c_str(), FILE_WRITE);
237  idxdef.write((const uint8_t *)keyNew.c_str(), keyNew.length());
238  idxdef.close();
239  }
240 
241  size_t indexFileTSize() {
242  FileT idxfile = p_sd->open(idx_path.c_str());
243  size_t result = idxfile.size();
244  idxfile.close();
245  return result;
246  }
247 
248  void rewind(FileT f){
249  TRACED();
250 #ifdef USE_SDFAT
251  f.rewind();
252 #else
253  f.rewindDirectory();
254 #endif
255  }
256 
258  const char* fileName(FileT&file){
259 #ifdef USE_SDFAT
260  // add name
261  static char name[MAX_FILE_LEN];
262  file.getName(name,MAX_FILE_LEN);
263  return name;
264 #else
265  Str tmp(file.name());
266  int pos=0;
267  // remove directories
268  if (tmp.contains("/")){
269  pos = tmp.lastIndexOf("/")+1;
270  }
271  return file.name()+pos;
272 #endif
273  }
274 
276  const char* fileNamePath(FileT &file){
277 #if defined(USE_SDFAT) || ESP_IDF_VERSION_MAJOR >= 4
278  LOGD("-> fileNamePath: %s", fileName(file));
279  file_path_str = start_dir;
280  if (!file_path_str.endsWith("/")){
281  file_path_str += "/";
282  }
283  for (int j=0; j<file_path_stack.size(); j++){
284  file_path_str += file_path_stack[j]+"/";
285  }
286  // add name
287  static char name[MAX_FILE_LEN];
288  strncpy(name, fileName(file), MAX_FILE_LEN);
289  file_path_str += name;
290  const char* result = file_path_str.c_str();
291  LOGD("<- fileNamePath: %s", result);
292  return result;
293 #else
294  return file.name();
295 #endif
296  }
297 
298  bool isHidden(FileT f){
299 #ifdef USE_SDFAT
300  return f.isHidden();
301 #else
302  return Str(fileNamePath(f)).contains("/.");
303 #endif
304 
305  }
306 
307  FileT open(const char* name){
308  TRACED();
309 #ifdef USE_SDFAT
310  FileT result;
311  if (!result.open(name)){
312  LOGE("FileT open error: %s", name);
313  }
314  return result;
315 #else
316  return p_sd->open(name);
317 #endif
318  }
319 
320 };
321 
322 }
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:18
const char * operator[](int idx)
Access file name by index.
Definition: SDIndex.h:58
const char * fileNamePath(FileT &file)
Returns the filename including the path.
Definition: SDIndex.h:276
const char * fileName(FileT &file)
Returns the filename w/o the path.
Definition: SDIndex.h:258
void listDir(Print &idxfile, const char *dirname)
Writes the index file.
Definition: SDIndex.h:134
bool isValidAudioFile(FileT &file)
checks if the file is a valid audio file
Definition: SDIndex.h:214
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: Str.h:27
virtual bool contains(const char *str)
checks if the string contains a substring
Definition: Str.h:281
virtual int lastIndexOf(const char *cont)
provides the position of the last occurrence of the indicated substring
Definition: Str.h:298
virtual bool matches(const char *pattern)
Definition: Str.h:204
virtual bool endsWithIgnoreCase(const char *str)
checks if the string ends with the indicated substring
Definition: Str.h:184
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10