From c8b8f9ed59c0d99e7a9fc677fa60a82b648a3c00 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 21 Oct 2024 18:18:23 +0300 Subject: [PATCH] improve `readTextFileLines` --- plugins/fs/api-iife.js | 2 +- plugins/fs/guest-js/index.ts | 21 ++++++++++++++---- plugins/fs/src/commands.rs | 42 ++++++++++++++++++++++++------------ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/plugins/fs/api-iife.js b/plugins/fs/api-iife.js index 18076c61..0fdbdd2c 100644 --- a/plugins/fs/api-iife.js +++ b/plugins/fs/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e?t!==e||!o:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a,s,c;"function"==typeof SuppressedError&&SuppressedError;class f{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),o.set(this,0),r.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:a})=>{if(a===e(this,o,"f")){n(this,o,a+1),e(this,i,"f").call(this,t);const s=Object.keys(e(this,r,"f"));if(s.length>0){let t=a+1;for(const n of s.sort()){if(parseInt(n)!==t)break;{const o=e(this,r,"f")[n];delete e(this,r,"f")[n],e(this,i,"f").call(this,o),t+=1}}n(this,o,t)}}else e(this,r,"f")[a.toString()]=t}))}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}i=new WeakMap,o=new WeakMap,r=new WeakMap;class u{get rid(){return e(this,a,"f")}constructor(t){a.set(this,void 0),n(this,a,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}function p(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}a=new WeakMap,t.BaseDirectory=void 0,(s=t.BaseDirectory||(t.BaseDirectory={}))[s.Audio=1]="Audio",s[s.Cache=2]="Cache",s[s.Config=3]="Config",s[s.Data=4]="Data",s[s.LocalData=5]="LocalData",s[s.Document=6]="Document",s[s.Download=7]="Download",s[s.Picture=8]="Picture",s[s.Public=9]="Public",s[s.Video=10]="Video",s[s.Resource=11]="Resource",s[s.Temp=12]="Temp",s[s.AppConfig=13]="AppConfig",s[s.AppData=14]="AppData",s[s.AppLocalData=15]="AppLocalData",s[s.AppCache=16]="AppCache",s[s.AppLog=17]="AppLog",s[s.Desktop=18]="Desktop",s[s.Executable=19]="Executable",s[s.Font=20]="Font",s[s.Home=21]="Home",s[s.Runtime=22]="Runtime",s[s.Template=23]="Template",t.SeekMode=void 0,(c=t.SeekMode||(t.SeekMode={}))[c.Start=0]="Start",c[c.Current=1]="Current",c[c.End=2]="End";class w extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;tt instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{h(a)}},t.watchImmediate=async function(t,e,n){const i={recursive:!1,...n,delayMs:null},o=Array.isArray(t)?t:[t];for(const t of o)if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const r=new f;r.onmessage=e;const a=await l("plugin:fs|watch",{paths:o.map((t=>t instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{h(a)}},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await l("plugin:fs|write_text_file",i.encode(e),{headers:{path:t instanceof URL?t.toString():t,options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e?t!==e||!o:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a,s,c;"function"==typeof SuppressedError&&SuppressedError;class f{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),o.set(this,0),r.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:a})=>{if(a===e(this,o,"f")){n(this,o,a+1),e(this,i,"f").call(this,t);const s=Object.keys(e(this,r,"f"));if(s.length>0){let t=a+1;for(const n of s.sort()){if(parseInt(n)!==t)break;{const o=e(this,r,"f")[n];delete e(this,r,"f")[n],e(this,i,"f").call(this,o),t+=1}}n(this,o,t)}}else e(this,r,"f")[a.toString()]=t}))}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}i=new WeakMap,o=new WeakMap,r=new WeakMap;class u{get rid(){return e(this,a,"f")}constructor(t){a.set(this,void 0),n(this,a,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}function p(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}a=new WeakMap,t.BaseDirectory=void 0,(s=t.BaseDirectory||(t.BaseDirectory={}))[s.Audio=1]="Audio",s[s.Cache=2]="Cache",s[s.Config=3]="Config",s[s.Data=4]="Data",s[s.LocalData=5]="LocalData",s[s.Document=6]="Document",s[s.Download=7]="Download",s[s.Picture=8]="Picture",s[s.Public=9]="Public",s[s.Video=10]="Video",s[s.Resource=11]="Resource",s[s.Temp=12]="Temp",s[s.AppConfig=13]="AppConfig",s[s.AppData=14]="AppData",s[s.AppLocalData=15]="AppLocalData",s[s.AppCache=16]="AppCache",s[s.AppLog=17]="AppLog",s[s.Desktop=18]="Desktop",s[s.Executable=19]="Executable",s[s.Font=20]="Font",s[s.Home=21]="Home",s[s.Runtime=22]="Runtime",s[s.Template=23]="Template",t.SeekMode=void 0,(c=t.SeekMode||(t.SeekMode={}))[c.Start=0]="Start",c[c.Current=1]="Current",c[c.End=2]="End";class w extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;tt instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{d(a)}},t.watchImmediate=async function(t,e,n){const i={recursive:!1,...n,delayMs:null},o=Array.isArray(t)?t:[t];for(const t of o)if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const r=new f;r.onmessage=e;const a=await l("plugin:fs|watch",{paths:o.map((t=>t instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{d(a)}},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await l("plugin:fs|write_text_file",i.encode(e),{headers:{path:t instanceof URL?t.toString():t,options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})} diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts index 4d39d077..f8878ae6 100644 --- a/plugins/fs/guest-js/index.ts +++ b/plugins/fs/guest-js/index.ts @@ -796,6 +796,7 @@ async function readTextFileLines( return await Promise.resolve({ path: pathStr, rid: null as number | null, + async next(): Promise> { if (this.rid === null) { this.rid = await invoke('plugin:fs|read_text_file_lines', { @@ -804,19 +805,31 @@ async function readTextFileLines( }) } - const [line, done] = await invoke<[string | null, boolean]>( + const arr = await invoke( 'plugin:fs|read_text_file_lines_next', { rid: this.rid } ) - // an iteration is over, reset rid for next iteration - if (done) this.rid = null + const bytes = + arr instanceof ArrayBuffer ? new Uint8Array(arr) : Uint8Array.from(arr) + + const done = bytes[bytes.byteLength - 1] === 1 + + if (done) { + // a full iteration is over, reset rid for next iteration + this.rid = null + return { value: null, done } + } + + const data = bytes.slice(0, bytes.byteLength) + const line = data.byteLength !== 0 ? new TextDecoder().decode(data) : '' return { - value: done ? '' : line!, + value: line, done } }, + [Symbol.asyncIterator](): AsyncIterableIterator { return this } diff --git a/plugins/fs/src/commands.rs b/plugins/fs/src/commands.rs index 7ceddeda..ed9500a1 100644 --- a/plugins/fs/src/commands.rs +++ b/plugins/fs/src/commands.rs @@ -15,7 +15,7 @@ use tauri::{ use std::{ borrow::Cow, fs::File, - io::{BufReader, Lines, Read, Write}, + io::{BufRead, BufReader, Read, Write}, path::{Path, PathBuf}, str::FromStr, sync::Mutex, @@ -389,8 +389,6 @@ pub fn read_text_file_lines( path: SafeFilePath, options: Option, ) -> CommandResult { - use std::io::BufRead; - let resolved_path = resolve_path( &webview, &global_scope, @@ -406,7 +404,7 @@ pub fn read_text_file_lines( ) })?; - let lines = BufReader::new(file).lines(); + let lines = BufReader::new(file); let rid = webview.resources_table().add(StdLinesResource::new(lines)); Ok(rid) @@ -416,18 +414,34 @@ pub fn read_text_file_lines( pub async fn read_text_file_lines_next( webview: Webview, rid: ResourceId, -) -> CommandResult<(Option, bool)> { +) -> CommandResult { let mut resource_table = webview.resources_table(); let lines = resource_table.get::(rid)?; - let ret = StdLinesResource::with_lock(&lines, |lines| { - lines.next().map(|a| (a.ok(), false)).unwrap_or_else(|| { - let _ = resource_table.close(rid); - (None, true) - }) + let ret = StdLinesResource::with_lock(&lines, |lines| -> CommandResult> { + let mut buf = Vec::new(); + + match lines.read_until(b'\n', &mut buf) { + Ok(0) => { + resource_table.close(rid)?; + buf.push(true as u8); + } + // retain same behavior as `BufReader::lines` and `Lines` iterator + Err(_) | Ok(_) => { + if buf.last() == Some(&b'\n') { + buf.pop(); + if buf.last() == Some(&b'\r') { + buf.pop(); + } + } + buf.push(false as u8); + } + } + + Ok(buf) }); - Ok(ret) + ret.map(tauri::ipc::Response::new) } #[derive(Debug, Clone, Deserialize)] @@ -1012,14 +1026,14 @@ impl StdFileResource { impl Resource for StdFileResource {} -struct StdLinesResource(Mutex>>); +struct StdLinesResource(Mutex>); impl StdLinesResource { - fn new(lines: Lines>) -> Self { + fn new(lines: BufReader) -> Self { Self(Mutex::new(lines)) } - fn with_lock>) -> R>(&self, mut f: F) -> R { + fn with_lock) -> R>(&self, mut f: F) -> R { let mut lines = self.0.lock().unwrap(); f(&mut lines) }