Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fs): support ReadableStream<Unit8Array> for writeFile API #1964

Merged
merged 6 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changes/fs-readable-stream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"fs": "patch"
"fs-js": "patch"
---

Add support for using `ReadableStream<Unit8Array>` with `writeFile` API.

2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions plugins/fs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ ios = { level = "partial", notes = "Access is restricted to Application folder b
tauri-plugin = { workspace = true, features = ["build"] }
schemars = { workspace = true }
serde = { workspace = true }
toml = "0.8"
tauri-utils = { workspace = true, features = ["build"] }

[dependencies]
serde = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion plugins/fs/api-iife.js

Large diffs are not rendered by default.

92 changes: 66 additions & 26 deletions plugins/fs/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use std::{
path::{Path, PathBuf},
};

use tauri_utils::acl::manifest::PermissionFile;

#[path = "src/scope.rs"]
#[allow(dead_code)]
mod scope;
Expand Down Expand Up @@ -75,31 +77,31 @@ const BASE_DIR_VARS: &[&str] = &[
"APPCACHE",
"APPLOG",
];
const COMMANDS: &[&str] = &[
"mkdir",
"create",
"copy_file",
"remove",
"rename",
"truncate",
"ftruncate",
"write",
"write_file",
"write_text_file",
"read_dir",
"read_file",
"read",
"open",
"read_text_file",
"read_text_file_lines",
"read_text_file_lines_next",
"seek",
"stat",
"lstat",
"fstat",
"exists",
"watch",
"unwatch",
const COMMANDS: &[(&str, &[&str])] = &[
("mkdir", &[]),
("create", &[]),
("copy_file", &[]),
("remove", &[]),
("rename", &[]),
("truncate", &[]),
("ftruncate", &[]),
("write", &[]),
("write_file", &["open", "write"]),
("write_text_file", &[]),
("read_dir", &[]),
("read_file", &[]),
("read", &[]),
("open", &[]),
("read_text_file", &[]),
("read_text_file_lines", &["read_text_file_lines_next"]),
("read_text_file_lines_next", &[]),
("seek", &[]),
("stat", &[]),
("lstat", &[]),
("fstat", &[]),
("exists", &[]),
("watch", &[]),
("unwatch", &[]),
];

fn main() {
Expand Down Expand Up @@ -205,9 +207,47 @@ permissions = [
}
}

tauri_plugin::Builder::new(COMMANDS)
tauri_plugin::Builder::new(&COMMANDS.iter().map(|c| c.0).collect::<Vec<_>>())
.global_api_script_path("./api-iife.js")
.global_scope_schema(schemars::schema_for!(FsScopeEntry))
.android_path("android")
.build();

// workaround to include nested permissions as `tauri_plugin` doesn't support it
let permissions_dir = autogenerated.join("commands");
for (command, nested_commands) in COMMANDS {
if nested_commands.is_empty() {
continue;
}

let permission_path = permissions_dir.join(format!("{command}.toml"));

let content = std::fs::read_to_string(&permission_path)
.unwrap_or_else(|_| panic!("failed to read {command}.toml"));

let mut permission_file = toml::from_str::<PermissionFile>(&content)
.unwrap_or_else(|_| panic!("failed to deserialize {command}.toml"));

for p in permission_file
.permission
.iter_mut()
.filter(|p| p.identifier.starts_with("allow"))
{
p.commands
.allow
.extend(nested_commands.iter().map(|s| s.to_string()));
}

let out = toml::to_string_pretty(&permission_file)
.unwrap_or_else(|_| panic!("failed to serialize {command}.toml"));
let out = format!(
r#"# Automatically generated - DO NOT EDIT!

"$schema" = "../../schemas/schema.json"

{out}"#
);
std::fs::write(permission_path, out)
.unwrap_or_else(|_| panic!("failed to write {command}.toml"));
}
}
29 changes: 19 additions & 10 deletions plugins/fs/guest-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ function fromBytes(buffer: FixedSizeArray<number, 8>): number {
const size = bytes.byteLength
let x = 0
for (let i = 0; i < size; i++) {
// eslint-disable-next-line security/detect-object-injection
const byte = bytes[i]
x *= 0x100
x += byte
Expand Down Expand Up @@ -427,11 +428,11 @@ class FileHandle extends Resource {
}

/**
* Writes `p.byteLength` bytes from `p` to the underlying data stream. It
* resolves to the number of bytes written from `p` (`0` <= `n` <=
* `p.byteLength`) or reject with the error encountered that caused the
* Writes `data.byteLength` bytes from `data` to the underlying data stream. It
* resolves to the number of bytes written from `data` (`0` <= `n` <=
* `data.byteLength`) or reject with the error encountered that caused the
* write to stop early. `write()` must reject with a non-null error if
* would resolve to `n` < `p.byteLength`. `write()` must not modify the
* would resolve to `n` < `data.byteLength`. `write()` must not modify the
* slice data, even temporarily.
*
* @example
Expand Down Expand Up @@ -1044,19 +1045,27 @@ interface WriteFileOptions {
*/
async function writeFile(
path: string | URL,
data: Uint8Array,
data: Uint8Array | ReadableStream<Uint8Array>,
options?: WriteFileOptions
): Promise<void> {
if (path instanceof URL && path.protocol !== 'file:') {
throw new TypeError('Must be a file URL.')
}

await invoke('plugin:fs|write_file', data, {
headers: {
path: encodeURIComponent(path instanceof URL ? path.toString() : path),
options: JSON.stringify(options)
if (data instanceof ReadableStream) {
const file = await open(path, options)
for await (const chunk of data) {
await file.write(chunk)
}
})
await file.close()
} else {
await invoke('plugin:fs|write_file', data, {
headers: {
path: encodeURIComponent(path instanceof URL ? path.toString() : path),
options: JSON.stringify(options)
}
})
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@
[[permission]]
identifier = "allow-read-text-file-lines"
description = "Enables the read_text_file_lines command without any pre-configured scope."
commands.allow = ["read_text_file_lines"]

[permission.commands]
allow = [
"read_text_file_lines",
"read_text_file_lines_next",
]
deny = []

[[permission]]
identifier = "deny-read-text-file-lines"
description = "Denies the read_text_file_lines command without any pre-configured scope."
commands.deny = ["read_text_file_lines"]

[permission.commands]
allow = []
deny = ["read_text_file_lines"]
14 changes: 12 additions & 2 deletions plugins/fs/permissions/autogenerated/commands/write_file.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,19 @@
[[permission]]
identifier = "allow-write-file"
description = "Enables the write_file command without any pre-configured scope."
commands.allow = ["write_file"]

[permission.commands]
allow = [
"write_file",
"open",
"write",
]
deny = []

[[permission]]
identifier = "deny-write-file"
description = "Denies the write_file command without any pre-configured scope."
commands.deny = ["write_file"]

[permission.commands]
allow = []
deny = ["write_file"]
Loading