/**1* @file lv_fs.c2*3*/45/*********************6* INCLUDES7*********************/8#include "lv_fs.h"9#if USE_LV_FILESYSTEM1011#include "lv_ll.h"12#include <string.h>13#include "lv_gc.h"1415#if defined(LV_GC_INCLUDE)16# include LV_GC_INCLUDE17#endif /* LV_ENABLE_GC */1819/*********************20* DEFINES21*********************/2223/* "free" is used as a function pointer (in lv_fs_drv_t).24* We must make sure "free" was not defined to a platform specific25* free function, otherwise compilation would fail.26*/27#ifdef free28#undef free29#endif3031/**********************32* TYPEDEFS33**********************/3435/**********************36* STATIC PROTOTYPES37**********************/38static const char * lv_fs_get_real_path(const char * path);39static lv_fs_drv_t * lv_fs_get_drv(char letter);404142/**********************43* STATIC VARIABLES44**********************/4546/**********************47* MACROS48**********************/4950/**********************51* GLOBAL FUNCTIONS52**********************/5354/**55* Initialize the File system interface56*/57void lv_fs_init(void)58{59lv_ll_init(&LV_GC_ROOT(_lv_drv_ll), sizeof(lv_fs_drv_t));60}6162/**63* Test if a drive is rady or not. If the `ready` function was not initialized `true` will be returned.64* @param letter letter of the drive65* @return true: drive is ready; false: drive is not ready66*/67bool lv_fs_is_ready(char letter)68{69lv_fs_drv_t * drv = lv_fs_get_drv(letter);7071if(drv == NULL) return false; /*An unknown driver in not ready*/7273if(drv->ready == NULL) return true; /*Assume the driver is always ready if no handler provided*/7475return drv->ready();76}7778/**79* Open a file80* @param file_p pointer to a lv_fs_file_t variable81* @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt)82* @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR83* @return LV_FS_RES_OK or any error from lv_fs_res_t enum84*/85lv_fs_res_t lv_fs_open(lv_fs_file_t * file_p, const char * path, lv_fs_mode_t mode)86{87file_p->drv = NULL;88file_p->file_d = NULL;8990if(path == NULL) return LV_FS_RES_INV_PARAM;9192char letter = path[0];9394file_p->drv = lv_fs_get_drv(letter);9596if(file_p->drv == NULL) {97file_p->file_d = NULL;98return LV_FS_RES_NOT_EX;99}100101if(file_p->drv->ready != NULL) {102if(file_p->drv->ready() == false) {103file_p->drv = NULL;104file_p->file_d = NULL;105return LV_FS_RES_HW_ERR;106}107}108109file_p->file_d = lv_mem_alloc(file_p->drv->file_size);110lv_mem_assert(file_p->file_d);111if(file_p->file_d == NULL) {112file_p->drv = NULL;113return LV_FS_RES_OUT_OF_MEM; /* Out of memory */114}115116if(file_p->drv->open == NULL) {117return LV_FS_RES_NOT_IMP;118}119120const char * real_path = lv_fs_get_real_path(path);121lv_fs_res_t res = file_p->drv->open(file_p->file_d, real_path, mode);122123if(res != LV_FS_RES_OK) {124lv_mem_free(file_p->file_d);125file_p->file_d = NULL;126file_p->drv = NULL;127}128129return res;130}131132/**133* Close an already opened file134* @param file_p pointer to a lv_fs_file_t variable135* @return LV_FS_RES_OK or any error from lv_fs_res_t enum136*/137lv_fs_res_t lv_fs_close(lv_fs_file_t * file_p)138{139if(file_p->drv == NULL) {140return LV_FS_RES_INV_PARAM;141}142143if(file_p->drv->close == NULL) {144return LV_FS_RES_NOT_IMP;145}146147lv_fs_res_t res = file_p->drv->close(file_p->file_d);148149lv_mem_free(file_p->file_d); /*Clean up*/150file_p->file_d = NULL;151file_p->drv = NULL;152file_p->file_d = NULL;153154return res;155}156157/**158* Delete a file159* @param path path of the file to delete160* @return LV_FS_RES_OK or any error from lv_fs_res_t enum161*/162lv_fs_res_t lv_fs_remove(const char * path)163{164if(path == NULL) return LV_FS_RES_INV_PARAM;165lv_fs_drv_t * drv = NULL;166167char letter = path[0];168169drv = lv_fs_get_drv(letter);170if(drv == NULL) return LV_FS_RES_NOT_EX;171if(drv->ready != NULL) {172if(drv->ready() == false) return LV_FS_RES_HW_ERR;173}174175if(drv->remove == NULL) return LV_FS_RES_NOT_IMP;176177const char * real_path = lv_fs_get_real_path(path);178lv_fs_res_t res = drv->remove(real_path);179180return res;181}182183/**184* Read from a file185* @param file_p pointer to a lv_fs_file_t variable186* @param buf pointer to a buffer where the read bytes are stored187* @param btr Bytes To Read188* @param br the number of real read bytes (Bytes Read). NULL if unused.189* @return LV_FS_RES_OK or any error from lv_fs_res_t enum190*/191lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br)192{193if(br != NULL) *br = 0;194if(file_p->drv == NULL) return LV_FS_RES_INV_PARAM;195if(file_p->drv->read == NULL) return LV_FS_RES_NOT_IMP;196197uint32_t br_tmp = 0;198lv_fs_res_t res = file_p->drv->read(file_p->file_d, buf, btr, &br_tmp);199if(br != NULL) *br = br_tmp;200201return res;202}203204/**205* Write into a file206* @param file_p pointer to a lv_fs_file_t variable207* @param buf pointer to a buffer with the bytes to write208* @param btr Bytes To Write209* @param br the number of real written bytes (Bytes Written). NULL if unused.210* @return LV_FS_RES_OK or any error from lv_fs_res_t enum211*/212lv_fs_res_t lv_fs_write(lv_fs_file_t * file_p, const void * buf, uint32_t btw, uint32_t * bw)213{214if(bw != NULL) *bw = 0;215216if(file_p->drv == NULL) {217return LV_FS_RES_INV_PARAM;218}219220if(file_p->drv->write == NULL) {221return LV_FS_RES_NOT_IMP;222}223224uint32_t bw_tmp = 0;225lv_fs_res_t res = file_p->drv->write(file_p->file_d, buf, btw, &bw_tmp);226if(bw != NULL) *bw = bw_tmp;227228return res;229}230231/**232* Set the position of the 'cursor' (read write pointer) in a file233* @param file_p pointer to a lv_fs_file_t variable234* @param pos the new position expressed in bytes index (0: start of file)235* @return LV_FS_RES_OK or any error from lv_fs_res_t enum236*/237lv_fs_res_t lv_fs_seek(lv_fs_file_t * file_p, uint32_t pos)238{239if(file_p->drv == NULL) {240return LV_FS_RES_INV_PARAM;241}242243if(file_p->drv->seek == NULL) {244return LV_FS_RES_NOT_IMP;245}246247lv_fs_res_t res = file_p->drv->seek(file_p->file_d, pos);248249return res;250}251252/**253* Give the position of the read write pointer254* @param file_p pointer to a lv_fs_file_t variable255* @param pos_p pointer to store the position of the read write pointer256* @return LV_FS_RES_OK or any error from 'fs_res_t'257*/258lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos)259{260if(file_p->drv == NULL) {261pos = 0;262return LV_FS_RES_INV_PARAM;263}264265if(file_p->drv->tell == NULL) {266pos = 0;267return LV_FS_RES_NOT_IMP;268}269270lv_fs_res_t res = file_p->drv->tell(file_p->file_d, pos);271272return res;273}274275/**276* Truncate the file size to the current position of the read write pointer277* @param file_p pointer to an 'ufs_file_t' variable. (opened with lv_fs_open )278* @return LV_FS_RES_OK: no error, the file is read279* any error from lv_fs_res_t enum280*/281lv_fs_res_t lv_fs_trunc(lv_fs_file_t * file_p)282{283if(file_p->drv == NULL) {284return LV_FS_RES_INV_PARAM;285}286287if(file_p->drv->tell == NULL) {288return LV_FS_RES_NOT_IMP;289}290291lv_fs_res_t res = file_p->drv->trunc(file_p->file_d);292293return res;294}295/**296* Give the size of a file bytes297* @param file_p pointer to a lv_fs_file_t variable298* @param size pointer to a variable to store the size299* @return LV_FS_RES_OK or any error from lv_fs_res_t enum300*/301lv_fs_res_t lv_fs_size(lv_fs_file_t * file_p, uint32_t * size)302{303if(file_p->drv == NULL) {304return LV_FS_RES_INV_PARAM;305}306307if(file_p->drv->size == NULL) return LV_FS_RES_NOT_IMP;308309310if(size == NULL) return LV_FS_RES_INV_PARAM;311312lv_fs_res_t res = file_p->drv->size(file_p->file_d, size);313314return res;315}316317/**318* Rename a file319* @param oldname path to the file320* @param newname path with the new name321* @return LV_FS_RES_OK or any error from 'fs_res_t'322*/323lv_fs_res_t lv_fs_rename(const char * oldname, const char * newname)324{325if(!oldname || !newname) return LV_FS_RES_INV_PARAM;326327char letter = oldname[0];328329lv_fs_drv_t * drv = lv_fs_get_drv(letter);330331if(!drv) {332return LV_FS_RES_NOT_EX;333}334335if(drv->ready != NULL) {336if(drv->ready() == false) {337return LV_FS_RES_HW_ERR;338}339}340341if(drv->rename == NULL) return LV_FS_RES_NOT_IMP;342343const char * old_real = lv_fs_get_real_path(oldname);344const char * new_real = lv_fs_get_real_path(newname);345lv_fs_res_t res = drv->rename(old_real, new_real);346347return res;348}349350351/**352* Initialize a 'fs_read_dir_t' variable for directory reading353* @param rddir_p pointer to a 'fs_read_dir_t' variable354* @param path path to a directory355* @return LV_FS_RES_OK or any error from lv_fs_res_t enum356*/357lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path)358{359if(path == NULL) return LV_FS_RES_INV_PARAM;360361char letter = path[0];362363rddir_p->drv = lv_fs_get_drv(letter);364365if(rddir_p->drv == NULL) {366rddir_p->dir_d = NULL;367return LV_FS_RES_NOT_EX;368}369370rddir_p->dir_d = lv_mem_alloc(rddir_p->drv->rddir_size);371lv_mem_assert(rddir_p->dir_d);372if(rddir_p->dir_d == NULL) {373rddir_p->dir_d = NULL;374return LV_FS_RES_OUT_OF_MEM; /* Out of memory */375}376377if(rddir_p->drv->dir_open == NULL) {378return LV_FS_RES_NOT_IMP;379}380381const char * real_path = lv_fs_get_real_path(path);382lv_fs_res_t res = rddir_p->drv->dir_open(rddir_p->dir_d, real_path);383384return res;385}386387/**388* Read the next filename form a directory.389* The name of the directories will begin with '/'390* @param rddir_p pointer to an initialized 'fs_read_dir_t' variable391* @param fn pointer to a buffer to store the filename392* @return LV_FS_RES_OK or any error from lv_fs_res_t enum393*/394lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn)395{396if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {397fn[0] = '\0';398return LV_FS_RES_INV_PARAM;399}400401if(rddir_p->drv->dir_read == NULL) {402return LV_FS_RES_NOT_IMP;403}404405lv_fs_res_t res = rddir_p->drv->dir_read(rddir_p->dir_d, fn);406407return res;408}409410/**411* Close the directory reading412* @param rddir_p pointer to an initialized 'fs_read_dir_t' variable413* @return LV_FS_RES_OK or any error from lv_fs_res_t enum414*/415lv_fs_res_t lv_fs_dir_close(lv_fs_dir_t * rddir_p)416{417if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {418return LV_FS_RES_INV_PARAM;419}420421lv_fs_res_t res;422423if(rddir_p->drv->dir_close == NULL) {424res = LV_FS_RES_NOT_IMP;425} else {426res = rddir_p->drv->dir_close(rddir_p->dir_d);427}428429lv_mem_free(rddir_p->dir_d); /*Clean up*/430rddir_p->dir_d = NULL;431rddir_p->drv = NULL;432rddir_p->dir_d = NULL;433434return res;435}436437/**438* Get the free and total size of a driver in kB439* @param letter the driver letter440* @param total_p pointer to store the total size [kB]441* @param free_p pointer to store the free size [kB]442* @return LV_FS_RES_OK or any error from lv_fs_res_t enum443*/444lv_fs_res_t lv_fs_free(char letter, uint32_t * total_p, uint32_t * free_p)445{446lv_fs_drv_t * drv = lv_fs_get_drv(letter);447448if(drv == NULL) {449return LV_FS_RES_INV_PARAM;450}451452lv_fs_res_t res;453454if(drv->free == NULL) {455res = LV_FS_RES_NOT_IMP;456} else {457uint32_t total_tmp = 0;458uint32_t free_tmp = 0;459res = drv->free(&total_tmp, &free_tmp);460461if(total_p != NULL) *total_p = total_tmp;462if(free_p != NULL) *free_p = free_tmp;463}464465return res;466}467468/**469* Add a new drive470* @param drv_p pointer to an lv_fs_drv_t structure which is inited with the471* corresponding function pointers. The data will be copied so the variable can be local.472*/473void lv_fs_add_drv(lv_fs_drv_t * drv_p)474{475/*Save the new driver*/476lv_fs_drv_t * new_drv;477new_drv = lv_ll_ins_head(&LV_GC_ROOT(_lv_drv_ll));478lv_mem_assert(new_drv);479if(new_drv == NULL) return;480481memcpy(new_drv, drv_p, sizeof(lv_fs_drv_t));482}483484/**485* Fill a buffer with the letters of existing drivers486* @param buf buffer to store the letters ('\0' added after the last letter)487* @return the buffer488*/489char * lv_fs_get_letters(char * buf)490{491lv_fs_drv_t * drv;492uint8_t i = 0;493494LL_READ(LV_GC_ROOT(_lv_drv_ll), drv) {495buf[i] = drv->letter;496i++;497}498499buf[i] = '\0';500501return buf;502}503504505/**506* Return with the extension of the filename507* @param fn string with a filename508* @return pointer to the beginning extension or empty string if no extension509*/510const char * lv_fs_get_ext(const char * fn)511{512uint16_t i;513for(i = strlen(fn); i > 0; i --) {514if(fn[i] == '.') {515return &fn[i + 1];516} else if(fn[i] == '/' || fn[i] == '\\') {517return ""; /*No extension if a '\' or '/' found*/518}519}520521return ""; /*Empty string if no '.' in the file name. */522}523524/**525* Step up one level526* @param path pointer to a file name527* @return the truncated file name528*/529char * lv_fs_up(char * path)530{531uint16_t len = strlen(path);532if(len == 0) return path;533534len --; /*Go before the trailing '\0'*/535536/*Ignore trailing '/' or '\'*/537while(path[len] == '/' || path[len] == '\\') {538path[len] = '\0';539if(len > 0) len --;540else return path;541}542543uint16_t i;544for(i = len; i > 0; i --) {545if(path[i] == '/' || path[i] == '\\') break;546}547548if(i > 0) path[i] = '\0';549550return path;551}552553/**554* Get the last element of a path (e.g. U:/folder/file -> file)555* @param path a character sting with the path to search in556* @return pointer to the beginning of the last element in the path557*/558const char * lv_fs_get_last(const char * path)559{560uint16_t len = strlen(path);561if(len == 0) return path;562563len --; /*Go before the trailing '\0'*/564565/*Ignore trailing '/' or '\'*/566while(path[len] == '/' || path[len] == '\\') {567if(len > 0) len --;568else return path;569}570571uint16_t i;572for(i = len; i > 0; i --) {573if(path[i] == '/' || path[i] == '\\') break;574}575576/*No '/' or '\' in the path so return with path itself*/577if(i == 0) return path;578579return &path[i + 1];580}581/**********************582* STATIC FUNCTIONS583**********************/584585/**586* Leave the driver letters and / or \ letters from beginning of the path587* @param path path string (E.g. S:/folder/file.txt)588* @return pointer to the beginning of the real path (E.g. folder/file.txt)589*/590static const char * lv_fs_get_real_path(const char * path)591{592/* Example path: "S:/folder/file.txt"593* Leave the letter and the : / \ characters*/594595path ++; /*Ignore the driver letter*/596597while(*path != '\0') {598if(*path == ':' || *path == '\\' || *path == '/') {599path ++;600} else {601break;602}603}604605return path;606}607608/**609* Give a pointer to a driver from its letter610* @param letter the driver letter611* @return pointer to a driver or NULL if not found612*/613static lv_fs_drv_t * lv_fs_get_drv(char letter)614{615lv_fs_drv_t * drv;616617LL_READ(LV_GC_ROOT(_lv_drv_ll), drv) {618if(drv->letter == letter) {619return drv;620}621}622623return NULL;624}625626#endif /*USE_LV_FILESYSTEM*/627628629