mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-07-28 17:38:53 +02:00
fix(fs): writeFile with ReadableStream throws (#2907)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
fs: patch
|
||||||
|
fs-js: patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument`
|
||||||
@@ -1,194 +1,190 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as fs from "@tauri-apps/plugin-fs";
|
import * as fs from '@tauri-apps/plugin-fs'
|
||||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { arrayBufferToBase64 } from "../lib/utils";
|
import { arrayBufferToBase64 } from '../lib/utils'
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from 'svelte'
|
||||||
|
|
||||||
export let onMessage;
|
const { onMessage, insecureRenderHtml } = $props()
|
||||||
export let insecureRenderHtml;
|
|
||||||
|
|
||||||
let path = "";
|
let path = $state('')
|
||||||
let img;
|
let img
|
||||||
/** @type {fs.FileHandle} */
|
/** @type {fs.FileHandle} */
|
||||||
let file;
|
let file = $state()
|
||||||
let renameTo;
|
let renameTo = $state()
|
||||||
let watchPath = "";
|
let watchPath = $state('')
|
||||||
let watchDebounceDelay = "0";
|
let watchDebounceDelay = $state(0)
|
||||||
let watchRecursive = false;
|
let watchRecursive = $state(false)
|
||||||
let unwatchFn;
|
/** @type {fs.BaseDirectory | undefined} */
|
||||||
let unwatchPath = "";
|
let baseDir = $state()
|
||||||
|
let unwatchFn
|
||||||
|
let unwatchPath = ''
|
||||||
|
|
||||||
function getDir() {
|
const dirOptions = Object.keys(fs.BaseDirectory).filter((key) =>
|
||||||
const dirSelect = document.getElementById("dir");
|
isNaN(parseInt(key))
|
||||||
return dirSelect.value ? parseInt(dir.value) : null;
|
)
|
||||||
}
|
|
||||||
|
|
||||||
const DirOptions = Object.keys(fs.BaseDirectory)
|
|
||||||
.filter((key) => isNaN(parseInt(key)))
|
|
||||||
.map((dir) => [dir, fs.BaseDirectory[dir]]);
|
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
fs.open(path, {
|
fs.open(path, {
|
||||||
baseDir: getDir(),
|
baseDir,
|
||||||
read: true,
|
read: true,
|
||||||
write: true,
|
write: true,
|
||||||
create: true,
|
create: true
|
||||||
})
|
})
|
||||||
.then((f) => {
|
.then((f) => {
|
||||||
file = f;
|
file = f
|
||||||
onMessage(`Opened ${path}`);
|
onMessage(`Opened ${path}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkdir() {
|
function mkdir() {
|
||||||
fs.mkdir(path, { baseDir: getDir() })
|
fs.mkdir(path, { baseDir })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onMessage(`Created dir ${path}`);
|
onMessage(`Created dir ${path}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
fs.remove(path, { baseDir: getDir() })
|
fs.remove(path, { baseDir })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onMessage(`Removed ${path}`);
|
onMessage(`Removed ${path}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function rename() {
|
function rename() {
|
||||||
fs.rename(path, renameTo, {
|
fs.rename(path, renameTo, {
|
||||||
oldPathBaseDir: getDir(),
|
oldPathBaseDir,
|
||||||
newPathBaseDir: getDir(),
|
newPathBaseDir
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onMessage(`Renamed ${path} to ${renameTo}`);
|
onMessage(`Renamed ${path} to ${renameTo}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncate() {
|
function truncate() {
|
||||||
file
|
file
|
||||||
.truncate(0)
|
.truncate(0)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onMessage(`Truncated file`);
|
onMessage(`Truncated file`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stat() {
|
function stat() {
|
||||||
file
|
file
|
||||||
.stat()
|
.stat()
|
||||||
.then((stat) => {
|
.then((stat) => {
|
||||||
onMessage(`File stat ${JSON.stringify(stat)}`);
|
onMessage(`File stat ${JSON.stringify(stat)}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function read() {
|
function read() {
|
||||||
const opts = {
|
const opts = {
|
||||||
baseDir: getDir(),
|
baseDir
|
||||||
};
|
}
|
||||||
fs.stat(path, opts)
|
fs.stat(path, opts)
|
||||||
.then((stat) => {
|
.then((stat) => {
|
||||||
const isFile = stat.isFile;
|
const isFile = stat.isFile
|
||||||
|
|
||||||
const promise = isFile
|
const promise = isFile
|
||||||
? fs.readFile(path, opts)
|
? fs.readFile(path, opts)
|
||||||
: fs.readDir(path, opts);
|
: fs.readDir(path, opts)
|
||||||
promise
|
promise
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
if (isFile) {
|
if (isFile) {
|
||||||
if (path.includes(".png") || path.includes(".jpg")) {
|
if (path.includes('.png') || path.includes('.jpg')) {
|
||||||
arrayBufferToBase64(
|
arrayBufferToBase64(
|
||||||
new Uint8Array(response),
|
new Uint8Array(response),
|
||||||
function (base64) {
|
function (base64) {
|
||||||
const src = "data:image/png;base64," + base64;
|
const src = 'data:image/png;base64,' + base64
|
||||||
insecureRenderHtml('<img src="' + src + '"></img>');
|
insecureRenderHtml('<img src="' + src + '"></img>')
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
const value = String.fromCharCode.apply(null, response);
|
const value = String.fromCharCode.apply(null, response)
|
||||||
insecureRenderHtml(
|
insecureRenderHtml(
|
||||||
'<textarea id="file-response"></textarea><button id="file-save">Save</button>'
|
'<textarea id="file-response"></textarea><button id="file-save">Save</button>'
|
||||||
);
|
)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const fileInput = document.getElementById("file-response");
|
const fileInput = document.getElementById('file-response')
|
||||||
fileInput.value = value;
|
fileInput.value = value
|
||||||
document
|
document
|
||||||
.getElementById("file-save")
|
.getElementById('file-save')
|
||||||
.addEventListener("click", function () {
|
.addEventListener('click', function () {
|
||||||
fs.writeTextFile(path, fileInput.value, {
|
fs.writeTextFile(path, fileInput.value, {
|
||||||
baseDir: getDir(),
|
baseDir
|
||||||
}).catch(onMessage);
|
}).catch(onMessage)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onMessage(response);
|
onMessage(response)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSrc() {
|
function setSrc() {
|
||||||
img.src = convertFileSrc(path);
|
img.src = convertFileSrc(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
function watch() {
|
function watch() {
|
||||||
unwatch();
|
unwatch()
|
||||||
if (watchPath) {
|
if (watchPath) {
|
||||||
onMessage(`Watching ${watchPath} for changes`);
|
onMessage(`Watching ${watchPath} for changes`)
|
||||||
let options = {
|
let options = {
|
||||||
recursive: watchRecursive,
|
recursive: watchRecursive,
|
||||||
delayMs: parseInt(watchDebounceDelay),
|
delayMs: watchDebounceDelay
|
||||||
};
|
}
|
||||||
if (options.delayMs === 0) {
|
if (options.delayMs === 0) {
|
||||||
fs.watchImmediate(watchPath, onMessage, options)
|
fs.watchImmediate(watchPath, onMessage, options)
|
||||||
.then((fn) => {
|
.then((fn) => {
|
||||||
unwatchFn = fn;
|
unwatchFn = fn
|
||||||
unwatchPath = watchPath;
|
unwatchPath = watchPath
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
} else {
|
} else {
|
||||||
fs.watch(watchPath, onMessage, options)
|
fs.watch(watchPath, onMessage, options)
|
||||||
.then((fn) => {
|
.then((fn) => {
|
||||||
unwatchFn = fn;
|
unwatchFn = fn
|
||||||
unwatchPath = watchPath;
|
unwatchPath = watchPath
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unwatch() {
|
function unwatch() {
|
||||||
if (unwatchFn) {
|
if (unwatchFn) {
|
||||||
onMessage(`Stopped watching ${unwatchPath} for changes`);
|
onMessage(`Stopped watching ${unwatchPath} for changes`)
|
||||||
unwatchFn();
|
unwatchFn()
|
||||||
}
|
}
|
||||||
unwatchFn = undefined;
|
unwatchFn = undefined
|
||||||
unwatchPath = undefined;
|
unwatchPath = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (file) {
|
if (file) {
|
||||||
file.close();
|
file.close()
|
||||||
}
|
}
|
||||||
if (unwatchFn) {
|
if (unwatchFn) {
|
||||||
unwatchFn();
|
unwatchFn()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<select class="input" id="dir">
|
<select class="input" bind:value={baseDir}>
|
||||||
<option value="">None</option>
|
<option value={undefined} selected>None</option>
|
||||||
{#each DirOptions as dir}
|
{#each dirOptions as dir}
|
||||||
<option value={dir[1]}>{dir[0]}</option>
|
<option value={fs.BaseDirectory[dir]}>{dir}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<input
|
<input
|
||||||
@@ -199,20 +195,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" on:click={open}>Open</button>
|
<button class="btn" onclick={open}>Open</button>
|
||||||
<button class="btn" on:click={read}>Read</button>
|
<button class="btn" onclick={read}>Read</button>
|
||||||
<button class="btn" on:click={mkdir}>Mkdir</button>
|
<button class="btn" onclick={mkdir}>Mkdir</button>
|
||||||
<button class="btn" on:click={remove}>Remove</button>
|
<button class="btn" onclick={remove}>Remove</button>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<button class="btn" on:click={rename}>Rename</button>
|
<button class="btn" onclick={rename}>Rename</button>
|
||||||
<input class="input" bind:value={renameTo} placeholder="To" />
|
<input class="input" bind:value={renameTo} placeholder="To" />
|
||||||
</div>
|
</div>
|
||||||
<button class="btn" type="button" on:click={setSrc}>Use as img src</button>
|
<button class="btn" type="button" onclick={setSrc}>Use as img src</button>
|
||||||
</div>
|
</div>
|
||||||
{#if file}
|
{#if file}
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" on:click={truncate}>Truncate</button>
|
<button class="btn" onclick={truncate}>Truncate</button>
|
||||||
<button class="btn" on:click={stat}>Stat</button>
|
<button class="btn" onclick={stat}>Stat</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -241,8 +237,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" on:click={watch}>Watch</button>
|
<button class="btn" onclick={watch}>Watch</button>
|
||||||
<button class="btn" on:click={unwatch}>Unwatch</button>
|
<button class="btn" onclick={unwatch}>Unwatch</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1074,7 +1074,12 @@ async function writeFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data instanceof ReadableStream) {
|
if (data instanceof ReadableStream) {
|
||||||
const file = await open(path, { create: true, ...options })
|
const file = await open(path, {
|
||||||
|
read: false,
|
||||||
|
create: true,
|
||||||
|
write: true,
|
||||||
|
...options
|
||||||
|
})
|
||||||
const reader = data.getReader()
|
const reader = data.getReader()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user