Skip to main content

tauri_plugin_android_fs/api/models/
fs_uri.rs

1use crate::*;
2use serde::{Deserialize, Serialize};
3
4/// URI for a file or directory.
5///
6/// # Serialization
7/// Serialized by `serde` as the following TypeScript type:
8///
9/// ```ts
10/// type AndroidFsUri = {
11///     uri: string,
12///     documentTopTreeUri: string | null
13/// };
14/// ```
15#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct FsUri {
18
19    /// URI for a file or directory.
20    ///
21    /// # Note
22    /// This is a URI with either the `content` or `file` scheme.
23    pub uri: String,
24
25    /// Document tree URI of the root directory from which this entry originates.
26    ///
27    /// # Note
28    /// This field is set for directories obtained via Directory Picker
29    /// and for entries derived from those directories.
30    pub document_top_tree_uri: Option<String>,
31}
32
33impl FsUri {
34
35    /// Same as `serde_json::to_string()`
36    pub fn to_json_string(&self) -> Result<String> {
37        serde_json::to_string(self).map_err(Into::into)
38    }
39
40    /// Same as `serde_json::from_str()`
41    pub fn from_json_str(json: impl AsRef<str>) -> Result<Self> {
42        serde_json::from_str(json.as_ref()).map_err(Into::into)
43    }
44
45    pub fn from_uri(uri: impl Into<String>) -> Self {
46        FsUri {
47            uri: uri.into(),
48            document_top_tree_uri: None,
49        }
50    }
51
52    /// Constructs a URI from the absolute path of a file or directory.
53    ///
54    /// # Note
55    /// The path must be absolute and must not contain `./` or `../`.
56    /// Even if the path is invalid, this function will not return an error or panic;
57    /// instead, it returns an invalid URI.
58    ///
59    /// Note the following:
60    /// - This URI cannot be used with [`Opener`](crate::api::api_async::Opener) to open files in other apps.
61    /// - Operations using this URI may fall back to [`std::fs`] instead of the Kotlin API.
62    pub fn from_path(path: impl AsRef<std::path::Path>) -> Self {
63        Self {
64            uri: path_to_android_file_uri(path),
65            document_top_tree_uri: None,
66        }
67    }
68
69    /// Returns the path if this URI uses the `file` scheme;
70    /// otherwise, returns `None`.
71    pub fn to_path(&self) -> Option<std::path::PathBuf> {
72        if self.is_file_scheme() {
73            return Some(android_file_uri_to_path(&self.uri));
74        }
75        None
76    }
77
78    /// Returns `true` if this URI uses the `file` scheme.
79    pub fn is_file_scheme(&self) -> bool {
80        self.uri.starts_with("file://")
81    }
82
83    /// Returns `true` if this URI uses the `content` scheme.
84    pub fn is_content_scheme(&self) -> bool {
85        self.uri.starts_with("content://")
86    }
87}
88
89impl From<&std::path::Path> for FsUri {
90    fn from(path: &std::path::Path) -> Self {
91        Self::from_path(path)
92    }
93}
94
95impl From<&std::path::PathBuf> for FsUri {
96    fn from(path: &std::path::PathBuf) -> Self {
97        Self::from_path(path)
98    }
99}
100
101impl From<std::path::PathBuf> for FsUri {
102    fn from(path: std::path::PathBuf) -> Self {
103        Self::from_path(path)
104    }
105}
106
107impl From<tauri_plugin_fs::FilePath> for FsUri {
108    fn from(value: tauri_plugin_fs::FilePath) -> Self {
109        match value {
110            tauri_plugin_fs::FilePath::Url(url) => Self::from_uri(url),
111            tauri_plugin_fs::FilePath::Path(path) => Self::from_path(path),
112        }
113    }
114}
115
116impl From<FsUri> for tauri_plugin_fs::FilePath {
117    fn from(value: FsUri) -> Self {
118        type NeverErr<T> = std::result::Result<T, std::convert::Infallible>;
119        NeverErr::unwrap(value.uri.parse())
120    }
121}
122
123fn android_file_uri_to_path(uri: impl AsRef<str>) -> std::path::PathBuf {
124    let uri = uri.as_ref();
125    let path_part = uri.strip_prefix("file://").unwrap_or(uri);
126    let decoded = percent_encoding::percent_decode_str(path_part).decode_utf8_lossy();
127
128    std::path::PathBuf::from(decoded.as_ref())
129}
130
131fn path_to_android_file_uri(path: impl AsRef<std::path::Path>) -> String {
132    let encoded = path
133        .as_ref()
134        .to_string_lossy()
135        .split('/')
136        .map(|s| encode_android_uri_component(s))
137        .collect::<Vec<_>>()
138        .join("/");
139
140    format!("file://{}", encoded)
141}