1 /**
2 * @file lv_fs.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_fs.h"
10
11 #include "../misc/lv_assert.h"
12 #include "lv_ll.h"
13 #include <string.h>
14 #include "lv_gc.h"
15
16 /*********************
17 * DEFINES
18 *********************/
19
20 /**********************
21 * TYPEDEFS
22 **********************/
23
24 /**********************
25 * STATIC PROTOTYPES
26 **********************/
27 static const char * lv_fs_get_real_path(const char * path);
28
29 /**********************
30 * STATIC VARIABLES
31 **********************/
32
33 /**********************
34 * MACROS
35 **********************/
36
37 /**********************
38 * GLOBAL FUNCTIONS
39 **********************/
40
_lv_fs_init(void)41 void _lv_fs_init(void)
42 {
43 _lv_ll_init(&LV_GC_ROOT(_lv_fsdrv_ll), sizeof(lv_fs_drv_t *));
44 }
45
lv_fs_is_ready(char letter)46 bool lv_fs_is_ready(char letter)
47 {
48 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
49
50 if(drv == NULL) return false; /*An unknown driver in not ready*/
51
52 if(drv->ready_cb == NULL) return true; /*Assume the driver is always ready if no handler provided*/
53
54 return drv->ready_cb(drv);
55 }
56
lv_fs_open(lv_fs_file_t * file_p,const char * path,lv_fs_mode_t mode)57 lv_fs_res_t lv_fs_open(lv_fs_file_t * file_p, const char * path, lv_fs_mode_t mode)
58 {
59 if(path == NULL) {
60 LV_LOG_WARN("Can't open file: path is NULL");
61 return LV_FS_RES_INV_PARAM;
62 }
63
64 char letter = path[0];
65 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
66
67 if(drv == NULL) {
68 LV_LOG_WARN("Can't open file (%s): unknown driver letter", path);
69 return LV_FS_RES_NOT_EX;
70 }
71
72 if(drv->ready_cb) {
73 if(drv->ready_cb(drv) == false) {
74 LV_LOG_WARN("Can't open file (%s): driver not ready", path);
75 return LV_FS_RES_HW_ERR;
76 }
77 }
78
79 if(drv->open_cb == NULL) {
80 LV_LOG_WARN("Can't open file (%s): open function not exists", path);
81 return LV_FS_RES_NOT_IMP;
82 }
83
84 const char * real_path = lv_fs_get_real_path(path);
85 void * file_d = drv->open_cb(drv, real_path, mode);
86
87 if(file_d == NULL || file_d == (void *)(-1)) {
88 return LV_FS_RES_UNKNOWN;
89 }
90
91 file_p->drv = drv;
92 file_p->file_d = file_d;
93
94 if(drv->cache_size) {
95 file_p->cache = lv_mem_alloc(sizeof(lv_fs_file_cache_t));
96 LV_ASSERT_MALLOC(file_p->cache);
97 lv_memset_00(file_p->cache, sizeof(lv_fs_file_cache_t));
98 }
99
100 return LV_FS_RES_OK;
101 }
102
lv_fs_close(lv_fs_file_t * file_p)103 lv_fs_res_t lv_fs_close(lv_fs_file_t * file_p)
104 {
105 if(file_p->drv == NULL) {
106 return LV_FS_RES_INV_PARAM;
107 }
108
109 if(file_p->drv->close_cb == NULL) {
110 return LV_FS_RES_NOT_IMP;
111 }
112
113 lv_fs_res_t res = file_p->drv->close_cb(file_p->drv, file_p->file_d);
114
115 if(file_p->drv->cache_size && file_p->cache) {
116 if(file_p->cache->buffer) {
117 lv_mem_free(file_p->cache->buffer);
118 }
119
120 lv_mem_free(file_p->cache);
121 }
122
123 file_p->file_d = NULL;
124 file_p->drv = NULL;
125 file_p->cache = NULL;
126
127 return res;
128 }
129
lv_fs_read_cached(lv_fs_file_t * file_p,char * buf,uint32_t btr,uint32_t * br)130 static lv_fs_res_t lv_fs_read_cached(lv_fs_file_t * file_p, char * buf, uint32_t btr, uint32_t * br)
131 {
132 lv_fs_res_t res = LV_FS_RES_OK;
133 uint32_t file_position = file_p->cache->file_position;
134 uint32_t start = file_p->cache->start;
135 uint32_t end = file_p->cache->end;
136 char * buffer = file_p->cache->buffer;
137 uint16_t buffer_size = file_p->drv->cache_size;
138
139 if(start <= file_position && file_position < end) {
140 /* Data can be read from cache buffer */
141
142 uint16_t buffer_offset = file_position - start;
143 uint16_t buffer_remaining_length = buffer_size - buffer_offset;
144
145 if(btr <= buffer_remaining_length) {
146 /*Data is in cache buffer, and buffer end not reached, no need to read from FS*/
147 lv_memcpy(buf, buffer + buffer_offset, btr);
148 }
149 else {
150 /*First part of data is in cache buffer, but we need to read rest of data from FS*/
151 lv_memcpy(buf, buffer + buffer_offset, buffer_remaining_length);
152
153 if(btr > buffer_size) {
154 /*If remaining data chuck is bigger than buffer size, then do not use cache, instead read it directly from FS*/
155 res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)(buf + buffer_remaining_length),
156 btr - buffer_remaining_length, br);
157 }
158 else {
159 /*If remaining data chunk is smaller than buffer size, then read into cache buffer*/
160 uint32_t bytes_read_to_buffer = 0;
161
162 /*Read into cache buffer:*/
163 res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buffer, buffer_size, &bytes_read_to_buffer);
164 file_p->cache->start = file_p->cache->end + 1;
165 file_p->cache->end = file_p->cache->start + bytes_read_to_buffer;
166
167 uint16_t data_chunk_remaining = btr - buffer_remaining_length;
168 memcpy(buf + buffer_remaining_length, buffer, data_chunk_remaining);
169 }
170 }
171 }
172 else {
173 /*Data is not in cache buffer*/
174
175 if(btr > buffer_size) {
176 /*If bigger data is requested, then do not use cache, instead read it directly*/
177 res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buf, btr, br);
178 }
179 else {
180 /*If small data is requested, then read from FS into cache buffer*/
181 if(buffer == NULL) {
182 file_p->cache->buffer = lv_mem_alloc(buffer_size);
183 LV_ASSERT_MALLOC(file_p->cache->buffer);
184 buffer = file_p->cache->buffer;
185 }
186
187 uint32_t bytes_read_to_buffer = 0;
188 res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buffer, buffer_size, &bytes_read_to_buffer);
189 file_p->cache->start = file_position;
190 file_p->cache->end = file_p->cache->start + bytes_read_to_buffer;
191
192 memcpy(buf, buffer, btr);
193 }
194 }
195
196 if(res == LV_FS_RES_OK) {
197 *br = btr;
198 file_p->cache->file_position += btr;
199 }
200
201 return res;
202 }
203
lv_fs_read(lv_fs_file_t * file_p,void * buf,uint32_t btr,uint32_t * br)204 lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br)
205 {
206 if(br != NULL) *br = 0;
207 if(file_p->drv == NULL) return LV_FS_RES_INV_PARAM;
208 if(file_p->drv->read_cb == NULL) return LV_FS_RES_NOT_IMP;
209
210 uint32_t br_tmp = 0;
211 lv_fs_res_t res;
212
213 if(file_p->drv->cache_size) {
214 res = lv_fs_read_cached(file_p, (char *)buf, btr, &br_tmp);
215 }
216 else {
217 res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buf, btr, &br_tmp);
218 }
219
220 if(br != NULL) *br = br_tmp;
221
222 return res;
223 }
224
lv_fs_write(lv_fs_file_t * file_p,const void * buf,uint32_t btw,uint32_t * bw)225 lv_fs_res_t lv_fs_write(lv_fs_file_t * file_p, const void * buf, uint32_t btw, uint32_t * bw)
226 {
227 if(bw != NULL) *bw = 0;
228
229 if(file_p->drv == NULL) {
230 return LV_FS_RES_INV_PARAM;
231 }
232
233 if(file_p->drv->write_cb == NULL) {
234 return LV_FS_RES_NOT_IMP;
235 }
236
237 uint32_t bw_tmp = 0;
238 lv_fs_res_t res = file_p->drv->write_cb(file_p->drv, file_p->file_d, buf, btw, &bw_tmp);
239 if(bw != NULL) *bw = bw_tmp;
240
241 return res;
242 }
243
lv_fs_seek(lv_fs_file_t * file_p,uint32_t pos,lv_fs_whence_t whence)244 lv_fs_res_t lv_fs_seek(lv_fs_file_t * file_p, uint32_t pos, lv_fs_whence_t whence)
245 {
246 if(file_p->drv == NULL) {
247 return LV_FS_RES_INV_PARAM;
248 }
249
250 if(file_p->drv->seek_cb == NULL) {
251 return LV_FS_RES_NOT_IMP;
252 }
253
254 lv_fs_res_t res = LV_FS_RES_OK;
255 if(file_p->drv->cache_size) {
256 switch(whence) {
257 case LV_FS_SEEK_SET: {
258 file_p->cache->file_position = pos;
259
260 /*FS seek if new position is outside cache buffer*/
261 if(file_p->cache->file_position < file_p->cache->start || file_p->cache->file_position > file_p->cache->end) {
262 res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, LV_FS_SEEK_SET);
263 }
264
265 break;
266 }
267 case LV_FS_SEEK_CUR: {
268 file_p->cache->file_position += pos;
269
270 /*FS seek if new position is outside cache buffer*/
271 if(file_p->cache->file_position < file_p->cache->start || file_p->cache->file_position > file_p->cache->end) {
272 res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, LV_FS_SEEK_SET);
273 }
274
275 break;
276 }
277 case LV_FS_SEEK_END: {
278 /*Because we don't know the file size, we do a little trick: do a FS seek, then get new file position from FS*/
279 res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos, whence);
280 if(res == LV_FS_RES_OK) {
281 uint32_t tmp_position;
282 res = file_p->drv->tell_cb(file_p->drv, file_p->file_d, &tmp_position);
283
284 if(res == LV_FS_RES_OK) {
285 file_p->cache->file_position = tmp_position;
286 }
287 }
288 break;
289 }
290 }
291 }
292 else {
293 res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos, whence);
294 }
295
296 return res;
297 }
298
lv_fs_tell(lv_fs_file_t * file_p,uint32_t * pos)299 lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos)
300 {
301 if(file_p->drv == NULL) {
302 *pos = 0;
303 return LV_FS_RES_INV_PARAM;
304 }
305
306 if(file_p->drv->tell_cb == NULL) {
307 *pos = 0;
308 return LV_FS_RES_NOT_IMP;
309 }
310
311 lv_fs_res_t res;
312 if(file_p->drv->cache_size) {
313 *pos = file_p->cache->file_position;
314 res = LV_FS_RES_OK;
315 }
316 else {
317 res = file_p->drv->tell_cb(file_p->drv, file_p->file_d, pos);
318 }
319
320 return res;
321 }
322
lv_fs_dir_open(lv_fs_dir_t * rddir_p,const char * path)323 lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path)
324 {
325 if(path == NULL) return LV_FS_RES_INV_PARAM;
326
327 char letter = path[0];
328 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
329
330 if(drv == NULL) {
331 return LV_FS_RES_NOT_EX;
332 }
333
334 if(drv->ready_cb) {
335 if(drv->ready_cb(drv) == false) {
336 return LV_FS_RES_HW_ERR;
337 }
338 }
339
340 if(drv->dir_open_cb == NULL) {
341 return LV_FS_RES_NOT_IMP;
342 }
343
344 const char * real_path = lv_fs_get_real_path(path);
345 void * dir_d = drv->dir_open_cb(drv, real_path);
346
347 if(dir_d == NULL || dir_d == (void *)(-1)) {
348 return LV_FS_RES_UNKNOWN;
349 }
350
351 rddir_p->drv = drv;
352 rddir_p->dir_d = dir_d;
353
354 return LV_FS_RES_OK;
355 }
356
lv_fs_dir_read(lv_fs_dir_t * rddir_p,char * fn)357 lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn)
358 {
359 if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
360 fn[0] = '\0';
361 return LV_FS_RES_INV_PARAM;
362 }
363
364 if(rddir_p->drv->dir_read_cb == NULL) {
365 fn[0] = '\0';
366 return LV_FS_RES_NOT_IMP;
367 }
368
369 lv_fs_res_t res = rddir_p->drv->dir_read_cb(rddir_p->drv, rddir_p->dir_d, fn);
370
371 return res;
372 }
373
lv_fs_dir_close(lv_fs_dir_t * rddir_p)374 lv_fs_res_t lv_fs_dir_close(lv_fs_dir_t * rddir_p)
375 {
376 if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
377 return LV_FS_RES_INV_PARAM;
378 }
379
380 if(rddir_p->drv->dir_close_cb == NULL) {
381 return LV_FS_RES_NOT_IMP;
382 }
383
384 lv_fs_res_t res = rddir_p->drv->dir_close_cb(rddir_p->drv, rddir_p->dir_d);
385
386 rddir_p->dir_d = NULL;
387 rddir_p->drv = NULL;
388
389 return res;
390 }
391
lv_fs_drv_init(lv_fs_drv_t * drv)392 void lv_fs_drv_init(lv_fs_drv_t * drv)
393 {
394 lv_memset_00(drv, sizeof(lv_fs_drv_t));
395 }
396
lv_fs_drv_register(lv_fs_drv_t * drv_p)397 void lv_fs_drv_register(lv_fs_drv_t * drv_p)
398 {
399 /*Save the new driver*/
400 lv_fs_drv_t ** new_drv;
401 new_drv = _lv_ll_ins_head(&LV_GC_ROOT(_lv_fsdrv_ll));
402 LV_ASSERT_MALLOC(new_drv);
403 if(new_drv == NULL) return;
404
405 *new_drv = drv_p;
406 }
407
lv_fs_get_drv(char letter)408 lv_fs_drv_t * lv_fs_get_drv(char letter)
409 {
410 lv_fs_drv_t ** drv;
411
412 _LV_LL_READ(&LV_GC_ROOT(_lv_fsdrv_ll), drv) {
413 if((*drv)->letter == letter) {
414 return *drv;
415 }
416 }
417
418 return NULL;
419 }
420
lv_fs_get_letters(char * buf)421 char * lv_fs_get_letters(char * buf)
422 {
423 lv_fs_drv_t ** drv;
424 uint8_t i = 0;
425
426 _LV_LL_READ(&LV_GC_ROOT(_lv_fsdrv_ll), drv) {
427 buf[i] = (*drv)->letter;
428 i++;
429 }
430
431 buf[i] = '\0';
432
433 return buf;
434 }
435
lv_fs_get_ext(const char * fn)436 const char * lv_fs_get_ext(const char * fn)
437 {
438 size_t i;
439 for(i = strlen(fn); i > 0; i--) {
440 if(fn[i] == '.') {
441 return &fn[i + 1];
442 }
443 else if(fn[i] == '/' || fn[i] == '\\') {
444 return ""; /*No extension if a '\' or '/' found*/
445 }
446 }
447
448 return ""; /*Empty string if no '.' in the file name.*/
449 }
450
lv_fs_up(char * path)451 char * lv_fs_up(char * path)
452 {
453 size_t len = strlen(path);
454 if(len == 0) return path;
455
456 len--; /*Go before the trailing '\0'*/
457
458 /*Ignore trailing '/' or '\'*/
459 while(path[len] == '/' || path[len] == '\\') {
460 path[len] = '\0';
461 if(len > 0)
462 len--;
463 else
464 return path;
465 }
466
467 size_t i;
468 for(i = len; i > 0; i--) {
469 if(path[i] == '/' || path[i] == '\\') break;
470 }
471
472 if(i > 0) path[i] = '\0';
473
474 return path;
475 }
476
lv_fs_get_last(const char * path)477 const char * lv_fs_get_last(const char * path)
478 {
479 size_t len = strlen(path);
480 if(len == 0) return path;
481
482 len--; /*Go before the trailing '\0'*/
483
484 /*Ignore trailing '/' or '\'*/
485 while(path[len] == '/' || path[len] == '\\') {
486 if(len > 0)
487 len--;
488 else
489 return path;
490 }
491
492 size_t i;
493 for(i = len; i > 0; i--) {
494 if(path[i] == '/' || path[i] == '\\') break;
495 }
496
497 /*No '/' or '\' in the path so return with path itself*/
498 if(i == 0) return path;
499
500 return &path[i + 1];
501 }
502 /**********************
503 * STATIC FUNCTIONS
504 **********************/
505
506 /**
507 * Skip the driver letter and the possible : after the letter
508 * @param path path string (E.g. S:/folder/file.txt)
509 * @return pointer to the beginning of the real path (E.g. /folder/file.txt)
510 */
lv_fs_get_real_path(const char * path)511 static const char * lv_fs_get_real_path(const char * path)
512 {
513 path++; /*Ignore the driver letter*/
514 if(*path == ':') path++;
515
516 return path;
517 }
518