/* * This file is part of Espruino, a JavaScript interpreter for Microcontrollers * * Copyright (C) 2013 Gordon Williams * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * ---------------------------------------------------------------------------- * This file is designed to be parsed during the build process * * Contains built-in functions for SD card access * ---------------------------------------------------------------------------- */ #include "jswrap_fat.h" #include "jsutils.h" #include "jsvar.h" #include "jsparse.h" #include "jsinteractive.h" #ifndef LINUX #include "ff.h" // filesystem stuff #else #include #include // for readdir #endif /*JSON{ "type":"class", "class" : "fs", "description" : ["This class handles interfacing with a FAT32 filesystem on an SD card. The API is designed to be similar to node.js's - However Espruino does not currently support asynchronous file IO, so the functions behave like node.js's xxxxSync functions. Versions of the functions with 'Sync' after them are also provided for compatibility.", "Currently this provides minimal file IO - it's great for logging and loading/saving settings, but not good for loading large amounts of data as you will soon fill your memory up.", "It is currently only available on boards that contain an SD card slot, such as the Olimexino and the HY. It can not currently be added to boards that did not ship with a card slot." ] }*/ #ifndef LINUX #define JS_DIR_BUF_SIZE 64 #else #define JS_DIR_BUF_SIZE 256 typedef int FRESULT; #define FR_OK (0) #endif #ifndef LINUX #if _USE_LFN #define GET_FILENAME(Finfo) *Finfo.lfname ? Finfo.lfname : Finfo.fname #else #define GET_FILENAME(Finfo) Finfo.fname #endif FATFS jsfsFAT; #endif void jsfsReportError(const char *msg, FRESULT res) { char buf[JS_ERROR_BUF_SIZE]; strncpy(buf, msg, JS_ERROR_BUF_SIZE); if (res==FR_OK ) strncat(buf," : OK", JS_ERROR_BUF_SIZE); #ifndef LINUX if (res==FR_DISK_ERR ) strncat(buf," : DISK_ERR", JS_ERROR_BUF_SIZE); if (res==FR_INT_ERR ) strncat(buf," : INT_ERR", JS_ERROR_BUF_SIZE); if (res==FR_NOT_READY ) strncat(buf," : NOT_READY", JS_ERROR_BUF_SIZE); if (res==FR_NO_FILE ) strncat(buf," : NO_FILE", JS_ERROR_BUF_SIZE); if (res==FR_NO_PATH ) strncat(buf," : NO_PATH", JS_ERROR_BUF_SIZE); if (res==FR_INVALID_NAME ) strncat(buf," : INVALID_NAME", JS_ERROR_BUF_SIZE); if (res==FR_DENIED ) strncat(buf," : DENIED", JS_ERROR_BUF_SIZE); if (res==FR_EXIST ) strncat(buf," : EXIST", JS_ERROR_BUF_SIZE); if (res==FR_INVALID_OBJECT ) strncat(buf," : INVALID_OBJECT", JS_ERROR_BUF_SIZE); if (res==FR_WRITE_PROTECTED) strncat(buf," : WRITE_PROTECTED", JS_ERROR_BUF_SIZE); if (res==FR_INVALID_DRIVE ) strncat(buf," : INVALID_DRIVE", JS_ERROR_BUF_SIZE); if (res==FR_NOT_ENABLED ) strncat(buf," : NOT_ENABLED", JS_ERROR_BUF_SIZE); if (res==FR_NO_FILESYSTEM ) strncat(buf," : NO_FILESYSTEM", JS_ERROR_BUF_SIZE); if (res==FR_MKFS_ABORTED ) strncat(buf," : MKFS_ABORTED", JS_ERROR_BUF_SIZE); if (res==FR_TIMEOUT ) strncat(buf," : TIMEOUT", JS_ERROR_BUF_SIZE); #endif jsError(buf); } bool jsfsInit() { #ifndef LINUX static bool inited = false; if (!inited) { FRESULT res; if ((res = f_mount(0, &jsfsFAT)) != FR_OK) { jsfsReportError("Unable to mount SD card", res); return false; } inited = true; } #endif return true; } /* Unmount... if (res==FR_OK) { jsiConsolePrint("Unmounting...\n"); res = f_mount(0, 0); } */ /*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "readdir", "generate" : "wrap_fat_readdir", "description" : [ "List all files in the supplied directory, returning them as an array of strings.", "NOTE: Espruino does not yet support Async file IO, so this function behaves like the 'Sync' version." ], "params" : [ [ "path", "JsVar", "The path of the directory to list. If it is not supplied, '' is assumed, which will list the root directory" ] ], "return" : [ "JsVar", "An array of filename strings" ] }*/ /*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "readdirSync", "ifndef" : "SAVE_ON_FLASH", "generate" : "wrap_fat_readdir", "description" : [ "List all files in the supplied directory, returning them as an array of strings." ], "params" : [ [ "path", "JsVar", "The path of the directory to list. If it is not supplied, '' is assumed, which will list the root directory" ] ], "return" : [ "JsVar", "An array of filename strings" ] }*/ JsVar *wrap_fat_readdir(JsVar *path) { JsVar *arr = jsvNewWithFlags(JSV_ARRAY); if (!arr) return 0; // out of memory char pathStr[JS_DIR_BUF_SIZE] = ""; if (!jsvIsUndefined(path)) jsvGetString(path, pathStr, JS_DIR_BUF_SIZE); #ifdef LINUX if (!pathStr[0]) strcpy(pathStr, "."); // deal with empty readdir #endif FRESULT res = 0; if (jsfsInit()) { #ifndef LINUX DIR dirs; if ((res=f_opendir(&dirs, pathStr)) == FR_OK) { FILINFO Finfo; while (((res=f_readdir(&dirs, &Finfo)) == FR_OK) && Finfo.fname[0]) { char *fn = GET_FILENAME(Finfo); #else DIR *dir = opendir(pathStr); if(dir) { struct dirent *pDir=NULL; while((pDir = readdir(dir)) != NULL) { char *fn = (*pDir).d_name; #endif JsVar *fnVar = jsvNewFromString(fn); if (fnVar) // out of memory? jsvArrayPush(arr, fnVar); } #ifdef LINUX closedir(dir); #endif } } if (res) jsfsReportError("Unable to list files", res); return arr; } /*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "writeFile", "generate" : "wrap_fat_writeFile", "description" : [ "Write the data to the given file", "NOTE: Espruino does not yet support Async file IO, so this function behaves like the 'Sync' version." ], "params" : [ [ "path", "JsVar", "The path of the file to write" ], [ "data", "JsVar", "The data to write to the file" ] ] }*/ /*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "writeFileSync", "ifndef" : "SAVE_ON_FLASH", "generate" : "wrap_fat_writeFile", "description" : [ "Write the data to the given file" ], "params" : [ [ "path", "JsVar", "The path of the file to write" ], [ "data", "JsVar", "The data to write to the file" ] ] }*/ /*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "appendFile", "generate" : "wrap_fat_appendFile", "description" : [ "Append the data to the given file, created a new file if it doesn't exist", "NOTE: Espruino does not yet support Async file IO, so this function behaves like the 'Sync' version." ], "params" : [ [ "path", "JsVar", "The path of the file to write" ], [ "data", "JsVar", "The data to write to the file" ] ] }*/ /*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "appendFileSync", "ifndef" : "SAVE_ON_FLASH", "generate" : "wrap_fat_appendFile", "description" : [ "Append the data to the given file, created a new file if it doesn't exist" ], "params" : [ [ "path", "JsVar", "The path of the file to write" ], [ "data", "JsVar", "The data to write to the file" ] ] }*/ void wrap_fat_writeOrAppendFile(JsVar *path, JsVar *data, bool append) { char pathStr[JS_DIR_BUF_SIZE] = ""; if (!jsvIsUndefined(path)) jsvGetString(path, pathStr, JS_DIR_BUF_SIZE); FRESULT res = 0; if (jsfsInit()) { #ifndef LINUX FIL file; if ((res=f_open(&file, pathStr, FA_WRITE|(append ? FA_OPEN_ALWAYS : FA_CREATE_ALWAYS))) == FR_OK) { if (append) { // move to end of file to append data f_lseek(&file, file.fsize); // if (res != FR_OK) jsfsReportError("Unable to move to end of file", res); } #else FILE *file = fopen(pathStr, append?"a":"w"); if (file) { #endif JsvStringIterator it; JsVar *dataString = jsvAsString(data, false); jsvStringIteratorNew(&it, dataString, 0); unsigned int toWrite = 0; unsigned int written = 0; while (jsvStringIteratorHasChar(&it) && res==FR_OK && written==toWrite) { // re-use pathStr buffer toWrite = 0; while (jsvStringIteratorHasChar(&it) && toWrite < JS_DIR_BUF_SIZE) { pathStr[toWrite++] = jsvStringIteratorGetChar(&it); jsvStringIteratorNext(&it); } //jsiConsolePrint("Write ");jsiConsolePrintInt(toWrite);jsiConsolePrint("\n"); #ifndef LINUX res = f_write(&file, pathStr, toWrite, &written); #else written = fwrite(pathStr, 1, toWrite, file); #endif } jsvStringIteratorFree(&it); jsvUnLock(dataString); #ifndef LINUX f_close(&file); #else fclose(file); #endif } } if (res) jsfsReportError("Unable to write file", res); } void wrap_fat_writeFile(JsVar *path, JsVar *data) { wrap_fat_writeOrAppendFile(path, data, false); } void wrap_fat_appendFile(JsVar *path, JsVar *data) { wrap_fat_writeOrAppendFile(path, data, true); } /*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "readFile", "generate" : "wrap_fat_readFile", "description" : [ "Read all data from a file and return as a string", "NOTE: Espruino does not yet support Async file IO, so this function behaves like the 'Sync' version." ], "params" : [ [ "path", "JsVar", "The path of the file to read" ] ], "return" : [ "JsVar", "A string containing the contents of the file" ] }*/ /*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "readFileSync", "ifndef" : "SAVE_ON_FLASH", "generate" : "wrap_fat_readFile", "description" : [ "Read all data from a file and return as a string" ], "params" : [ [ "path", "JsVar", "The path of the file to read" ] ], "return" : [ "JsVar", "A string containing the contents of the file" ] }*/ JsVar *wrap_fat_readFile(JsVar *path) { char pathStr[JS_DIR_BUF_SIZE] = ""; if (!jsvIsUndefined(path)) jsvGetString(path, pathStr, JS_DIR_BUF_SIZE); JsVar *result = jsvNewFromEmptyString(); if (!result) return 0; // out of memory FRESULT res = 0; if (jsfsInit()) { #ifndef LINUX FIL file; if ((res=f_open(&file, pathStr, FA_READ)) == FR_OK) { #else FILE *file = fopen(pathStr, "r"); if (file) { #endif // re-use pathStr buffer unsigned int bytesRead = JS_DIR_BUF_SIZE; while (res==FR_OK && bytesRead==JS_DIR_BUF_SIZE) { #ifndef LINUX res = f_read (&file, pathStr, JS_DIR_BUF_SIZE, &bytesRead); #else bytesRead = fread(pathStr,1,JS_DIR_BUF_SIZE,file); #endif //jsiConsolePrint("Read ");jsiConsolePrintInt(bytesRead);jsiConsolePrint("\n"); jsvAppendStringBuf(result, pathStr, (int)bytesRead); } #ifndef LINUX f_close(&file); #else fclose(file); #endif } } if (res) jsfsReportError("Unable to read file", res); return result; }