238 lines
8.4 KiB
C++
238 lines
8.4 KiB
C++
#define HAL_STORAGE_IMPL
|
|
#include "HalStorage.h"
|
|
|
|
#include <FS.h> // need to be included before SdFat.h for compatibility with FS.h's File class
|
|
#include <HalClock.h>
|
|
#include <Logging.h>
|
|
#include <SDCardManager.h>
|
|
#include <SdFat.h>
|
|
|
|
#include <cassert>
|
|
#include <ctime>
|
|
#include <new>
|
|
|
|
#define SDCard SDCardManager::getInstance()
|
|
|
|
HalStorage HalStorage::instance;
|
|
|
|
HalStorage::HalStorage() {
|
|
storageMutex = xSemaphoreCreateMutex();
|
|
assert(storageMutex != nullptr);
|
|
}
|
|
|
|
// begin() and ready() are only called from setup, no need to acquire mutex for them
|
|
|
|
bool HalStorage::begin() {
|
|
if (!SDCard.begin()) return false;
|
|
FsDateTime::setCallback([](uint16_t* date, uint16_t* time) {
|
|
if (!HalClock::isSynced()) return;
|
|
const time_t t = HalClock::now();
|
|
const struct tm* tm = localtime(&t);
|
|
if (!tm) return;
|
|
*date = FS_DATE(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
|
|
*time = FS_TIME(tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
bool HalStorage::ready() const { return SDCard.ready(); }
|
|
|
|
// For the rest of the methods, we acquire the mutex to ensure thread safety
|
|
|
|
class HalStorage::StorageLock {
|
|
public:
|
|
StorageLock() { xSemaphoreTake(HalStorage::getInstance().storageMutex, portMAX_DELAY); }
|
|
~StorageLock() { xSemaphoreGive(HalStorage::getInstance().storageMutex); }
|
|
};
|
|
|
|
#define HAL_STORAGE_WRAPPED_CALL(method, ...) \
|
|
HalStorage::StorageLock lock; \
|
|
return SDCard.method(__VA_ARGS__);
|
|
|
|
std::vector<String> HalStorage::listFiles(const char* path, int maxFiles) {
|
|
HAL_STORAGE_WRAPPED_CALL(listFiles, path, maxFiles);
|
|
}
|
|
|
|
String HalStorage::readFile(const char* path) { HAL_STORAGE_WRAPPED_CALL(readFile, path); }
|
|
|
|
bool HalStorage::readFileToStream(const char* path, Print& out, size_t chunkSize) {
|
|
HAL_STORAGE_WRAPPED_CALL(readFileToStream, path, out, chunkSize);
|
|
}
|
|
|
|
size_t HalStorage::readFileToBuffer(const char* path, char* buffer, size_t bufferSize, size_t maxBytes) {
|
|
HAL_STORAGE_WRAPPED_CALL(readFileToBuffer, path, buffer, bufferSize, maxBytes);
|
|
}
|
|
|
|
bool HalStorage::writeFile(const char* path, const String& content) {
|
|
HAL_STORAGE_WRAPPED_CALL(writeFile, path, content);
|
|
}
|
|
|
|
bool HalStorage::ensureDirectoryExists(const char* path) { HAL_STORAGE_WRAPPED_CALL(ensureDirectoryExists, path); }
|
|
|
|
uint64_t HalStorage::sdTotalBytes() const {
|
|
StorageLock lock;
|
|
return SDCard.sdTotalBytes();
|
|
}
|
|
|
|
uint64_t HalStorage::sdUsedBytes() {
|
|
StorageLock lock;
|
|
return SDCard.sdUsedBytes();
|
|
}
|
|
|
|
uint64_t HalStorage::sdFreeBytes() {
|
|
uint64_t total = sdTotalBytes();
|
|
uint64_t used = sdUsedBytes();
|
|
if (total <= used) return 0;
|
|
return total - used;
|
|
}
|
|
|
|
class HalFile::Impl {
|
|
public:
|
|
Impl(FsFile&& fsFile) : file(std::move(fsFile)) {}
|
|
FsFile file;
|
|
};
|
|
|
|
HalFile::HalFile() = default;
|
|
|
|
HalFile::HalFile(std::unique_ptr<Impl> impl) : impl(std::move(impl)) {}
|
|
|
|
HalFile::~HalFile() = default;
|
|
|
|
HalFile::HalFile(HalFile&&) = default;
|
|
|
|
HalFile& HalFile::operator=(HalFile&&) = default;
|
|
|
|
HalFile HalStorage::open(const char* path, const oflag_t oflag) {
|
|
StorageLock lock; // ensure thread safety for the duration of this function
|
|
return HalFile(std::make_unique<HalFile::Impl>(SDCard.open(path, oflag)));
|
|
}
|
|
|
|
bool HalStorage::mkdir(const char* path, const bool pFlag) { HAL_STORAGE_WRAPPED_CALL(mkdir, path, pFlag); }
|
|
|
|
bool HalStorage::exists(const char* path) { HAL_STORAGE_WRAPPED_CALL(exists, path); }
|
|
|
|
bool HalStorage::remove(const char* path) { HAL_STORAGE_WRAPPED_CALL(remove, path); }
|
|
bool HalStorage::rename(const char* oldPath, const char* newPath) {
|
|
HAL_STORAGE_WRAPPED_CALL(rename, oldPath, newPath);
|
|
}
|
|
|
|
bool HalStorage::rmdir(const char* path) { HAL_STORAGE_WRAPPED_CALL(rmdir, path); }
|
|
|
|
bool HalStorage::openFileForRead(const char* moduleName, const char* path, HalFile& file) {
|
|
StorageLock lock; // ensure thread safety for the duration of this function
|
|
FsFile fsFile;
|
|
bool ok = SDCard.openFileForRead(moduleName, path, fsFile);
|
|
file = HalFile(std::make_unique<HalFile::Impl>(std::move(fsFile)));
|
|
return ok;
|
|
}
|
|
|
|
bool HalStorage::openFileForRead(const char* moduleName, const std::string& path, HalFile& file) {
|
|
return openFileForRead(moduleName, path.c_str(), file);
|
|
}
|
|
|
|
bool HalStorage::openFileForRead(const char* moduleName, const String& path, HalFile& file) {
|
|
return openFileForRead(moduleName, path.c_str(), file);
|
|
}
|
|
|
|
bool HalStorage::openFileForWrite(const char* moduleName, const char* path, HalFile& file) {
|
|
StorageLock lock; // ensure thread safety for the duration of this function
|
|
FsFile fsFile;
|
|
bool ok = SDCard.openFileForWrite(moduleName, path, fsFile);
|
|
file = HalFile(std::make_unique<HalFile::Impl>(std::move(fsFile)));
|
|
return ok;
|
|
}
|
|
|
|
bool HalStorage::openFileForWrite(const char* moduleName, const std::string& path, HalFile& file) {
|
|
return openFileForWrite(moduleName, path.c_str(), file);
|
|
}
|
|
|
|
bool HalStorage::openFileForWrite(const char* moduleName, const String& path, HalFile& file) {
|
|
return openFileForWrite(moduleName, path.c_str(), file);
|
|
}
|
|
|
|
bool HalStorage::removeDir(const char* path) { HAL_STORAGE_WRAPPED_CALL(removeDir, path); }
|
|
|
|
bool HalStorage::copyFile(const char* moduleName, const std::string& srcPath, const char* dstPath) {
|
|
HalFile src, dst;
|
|
if (!openFileForRead(moduleName, srcPath, src)) return false;
|
|
if (!openFileForWrite(moduleName, dstPath, dst)) {
|
|
src.close();
|
|
return false;
|
|
}
|
|
constexpr size_t BUF_SIZE = 4096;
|
|
auto* buf = new (std::nothrow) uint8_t[BUF_SIZE];
|
|
if (!buf) {
|
|
dst.close();
|
|
src.close();
|
|
return false;
|
|
}
|
|
bool ok = true;
|
|
while (src.available()) {
|
|
const auto bytesRead = src.read(buf, BUF_SIZE);
|
|
if (bytesRead <= 0) break;
|
|
if (dst.write(buf, bytesRead) != static_cast<size_t>(bytesRead)) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
delete[] buf;
|
|
dst.close();
|
|
src.close();
|
|
return ok;
|
|
}
|
|
|
|
// HalFile implementation
|
|
// Allow doing file operations while ensuring thread safety via HalStorage's mutex.
|
|
// Please keep the list below in sync with the HalFile.h header
|
|
|
|
#define HAL_FILE_WRAPPED_CALL(method, ...) \
|
|
HalStorage::StorageLock lock; \
|
|
assert(impl != nullptr); \
|
|
return impl->file.method(__VA_ARGS__);
|
|
|
|
#define HAL_FILE_FORWARD_CALL(method, ...) \
|
|
assert(impl != nullptr); \
|
|
return impl->file.method(__VA_ARGS__);
|
|
|
|
void HalFile::flush() { HAL_FILE_WRAPPED_CALL(flush, ); }
|
|
size_t HalFile::getName(char* name, size_t len) { HAL_FILE_WRAPPED_CALL(getName, name, len); }
|
|
size_t HalFile::size() {
|
|
assert(impl != nullptr);
|
|
return static_cast<size_t>(impl->file.size());
|
|
}
|
|
size_t HalFile::fileSize() {
|
|
assert(impl != nullptr);
|
|
return static_cast<size_t>(impl->file.fileSize());
|
|
}
|
|
bool HalFile::seek(size_t pos) { HAL_FILE_WRAPPED_CALL(seekSet, pos); }
|
|
bool HalFile::seekCur(int64_t offset) { HAL_FILE_WRAPPED_CALL(seekCur, offset); }
|
|
bool HalFile::seekSet(size_t offset) { HAL_FILE_WRAPPED_CALL(seekSet, offset); }
|
|
int HalFile::available() const { HAL_FILE_WRAPPED_CALL(available, ); }
|
|
size_t HalFile::position() const {
|
|
assert(impl != nullptr);
|
|
return static_cast<size_t>(impl->file.position());
|
|
}
|
|
int HalFile::read(void* buf, size_t count) { HAL_FILE_WRAPPED_CALL(read, buf, count); }
|
|
int HalFile::read() { HAL_FILE_WRAPPED_CALL(read, ); }
|
|
size_t HalFile::write(const void* buf, size_t count) { HAL_FILE_WRAPPED_CALL(write, buf, count); }
|
|
size_t HalFile::write(uint8_t b) { HAL_FILE_WRAPPED_CALL(write, b); }
|
|
bool HalFile::rename(const char* newPath) { HAL_FILE_WRAPPED_CALL(rename, newPath); }
|
|
bool HalFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) {
|
|
HAL_FILE_WRAPPED_CALL(getModifyDateTime, pdate, ptime);
|
|
}
|
|
bool HalFile::isDirectory() const { HAL_FILE_FORWARD_CALL(isDirectory, ); } // already thread-safe, no need to wrap
|
|
void HalFile::rewindDirectory() { HAL_FILE_WRAPPED_CALL(rewindDirectory, ); }
|
|
bool HalFile::close() { HAL_FILE_WRAPPED_CALL(close, ); }
|
|
uint64_t HalFile::size64() { HAL_FILE_FORWARD_CALL(size, ); }
|
|
uint64_t HalFile::fileSize64() { HAL_FILE_FORWARD_CALL(fileSize, ); }
|
|
bool HalFile::seek64(uint64_t pos) { HAL_FILE_WRAPPED_CALL(seekSet, pos); }
|
|
bool HalFile::seekSet64(uint64_t offset) { HAL_FILE_WRAPPED_CALL(seekSet, offset); }
|
|
uint64_t HalFile::position64() const { HAL_FILE_FORWARD_CALL(position, ); }
|
|
HalFile HalFile::openNextFile() {
|
|
HalStorage::StorageLock lock;
|
|
assert(impl != nullptr);
|
|
return HalFile(std::make_unique<Impl>(impl->file.openNextFile()));
|
|
}
|
|
bool HalFile::isOpen() const { return impl != nullptr && impl->file.isOpen(); } // already thread-safe, no need to wrap
|
|
HalFile::operator bool() const { return isOpen(); }
|