From ca68d68df004d2e7b3cb9bfa3794da20bc2472a3 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 16 Aug 2023 15:41:07 +0100 Subject: [PATCH] Allow E.pipe to pipe from Strings, remove pipe's internal `position` counter (fix #2352) --- ChangeLog | 1 + src/jswrap_espruino.c | 36 ++++++++++++++++++++++++++++++++++-- src/jswrap_pipe.c | 31 +++++++++++++++---------------- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/ChangeLog b/ChangeLog index a4f096f789..574bfdd325 100644 --- a/ChangeLog +++ b/ChangeLog @@ -45,6 +45,7 @@ Bangle.js2: Ensure `HRM-raw.raw` is set correctly after we moved to binary hrm algorithm nRF5x: Add `NRF.on("passkey", ...)` to allow passkey pairing with `NRF.setSecurity({mitm:1, display:1});` nrf52: Allow disabling pairing with `NRF.setSecurity({pair:0})` + Allow E.pipe to pipe from Strings, remove pipe's internal `position` counter (fix #2352) 2v18 : Fix drawString with setClipRect with 90/270-degree rotation (fix #2343) Pico/Wifi: Enabled JIT compiler diff --git a/src/jswrap_espruino.c b/src/jswrap_espruino.c index 2d6b96219c..3965a000b0 100644 --- a/src/jswrap_espruino.c +++ b/src/jswrap_espruino.c @@ -777,12 +777,44 @@ their values. "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_pipe", "params" : [ - ["source","JsVar","The source file/stream that will send content."], + ["source","JsVar","The source file/stream that will send content. As of 2v19 this can also be a `String`"], ["destination","JsVar","The destination file/stream that will receive content from the source."], ["options","JsVar",["[optional] An object `{ chunkSize : int=64, 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"]] ], "typescript" : "pipe(source: any, destination: any, options?: PipeOptions): void" -}*/ +} +Pipe one stream to another. + +This can be given any object with a `read` method as a source, and any object with a `.write(data)` method as a destination. + +Data will be piped from `source` to `destination` in the idle loop until `source.read(...)` returns `undefined`. + +For instance: + +``` +// Print a really big string to the console, 1 character at a time and write 'Finished!' at the end +E.pipe("This is a really big String", + {write: print}, + {chunkSize:1, complete:()=>print("Finished!")}); + +// Pipe the numbers 1 to 100 to a StorageFile in Storage +E.pipe({ n:0, read : function() { if (this.n<100) return (this.n++)+"\n"; }}, + require("Storage").open("testfile","w")); + +// Pipe a StorageFile straight to the Bluetooth UART +E.pipe(require("Storage").open("testfile","r"), Bluetooth); + +// Pipe a normal file in Storage (not StorageFile) straight to the Bluetooth UART +E.pipe(require("Storage").read("blob.txt"), Bluetooth); + +// Pipe a normal file in Storage as a response to an HTTP request +function onPageRequest(req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + E.pipe(require("Storage").read("webpage.txt"), res); +} +require("http").createServer(onPageRequest).listen(80); +``` +*/ /*JSON{ "type" : "staticmethod", diff --git a/src/jswrap_pipe.c b/src/jswrap_pipe.c index df3bc12f68..c6f0b59bc2 100644 --- a/src/jswrap_pipe.c +++ b/src/jswrap_pipe.c @@ -52,10 +52,6 @@ static void handlePipeClose(JsVar *arr, JsvObjectIterator *it, JsVar* pipe) { jsvUnLock(jspExecuteFunction(writeFunc, destination, 1, &buffer)); } jsvUnLock(writeFunc); - // update position - JsVar *position = jsvObjectGetChildIfExists(pipe,"position"); - jsvSetInteger(position, jsvGetInteger(position) + (JsVarInt)jsvGetStringLength(buffer)); - jsvUnLock(position); } jsvUnLock(buffer); } @@ -104,13 +100,12 @@ static bool handlePipe(JsVar *arr, JsvObjectIterator *it, JsVar* pipe) { bool paused = jsvGetBoolAndUnLock(jsvObjectGetChildIfExists(pipe,"drainWait")); if (paused) return false; - JsVar *position = jsvObjectGetChildIfExists(pipe,"position"); JsVar *chunkSize = jsvObjectGetChildIfExists(pipe,"chunkSize"); JsVar *source = jsvObjectGetChildIfExists(pipe,"source"); JsVar *destination = jsvObjectGetChildIfExists(pipe,"destination"); bool dataTransferred = false; - if(source && destination && chunkSize && position) { + if(source && destination && chunkSize) { JsVar *readFunc = jspGetNamedField(source, "read", false); JsVar *writeFunc = jspGetNamedField(destination, "write", false); if (jsvIsFunction(readFunc) && jsvIsFunction(writeFunc)) { // do the objects have the necessary methods on them? @@ -124,7 +119,6 @@ static bool handlePipe(JsVar *arr, JsvObjectIterator *it, JsVar* pipe) { jsvObjectSetChildAndUnLock(pipe,"drainWait",jsvNewFromBool(true)); } jsvUnLock(response); - jsvSetInteger(position, jsvGetInteger(position) + bufferSize); } jsvUnLock(buffer); dataTransferred = true; // so we don't close the pipe if we get an empty string @@ -142,7 +136,6 @@ static bool handlePipe(JsVar *arr, JsvObjectIterator *it, JsVar* pipe) { handlePipeClose(arr, it, pipe); } jsvUnLock3(source, destination, chunkSize); - jsvUnLock(position); return dataTransferred; } @@ -256,13 +249,20 @@ type PipeOptions = { ["options","JsVar",["[optional] An object `{ chunkSize : int=64, 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"]] ], "typescript": "pipe(destination: any, options?: PipeOptions): void" -}*/ +} +Pipe this file to a destination stream (object which has a `.write(data)` method). +*/ void jswrap_pipe(JsVar* source, JsVar* dest, JsVar* options) { if (!source || !dest) return; + jsvLockAgain(source); // source should be unlocked at end JsVar *pipe = jspNewObject(0, "Pipe"); JsVar *arr = pipeGetArray(true); - JsVar* position = jsvNewFromInteger(0); - if (pipe && arr && position) {// out of memory? + if (pipe && arr) {// out of memory? + if (jsvIsString(source)) { // Single-line 'StringStream' object to add ability to stream from Strings + JsVar *stream = jspExecuteJSFunction("(function(s){var p=0;return{read:function(l){return s.substring(p,p+=l)||undefined;}}})",NULL,1,&source); + jsvUnLock(source); + source = stream; + } JsVar *readFunc = jspGetNamedField(source, "read", false); JsVar *writeFunc = jspGetNamedField(dest, "write", false); if(jsvIsFunction(readFunc)) { @@ -297,19 +297,18 @@ void jswrap_pipe(JsVar* source, JsVar* dest, JsVar* options) { // set up the rest of the pipe jsvObjectSetChildAndUnLock(pipe, "chunkSize", jsvNewFromInteger(chunkSize)); jsvObjectSetChildAndUnLock(pipe, "end", jsvNewFromBool(callEnd)); - jsvUnLock3(jsvAddNamedChild(pipe, position, "position"), - jsvAddNamedChild(pipe, source, "source"), + jsvUnLock2(jsvAddNamedChild(pipe, source, "source"), jsvAddNamedChild(pipe, dest, "destination")); // add the pipe to our list jsvArrayPush(arr, pipe); } else { - jsExceptionHere(JSET_ERROR, "Destination object does not implement the required write(buffer, length, position) method."); + jsExceptionHere(JSET_ERROR, "Destination object does not implement the required write(buffer, length) method."); } } else { - jsExceptionHere(JSET_ERROR, "Source object does not implement the required read(buffer, length, position) method."); + jsExceptionHere(JSET_ERROR, "Source object does not implement the required read(buffer, length) method."); } jsvUnLock2(readFunc, writeFunc); } - jsvUnLock3(arr, pipe, position); + jsvUnLock3(arr, pipe, source); }