text: /**
   * The MB_FS, file wrapper class v1.0.1.
   *
   * This wrapper class is for SD and Flash file interfaces which support SdFat in ESP32 (//https://github.com/greiman/SdFat)
   *
   *  Created February 1, 2022
   *
   * The MIT License (MIT)
   * Copyright (c) 2022 K. Suwatchai (Mobizt)
   *
   *
   * Permission is hereby granted, free of charge, to any person returning a copy of
   * this software and associated documentation files (the "Software"), to deal in
   * the Software without restriction, including without limitation the rights to
   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
   * the Software, and to permit persons to whom the Software is furnished to do so,
   * subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in all
   * copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
   * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
   * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   */
  
  #ifndef MBFS_CLASS_H
  #define MBFS_CLASS_H
  
  #define FS_NO_GLOBALS
  #if defined(ESP32) || defined(ESP8266)
  #include <FS.h>
  #endif
  #include "MB_FS_Interfaces.h"
  #include "./extras/fastcrc/FastCRC.h"
  #include "MB_String.h"
  
  using namespace mb_string;
  
  #if defined(BOARD_HAS_PSRAM) && defined(MB_STRING_USE_PSRAM)
  #include <esp32-hal-psram.h>
  #endif
  
  #define MB_FS_ERROR_FILE_IO_ERROR -300
  #define MB_FS_ERROR_FILE_NOT_FOUND -301
  #define MB_FS_ERROR_FLASH_STORAGE_IS_NOT_READY -302
  #define MB_FS_ERROR_SD_STORAGE_IS_NOT_READY -303
  #define MB_FS_ERROR_FILE_STILL_OPENED -304
  
  typedef enum
  {
      mb_fs_mem_storage_type_undefined,
      mb_fs_mem_storage_type_flash,
      mb_fs_mem_storage_type_sd
  } mb_fs_mem_storage_type;
  
  typedef enum
  {
      mb_fs_open_mode_undefined = -1,
      mb_fs_open_mode_read = 0,
      mb_fs_open_mode_write,
      mb_fs_open_mode_append
  } mb_fs_open_mode;
  
  #define mbfs_file_type mb_fs_mem_storage_type
  #define mbfs_flash mb_fs_mem_storage_type_flash
  #define mbfs_sd mb_fs_mem_storage_type_sd
  #define mbfs_undefined mb_fs_mem_storage_type_undefined
  
  #define mbfs_type (mbfs_file_type)
  
  #if defined(ARDUINO_ARCH_SAMD) || defined(__AVR_ATmega4809__) || defined(ARDUINO_NANO_RP2040_CONNECT)
  #if !defined(MBFS_SDFAT_ENABLED)
  struct mbfs_sd_config_info_t
  {
      int ss = -1;
  };
  #endif
  #elif defined(ESP32) || defined(MBFS_SDFAT_ENABLED)
  
  #if defined(ESP32)
  struct mbfs_sd_mmc_config_info_t
  {
      const char *mountpoint = "";
      bool mode1bit = false;
      bool format_if_mount_failed = false;
  };
  #endif
  
  struct mbfs_sd_config_info_t
  {
      int ss = -1;
      int sck = -1;
      int miso = -1;
      int mosi = -1;
  
  #if defined(MBFS_ESP32_SDFAT_ENABLED) || defined(MBFS_SDFAT_ENABLED)
      SdSpiConfig *sdFatSPIConfig = nullptr;
  #endif
  
  #if defined(ESP32)
  
  #if defined(MBFS_SD_FS)
      SPIClass *spiConfig = nullptr;
  #endif
      mbfs_sd_mmc_config_info_t sdMMCConfig;
  #endif
  
  #if defined(MBFS_SDFAT_ENABLED)
      SPIClass *spiConfig = nullptr;
  #endif
  };
  
  #elif defined(ESP8266)
  struct mbfs_sd_config_info_t
  {
      int ss = -1;
  #if defined(MBFS_SD_FS)
      SDFSConfig *sdFSConfig = nullptr;
  #endif
  };
  #else
  struct mbfs_sd_config_info_t
  {
      int ss = -1;
  };
  #endif
  
  class MB_FS
  {
  
  public:
      MB_FS() {}
      ~MB_FS() {}
  
      struct mbfs_sd_config_info_t sd_config;
  
      // Assign the SD card interfaces with GPIO pins.
      bool sdBegin(int ss = -1, int sck = -1, int miso = -1, int mosi = -1)
      {
          if (sd_rdy)
              return true;
  
  #if defined(MBFS_SD_FS) && defined(MBFS_CARD_TYPE_SD)
          sd_config.ss = ss;
  #if defined(ESP32)
          sd_config.sck = sck;
          sd_config.miso = miso;
          sd_config.mosi = mosi;
          SPI.begin(sck, miso, mosi, ss);
          return sdSPIBegin(ss, &SPI);
  #elif defined(ESP8266) || defined(ARDUINO_ARCH_SAMD) || defined(__AVR_ATmega4809__) || defined(ARDUINO_NANO_RP2040_CONNECT)
          sd_rdy = MBFS_SD_FS.begin(ss);
          return sd_rdy;
  #endif
  
  #endif
          return false;
      }
  
  #if defined(ESP32) && defined(MBFS_SD_FS) && defined(MBFS_CARD_TYPE_SD)
  
      // Assign the SD card interfaces with SPIClass object pointer (ESP32 only).
      bool sdSPIBegin(int ss, SPIClass *spiConfig)
      {
  
          if (sd_rdy)
              return true;
  
  #if defined(ESP32)
  
          sd_config.ss = ss;
  
          if (spiConfig)
              sd_config.spiConfig = spiConfig;
          else
              sd_config.spiConfig = &SPI;
  
  #if !defined(MBFS_ESP32_SDFAT_ENABLED) || defined(MBFS_SDFAT_ENABLED)
          if (ss > -1)
              sd_rdy = MBFS_SD_FS.begin(ss, *sd_config.spiConfig);
          else
              sd_rdy = MBFS_SD_FS.begin();
  #endif
  
  #elif defined(ESP8266)
  
          cfg->_int.sd_config.sck = sck;
  
          if (ss > -1)
              sd_rdy = MBFS_SD_FS.begin(ss);
          else
              sd_rdy = MBFS_SD_FS.begin(SD_CS_PIN);
  #endif
  
          return sd_rdy;
      }
  
  #endif
  
  #if defined(MBFS_ESP32_SDFAT_ENABLED) || defined(MBFS_SDFAT_ENABLED)
  
      // Assign the SD card interfaces with SdSpiConfig object pointer and SPI pins assignment (ESP32 only).
      bool sdFatBegin(SdSpiConfig *sdFatSPIConfig, int ss, int sck, int miso, int mosi)
      {
  
          if (sd_rdy)
              return true;
  
          if (sdFatSPIConfig)
          {
              sd_config.sdFatSPIConfig = sdFatSPIConfig;
              sd_config.spiConfig = &SPI;
              sd_config.ss = ss;
  
  #if defined(ESP32)
              if (ss > -1)
                  sd_config.spiConfig->begin(sck, miso, mosi, ss);
  #endif
  
              sd_rdy = MBFS_SD_FS.begin(*sd_config.sdFatSPIConfig);
              return sd_rdy;
          }
  
          return false;
      }
  #endif
  
  #if defined(ESP8266) && defined(MBFS_SD_FS)
      // Assign the SD card interfaces with SDFSConfig object pointer (ESP8266 only).
      bool sdFatBegin(SDFSConfig *sdFSConfig)
      {
  
          if (sd_rdy)
              return true;
  
          if (sdFSConfig)
          {
              sd_config.sdFSConfig = sdFSConfig;
              SDFS.setConfig(*sd_config.sdFSConfig);
              sd_rdy = SDFS.begin();
              return sd_rdy;
          }
  
          return false;
      }
  #endif
  
      // Assign the SD_MMC card interfaces (ESP32 only).
      bool sdMMCBegin(const char *mountpoint, bool mode1bit, bool format_if_mount_failed)
      {
  
          if (sd_rdy)
              return true;
  
  #if defined(ESP32)
  #if defined(MBFS_CARD_TYPE_SD_MMC)
  
          sd_config.sdMMCConfig.mountpoint = mountpoint;
          sd_config.sdMMCConfig.mode1bit = mode1bit;
          sd_config.sdMMCConfig.format_if_mount_failed = format_if_mount_failed;
  
          sd_rdy = MBFS_SD_FS.begin(mountpoint, mode1bit, format_if_mount_failed);
          return sd_rdy;
  #endif
  #endif
          return false;
      }
  
      // Check the mounting status of Flash storage.
      bool flashReady()
      {
  #if defined MBFS_FLASH_FS
  
          if (flash_rdy)
              return true;
  
  #if defined(ESP32)
  
  #if defined(MBFS_FORMAT_FLASH)
          flash_rdy = MBFS_FLASH_FS.begin(true);
  #else
          flash_rdy = MBFS_FLASH_FS.begin();
  #endif
  
  #elif defined(ESP8266)
          flash_rdy = MBFS_FLASH_FS.begin();
  #endif
  
  #endif
  
          return flash_rdy;
      }
  
      // Check the mounting status of SD storage.
      bool sdReady()
      {
  
  #if defined(MBFS_SD_FS)
  
          if (sd_rdy)
              return true;
  
  #if defined(ESP32)
  
  #if defined(MBFS_CARD_TYPE_SD)
  
          if (!sd_config.spiConfig)
          {
              if (sd_config.ss > -1)
                  SPI.begin(sd_config.sck, sd_config.miso, sd_config.mosi, sd_config.ss);
              sd_config.spiConfig = &SPI;
          }
  
  #if defined(MBFS_ESP32_SDFAT_ENABLED) || defined(MBFS_SDFAT_ENABLED)
  
          if (sd_config.sdFatSPIConfig && !sd_rdy)
              sd_rdy = MBFS_SD_FS.begin(*sd_config.sdFatSPIConfig);
  #else
          if (!sd_rdy)
              sd_rdy = sdSPIBegin(sd_config.ss, sd_config.spiConfig);
  
  #endif
  
  #elif defined(MBFS_CARD_TYPE_SD_MMC)
          if (!sd_rdy)
              sd_rdy = sdMMCBegin(sd_config.sd_mmc_mountpoint, sd_config.sd_mmc_mode1bit, sd_config.sd_mmc_format_if_mount_failed);
  #endif
  
  #elif defined(ESP8266)
          if (!sd_rdy)
          {
              if (sd_config.sdFSConfig)
                  sd_rdy = sdFatBegin(sd_config.sdFSConfig);
              else
                  sd_rdy = sdBegin(sd_config.ss);
          }
  
  #elif defined(ARDUINO_ARCH_SAMD) || defined(__AVR_ATmega4809__) || defined(ARDUINO_NANO_RP2040_CONNECT)
          if (!sd_rdy)
              sd_rdy = sdBegin(sd_config.ss);
  #endif
  
  #endif
  
          return sd_rdy;
      }
  
      // Check the mounting status of Flash or SD storage with mb_fs_mem_storage_type.
      bool checkStorageReady(mbfs_file_type type)
      {
  
  #if defined(MBFS_USE_FILE_STORAGE)
          if (type == mbfs_flash)
          {
              if (!flash_rdy)
                  flashReady();
              return flash_rdy;
          }
          else if (type == mbfs_sd)
          {
              if (!sd_rdy)
                  sdReady();
              return sd_rdy;
          }
  #endif
  
          return false;
      }
  
      // Open file for read or write with file name, mb_fs_mem_storage_type and mb_fs_open_mode.
      // return size of file (read) or 0 (write) or negative value for error
      int open(const MB_String &filename, mbfs_file_type type, mb_fs_open_mode mode)
      {
  
  #if defined(MBFS_USE_FILE_STORAGE)
  
          if (!checkStorageReady(type))
          {
              if (type == mbfs_flash)
                  return MB_FS_ERROR_FLASH_STORAGE_IS_NOT_READY;
              else if (type == mbfs_sd)
                  return MB_FS_ERROR_SD_STORAGE_IS_NOT_READY;
              else
                  return MB_FS_ERROR_FILE_IO_ERROR;
          }
  
          if (mode == mb_fs_open_mode_read)
          {
              if (!existed(filename.c_str(), type))
                  return MB_FS_ERROR_FILE_NOT_FOUND;
          }
  
          int ret = openFile(filename, type, mode);
  
          if (ret < 0)
              return ret;
  
          if (ready(type))
              return ret;
  
  #endif
          return MB_FS_ERROR_FILE_IO_ERROR;
      }
  
      // Check if file is already open.
      bool ready(mbfs_file_type type)
      {
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              return true;
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
              return true;
  #endif
          return false;
      }
  
      // Get file for read/write with file name, mb_fs_mem_storage_type and mb_fs_open_mode.
      int size(mbfs_file_type type)
      {
          int size = 0;
  
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              size = mb_flashFs.size();
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
              size = mb_sdFs.size();
  #endif
          return size;
      }
  
      // Check if file is ready to read/write.
      int available(mbfs_file_type type)
      {
          int available = 0;
  
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              available = mb_flashFs.available();
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
              available = mb_sdFs.available();
  #endif
          return available;
      }
  
      // Read byte array. Return the number of bytes that completed read or negative value for error.
      int read(mbfs_file_type type, uint8_t *buf, size_t len)
      {
          int read = 0;
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              read = mb_flashFs.read(buf, len);
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
              read = mb_sdFs.read(buf, len);
  #endif
          return read;
      }
  
      // Print char array. Return the number of bytes that completed write or negative value for error.
      int print(mbfs_file_type type, const char *str)
      {
          int write = 0;
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              write = mb_flashFs.print(str);
  #endif
  #if defined(MBFS_SD_FS)
  
          if (type == mbfs_sd && mb_sdFs)
              write = mb_sdFs.print(str);
  #endif
          return write;
      }
  
      // Print char array with new line. Return the number of bytes that completed write or negative value for error.
      int println(mbfs_file_type type, const char *str)
      {
          int write = print(type, str);
          if (write == (int)strlen(str))
              write += print(type, (const char *)MBSTRING_FLASH_MCR("\n"));
          return write;
      }
  
      // Print integer. Return the number of bytes that completed write or negative value for error.
      int print(mbfs_file_type type, int v)
      {
          int write = 0;
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              write = mb_flashFs.print(v);
  #endif
  #if defined(MBFS_SD_FS)
  
          if (type == mbfs_sd && mb_sdFs)
              write = mb_sdFs.print(v);
  #endif
          return write;
      }
  
      // Print integer with newline. Return the number of bytes that completed write or negative value for error.
      int println(mbfs_file_type type, int v)
      {
          int write = print(type, v);
          if (write > 0)
              write += print(type, (const char *)MBSTRING_FLASH_MCR("\n"));
          return write;
      }
  
      int print(mbfs_file_type type, unsigned int v)
      {
          int write = 0;
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              write = mb_flashFs.print(v);
  #endif
  #if defined(MBFS_SD_FS)
  
          if (type == mbfs_sd && mb_sdFs)
              write = mb_sdFs.print(v);
  #endif
          return write;
      }
  
      // Print integer with newline. Return the number of bytes that completed write or negative value for error.
      int println(mbfs_file_type type, unsigned int v)
      {
          int write = print(type, v);
          if (write > 0)
              write += print(type, (const char *)MBSTRING_FLASH_MCR("\n"));
          return write;
      }
  
      // Write byte array. Return the number of bytes that completed write or negative value for error.
      int write(mbfs_file_type type, uint8_t *buf, size_t len)
      {
          int write = 0;
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              write = mb_flashFs.write(buf, len);
  #endif
  #if defined(MBFS_SD_FS)
  
          if (type == mbfs_sd && mb_sdFs)
              write = mb_sdFs.write(buf, len);
  #endif
          return write;
      }
  
      // Close file.
      void close(mbfs_file_type type)
      {
  
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
          {
              mb_flashFs.close();
              flash_filename_crc = 0;
              flash_opened = false;
              flash_open_mode = mb_fs_open_mode_undefined;
          }
  #endif
  
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
          {
              mb_sdFs.close();
              sd_filename_crc = 0;
              sd_opened = false;
              sd_open_mode = mb_fs_open_mode_undefined;
          }
  #endif
      }
  
      // Check file existence.
      bool existed(const MB_String &filename, mbfs_file_type type)
      {
  
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash)
              return MBFS_FLASH_FS.exists(filename.c_str());
  #endif
  
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd)
          {
  #if defined(MBFS_ESP32_SDFAT_ENABLED) || defined(MBFS_SDFAT_ENABLED)
              MBFS_SD_FILE file;
              bool ret = file.open(filename.c_str(), O_RDONLY);
              file.close();
              return ret;
  #else
              return MBFS_SD_FS.exists(filename.c_str());
  #endif
          }
  #endif
  
          return false;
      }
  
      // Seek to position in file.
      void seek(mbfs_file_type type, int pos)
      {
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              mb_flashFs.seek(pos);
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
              mb_sdFs.seek(pos);
  #endif
      }
  
      // Read byte. Return the 1 for completed read or negative value for error.
      int read(mbfs_file_type type)
      {
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              return mb_flashFs.read();
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
              return mb_sdFs.read();
  #endif
          return -1;
      }
  
      // Write byte. Return the 1 for completed write or negative value for error.
      int write(mbfs_file_type type, uint8_t v)
      {
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              return mb_flashFs.write(v);
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
              return mb_sdFs.write(v);
  #endif
          return -1;
      }
  
      bool remove(const MB_String &filename, mbfs_file_type type)
      {
          if (!existed(filename, type))
              return true;
  
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash)
              return MBFS_FLASH_FS.remove(filename.c_str());
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd)
          {
  #if defined(MBFS_ESP32_SDFAT_ENABLED) || defined(MBFS_SDFAT_ENABLED)
              if (mb_sdFs.open(filename.c_str(), O_RDWR | O_CREAT | O_APPEND))
              {
                  mb_sdFs.remove();
                  mb_sdFs.close();
                  return true;
              }
  #else
              return MBFS_SD_FS.remove(filename.c_str());
  #endif
          }
  
  #endif
          return false;
      }
  
  // Get the Flash file instance.
  #if defined(MBFS_FLASH_FS)
      fs::File &getFlashFile()
      {
          return mb_flashFs;
      }
  #endif
  
  // Get the SD file instance.
  #if defined(MBFS_SD_FS)
      MBFS_SD_FILE &getSDFile()
      {
          return mb_sdFs;
      }
  #endif
  
      // Get name of opened file.
      const char *name(mbfs_file_type type)
      {
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash && mb_flashFs)
              return flash_file.c_str();
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd && mb_sdFs)
              return sd_file.c_str();
  #endif
  
          return "";
      }
  
      // Calculate CRC16 of byte array.
      uint16_t calCRC(const char *buf)
      {
          return CRC16.ccitt((uint8_t *)buf, strlen(buf));
      }
  
      // Free reserved memory at pointer.
      void delP(void *ptr)
      {
          void **p = (void **)ptr;
          if (*p)
          {
              free(*p);
              *p = 0;
          }
      }
  
      // Allocate memory
      void *newP(size_t len)
      {
          void *p;
          size_t newLen = getReservedLen(len);
  #if defined(BOARD_HAS_PSRAM) && defined(MB_STRING_USE_PSRAM)
  
          if (ESP.getPsramSize() > 0)
              p = (void *)ps_malloc(newLen);
          else
              p = (void *)malloc(newLen);
  
          if (!p)
              return NULL;
  
  #else
  
  #if defined(ESP8266_USE_EXTERNAL_HEAP)
          ESP.setExternalHeap();
  #endif
  
          p = (void *)malloc(newLen);
          bool nn = p ? true : false;
  
  #if defined(ESP8266_USE_EXTERNAL_HEAP)
          ESP.resetHeap();
  #endif
  
          if (!nn)
              return NULL;
  
  #endif
          memset(p, 0, newLen);
          return p;
      }
  
      size_t getReservedLen(size_t len)
      {
          int blen = len + 1;
  
          int newlen = (blen / 4) * 4;
  
          if (newlen < blen)
              newlen += 4;
  
          return (size_t)newlen;
      }
  
      void createDirs(MB_String dirs)
      {
          if (!longNameSupported())
              return;
  
  #if defined(MBFS_SD_FS)
          MB_String dir;
          int count = 0;
          for (size_t i = 0; i < dirs.length(); i++)
          {
              dir.append(1, dirs[i]);
              count++;
              if (dirs[i] == '/' && i > 0)
              {
                  if (dir.length() > 0)
                      MBFS_SD_FS.mkdir(dir.substr(0, dir.length() - 1).c_str());
                  count = 0;
              }
          }
  
          if (count > 0)
              MBFS_SD_FS.mkdir(dir.c_str());
          dir.clear();
  #endif
      }
  
      bool longNameSupported()
      {
  
  #if defined(MBFS_SDFAT_ENABLED) || defined(MBFS_FLASH_FS)
          return true;
  #endif
  
  #if defined(MBFS_SD_FS) && (defined(ESP32) || defined(ESP8266))
          return true;
  #endif
  
          return false;
      }
  
  private:
      FastCRC16 CRC16;
      uint16_t flash_filename_crc = 0;
      uint16_t sd_filename_crc = 0;
      MB_String flash_file, sd_file;
      mb_fs_open_mode flash_open_mode = mb_fs_open_mode_undefined;
      mb_fs_open_mode sd_open_mode = mb_fs_open_mode_undefined;
      bool flash_opened = false;
      bool sd_opened = false;
      bool sd_rdy = false;
      bool flash_rdy = false;
  
  #if defined(MBFS_FLASH_FS)
      fs::File mb_flashFs;
  #endif
  #if defined(MBFS_SD_FS)
      MBFS_SD_FILE mb_sdFs;
  #endif
  
      int openFile(const MB_String &filename, mb_fs_mem_storage_type type, mb_fs_open_mode mode)
      {
  
  #if defined(MBFS_FLASH_FS)
          if (type == mbfs_flash)
              return openFlashFile(filename, mode);
  #endif
  #if defined(MBFS_SD_FS)
          if (type == mbfs_sd)
              return openSDFile(filename, mode);
  #endif
          return MB_FS_ERROR_FILE_IO_ERROR;
      }
  
      int openSDFile(const MB_String &filename, mb_fs_open_mode mode)
      {
          int ret = MB_FS_ERROR_FILE_IO_ERROR;
  
  #if defined(MBFS_SD_FS)
  
          if (mode == mb_fs_open_mode_read || mode == mb_fs_open_mode_write)
          {
              uint16_t crc = calCRC(filename.c_str());
  
              if (mode == sd_open_mode && flash_filename_crc == crc && sd_opened) // same sd file opened, leave it
                  return MB_FS_ERROR_FILE_STILL_OPENED;
  
              if (sd_opened)
                  close(mbfs_sd); // sd file opened, close it
  
              flash_filename_crc = crc;
          }
  
  #if defined(MBFS_ESP32_SDFAT_ENABLED) || defined(MBFS_SDFAT_ENABLED)
  
          if (mode == mb_fs_open_mode_read)
          {
              if (mb_sdFs.open(filename.c_str(), O_RDONLY))
              {
                  sd_file = filename;
                  sd_opened = true;
                  sd_open_mode = mode;
                  ret = mb_sdFs.size();
              }
          }
          else if (mode == mb_fs_open_mode_write)
          {
              remove(filename, mb_fs_mem_storage_type_sd);
              if (mb_sdFs.open(filename.c_str(), O_RDWR | O_CREAT | O_APPEND))
              {
                  sd_file = filename;
                  sd_opened = true;
                  sd_open_mode = mode;
                  ret = 0;
              }
          }
  
  #else
  
          if (mode == mb_fs_open_mode_read)
          {
              mb_sdFs = MBFS_SD_FS.open(filename.c_str(), FILE_READ);
              if (mb_sdFs)
              {
                  sd_file = filename;
                  sd_opened = true;
                  sd_open_mode = mode;
                  ret = mb_sdFs.size();
              }
          }
          else if (mode == mb_fs_open_mode_write)
          {
              remove(filename, mb_fs_mem_storage_type_sd);
              mb_sdFs = MBFS_SD_FS.open(filename.c_str(), FILE_WRITE);
              if (mb_sdFs)
              {
                  sd_file = filename;
                  sd_opened = true;
                  sd_open_mode = mode;
                  ret = 0;
              }
          }
  #endif
  
  #endif
          return ret;
      }
  
      int openFlashFile(const MB_String &filename, mb_fs_open_mode mode)
      {
          int ret = MB_FS_ERROR_FILE_IO_ERROR;
  
  #if defined(MBFS_FLASH_FS)
  
          if (mode == mb_fs_open_mode_read || mode == mb_fs_open_mode_write)
          {
              uint16_t crc = calCRC(filename.c_str());
              if (mode == flash_open_mode && sd_filename_crc == crc && flash_opened) // same flash file opened, leave it
                  return MB_FS_ERROR_FILE_STILL_OPENED;
  
              if (flash_opened)
                  close(mbfs_flash); // flash file opened, close it
  
              sd_filename_crc = crc;
          }
  
          if (mode == mb_fs_open_mode_read)
          {
              mb_flashFs = MBFS_FLASH_FS.open(filename.c_str(), "r");
              if (mb_flashFs)
              {
                  flash_file = filename;
                  flash_opened = true;
                  flash_open_mode = mode;
                  ret = mb_flashFs.size();
              }
          }
          else if (mode == mb_fs_open_mode_write)
          {
              remove(filename, mb_fs_mem_storage_type_flash);
              mb_flashFs = MBFS_FLASH_FS.open(filename.c_str(), "w");
              if (mb_flashFs)
              {
                  flash_file = filename;
                  flash_opened = true;
                  flash_open_mode = mode;
                  ret = 0;
              }
          }
  
  #endif
          return ret;
      }
  };
  
  #endif
  