/* * 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_file.h" #define JS_FS_DATA_NAME JS_HIDDEN_CHAR_STR"FSdata" // the data in each file #define JS_FS_OPEN_FILES_NAME JS_HIDDEN_CHAR_STR"FSOpenFiles" // the list of open files // from jswrap_fat extern bool jsfsInit(); extern void jsfsReportError(const char *msg, FRESULT res); /*JSON{ "type":"library", "class" : "File", "description" : ["This is the stream related file IO library.", "To use this, you must type ```var fd = require('fs').open('filepath','flags','mode')``` to open a file stream." ] }*/ static JsVar* fsGetArray(const char *name, bool create) { JsVar *arrayName = jsvFindChildFromString(execInfo.root, name, create); JsVar *arr = jsvSkipName(arrayName); if (!arr && create) { arr = jsvNewWithFlags(JSV_ARRAY); jsvSetValueOfName(arrayName, arr); } jsvUnLock(arrayName); return arr; } static bool fileGetFromVar(JsFile *file, JsVar *parent) { bool ret = false; JsVar *fHandle = jsvObjectGetChild(parent, JS_FS_DATA_NAME, 0); if (fHandle) { jsvGetString(fHandle, (char*)&file->data, sizeof(JsFileData)+1/*trailing zero*/); jsvUnLock(fHandle); file->fileVar = parent; if(file->data.state == FS_OPEN) {// return false if the file has been closed. ret = true; } } return ret; } static void fileSetVar(JsFile *file) { JsVar *fHandle = jsvFindChildFromString(file->fileVar, JS_FS_DATA_NAME, true); JsVar *data = jsvSkipName(fHandle); if (!data) { data = jsvNewStringOfLength(sizeof(JsFileData)); jsvSetValueOfName(fHandle, data); } jsvUnLock(fHandle); assert(data); jsvSetString(data, (char*)&file->data, sizeof(JsFileData)); jsvUnLock(data); } /*JSON{ "type":"kill", "generate" : "jswrap_file_kill" }*/ void jswrap_file_kill() { { JsVar *arr = fsGetArray(JS_FS_OPEN_FILES_NAME,false); if (arr) { JsvArrayIterator it; jsvArrayIteratorNew(&it, arr); while (jsvArrayIteratorHasElement(&it)) { JsVar *file = jsvArrayIteratorGetElement(&it); jswrap_file_close(file); jsvUnLock(file); jsvArrayIteratorNext(&it); } jsvArrayIteratorFree(&it); jsvRemoveAllChildren(arr); jsvUnLock(arr); } } } static bool allocateJsFile(JsFile* file,FileMode mode, FileType type) { JsVar *parent = jspNewObject(0, "File"); if (!parent) return false; // low memory file->fileVar = parent; file->data.mode = mode; file->data.type = type; file->data.state = FS_NONE; return true; } /*JSON{ "type":"constructor", "class" : "File", "name" : "File", "generate" : "jswrap_file_constructor", "description" : [ "Open a file" ], "params" : [ [ "path", "JsVar", "the path to the file to open." ], [ "mode", "JsVar", "The mode to use when opening the file. Valid values for mode are 'r' for read, 'w' for write and 'a' for append. If not specified, the default is 'r'."] ], "return" : ["JsVar", "A File object"] }*/ JsVar *jswrap_file_constructor(JsVar* path, JsVar* mode) { FRESULT res = FR_INVALID_NAME; JsFile file; file.fileVar = 0; FileMode fMode = FM_NONE; if (jsfsInit()) { JsVar *arr = fsGetArray(JS_FS_OPEN_FILES_NAME, true); if (!arr) return 0; // out of memory char pathStr[JS_DIR_BUF_SIZE] = ""; char modeStr[3] = "r"; if (!jsvIsUndefined(path)) { jsvGetString(path, pathStr, JS_DIR_BUF_SIZE); if (!jsvIsUndefined(mode)) jsvGetString(mode, modeStr, 3); if(strcmp(modeStr,"r") == 0) { fMode = FM_READ; } else if(strcmp(modeStr,"w") == 0 || strcmp(modeStr,"a") == 0) { fMode = FM_WRITE; } else if(strcmp(modeStr,"w+") == 0 || strcmp(modeStr,"r+") == 0) { fMode = FM_READ_WRITE; } if(fMode != FM_NONE && allocateJsFile(&file, fMode, FT_FILE)) { #ifndef LINUX if ((res=f_open(&file.data.handle, pathStr, fMode)) == FR_OK) { #else file.data.handle = fopen(pathStr, modeStr); if (file.data.handle) { res=FR_OK; #endif file.data.state = FS_OPEN; fileSetVar(&file); // add to list of open files jsvArrayPush(arr, file.fileVar); jsvUnLock(arr); } else { // File open failed jsvUnLock(file.fileVar); file.fileVar = 0; } if(res != FR_OK) jsfsReportError("Could not open file", res); } } else { jsError("Path is undefined"); } } return file.fileVar; } /*JSON{ "type" : "method", "class" : "File", "name" : "close", "generate_full" : "jswrap_file_close(parent)", "description" : [ "Close an open file."] }*/ void jswrap_file_close(JsVar* parent) { if (jsfsInit()) { JsFile file; if (fileGetFromVar(&file, parent) && file.data.state == FS_OPEN) { #ifndef LINUX f_close(&file.data.handle); #else fclose(file.data.handle); file.data.handle = 0; #endif file.data.state = FS_CLOSED; fileSetVar(&file); JsVar *arr = fsGetArray(JS_FS_OPEN_FILES_NAME, false); if (arr) { JsVar *idx = jsvGetArrayIndexOf(arr, file.fileVar, true); if (idx) { jsvRemoveChild(arr, idx); jsvUnLock(idx); } jsvUnLock(arr); } } } } /*JSON{ "type" : "method", "class" : "File", "name" : "write", "generate" : "jswrap_file_write", "description" : [ "write data to a file"], "params" : [ ["buffer", "JsVar", "A string containing the bytes to write"] ], "return" : [ "int32", "the number of bytes written" ] }*/ size_t jswrap_file_write(JsVar* parent, JsVar* buffer) { FRESULT res = 0; size_t bytesWritten = 0; if (jsfsInit()) { JsFile file; if (fileGetFromVar(&file, parent)) { if(file.data.mode == FM_WRITE || file.data.mode == FM_READ_WRITE) { JsvIterator it; jsvIteratorNew(&it, buffer); char buf[32]; while (jsvIteratorHasElement(&it)) { // pull in a buffer's worth of data size_t n = 0; while (jsvIteratorHasElement(&it) && n sizeof( buf )) requested = sizeof( buf ); actual = 0; #ifndef LINUX res = f_read(&file.data.handle, buf, requested, &actual); if(res) break; #else actual = fread(buf, 1, requested, file.data.handle); #endif if (actual>0) { if (!buffer) { buffer = jsvNewFromEmptyString(); if (!buffer) return 0; // out of memory } jsvAppendStringBuf(buffer, buf, actual); } bytesRead += actual; if(actual != requested) break; } } } } if (res) jsfsReportError("Unable to read file", res); // automatically close this file if we're at the end of it if (bytesRead!=(size_t)length) jswrap_file_close(parent); return buffer; } /*JSON{ "type" : "method", "class" : "File", "name" : "skip", "generate" : "jswrap_file_skip", "description" : [ "Skip the specified number of bytes forwards"], "params" : [ ["nBytes", "int32", "is an integer specifying the number of bytes to skip forwards."] ] }*/ void jswrap_file_skip(JsVar* parent, int length) { if (length<=0) { jsWarn("length for skip must be greater than 0"); return; } FRESULT res = 0; if (jsfsInit()) { JsFile file; if (fileGetFromVar(&file, parent)) { if(file.data.mode == FM_READ || file.data.mode == FM_READ_WRITE) { #ifndef LINUX res = f_lseek(&file.data.handle, f_tell(&file.data.handle) + length); #else fseek(file.data.handle, length, SEEK_CUR); #endif } } } if (res) jsfsReportError("Unable to skip", res); } /*JSON{ "type" : "method", "class" : "File", "name" : "pipe", "generate" : "jswrap_pipe", "description" : [ "Pipe this file to a stream (and object with a 'write' method)"], "params" : [ ["destination", "JsVar", "The destination file/stream that will receive content from the source."], ["options", "JsVar", [ "An optional object `{ chunkSize : int=32, end : bool=true, complete : function }`", "chunkSize : The amount of data to pipe from source to destination at a time", "complete : a function to call when the pipe activity is complete", "end : call the 'end' function on the destination when the source is finished"] ] ] }*/