fix models list reload after USB mass storage connection (#5963)
[opentx.git] / radio / src / sdcard.cpp
blobb24759c203d50731f737206023f01643194b49d6
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
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.
21 #include <stdint.h>
22 #include "opentx.h"
23 #include "diskio.h"
25 bool sdCardFormat()
27 BYTE work[_MAX_SS];
28 FRESULT res = f_mkfs("", FM_FAT32, 0, work, sizeof(work));
29 switch(res) {
30 case FR_OK :
31 return true;
32 case FR_DISK_ERR:
33 POPUP_WARNING("Format error");
34 return false;
35 case FR_NOT_READY:
36 POPUP_WARNING("SDCard not ready");
37 return false;
38 case FR_WRITE_PROTECTED:
39 POPUP_WARNING("SDCard write protected");
40 return false;
41 case FR_INVALID_PARAMETER:
42 POPUP_WARNING("Format param invalid");
43 return false;
44 case FR_INVALID_DRIVE:
45 POPUP_WARNING("Invalid drive");
46 return false;
47 case FR_MKFS_ABORTED:
48 POPUP_WARNING("Format aborted");
49 return false;
50 default:
51 POPUP_WARNING(STR_SDCARD_ERROR);
52 return false;
56 const char * sdCheckAndCreateDirectory(const char * path)
58 DIR archiveFolder;
60 FRESULT result = f_opendir(&archiveFolder, path);
61 if (result != FR_OK) {
62 if (result == FR_NO_PATH)
63 result = f_mkdir(path);
64 if (result != FR_OK)
65 return SDCARD_ERROR(result);
67 else {
68 f_closedir(&archiveFolder);
71 return NULL;
74 bool isFileAvailable(const char * path, bool exclDir)
76 if (exclDir) {
77 FILINFO fno;
78 return (f_stat(path, &fno) == FR_OK && !(fno.fattrib & AM_DIR));
80 return f_stat(path, 0) == FR_OK;
83 /**
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)
100 uint8_t fplen;
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);
106 return false;
109 strcpy(fqfp, path);
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);
117 else {
118 // extensions list search
119 const char *ext;
120 uint16_t len;
121 uint8_t extlen, fnlen;
122 int plen;
124 getFileExtension(file, 0, 0, &fnlen, &extlen);
125 len = fplen + fnlen - extlen;
126 fqfp[len] = '\0';
127 ext = getFileExtension(pattern, 0, 0, &fnlen, &extlen);
128 plen = (int)fnlen;
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);
133 return true;
135 plen -= extlen;
136 if (plen > 0) {
137 fqfp[len] = '\0';
138 ext = getFileExtension(pattern, plen, 0, NULL, &extlen);
142 return false;
145 char * getFileIndex(char * filename, unsigned int & value)
147 value = 0;
148 char * pos = (char *)getFileExtension(filename);
149 if (!pos || pos == filename)
150 return NULL;
151 int multiplier = 1;
152 while (pos > filename) {
153 pos--;
154 char c = *pos;
155 if (c >= '0' && c <= '9') {
156 value += multiplier * (c - '0');
157 multiplier *= 10;
159 else {
160 return pos+1;
163 return filename;
166 uint8_t getDigitsCount(unsigned int value)
168 uint8_t count = 1;
169 while (value >= 10) {
170 value /= 10;
171 ++count;
173 return count;
176 int findNextFileIndex(char * filename, uint8_t size, const char * directory)
178 unsigned int index;
179 uint8_t extlen;
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);
184 while (1) {
185 index++;
186 if ((indexPos - filename) + getDigitsCount(index) + extlen > size) {
187 return 0;
189 char * pos = strAppendUnsigned(indexPos, index);
190 strAppend(pos, extension);
191 if (!isFilePatternAvailable(directory, filename, NULL, false)) {
192 return index;
197 const char * getFileExtension(const char * filename, uint8_t size, uint8_t extMaxLen, uint8_t *fnlen, uint8_t *extlen)
199 int len = size;
200 if (!size) {
201 len = strlen(filename);
203 if (!extMaxLen) {
204 extMaxLen = LEN_FILE_EXTENSION_MAX;
206 if (fnlen != NULL) {
207 *fnlen = (uint8_t)len;
209 for (int i=len-1; i >= 0 && len-i <= extMaxLen; --i) {
210 if (filename[i] == '.') {
211 if (extlen) {
212 *extlen = len-i;
214 return &filename[i];
217 if (extlen != NULL) {
218 *extlen = 0;
220 return 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)
234 const char *ext;
235 uint8_t extlen, fnlen;
236 int plen;
238 ext = getFileExtension(pattern, 0, 0, &fnlen, &extlen);
239 plen = (int)fnlen;
240 while (plen > 0 && ext) {
241 if (!strncasecmp(extension, ext, extlen)) {
242 if (match != NULL) strncat(&(match[0]='\0'), ext, extlen);
243 return true;
245 plen -= extlen;
246 if (plen > 0) {
247 ext = getFileExtension(pattern, plen, 0, NULL, &extlen);
250 return false;
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;
256 FILINFO fno;
257 DIR dir;
258 const char * fnExt;
259 uint8_t fnLen, extLen;
260 char tmpExt[LEN_FILE_EXTENSION_MAX+1] = "\0";
262 #if defined(CPUARM)
263 popupMenuOffsetType = MENU_OFFSET_EXTERNAL;
264 #endif
266 #if defined(CPUARM)
267 static uint8_t s_last_flags;
269 if (selection) {
270 s_last_flags = flags;
271 if (!isFilePatternAvailable(path, selection, ((flags & LIST_SD_FILE_EXT) ? NULL : extension))) selection = NULL;
273 else {
274 flags = s_last_flags;
276 #endif
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
288 return true;
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);
294 else {
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);
303 if (res == FR_OK) {
305 if (flags & LIST_NONE_SD_FILE) {
306 popupMenuNoItems++;
307 if (selection) {
308 lastpopupMenuOffset++;
310 else if (popupMenuOffset==0 || popupMenuOffset < lastpopupMenuOffset) {
311 char * line = reusableBuffer.modelsel.menu_bss[0];
312 memset(line, 0, MENU_LINE_LENGTH);
313 strcpy(line, "---");
314 popupMenuItems[0] = line;
318 for (;;) {
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);
326 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
342 continue;
345 popupMenuNoItems++;
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++;
355 else {
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);
362 break;
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);
378 break;
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);
391 else {
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);
398 f_closedir(&dir);
401 if (popupMenuOffset > 0)
402 lastpopupMenuOffset = popupMenuOffset;
403 else
404 popupMenuOffset = lastpopupMenuOffset;
406 return popupMenuNoItems;
409 // returns true if current working dir is at the root level
410 bool isCwdAtRoot()
412 char path[10];
413 if (f_getcwd(path, sizeof(path)-1) == FR_OK) {
414 return (strcasecmp("/", path) == 0);
416 return false;
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)
426 FRESULT res;
427 if (firstTime && !isCwdAtRoot()) {
428 // fake parent directory entry
429 strcpy(fno->fname, "..");
430 fno->fattrib = AM_DIR;
431 res = FR_OK;
433 else {
434 res = f_readdir(dir, fno); /* Read a directory item */
436 firstTime = false;
437 return res;
440 #if defined(CPUARM) && defined(SDCARD)
441 const char * sdCopyFile(const char * srcPath, const char * destPath)
443 FIL srcFile;
444 FIL destFile;
445 char buf[256];
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) {
456 f_close(&srcFile);
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);
467 f_close(&destFile);
468 f_close(&srcFile);
470 if (result != FR_OK) {
471 return SDCARD_ERROR(result);
474 return NULL;
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);
481 *tmp++ = '/';
482 strAppend(tmp, srcFilename, CLIPBOARD_PATH_LEN);
484 char destPath[2*CLIPBOARD_PATH_LEN+1];
485 tmp = strAppend(destPath, destDir, CLIPBOARD_PATH_LEN);
486 *tmp++ = '/';
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);
501 return noSectors;
504 uint32_t sdGetSize()
506 return (sdGetNoSectors() / 1000000) * BLOCK_SIZE;
509 uint32_t sdGetFreeSectors()
511 DWORD nofree;
512 FATFS * fat;
513 if (f_getfree("", &nofree, &fat) != FR_OK) {
514 return 0;
516 return nofree * fat->csize;
519 #else // #if !defined(SIMU) || defined(SIMU_DISKIO)
521 uint32_t sdGetNoSectors()
523 return 0;
526 uint32_t sdGetSize()
528 return 0;
531 uint32_t sdGetFreeSectors()
533 return 10;
536 #endif // #if !defined(SIMU) || defined(SIMU_DISKIO)