Skip to content

Commit

Permalink
refactor: add methods and implement traits for FilePath and `SafeFi…
Browse files Browse the repository at this point in the history
…lePath` (#1727)

* refactor: add methods and implement traits for `FilePath` and `SafeFilePath`

closes #1726

* clippy

* path -> as_path

* fix prettierignore

* Discard changes to Cargo.lock

* Discard changes to Cargo.toml

* update tauri deps
  • Loading branch information
amrbashir authored Sep 5, 2024
1 parent d00519e commit a2fe555
Show file tree
Hide file tree
Showing 17 changed files with 370 additions and 233 deletions.
10 changes: 10 additions & 0 deletions .changes/fs-dialog-file-path-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"fs": patch
"dialog": patch
---

Add utility methods on `FilePath` and `SafeFilePath` enums which are:

- `path`
- `simplified`
- `into_path`
6 changes: 6 additions & 0 deletions .changes/fs-dialog-file-path-traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"fs": patch
"dialog": patch
---

Implement `Serialize`, `Deserialize`, `From`, `TryFrom` and `FromStr` traits for `FilePath` and `SafeFilePath` enums.
6 changes: 6 additions & 0 deletions .changes/fs-dialog-non-exhaustive-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"fs": patch
"dialog": patch
---

Mark `Error` enum as `#[non_exhuastive]`.
6 changes: 6 additions & 0 deletions .changes/fs-dialog-safe-file-path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"fs": patch
"dialog": patch
---

Add `SafeFilePath` enum.
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion plugins/dialog/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
10 changes: 5 additions & 5 deletions plugins/dialog/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ pub(crate) async fn open<R: Runtime>(
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);
}
Expand All @@ -149,7 +149,7 @@ pub(crate) async fn open<R: Runtime>(
} 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);
}
Expand All @@ -164,7 +164,7 @@ pub(crate) async fn open<R: Runtime>(
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);
}
Expand All @@ -178,7 +178,7 @@ pub(crate) async fn open<R: Runtime>(
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);
}
Expand Down Expand Up @@ -218,7 +218,7 @@ pub(crate) async fn save<R: Runtime>(

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);
}
Expand Down
3 changes: 1 addition & 2 deletions plugins/dialog/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use serde::{ser::Serializer, Serialize};
pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Tauri(#[from] tauri::Error),
Expand All @@ -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 {
Expand Down
54 changes: 2 additions & 52 deletions plugins/dialog/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,6 +24,7 @@ use std::{

pub use models::*;

pub use tauri_plugin_fs::FilePath;
#[cfg(desktop)]
mod desktop;
#[cfg(mobile)]
Expand Down Expand Up @@ -294,57 +295,6 @@ impl<R: Runtime> MessageDialogBuilder<R> {
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<PathBuf> for FilePath {
fn from(value: PathBuf) -> Self {
Self::Path(value)
}
}

impl From<url::Url> for FilePath {
fn from(value: url::Url) -> Self {
Self::Url(value)
}
}

impl From<FilePath> 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<PathBuf> {
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,
Expand Down
1 change: 1 addition & 0 deletions plugins/fs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
79 changes: 3 additions & 76 deletions plugins/fs/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, s: &str) -> std::result::Result<Self::Value, E>
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<SafeFilePath> 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<Self, Self::Err> {
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<SafePathBuf> {
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 {
Expand Down Expand Up @@ -1052,7 +979,7 @@ pub fn resolve_path<R: Runtime>(
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(
Expand Down
5 changes: 5 additions & 0 deletions plugins/fs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit a2fe555

Please sign in to comment.