Espruino/libs/jswrap_fat.c
Gordon Williams f3d6e0bc83 First commit
2013-09-26 14:39:04 +01:00

295 lines
11 KiB
C

/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
*
* 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 <stdio.h>
#include <dirent.h> // 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;
}