mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-29 14:04:19 +03:00
refactor: unify fs read and write cmds for binary/text data [TRI-009] (#34)
This commit is contained in:
parent
bf5667f21c
commit
766c4f2c57
@ -602,18 +602,12 @@ pub struct FsAllowlistConfig {
|
||||
/// Use this flag to enable all file system API features.
|
||||
#[serde(default)]
|
||||
pub all: bool,
|
||||
/// Read text file from local filesystem.
|
||||
/// Read file from local filesystem.
|
||||
#[serde(default)]
|
||||
pub read_text_file: bool,
|
||||
/// Read binary file from local filesystem.
|
||||
#[serde(default)]
|
||||
pub read_binary_file: bool,
|
||||
/// Write text file to local filesystem.
|
||||
pub read_file: bool,
|
||||
/// Write file to local filesystem.
|
||||
#[serde(default)]
|
||||
pub write_file: bool,
|
||||
/// Write binary file to local filesystem.
|
||||
#[serde(default)]
|
||||
pub write_binary_file: bool,
|
||||
/// Read directory from local filesystem.
|
||||
#[serde(default)]
|
||||
pub read_dir: bool,
|
||||
@ -639,10 +633,8 @@ impl Allowlist for FsAllowlistConfig {
|
||||
let allowlist = Self {
|
||||
scope: Default::default(),
|
||||
all: false,
|
||||
read_text_file: true,
|
||||
read_binary_file: true,
|
||||
read_file: true,
|
||||
write_file: true,
|
||||
write_binary_file: true,
|
||||
read_dir: true,
|
||||
copy_file: true,
|
||||
create_dir: true,
|
||||
@ -660,10 +652,8 @@ impl Allowlist for FsAllowlistConfig {
|
||||
vec!["fs-all"]
|
||||
} else {
|
||||
let mut features = Vec::new();
|
||||
check_feature!(self, features, read_text_file, "fs-read-text-file");
|
||||
check_feature!(self, features, read_binary_file, "fs-read-binary-file");
|
||||
check_feature!(self, features, read_file, "fs-read-file");
|
||||
check_feature!(self, features, write_file, "fs-write-file");
|
||||
check_feature!(self, features, write_binary_file, "fs-write-binary-file");
|
||||
check_feature!(self, features, read_dir, "fs-read-dir");
|
||||
check_feature!(self, features, copy_file, "fs-copy-file");
|
||||
check_feature!(self, features, create_dir, "fs-create-dir");
|
||||
|
@ -140,24 +140,20 @@ dialog-save = ["dialog"]
|
||||
fs-all = [
|
||||
"fs-copy-file",
|
||||
"fs-create-dir",
|
||||
"fs-read-binary-file",
|
||||
"fs-read-file",
|
||||
"fs-read-dir",
|
||||
"fs-read-text-file",
|
||||
"fs-remove-dir",
|
||||
"fs-remove-file",
|
||||
"fs-rename-file",
|
||||
"fs-write-binary-file",
|
||||
"fs-write-file"
|
||||
]
|
||||
fs-copy-file = []
|
||||
fs-create-dir = []
|
||||
fs-read-binary-file = []
|
||||
fs-read-file = []
|
||||
fs-read-dir = []
|
||||
fs-read-text-file = []
|
||||
fs-remove-dir = []
|
||||
fs-remove-file = []
|
||||
fs-rename-file = []
|
||||
fs-write-binary-file = ["base64"]
|
||||
fs-write-file = []
|
||||
global-shortcut-all = []
|
||||
http-all = ["http-request"]
|
||||
|
File diff suppressed because one or more lines are too long
@ -25,7 +25,7 @@ pub enum Error {
|
||||
#[error("user cancelled the dialog")]
|
||||
DialogCancelled,
|
||||
/// The network error.
|
||||
#[cfg(all(feature = "http", not(feature = "reqwest-client")))]
|
||||
#[cfg(all(feature = "http-api", not(feature = "reqwest-client")))]
|
||||
#[error("Network Error: {0}")]
|
||||
Network(#[from] attohttpc::Error),
|
||||
/// The network error.
|
||||
|
@ -44,25 +44,14 @@ pub struct FileOperationOptions {
|
||||
#[serde(tag = "cmd", rename_all = "camelCase")]
|
||||
pub enum Cmd {
|
||||
/// The read text file API.
|
||||
ReadTextFile {
|
||||
path: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
},
|
||||
/// The read binary file API.
|
||||
ReadBinaryFile {
|
||||
ReadFile {
|
||||
path: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
},
|
||||
/// The write file API.
|
||||
WriteFile {
|
||||
path: PathBuf,
|
||||
contents: String,
|
||||
options: Option<FileOperationOptions>,
|
||||
},
|
||||
/// The write binary file API.
|
||||
WriteBinaryFile {
|
||||
path: PathBuf,
|
||||
contents: String,
|
||||
contents: Vec<u8>,
|
||||
options: Option<FileOperationOptions>,
|
||||
},
|
||||
/// The read dir API.
|
||||
@ -101,24 +90,8 @@ pub enum Cmd {
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
#[module_command_handler(fs_read_text_file, "fs > readTextFile")]
|
||||
fn read_text_file<R: Runtime>(
|
||||
context: InvokeContext<R>,
|
||||
path: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
) -> crate::Result<String> {
|
||||
file::read_string(resolve_path(
|
||||
&context.config,
|
||||
&context.package_info,
|
||||
&context.window,
|
||||
path,
|
||||
options.and_then(|o| o.dir),
|
||||
)?)
|
||||
.map_err(crate::Error::FailedToExecuteApi)
|
||||
}
|
||||
|
||||
#[module_command_handler(fs_read_binary_file, "fs > readBinaryFile")]
|
||||
fn read_binary_file<R: Runtime>(
|
||||
#[module_command_handler(fs_read_file, "fs > readFile")]
|
||||
fn read_file<R: Runtime>(
|
||||
context: InvokeContext<R>,
|
||||
path: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
@ -137,7 +110,7 @@ impl Cmd {
|
||||
fn write_file<R: Runtime>(
|
||||
context: InvokeContext<R>,
|
||||
path: PathBuf,
|
||||
contents: String,
|
||||
contents: Vec<u8>,
|
||||
options: Option<FileOperationOptions>,
|
||||
) -> crate::Result<()> {
|
||||
File::create(resolve_path(
|
||||
@ -147,32 +120,8 @@ impl Cmd {
|
||||
path,
|
||||
options.and_then(|o| o.dir),
|
||||
)?)
|
||||
.map_err(crate::Error::Io)
|
||||
.and_then(|mut f| f.write_all(contents.as_bytes()).map_err(|err| err.into()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[module_command_handler(fs_write_binary_file, "fs > writeBinaryFile")]
|
||||
fn write_binary_file<R: Runtime>(
|
||||
context: InvokeContext<R>,
|
||||
path: PathBuf,
|
||||
contents: String,
|
||||
options: Option<FileOperationOptions>,
|
||||
) -> crate::Result<()> {
|
||||
base64::decode(contents)
|
||||
.map_err(crate::Error::Base64Decode)
|
||||
.and_then(|c| {
|
||||
File::create(resolve_path(
|
||||
&context.config,
|
||||
&context.package_info,
|
||||
&context.window,
|
||||
path,
|
||||
options.and_then(|o| o.dir),
|
||||
)?)
|
||||
.map_err(Into::into)
|
||||
.and_then(|mut f| f.write_all(&c).map_err(|err| err.into()))
|
||||
})?;
|
||||
Ok(())
|
||||
.map_err(Into::into)
|
||||
.and_then(|mut f| f.write_all(&contents).map_err(|err| err.into()))
|
||||
}
|
||||
|
||||
#[module_command_handler(fs_read_dir, "fs > readDir")]
|
||||
@ -385,35 +334,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri_macros::module_command_test(fs_read_text_file, "fs > readTextFile")]
|
||||
#[tauri_macros::module_command_test(fs_read_file, "fs > readFile")]
|
||||
#[quickcheck_macros::quickcheck]
|
||||
fn read_text_file(path: PathBuf, options: Option<FileOperationOptions>) {
|
||||
fn read_file(path: PathBuf, options: Option<FileOperationOptions>) {
|
||||
let res = super::Cmd::read_text_file(crate::test::mock_invoke_context(), path, options);
|
||||
assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_))));
|
||||
}
|
||||
|
||||
#[tauri_macros::module_command_test(fs_read_binary_file, "fs > readBinaryFile")]
|
||||
#[quickcheck_macros::quickcheck]
|
||||
fn read_binary_file(path: PathBuf, options: Option<FileOperationOptions>) {
|
||||
let res = super::Cmd::read_binary_file(crate::test::mock_invoke_context(), path, options);
|
||||
assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_))));
|
||||
}
|
||||
|
||||
#[tauri_macros::module_command_test(fs_write_file, "fs > writeFile")]
|
||||
#[quickcheck_macros::quickcheck]
|
||||
fn write_file(path: PathBuf, contents: String, options: Option<FileOperationOptions>) {
|
||||
fn write_file(path: PathBuf, contents: Vec<u8>, options: Option<FileOperationOptions>) {
|
||||
let res = super::Cmd::write_file(crate::test::mock_invoke_context(), path, contents, options);
|
||||
assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_))));
|
||||
}
|
||||
|
||||
#[tauri_macros::module_command_test(fs_read_binary_file, "fs > writeBinaryFile")]
|
||||
#[quickcheck_macros::quickcheck]
|
||||
fn write_binary_file(path: PathBuf, contents: String, options: Option<FileOperationOptions>) {
|
||||
let res =
|
||||
super::Cmd::write_binary_file(crate::test::mock_invoke_context(), path, contents, options);
|
||||
assert!(!matches!(res, Err(crate::Error::ApiNotAllowlisted(_))));
|
||||
}
|
||||
|
||||
#[tauri_macros::module_command_test(fs_read_dir, "fs > readDir")]
|
||||
#[quickcheck_macros::quickcheck]
|
||||
fn read_dir(path: PathBuf, options: Option<DirOperationOptions>) {
|
||||
|
@ -42,7 +42,7 @@ pub enum Error {
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// Failed to decode base64.
|
||||
#[cfg(any(fs_write_binary_file, feature = "updater"))]
|
||||
#[cfg(feature = "updater")]
|
||||
#[error("Failed to decode base64 string: {0}")]
|
||||
Base64Decode(#[from] base64::DecodeError),
|
||||
/// Failed to load window icon.
|
||||
|
@ -50,14 +50,12 @@
|
||||
//! - **fs-all**: Enables all [Filesystem APIs](https://tauri.studio/en/docs/api/js/modules/fs).
|
||||
//! - **fs-copy-file**: Enables the [`copyFile` API](https://tauri.studio/en/docs/api/js/modules/fs#copyfile).
|
||||
//! - **fs-create-dir**: Enables the [`createDir` API](https://tauri.studio/en/docs/api/js/modules/fs#createdir).
|
||||
//! - **fs-read-binary-file**: Enables the [`readBinaryFile` API](https://tauri.studio/en/docs/api/js/modules/fs#readbinaryfile).
|
||||
//! - **fs-read-dir**: Enables the [`readDir` API](https://tauri.studio/en/docs/api/js/modules/fs#readdir).
|
||||
//! - **fs-read-text-file**: Enables the [`readTextFile` API](https://tauri.studio/en/docs/api/js/modules/fs#readtextfile).
|
||||
//! - **fs-read-file**: Enables the [`readTextFile` API](https://tauri.studio/en/docs/api/js/modules/fs#readtextfile) and the [`readBinaryFile` API](https://tauri.studio/en/docs/api/js/modules/fs#readbinaryfile).
|
||||
//! - **fs-remove-dir**: Enables the [`removeDir` API](https://tauri.studio/en/docs/api/js/modules/fs#removedir).
|
||||
//! - **fs-remove-file**: Enables the [`removeFile` API](https://tauri.studio/en/docs/api/js/modules/fs#removefile).
|
||||
//! - **fs-rename-file**: Enables the [`renameFile` API](https://tauri.studio/en/docs/api/js/modules/fs#renamefile).
|
||||
//! - **fs-write-binary-file**: Enables the [`writeBinaryFile` API](https://tauri.studio/en/docs/api/js/modules/fs#writebinaryfile).
|
||||
//! - **fs-write-file**: Enables the [`writeFile` API](https://tauri.studio/en/docs/api/js/modules/fs#writefile).
|
||||
//! - **fs-write-file**: Enables the [`writeFile` API](https://tauri.studio/en/docs/api/js/modules/fs#writefile) and the [`writeBinaryFile` API](https://tauri.studio/en/docs/api/js/modules/fs#writebinaryfile).
|
||||
//!
|
||||
//! ### Global shortcut allowlist
|
||||
//!
|
||||
|
@ -14,10 +14,8 @@
|
||||
* "allowlist": {
|
||||
* "fs": {
|
||||
* "all": true, // enable all FS APIs
|
||||
* "readTextFile": true,
|
||||
* "readBinaryFile": true,
|
||||
* "readFile": true,
|
||||
* "writeFile": true,
|
||||
* "writeBinaryFile": true,
|
||||
* "readDir": true,
|
||||
* "copyFile": true,
|
||||
* "createDir": true,
|
||||
@ -67,14 +65,20 @@ interface FsDirOptions {
|
||||
recursive?: boolean
|
||||
}
|
||||
|
||||
/** Options object used to write a UTF-8 string to a file. */
|
||||
interface FsTextFileOption {
|
||||
/** Path to the file to write. */
|
||||
path: string
|
||||
/** The UTF-8 string to write to the file. */
|
||||
contents: string
|
||||
}
|
||||
|
||||
/** Options object used to write a binary data to a file. */
|
||||
interface FsBinaryFileOption {
|
||||
/** Path to the file to write. */
|
||||
path: string
|
||||
contents: ArrayBuffer
|
||||
/** The byte array contents. */
|
||||
contents: Iterable<number> | ArrayLike<number>
|
||||
}
|
||||
|
||||
interface FileEntry {
|
||||
@ -89,7 +93,7 @@ interface FileEntry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file as UTF-8 encoded string.
|
||||
* Reads a file as an UTF-8 encoded string.
|
||||
*
|
||||
* @param filePath Path to the file.
|
||||
* @param options Configuration object.
|
||||
@ -99,14 +103,14 @@ async function readTextFile(
|
||||
filePath: string,
|
||||
options: FsOptions = {}
|
||||
): Promise<string> {
|
||||
return invokeTauriCommand<string>({
|
||||
return invokeTauriCommand<number[]>({
|
||||
__tauriModule: 'Fs',
|
||||
message: {
|
||||
cmd: 'readTextFile',
|
||||
cmd: 'readFile',
|
||||
path: filePath,
|
||||
options
|
||||
}
|
||||
})
|
||||
}).then((data) => new TextDecoder().decode(new Uint8Array(data)))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,7 +127,7 @@ async function readBinaryFile(
|
||||
return invokeTauriCommand<number[]>({
|
||||
__tauriModule: 'Fs',
|
||||
message: {
|
||||
cmd: 'readBinaryFile',
|
||||
cmd: 'readFile',
|
||||
path: filePath,
|
||||
options
|
||||
}
|
||||
@ -131,7 +135,7 @@ async function readBinaryFile(
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a text file.
|
||||
* Writes a UTF-8 text file.
|
||||
*
|
||||
* @param file File configuration object.
|
||||
* @param options Configuration object.
|
||||
@ -153,50 +157,14 @@ async function writeFile(
|
||||
message: {
|
||||
cmd: 'writeFile',
|
||||
path: file.path,
|
||||
contents: file.contents,
|
||||
contents: Array.from(new TextEncoder().encode(file.contents)),
|
||||
options
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
const CHUNK_SIZE = 65536
|
||||
|
||||
/**
|
||||
* Convert an Uint8Array to ascii string.
|
||||
*
|
||||
* @ignore
|
||||
* @param arr
|
||||
* @returns An ASCII string.
|
||||
*/
|
||||
function uint8ArrayToString(arr: Uint8Array): string {
|
||||
if (arr.length < CHUNK_SIZE) {
|
||||
return String.fromCharCode.apply(null, Array.from(arr))
|
||||
}
|
||||
|
||||
let result = ''
|
||||
const arrLen = arr.length
|
||||
for (let i = 0; i < arrLen; i++) {
|
||||
const chunk = arr.subarray(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE)
|
||||
result += String.fromCharCode.apply(null, Array.from(chunk))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ArrayBuffer to base64 encoded string.
|
||||
*
|
||||
* @ignore
|
||||
* @param buffer
|
||||
* @returns A base64 encoded string.
|
||||
*/
|
||||
function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
||||
const str = uint8ArrayToString(new Uint8Array(buffer))
|
||||
return btoa(str)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a binary file.
|
||||
* Writes a byte array content to a file.
|
||||
*
|
||||
* @param file Write configuration object.
|
||||
* @param options Configuration object.
|
||||
@ -216,9 +184,9 @@ async function writeBinaryFile(
|
||||
return invokeTauriCommand({
|
||||
__tauriModule: 'Fs',
|
||||
message: {
|
||||
cmd: 'writeBinaryFile',
|
||||
cmd: 'writeFile',
|
||||
path: file.path,
|
||||
contents: arrayBufferToBase64(file.contents),
|
||||
contents: Array.from(file.contents),
|
||||
options
|
||||
}
|
||||
})
|
||||
|
@ -60,16 +60,14 @@
|
||||
"all": false,
|
||||
"copyFile": false,
|
||||
"createDir": false,
|
||||
"readBinaryFile": false,
|
||||
"readDir": false,
|
||||
"readTextFile": false,
|
||||
"readFile": false,
|
||||
"removeDir": false,
|
||||
"removeFile": false,
|
||||
"renameFile": false,
|
||||
"scope": [
|
||||
"$APP/**"
|
||||
],
|
||||
"writeBinaryFile": false,
|
||||
"writeFile": false
|
||||
},
|
||||
"globalShortcut": {
|
||||
@ -234,16 +232,14 @@
|
||||
"all": false,
|
||||
"copyFile": false,
|
||||
"createDir": false,
|
||||
"readBinaryFile": false,
|
||||
"readDir": false,
|
||||
"readTextFile": false,
|
||||
"readFile": false,
|
||||
"removeDir": false,
|
||||
"removeFile": false,
|
||||
"renameFile": false,
|
||||
"scope": [
|
||||
"$APP/**"
|
||||
],
|
||||
"writeBinaryFile": false,
|
||||
"writeFile": false
|
||||
},
|
||||
"allOf": [
|
||||
@ -972,18 +968,13 @@
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readBinaryFile": {
|
||||
"description": "Read binary file from local filesystem.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readDir": {
|
||||
"description": "Read directory from local filesystem.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readTextFile": {
|
||||
"description": "Read text file from local filesystem.",
|
||||
"readFile": {
|
||||
"description": "Read file from local filesystem.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -1013,13 +1004,8 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"writeBinaryFile": {
|
||||
"description": "Write binary file to local filesystem.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"writeFile": {
|
||||
"description": "Write text file to local filesystem.",
|
||||
"description": "Write file to local filesystem.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
@ -1256,14 +1242,14 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"csp": {
|
||||
"description": "The Content Security Policy that will be injected on all HTML files on the built application. If [`dev_csp`](SecurityConfig.dev_csp) is not specified, this value is also injected on dev.\n\nThis is a really important part of the configuration since it helps you ensure your WebView is secured. See https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP.",
|
||||
"description": "The Content Security Policy that will be injected on all HTML files on the built application. If [`dev_csp`](SecurityConfig.dev_csp) is not specified, this value is also injected on dev.\n\nThis is a really important part of the configuration since it helps you ensure your WebView is secured. See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"devCsp": {
|
||||
"description": "The Content Security Policy that will be injected on all HTML files on development.\n\nThis is a really important part of the configuration since it helps you ensure your WebView is secured. See https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP.",
|
||||
"description": "The Content Security Policy that will be injected on all HTML files on development.\n\nThis is a really important part of the configuration since it helps you ensure your WebView is secured. See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@ -1292,7 +1278,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"sidecar": {
|
||||
"description": "Enable sidecar execution, allowing the JavaScript layer to spawn a sidecar program, an executable that is shipped with the application. For more information see https://tauri.studio/en/docs/usage/guides/bundler/sidecar.",
|
||||
"description": "Enable sidecar execution, allowing the JavaScript layer to spawn a sidecar program, an executable that is shipped with the application. For more information see <https://tauri.studio/en/docs/usage/guides/bundler/sidecar>.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
@ -1343,16 +1329,14 @@
|
||||
"all": false,
|
||||
"copyFile": false,
|
||||
"createDir": false,
|
||||
"readBinaryFile": false,
|
||||
"readDir": false,
|
||||
"readTextFile": false,
|
||||
"readFile": false,
|
||||
"removeDir": false,
|
||||
"removeFile": false,
|
||||
"renameFile": false,
|
||||
"scope": [
|
||||
"$APP/**"
|
||||
],
|
||||
"writeBinaryFile": false,
|
||||
"writeFile": false
|
||||
},
|
||||
"globalShortcut": {
|
||||
@ -1960,7 +1944,7 @@
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"description": "The installer language. See https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables.",
|
||||
"description": "The installer language. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.",
|
||||
"default": "en-US",
|
||||
"type": "string"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user