Browse Source

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

Lucas Nogueira 3 years ago
parent
commit
766c4f2c57

+ 5 - 15
core/tauri-utils/src/config.rs

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

+ 2 - 6
core/tauri/Cargo.toml

@@ -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 it is too large
+ 0 - 0
core/tauri/scripts/bundle.js


+ 1 - 1
core/tauri/src/api/error.rs

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

+ 10 - 76
core/tauri/src/endpoints/file_system.rs

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

+ 1 - 1
core/tauri/src/error.rs

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

+ 2 - 4
core/tauri/src/lib.rs

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

+ 18 - 50
tooling/api/src/fs.ts

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

+ 10 - 26
tooling/cli.rs/schema.json

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

Some files were not shown because too many files changed in this diff