text: /**
   * @file lv_fs.c
   *
   */
  
  /*********************
   *      INCLUDES
   *********************/
  #include "lv_fs.h"
  #if LV_USE_FILESYSTEM
  
  #include "../lv_misc/lv_debug.h"
  #include "lv_ll.h"
  #include <string.h>
  #include "lv_gc.h"
  
  /*********************
   *      DEFINES
   *********************/
  
  /* "free" is used as a function pointer (in lv_fs_drv_t).
   * We must make sure "free" was not defined to a platform specific
   * free function, otherwise compilation would fail.
   */
  #ifdef free
  #undef free
  #endif
  
  /**********************
   *      TYPEDEFS
   **********************/
  
  /**********************
   *  STATIC PROTOTYPES
   **********************/
  static const char *lv_fs_get_real_path(const char *path);
  
  /**********************
   *  STATIC VARIABLES
   **********************/
  
  /**********************
   *      MACROS
   **********************/
  
  /**********************
   *   GLOBAL FUNCTIONS
   **********************/
  
  /**
   * Initialize the File system interface
   */
  void _lv_fs_init(void)
  {
      _lv_ll_init(&LV_GC_ROOT(_lv_drv_ll), sizeof(lv_fs_drv_t));
  }
  
  /**
   * Test if a drive is ready or not. If the `ready` function was not initialized `true` will be
   * returned.
   * @param letter letter of the drive
   * @return true: drive is ready; false: drive is not ready
   */
  bool lv_fs_is_ready(char letter)
  {
      lv_fs_drv_t *drv = lv_fs_get_drv(letter);
  
      if (drv == NULL) {
          return false; /*An unknown driver in not ready*/
      }
  
      if (drv->ready_cb == NULL) {
          return true; /*Assume the driver is always ready if no handler provided*/
      }
  
      return drv->ready_cb(drv);
  }
  
  /**
   * Open a file
   * @param file_p pointer to a lv_fs_file_t variable
   * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt)
   * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_open(lv_fs_file_t *file_p, const char *path, lv_fs_mode_t mode)
  {
      file_p->drv = NULL;
      file_p->file_d = NULL;
  
      if (path == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      char letter = path[0];
  
      file_p->drv = lv_fs_get_drv(letter);
  
      if (file_p->drv == NULL) {
          return LV_FS_RES_NOT_EX;
      }
  
      if (file_p->drv->ready_cb != NULL) {
          if (file_p->drv->ready_cb(file_p->drv) == false) {
              file_p->drv = NULL;
              return LV_FS_RES_HW_ERR;
          }
      }
  
      if (file_p->drv->open_cb == NULL) {
          file_p->drv = NULL;
          return LV_FS_RES_NOT_IMP;
      }
  
      const char *real_path = lv_fs_get_real_path(path);
  
      if (file_p->drv->file_size == 0) /*Is file_d zero size?*/
      {
          /*Pass file_d's address to open_cb, so the implementor can allocate memory byself*/
          return file_p->drv->open_cb(file_p->drv, &file_p->file_d, real_path, mode);
      }
  
      file_p->file_d = lv_mem_alloc(file_p->drv->file_size);
      LV_ASSERT_MEM(file_p->file_d);
  
      if (file_p->file_d == NULL) {
          file_p->drv = NULL;
          return LV_FS_RES_OUT_OF_MEM; /* Out of memory */
      }
  
      lv_fs_res_t res = file_p->drv->open_cb(file_p->drv, file_p->file_d, real_path, mode);
  
      if (res != LV_FS_RES_OK) {
          lv_mem_free(file_p->file_d);
          file_p->file_d = NULL;
          file_p->drv = NULL;
      }
  
      return res;
  }
  
  /**
   * Close an already opened file
   * @param file_p pointer to a lv_fs_file_t variable
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_close(lv_fs_file_t *file_p)
  {
      if (file_p->drv == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      if (file_p->drv->close_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      lv_fs_res_t res = file_p->drv->close_cb(file_p->drv, file_p->file_d);
  
      lv_mem_free(file_p->file_d); /*Clean up*/
      file_p->file_d = NULL;
      file_p->drv = NULL;
  
      return res;
  }
  
  /**
   * Delete a file
   * @param path path of the file to delete
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_remove(const char *path)
  {
      if (path == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      char letter = path[0];
  
      lv_fs_drv_t *drv = lv_fs_get_drv(letter);
  
      if (drv == NULL) {
          return LV_FS_RES_NOT_EX;
      }
  
      if (drv->ready_cb != NULL) {
          if (drv->ready_cb(drv) == false) {
              return LV_FS_RES_HW_ERR;
          }
      }
  
      if (drv->remove_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      const char *real_path = lv_fs_get_real_path(path);
      lv_fs_res_t res = drv->remove_cb(drv, real_path);
  
      return res;
  }
  
  /**
   * Read from a file
   * @param file_p pointer to a lv_fs_file_t variable
   * @param buf pointer to a buffer where the read bytes are stored
   * @param btr Bytes To Read
   * @param br the number of real read bytes (Bytes Read). NULL if unused.
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_read(lv_fs_file_t *file_p, void *buf, uint32_t btr, uint32_t *br)
  {
      if (br != NULL) {
          *br = 0;
      }
  
      if (file_p->drv == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      if (file_p->drv->read_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      uint32_t br_tmp = 0;
      lv_fs_res_t res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buf, btr, &br_tmp);
  
      if (br != NULL) {
          *br = br_tmp;
      }
  
      return res;
  }
  
  /**
   * Write into a file
   * @param file_p pointer to a lv_fs_file_t variable
   * @param buf pointer to a buffer with the bytes to write
   * @param btr Bytes To Write
   * @param br the number of real written bytes (Bytes Written). NULL if unused.
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_write(lv_fs_file_t *file_p, const void *buf, uint32_t btw, uint32_t *bw)
  {
      if (bw != NULL) {
          *bw = 0;
      }
  
      if (file_p->drv == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      if (file_p->drv->write_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      uint32_t bw_tmp = 0;
      lv_fs_res_t res = file_p->drv->write_cb(file_p->drv, file_p->file_d, buf, btw, &bw_tmp);
  
      if (bw != NULL) {
          *bw = bw_tmp;
      }
  
      return res;
  }
  
  /**
   * Set the position of the 'cursor' (read write pointer) in a file
   * @param file_p pointer to a lv_fs_file_t variable
   * @param pos the new position expressed in bytes index (0: start of file)
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_seek(lv_fs_file_t *file_p, uint32_t pos)
  {
      if (file_p->drv == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      if (file_p->drv->seek_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      lv_fs_res_t res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos);
  
      return res;
  }
  
  /**
   * Give the position of the read write pointer
   * @param file_p pointer to a lv_fs_file_t variable
   * @param pos_p pointer to store the position of the read write pointer
   * @return LV_FS_RES_OK or any error from 'fs_res_t'
   */
  lv_fs_res_t lv_fs_tell(lv_fs_file_t *file_p, uint32_t *pos)
  {
      if (file_p->drv == NULL) {
          *pos = 0;
          return LV_FS_RES_INV_PARAM;
      }
  
      if (file_p->drv->tell_cb == NULL) {
          *pos = 0;
          return LV_FS_RES_NOT_IMP;
      }
  
      lv_fs_res_t res = file_p->drv->tell_cb(file_p->drv, file_p->file_d, pos);
  
      return res;
  }
  
  /**
   * Truncate the file size to the current position of the read write pointer
   * @param file_p pointer to an 'ufs_file_t' variable. (opened with lv_fs_open )
   * @return LV_FS_RES_OK: no error, the file is read
   *         any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_trunc(lv_fs_file_t *file_p)
  {
      if (file_p->drv == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      if (file_p->drv->trunc_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      lv_fs_res_t res = file_p->drv->trunc_cb(file_p->drv, file_p->file_d);
  
      return res;
  }
  /**
   * Give the size of a file bytes
   * @param file_p pointer to a lv_fs_file_t variable
   * @param size pointer to a variable to store the size
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_size(lv_fs_file_t *file_p, uint32_t *size)
  {
      if (file_p->drv == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      if (file_p->drv->size_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      if (size == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      lv_fs_res_t res = file_p->drv->size_cb(file_p->drv, file_p->file_d, size);
  
      return res;
  }
  
  /**
   * Rename a file
   * @param oldname path to the file
   * @param newname path with the new name
   * @return LV_FS_RES_OK or any error from 'fs_res_t'
   */
  lv_fs_res_t lv_fs_rename(const char *oldname, const char *newname)
  {
      if (!oldname || !newname) {
          return LV_FS_RES_INV_PARAM;
      }
  
      char letter = oldname[0];
  
      lv_fs_drv_t *drv = lv_fs_get_drv(letter);
  
      if (!drv) {
          return LV_FS_RES_NOT_EX;
      }
  
      if (drv->ready_cb != NULL) {
          if (drv->ready_cb(drv) == false) {
              return LV_FS_RES_HW_ERR;
          }
      }
  
      if (drv->rename_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      const char *old_real = lv_fs_get_real_path(oldname);
      const char *new_real = lv_fs_get_real_path(newname);
  
      lv_fs_res_t res = drv->rename_cb(drv, old_real, new_real);
  
      return res;
  }
  
  /**
   * Initialize a 'lv_fs_dir_t' variable for directory reading
   * @param rddir_p pointer to a 'lv_fs_dir_t' variable
   * @param path path to a directory
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t *rddir_p, const char *path)
  {
      rddir_p->drv = NULL;
      rddir_p->dir_d = NULL;
  
      if (path == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      char letter = path[0];
  
      rddir_p->drv = lv_fs_get_drv(letter);
  
      if (rddir_p->drv == NULL) {
          return LV_FS_RES_NOT_EX;
      }
  
      if (rddir_p->drv->ready_cb != NULL) {
          if (rddir_p->drv->ready_cb(rddir_p->drv) == false) {
              rddir_p->drv = NULL;
              return LV_FS_RES_HW_ERR;
          }
      }
  
      if (rddir_p->drv->dir_open_cb == NULL) {
          rddir_p->drv = NULL;
          return LV_FS_RES_NOT_IMP;
      }
  
      const char *real_path = lv_fs_get_real_path(path);
  
      if (rddir_p->drv->rddir_size == 0) /*Is dir_d zero size?*/
      {
          /*Pass dir_d's address to dir_open_cb, so the implementor can allocate memory byself*/
          return rddir_p->drv->dir_open_cb(rddir_p->drv, &rddir_p->dir_d, real_path);
      }
  
      rddir_p->dir_d = lv_mem_alloc(rddir_p->drv->rddir_size);
      LV_ASSERT_MEM(rddir_p->dir_d);
  
      if (rddir_p->dir_d == NULL) {
          rddir_p->drv = NULL;
          return LV_FS_RES_OUT_OF_MEM; /* Out of memory */
      }
  
      lv_fs_res_t res = rddir_p->drv->dir_open_cb(rddir_p->drv, rddir_p->dir_d, real_path);
  
      if (res != LV_FS_RES_OK) {
          lv_mem_free(rddir_p->dir_d);
          rddir_p->dir_d = NULL;
          rddir_p->drv = NULL;
      }
  
      return res;
  }
  
  /**
   * Read the next filename form a directory.
   * The name of the directories will begin with '/'
   * @param rddir_p pointer to an initialized 'lv_fs_dir_t' variable
   * @param fn pointer to a buffer to store the filename
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t *rddir_p, char *fn)
  {
      if (rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
          fn[0] = '\0';
          return LV_FS_RES_INV_PARAM;
      }
  
      if (rddir_p->drv->dir_read_cb == NULL) {
          fn[0] = '\0';
          return LV_FS_RES_NOT_IMP;
      }
  
      lv_fs_res_t res = rddir_p->drv->dir_read_cb(rddir_p->drv, rddir_p->dir_d, fn);
  
      return res;
  }
  
  /**
   * Close the directory reading
   * @param rddir_p pointer to an initialized 'lv_fs_dir_t' variable
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_dir_close(lv_fs_dir_t *rddir_p)
  {
      if (rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      if (rddir_p->drv->dir_close_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      lv_fs_res_t res = rddir_p->drv->dir_close_cb(rddir_p->drv, rddir_p->dir_d);
  
      lv_mem_free(rddir_p->dir_d); /*Clean up*/
      rddir_p->dir_d = NULL;
      rddir_p->drv = NULL;
  
      return res;
  }
  
  /**
   * Get the free and total size of a driver in kB
   * @param letter the driver letter
   * @param total_p pointer to store the total size [kB]
   * @param free_p pointer to store the free size_cb [kB]
   * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
   */
  lv_fs_res_t lv_fs_free_space(char letter, uint32_t *total_p, uint32_t *free_p)
  {
      lv_fs_drv_t *drv = lv_fs_get_drv(letter);
  
      if (drv == NULL) {
          return LV_FS_RES_INV_PARAM;
      }
  
      if (drv->ready_cb != NULL) {
          if (drv->ready_cb(drv) == false) {
              return LV_FS_RES_HW_ERR;
          }
      }
  
      if (drv->free_space_cb == NULL) {
          return LV_FS_RES_NOT_IMP;
      }
  
      uint32_t total_tmp = 0;
      uint32_t free_tmp = 0;
      lv_fs_res_t res = drv->free_space_cb(drv, &total_tmp, &free_tmp);
  
      if (total_p != NULL) {
          *total_p = total_tmp;
      }
  
      if (free_p != NULL) {
          *free_p = free_tmp;
      }
  
      return res;
  }
  
  /**
   * Initialize a file system driver with default values.
   * It is used to surly have known values in the fields ant not memory junk.
   * After it you can set the fields.
   * @param drv pointer to driver variable to initialize
   */
  void lv_fs_drv_init(lv_fs_drv_t *drv)
  {
      _lv_memset_00(drv, sizeof(lv_fs_drv_t));
  }
  
  /**
   * Add a new drive
   * @param drv_p pointer to an lv_fs_drv_t structure which is inited with the
   * corresponding function pointers. The data will be copied so the variable can be local.
   */
  void lv_fs_drv_register(lv_fs_drv_t *drv_p)
  {
      /*Save the new driver*/
      lv_fs_drv_t *new_drv;
      new_drv = _lv_ll_ins_head(&LV_GC_ROOT(_lv_drv_ll));
      LV_ASSERT_MEM(new_drv);
  
      if (new_drv == NULL) {
          return;
      }
  
      _lv_memcpy(new_drv, drv_p, sizeof(lv_fs_drv_t));
  }
  
  /**
   * Give a pointer to a driver from its letter
   * @param letter the driver letter
   * @return pointer to a driver or NULL if not found
   */
  lv_fs_drv_t *lv_fs_get_drv(char letter)
  {
      lv_fs_drv_t *drv;
  
      _LV_LL_READ(LV_GC_ROOT(_lv_drv_ll), drv)
      {
          if (drv->letter == letter) {
              return drv;
          }
      }
  
      return NULL;
  }
  /**
   * Fill a buffer with the letters of existing drivers
   * @param buf buffer to store the letters ('\0' added after the last letter)
   * @return the buffer
   */
  char *lv_fs_get_letters(char *buf)
  {
      lv_fs_drv_t *drv;
      uint8_t i = 0;
  
      _LV_LL_READ(LV_GC_ROOT(_lv_drv_ll), drv)
      {
          buf[i] = drv->letter;
          i++;
      }
  
      buf[i] = '\0';
  
      return buf;
  }
  
  /**
   * Return with the extension of the filename
   * @param fn string with a filename
   * @return pointer to the beginning extension or empty string if no extension
   */
  const char *lv_fs_get_ext(const char *fn)
  {
      size_t i;
  
      for (i = strlen(fn); i > 0; i--) {
          if (fn[i] == '.') {
              return &fn[i + 1];
          } else if (fn[i] == '/' || fn[i] == '\\') {
              return ""; /*No extension if a '\' or '/' found*/
          }
      }
  
      return ""; /*Empty string if no '.' in the file name. */
  }
  
  /**
   * Step up one level
   * @param path pointer to a file name
   * @return the truncated file name
   */
  char *lv_fs_up(char *path)
  {
      size_t len = strlen(path);
  
      if (len == 0) {
          return path;
      }
  
      len--; /*Go before the trailing '\0'*/
  
      /*Ignore trailing '/' or '\'*/
      while (path[len] == '/' || path[len] == '\\') {
          path[len] = '\0';
  
          if (len > 0) {
              len--;
          } else {
              return path;
          }
      }
  
      size_t i;
  
      for (i = len; i > 0; i--) {
          if (path[i] == '/' || path[i] == '\\') {
              break;
          }
      }
  
      if (i > 0) {
          path[i] = '\0';
      }
  
      return path;
  }
  
  /**
   * Get the last element of a path (e.g. U:/folder/file -> file)
   * @param path a character sting with the path to search in
   * @return pointer to the beginning of the last element in the path
   */
  const char *lv_fs_get_last(const char *path)
  {
      size_t len = strlen(path);
  
      if (len == 0) {
          return path;
      }
  
      len--; /*Go before the trailing '\0'*/
  
      /*Ignore trailing '/' or '\'*/
      while (path[len] == '/' || path[len] == '\\') {
          if (len > 0) {
              len--;
          } else {
              return path;
          }
      }
  
      size_t i;
  
      for (i = len; i > 0; i--) {
          if (path[i] == '/' || path[i] == '\\') {
              break;
          }
      }
  
      /*No '/' or '\' in the path so return with path itself*/
      if (i == 0) {
          return path;
      }
  
      return &path[i + 1];
  }
  /**********************
   *   STATIC FUNCTIONS
   **********************/
  
  /**
   * Leave the driver letters and / or \ letters from beginning of the path
   * @param path path string (E.g. S:/folder/file.txt)
   * @return pointer to the beginning of the real path (E.g. folder/file.txt)
   */
  static const char *lv_fs_get_real_path(const char *path)
  {
      /* Example path: "S:/folder/file.txt"
       * Leave the letter and the : / \ characters*/
  
      path++; /*Ignore the driver letter*/
  
      while (*path != '\0') {
          if (*path == ':' || *path == '\\' || *path == '/') {
              path++;
          } else {
              break;
          }
      }
  
      return path;
  }
  
  #endif /*LV_USE_FILESYSTEM*/
  