mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-10-10 22:38:44 +02:00
fix(fs): truncate the file when writeFile writes a stream (#3629)
In plugins/fs/guest-js/index.ts writeFile, the stream path opened the
file with {write, create} but no truncate, unlike the buffer path which
truncates unless `append` is set (commands.rs write_file_inner), so
shorter content left stale trailing bytes.
The stream path now passes `truncate: !options?.append`. api-iife.js
regenerated. E2E spec covers overwrite and append with a stream.
This commit is contained in:
1 parent
5e6c023a79
commit
dcc8cb032b
4 files changed
+43
-2
No files matched your search
@@ -0,0 +1,6 @@
|
||||
---
|
||||
fs: patch
|
||||
fs-js: patch
|
||||
---
|
||||
|
||||
Fixed `writeFile` with a `ReadableStream` leaving the end of the previous contents in place when the new data is shorter. The file is now truncated first unless `append` is set, like when writing a buffer.
|
||||
@@ -99,6 +99,39 @@ describePlugin('fs', () => {
|
||||
expect(read).toEqual(bytes)
|
||||
})
|
||||
|
||||
it('writeFile with a stream overwrites or appends', async () => {
|
||||
const result = await tauri(async (api, path) => {
|
||||
const baseDir = api.fs.BaseDirectory.AppData
|
||||
const encoder = new TextEncoder()
|
||||
const streamOf = (...chunks: string[]) =>
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
for (const chunk of chunks) {
|
||||
controller.enqueue(encoder.encode(chunk))
|
||||
}
|
||||
controller.close()
|
||||
}
|
||||
})
|
||||
await api.fs.writeTextFile(path, 'a much longer previous content', {
|
||||
baseDir
|
||||
})
|
||||
await api.fs.writeFile(path, streamOf('short', ' stream'), { baseDir })
|
||||
const overwritten = await api.fs.readTextFile(path, { baseDir })
|
||||
await api.fs.writeFile(path, streamOf(', appended'), {
|
||||
baseDir,
|
||||
append: true
|
||||
})
|
||||
return {
|
||||
overwritten,
|
||||
appended: await api.fs.readTextFile(path, { baseDir })
|
||||
}
|
||||
}, `${dir}/stream.txt`)
|
||||
expect(result).toEqual({
|
||||
overwritten: 'short stream',
|
||||
appended: 'short stream, appended'
|
||||
})
|
||||
})
|
||||
|
||||
it('stat, lstat and size describe files and directories', async () => {
|
||||
const result = await tauri(async (api, dir) => {
|
||||
const baseDir = api.fs.BaseDirectory.AppData
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(e,t,n){"use strict";var i;function o(e){return{isFile:e.isFile,isDirectory:e.isDirectory,isSymlink:e.isSymlink,size:e.size,mtime:null!==e.mtime?new Date(e.mtime):null,atime:null!==e.atime?new Date(e.atime):null,birthtime:null!==e.birthtime?new Date(e.birthtime):null,readonly:e.readonly,fileAttributes:e.fileAttributes,dev:e.dev,ino:e.ino,mode:e.mode,nlink:e.nlink,uid:e.uid,gid:e.gid,rdev:e.rdev,blksize:e.blksize,blocks:e.blocks}}e.SeekMode=void 0,(i=e.SeekMode||(e.SeekMode={}))[i.Start=0]="Start",i[i.Current=1]="Current",i[i.End=2]="End";class r extends n.Resource{async read(e){if(0===e.byteLength)return 0;const t=await n.invoke("plugin:fs|read",{rid:this.rid,len:e.byteLength}),i=function(e){const t=new Uint8ClampedArray(e),n=t.byteLength;let i=0;for(let e=0;e<n;e++)i*=256,i+=t[e];return i}(t.slice(-8)),o=t instanceof ArrayBuffer?new Uint8Array(t):t;return e.set(o.slice(0,o.length-8)),0===i?null:i}async seek(e,t){return await n.invoke("plugin:fs|seek",{rid:this.rid,offset:e,whence:t})}async stat(){return o(await n.invoke("plugin:fs|fstat",{rid:this.rid}))}async truncate(e){await n.invoke("plugin:fs|ftruncate",{rid:this.rid,len:e})}async write(e){return await n.invoke("plugin:fs|write",{rid:this.rid,data:e})}}async function a(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=await n.invoke("plugin:fs|open",{path:e instanceof URL?e.toString():e,options:t});return new r(i)}class s extends n.Resource{}async function c(e,t,i){const o=Array.isArray(e)?e:[e];for(const e of o)if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const r=new n.Channel;r.onmessage=t;const a=await n.invoke("plugin:fs|watch",{paths:o.map(e=>e instanceof URL?e.toString():e),options:i,onEvent:r}),c=new s(a);return()=>{c.close()}}return Object.defineProperty(e,"BaseDirectory",{enumerable:!0,get:function(){return t.BaseDirectory}}),e.FileHandle=r,e.copyFile=async function(e,t,i){if(e instanceof URL&&"file:"!==e.protocol||t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await n.invoke("plugin:fs|copy_file",{fromPath:e instanceof URL?e.toString():e,toPath:t instanceof URL?t.toString():t,options:i})},e.create=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=await n.invoke("plugin:fs|create",{path:e instanceof URL?e.toString():e,options:t});return new r(i)},e.exists=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");return await n.invoke("plugin:fs|exists",{path:e instanceof URL?e.toString():e,options:t})},e.lstat=async function(e,t){return o(await n.invoke("plugin:fs|lstat",{path:e instanceof URL?e.toString():e,options:t}))},e.mkdir=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await n.invoke("plugin:fs|mkdir",{path:e instanceof URL?e.toString():e,options:t})},e.open=a,e.readDir=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");return await n.invoke("plugin:fs|read_dir",{path:e instanceof URL?e.toString():e,options:t})},e.readFile=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=await n.invoke("plugin:fs|read_file",{path:e instanceof URL?e.toString():e,options:t});return i instanceof ArrayBuffer?new Uint8Array(i):Uint8Array.from(i)},e.readTextFile=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=await n.invoke("plugin:fs|read_text_file",{path:e instanceof URL?e.toString():e,options:t}),o=i instanceof ArrayBuffer?i:Uint8Array.from(i);return new TextDecoder(t?.encoding??"utf-8").decode(o)},e.readTextFileLines=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=e instanceof URL?e.toString():e;return await Promise.resolve({path:i,rid:null,async next(){const e=new TextDecoder(t?.encoding??"utf-8");if(null===this.rid){const o=e.encoding;this.rid=await n.invoke("plugin:fs|read_text_file_lines",{path:i,options:null!=t?{...t,encoding:o}:void 0})}let o;try{o=await n.invoke("plugin:fs|read_text_file_lines_next",{rid:this.rid})}catch(e){throw this.rid=null,e}const r=o instanceof ArrayBuffer?new Uint8Array(o):Uint8Array.from(o),a=1===r[r.byteLength-1];if(a)return this.rid=null,{value:null,done:a};return{value:e.decode(r.slice(0,r.byteLength-1)),done:a}},async return(){if(null!==this.rid){const e=this.rid;this.rid=null,await new n.Resource(e).close()}return{value:null,done:!0}},[Symbol.asyncIterator](){return this}})},e.remove=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await n.invoke("plugin:fs|remove",{path:e instanceof URL?e.toString():e,options:t})},e.rename=async function(e,t,i){if(e instanceof URL&&"file:"!==e.Line truncated
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(e,t,n){"use strict";var i;function o(e){return{isFile:e.isFile,isDirectory:e.isDirectory,isSymlink:e.isSymlink,size:e.size,mtime:null!==e.mtime?new Date(e.mtime):null,atime:null!==e.atime?new Date(e.atime):null,birthtime:null!==e.birthtime?new Date(e.birthtime):null,readonly:e.readonly,fileAttributes:e.fileAttributes,dev:e.dev,ino:e.ino,mode:e.mode,nlink:e.nlink,uid:e.uid,gid:e.gid,rdev:e.rdev,blksize:e.blksize,blocks:e.blocks}}e.SeekMode=void 0,(i=e.SeekMode||(e.SeekMode={}))[i.Start=0]="Start",i[i.Current=1]="Current",i[i.End=2]="End";class r extends n.Resource{async read(e){if(0===e.byteLength)return 0;const t=await n.invoke("plugin:fs|read",{rid:this.rid,len:e.byteLength}),i=function(e){const t=new Uint8ClampedArray(e),n=t.byteLength;let i=0;for(let e=0;e<n;e++)i*=256,i+=t[e];return i}(t.slice(-8)),o=t instanceof ArrayBuffer?new Uint8Array(t):t;return e.set(o.slice(0,o.length-8)),0===i?null:i}async seek(e,t){return await n.invoke("plugin:fs|seek",{rid:this.rid,offset:e,whence:t})}async stat(){return o(await n.invoke("plugin:fs|fstat",{rid:this.rid}))}async truncate(e){await n.invoke("plugin:fs|ftruncate",{rid:this.rid,len:e})}async write(e){return await n.invoke("plugin:fs|write",{rid:this.rid,data:e})}}async function a(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=await n.invoke("plugin:fs|open",{path:e instanceof URL?e.toString():e,options:t});return new r(i)}class s extends n.Resource{}async function c(e,t,i){const o=Array.isArray(e)?e:[e];for(const e of o)if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const r=new n.Channel;r.onmessage=t;const a=await n.invoke("plugin:fs|watch",{paths:o.map(e=>e instanceof URL?e.toString():e),options:i,onEvent:r}),c=new s(a);return()=>{c.close()}}return Object.defineProperty(e,"BaseDirectory",{enumerable:!0,get:function(){return t.BaseDirectory}}),e.FileHandle=r,e.copyFile=async function(e,t,i){if(e instanceof URL&&"file:"!==e.protocol||t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await n.invoke("plugin:fs|copy_file",{fromPath:e instanceof URL?e.toString():e,toPath:t instanceof URL?t.toString():t,options:i})},e.create=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=await n.invoke("plugin:fs|create",{path:e instanceof URL?e.toString():e,options:t});return new r(i)},e.exists=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");return await n.invoke("plugin:fs|exists",{path:e instanceof URL?e.toString():e,options:t})},e.lstat=async function(e,t){return o(await n.invoke("plugin:fs|lstat",{path:e instanceof URL?e.toString():e,options:t}))},e.mkdir=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await n.invoke("plugin:fs|mkdir",{path:e instanceof URL?e.toString():e,options:t})},e.open=a,e.readDir=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");return await n.invoke("plugin:fs|read_dir",{path:e instanceof URL?e.toString():e,options:t})},e.readFile=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=await n.invoke("plugin:fs|read_file",{path:e instanceof URL?e.toString():e,options:t});return i instanceof ArrayBuffer?new Uint8Array(i):Uint8Array.from(i)},e.readTextFile=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=await n.invoke("plugin:fs|read_text_file",{path:e instanceof URL?e.toString():e,options:t}),o=i instanceof ArrayBuffer?i:Uint8Array.from(i);return new TextDecoder(t?.encoding??"utf-8").decode(o)},e.readTextFileLines=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");const i=e instanceof URL?e.toString():e;return await Promise.resolve({path:i,rid:null,async next(){const e=new TextDecoder(t?.encoding??"utf-8");if(null===this.rid){const o=e.encoding;this.rid=await n.invoke("plugin:fs|read_text_file_lines",{path:i,options:null!=t?{...t,encoding:o}:void 0})}let o;try{o=await n.invoke("plugin:fs|read_text_file_lines_next",{rid:this.rid})}catch(e){throw this.rid=null,e}const r=o instanceof ArrayBuffer?new Uint8Array(o):Uint8Array.from(o),a=1===r[r.byteLength-1];if(a)return this.rid=null,{value:null,done:a};return{value:e.decode(r.slice(0,r.byteLength-1)),done:a}},async return(){if(null!==this.rid){const e=this.rid;this.rid=null,await new n.Resource(e).close()}return{value:null,done:!0}},[Symbol.asyncIterator](){return this}})},e.remove=async function(e,t){if(e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await n.invoke("plugin:fs|remove",{path:e instanceof URL?e.toString():e,options:t})},e.rename=async function(e,t,i){if(e instanceof URL&&"file:"!==e.Line truncated
|
||||
@@ -1193,7 +1193,9 @@ async function writeFile(
|
||||
read: false,
|
||||
create: true,
|
||||
write: true,
|
||||
...options
|
||||
...options,
|
||||
// overwrite the previous contents, like when writing a buffer
|
||||
truncate: !options?.append
|
||||
})
|
||||
const reader = data.getReader()
|
||||
|
||||
|
||||
Reference in new issue
Block a user