5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
28 FRESULT res
= f_mkfs("", FM_FAT32
, 0, work
, sizeof(work
));
33 POPUP_WARNING("Format error");
36 POPUP_WARNING("SDCard not ready");
38 case FR_WRITE_PROTECTED
:
39 POPUP_WARNING("SDCard write protected");
41 case FR_INVALID_PARAMETER
:
42 POPUP_WARNING("Format param invalid");
44 case FR_INVALID_DRIVE
:
45 POPUP_WARNING("Invalid drive");
48 POPUP_WARNING("Format aborted");
51 POPUP_WARNING(STR_SDCARD_ERROR
);
56 const char * sdCheckAndCreateDirectory(const char * path
)
60 FRESULT result
= f_opendir(&archiveFolder
, path
);
61 if (result
!= FR_OK
) {
62 if (result
== FR_NO_PATH
)
63 result
= f_mkdir(path
);
65 return SDCARD_ERROR(result
);
68 f_closedir(&archiveFolder
);
74 bool isFileAvailable(const char * path
, bool exclDir
)
78 return (f_stat(path
, &fno
) == FR_OK
&& !(fno
.fattrib
& AM_DIR
));
80 return f_stat(path
, nullptr) == FR_OK
;
84 Search file system path for a file. Can optionally take a list of file extensions to search through.
85 Eg. find "splash.bmp", or the first occurrence of one of "splash.[bmp|jpeg|jpg|gif]".
87 @param path String with path name, no trailing slash. eg; "/BITMAPS"
88 @param file String containing file name to search for, with or w/out an extension.
89 eg; "splash.bmp" or "splash"
90 @param pattern Optional list of one or more file extensions concatenated together, including the period(s).
91 The list is searched backwards and the first match, if any, is returned. If null, then only the actual filename
92 passed will be searched for.
93 eg: ".gif.jpg.jpeg.bmp"
94 @param exclDir true/false whether to allow directory matches (default true, excludes directories)
95 @param match Optional container to hold the matched file extension (wide enough to hold LEN_FILE_EXTENSION_MAX + 1).
96 @retval true if a file was found, false otherwise.
98 bool isFilePatternAvailable(const char * path
, const char * file
, const char * pattern
= nullptr, bool exclDir
= true, char * match
= nullptr)
101 char fqfp
[LEN_FILE_PATH_MAX
+ _MAX_LFN
+ 1] = "\0";
103 fplen
= strlen(path
);
104 if (fplen
> LEN_FILE_PATH_MAX
) {
105 TRACE_ERROR("isFilePatternAvailable(%s) = error: file path too long.\n", path
, file
);
110 strcpy(fqfp
+ fplen
, "/");
111 strncat(fqfp
+ (++fplen
), file
, _MAX_LFN
);
113 if (pattern
== nullptr) {
114 // no extensions list, just check the filename as-is
115 return isFileAvailable(fqfp
, exclDir
);
118 // extensions list search
121 uint8_t extlen
, fnlen
;
124 getFileExtension(file
, 0, 0, &fnlen
, &extlen
);
125 len
= fplen
+ fnlen
- extlen
;
127 ext
= getFileExtension(pattern
, 0, 0, &fnlen
, &extlen
);
129 while (plen
> 0 && ext
) {
130 strncat(fqfp
+ len
, ext
, extlen
);
131 if (isFileAvailable(fqfp
, exclDir
)) {
132 if (match
!= nullptr) strncat(&(match
[0]='\0'), ext
, extlen
);
138 ext
= getFileExtension(pattern
, plen
, 0, nullptr, &extlen
);
145 char * getFileIndex(char * filename
, unsigned int & value
)
148 char * pos
= (char *)getFileExtension(filename
);
149 if (!pos
|| pos
== filename
)
152 while (pos
> filename
) {
155 if (c
>= '0' && c
<= '9') {
156 value
+= multiplier
* (c
- '0');
166 uint8_t getDigitsCount(unsigned int value
)
169 while (value
>= 10) {
176 int findNextFileIndex(char * filename
, uint8_t size
, const char * directory
)
180 char * indexPos
= getFileIndex(filename
, index
);
181 char extension
[LEN_FILE_EXTENSION_MAX
+1] = "\0";
182 char * p
= (char *)getFileExtension(filename
, 0, 0, nullptr, &extlen
);
183 if (p
) strncat(extension
, p
, sizeof(extension
)-1);
186 if ((indexPos
- filename
) + getDigitsCount(index
) + extlen
> size
) {
189 char * pos
= strAppendUnsigned(indexPos
, index
);
190 strAppend(pos
, extension
);
191 if (!isFilePatternAvailable(directory
, filename
, nullptr, false)) {
197 const char * getBasename(const char * path
)
199 for (int8_t i
= strlen(path
) - 1; i
>= 0; i
--) {
200 if (path
[i
] == '/') {
207 const char * getFileExtension(const char * filename
, uint8_t size
, uint8_t extMaxLen
, uint8_t *fnlen
, uint8_t *extlen
)
211 len
= strlen(filename
);
214 extMaxLen
= LEN_FILE_EXTENSION_MAX
;
216 if (fnlen
!= nullptr) {
217 *fnlen
= (uint8_t)len
;
219 for (int i
=len
-1; i
>= 0 && len
-i
<= extMaxLen
; --i
) {
220 if (filename
[i
] == '.') {
227 if (extlen
!= nullptr) {
234 Check if given extension exists in a list of extensions.
235 @param extension The extension to search for, including leading period.
236 @param pattern One or more file extensions concatenated together, including the periods.
237 The list is searched backwards and the first match, if any, is returned.
238 eg: ".gif.jpg.jpeg.png"
239 @param match Optional container to hold the matched file extension (wide enough to hold LEN_FILE_EXTENSION_MAX + 1).
240 @retval true if a extension was found in the lost, false otherwise.
242 bool isExtensionMatching(const char * extension
, const char * pattern
, char * match
)
245 uint8_t extlen
, fnlen
;
248 ext
= getFileExtension(pattern
, 0, 0, &fnlen
, &extlen
);
250 while (plen
> 0 && ext
) {
251 if (!strncasecmp(extension
, ext
, extlen
)) {
252 if (match
!= nullptr) strncat(&(match
[0]='\0'), ext
, extlen
);
257 ext
= getFileExtension(pattern
, plen
, 0, nullptr, &extlen
);
263 bool sdListFiles(const char * path
, const char * extension
, const uint8_t maxlen
, const char * selection
, uint8_t flags
)
265 static uint16_t lastpopupMenuOffset
= 0;
269 uint8_t fnLen
, extLen
;
270 char tmpExt
[LEN_FILE_EXTENSION_MAX
+1] = "\0";
272 popupMenuOffsetType
= MENU_OFFSET_EXTERNAL
;
274 static uint8_t s_last_flags
;
277 s_last_flags
= flags
;
278 if (!isFilePatternAvailable(path
, selection
, ((flags
& LIST_SD_FILE_EXT
) ? nullptr : extension
))) selection
= nullptr;
281 flags
= s_last_flags
;
284 if (popupMenuOffset
== 0) {
285 lastpopupMenuOffset
= 0;
286 memset(reusableBuffer
.modelsel
.menu_bss
, 0, sizeof(reusableBuffer
.modelsel
.menu_bss
));
288 else if (popupMenuOffset
== popupMenuItemsCount
- MENU_MAX_DISPLAY_LINES
) {
289 lastpopupMenuOffset
= 0xffff;
290 memset(reusableBuffer
.modelsel
.menu_bss
, 0, sizeof(reusableBuffer
.modelsel
.menu_bss
));
292 else if (popupMenuOffset
== lastpopupMenuOffset
) {
293 // should not happen, only there because of Murphy's law
296 else if (popupMenuOffset
> lastpopupMenuOffset
) {
297 memmove(reusableBuffer
.modelsel
.menu_bss
[0], reusableBuffer
.modelsel
.menu_bss
[1], (MENU_MAX_DISPLAY_LINES
-1)*MENU_LINE_LENGTH
);
298 memset(reusableBuffer
.modelsel
.menu_bss
[MENU_MAX_DISPLAY_LINES
-1], 0xff, MENU_LINE_LENGTH
);
301 memmove(reusableBuffer
.modelsel
.menu_bss
[1], reusableBuffer
.modelsel
.menu_bss
[0], (MENU_MAX_DISPLAY_LINES
-1)*MENU_LINE_LENGTH
);
302 memset(reusableBuffer
.modelsel
.menu_bss
[0], 0, MENU_LINE_LENGTH
);
305 popupMenuItemsCount
= 0;
307 FRESULT res
= f_opendir(&dir
, path
);
310 if (flags
& LIST_NONE_SD_FILE
) {
311 popupMenuItemsCount
++;
313 lastpopupMenuOffset
++;
315 else if (popupMenuOffset
==0 || popupMenuOffset
< lastpopupMenuOffset
) {
316 char * line
= reusableBuffer
.modelsel
.menu_bss
[0];
317 memset(line
, 0, MENU_LINE_LENGTH
);
319 popupMenuItems
[0] = line
;
324 res
= f_readdir(&dir
, &fno
); /* Read a directory item */
325 if (res
!= FR_OK
|| fno
.fname
[0] == 0) break; /* Break on error or end of dir */
326 if (fno
.fattrib
& AM_DIR
) continue; /* Skip subfolders */
327 if (fno
.fattrib
& AM_HID
) continue; /* Skip hidden files */
328 if (fno
.fattrib
& AM_SYS
) continue; /* Skip system files */
330 fnExt
= getFileExtension(fno
.fname
, 0, 0, &fnLen
, &extLen
);
333 // TRACE_DEBUG("listSdFiles(%s, %s, %u, %s, %u): fn='%s'; fnExt='%s'; match=%d\n",
334 // path, extension, maxlen, (selection ? selection : "nul"), flags, fno.fname, (fnExt ? fnExt : "nul"), (fnExt && isExtensionMatching(fnExt, extension)));
335 // file validation checks
336 if (!fnLen
|| fnLen
> maxlen
|| ( // wrong size
337 fnExt
&& extension
&& ( // extension-based checks follow...
338 !isExtensionMatching(fnExt
, extension
) || ( // wrong extension
339 !(flags
& LIST_SD_FILE_EXT
) && // only if we want unique file names...
340 strcasecmp(fnExt
, getFileExtension(extension
)) && // possible duplicate file name...
341 isFilePatternAvailable(path
, fno
.fname
, extension
, true, tmpExt
) && // find the first file from extensions list...
342 strncasecmp(fnExt
, tmpExt
, LEN_FILE_EXTENSION_MAX
) // found file doesn't match, this is a duplicate
350 popupMenuItemsCount
++;
352 if (!(flags
& LIST_SD_FILE_EXT
)) {
353 fno
.fname
[fnLen
] = '\0'; // strip extension
356 if (popupMenuOffset
== 0) {
357 if (selection
&& strncasecmp(fno
.fname
, selection
, maxlen
) < 0) {
358 lastpopupMenuOffset
++;
361 for (uint8_t i
=0; i
<MENU_MAX_DISPLAY_LINES
; i
++) {
362 char * line
= reusableBuffer
.modelsel
.menu_bss
[i
];
363 if (line
[0] == '\0' || strcasecmp(fno
.fname
, line
) < 0) {
364 if (i
< MENU_MAX_DISPLAY_LINES
-1) memmove(reusableBuffer
.modelsel
.menu_bss
[i
+1], line
, sizeof(reusableBuffer
.modelsel
.menu_bss
[i
]) * (MENU_MAX_DISPLAY_LINES
-1-i
));
365 memset(line
, 0, MENU_LINE_LENGTH
);
366 strcpy(line
, fno
.fname
);
371 for (uint8_t i
=0; i
<min(popupMenuItemsCount
, (uint16_t)MENU_MAX_DISPLAY_LINES
); i
++) {
372 popupMenuItems
[i
] = reusableBuffer
.modelsel
.menu_bss
[i
];
375 else if (lastpopupMenuOffset
== 0xffff) {
376 for (int i
=MENU_MAX_DISPLAY_LINES
-1; i
>=0; i
--) {
377 char * line
= reusableBuffer
.modelsel
.menu_bss
[i
];
378 if (line
[0] == '\0' || strcasecmp(fno
.fname
, line
) > 0) {
379 if (i
> 0) memmove(reusableBuffer
.modelsel
.menu_bss
[0], reusableBuffer
.modelsel
.menu_bss
[1], sizeof(reusableBuffer
.modelsel
.menu_bss
[i
]) * i
);
380 memset(line
, 0, MENU_LINE_LENGTH
);
381 strcpy(line
, fno
.fname
);
385 for (uint8_t i
=0; i
<min(popupMenuItemsCount
, (uint16_t)MENU_MAX_DISPLAY_LINES
); i
++) {
386 popupMenuItems
[i
] = reusableBuffer
.modelsel
.menu_bss
[i
];
389 else if (popupMenuOffset
> lastpopupMenuOffset
) {
390 if (strcasecmp(fno
.fname
, reusableBuffer
.modelsel
.menu_bss
[MENU_MAX_DISPLAY_LINES
-2]) > 0 && strcasecmp(fno
.fname
, reusableBuffer
.modelsel
.menu_bss
[MENU_MAX_DISPLAY_LINES
-1]) < 0) {
391 memset(reusableBuffer
.modelsel
.menu_bss
[MENU_MAX_DISPLAY_LINES
-1], 0, MENU_LINE_LENGTH
);
392 strcpy(reusableBuffer
.modelsel
.menu_bss
[MENU_MAX_DISPLAY_LINES
-1], fno
.fname
);
396 if (strcasecmp(fno
.fname
, reusableBuffer
.modelsel
.menu_bss
[1]) < 0 && strcasecmp(fno
.fname
, reusableBuffer
.modelsel
.menu_bss
[0]) > 0) {
397 memset(reusableBuffer
.modelsel
.menu_bss
[0], 0, MENU_LINE_LENGTH
);
398 strcpy(reusableBuffer
.modelsel
.menu_bss
[0], fno
.fname
);
405 if (popupMenuOffset
> 0)
406 lastpopupMenuOffset
= popupMenuOffset
;
408 popupMenuOffset
= lastpopupMenuOffset
;
410 return popupMenuItemsCount
;
413 constexpr uint32_t TEXT_FILE_MAXSIZE
= 2048;
415 void sdReadTextFile(const char * filename
, char lines
[NUM_BODY_LINES
][LCD_COLS
+ 1], int & lines_count
)
423 char escape_chars
[4] = {0};
424 int current_line
= 0;
426 memclear(lines
, NUM_BODY_LINES
* (LCD_COLS
+ 1));
428 result
= f_open(&file
, filename
, FA_OPEN_EXISTING
| FA_READ
);
429 if (result
== FR_OK
) {
430 for (unsigned i
= 0; i
< TEXT_FILE_MAXSIZE
&& f_read(&file
, &c
, 1, &sz
) == FR_OK
&& sz
== 1 && (lines_count
== 0 || current_line
- menuVerticalOffset
< NUM_BODY_LINES
); i
++) {
436 else if (c
!='\r' && current_line
>=menuVerticalOffset
&& current_line
-menuVerticalOffset
<NUM_BODY_LINES
&& line_length
<LCD_COLS
) {
437 if (c
== '\\' && escape
== 0) {
441 else if (c
!= '\\' && escape
> 0 && escape
< sizeof(escape_chars
)) {
442 escape_chars
[escape
- 1] = c
;
443 if (escape
== 2 && !strncmp(escape_chars
, "up", 2)) {
446 else if (escape
== 2 && !strncmp(escape_chars
, "dn", 2)) {
449 else if (escape
== 3) {
450 int val
= atoi(escape_chars
);
451 if (val
>= 200 && val
< 225) {
452 c
= '\200' + val
-200;
467 lines
[current_line
-menuVerticalOffset
][line_length
++] = c
;
476 if (lines_count
== 0) {
477 lines_count
= current_line
;
481 // returns true if current working dir is at the root level
485 if (f_getcwd(path
, sizeof(path
)-1) == FR_OK
) {
486 return (strcasecmp("/", path
) == 0);
492 Wrapper around the f_readdir() function which
493 also returns ".." entry for sub-dirs. (FatFS 0.12 does
494 not return ".", ".." dirs anymore)
496 FRESULT
sdReadDir(DIR * dir
, FILINFO
* fno
, bool & firstTime
)
499 if (firstTime
&& !isCwdAtRoot()) {
500 // fake parent directory entry
501 strcpy(fno
->fname
, "..");
502 fno
->fattrib
= AM_DIR
;
506 res
= f_readdir(dir
, fno
); /* Read a directory item */
513 const char * sdCopyFile(const char * srcPath
, const char * destPath
)
518 UINT read
= sizeof(buf
);
519 UINT written
= sizeof(buf
);
521 FRESULT result
= f_open(&srcFile
, srcPath
, FA_OPEN_EXISTING
| FA_READ
);
522 if (result
!= FR_OK
) {
523 return SDCARD_ERROR(result
);
526 result
= f_open(&destFile
, destPath
, FA_CREATE_ALWAYS
| FA_WRITE
);
527 if (result
!= FR_OK
) {
529 return SDCARD_ERROR(result
);
532 while (result
==FR_OK
&& read
==sizeof(buf
) && written
==sizeof(buf
)) {
533 result
= f_read(&srcFile
, buf
, sizeof(buf
), &read
);
534 if (result
== FR_OK
) {
535 result
= f_write(&destFile
, buf
, read
, &written
);
542 if (result
!= FR_OK
) {
543 return SDCARD_ERROR(result
);
549 const char * sdCopyFile(const char * srcFilename
, const char * srcDir
, const char * destFilename
, const char * destDir
)
551 char srcPath
[2*CLIPBOARD_PATH_LEN
+1];
552 char * tmp
= strAppend(srcPath
, srcDir
, CLIPBOARD_PATH_LEN
);
554 strAppend(tmp
, srcFilename
, CLIPBOARD_PATH_LEN
);
556 char destPath
[2*CLIPBOARD_PATH_LEN
+1];
557 tmp
= strAppend(destPath
, destDir
, CLIPBOARD_PATH_LEN
);
559 strAppend(tmp
, destFilename
, CLIPBOARD_PATH_LEN
);
561 return sdCopyFile(srcPath
, destPath
);
563 #endif // defined(SDCARD)
566 #if !defined(SIMU) || defined(SIMU_DISKIO)
567 uint32_t sdGetNoSectors()
569 static DWORD noSectors
= 0;
570 if (noSectors
== 0 ) {
571 disk_ioctl(0, GET_SECTOR_COUNT
, &noSectors
);
578 return (sdGetNoSectors() / 1000000) * BLOCK_SIZE
;
581 uint32_t sdGetFreeSectors()
585 if (f_getfree("", &nofree
, &fat
) != FR_OK
) {
588 return nofree
* fat
->csize
;
591 #else // #if !defined(SIMU) || defined(SIMU_DISKIO)
593 uint32_t sdGetNoSectors()
603 uint32_t sdGetFreeSectors()
608 #endif // #if !defined(SIMU) || defined(SIMU_DISKIO)