diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.h b/lib/JpegToBmpConverter/JpegToBmpConverter.h index 9b92bb6d..125692e4 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.h +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.h @@ -1,13 +1,14 @@ #pragma once -class FsFile; +#include + class Print; class ZipFile; class JpegToBmpConverter { static unsigned char jpegReadCallback(unsigned char* pBuf, unsigned char buf_size, unsigned char* pBytes_actually_read, void* pCallback_data); - static bool jpegFileToBmpStreamInternal(class FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight, + static bool jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight, bool oneBit, bool crop = true); public: diff --git a/lib/PngToBmpConverter/PngToBmpConverter.h b/lib/PngToBmpConverter/PngToBmpConverter.h index 73914e59..bf9d3a2c 100644 --- a/lib/PngToBmpConverter/PngToBmpConverter.h +++ b/lib/PngToBmpConverter/PngToBmpConverter.h @@ -1,6 +1,7 @@ #pragma once -class FsFile; +#include + class Print; class PngToBmpConverter { diff --git a/lib/hal/HalStorage.cpp b/lib/hal/HalStorage.cpp index a2ef8a54..24395e4f 100644 --- a/lib/hal/HalStorage.cpp +++ b/lib/hal/HalStorage.cpp @@ -1,67 +1,159 @@ +#define HAL_STORAGE_IMPL #include "HalStorage.h" +#include // need to be included before SdFat.h for compatibility with FS.h's File class +#include #include +#include + #define SDCard SDCardManager::getInstance() HalStorage HalStorage::instance; -HalStorage::HalStorage() {} +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() { return SDCard.begin(); } bool HalStorage::ready() const { return SDCard.ready(); } -std::vector HalStorage::listFiles(const char* path, int maxFiles) { return SDCard.listFiles(path, maxFiles); } +// For the rest of the methods, we acquire the mutex to ensure thread safety -String HalStorage::readFile(const char* path) { return SDCard.readFile(path); } +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 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) { - return SDCard.readFileToStream(path, out, chunkSize); + HAL_STORAGE_WRAPPED_CALL(readFileToStream, path, out, chunkSize); } size_t HalStorage::readFileToBuffer(const char* path, char* buffer, size_t bufferSize, size_t maxBytes) { - return SDCard.readFileToBuffer(path, buffer, bufferSize, maxBytes); + HAL_STORAGE_WRAPPED_CALL(readFileToBuffer, path, buffer, bufferSize, maxBytes); } -bool HalStorage::writeFile(const char* path, const String& content) { return SDCard.writeFile(path, content); } - -bool HalStorage::ensureDirectoryExists(const char* path) { return SDCard.ensureDirectoryExists(path); } - -FsFile HalStorage::open(const char* path, const oflag_t oflag) { return SDCard.open(path, oflag); } - -bool HalStorage::mkdir(const char* path, const bool pFlag) { return SDCard.mkdir(path, pFlag); } - -bool HalStorage::exists(const char* path) { return SDCard.exists(path); } - -bool HalStorage::remove(const char* path) { return SDCard.remove(path); } - -bool HalStorage::rename(const char* oldPath, const char* newPath) { return SDCard.rename(oldPath, newPath); } - -bool HalStorage::rmdir(const char* path) { return SDCard.rmdir(path); } - -bool HalStorage::openFileForRead(const char* moduleName, const char* path, FsFile& file) { - return SDCard.openFileForRead(moduleName, path, file); +bool HalStorage::writeFile(const char* path, const String& content) { + HAL_STORAGE_WRAPPED_CALL(writeFile, path, content); } -bool HalStorage::openFileForRead(const char* moduleName, const std::string& path, FsFile& file) { +bool HalStorage::ensureDirectoryExists(const char* path) { HAL_STORAGE_WRAPPED_CALL(ensureDirectoryExists, path); } + +class HalFile::Impl { + public: + Impl(FsFile&& fsFile) : file(std::move(fsFile)) {} + FsFile file; +}; + +HalFile::HalFile() = default; + +HalFile::HalFile(std::unique_ptr 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(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(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, FsFile& 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, FsFile& file) { - return SDCard.openFileForWrite(moduleName, path, 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(std::move(fsFile))); + return ok; } -bool HalStorage::openFileForWrite(const char* moduleName, const std::string& path, FsFile& file) { +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, FsFile& 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) { return SDCard.removeDir(path); } \ No newline at end of file +bool HalStorage::removeDir(const char* path) { HAL_STORAGE_WRAPPED_CALL(removeDir, path); } + +// 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() { HAL_FILE_FORWARD_CALL(size, ); } // already thread-safe, no need to wrap +size_t HalFile::fileSize() { HAL_FILE_FORWARD_CALL(fileSize, ); } // already thread-safe, no need to wrap +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 { HAL_FILE_WRAPPED_CALL(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::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, ); } +HalFile HalFile::openNextFile() { + HalStorage::StorageLock lock; + assert(impl != nullptr); + return HalFile(std::make_unique(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(); } diff --git a/lib/hal/HalStorage.h b/lib/hal/HalStorage.h index 0a26d29f..d5824bae 100644 --- a/lib/hal/HalStorage.h +++ b/lib/hal/HalStorage.h @@ -1,10 +1,15 @@ #pragma once -#include // need to be included before SdFat.h for compatibility with FS.h's File class -#include +#include +#include // for oflag_t +#include +#include +#include #include +class HalFile; + class HalStorage { public: HalStorage(); @@ -25,31 +30,77 @@ class HalStorage { // Ensure a directory exists, creating it if necessary. Returns true on success. bool ensureDirectoryExists(const char* path); - FsFile open(const char* path, const oflag_t oflag = O_RDONLY); + HalFile open(const char* path, const oflag_t oflag = O_RDONLY); bool mkdir(const char* path, const bool pFlag = true); bool exists(const char* path); bool remove(const char* path); bool rename(const char* oldPath, const char* newPath); bool rmdir(const char* path); - bool openFileForRead(const char* moduleName, const char* path, FsFile& file); - bool openFileForRead(const char* moduleName, const std::string& path, FsFile& file); - bool openFileForRead(const char* moduleName, const String& path, FsFile& file); - bool openFileForWrite(const char* moduleName, const char* path, FsFile& file); - bool openFileForWrite(const char* moduleName, const std::string& path, FsFile& file); - bool openFileForWrite(const char* moduleName, const String& path, FsFile& file); + bool openFileForRead(const char* moduleName, const char* path, HalFile& file); + bool openFileForRead(const char* moduleName, const std::string& path, HalFile& file); + bool openFileForRead(const char* moduleName, const String& path, HalFile& file); + bool openFileForWrite(const char* moduleName, const char* path, HalFile& file); + bool openFileForWrite(const char* moduleName, const std::string& path, HalFile& file); + bool openFileForWrite(const char* moduleName, const String& path, HalFile& file); bool removeDir(const char* path); static HalStorage& getInstance() { return instance; } + class StorageLock; // private class, used internally + private: static HalStorage instance; bool initialized = false; + SemaphoreHandle_t storageMutex = nullptr; }; #define Storage HalStorage::getInstance() +class HalFile : public Print { + friend class HalStorage; + class Impl; + std::unique_ptr impl; + explicit HalFile(std::unique_ptr impl); + + public: + HalFile(); + ~HalFile(); + HalFile(HalFile&&); + HalFile& operator=(HalFile&&); + HalFile(const HalFile&) = delete; + HalFile& operator=(const HalFile&) = delete; + + void flush(); + size_t getName(char* name, size_t len); + size_t size(); + size_t fileSize(); + bool seek(size_t pos); + bool seekCur(int64_t offset); + bool seekSet(size_t offset); + int available() const; + size_t position() const; + int read(void* buf, size_t count); + int read(); // read a single byte + size_t write(const void* buf, size_t count); + size_t write(uint8_t b) override; + bool rename(const char* newPath); + bool isDirectory() const; + void rewindDirectory(); + bool close(); + HalFile openNextFile(); + bool isOpen() const; + operator bool() const; +}; + +// Only do renaming FsFile to HalFile if this header is included by downstream code +// The renaming is to allow using the thread-safe HalFile instead of the raw FsFile, without needing to change the +// downstream code +#ifndef HAL_STORAGE_IMPL +using FsFile = HalFile; +#endif + // Downstream code must use Storage instead of SdMan #ifdef SdMan #undef SdMan diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 996ffb0f..5ba7bde2 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -1,10 +1,9 @@ #pragma once +#include + #include #include -// Forward declarations -class FsFile; - class CrossPointSettings { private: // Private constructor for singleton