mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
422 lines
12 KiB
C
Executable File
422 lines
12 KiB
C
Executable File
/*
|
|
* 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, based on node.js 'fs' module
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "jswrap_fs.h"
|
|
#include "jswrap_file.h"
|
|
#include "jsutils.h"
|
|
#include "jsvar.h"
|
|
#include "jsparse.h"
|
|
#include "jsinteractive.h"
|
|
#include "jswrap_date.h"
|
|
|
|
#ifndef LINUX
|
|
#include "ff.h" // filesystem stuff
|
|
#else
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h> // for readdir
|
|
#endif
|
|
|
|
|
|
/*JSON{
|
|
"type" : "library",
|
|
"class" : "fs"
|
|
}
|
|
This library 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.
|
|
|
|
To use this, you must type ```var fs = require('fs')``` to get access to the library
|
|
|
|
See [the page on File IO](http://www.espruino.com/File+IO) for more information, and for examples on wiring up an SD card if your device doesn't come with one.
|
|
|
|
**Note:** If you want to remove an SD card after you have started using it, you *must* call `E.unmountSD()` or you may cause damage to the card.
|
|
*/
|
|
|
|
#ifndef LINUX
|
|
#define JS_DIR_BUF_SIZE 64
|
|
#else
|
|
#define JS_DIR_BUF_SIZE 256
|
|
#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
|
|
|
|
#endif
|
|
|
|
// from jswrap_file
|
|
bool jsfsGetPathString(char *pathStr, JsVar *path);
|
|
extern bool jsfsInit();
|
|
extern void jsfsReportError(const char *msg, FRESULT res);
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "readdir",
|
|
"generate" : "jswrap_fs_readdir",
|
|
"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 (or undefined if the directory couldn't be listed)"]
|
|
}
|
|
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.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "readdirSync",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate" : "jswrap_fs_readdir",
|
|
"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 (or undefined if the directory couldn't be listed)"]
|
|
}
|
|
List all files in the supplied directory, returning them as an array of strings.
|
|
*/
|
|
|
|
JsVar *jswrap_fs_readdir(JsVar *path) {
|
|
JsVar *arr = 0; // undefined unless we can open card
|
|
|
|
char pathStr[JS_DIR_BUF_SIZE] = "";
|
|
if (!jsvIsUndefined(path))
|
|
if (!jsfsGetPathString(pathStr, path)) return 0;
|
|
#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;
|
|
#if _USE_LFN!=0
|
|
char lfnBuf[_MAX_LFN+1];
|
|
Finfo.lfname = lfnBuf;
|
|
Finfo.lfsize = sizeof(lfnBuf);
|
|
#endif
|
|
#else
|
|
DIR *dir = opendir(pathStr);
|
|
if(dir) {
|
|
#endif
|
|
arr = jsvNewEmptyArray();
|
|
if (arr) { // could be out of memory
|
|
#ifndef LINUX
|
|
while (((res=f_readdir(&dirs, &Finfo)) == FR_OK) && Finfo.fname[0]) {
|
|
char *fn = GET_FILENAME(Finfo);
|
|
#else
|
|
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);
|
|
jsvUnLock(fnVar);
|
|
}
|
|
}
|
|
}
|
|
#ifndef LINUX
|
|
f_closedir(&dirs);
|
|
#else
|
|
closedir(dir);
|
|
#endif
|
|
}
|
|
}
|
|
if (res) jsfsReportError("Unable to list files", res);
|
|
return arr;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "writeFile",
|
|
"generate_full" : " jswrap_fs_writeOrAppendFile(path, data, false)",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to write"],
|
|
["data","JsVar","The data to write to the file"]
|
|
],
|
|
"return" : ["bool","True on success, false on failure"]
|
|
}
|
|
Write the data to the given file
|
|
|
|
NOTE: Espruino does not yet support Async file IO, so this function behaves like the 'Sync' version.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "writeFileSync",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate_full" : " jswrap_fs_writeOrAppendFile(path, data, false)",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to write"],
|
|
["data","JsVar","The data to write to the file"]
|
|
],
|
|
"return" : ["bool","True on success, false on failure"]
|
|
}
|
|
Write the data to the given file
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "appendFile",
|
|
"generate_full" : " jswrap_fs_writeOrAppendFile(path, data, true)",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to write"],
|
|
["data","JsVar","The data to write to the file"]
|
|
],
|
|
"return" : ["bool","True on success, false on failure"]
|
|
}
|
|
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.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "appendFileSync",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate_full" : "jswrap_fs_writeOrAppendFile(path, data, true)",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to write"],
|
|
["data","JsVar","The data to write to the file"]
|
|
],
|
|
"return" : ["bool","True on success, false on failure"]
|
|
}
|
|
Append the data to the given file, created a new file if it doesn't exist
|
|
*/
|
|
bool jswrap_fs_writeOrAppendFile(JsVar *path, JsVar *data, bool append) {
|
|
if (!data) return false;
|
|
JsVar *fMode = jsvNewFromString(append ? "a" : "w");
|
|
JsVar *f = jswrap_E_openFile(path, fMode);
|
|
jsvUnLock(fMode);
|
|
if (!f) return 0;
|
|
size_t amt = jswrap_file_write(f, data);
|
|
jswrap_file_close(f);
|
|
jsvUnLock(f);
|
|
return amt>0;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "readFile",
|
|
"generate" : "jswrap_fs_readFile",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to read"]
|
|
],
|
|
"return" : ["JsVar","A string containing the contents of the file (or undefined if the file doesn't exist)"]
|
|
}
|
|
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.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "readFileSync",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate" : "jswrap_fs_readFile",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to read"]
|
|
],
|
|
"return" : ["JsVar","A string containing the contents of the file (or undefined if the file doesn't exist)"]
|
|
}
|
|
Read all data from a file and return as a string.
|
|
|
|
**Note:** The size of files you can load using this method is limited by the amount of available RAM. To read files a bit at a time, see the `File` class.
|
|
*/
|
|
JsVar *jswrap_fs_readFile(JsVar *path) {
|
|
JsVar *fMode = jsvNewFromString("r");
|
|
JsVar *f = jswrap_E_openFile(path, fMode);
|
|
jsvUnLock(fMode);
|
|
if (!f) return 0;
|
|
JsVar *buffer = jswrap_file_read(f, 0x7FFFFFFF);
|
|
jswrap_file_close(f);
|
|
jsvUnLock(f);
|
|
return buffer;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "unlink",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate" : "jswrap_fs_unlink",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to delete"]
|
|
],
|
|
"return" : ["bool","True on success, or false on failure"]
|
|
}
|
|
Delete the given file
|
|
|
|
NOTE: Espruino does not yet support Async file IO, so this function behaves like the 'Sync' version.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "unlinkSync",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate" : "jswrap_fs_unlink",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to delete"]
|
|
],
|
|
"return" : ["bool","True on success, or false on failure"]
|
|
}
|
|
Delete the given file
|
|
*/
|
|
bool jswrap_fs_unlink(JsVar *path) {
|
|
char pathStr[JS_DIR_BUF_SIZE] = "";
|
|
if (!jsvIsUndefined(path))
|
|
if (!jsfsGetPathString(pathStr, path)) return 0;
|
|
|
|
#ifndef LINUX
|
|
FRESULT res = 0;
|
|
if (jsfsInit()) {
|
|
res = f_unlink(pathStr);
|
|
}
|
|
#else
|
|
FRESULT res = remove(pathStr);
|
|
#endif
|
|
|
|
if (res) {
|
|
jsfsReportError("Unable to delete file", res);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "statSync",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate" : "jswrap_fs_stat",
|
|
"params" : [
|
|
["path","JsVar","The path of the file to get information on"]
|
|
],
|
|
"return" : ["JsVar","An object describing the file, or undefined on failure"]
|
|
}
|
|
Return information on the given file. This returns an object with the following
|
|
fields:
|
|
|
|
size: size in bytes
|
|
dir: a boolean specifying if the file is a directory or not
|
|
mtime: A Date structure specifying the time the file was last modified
|
|
*/
|
|
JsVar *jswrap_fs_stat(JsVar *path) {
|
|
char pathStr[JS_DIR_BUF_SIZE] = "";
|
|
if (!jsvIsUndefined(path))
|
|
if (!jsfsGetPathString(pathStr, path)) return 0;
|
|
|
|
#ifndef LINUX
|
|
FRESULT res = 0;
|
|
if (jsfsInit()) {
|
|
FILINFO info;
|
|
memset(&info,0,sizeof(info));
|
|
res = f_stat(pathStr, &info);
|
|
if (res==0 /*ok*/) {
|
|
JsVar *obj = jsvNewObject();
|
|
if (!obj) return 0;
|
|
jsvObjectSetChildAndUnLock(obj, "size", jsvNewFromInteger((JsVarInt)info.fsize));
|
|
jsvObjectSetChildAndUnLock(obj, "dir", jsvNewFromBool(info.fattrib & AM_DIR));
|
|
|
|
CalendarDate date;
|
|
date.year = 1980+(int)((info.fdate>>9)&127);
|
|
date.month = (int)(((info.fdate>>5)&15)-1); // TomWS: Month is 0 based.
|
|
date.day = (int)((info.fdate)&31);
|
|
TimeInDay td;
|
|
td.daysSinceEpoch = fromCalenderDate(&date);
|
|
td.hour = (int)((info.ftime>>11)&31);
|
|
td.min = (int)((info.ftime>>5)&63);
|
|
td.sec = (int)((info.ftime)&63);
|
|
td.ms = 0;
|
|
setCorrectTimeZone(&td); // TomWS: add adjustment for timezone offset introduced in date_from_milliseconds
|
|
jsvObjectSetChildAndUnLock(obj, "mtime", jswrap_date_from_milliseconds(fromTimeInDay(&td)));
|
|
return obj;
|
|
}
|
|
}
|
|
#else
|
|
struct stat info;
|
|
if (stat(pathStr, &info)==0 /*ok*/) {
|
|
JsVar *obj = jsvNewObject();
|
|
if (!obj) return 0;
|
|
jsvObjectSetChildAndUnLock(obj, "size", jsvNewFromInteger((JsVarInt)info.st_size));
|
|
jsvObjectSetChildAndUnLock(obj, "dir", jsvNewFromBool(S_ISDIR(info.st_mode)));
|
|
jsvObjectSetChildAndUnLock(obj, "mtime", jswrap_date_from_milliseconds((JsVarFloat)info.st_mtime*1000.0));
|
|
return obj;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "mkdir",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate" : "jswrap_fs_mkdir",
|
|
"params" : [
|
|
["path","JsVar","The name of the directory to create"]
|
|
],
|
|
"return" : ["bool","True on success, or false on failure"]
|
|
}
|
|
Create the directory
|
|
|
|
NOTE: Espruino does not yet support Async file IO, so this function behaves like the 'Sync' version.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "fs",
|
|
"name" : "mkdirSync",
|
|
"ifndef" : "SAVE_ON_FLASH",
|
|
"generate" : "jswrap_fs_mkdir",
|
|
"params" : [
|
|
["path","JsVar","The name of the directory to create"]
|
|
],
|
|
"return" : ["bool","True on success, or false on failure"]
|
|
}
|
|
Create the directory
|
|
*/
|
|
bool jswrap_fs_mkdir(JsVar *path) {
|
|
char pathStr[JS_DIR_BUF_SIZE] = "";
|
|
if (!jsvIsUndefined(path))
|
|
if (!jsfsGetPathString(pathStr, path)) return 0;
|
|
|
|
#ifndef LINUX
|
|
FRESULT res = 0;
|
|
if (jsfsInit()) {
|
|
res = f_mkdir(pathStr);
|
|
}
|
|
#else
|
|
FRESULT res = mkdir(pathStr, 0777);
|
|
#endif
|
|
|
|
if (res) {
|
|
jsfsReportError("Unable to create the directory", res);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|