diff --git a/files.cpp b/files.cpp index 58e69cd..58a9b37 100644 --- a/files.cpp +++ b/files.cpp @@ -39,41 +39,6 @@ void initFileSystem() } } -/** - * Appends text and a new line to a file - * @param filename file name, eg: "output.txt" - * @param text the string to append to the end of the file - */ -//% blockId="files_append_line" block="file %filename|append line %text" -//% blockExternalInputs=1 weight=90 blockGap=8 -void appendLine(StringData *filename, StringData *text) -{ - initFileSystem(); - ManagedString fn(filename); - ManagedString t(text); - MicroBitFile f(fn); - f.append(t); - f.append("\r\n"); - f.close(); -} - -/** - * Appends text to a file - * @param filename file name, eg: "output.txt" - * @param text the string to append to the end of the file - */ -//% blockId="fs_append_string" block="file %filename|append string %text" -//% blockExternalInputs=1 weight=86 blockGap=8 -void appendString(StringData *filename, StringData *text) -{ - initFileSystem(); - ManagedString fn(filename); - ManagedString t(text); - MicroBitFile f(fn); - f.append(t); - f.close(); -} - /** * Reads the content of the file to send it to serial * @param filename file name, eg: "output.txt" @@ -223,16 +188,35 @@ Buffer fsReadBuffer(int fd, int length) { return mkBuffer(NULL, 0); initFileSystem(); - Buffer buf = mkBuffer(NULL, length); - + // compute position, length + int pos = MicroBitFileSystem::defaultFileSystem->seek(fd, 0, FileSystemSeekFlags::Current); + int end = MicroBitFileSystem::defaultFileSystem->seek(fd, 0, FileSystemSeekFlags::End); + MicroBitFileSystem::defaultFileSystem->seek(fd, pos, FileSystemSeekFlags::Set); + // cap length + length = min(length, end - pos); + auto buf = mkBuffer(NULL, length); int ret = MicroBitFileSystem::defaultFileSystem->read(fd, PXT_BUFFER_DATA(buf), buf->length); - if (ret < 0) return mkBuffer(NULL, 0); - else if (ret != length) { - auto sbuf = mkBuffer(PXT_BUFFER_DATA(buf), ret); - decrRC(buf); - return sbuf; - } + else return buf; +} + +/** +*/ +//% weight=0 advanced=true +String fsReadString(int fd, int length) { + if (fd < 0 || length < 0) + return mkString(NULL, 0); + + initFileSystem(); + // compute position, length + int pos = MicroBitFileSystem::defaultFileSystem->seek(fd, 0, FileSystemSeekFlags::Current); + int end = MicroBitFileSystem::defaultFileSystem->seek(fd, 0, FileSystemSeekFlags::End); + MicroBitFileSystem::defaultFileSystem->seek(fd, pos, FileSystemSeekFlags::Set); + // cap length + length = min(length, end - pos); + auto buf = mkString(NULL, length); + int ret = MicroBitFileSystem::defaultFileSystem->read(fd, (uint8_t*)buf->data, buf->length); + if (ret < 0) return mkString(NULL, 0); else return buf; } diff --git a/files.ts b/files.ts index a4082ee..5d20f5e 100644 --- a/files.ts +++ b/files.ts @@ -3,6 +3,38 @@ */ //% weight=5 color=#002050 icon="\uf0a0" namespace files { + export let NEW_LINE = "\r\n"; + export let TAB = "\t"; + + /** + * Appends text and a new line to a file + * @param filename file name, eg: "output.txt" + * @param text the string to append to the end of the file + */ + //% blockId="files_append_line" block="file %filename|append line %text" + //% blockExternalInputs=1 weight=90 blockGap=8 + export function appendLine(filename: string, text: string): void { + const file = open(filename); + file.seek(0, FileSystemSeekFlags.End); + file.writeString(text); + file.writeString(NEW_LINE); + file.close(); + } + + /** + * Appends text to a file + * @param filename file name, eg: "output.txt" + * @param text the string to append to the end of the file + */ + //% blockId="fs_append_string" block="file %filename|append string %text" + //% blockExternalInputs=1 weight=86 blockGap=8 + export function appendString(filename: string, text: string): void { + const file = open(filename); + file.seek(0, FileSystemSeekFlags.End); + file.writeString(text); + file.close(); + } + /** * Appends a number to a file * @param filename file name, eg: "output.txt" @@ -11,7 +43,24 @@ namespace files { //% blockId="fs_append_number" block="file %filename|append number %value" //% blockExternalInputs=1 weight=85 export function appendNumber(filename: string, value: number) { - files.appendString(filename, value.toString()); + const file = open(filename); + file.seek(0, FileSystemSeekFlags.End); + file.writeString(value.toString()); + file.close(); + } + + /** + * Appends a buffer to the end of the file + * @param filename + * @param buffer + */ + //% blockId="fs_append_buffer" block="file %filename|append buffer $buffer" + //% weight=84 + export function appendBuffer(filename: string, buffer: Buffer) { + const file = open(filename); + file.seek(0, FileSystemSeekFlags.End); + file.writeBuffer(buffer); + file.close(); } /** @@ -110,7 +159,7 @@ namespace files { */ //% blockGap=8 //% blockId=fs_file_position block="%this|position" advanced=true - public position(): number { + public get position(): number { return files.fsSeek(this.fd, 0, FileSystemSeekFlags.Current); } @@ -120,10 +169,22 @@ namespace files { */ //% blockGap=8 advanced=true //% blockId=fs_file_set_position block="%this|set position %position" - public setPosition(position: number): void { + public set position(position: number) { files.fsSeek(this.fd, position, FileSystemSeekFlags.Set); } + /** + * Returns the file length in bytes + */ + //% blockGap=8 advanced=true + //% blockId=fs_file_length block="%this|length" + public get length(): number { + files.fsSeek(this.fd, 0, FileSystemSeekFlags.End); + const l = files.fsSeek(this.fd, 0, FileSystemSeekFlags.Current); + files.fsSeek(this.fd, 0, FileSystemSeekFlags.Set); + return l; + } + /** * Write a string to the file. */ @@ -144,14 +205,34 @@ namespace files { /** * Reads the file at the current position and fills a buffer - * @param length maximum number of bytes to read, eg: 64 + * @param length maximum number of bytes to read, if negative reads the entire file eg: 64 */ //% blockGap=8 - //% blockId=fs_file_read_buffer block="%this|read buffer (bytes) %length" advanced=true + //% blockId=fs_file_read_buffer block="%this|read buffer $length (bytes)" advanced=true public readBuffer(length: number): Buffer { + const p = this.position; + const l = this.length; + if (length < 0) + length = l; + length = Math.min(length, l - p); return files.fsReadBuffer(this.fd, length); } + /** + * Reads the file at the current position and a string. + * @param length maximum number of bytes to read, if negative reads the entire file eg: 64 + */ + //% blockGap=8 + //% blockId=fs_file_read_string block="%this|read string $length (chars)" advanced=true + public readString(length: number): String { + const p = this.position; + const l = this.length; + if (length < 0) + length = l; + length = Math.min(length, l - p); + return files.fsReadString(this.fd, length); + } + /** * Reads the next character in the file at the current position */ @@ -160,5 +241,41 @@ namespace files { public read(): number { return files.fsRead(this.fd); } + + /** + * Reads the next line in the file at the current position + */ + //% blockGap=8 + //% blockId=fs_file_read_line block="%this|read line" advanced=true + public readLine(): string { + const nl = NEW_LINE; + const length = this.length; + const start = this.position; + // scan until new line or eol + let end = start; + while (end < length) { + let c = this.read(); + if (c == 13 /* \r */ && this.position < length) { + // try scanning for next character + c = this.read(); + } + if (c == 10 /* \n */) { + // found a new line + break; + } + // update end position + end = this.position; + } + + // end of file shortcut + if (start == end) return ""; + + // remember current position + const pos = this.position; + this.position = start; // reset current cursor + const line = fsReadString(this.fd, end - start); // read line + this.position = pos; // skip new line + return line; + } } } diff --git a/shims.d.ts b/shims.d.ts index 42110dd..325fdc6 100644 --- a/shims.d.ts +++ b/shims.d.ts @@ -7,24 +7,6 @@ //% weight=5 color=#002050 icon="\uf0a0" declare namespace files { - /** - * Appends text and a new line to a file - * @param filename file name, eg: "output.txt" - * @param text the string to append to the end of the file - */ - //% blockId="files_append_line" block="file %filename|append line %text" - //% blockExternalInputs=1 weight=90 blockGap=8 shim=files::appendLine - function appendLine(filename: string, text: string): void; - - /** - * Appends text to a file - * @param filename file name, eg: "output.txt" - * @param text the string to append to the end of the file - */ - //% blockId="fs_append_string" block="file %filename|append string %text" - //% blockExternalInputs=1 weight=86 blockGap=8 shim=files::appendString - function appendString(filename: string, text: string): void; - /** * Reads the content of the file to send it to serial * @param filename file name, eg: "output.txt" @@ -104,6 +86,11 @@ declare namespace files { //% weight=0 advanced=true shim=files::fsReadBuffer function fsReadBuffer(fd: int32, length: int32): Buffer; + /** + */ + //% weight=0 advanced=true shim=files::fsReadString + function fsReadString(fd: int32, length: int32): string; + /** * */ diff --git a/tests.ts b/tests.ts index 823f8da..e4afb29 100644 --- a/tests.ts +++ b/tests.ts @@ -1,66 +1,86 @@ let fi = 0; -let file = ""; -input.onButtonPressed(Button.B, () => { - file = "data" + (fi++) + ".csv"; - serial.writeLine(file); - files.remove(file) - files.readToSerial(file); +{ // settings + console.log("settings"); + fi++; + let test = Math.randomRange(0, 1000); + files.settingsSaveNumber("test", test); + serial.writeValue("test", test); + let serTest = files.settingsReadNumber("test"); + serial.writeValue("serTest", serTest); + control.assert(test == serTest); +} + +{ // readline + console.log("append") + const fn = "test" + (fi++) + ".txt"; + let expected = ""; + const r = Math.random().toString(); + const n = 3; + serial.writeValue("expected lines", n); + for (let i = 0; i < n; ++i) { + files.appendLine(fn, r); + expected += r + files.NEW_LINE; + } + + { // readString + let actual = files.open(fn).readString(-1); + serial.writeLine("readstring: " + actual); + control.assert(expected == actual); + } + + { // readline + let actual = ""; + let line = ""; + const f = files.open(fn); + let i = 0; + do { + line = f.readLine(); + actual += line + files.NEW_LINE; + i++; + } while (line.length) + serial.writeValue("actual lines", i); + control.assert(n == i); + serial.writeLine("readline: " + actual); + control.assert(expected == actual); + } +} + +{ // file append line + console.log("file") + const fn = "test" + (fi++) + ".txt"; + let f = files.open(fn); + const expected = "writeString\r\n"; + f.writeString(expected); + f.seek(0, FileSystemSeekFlags.End); + f.flush(); + f.close(); + let actual = files.open(fn).readString(-1); + control.assert(expected == actual); +} + +{ // stress + console.log("stress") + const fn = "test" + (fi++) + ".txt"; + serial.writeLine(fn); + files.remove(fn) + files.readToSerial(fn); serial.writeLine(""); basic.showString("o") + const buf = control.createBuffer(4); + buf.setNumber(NumberFormat.UInt32LE, 0, ~0); for (let i = 0; i < 200; ++i) { let t = input.runningTime(); let ay = input.acceleration(Dimension.Y); - files.appendNumber(file, i); - files.appendString(file, " "); - files.appendNumber(file, i * i); - files.appendLine(file, ""); + files.appendNumber(fn, i); + files.appendString(fn, " "); + files.appendNumber(fn, i * i); + files.appendLine(fn, ""); + files.appendBuffer(fn, buf); + files.appendLine(fn, ""); serial.writeLine(".") basic.pause(10) } serial.writeLine(""); - files.readToSerial(file); + files.readToSerial(fn); basic.showString(":)") -}) -let test = Math.randomRange(0, 1000); -files.settingsSaveNumber("test", test); -serial.writeValue("test", test); -let serTest = files.settingsReadNumber("test"); -serial.writeValue("serTest", serTest); -control.assert(test == serTest); - -let f = files.open("output.txt"); -f.writeString("writeString\r\n"); -f.seek(0, FileSystemSeekFlags.End); -f.flush(); -f.close(); - -input.onButtonPressed(Button.A, () => { - files.appendLine( - "output.txt", - "hello" - ) -}) -input.onButtonPressed(Button.B, () => { - basic.showString("H") - files.readToSerial("output.txt") - serial.writeString("Hi") -}) - -const fn = "out2.txt"; -input.onButtonPressed(Button.A, () => { - basic.showString("o") - files.appendLine(fn, "hello"); - serial.writeString("[") - files.readToSerial(fn) -}) -const fo = "output.txt"; -input.onButtonPressed(Button.A, () => { - files.appendLine(fo, "hello") - serial.writeString("W") - basic.showString("W") -}) -input.onButtonPressed(Button.B, () => { - files.readToSerial(fo) - serial.writeString("Hi") - basic.showString("H") -}) +} diff --git a/tsconfig.json b/tsconfig.json index 1ba59f2..a2079ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,5 +4,6 @@ "noImplicitAny": true, "outDir": "built", "rootDir": "." - } + }, + "exclude": ["pxt_modules/**/*test.ts"] }