Fix #4881 (#4884)
[opentx.git] / radio / src / sdcard.cpp
blobc3544c10b9b438599d2c7d2490c65ccc2e36dc63
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 const char * sdCheckAndCreateDirectory(const char * path)
27 DIR archiveFolder;
29 FRESULT result = f_opendir(&archiveFolder, path);
30 if (result != FR_OK) {
31 if (result == FR_NO_PATH)
32 result = f_mkdir(path);
33 if (result != FR_OK)
34 return SDCARD_ERROR(result);
36 else {
37 f_closedir(&archiveFolder);
40 return NULL;
43 bool isFileAvailable(const char * path, bool exclDir)
45 if (exclDir) {
46 FILINFO fno;
47 return (f_stat(path, &fno) == FR_OK && !(fno.fattrib & AM_DIR));
49 return f_stat(path, 0) == FR_OK;
52 /**
53 Search file system path for a file. Can optionally take a list of file extensions to search through.
54 Eg. find "splash.bmp", or the first occurrence of one of "splash.[bmp|jpeg|jpg|gif]".
56 @param path String with path name, no trailing slash. eg; "/BITMAPS"
57 @param file String containing file name to search for, with or w/out an extension.
58 eg; "splash.bmp" or "splash"
59 @param pattern Optional list of one or more file extensions concatenated together, including the period(s).
60 The list is searched backwards and the first match, if any, is returned. If null, then only the actual filename
61 passed will be searched for.
62 eg: ".gif.jpg.jpeg.bmp"
63 @param exclDir true/false whether to allow directory matches (default true, excludes directories)
64 @param match Optional container to hold the matched file extension (wide enough to hold LEN_FILE_EXTENSION_MAX + 1).
65 @retval true if a file was found, false otherwise.
67 bool isFilePatternAvailable(const char * path, const char * file, const char * pattern = NULL, bool exclDir = true, char * match = NULL)
69 uint8_t fplen;
70 char fqfp[LEN_FILE_PATH_MAX + _MAX_LFN + 1] = "\0";
72 fplen = strlen(path);
73 if (fplen > LEN_FILE_PATH_MAX) {
74 TRACE_ERROR("isFilePatternAvailable(%s) = error: file path too long.\n", path, file);
75 return false;
78 strcpy(fqfp, path);
79 strcpy(fqfp + fplen, "/");
80 strncat(fqfp + (++fplen), file, _MAX_LFN);
82 if (pattern == NULL) {
83 // no extensions list, just check the filename as-is
84 return isFileAvailable(fqfp, exclDir);
86 else {
87 // extensions list search
88 const char *ext;
89 uint16_t len;
90 uint8_t extlen, fnlen;
91 int plen;
93 getFileExtension(file, 0, 0, &fnlen, &extlen);
94 len = fplen + fnlen - extlen;
95 fqfp[len] = '\0';
96 ext = getFileExtension(pattern, 0, 0, &fnlen, &extlen);
97 plen = (int)fnlen;
98 while (plen > 0 && ext) {
99 strncat(fqfp + len, ext, extlen);
100 if (isFileAvailable(fqfp, exclDir)) {
101 if (match != NULL) strncat(&(match[0]='\0'), ext, extlen);
102 return true;
104 plen -= extlen;
105 if (plen > 0) {
106 fqfp[len] = '\0';
107 ext = getFileExtension(pattern, plen, 0, NULL, &extlen);
111 return false;
114 char * getFileIndex(char * filename, unsigned int & value)
116 value = 0;
117 char * pos = (char *)getFileExtension(filename);
118 if (!pos || pos == filename)
119 return NULL;
120 int multiplier = 1;
121 while (pos > filename) {
122 pos--;
123 char c = *pos;
124 if (c >= '0' && c <= '9') {
125 value += multiplier * (c - '0');
126 multiplier *= 10;
128 else {
129 return pos+1;
132 return filename;
135 uint8_t getDigitsCount(unsigned int value)
137 uint8_t count = 1;
138 while (value >= 10) {
139 value /= 10;
140 ++count;
142 return count;
145 int findNextFileIndex(char * filename, uint8_t size, const char * directory)
147 unsigned int index;
148 uint8_t extlen;
149 char * indexPos = getFileIndex(filename, index);
150 char extension[LEN_FILE_EXTENSION_MAX+1] = "\0";
151 char * p = (char *)getFileExtension(filename, 0, 0, NULL, &extlen);
152 if (p) strncat(extension, p, sizeof(extension)-1);
153 while (1) {
154 index++;
155 if ((indexPos - filename) + getDigitsCount(index) + extlen > size) {
156 return 0;
158 char * pos = strAppendUnsigned(indexPos, index);
159 strAppend(pos, extension);
160 if (!isFilePatternAvailable(directory, filename, NULL, false)) {
161 return index;
166 const char * getFileExtension(const char * filename, uint8_t size, uint8_t extMaxLen, uint8_t *fnlen, uint8_t *extlen)
168 int len = size;
169 if (!size) {
170 len = strlen(filename);
172 if (!extMaxLen) {
173 extMaxLen = LEN_FILE_EXTENSION_MAX;
175 if (fnlen != NULL) {
176 *fnlen = (uint8_t)len;
178 for (int i=len-1; i >= 0 && len-i <= extMaxLen; --i) {
179 if (filename[i] == '.') {
180 if (extlen) {
181 *extlen = len-i;
183 return &filename[i];
186 if (extlen != NULL) {
187 *extlen = 0;
189 return NULL;
193 Check if given extension exists in a list of extensions.
194 @param extension The extension to search for, including leading period.
195 @param pattern One or more file extensions concatenated together, including the periods.
196 The list is searched backwards and the first match, if any, is returned.
197 eg: ".gif.jpg.jpeg.png"
198 @param match Optional container to hold the matched file extension (wide enough to hold LEN_FILE_EXTENSION_MAX + 1).
199 @retval true if a extension was found in the lost, false otherwise.
201 bool isExtensionMatching(const char * extension, const char * pattern, char * match)
203 const char *ext;
204 uint8_t extlen, fnlen;
205 int plen;
207 ext = getFileExtension(pattern, 0, 0, &fnlen, &extlen);
208 plen = (int)fnlen;
209 while (plen > 0 && ext) {
210 if (!strncasecmp(extension, ext, extlen)) {
211 if (match != NULL) strncat(&(match[0]='\0'), ext, extlen);
212 return true;
214 plen -= extlen;
215 if (plen > 0) {
216 ext = getFileExtension(pattern, plen, 0, NULL, &extlen);
219 return false;
222 bool sdListFiles(const char * path, const char * extension, const uint8_t maxlen, const char * selection, uint8_t flags)
224 static uint16_t lastpopupMenuOffset = 0;
225 FILINFO fno;
226 DIR dir;
227 const char * fnExt;
228 uint8_t fnLen, extLen;
229 char tmpExt[LEN_FILE_EXTENSION_MAX+1] = "\0";
231 #if defined(CPUARM)
232 popupMenuOffsetType = MENU_OFFSET_EXTERNAL;
233 #endif
235 #if defined(CPUARM)
236 static uint8_t s_last_flags;
238 if (selection) {
239 s_last_flags = flags;
240 if (!isFilePatternAvailable(path, selection, ((flags & LIST_SD_FILE_EXT) ? NULL : extension))) selection = NULL;
242 else {
243 flags = s_last_flags;
245 #endif
247 if (popupMenuOffset == 0) {
248 lastpopupMenuOffset = 0;
249 memset(reusableBuffer.modelsel.menu_bss, 0, sizeof(reusableBuffer.modelsel.menu_bss));
251 else if (popupMenuOffset == popupMenuNoItems - MENU_MAX_DISPLAY_LINES) {
252 lastpopupMenuOffset = 0xffff;
253 memset(reusableBuffer.modelsel.menu_bss, 0, sizeof(reusableBuffer.modelsel.menu_bss));
255 else if (popupMenuOffset == lastpopupMenuOffset) {
256 // should not happen, only there because of Murphy's law
257 return true;
259 else if (popupMenuOffset > lastpopupMenuOffset) {
260 memmove(reusableBuffer.modelsel.menu_bss[0], reusableBuffer.modelsel.menu_bss[1], (MENU_MAX_DISPLAY_LINES-1)*MENU_LINE_LENGTH);
261 memset(reusableBuffer.modelsel.menu_bss[MENU_MAX_DISPLAY_LINES-1], 0xff, MENU_LINE_LENGTH);
263 else {
264 memmove(reusableBuffer.modelsel.menu_bss[1], reusableBuffer.modelsel.menu_bss[0], (MENU_MAX_DISPLAY_LINES-1)*MENU_LINE_LENGTH);
265 memset(reusableBuffer.modelsel.menu_bss[0], 0, MENU_LINE_LENGTH);
268 popupMenuNoItems = 0;
269 POPUP_MENU_SET_BSS_FLAG();
271 FRESULT res = f_opendir(&dir, path);
272 if (res == FR_OK) {
274 if (flags & LIST_NONE_SD_FILE) {
275 popupMenuNoItems++;
276 if (selection) {
277 lastpopupMenuOffset++;
279 else if (popupMenuOffset==0 || popupMenuOffset < lastpopupMenuOffset) {
280 char * line = reusableBuffer.modelsel.menu_bss[0];
281 memset(line, 0, MENU_LINE_LENGTH);
282 strcpy(line, "---");
283 popupMenuItems[0] = line;
287 for (;;) {
288 res = f_readdir(&dir, &fno); /* Read a directory item */
289 if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
290 if (fno.fattrib & AM_DIR) continue; /* Skip subfolders */
291 if (fno.fattrib & AM_HID) continue; /* Skip hidden files */
292 if (fno.fattrib & AM_SYS) continue; /* Skip system files */
294 fnExt = getFileExtension(fno.fname, 0, 0, &fnLen, &extLen);
295 fnLen -= extLen;
297 // TRACE_DEBUG("listSdFiles(%s, %s, %u, %s, %u): fn='%s'; fnExt='%s'; match=%d\n",
298 // path, extension, maxlen, (selection ? selection : "nul"), flags, fno.fname, (fnExt ? fnExt : "nul"), (fnExt && isExtensionMatching(fnExt, extension)));
299 // file validation checks
300 if (!fnLen || fnLen > maxlen || ( // wrong size
301 fnExt && extension && ( // extension-based checks follow...
302 !isExtensionMatching(fnExt, extension) || ( // wrong extension
303 !(flags & LIST_SD_FILE_EXT) && // only if we want unique file names...
304 strcasecmp(fnExt, getFileExtension(extension)) && // possible duplicate file name...
305 isFilePatternAvailable(path, fno.fname, extension, true, tmpExt) && // find the first file from extensions list...
306 strncasecmp(fnExt, tmpExt, LEN_FILE_EXTENSION_MAX) // found file doesn't match, this is a duplicate
311 continue;
314 popupMenuNoItems++;
316 if (!(flags & LIST_SD_FILE_EXT)) {
317 fno.fname[fnLen] = '\0'; // strip extension
320 if (popupMenuOffset == 0) {
321 if (selection && strncasecmp(fno.fname, selection, maxlen) < 0) {
322 lastpopupMenuOffset++;
324 else {
325 for (uint8_t i=0; i<MENU_MAX_DISPLAY_LINES; i++) {
326 char * line = reusableBuffer.modelsel.menu_bss[i];
327 if (line[0] == '\0' || strcasecmp(fno.fname, line) < 0) {
328 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));
329 memset(line, 0, MENU_LINE_LENGTH);
330 strcpy(line, fno.fname);
331 break;
335 for (uint8_t i=0; i<min(popupMenuNoItems, (uint16_t)MENU_MAX_DISPLAY_LINES); i++) {
336 popupMenuItems[i] = reusableBuffer.modelsel.menu_bss[i];
340 else if (lastpopupMenuOffset == 0xffff) {
341 for (int i=MENU_MAX_DISPLAY_LINES-1; i>=0; i--) {
342 char * line = reusableBuffer.modelsel.menu_bss[i];
343 if (line[0] == '\0' || strcasecmp(fno.fname, line) > 0) {
344 if (i > 0) memmove(reusableBuffer.modelsel.menu_bss[0], reusableBuffer.modelsel.menu_bss[1], sizeof(reusableBuffer.modelsel.menu_bss[i]) * i);
345 memset(line, 0, MENU_LINE_LENGTH);
346 strcpy(line, fno.fname);
347 break;
350 for (uint8_t i=0; i<min(popupMenuNoItems, (uint16_t)MENU_MAX_DISPLAY_LINES); i++) {
351 popupMenuItems[i] = reusableBuffer.modelsel.menu_bss[i];
354 else if (popupMenuOffset > lastpopupMenuOffset) {
355 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) {
356 memset(reusableBuffer.modelsel.menu_bss[MENU_MAX_DISPLAY_LINES-1], 0, MENU_LINE_LENGTH);
357 strcpy(reusableBuffer.modelsel.menu_bss[MENU_MAX_DISPLAY_LINES-1], fno.fname);
360 else {
361 if (strcasecmp(fno.fname, reusableBuffer.modelsel.menu_bss[1]) < 0 && strcasecmp(fno.fname, reusableBuffer.modelsel.menu_bss[0]) > 0) {
362 memset(reusableBuffer.modelsel.menu_bss[0], 0, MENU_LINE_LENGTH);
363 strcpy(reusableBuffer.modelsel.menu_bss[0], fno.fname);
367 f_closedir(&dir);
370 if (popupMenuOffset > 0)
371 lastpopupMenuOffset = popupMenuOffset;
372 else
373 popupMenuOffset = lastpopupMenuOffset;
375 return popupMenuNoItems;
378 // returns true if current working dir is at the root level
379 bool isCwdAtRoot()
381 char path[10];
382 if (f_getcwd(path, sizeof(path)-1) == FR_OK) {
383 return (strcasecmp("/", path) == 0);
385 return false;
389 Wrapper around the f_readdir() function which
390 also returns ".." entry for sub-dirs. (FatFS 0.12 does
391 not return ".", ".." dirs anymore)
393 FRESULT sdReadDir(DIR * dir, FILINFO * fno, bool & firstTime)
395 FRESULT res;
396 if (firstTime && !isCwdAtRoot()) {
397 // fake parent directory entry
398 strcpy(fno->fname, "..");
399 fno->fattrib = AM_DIR;
400 res = FR_OK;
402 else {
403 res = f_readdir(dir, fno); /* Read a directory item */
405 firstTime = false;
406 return res;
409 #if defined(CPUARM) && defined(SDCARD)
410 const char * sdCopyFile(const char * srcPath, const char * destPath)
412 FIL srcFile;
413 FIL destFile;
414 char buf[256];
415 UINT read = sizeof(buf);
416 UINT written = sizeof(buf);
418 FRESULT result = f_open(&srcFile, srcPath, FA_OPEN_EXISTING | FA_READ);
419 if (result != FR_OK) {
420 return SDCARD_ERROR(result);
423 result = f_open(&destFile, destPath, FA_CREATE_ALWAYS | FA_WRITE);
424 if (result != FR_OK) {
425 f_close(&srcFile);
426 return SDCARD_ERROR(result);
429 while (result==FR_OK && read==sizeof(buf) && written==sizeof(buf)) {
430 result = f_read(&srcFile, buf, sizeof(buf), &read);
431 if (result == FR_OK) {
432 result = f_write(&destFile, buf, read, &written);
436 f_close(&destFile);
437 f_close(&srcFile);
439 if (result != FR_OK) {
440 return SDCARD_ERROR(result);
443 return NULL;
446 const char * sdCopyFile(const char * srcFilename, const char * srcDir, const char * destFilename, const char * destDir)
448 char srcPath[2*CLIPBOARD_PATH_LEN+1];
449 char * tmp = strAppend(srcPath, srcDir, CLIPBOARD_PATH_LEN);
450 *tmp++ = '/';
451 strAppend(tmp, srcFilename, CLIPBOARD_PATH_LEN);
453 char destPath[2*CLIPBOARD_PATH_LEN+1];
454 tmp = strAppend(destPath, destDir, CLIPBOARD_PATH_LEN);
455 *tmp++ = '/';
456 strAppend(tmp, destFilename, CLIPBOARD_PATH_LEN);
458 return sdCopyFile(srcPath, destPath);
460 #endif // defined(CPUARM) && defined(SDCARD)
463 #if !defined(SIMU) || defined(SIMU_DISKIO)
464 uint32_t sdGetNoSectors()
466 static DWORD noSectors = 0;
467 if (noSectors == 0 ) {
468 disk_ioctl(0, GET_SECTOR_COUNT, &noSectors);
470 return noSectors;
473 uint32_t sdGetSize()
475 return (sdGetNoSectors() * BLOCK_SIZE) / 1000000;
478 uint32_t sdGetFreeSectors()
480 DWORD nofree;
481 FATFS * fat;
482 if (f_getfree("", &nofree, &fat) != FR_OK) {
483 return 0;
485 return nofree * fat->csize;
488 #else // #if !defined(SIMU) || defined(SIMU_DISKIO)
490 uint32_t sdGetNoSectors()
492 return 0;
495 uint32_t sdGetSize()
497 return 0;
500 uint32_t sdGetFreeSectors()
502 return 10;
505 #endif // #if !defined(SIMU) || defined(SIMU_DISKIO)