diff --git a/.changes/fs-dialog-file-path-methods.md b/.changes/fs-dialog-file-path-methods.md new file mode 100644 index 0000000000..1adfbbf337 --- /dev/null +++ b/.changes/fs-dialog-file-path-methods.md @@ -0,0 +1,10 @@ +--- +"fs": patch +"dialog": patch +--- + +Add utility methods on `FilePath` and `SafeFilePath` enums which are: + +- `path` +- `simplified` +- `into_path` diff --git a/.changes/fs-dialog-file-path-traits.md b/.changes/fs-dialog-file-path-traits.md new file mode 100644 index 0000000000..3dfa4ee9cf --- /dev/null +++ b/.changes/fs-dialog-file-path-traits.md @@ -0,0 +1,6 @@ +--- +"fs": patch +"dialog": patch +--- + +Implement `Serialize`, `Deserialize`, `From`, `TryFrom` and `FromStr` traits for `FilePath` and `SafeFilePath` enums. diff --git a/.changes/fs-dialog-non-exhaustive-error.md b/.changes/fs-dialog-non-exhaustive-error.md new file mode 100644 index 0000000000..d17791935b --- /dev/null +++ b/.changes/fs-dialog-non-exhaustive-error.md @@ -0,0 +1,6 @@ +--- +"fs": patch +"dialog": patch +--- + +Mark `Error` enum as `#[non_exhuastive]`. diff --git a/.changes/fs-dialog-safe-file-path.md b/.changes/fs-dialog-safe-file-path.md new file mode 100644 index 0000000000..4e4606832b --- /dev/null +++ b/.changes/fs-dialog-safe-file-path.md @@ -0,0 +1,6 @@ +--- +"fs": patch +"dialog": patch +--- + +Add `SafeFilePath` enum. diff --git a/.prettierignore b/.prettierignore index 82c1a32ef1..ff5627141c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,7 +12,7 @@ pnpm-lock.yaml # examples gen directory examples/*/src-tauri/gen/ -plugins/examples/*/src-tauri/gen/ +plugins/*/examples/*/src-tauri/gen/ # autogenerated files **/autogenerated/**/*.md diff --git a/Cargo.lock b/Cargo.lock index 323d0f72ba..2ed68deacf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6253,9 +6253,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.29.1" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a97abbc7d6cfd0720da3e06fcb1cf2ac87cbfdb5bbbce103a1279a211c4d81" +checksum = "2a93f2c6b8fdaeb7f417bda89b5bc767999745c3052969664ae1fa65892deb7e" dependencies = [ "bitflags 2.6.0", "cocoa 0.26.0", @@ -6326,9 +6326,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.0.0-rc.8" +version = "2.0.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8345ccc676ef16e26b61fc0f5340b4e770678b1e1f53f08c69ebdac5e56b422" +checksum = "0b805e6bf5f6a4df7d1a64b2952d33fca6d538746efe9c9cdae4157a1efc5b17" dependencies = [ "anyhow", "bytes", @@ -6379,9 +6379,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.0-rc.7" +version = "2.0.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5ad5fcfaf02cf79aa6727f6c5df38567d8dce172b00b62690c6bc46c08b7ce" +checksum = "4acec578ff9de14da177722c8fb5e3d6c88af296696190c70b83bec91437248a" dependencies = [ "anyhow", "cargo_toml", @@ -6403,9 +6403,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.0-rc.7" +version = "2.0.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809ef6316726fc72593d296cf6f4e7461326e310c313d6a6c42b6e7f1e2671cf" +checksum = "0744bec087358e5de9a078a1b19346ed9b775f578395975f5a74ccd0c717b22a" dependencies = [ "base64 0.22.1", "brotli", @@ -6430,9 +6430,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.0-rc.6" +version = "2.0.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1359e8861d210d25731f8b1bfbb4d111dd06406cf73c59659366ef450364d811" +checksum = "b043cac341130f288044dca76fae8e62d7c18fdcd8012239a66af03868b7ca37" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -6444,9 +6444,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.0.0-rc.7" +version = "2.0.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dded420c86183f592d0fe925ef9447f41e26fa79f0bdfef8d3f17bfbcdbfb7" +checksum = "1c9bb31aad7296f85df545171023c72a263b54aac350197f923893fb5e6f90b4" dependencies = [ "anyhow", "glob", @@ -6546,7 +6546,6 @@ dependencies = [ name = "tauri-plugin-dialog" version = "2.0.0-rc.4" dependencies = [ - "dunce", "log", "raw-window-handle 0.6.2", "rfd", @@ -6564,6 +6563,7 @@ name = "tauri-plugin-fs" version = "2.0.0-rc.2" dependencies = [ "anyhow", + "dunce", "glob", "notify", "notify-debouncer-full", @@ -6916,9 +6916,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.0.0-rc.7" +version = "2.0.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c72b844f387bfc3341c355f3e16b8cbf4161848fa4e348670effb222cd3ba5" +checksum = "d7c7a6530acc06640e8f07cfeb01ac694f1de2f4e565525a2199e0dca80ff9f7" dependencies = [ "dpi", "gtk", @@ -6935,9 +6935,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.0.0-rc.7" +version = "2.0.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73accf936a7cd01d1382de7850726fdf6c1f6ab3b01ccb7a0950cb852e332596" +checksum = "5fcadbc24646c8d3362ed4e332cb42932e08c632220a20a61cb7e5fe36ddd85c" dependencies = [ "cocoa 0.26.0", "gtk", @@ -6959,9 +6959,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.0-rc.7" +version = "2.0.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53d9fe87e985b273696ae22ce2b9f099a8f1b44bc8fb127467bda5fcb3e4371" +checksum = "201498c8281ab2597e344b4a4c923e8d491782305979d71e7cdf8fb79aab5948" dependencies = [ "aes-gcm", "brotli", @@ -8508,9 +8508,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.42.0" +version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b8049c8f239cdbfaaea4bacb9646f6b208938ceec0acd5b3e99cd05f70903f" +checksum = "f4d715cf5fe88e9647f3d17b207b6d060d4a88e7171d4ccb2d2c657dd1d44728" dependencies = [ "base64 0.22.1", "block", diff --git a/Cargo.toml b/Cargo.toml index 9b9763b49d..07f696f2ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,10 @@ resolver = "2" [workspace.dependencies] serde = { version = "1", features = ["derive"] } log = "0.4" -tauri = { version = "2.0.0-rc.8", default-features = false } -tauri-build = "2.0.0-rc.7" -tauri-plugin = "2.0.0-rc.7" -tauri-utils = "2.0.0-rc.7" +tauri = { version = "2.0.0-rc.9", default-features = false } +tauri-build = "2.0.0-rc.8" +tauri-plugin = "2.0.0-rc.8" +tauri-utils = "2.0.0-rc.8" serde_json = "1" thiserror = "1" url = "2" diff --git a/plugins/dialog/Cargo.toml b/plugins/dialog/Cargo.toml index a05bf8e43a..681e6650ec 100644 --- a/plugins/dialog/Cargo.toml +++ b/plugins/dialog/Cargo.toml @@ -26,7 +26,6 @@ serde_json = { workspace = true } tauri = { workspace = true } log = { workspace = true } thiserror = { workspace = true } -dunce = { workspace = true } url = { workspace = true } tauri-plugin-fs = { path = "../fs", version = "2.0.0-rc.2" } diff --git a/plugins/dialog/src/commands.rs b/plugins/dialog/src/commands.rs index 2d884b6e00..76a92e0982 100644 --- a/plugins/dialog/src/commands.rs +++ b/plugins/dialog/src/commands.rs @@ -136,7 +136,7 @@ pub(crate) async fn open( let folders = dialog_builder.blocking_pick_folders(); if let Some(folders) = &folders { for folder in folders { - if let Ok(path) = folder.path() { + if let Ok(path) = folder.clone().into_path() { if let Some(s) = window.try_fs_scope() { s.allow_directory(path, options.recursive); } @@ -149,7 +149,7 @@ pub(crate) async fn open( } else { let folder = dialog_builder.blocking_pick_folder(); if let Some(folder) = &folder { - if let Ok(path) = folder.path() { + if let Ok(path) = folder.clone().into_path() { if let Some(s) = window.try_fs_scope() { s.allow_directory(path, options.recursive); } @@ -164,7 +164,7 @@ pub(crate) async fn open( let files = dialog_builder.blocking_pick_files(); if let Some(files) = &files { for file in files { - if let Ok(path) = file.path() { + if let Ok(path) = file.clone().into_path() { if let Some(s) = window.try_fs_scope() { s.allow_file(&path); } @@ -178,7 +178,7 @@ pub(crate) async fn open( let file = dialog_builder.blocking_pick_file(); if let Some(file) = &file { - if let Ok(path) = file.path() { + if let Ok(path) = file.clone().into_path() { if let Some(s) = window.try_fs_scope() { s.allow_file(&path); } @@ -218,7 +218,7 @@ pub(crate) async fn save( let path = dialog_builder.blocking_save_file(); if let Some(p) = &path { - if let Ok(path) = p.path() { + if let Ok(path) = p.clone().into_path() { if let Some(s) = window.try_fs_scope() { s.allow_file(&path); } diff --git a/plugins/dialog/src/error.rs b/plugins/dialog/src/error.rs index cb70e714f0..0c3ed5b866 100644 --- a/plugins/dialog/src/error.rs +++ b/plugins/dialog/src/error.rs @@ -7,6 +7,7 @@ use serde::{ser::Serializer, Serialize}; pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { #[error(transparent)] Tauri(#[from] tauri::Error), @@ -20,8 +21,6 @@ pub enum Error { FolderPickerNotImplemented, #[error(transparent)] Fs(#[from] tauri_plugin_fs::Error), - #[error("URL is not a valid path")] - InvalidPathUrl, } impl Serialize for Error { diff --git a/plugins/dialog/src/lib.rs b/plugins/dialog/src/lib.rs index 976878ccd0..26048eca5c 100644 --- a/plugins/dialog/src/lib.rs +++ b/plugins/dialog/src/lib.rs @@ -11,7 +11,7 @@ html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" )] -use serde::{Deserialize, Serialize}; +use serde::Serialize; use tauri::{ plugin::{Builder, TauriPlugin}, Manager, Runtime, @@ -24,6 +24,7 @@ use std::{ pub use models::*; +pub use tauri_plugin_fs::FilePath; #[cfg(desktop)] mod desktop; #[cfg(mobile)] @@ -294,57 +295,6 @@ impl MessageDialogBuilder { blocking_fn!(self, show) } } - -/// Represents either a filesystem path or a URI pointing to a file -/// such as `file://` URIs or Android `content://` URIs. -#[derive(Debug, Deserialize, Serialize)] -#[serde(untagged)] -pub enum FilePath { - Url(url::Url), - Path(PathBuf), -} - -impl From for FilePath { - fn from(value: PathBuf) -> Self { - Self::Path(value) - } -} - -impl From for FilePath { - fn from(value: url::Url) -> Self { - Self::Url(value) - } -} - -impl From for tauri_plugin_fs::FilePath { - fn from(value: FilePath) -> Self { - match value { - FilePath::Path(p) => tauri_plugin_fs::FilePath::Path(p), - FilePath::Url(url) => tauri_plugin_fs::FilePath::Url(url), - } - } -} - -impl FilePath { - fn simplified(self) -> Self { - match self { - Self::Url(url) => Self::Url(url), - Self::Path(p) => Self::Path(dunce::simplified(&p).to_path_buf()), - } - } - - #[inline] - fn path(&self) -> Result { - match self { - Self::Url(url) => url - .to_file_path() - .map(PathBuf::from) - .map_err(|_| Error::InvalidPathUrl), - Self::Path(p) => Ok(p.to_owned()), - } - } -} - #[derive(Debug, Serialize)] pub(crate) struct Filter { pub name: String, diff --git a/plugins/fs/Cargo.toml b/plugins/fs/Cargo.toml index f4719dc19c..5d99eaeadf 100644 --- a/plugins/fs/Cargo.toml +++ b/plugins/fs/Cargo.toml @@ -30,6 +30,7 @@ uuid = { version = "1", features = ["v4"] } glob = "0.3" notify = { version = "6", optional = true, features = ["serde"] } notify-debouncer-full = { version = "0.3", optional = true } +dunce = { workspace = true } [features] watch = ["notify", "notify-debouncer-full"] diff --git a/plugins/fs/src/commands.rs b/plugins/fs/src/commands.rs index c59706e149..b72e3b3f56 100644 --- a/plugins/fs/src/commands.rs +++ b/plugins/fs/src/commands.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize, Serializer}; use serde_repr::{Deserialize_repr, Serialize_repr}; use tauri::{ ipc::{CommandScope, GlobalScope}, - path::{BaseDirectory, SafePathBuf}, + path::BaseDirectory, utils::config::FsScope, AppHandle, Manager, Resource, ResourceId, Runtime, Webview, }; @@ -22,80 +22,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use crate::{scope::Entry, Error, FilePath, FsExt}; - -// TODO: Combine this with FilePath -#[derive(Debug)] -pub enum SafeFilePath { - Url(url::Url), - Path(SafePathBuf), -} - -impl<'de> serde::Deserialize<'de> for SafeFilePath { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct SafeFilePathVisitor; - - impl<'de> serde::de::Visitor<'de> for SafeFilePathVisitor { - type Value = SafeFilePath; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a string representing an file URL or a path") - } - - fn visit_str(self, s: &str) -> std::result::Result - where - E: serde::de::Error, - { - SafeFilePath::from_str(s).map_err(|e| { - serde::de::Error::invalid_value( - serde::de::Unexpected::Str(s), - &e.to_string().as_str(), - ) - }) - } - } - - deserializer.deserialize_str(SafeFilePathVisitor) - } -} - -impl From for FilePath { - fn from(value: SafeFilePath) -> Self { - match value { - SafeFilePath::Url(url) => FilePath::Url(url), - SafeFilePath::Path(p) => FilePath::Path(p.as_ref().to_owned()), - } - } -} - -impl FromStr for SafeFilePath { - type Err = CommandError; - fn from_str(s: &str) -> Result { - if let Ok(url) = url::Url::from_str(s) { - if url.scheme().len() != 1 { - return Ok(Self::Url(url)); - } - } - Ok(Self::Path(SafePathBuf::new(s.into())?)) - } -} - -impl SafeFilePath { - #[inline] - fn into_path(self) -> CommandResult { - match self { - Self::Url(url) => SafePathBuf::new( - url.to_file_path() - .map_err(|_| format!("failed to get path from {url}"))?, - ) - .map_err(Into::into), - Self::Path(p) => Ok(p), - } - } -} +use crate::{scope::Entry, Error, FsExt, SafeFilePath}; #[derive(Debug, thiserror::Error)] pub enum CommandError { @@ -1052,7 +979,7 @@ pub fn resolve_path( let path = if let Some(base_dir) = base_dir { webview.path().resolve(&path, base_dir)? } else { - path.as_ref().to_path_buf() + path }; let scope = tauri::scope::fs::Scope::new( diff --git a/plugins/fs/src/error.rs b/plugins/fs/src/error.rs index d62729a443..0c98e83fc3 100644 --- a/plugins/fs/src/error.rs +++ b/plugins/fs/src/error.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; use serde::{Serialize, Serializer}; #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { #[error(transparent)] Json(#[from] serde_json::Error), @@ -26,6 +27,10 @@ pub enum Error { #[cfg(target_os = "android")] #[error(transparent)] PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + #[error("URL is not a valid path")] + InvalidPathUrl, + #[error("Unsafe PathBuf: {0}")] + UnsafePathBuf(&'static str), } impl Serialize for Error { diff --git a/plugins/fs/src/file_path.rs b/plugins/fs/src/file_path.rs new file mode 100644 index 0000000000..967696b9a0 --- /dev/null +++ b/plugins/fs/src/file_path.rs @@ -0,0 +1,314 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + convert::Infallible, + path::{Path, PathBuf}, + str::FromStr, +}; + +use serde::Serialize; +use tauri::path::SafePathBuf; + +use crate::{Error, Result}; + +/// Represents either a filesystem path or a URI pointing to a file +/// such as `file://` URIs or Android `content://` URIs. +#[derive(Debug, Serialize, Clone)] +#[serde(untagged)] +pub enum FilePath { + /// `file://` URIs or Android `content://` URIs. + Url(url::Url), + /// Regular [`PathBuf`] + Path(PathBuf), +} + +/// Represents either a safe filesystem path or a URI pointing to a file +/// such as `file://` URIs or Android `content://` URIs. +#[derive(Debug, Clone, Serialize)] +pub enum SafeFilePath { + /// `file://` URIs or Android `content://` URIs. + Url(url::Url), + /// Safe [`PathBuf`], see [`SafePathBuf``]. + Path(SafePathBuf), +} + +impl FilePath { + /// Get a reference to the contaiend [`Path`] if the variant is [`FilePath::Path`]. + /// + /// Use [`FilePath::into_path`] to try to convert the [`FilePath::Url`] variant as well. + #[inline] + pub fn as_path(&self) -> Option<&Path> { + match self { + Self::Url(_) => None, + Self::Path(p) => Some(p), + } + } + + /// Try to convert into [`PathBuf`] if possible. + /// + /// This calls [`Url::to_file_path`](url::Url::to_file_path) if the variant is [`FilePath::Url`], + /// otherwise returns the contained [PathBuf] as is. + #[inline] + pub fn into_path(self) -> Result { + match self { + Self::Url(url) => url + .to_file_path() + .map(PathBuf::from) + .map_err(|_| Error::InvalidPathUrl), + Self::Path(p) => Ok(p), + } + } + + /// Takes the contained [`PathBuf`] if the variant is [`FilePath::Path`], + /// and when possible, converts Windows UNC paths to regular paths. + #[inline] + pub fn simplified(self) -> Self { + match self { + Self::Url(url) => Self::Url(url), + Self::Path(p) => Self::Path(dunce::simplified(&p).to_path_buf()), + } + } +} + +impl SafeFilePath { + /// Get a reference to the contaiend [`Path`] if the variant is [`SafeFilePath::Path`]. + /// + /// Use [`SafeFilePath::into_path`] to try to convert the [`SafeFilePath::Url`] variant as well. + #[inline] + pub fn as_path(&self) -> Option<&Path> { + match self { + Self::Url(_) => None, + Self::Path(p) => Some(p.as_ref()), + } + } + + /// Try to convert into [`PathBuf`] if possible. + /// + /// This calls [`Url::to_file_path`](url::Url::to_file_path) if the variant is [`SafeFilePath::Url`], + /// otherwise returns the contained [PathBuf] as is. + #[inline] + pub fn into_path(self) -> Result { + match self { + Self::Url(url) => url + .to_file_path() + .map(PathBuf::from) + .map_err(|_| Error::InvalidPathUrl), + Self::Path(p) => Ok(p.as_ref().to_owned()), + } + } + + /// Takes the contained [`PathBuf`] if the variant is [`SafeFilePath::Path`], + /// and when possible, converts Windows UNC paths to regular paths. + #[inline] + pub fn simplified(self) -> Self { + match self { + Self::Url(url) => Self::Url(url), + Self::Path(p) => { + // Safe to unwrap since it was a safe file path already + Self::Path(SafePathBuf::new(dunce::simplified(p.as_ref()).to_path_buf()).unwrap()) + } + } + } +} + +impl std::fmt::Display for FilePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Url(u) => u.fmt(f), + Self::Path(p) => p.display().fmt(f), + } + } +} + +impl std::fmt::Display for SafeFilePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Url(u) => u.fmt(f), + Self::Path(p) => p.display().fmt(f), + } + } +} + +impl<'de> serde::Deserialize<'de> for FilePath { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct FilePathVisitor; + + impl<'de> serde::de::Visitor<'de> for FilePathVisitor { + type Value = FilePath; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string representing an file URL or a path") + } + + fn visit_str(self, s: &str) -> std::result::Result + where + E: serde::de::Error, + { + FilePath::from_str(s).map_err(|e| { + serde::de::Error::invalid_value( + serde::de::Unexpected::Str(s), + &e.to_string().as_str(), + ) + }) + } + } + + deserializer.deserialize_str(FilePathVisitor) + } +} + +impl<'de> serde::Deserialize<'de> for SafeFilePath { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct SafeFilePathVisitor; + + impl<'de> serde::de::Visitor<'de> for SafeFilePathVisitor { + type Value = SafeFilePath; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string representing an file URL or a path") + } + + fn visit_str(self, s: &str) -> std::result::Result + where + E: serde::de::Error, + { + SafeFilePath::from_str(s).map_err(|e| { + serde::de::Error::invalid_value( + serde::de::Unexpected::Str(s), + &e.to_string().as_str(), + ) + }) + } + } + + deserializer.deserialize_str(SafeFilePathVisitor) + } +} + +impl FromStr for FilePath { + type Err = Infallible; + fn from_str(s: &str) -> std::result::Result { + if let Ok(url) = url::Url::from_str(s) { + if url.scheme().len() != 1 { + return Ok(Self::Url(url)); + } + } + Ok(Self::Path(PathBuf::from(s))) + } +} + +impl FromStr for SafeFilePath { + type Err = Error; + fn from_str(s: &str) -> Result { + if let Ok(url) = url::Url::from_str(s) { + if url.scheme().len() != 1 { + return Ok(Self::Url(url)); + } + } + + SafePathBuf::new(s.into()) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf) + } +} + +impl From for FilePath { + fn from(value: PathBuf) -> Self { + Self::Path(value) + } +} + +impl TryFrom for SafeFilePath { + type Error = Error; + fn try_from(value: PathBuf) -> Result { + SafePathBuf::new(value) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf) + } +} + +impl From<&Path> for FilePath { + fn from(value: &Path) -> Self { + Self::Path(value.to_owned()) + } +} + +impl TryFrom<&Path> for SafeFilePath { + type Error = Error; + fn try_from(value: &Path) -> Result { + SafePathBuf::new(value.to_path_buf()) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf) + } +} + +impl From<&PathBuf> for FilePath { + fn from(value: &PathBuf) -> Self { + Self::Path(value.to_owned()) + } +} + +impl TryFrom<&PathBuf> for SafeFilePath { + type Error = Error; + fn try_from(value: &PathBuf) -> Result { + SafePathBuf::new(value.to_owned()) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf) + } +} + +impl From for FilePath { + fn from(value: url::Url) -> Self { + Self::Url(value) + } +} + +impl From for SafeFilePath { + fn from(value: url::Url) -> Self { + Self::Url(value) + } +} + +impl TryFrom for PathBuf { + type Error = Error; + fn try_from(value: FilePath) -> Result { + value.into_path() + } +} + +impl TryFrom for PathBuf { + type Error = Error; + fn try_from(value: SafeFilePath) -> Result { + value.into_path() + } +} + +impl From for FilePath { + fn from(value: SafeFilePath) -> Self { + match value { + SafeFilePath::Url(url) => FilePath::Url(url), + SafeFilePath::Path(p) => FilePath::Path(p.as_ref().to_owned()), + } + } +} + +impl TryFrom for SafeFilePath { + type Error = Error; + + fn try_from(value: FilePath) -> Result { + match value { + FilePath::Url(url) => Ok(SafeFilePath::Url(url)), + FilePath::Path(p) => SafePathBuf::new(p) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf), + } + } +} diff --git a/plugins/fs/src/lib.rs b/plugins/fs/src/lib.rs index 975c126254..5cb903f8ee 100644 --- a/plugins/fs/src/lib.rs +++ b/plugins/fs/src/lib.rs @@ -11,13 +11,7 @@ html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" )] -use std::{ - convert::Infallible, - fmt, - io::Read, - path::{Path, PathBuf}, - str::FromStr, -}; +use std::io::Read; use serde::Deserialize; use tauri::{ @@ -32,6 +26,7 @@ mod config; #[cfg(not(target_os = "android"))] mod desktop; mod error; +mod file_path; #[cfg(target_os = "android")] mod mobile; #[cfg(target_os = "android")] @@ -48,92 +43,10 @@ pub use mobile::Fs; pub use error::Error; pub use scope::{Event as ScopeEvent, Scope}; -type Result = std::result::Result; - -// TODO: Combine this with SafeFilePath -/// Represents either a filesystem path or a URI pointing to a file -/// such as `file://` URIs or Android `content://` URIs. -#[derive(Debug)] -pub enum FilePath { - Url(url::Url), - Path(PathBuf), -} - -impl<'de> serde::Deserialize<'de> for FilePath { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct FilePathVisitor; - - impl<'de> serde::de::Visitor<'de> for FilePathVisitor { - type Value = FilePath; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a string representing an file URL or a path") - } - - fn visit_str(self, s: &str) -> std::result::Result - where - E: serde::de::Error, - { - FilePath::from_str(s).map_err(|e| { - serde::de::Error::invalid_value( - serde::de::Unexpected::Str(s), - &e.to_string().as_str(), - ) - }) - } - } - - deserializer.deserialize_str(FilePathVisitor) - } -} - -impl FromStr for FilePath { - type Err = Infallible; - fn from_str(s: &str) -> std::result::Result { - if let Ok(url) = url::Url::from_str(s) { - if url.scheme().len() != 1 { - return Ok(Self::Url(url)); - } - } - Ok(Self::Path(PathBuf::from(s))) - } -} - -impl From for FilePath { - fn from(value: PathBuf) -> Self { - Self::Path(value) - } -} - -impl From<&Path> for FilePath { - fn from(value: &Path) -> Self { - Self::Path(value.to_owned()) - } -} - -impl From<&PathBuf> for FilePath { - fn from(value: &PathBuf) -> Self { - Self::Path(value.to_owned()) - } -} - -impl From for FilePath { - fn from(value: url::Url) -> Self { - Self::Url(value) - } -} +pub use file_path::FilePath; +pub use file_path::SafeFilePath; -impl fmt::Display for FilePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(u) => u.fmt(f), - Self::Path(p) => p.display().fmt(f), - } - } -} +type Result = std::result::Result; #[derive(Debug, Default, Clone, Deserialize)] #[serde(rename_all = "camelCase")] @@ -151,8 +64,10 @@ pub struct OpenOptions { #[serde(default)] create_new: bool, #[serde(default)] + #[allow(unused)] mode: Option, #[serde(default)] + #[allow(unused)] custom_flags: Option, } diff --git a/plugins/fs/src/watcher.rs b/plugins/fs/src/watcher.rs index 5849cdf82b..cf2af50392 100644 --- a/plugins/fs/src/watcher.rs +++ b/plugins/fs/src/watcher.rs @@ -22,8 +22,9 @@ use std::{ }; use crate::{ - commands::{resolve_path, CommandResult, SafeFilePath}, + commands::{resolve_path, CommandResult}, scope::Entry, + SafeFilePath, }; struct InnerWatcher { diff --git a/plugins/single-instance/src/semver_compat.rs b/plugins/single-instance/src/semver_compat.rs index c613fdd7c4..80487cae6d 100644 --- a/plugins/single-instance/src/semver_compat.rs +++ b/plugins/single-instance/src/semver_compat.rs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#![cfg(feature = "semver")] - /// Takes a version and spits out a String with trailing _x, thus only considering the digits /// relevant regarding semver compatibility pub fn semver_compat_string(version: semver::Version) -> String {