Skip to content

Commit 5092ea5

Browse files
authored
feat(fs): support ReadableStream<Unit8Array> for writeFile API (tauri-apps#1964)
1 parent ac2edc2 commit 5092ea5

File tree

8 files changed

+120
-41
lines changed

8 files changed

+120
-41
lines changed

.changes/fs-readable-stream.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"fs": "patch"
3+
"fs-js": "patch"
4+
---
5+
6+
Add support for using `ReadableStream<Unit8Array>` with `writeFile` API.
7+

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/fs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ ios = { level = "partial", notes = "Access is restricted to Application folder b
2424
tauri-plugin = { workspace = true, features = ["build"] }
2525
schemars = { workspace = true }
2626
serde = { workspace = true }
27+
toml = "0.8"
28+
tauri-utils = { workspace = true, features = ["build"] }
2729

2830
[dependencies]
2931
serde = { workspace = true }

plugins/fs/api-iife.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/fs/build.rs

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use std::{
77
path::{Path, PathBuf},
88
};
99

10+
use tauri_utils::acl::manifest::PermissionFile;
11+
1012
#[path = "src/scope.rs"]
1113
#[allow(dead_code)]
1214
mod scope;
@@ -75,31 +77,31 @@ const BASE_DIR_VARS: &[&str] = &[
7577
"APPCACHE",
7678
"APPLOG",
7779
];
78-
const COMMANDS: &[&str] = &[
79-
"mkdir",
80-
"create",
81-
"copy_file",
82-
"remove",
83-
"rename",
84-
"truncate",
85-
"ftruncate",
86-
"write",
87-
"write_file",
88-
"write_text_file",
89-
"read_dir",
90-
"read_file",
91-
"read",
92-
"open",
93-
"read_text_file",
94-
"read_text_file_lines",
95-
"read_text_file_lines_next",
96-
"seek",
97-
"stat",
98-
"lstat",
99-
"fstat",
100-
"exists",
101-
"watch",
102-
"unwatch",
80+
const COMMANDS: &[(&str, &[&str])] = &[
81+
("mkdir", &[]),
82+
("create", &[]),
83+
("copy_file", &[]),
84+
("remove", &[]),
85+
("rename", &[]),
86+
("truncate", &[]),
87+
("ftruncate", &[]),
88+
("write", &[]),
89+
("write_file", &["open", "write"]),
90+
("write_text_file", &[]),
91+
("read_dir", &[]),
92+
("read_file", &[]),
93+
("read", &[]),
94+
("open", &[]),
95+
("read_text_file", &[]),
96+
("read_text_file_lines", &["read_text_file_lines_next"]),
97+
("read_text_file_lines_next", &[]),
98+
("seek", &[]),
99+
("stat", &[]),
100+
("lstat", &[]),
101+
("fstat", &[]),
102+
("exists", &[]),
103+
("watch", &[]),
104+
("unwatch", &[]),
103105
];
104106

105107
fn main() {
@@ -205,9 +207,47 @@ permissions = [
205207
}
206208
}
207209

208-
tauri_plugin::Builder::new(COMMANDS)
210+
tauri_plugin::Builder::new(&COMMANDS.iter().map(|c| c.0).collect::<Vec<_>>())
209211
.global_api_script_path("./api-iife.js")
210212
.global_scope_schema(schemars::schema_for!(FsScopeEntry))
211213
.android_path("android")
212214
.build();
215+
216+
// workaround to include nested permissions as `tauri_plugin` doesn't support it
217+
let permissions_dir = autogenerated.join("commands");
218+
for (command, nested_commands) in COMMANDS {
219+
if nested_commands.is_empty() {
220+
continue;
221+
}
222+
223+
let permission_path = permissions_dir.join(format!("{command}.toml"));
224+
225+
let content = std::fs::read_to_string(&permission_path)
226+
.unwrap_or_else(|_| panic!("failed to read {command}.toml"));
227+
228+
let mut permission_file = toml::from_str::<PermissionFile>(&content)
229+
.unwrap_or_else(|_| panic!("failed to deserialize {command}.toml"));
230+
231+
for p in permission_file
232+
.permission
233+
.iter_mut()
234+
.filter(|p| p.identifier.starts_with("allow"))
235+
{
236+
p.commands
237+
.allow
238+
.extend(nested_commands.iter().map(|s| s.to_string()));
239+
}
240+
241+
let out = toml::to_string_pretty(&permission_file)
242+
.unwrap_or_else(|_| panic!("failed to serialize {command}.toml"));
243+
let out = format!(
244+
r#"# Automatically generated - DO NOT EDIT!
245+
246+
"$schema" = "../../schemas/schema.json"
247+
248+
{out}"#
249+
);
250+
std::fs::write(permission_path, out)
251+
.unwrap_or_else(|_| panic!("failed to write {command}.toml"));
252+
}
213253
}

plugins/fs/guest-js/index.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ function fromBytes(buffer: FixedSizeArray<number, 8>): number {
266266
const size = bytes.byteLength
267267
let x = 0
268268
for (let i = 0; i < size; i++) {
269+
// eslint-disable-next-line security/detect-object-injection
269270
const byte = bytes[i]
270271
x *= 0x100
271272
x += byte
@@ -427,11 +428,11 @@ class FileHandle extends Resource {
427428
}
428429

429430
/**
430-
* Writes `p.byteLength` bytes from `p` to the underlying data stream. It
431-
* resolves to the number of bytes written from `p` (`0` <= `n` <=
432-
* `p.byteLength`) or reject with the error encountered that caused the
431+
* Writes `data.byteLength` bytes from `data` to the underlying data stream. It
432+
* resolves to the number of bytes written from `data` (`0` <= `n` <=
433+
* `data.byteLength`) or reject with the error encountered that caused the
433434
* write to stop early. `write()` must reject with a non-null error if
434-
* would resolve to `n` < `p.byteLength`. `write()` must not modify the
435+
* would resolve to `n` < `data.byteLength`. `write()` must not modify the
435436
* slice data, even temporarily.
436437
*
437438
* @example
@@ -1044,19 +1045,27 @@ interface WriteFileOptions {
10441045
*/
10451046
async function writeFile(
10461047
path: string | URL,
1047-
data: Uint8Array,
1048+
data: Uint8Array | ReadableStream<Uint8Array>,
10481049
options?: WriteFileOptions
10491050
): Promise<void> {
10501051
if (path instanceof URL && path.protocol !== 'file:') {
10511052
throw new TypeError('Must be a file URL.')
10521053
}
10531054

1054-
await invoke('plugin:fs|write_file', data, {
1055-
headers: {
1056-
path: encodeURIComponent(path instanceof URL ? path.toString() : path),
1057-
options: JSON.stringify(options)
1055+
if (data instanceof ReadableStream) {
1056+
const file = await open(path, options)
1057+
for await (const chunk of data) {
1058+
await file.write(chunk)
10581059
}
1059-
})
1060+
await file.close()
1061+
} else {
1062+
await invoke('plugin:fs|write_file', data, {
1063+
headers: {
1064+
path: encodeURIComponent(path instanceof URL ? path.toString() : path),
1065+
options: JSON.stringify(options)
1066+
}
1067+
})
1068+
}
10601069
}
10611070

10621071
/**

plugins/fs/permissions/autogenerated/commands/read_text_file_lines.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@
55
[[permission]]
66
identifier = "allow-read-text-file-lines"
77
description = "Enables the read_text_file_lines command without any pre-configured scope."
8-
commands.allow = ["read_text_file_lines"]
8+
9+
[permission.commands]
10+
allow = [
11+
"read_text_file_lines",
12+
"read_text_file_lines_next",
13+
]
14+
deny = []
915

1016
[[permission]]
1117
identifier = "deny-read-text-file-lines"
1218
description = "Denies the read_text_file_lines command without any pre-configured scope."
13-
commands.deny = ["read_text_file_lines"]
19+
20+
[permission.commands]
21+
allow = []
22+
deny = ["read_text_file_lines"]

plugins/fs/permissions/autogenerated/commands/write_file.toml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,19 @@
55
[[permission]]
66
identifier = "allow-write-file"
77
description = "Enables the write_file command without any pre-configured scope."
8-
commands.allow = ["write_file"]
8+
9+
[permission.commands]
10+
allow = [
11+
"write_file",
12+
"open",
13+
"write",
14+
]
15+
deny = []
916

1017
[[permission]]
1118
identifier = "deny-write-file"
1219
description = "Denies the write_file command without any pre-configured scope."
13-
commands.deny = ["write_file"]
20+
21+
[permission.commands]
22+
allow = []
23+
deny = ["write_file"]

0 commit comments

Comments
 (0)