From 8ac066700adff1fd60a13d3b830c676f1bfe4d6a Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Mon, 16 Mar 2020 17:04:15 -0300 Subject: [PATCH] feat(tauri) extend file system API (#512) * feat(tauri) extend file system API * chore(deps) pin web-view deps --- cli/tauri.js/api/fs.js | 64 ++++++++- cli/tauri.js/templates/tauri.js | 124 +++++++++++++++++ tauri-api/src/dir.rs | 2 +- tauri/Cargo.toml | 8 +- .../communication/dist/index.tauri.html | 99 +++++++++++++ tauri/src/endpoints.rs | 44 ++++++ tauri/src/endpoints/cmd.rs | 38 ++++- tauri/src/endpoints/file_system.rs | 131 +++++++++++++++++- tauri/src/lib.rs | 4 + 9 files changed, 502 insertions(+), 12 deletions(-) diff --git a/cli/tauri.js/api/fs.js b/cli/tauri.js/api/fs.js index 903178555..7ad6e604d 100644 --- a/cli/tauri.js/api/fs.js +++ b/cli/tauri.js/api/fs.js @@ -51,9 +51,71 @@ function readDir (dir, options = {}) { return tauri.readDir(dir, options) } +/** + * Creates a directory + * If one of the path's parent components doesn't exist and the `recursive` option isn't set to true, it will be rejected + * + * @param {string} dir path to the directory to create + * @param {object} [options] configuration object + * @param {boolean} [options.recursive] whether to create the directory's parent components or not + * @return {Promise} + */ +function createDir (dir, options = {}) { + return tauri.createDir(dir, options) +} + +/** + * Removes a directory + * If the directory is not empty and the `recursive` option isn't set to true, it will be rejected + * + * @param {string} dir path to the directory to remove + * @param {object} [options] configuration object + * @param {boolean} [options.recursive] whether to remove all of the directory's content or not + * @return {Promise} + */ +function removeDir (dir, options = {}) { + return tauri.removeDir(dir, options) +} + +/** + * Copy file + * + * @param {string} source + * @param {string} destination + * @return {Promise} + */ +function copyFile (source, destination) { + return tauri.copyFile(source, destination) +} + +/** + * Removes a file + * + * @param {string} file path to the file to remove + * @return {Promise} + */ +function removeFile (file) { + return tauri.removeFile(file) +} + +/** + * Renames a file + * + * @param {string} oldPath + * @param {string} newPath + * @return {Promise} + */ +function renameFile (oldPath, newPath) { + return tauri.renameFile(oldPath, newPath) +} + export { readTextFile, readBinaryFile, writeFile, - readDir + readDir, + createDir, + removeDir, + copyFile, + removeFile } diff --git a/cli/tauri.js/templates/tauri.js b/cli/tauri.js/templates/tauri.js index 26ad81018..1d91cdaf1 100644 --- a/cli/tauri.js/templates/tauri.js +++ b/cli/tauri.js/templates/tauri.js @@ -306,6 +306,130 @@ window.tauri = { <% } %> }, + <% if (ctx.dev) { %> + /** + * @name createDir + * @description Creates a directory + * Permissions based on the app's PID owner + * @param {String} path + * @param {Object} [options] + * @param {Boolean} [options.recursive] + * @returns {*|Promise|Promise} + */ + <% } %> + createDir: function createDir(path, options) { + <% if (tauri.whitelist.createDir === true || tauri.whitelist.all === true) { %> + return this.promisified({ + cmd: 'createDir', + path: path, + options: options + }); + <% } else { %> + <% if (ctx.dev) { %> + return __whitelistWarning('createDir') + <% } %> + return __reject() + <% } %> + }, + + <% if (ctx.dev) { %> + /** + * @name removeDir + * @description Removes a directory + * Permissions based on the app's PID owner + * @param {String} path + * @param {Object} [options] + * @param {Boolean} [options.recursive] + * @returns {*|Promise|Promise} + */ + <% } %> + removeDir: function removeDir(path, options) { + <% if (tauri.whitelist.removeDir === true || tauri.whitelist.all === true) { %> + return this.promisified({ + cmd: 'removeDir', + path: path, + options: options + }); + <% } else { %> + <% if (ctx.dev) { %> + return __whitelistWarning('removeDir') + <% } %> + return __reject() + <% } %> + }, + + <% if (ctx.dev) { %> + /** + * @name copyFile + * @description Copy file + * Permissions based on the app's PID owner + * @param {String} source + * @param {String} destination + * @returns {*|Promise|Promise} + */ + <% } %> + copyFile: function copyFile(source, destination) { + <% if (tauri.whitelist.copyFile === true || tauri.whitelist.all === true) { %> + return this.promisified({ + cmd: 'copyFile', + source: source, + destination: destination + }); + <% } else { %> + <% if (ctx.dev) { %> + return __whitelistWarning('copyFile') + <% } %> + return __reject() + <% } %> + }, + + <% if (ctx.dev) { %> + /** + * @name removeFile + * @description Removes a file + * Permissions based on the app's PID owner + * @param {String} path + * @returns {*|Promise|Promise} + */ + <% } %> + removeFile: function removeFile(path) { + <% if (tauri.whitelist.removeFile === true || tauri.whitelist.all === true) { %> + return this.promisified({ + cmd: 'removeFile', + path: path + }); + <% } else { %> + <% if (ctx.dev) { %> + return __whitelistWarning('removeFile') + <% } %> + return __reject() + <% } %> + }, + + <% if (ctx.dev) { %> + /** + * @name renameFile + * @description Renames a file + * Permissions based on the app's PID owner + * @param {String} path + * @returns {*|Promise|Promise} + */ + <% } %> + renameFile: function renameFile(oldPath, newPath) { + <% if (tauri.whitelist.renameFile === true || tauri.whitelist.all === true) { %> + return this.promisified({ + cmd: 'renameFile', + old_path: oldPath, + new_path: newPath + }); + <% } else { %> + <% if (ctx.dev) { %> + return __whitelistWarning('renameFile') + <% } %> + return __reject() + <% } %> + }, + <% if (ctx.dev) { %> /** * @name setTitle diff --git a/tauri-api/src/dir.rs b/tauri-api/src/dir.rs index 4ef1241e6..275d47e67 100644 --- a/tauri-api/src/dir.rs +++ b/tauri-api/src/dir.rs @@ -44,7 +44,7 @@ pub fn walk_dir(path_copy: String) -> crate::Result> { pub fn list_dir_contents(dir_path: String) -> crate::Result> { fs::read_dir(dir_path) - .map_err(|err| crate::Error::with_chain(err, "read string failed")) + .map_err(|err| crate::Error::with_chain(err, "read dir failed")) .and_then(|paths| { let mut dirs: Vec = vec![]; for path in paths { diff --git a/tauri/Cargo.toml b/tauri/Cargo.toml index f176fffca..58fe98d9c 100644 --- a/tauri/Cargo.toml +++ b/tauri/Cargo.toml @@ -12,7 +12,8 @@ exclude = ["test/fixture/**"] [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -web-view = "0.6.0" +webview-sys = "=0.4.0" +web-view = "=0.6.0" tauri_includedir = "0.5.0" phf = "0.8.0" base64 = "0.12.0" @@ -44,6 +45,11 @@ read-text-file = [] read-binary-file = [] write-file = [] read-dir = [] +copy-file = [] +create-dir = [] +remove-dir = [] +remove-file = [] +rename-file = [] set-title = [] execute = [] open = [] diff --git a/tauri/examples/communication/dist/index.tauri.html b/tauri/examples/communication/dist/index.tauri.html index b5f31a82d..5f7bdfa90 100644 --- a/tauri/examples/communication/dist/index.tauri.html +++ b/tauri/examples/communication/dist/index.tauri.html @@ -277,6 +277,105 @@ window.tauri = { }, + /** + * @name createDir + * @description Creates a directory + * Permissions based on the app's PID owner + * @param {String} path + * @param {Object} [options] + * @param {Boolean} [options.recursive] + * @returns {*|Promise|Promise} + */ + + createDir: function createDir(path, options) { + + return this.promisified({ + cmd: 'createDir', + path: path, + options: options + }); + + }, + + + /** + * @name removeDir + * @description Removes a directory + * Permissions based on the app's PID owner + * @param {String} path + * @param {Object} [options] + * @param {Boolean} [options.recursive] + * @returns {*|Promise|Promise} + */ + + removeDir: function removeDir(path, options) { + + return this.promisified({ + cmd: 'removeDir', + path: path, + options: options + }); + + }, + + + /** + * @name copyFile + * @description Copy file + * Permissions based on the app's PID owner + * @param {String} source + * @param {String} destination + * @returns {*|Promise|Promise} + */ + + copyFile: function copyFile(source, destination) { + + return this.promisified({ + cmd: 'copyFile', + source: source, + destination: destination + }); + + }, + + + /** + * @name removeFile + * @description Removes a file + * Permissions based on the app's PID owner + * @param {String} path + * @returns {*|Promise|Promise} + */ + + removeFile: function removeFile(path) { + + return this.promisified({ + cmd: 'removeFile', + path: path + }); + + }, + + + /** + * @name renameFile + * @description Renames a file + * Permissions based on the app's PID owner + * @param {String} path + * @returns {*|Promise|Promise} + */ + + renameFile: function renameFile(oldPath, newPath) { + + return this.promisified({ + cmd: 'renameFile', + old_path: oldPath, + new_path: newPath + }); + + }, + + /** * @name setTitle * @description Set the application's title diff --git a/tauri/src/endpoints.rs b/tauri/src/endpoints.rs index 595a81327..07e63196f 100644 --- a/tauri/src/endpoints.rs +++ b/tauri/src/endpoints.rs @@ -58,6 +58,50 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra } => { file_system::read_dir(webview, path, options, callback, error); } + #[cfg(any(feature = "all-api", feature = "copy-file"))] + CopyFile { + source, + destination, + callback, + error, + } => { + file_system::copy_file(webview, source, destination, callback, error); + } + #[cfg(any(feature = "all-api", feature = "create-dir"))] + CreateDir { + path, + options, + callback, + error, + } => { + file_system::create_dir(webview, path, options, callback, error); + } + #[cfg(any(feature = "all-api", feature = "remove-dir"))] + RemoveDir { + path, + options, + callback, + error, + } => { + file_system::remove_dir(webview, path, options, callback, error); + } + #[cfg(any(feature = "all-api", feature = "remove-file"))] + RemoveFile { + path, + callback, + error, + } => { + file_system::remove_file(webview, path, callback, error); + } + #[cfg(any(feature = "all-api", feature = "rename-file"))] + RenameFile { + old_path, + new_path, + callback, + error, + } => { + file_system::rename_file(webview, old_path, new_path, callback, error); + } #[cfg(any(feature = "all-api", feature = "set-title"))] SetTitle { title } => { webview.set_title(&title)?; diff --git a/tauri/src/endpoints/cmd.rs b/tauri/src/endpoints/cmd.rs index 3d564c061..a682afa0f 100644 --- a/tauri/src/endpoints/cmd.rs +++ b/tauri/src/endpoints/cmd.rs @@ -1,7 +1,7 @@ use serde::Deserialize; #[derive(Deserialize)] -pub struct ReadDirOptions { +pub struct DirOperationOptions { #[serde(default)] pub recursive: bool } @@ -48,7 +48,41 @@ pub enum Cmd { #[cfg(any(feature = "all-api", feature = "read-dir"))] ReadDir { path: String, - options: Option, + options: Option, + callback: String, + error: String, + }, + #[cfg(any(feature = "all-api", feature = "copy-file"))] + CopyFile { + source: String, + destination: String, + callback: String, + error: String, + }, + #[cfg(any(feature = "all-api", feature = "create-dir"))] + CreateDir { + path: String, + options: Option, + callback: String, + error: String, + }, + #[cfg(any(feature = "all-api", feature = "remove-dir"))] + RemoveDir { + path: String, + options: Option, + callback: String, + error: String, + }, + #[cfg(any(feature = "all-api", feature = "remove-file"))] + RemoveFile { + path: String, + callback: String, + error: String, + }, + #[cfg(any(feature = "all-api", feature = "rename-file"))] + RenameFile { + old_path: String, + new_path: String, callback: String, error: String, }, diff --git a/tauri/src/endpoints/file_system.rs b/tauri/src/endpoints/file_system.rs index c462f7e26..a6e5a72cb 100644 --- a/tauri/src/endpoints/file_system.rs +++ b/tauri/src/endpoints/file_system.rs @@ -3,15 +3,16 @@ use web_view::WebView; use tauri_api::dir; use tauri_api::file; +use std::fs; use std::fs::File; use std::io::Write; -use super::cmd::{ReadDirOptions}; +use super::cmd::{DirOperationOptions}; pub fn read_dir( webview: &mut WebView<'_, T>, path: String, - options: Option, + options: Option, callback: String, error: String, ) { @@ -25,11 +26,11 @@ pub fn read_dir( }; if recursive { dir::walk_dir(path) - .map_err(|e| crate::ErrorKind::Command(e.to_string()).into()) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) .and_then(|f| serde_json::to_string(&f).map_err(|err| err.into())) } else { dir::list_dir_contents(path) - .map_err(|e| crate::ErrorKind::Command(e.to_string()).into()) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) .and_then(|f| serde_json::to_string(&f).map_err(|err| err.into())) } }, @@ -38,6 +39,122 @@ pub fn read_dir( ); } +pub fn copy_file( + webview: &mut WebView<'_, T>, + source: String, + destination: String, + callback: String, + error: String, +) { + crate::execute_promise( + webview, + move || { + fs::copy(source, destination) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) + .map(|_| "".to_string()) + }, + callback, + error, + ); +} + +pub fn create_dir( + webview: &mut WebView<'_, T>, + path: String, + options: Option, + callback: String, + error: String, +) { + crate::execute_promise( + webview, + move || { + let recursive = if let Some(options_value) = options { + options_value.recursive + } else { + false + }; + let response = if recursive { + fs::create_dir_all(path) + } else { + fs::create_dir(path) + }; + + response + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) + .map(|_| "".to_string()) + }, + callback, + error, + ); +} + +pub fn remove_dir( + webview: &mut WebView<'_, T>, + path: String, + options: Option, + callback: String, + error: String, +) { + crate::execute_promise( + webview, + move || { + let recursive = if let Some(options_value) = options { + options_value.recursive + } else { + false + }; + let response = if recursive { + fs::remove_dir_all(path) + } else { + fs::remove_dir(path) + }; + + response + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) + .map(|_| "".to_string()) + }, + callback, + error, + ); +} + +pub fn remove_file( + webview: &mut WebView<'_, T>, + path: String, + callback: String, + error: String, +) { + crate::execute_promise( + webview, + move || { + fs::remove_file(path) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) + .map(|_| "".to_string()) + }, + callback, + error, + ); +} + +pub fn rename_file( + webview: &mut WebView<'_, T>, + old_path: String, + new_path: String, + callback: String, + error: String, +) { + crate::execute_promise( + webview, + move || { + fs::rename(old_path, new_path) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) + .map(|_| "".to_string()) + }, + callback, + error, + ); +} + pub fn write_file( webview: &mut WebView<'_, T>, file: String, @@ -49,7 +166,7 @@ pub fn write_file( webview, move || { File::create(file) - .map_err(|e| crate::ErrorKind::Command(e.to_string()).into()) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) .and_then(|mut f| { f.write_all(contents.as_bytes()) .map_err(|err| err.into()) @@ -71,7 +188,7 @@ pub fn read_text_file( webview, move || { file::read_string(path) - .map_err(|e| crate::ErrorKind::Command(e.to_string()).into()) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) .and_then(|f| { serde_json::to_string(&f) .map_err(|err| err.into()) @@ -92,7 +209,7 @@ pub fn read_binary_file( webview, move || { file::read_binary(path) - .map_err(|e| crate::ErrorKind::Command(e.to_string()).into()) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) .and_then(|f| { serde_json::to_string(&f) .map_err(|err| err.into()) diff --git a/tauri/src/lib.rs b/tauri/src/lib.rs index 3c344a39b..dcea7fe7e 100644 --- a/tauri/src/lib.rs +++ b/tauri/src/lib.rs @@ -42,6 +42,10 @@ error_chain! { description("Command Error") display("Command Error: '{}'", t) } + FileSystem(t: String) { + description("FileSystem Error") + display("FileSystem Error: '{}'", t) + } Dialog(t: String) { description("Dialog Error") display("Dialog Error: '{}'", t)