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
, 0) == 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
= NULL
, bool exclDir
= true, char * match
= NULL
)
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
== NULL
) {
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
!= NULL
) strncat(&(match
[0]='\0'), ext
, extlen
);
138 ext
= getFileExtension(pattern
, plen
, 0, NULL
, &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, NULL
, &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
, NULL
, false)) {
197 const char * getFileExtension(const char * filename
, uint8_t size
, uint8_t extMaxLen
, uint8_t *fnlen
, uint8_t *extlen
)
201 len
= strlen(filename
);
204 extMaxLen
= LEN_FILE_EXTENSION_MAX
;
207 *fnlen
= (uint8_t)len
;
209 for (int i
=len
-1; i
>= 0 && len
-i
<= extMaxLen
; --i
) {
210 if (filename
[i
] == '.') {
217 if (extlen
!= NULL
) {
224 Check if given extension exists in a list of extensions.
225 @param extension The extension to search for, including leading period.
226 @param pattern One or more file extensions concatenated together, including the periods.
227 The list is searched backwards and the first match, if any, is returned.
228 eg: ".gif.jpg.jpeg.png"
229 @param match Optional container to hold the matched file extension (wide enough to hold LEN_FILE_EXTENSION_MAX + 1).
230 @retval true if a extension was found in the lost, false otherwise.
232 bool isExtensionMatching(const char * extension
, const char * pattern
, char * match
)
235 uint8_t extlen
, fnlen
;
238 ext
= getFileExtension(pattern
, 0, 0, &fnlen
, &extlen
);
240 while (plen
> 0 && ext
) {
241 if (!strncasecmp(extension
, ext
, extlen
)) {
242 if (match
!= NULL
) strncat(&(match
[0]='\0'), ext
, extlen
);
247 ext
= getFileExtension(pattern
, plen
, 0, NULL
, &extlen
);
253 bool sdListFiles(const char * path
, const char * extension
, const uint8_t maxlen
, const char * selection
, uint8_t flags
)
255 static uint16_t lastpopupMenuOffset
= 0;
259 uint8_t fnLen
, extLen
;
260 char tmpExt
[LEN_FILE_EXTENSION_MAX
+1] = "\0";
263 popupMenuOffsetType
= MENU_OFFSET_EXTERNAL
;
267 static uint8_t s_last_flags
;
270 s_last_flags
= flags
;
271 if (!isFilePatternAvailable(path
, selection
, ((flags
& LIST_SD_FILE_EXT
) ? NULL
: extension
))) selection
= NULL
;
274 flags
= s_last_flags
;
278 if (popupMenuOffset
== 0) {
279 lastpopupMenuOffset
= 0;
280 memset(reusableBuffer
.modelsel
.menu_bss
, 0, sizeof(reusableBuffer
.modelsel
.menu_bss
));
282 else if (popupMenuOffset
== popupMenuNoItems
- MENU_MAX_DISPLAY_LINES
) {
283 lastpopupMenuOffset
= 0xffff;
284 memset(reusableBuffer
.modelsel
.menu_bss
, 0, sizeof(reusableBuffer
.modelsel
.menu_bss
));
286 else if (popupMenuOffset
== lastpopupMenuOffset
) {
287 // should not happen, only there because of Murphy's law
290 else if (popupMenuOffset
> lastpopupMenuOffset
) {
291 memmove(reusableBuffer
.modelsel
.menu_bss
[0], reusableBuffer
.modelsel
.menu_bss
[1], (MENU_MAX_DISPLAY_LINES
-1)*MENU_LINE_LENGTH
);
292 memset(reusableBuffer
.modelsel
.menu_bss
[MENU_MAX_DISPLAY_LINES
-1], 0xff, MENU_LINE_LENGTH
);
295 memmove(reusableBuffer
.modelsel
.menu_bss
[1], reusableBuffer
.modelsel
.menu_bss
[0], (MENU_MAX_DISPLAY_LINES
-1)*MENU_LINE_LENGTH
);
296 memset(reusableBuffer
.modelsel
.menu_bss
[0], 0, MENU_LINE_LENGTH
);
299 popupMenuNoItems
= 0;
300 POPUP_MENU_SET_BSS_FLAG();
302 FRESULT res
= f_opendir(&dir
, path
);
305 if (flags
& LIST_NONE_SD_FILE
) {
308 lastpopupMenuOffset
++;
310 else if (popupMenuOffset
==0 || popupMenuOffset
< lastpopupMenuOffset
) {
311 char * line
= reusableBuffer
.modelsel
.menu_bss
[0];
312 memset(line
, 0, MENU_LINE_LENGTH
);
314 popupMenuItems
[0] = line
;
319 res
= f_readdir(&dir
, &fno
); /* Read a directory item */
320 if (res
!= FR_OK
|| fno
.fname
[0] == 0) break; /* Break on error or end of dir */
321 if (fno
.fattrib
& AM_DIR
) continue; /* Skip subfolders */
322 if (fno
.fattrib
& AM_HID
) continue; /* Skip hidden files */
323 if (fno
.fattrib
& AM_SYS
) continue; /* Skip system files */
325 fnExt
= getFileExtension(fno
.fname
, 0, 0, &fnLen
, &extLen
);
328 // TRACE_DEBUG("listSdFiles(%s, %s, %u, %s, %u): fn='%s'; fnExt='%s'; match=%d\n",
329 // path, extension, maxlen, (selection ? selection : "nul"), flags, fno.fname, (fnExt ? fnExt : "nul"), (fnExt && isExtensionMatching(fnExt, extension)));
330 // file validation checks
331 if (!fnLen
|| fnLen
> maxlen
|| ( // wrong size
332 fnExt
&& extension
&& ( // extension-based checks follow...
333 !isExtensionMatching(fnExt
, extension
) || ( // wrong extension
334 !(flags
& LIST_SD_FILE_EXT
) && // only if we want unique file names...
335 strcasecmp(fnExt
, getFileExtension(extension
)) && // possible duplicate file name...
336 isFilePatternAvailable(path
, fno
.fname
, extension
, true, tmpExt
) && // find the first file from extensions list...
337 strncasecmp(fnExt
, tmpExt
, LEN_FILE_EXTENSION_MAX
) // found file doesn't match, this is a duplicate
347 if (!(flags
& LIST_SD_FILE_EXT
)) {
348 fno
.fname
[fnLen
] = '\0'; // strip extension
351 if (popupMenuOffset
== 0) {
352 if (selection
&& strncasecmp(fno
.fname
, selection
, maxlen
) < 0) {
353 lastpopupMenuOffset
++;
356 for (uint8_t i
=0; i
<MENU_MAX_DISPLAY_LINES
; i
++) {
357 char * line
= reusableBuffer
.modelsel
.menu_bss
[i
];
358 if (line
[0] == '\0' || strcasecmp(fno
.fname
, line
) < 0) {
359 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
));
360 memset(line
, 0, MENU_LINE_LENGTH
);
361 strcpy(line
, fno
.fname
);
366 for (uint8_t i
=0; i
<min(popupMenuNoItems
, (uint16_t)MENU_MAX_DISPLAY_LINES
); i
++) {
367 popupMenuItems
[i
] = reusableBuffer
.modelsel
.menu_bss
[i
];
371 else if (lastpopupMenuOffset
== 0xffff) {
372 for (int i
=MENU_MAX_DISPLAY_LINES
-1; i
>=0; i
--) {
373 char * line
= reusableBuffer
.modelsel
.menu_bss
[i
];
374 if (line
[0] == '\0' || strcasecmp(fno
.fname
, line
) > 0) {
375 if (i
> 0) memmove(reusableBuffer
.modelsel
.menu_bss
[0], reusableBuffer
.modelsel
.menu_bss
[1], sizeof(reusableBuffer
.modelsel
.menu_bss
[i
]) * i
);
376 memset(line
, 0, MENU_LINE_LENGTH
);
377 strcpy(line
, fno
.fname
);
381 for (uint8_t i
=0; i
<min(popupMenuNoItems
, (uint16_t)MENU_MAX_DISPLAY_LINES
); i
++) {
382 popupMenuItems
[i
] = reusableBuffer
.modelsel
.menu_bss
[i
];
385 else if (popupMenuOffset
> lastpopupMenuOffset
) {
386 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) {
387 memset(reusableBuffer
.modelsel
.menu_bss
[MENU_MAX_DISPLAY_LINES
-1], 0, MENU_LINE_LENGTH
);
388 strcpy(reusableBuffer
.modelsel
.menu_bss
[MENU_MAX_DISPLAY_LINES
-1], fno
.fname
);
392 if (strcasecmp(fno
.fname
, reusableBuffer
.modelsel
.menu_bss
[1]) < 0 && strcasecmp(fno
.fname
, reusableBuffer
.modelsel
.menu_bss
[0]) > 0) {
393 memset(reusableBuffer
.modelsel
.menu_bss
[0], 0, MENU_LINE_LENGTH
);
394 strcpy(reusableBuffer
.modelsel
.menu_bss
[0], fno
.fname
);
401 if (popupMenuOffset
> 0)
402 lastpopupMenuOffset
= popupMenuOffset
;
404 popupMenuOffset
= lastpopupMenuOffset
;
406 return popupMenuNoItems
;
409 // returns true if current working dir is at the root level
413 if (f_getcwd(path
, sizeof(path
)-1) == FR_OK
) {
414 return (strcasecmp("/", path
) == 0);
420 Wrapper around the f_readdir() function which
421 also returns ".." entry for sub-dirs. (FatFS 0.12 does
422 not return ".", ".." dirs anymore)
424 FRESULT
sdReadDir(DIR * dir
, FILINFO
* fno
, bool & firstTime
)
427 if (firstTime
&& !isCwdAtRoot()) {
428 // fake parent directory entry
429 strcpy(fno
->fname
, "..");
430 fno
->fattrib
= AM_DIR
;
434 res
= f_readdir(dir
, fno
); /* Read a directory item */
440 #if defined(CPUARM) && defined(SDCARD)
441 const char * sdCopyFile(const char * srcPath
, const char * destPath
)
446 UINT read
= sizeof(buf
);
447 UINT written
= sizeof(buf
);
449 FRESULT result
= f_open(&srcFile
, srcPath
, FA_OPEN_EXISTING
| FA_READ
);
450 if (result
!= FR_OK
) {
451 return SDCARD_ERROR(result
);
454 result
= f_open(&destFile
, destPath
, FA_CREATE_ALWAYS
| FA_WRITE
);
455 if (result
!= FR_OK
) {
457 return SDCARD_ERROR(result
);
460 while (result
==FR_OK
&& read
==sizeof(buf
) && written
==sizeof(buf
)) {
461 result
= f_read(&srcFile
, buf
, sizeof(buf
), &read
);
462 if (result
== FR_OK
) {
463 result
= f_write(&destFile
, buf
, read
, &written
);
470 if (result
!= FR_OK
) {
471 return SDCARD_ERROR(result
);
477 const char * sdCopyFile(const char * srcFilename
, const char * srcDir
, const char * destFilename
, const char * destDir
)
479 char srcPath
[2*CLIPBOARD_PATH_LEN
+1];
480 char * tmp
= strAppend(srcPath
, srcDir
, CLIPBOARD_PATH_LEN
);
482 strAppend(tmp
, srcFilename
, CLIPBOARD_PATH_LEN
);
484 char destPath
[2*CLIPBOARD_PATH_LEN
+1];
485 tmp
= strAppend(destPath
, destDir
, CLIPBOARD_PATH_LEN
);
487 strAppend(tmp
, destFilename
, CLIPBOARD_PATH_LEN
);
489 return sdCopyFile(srcPath
, destPath
);
491 #endif // defined(CPUARM) && defined(SDCARD)
494 #if !defined(SIMU) || defined(SIMU_DISKIO)
495 uint32_t sdGetNoSectors()
497 static DWORD noSectors
= 0;
498 if (noSectors
== 0 ) {
499 disk_ioctl(0, GET_SECTOR_COUNT
, &noSectors
);
506 return (sdGetNoSectors() / 1000000) * BLOCK_SIZE
;
509 uint32_t sdGetFreeSectors()
513 if (f_getfree("", &nofree
, &fat
) != FR_OK
) {
516 return nofree
* fat
->csize
;
519 #else // #if !defined(SIMU) || defined(SIMU_DISKIO)
521 uint32_t sdGetNoSectors()
531 uint32_t sdGetFreeSectors()
536 #endif // #if !defined(SIMU) || defined(SIMU_DISKIO)