mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-10-02 22:06:50 +02:00
feat(fs): support ReadableStream<Unit8Array> for writeFile API (#1964)
This commit is contained in:
1 parent
ac2edc2159
commit
5092ea5e89
8 files changed
+120
-41
No files matched your search
@@ -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 }
|
||||
|
||||
@@ -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;"function"==typeof SuppressedError&&SuppressedError;const s="__TAURI_TO_IPC_KEY__";class c{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")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,s)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[s]()}}async function f(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class l{get rid(){return e(this,a,"f")}constructor(t){a.set(this,void 0),n(this,a,t)}async close(){return f("plugin:resources|close",{rid:this.rid})}}var u,p;function w(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,(u=t.BaseDirectory||(t.BaseDirectory={}))[u.Audio=1]="Audio",u[u.Cache=2]="Cache",u[u.Config=3]="Config",u[u.Data=4]="Data",u[u.LocalData=5]="LocalData",u[u.Document=6]="Document",u[u.Download=7]="Download",u[u.Picture=8]="Picture",u[u.Public=9]="Public",u[u.Video=10]="Video",u[u.Resource=11]="Resource",u[u.Temp=12]="Temp",u[u.AppConfig=13]="AppConfig",u[u.AppData=14]="AppData",u[u.AppLocalData=15]="AppLocalData",u[u.AppCache=16]="AppCache",u[u.AppLog=17]="AppLog",u[u.Desktop=18]="Desktop",u[u.Executable=19]="Executable",u[u.Font=20]="Font",u[u.Home=21]="Home",u[u.Runtime=22]="Runtime",u[u.Template=23]="Template",t.SeekMode=void 0,(p=t.SeekMode||(t.SeekMode={}))[p.Start=0]="Start",p[p.Current=1]="Current",p[p.End=2]="End";class h extends l{async read(t){if(0===t.byteLength)return 0;const e=await f("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;t<n;t++)i*=256,i+=e[t];return i}(e.slice(-8)),i=e instanceof ArrayBuffer?new Uint8Array(e):e;return t.set(i.slice(0,i.length-8)),0===n?null:n}async seek(t,e){return await f("plugin:fs|seek",{rid:this.rid,offset:t,whence:e})}async stat(){return w(await f("plugin:fs|fstat",{rid:this.rid}))}async truncate(t){await f("plugin:fs|ftruncate",{rid:this.rid,len:t})}async write(t){return await f("plugin:fs|write",{rid:this.rid,data:t})}}async function d(t){await f("plugin:fs|unwatch",{rid:t})}return t.FileHandle=h,t.copyFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await f("plugin:fs|copy_file",{fromPath:t instanceof URL?t.toString():t,toPath:e instanceof URL?e.toString():e,options:n})},t.create=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await f("plugin:fs|create",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.exists=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await f("plugin:fs|exists",{path:t instanceof URL?t.toString():t,options:e})},t.lstat=async function(t,e){return w(await f("plugin:fs|lstat",{path:t instanceof URL?t.toString():t,options:e}))},t.mkdir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await f("plugin:fs|mkdir",{path:t instanceof URL?t.toString():t,options:e})},t.open=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await f("plugin:fs|open",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.readDir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await f("plugin:fs|read_dir",{path:t instanceof URL?t.toString():t,options:e})},t.readFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");coLine truncated
|
||||
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;"function"==typeof SuppressedError&&SuppressedError;const s="__TAURI_TO_IPC_KEY__";class c{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")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,s)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[s]()}}async function f(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class l{get rid(){return e(this,a,"f")}constructor(t){a.set(this,void 0),n(this,a,t)}async close(){return f("plugin:resources|close",{rid:this.rid})}}var u,p;function w(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,(u=t.BaseDirectory||(t.BaseDirectory={}))[u.Audio=1]="Audio",u[u.Cache=2]="Cache",u[u.Config=3]="Config",u[u.Data=4]="Data",u[u.LocalData=5]="LocalData",u[u.Document=6]="Document",u[u.Download=7]="Download",u[u.Picture=8]="Picture",u[u.Public=9]="Public",u[u.Video=10]="Video",u[u.Resource=11]="Resource",u[u.Temp=12]="Temp",u[u.AppConfig=13]="AppConfig",u[u.AppData=14]="AppData",u[u.AppLocalData=15]="AppLocalData",u[u.AppCache=16]="AppCache",u[u.AppLog=17]="AppLog",u[u.Desktop=18]="Desktop",u[u.Executable=19]="Executable",u[u.Font=20]="Font",u[u.Home=21]="Home",u[u.Runtime=22]="Runtime",u[u.Template=23]="Template",t.SeekMode=void 0,(p=t.SeekMode||(t.SeekMode={}))[p.Start=0]="Start",p[p.Current=1]="Current",p[p.End=2]="End";class h extends l{async read(t){if(0===t.byteLength)return 0;const e=await f("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;t<n;t++)i*=256,i+=e[t];return i}(e.slice(-8)),i=e instanceof ArrayBuffer?new Uint8Array(e):e;return t.set(i.slice(0,i.length-8)),0===n?null:n}async seek(t,e){return await f("plugin:fs|seek",{rid:this.rid,offset:t,whence:e})}async stat(){return w(await f("plugin:fs|fstat",{rid:this.rid}))}async truncate(t){await f("plugin:fs|ftruncate",{rid:this.rid,len:t})}async write(t){return await f("plugin:fs|write",{rid:this.rid,data:t})}}async function d(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await f("plugin:fs|open",{path:t instanceof URL?t.toString():t,options:e});return new h(n)}async function y(t){await f("plugin:fs|unwatch",{rid:t})}return t.FileHandle=h,t.copyFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await f("plugin:fs|copy_file",{fromPath:t instanceof URL?t.toString():t,toPath:e instanceof URL?e.toString():e,options:n})},t.create=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await f("plugin:fs|create",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.exists=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await f("plugin:fs|exists",{path:t instanceof URL?t.toString():t,options:e})},t.lstat=async function(t,e){return w(await f("plugin:fs|lstat",{path:t instanceof URL?t.toString():t,options:e}))},t.mkdir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await f("plugin:fs|mkdir",{path:t instanceof URL?t.toString():t,options:e})},t.open=d,t.readDir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await f("plugin:fs|read_dir",{path:t instanceof URL?t.toString():t,options:e})},t.readFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.")Line truncated
|
||||
+66
-26
@@ -7,6 +7,8 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use tauri_utils::acl::manifest::PermissionFile;
|
||||
|
||||
#[path = "src/scope.rs"]
|
||||
#[allow(dead_code)]
|
||||
mod scope;
|
||||
@@ -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() {
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
Reference in new issue
Block a user