/ lib / SDCardManager / src / SDCardManager.cpp
SDCardManager.cpp
  1  #include "SDCardManager.h"
  2  
  3  namespace {
  4  constexpr uint8_t SD_CS = 12;
  5  constexpr uint32_t SPI_FQ = 40000000;
  6  }  // namespace
  7  
  8  SDCardManager SDCardManager::instance;
  9  
 10  SDCardManager::SDCardManager() : sd() {}
 11  
 12  bool SDCardManager::begin() {
 13    if (!sd.begin(SD_CS, SPI_FQ)) {
 14      if (Serial) Serial.printf("[%lu] [SD] SD card not detected\n", millis());
 15      initialized = false;
 16    } else {
 17      if (Serial) Serial.printf("[%lu] [SD] SD card detected\n", millis());
 18      initialized = true;
 19    }
 20  
 21    return initialized;
 22  }
 23  
 24  bool SDCardManager::ready() const { return initialized; }
 25  
 26  std::vector<String> SDCardManager::listFiles(const char* path, const int maxFiles) {
 27    std::vector<String> ret;
 28    if (!initialized) {
 29      if (Serial) Serial.printf("[%lu] [SD] not initialized, returning empty list\n", millis());
 30      return ret;
 31    }
 32  
 33    auto root = sd.open(path);
 34    if (!root) {
 35      if (Serial) Serial.printf("[%lu] [SD] Failed to open directory\n", millis());
 36      return ret;
 37    }
 38    if (!root.isDirectory()) {
 39      if (Serial) Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
 40      root.close();
 41      return ret;
 42    }
 43  
 44    int count = 0;
 45    char name[128];
 46    for (auto f = root.openNextFile(); f && count < maxFiles; f = root.openNextFile()) {
 47      if (f.isDirectory()) {
 48        f.close();
 49        continue;
 50      }
 51      f.getName(name, sizeof(name));
 52      ret.emplace_back(name);
 53      f.close();
 54      count++;
 55    }
 56    root.close();
 57    return ret;
 58  }
 59  
 60  String SDCardManager::readFile(const char* path) {
 61    if (!initialized) {
 62      if (Serial) Serial.printf("[%lu] [SD] not initialized; cannot read file\n", millis());
 63      return {""};
 64    }
 65  
 66    FsFile f;
 67    if (!openFileForRead("SD", path, f)) {
 68      return {""};
 69    }
 70  
 71    constexpr size_t maxSize = 50000;  // Limit to 50KB
 72    const size_t fileSize = f.size();
 73    const size_t toRead = (fileSize < maxSize) ? fileSize : maxSize;
 74  
 75    String content;
 76    content.reserve(toRead);
 77  
 78    uint8_t buf[256];
 79    size_t readSize = 0;
 80    while (f.available() && readSize < toRead) {
 81      const size_t chunkSize = min(sizeof(buf), toRead - readSize);
 82      const int n = f.read(buf, chunkSize);
 83      if (n <= 0) break;
 84      content.concat(reinterpret_cast<char*>(buf), static_cast<size_t>(n));
 85      readSize += static_cast<size_t>(n);
 86    }
 87    f.close();
 88    return content;
 89  }
 90  
 91  bool SDCardManager::readFileToStream(const char* path, Print& out, const size_t chunkSize) {
 92    if (!initialized) {
 93      if (Serial) Serial.printf("[%lu] [SD] SD card not initialized\n", millis());
 94      return false;
 95    }
 96  
 97    FsFile f;
 98    if (!openFileForRead("SD", path, f)) {
 99      return false;
100    }
101  
102    constexpr size_t localBufSize = 256;
103    uint8_t buf[localBufSize];
104    const size_t toRead = (chunkSize == 0) ? localBufSize : (chunkSize < localBufSize ? chunkSize : localBufSize);
105  
106    while (f.available()) {
107      const int r = f.read(buf, toRead);
108      if (r > 0) {
109        out.write(buf, static_cast<size_t>(r));
110      } else {
111        break;
112      }
113    }
114  
115    f.close();
116    return true;
117  }
118  
119  size_t SDCardManager::readFileToBuffer(const char* path, char* buffer, const size_t bufferSize, const size_t maxBytes) {
120    if (!buffer || bufferSize == 0) return 0;
121    if (!initialized) {
122      if (Serial) Serial.printf("[%lu] [SD] SD card not initialized\n", millis());
123      buffer[0] = '\0';
124      return 0;
125    }
126  
127    FsFile f;
128    if (!openFileForRead("SD", path, f)) {
129      buffer[0] = '\0';
130      return 0;
131    }
132  
133    const size_t maxToRead = (maxBytes == 0) ? (bufferSize - 1) : min(maxBytes, bufferSize - 1);
134    size_t total = 0;
135  
136    while (f.available() && total < maxToRead) {
137      constexpr size_t chunk = 64;
138      const size_t want = maxToRead - total;
139      const size_t readLen = (want < chunk) ? want : chunk;
140      const int r = f.read(buffer + total, readLen);
141      if (r > 0) {
142        total += static_cast<size_t>(r);
143      } else {
144        break;
145      }
146    }
147  
148    buffer[total] = '\0';
149    f.close();
150    return total;
151  }
152  
153  bool SDCardManager::writeFile(const char* path, const String& content) {
154    if (!initialized) {
155      if (Serial) Serial.printf("[%lu] [SD] SD card not initialized\n", millis());
156      return false;
157    }
158  
159    // Remove existing file so we perform an overwrite rather than append
160    if (sd.exists(path)) {
161      sd.remove(path);
162    }
163  
164    FsFile f;
165    if (!openFileForWrite("SD", path, f)) {
166      return false;
167    }
168  
169    const size_t written = f.print(content);
170    f.close();
171    return written == content.length();
172  }
173  
174  bool SDCardManager::ensureDirectoryExists(const char* path) {
175    if (!initialized) {
176      if (Serial) Serial.printf("[%lu] [SD] SD card not initialized\n", millis());
177      return false;
178    }
179  
180    // Check if directory already exists
181    if (sd.exists(path)) {
182      FsFile dir = sd.open(path);
183      if (dir && dir.isDirectory()) {
184        dir.close();
185        return true;
186      }
187      dir.close();
188    }
189  
190    // Create the directory
191    if (sd.mkdir(path)) {
192      if (Serial) Serial.printf("[%lu] [SD] Created directory: %s\n", millis(), path);
193      return true;
194    } else {
195      if (Serial) Serial.printf("[%lu] [SD] Failed to create directory: %s\n", millis(), path);
196      return false;
197    }
198  }
199  
200  bool SDCardManager::openFileForRead(const char* moduleName, const char* path, FsFile& file) {
201    if (!sd.exists(path)) {
202      if (Serial) Serial.printf("[%lu] [%s] File does not exist: %s\n", millis(), moduleName, path);
203      return false;
204    }
205  
206    file = sd.open(path, O_RDONLY);
207    if (!file) {
208      if (Serial) Serial.printf("[%lu] [%s] Failed to open file for reading: %s\n", millis(), moduleName, path);
209      return false;
210    }
211    return true;
212  }
213  
214  bool SDCardManager::openFileForRead(const char* moduleName, const std::string& path, FsFile& file) {
215    return openFileForRead(moduleName, path.c_str(), file);
216  }
217  
218  bool SDCardManager::openFileForRead(const char* moduleName, const String& path, FsFile& file) {
219    return openFileForRead(moduleName, path.c_str(), file);
220  }
221  
222  bool SDCardManager::openFileForWrite(const char* moduleName, const char* path, FsFile& file) {
223    file = sd.open(path, O_RDWR | O_CREAT | O_TRUNC);
224    if (!file) {
225      if (Serial) Serial.printf("[%lu] [%s] Failed to open file for writing: %s\n", millis(), moduleName, path);
226      return false;
227    }
228    return true;
229  }
230  
231  bool SDCardManager::openFileForWrite(const char* moduleName, const std::string& path, FsFile& file) {
232    return openFileForWrite(moduleName, path.c_str(), file);
233  }
234  
235  bool SDCardManager::openFileForWrite(const char* moduleName, const String& path, FsFile& file) {
236    return openFileForWrite(moduleName, path.c_str(), file);
237  }
238  
239  bool SDCardManager::removeDir(const char* path) {
240    // 1. Open the directory
241    auto dir = sd.open(path);
242    if (!dir) {
243      return false;
244    }
245    if (!dir.isDirectory()) {
246      return false;
247    }
248  
249    auto file = dir.openNextFile();
250    char name[128];
251    while (file) {
252      String filePath = path;
253      if (!filePath.endsWith("/")) {
254        filePath += "/";
255      }
256      file.getName(name, sizeof(name));
257      filePath += name;
258  
259      if (file.isDirectory()) {
260        if (!removeDir(filePath.c_str())) {
261          return false;
262        }
263      } else {
264        if (!sd.remove(filePath.c_str())) {
265          return false;
266        }
267      }
268      file = dir.openNextFile();
269    }
270  
271    return sd.rmdir(path);
272  }