#pragma once #include #include #include #include #include #ifdef ARDUINO #ifdef BOARD_ESP32 //#include #include #elif BOARD_NRF52 //#include #include using namespace Adafruit_LittleFS_Namespace; #endif #else #include #include #include #include #include #include #include #endif class FileSystem : public RNS::FileSystemImpl { public: FileSystem() {} bool init() { TRACE("FileSystem initializing..."); #ifdef ARDUINO #ifdef BOARD_ESP32 // Setup FileSystem INFO("SPIFFS mounting FileSystem"); if (!SPIFFS.begin(true, "")){ ERROR("SPIFFS FileSystem mount failed"); return false; } uint32_t size = SPIFFS.totalBytes() / (1024 * 1024); Serial.print("size: "); Serial.print(size); Serial.println(" MB"); uint32_t used = SPIFFS.usedBytes() / (1024 * 1024); Serial.print("used: "); Serial.print(used); Serial.println(" MB"); // ensure FileSystem is writable and format if not RNS::Bytes test("test"); if (write_file("/test", test) < 4) { INFO("SPIFFS FileSystem is being formatted, please wait..."); SPIFFS.format(); } else { remove_file("/test"); } DEBUG("SPIFFS FileSystem is ready"); #elif BOARD_NRF52 // Initialize Internal File System INFO("InternalFS mounting FileSystem"); InternalFS.begin(); INFO("InternalFS FileSystem is ready"); #endif #endif return true; } public: static void listDir(const char* dir) { #ifdef ARDUINO Serial.print("DIR: "); Serial.println(dir); #ifdef BOARD_ESP32 File root = SPIFFS.open(dir); if (!root) { Serial.println("Failed to opend directory"); return; } File file = root.openNextFile(); while (file) { if (file.isDirectory()) { Serial.print(" DIR: "); } else { Serial.print(" FILE: "); } Serial.println(file.name()); file.close(); file = root.openNextFile(); } root.close(); #elif BOARD_NRF52 File root = InternalFS.open(dir); if (!root) { Serial.println("Failed to opend directory"); return; } File file = root.openNextFile(); while (file) { if (file.isDirectory()) { Serial.print(" DIR: "); } else { Serial.print(" FILE: "); } Serial.println(file.name()); file.close(); file = root.openNextFile(); } root.close(); #endif #endif } public: virtual bool file_exists(const char* file_path) { #ifdef ARDUINO #ifdef BOARD_ESP32 File file = SPIFFS.open(file_path, FILE_READ); if (file) { #elif BOARD_NRF52 File file(InternalFS); if (file.open(file_path, FILE_O_READ)) { #else if (false) { #endif #else // Native FILE* file = fopen(file_path, "r"); if (file != nullptr) { #endif //TRACE("file_exists: file exists, closing file"); #ifdef ARDUINO #ifdef BOARD_ESP32 file.close(); #elif BOARD_NRF52 file.close(); #endif #else // Native fclose(file); #endif return true; } else { ERRORF("file_exists: failed to open file %s", file_path); return false; } } virtual size_t read_file(const char* file_path, RNS::Bytes& data) { size_t read = 0; #ifdef ARDUINO #ifdef BOARD_ESP32 File file = SPIFFS.open(file_path, FILE_READ); if (file) { size_t size = file.size(); read = file.readBytes((char*)data.writable(size), size); #elif BOARD_NRF52 //File file(InternalFS); //if (file.open(file_path, FILE_O_READ)) { File file = InternalFS.open(file_path, FILE_O_READ); if (file) { size_t size = file.size(); read = file.readBytes((char*)data.writable(size), size); #else if (false) { size_t size = 0; #endif #else // Native FILE* file = fopen(file_path, "r"); if (file != nullptr) { fseek(file, 0, SEEK_END); size_t size = ftell(file); rewind(file); //size_t read = fread(data.writable(size), size, 1, file); read = fread(data.writable(size), 1, size, file); #endif TRACEF("read_file: read %zu bytes from file %s", read, file_path); if (read != size) { ERRORF("read_file: failed to read file %s", file_path); data.clear(); } //TRACE("read_file: closing input file"); #ifdef ARDUINO #ifdef BOARD_ESP32 file.close(); #elif BOARD_NRF52 file.close(); #endif #else // Native fclose(file); #endif } else { ERRORF("read_file: failed to open input file %s", file_path); } return read; } virtual size_t write_file(const char* file_path, const RNS::Bytes& data) { // CBA TODO Replace remove with working truncation remove_file(file_path); size_t wrote = 0; #ifdef ARDUINO #ifdef BOARD_ESP32 File file = SPIFFS.open(file_path, FILE_WRITE); if (file) { wrote = file.write(data.data(), data.size()); #elif BOARD_NRF52 File file(InternalFS); if (file.open(file_path, FILE_O_WRITE)) { wrote = file.write(data.data(), data.size()); #else if (false) { #endif #else // Native FILE* file = fopen(file_path, "w"); if (file != nullptr) { //size_t wrote = fwrite(data.data(), data.size(), 1, file); wrote = fwrite(data.data(), 1, data.size(), file); #endif TRACEF("write_file: wrote %zu bytes to file %s", wrote, file_path); if (wrote < data.size()) { WARNINGF("write_file: not all data was written to file %s", file_path); } //TRACE("write_file: closing output file"); #ifdef ARDUINO #ifdef BOARD_ESP32 file.close(); #elif BOARD_NRF52 file.close(); #endif #else // Native fclose(file); #endif } else { ERRORF("write_file: failed to open output file %s", file_path); } return wrote; } virtual RNS::FileStream open_file(const char* file_path, RNS::FileStream::MODE file_mode) { TRACEF("open_file: opening file %s", file_path); #ifdef ARDUINO #ifdef BOARD_ESP32 const char* mode; if (file_mode == RNS::FileStream::MODE_READ) { mode = FILE_READ; } else if (file_mode == RNS::FileStream::MODE_WRITE) { mode = FILE_WRITE; } else if (file_mode == RNS::FileStream::MODE_APPEND) { mode = FILE_APPEND; } else { ERRORF("open_file: unsupported mode %d", file_mode); return {RNS::Type::NONE}; } TRACEF("open_file: opening file %s in mode %s", file_path, mode); //// Using copy constructor to create a File* instead of local //File file = SPIFFS.open(file_path, mode); //if (!file) { File* file = new File(SPIFFS.open(file_path, mode)); if (file == nullptr || !(*file)) { if (file != nullptr) delete file; ERRORF("open_file: failed to open output file %s", file_path); return {RNS::Type::NONE}; } TRACEF("open_file: successfully opened file %s", file_path); return RNS::FileStream(new FileStreamImpl(file)); #elif BOARD_NRF52 int mode; if (file_mode == RNS::FileStream::MODE_READ) { mode = FILE_O_READ; } else if (file_mode == RNS::FileStream::MODE_WRITE) { mode = FILE_O_WRITE; // CBA TODO Replace remove with working truncation if (InternalFS.exists(file_path)) { InternalFS.remove(file_path); } } else if (file_mode == RNS::FileStream::MODE_APPEND) { // CBA This is the default write mode for nrf52 littlefs mode = FILE_O_WRITE; } else { ERRORF("open_file: unsupported mode %d", file_mode); return {RNS::Type::NONE}; } //File file = File(InternalFS); File* file = new File(InternalFS); if (file == nullptr || !file->open(file_path, mode)) { if (file != nullptr) delete file; ERRORF("open_file: failed to open output file %s", file_path); return {RNS::Type::NONE}; } // Seek to beginning to overwrite (this is failing on nrf52) //if (file_mode == RNS::FileStream::MODE_WRITE) { // file->seek(0); // file->truncate(0); //} TRACEF("open_file: successfully opened file %s", file_path); return RNS::FileStream(new FileStreamImpl(file)); #else #warning("unsupported"); return RNS::FileStream(RNS::Type::NONE); #endif #else // ARDUINO // Native const char* mode; if (file_mode == RNS::FileStream::MODE_READ) { mode = "r"; } else if (file_mode == RNS::FileStream::MODE_WRITE) { mode = "w"; } else if (file_mode == RNS::FileStream::MODE_APPEND) { mode = "a"; } else { ERRORF("open_file: unsupported mode %d", file_mode); return {RNS::Type::NONE}; } TRACEF("open_file: opening file %s in mode %s", file_path, mode); FILE* file = fopen(file_path, mode); if (file == nullptr) { ERRORF("open_file: failed to open output file %s", file_path); return {RNS::Type::NONE}; } TRACEF("open_file: successfully opened file %s", file_path); return RNS::FileStream(new FileStreamImpl(file)); #endif } virtual bool remove_file(const char* file_path) { #ifdef ARDUINO #ifdef BOARD_ESP32 return SPIFFS.remove(file_path); #elif BOARD_NRF52 return InternalFS.remove(file_path); #else return false; #endif #else // Native return (remove(file_path) == 0); #endif } virtual bool rename_file(const char* from_file_path, const char* to_file_path) { #ifdef ARDUINO #ifdef BOARD_ESP32 return SPIFFS.rename(from_file_path, to_file_path); #elif BOARD_NRF52 return InternalFS.rename(from_file_path, to_file_path); #else return false; #endif #else // Native return (rename(from_file_path, to_file_path) == 0); #endif } /*virtua*/ bool directory_exists(const char* directory_path) { TRACEF("directory_exists: checking for existence of directory %s", directory_path); #ifdef ARDUINO #ifdef BOARD_ESP32 File file = SPIFFS.open(directory_path, FILE_READ); if (file) { bool is_directory = file.isDirectory(); file.close(); return is_directory; } #elif BOARD_NRF52 File file(InternalFS); if (file.open(directory_path, FILE_O_READ)) { bool is_directory = file.isDirectory(); file.close(); return is_directory; } #else if (false) { return false; } #endif else { return false; } #else // Native struct stat st = {0}; return (stat(directory_path, &st) == 0); #endif } virtual bool create_directory(const char* directory_path) { #ifdef ARDUINO #ifdef BOARD_ESP32 if (!SPIFFS.mkdir(directory_path)) { ERRORF("create_directory: failed to create directorty %s", directory_path); return false; } return true; #elif BOARD_NRF52 if (!InternalFS.mkdir(directory_path)) { ERRORF("create_directory: failed to create directorty %s", directory_path); return false; } return true; #else return false; #endif #else // Native struct stat st = {0}; if (stat(directory_path, &st) == 0) { return true; } return (mkdir(directory_path, 0700) == 0); #endif } /*virtua*/ bool remove_directory(const char* directory_path) { TRACEF("remove_directory: removing directory %s", directory_path); #ifdef ARDUINO #ifdef BOARD_ESP32 //if (!LittleFS.rmdir_r(directory_path)) { if (!SPIFFS.rmdir(directory_path)) { ERRORF("remove_directory: failed to remove directorty %s", directory_path); return false; } return true; #elif BOARD_NRF52 if (!InternalFS.rmdir_r(directory_path)) { ERRORF("remove_directory: failed to remove directory %s", directory_path); return false; } return true; #else return false; #endif #else // Native if (rmdir(directory_path) == 0) { ERRORF("remove_directory: failed to remove directory %s", directory_path); return false; } return true; #endif } /*virtua*/ std::list list_directory(const char* directory_path, Callbacks::DirectoryListing callback = nullptr) { TRACEF("list_directory: listing directory %s", directory_path); std::list files; #ifdef ARDUINO #ifdef BOARD_ESP32 File root = SPIFFS.open(directory_path); #elif BOARD_NRF52 File root = InternalFS.open(directory_path); #endif if (!root) { ERRORF("list_directory: failed to open directory %s", directory_path); return files; } File file = root.openNextFile(); while (file) { if (!file.isDirectory()) { char* name = (char*)file.name(); if (callback) callback(name); else files.push_back(name); } // CBA Following close required to avoid leaking memory file.close(); file = root.openNextFile(); } TRACE("list_directory: returning directory listing"); root.close(); return files; #else // Native DIR *dir = opendir(directory_path); if (dir == NULL) { ERRORF("list_directory: failed to open directory %s", directory_path); return files; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } char* name = entry->d_name; if (callback) callback(name); else files.push_back(name); } if (closedir(dir) == -1) { ERRORF("list_directory: failed to close directory %s", directory_path); } return files; #endif } #ifdef ARDUINO #ifdef BOARD_ESP32 virtual size_t storage_size() { return SPIFFS.totalBytes(); } virtual size_t storage_available() { return (SPIFFS.totalBytes() - SPIFFS.usedBytes()); } #elif BOARD_NRF52 static int _countLfsBlock(void *p, lfs_block_t block){ lfs_size_t *size = (lfs_size_t*) p; *size += 1; return 0; } static lfs_ssize_t getUsedBlockCount() { lfs_size_t size = 0; lfs_traverse(InternalFS._getFS(), _countLfsBlock, &size); return size; } static int totalBytes() { const lfs_config* config = InternalFS._getFS()->cfg; return config->block_size * config->block_count; } static int usedBytes() { const lfs_config* config = InternalFS._getFS()->cfg; const int usedBlockCount = getUsedBlockCount(); return config->block_size * usedBlockCount; } virtual size_t storage_size() { //return totalBytes(); return InternalFS.totalBytes(); } virtual size_t storage_available() { //return (totalBytes() - usedBytes()); return (InternalFS.totalBytes() - InternalFS.usedBytes()); } #endif #else virtual size_t storage_size() { return 0; } virtual size_t storage_available() { return 0; } #endif protected: #if defined(BOARD_ESP32) || defined(BOARD_NRF52) class FileStreamImpl : public RNS::FileStreamImpl { private: std::unique_ptr _file; bool _closed = false; public: FileStreamImpl(File* file) : RNS::FileStreamImpl(), _file(file) {} virtual ~FileStreamImpl() { if (!_closed) close(); } public: inline virtual const char* name() { return _file->name(); } inline virtual size_t size() { return _file->size(); } inline virtual void close() { _closed = true; _file->close(); } // Print overrides inline virtual size_t write(uint8_t byte) { return _file->write(byte); } inline virtual size_t write(const uint8_t *buffer, size_t size) { return _file->write(buffer, size); } // Stream overrides inline virtual int available() { return _file->available(); } inline virtual int read() { return _file->read(); } inline virtual int peek() { return _file->peek(); } inline virtual void flush() { _file->flush(); } }; #else class FileStreamImpl : public RNS::FileStreamImpl { private: FILE* _file = nullptr; bool _closed = false; size_t _available = 0; char _filename[1024]; public: FileStreamImpl(FILE* file) : RNS::FileStreamImpl(), _file(file) { _available = size(); } virtual ~FileStreamImpl() { if (!_closed) close(); } public: inline virtual const char* name() { assert(_file); #if 0 char proclnk[1024]; snprintf(proclnk, sizeof(proclnk), "/proc/self/fd/%d", fileno(_file)); int r = readlink(proclnk, _filename, sizeof(_filename)); if (r < 0) { return nullptr); } _filename[r] = '\0'; return _filename; #elif 0 if (fcntl(fd, F_GETPATH, _filename) < 0) { rerturn nullptr; } return _filename; #else return nullptr; #endif } inline virtual size_t size() { assert(_file); struct stat st; fstat(fileno(_file), &st); return st.st_size; } inline virtual void close() { assert(_file); _closed = true; fclose(_file); _file = nullptr; } // Print overrides inline virtual size_t write(uint8_t byte) { assert(_file); int ch = fputc(byte, _file); if (ch == EOF) { return 0; } ++_available; return 1; } inline virtual size_t write(const uint8_t *buffer, size_t size) { assert(_file); size_t wrote = fwrite(buffer, sizeof(uint8_t), size, _file); _available += wrote; return wrote; } // Stream overrides inline virtual int available() { #if 0 assert(_file); int size = 0; ioctl(fileno(_file), FIONREAD, &size); TRACEF("FileStream::available: %d", size); return size; #else return _available; #endif } inline virtual int read() { if (_available <= 0) { return EOF; } assert(_file); int ch = fgetc(_file); if (ch == EOF) { return ch; } --_available; //TRACEF("FileStream::read: %c", ch); return ch; } inline virtual int peek() { if (_available <= 0) { return EOF; } assert(_file); int ch = fgetc(_file); ungetc(ch, _file); TRACEF("FileStream::peek: %c", ch); return ch; } inline virtual void flush() { assert(_file); fflush(_file); TRACE("FileStream::flush"); } }; #endif };