refactor: unify fs read and write cmds for binary/text data [TRI-009] (#34)

This commit is contained in:
Lucas Nogueira 2022-01-09 16:24:44 -03:00
parent bf5667f21c
commit 766c4f2c57
No known key found for this signature in database
GPG Key ID: 2714B66BCFB01F7F
9 changed files with 50 additions and 180 deletions

View File

@ -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");

View File

@ -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

View File

@ -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.

View File

@ -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>) {

View File

@ -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.

View File

@ -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
//!

View File

@ -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
}
})

View File

@ -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"
},