2025-12-04 19:20:21 +07:00
|
|
|
#include "SDCardManager.h"
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
namespace {
|
|
|
|
|
constexpr uint8_t SD_CS = 12;
|
|
|
|
|
constexpr uint32_t SPI_FQ = 40000000;
|
|
|
|
|
}
|
2025-12-04 19:20:21 +07:00
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
SDCardManager SDCardManager::instance;
|
2025-12-04 19:20:21 +07:00
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
SDCardManager::SDCardManager() : sd() {}
|
2025-12-04 19:20:21 +07:00
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
bool SDCardManager::begin() {
|
|
|
|
|
if (!sd.begin(SD_CS, SPI_FQ)) {
|
|
|
|
|
Serial.printf("[%lu] [SD] SD card not detected\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
initialized = false;
|
|
|
|
|
} else {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] SD card detected\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
initialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return initialized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::ready() const {
|
|
|
|
|
return initialized;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
std::vector<String> SDCardManager::listFiles(const char* path, const int maxFiles) {
|
2025-12-04 19:20:21 +07:00
|
|
|
std::vector<String> ret;
|
|
|
|
|
if (!initialized) {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] not initialized, returning empty list\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
auto root = sd.open(path);
|
2025-12-04 19:20:21 +07:00
|
|
|
if (!root) {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] Failed to open directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
if (!root.isDirectory()) {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
root.close();
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int count = 0;
|
2025-12-30 15:02:18 +10:00
|
|
|
char name[128];
|
|
|
|
|
for (auto f = root.openNextFile(); f && count < maxFiles; f = root.openNextFile()) {
|
2025-12-04 19:20:21 +07:00
|
|
|
if (f.isDirectory()) {
|
|
|
|
|
f.close();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-12-30 15:02:18 +10:00
|
|
|
f.getName(name, sizeof(name));
|
|
|
|
|
ret.emplace_back(name);
|
2025-12-04 19:20:21 +07:00
|
|
|
f.close();
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
root.close();
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String SDCardManager::readFile(const char* path) {
|
|
|
|
|
if (!initialized) {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] not initialized; cannot read file\n", millis());
|
|
|
|
|
return {""};
|
2025-12-04 19:20:21 +07:00
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
FsFile f;
|
|
|
|
|
if (!openFileForRead("SD", path, f)) {
|
|
|
|
|
return {""};
|
2025-12-04 19:20:21 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String content = "";
|
2025-12-30 15:02:18 +10:00
|
|
|
constexpr size_t maxSize = 50000; // Limit to 50KB
|
2025-12-04 19:20:21 +07:00
|
|
|
size_t readSize = 0;
|
|
|
|
|
while (f.available() && readSize < maxSize) {
|
2025-12-30 15:02:18 +10:00
|
|
|
const char c = static_cast<char>(f.read());
|
2025-12-04 19:20:21 +07:00
|
|
|
content += c;
|
|
|
|
|
readSize++;
|
|
|
|
|
}
|
|
|
|
|
f.close();
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
bool SDCardManager::readFileToStream(const char* path, Print& out, const size_t chunkSize) {
|
2025-12-04 19:20:21 +07:00
|
|
|
if (!initialized) {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
Serial.println("SDCardManager: not initialized; cannot read file");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
FsFile f;
|
|
|
|
|
if (!openFileForRead("SD", path, f)) {
|
2025-12-04 19:20:21 +07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
constexpr size_t localBufSize = 256;
|
2025-12-04 19:20:21 +07:00
|
|
|
uint8_t buf[localBufSize];
|
2025-12-30 15:02:18 +10:00
|
|
|
const size_t toRead = (chunkSize == 0) ? localBufSize : (chunkSize < localBufSize ? chunkSize : localBufSize);
|
2025-12-04 19:20:21 +07:00
|
|
|
|
|
|
|
|
while (f.available()) {
|
2025-12-30 15:02:18 +10:00
|
|
|
const int r = f.read(buf, toRead);
|
2025-12-04 19:20:21 +07:00
|
|
|
if (r > 0) {
|
2025-12-30 15:02:18 +10:00
|
|
|
out.write(buf, static_cast<size_t>(r));
|
2025-12-04 19:20:21 +07:00
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f.close();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
size_t SDCardManager::readFileToBuffer(const char* path, char* buffer, const size_t bufferSize, const size_t maxBytes) {
|
2025-12-04 19:20:21 +07:00
|
|
|
if (!buffer || bufferSize == 0)
|
|
|
|
|
return 0;
|
|
|
|
|
if (!initialized) {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
Serial.println("SDCardManager: not initialized; cannot read file");
|
|
|
|
|
buffer[0] = '\0';
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
FsFile f;
|
|
|
|
|
if (!openFileForRead("SD", path, f)) {
|
2025-12-04 19:20:21 +07:00
|
|
|
buffer[0] = '\0';
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
const size_t maxToRead = (maxBytes == 0) ? (bufferSize - 1) : min(maxBytes, bufferSize - 1);
|
2025-12-04 19:20:21 +07:00
|
|
|
size_t total = 0;
|
|
|
|
|
|
|
|
|
|
while (f.available() && total < maxToRead) {
|
2025-12-30 15:02:18 +10:00
|
|
|
constexpr size_t chunk = 64;
|
|
|
|
|
const size_t want = maxToRead - total;
|
|
|
|
|
const size_t readLen = (want < chunk) ? want : chunk;
|
|
|
|
|
const int r = f.read(buffer + total, readLen);
|
2025-12-04 19:20:21 +07:00
|
|
|
if (r > 0) {
|
2025-12-30 15:02:18 +10:00
|
|
|
total += static_cast<size_t>(r);
|
2025-12-04 19:20:21 +07:00
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buffer[total] = '\0';
|
|
|
|
|
f.close();
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::writeFile(const char* path, const String& content) {
|
|
|
|
|
if (!initialized) {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
Serial.println("SDCardManager: not initialized; cannot write file");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove existing file so we perform an overwrite rather than append
|
2025-12-30 15:02:18 +10:00
|
|
|
if (sd.exists(path)) {
|
|
|
|
|
sd.remove(path);
|
2025-12-04 19:20:21 +07:00
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
FsFile f;
|
|
|
|
|
if (!openFileForWrite("SD", path, f)) {
|
|
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
Serial.printf("Failed to open file for write: %s\n", path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:02:18 +10:00
|
|
|
const size_t written = f.print(content);
|
2025-12-04 19:20:21 +07:00
|
|
|
f.close();
|
2025-12-30 15:02:18 +10:00
|
|
|
return written == content.length();
|
2025-12-04 19:20:21 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::ensureDirectoryExists(const char* path) {
|
|
|
|
|
if (!initialized) {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
Serial.println("SDCardManager: not initialized; cannot create directory");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if directory already exists
|
2025-12-30 15:02:18 +10:00
|
|
|
if (sd.exists(path)) {
|
|
|
|
|
FsFile dir = sd.open(path);
|
2025-12-04 19:20:21 +07:00
|
|
|
if (dir && dir.isDirectory()) {
|
|
|
|
|
dir.close();
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
Serial.printf("Directory already exists: %s\n", path);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
dir.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the directory
|
2025-12-30 15:02:18 +10:00
|
|
|
if (sd.mkdir(path)) {
|
|
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
Serial.printf("Created directory: %s\n", path);
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
2025-12-30 15:02:18 +10:00
|
|
|
Serial.printf("[%lu] [SD] Path is not a directory\n", millis());
|
2025-12-04 19:20:21 +07:00
|
|
|
Serial.printf("Failed to create directory: %s\n", path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-30 15:02:18 +10:00
|
|
|
|
|
|
|
|
bool SDCardManager::openFileForRead(const char* moduleName, const char* path, FsFile& file) {
|
|
|
|
|
if (!sd.exists(path)) {
|
|
|
|
|
Serial.printf("[%lu] [%s] File does not exist: %s\n", millis(), moduleName, path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file = sd.open(path, O_RDONLY);
|
|
|
|
|
if (!file) {
|
|
|
|
|
Serial.printf("[%lu] [%s] Failed to open file for reading: %s\n", millis(), moduleName, path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::openFileForRead(const char* moduleName, const std::string& path, FsFile& file) {
|
|
|
|
|
return openFileForRead(moduleName, path.c_str(), file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::openFileForRead(const char* moduleName, const String& path, FsFile& file) {
|
|
|
|
|
return openFileForRead(moduleName, path.c_str(), file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::openFileForWrite(const char* moduleName, const char* path, FsFile& file) {
|
|
|
|
|
file = sd.open(path, O_RDWR | O_CREAT | O_TRUNC);
|
|
|
|
|
if (!file) {
|
|
|
|
|
Serial.printf("[%lu] [%s] Failed to open file for writing: %s\n", millis(), moduleName, path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::openFileForWrite(const char* moduleName, const std::string& path, FsFile& file) {
|
|
|
|
|
return openFileForWrite(moduleName, path.c_str(), file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::openFileForWrite(const char* moduleName, const String& path, FsFile& file) {
|
|
|
|
|
return openFileForWrite(moduleName, path.c_str(), file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SDCardManager::removeDir(const char* path) {
|
|
|
|
|
// 1. Open the directory
|
|
|
|
|
auto dir = sd.open(path);
|
|
|
|
|
if (!dir) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!dir.isDirectory()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto file = dir.openNextFile();
|
|
|
|
|
char name[128];
|
|
|
|
|
while (file) {
|
|
|
|
|
String filePath = path;
|
|
|
|
|
if (!filePath.endsWith("/")) {
|
|
|
|
|
filePath += "/";
|
|
|
|
|
}
|
|
|
|
|
file.getName(name, sizeof(name));
|
|
|
|
|
filePath += name;
|
|
|
|
|
|
|
|
|
|
if (file.isDirectory()) {
|
|
|
|
|
if (!removeDir(filePath.c_str())) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!sd.remove(filePath.c_str())) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
file = dir.openNextFile();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sd.rmdir(path);
|
|
|
|
|
}
|