use serde::{Deserialize, Serialize};
use crate::*;
#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FileUri {
pub uri: String,
pub document_top_tree_uri: Option<String>,
}
impl FileUri {
pub fn to_json_string(&self) -> Result<String> {
serde_json::to_string(self).map_err(Into::into)
}
pub fn from_json_str(json: impl AsRef<str>) -> Result<Self> {
serde_json::from_str(json.as_ref()).map_err(Into::into)
}
pub fn from_uri(uri: impl Into<String>) -> Self {
FileUri {
uri: uri.into(),
document_top_tree_uri: None
}
}
pub fn from_path(path: impl AsRef<std::path::Path>) -> Self {
Self {
uri: path_to_android_file_uri(path),
document_top_tree_uri: None
}
}
pub fn to_path(&self) -> Option<std::path::PathBuf> {
if self.is_file_scheme() {
return Some(android_file_uri_to_path(&self.uri))
}
None
}
pub fn is_file_scheme(&self) -> bool {
self.uri.starts_with("file://")
}
pub fn is_content_scheme(&self) -> bool {
self.uri.starts_with("content://")
}
}
impl From<&std::path::Path> for FileUri {
fn from(path: &std::path::Path) -> Self {
Self::from_path(path)
}
}
impl From<&std::path::PathBuf> for FileUri {
fn from(path: &std::path::PathBuf) -> Self {
Self::from_path(path)
}
}
impl From<std::path::PathBuf> for FileUri {
fn from(path: std::path::PathBuf) -> Self {
Self::from_path(path)
}
}
impl From<tauri_plugin_fs::FilePath> for FileUri {
fn from(value: tauri_plugin_fs::FilePath) -> Self {
match value {
tauri_plugin_fs::FilePath::Url(url) => Self::from_uri(url),
tauri_plugin_fs::FilePath::Path(path) => Self::from_path(path),
}
}
}
impl From<FileUri> for tauri_plugin_fs::FilePath {
fn from(value: FileUri) -> Self {
type NeverErr<T> = std::result::Result::<T, std::convert::Infallible>;
NeverErr::unwrap(value.uri.parse())
}
}
fn android_file_uri_to_path(uri: impl AsRef<str>) -> std::path::PathBuf {
let uri = uri.as_ref();
let path_part = uri.strip_prefix("file://").unwrap_or(uri);
let decoded = percent_encoding::percent_decode_str(path_part)
.decode_utf8_lossy();
std::path::PathBuf::from(decoded.as_ref())
}
fn path_to_android_file_uri(path: impl AsRef<std::path::Path>) -> String {
let encoded = path
.as_ref()
.to_string_lossy()
.split('/')
.map(|s| encode_android_uri_component(s))
.collect::<Vec<_>>()
.join("/");
format!("file://{}", encoded)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_android_safe_characters() {
let path = Path::new("/sdcard/test_file-name!.~'()*.txt");
let uri = path_to_android_file_uri(path);
assert_eq!(uri, "file:///sdcard/test_file-name!.~'()*.txt");
assert_eq!(android_file_uri_to_path(&uri), path);
}
#[test]
fn test_spaces_and_unsafe_chars() {
let path = Path::new("/sdcard/My Documents/file @#$%.txt");
let uri = path_to_android_file_uri(path);
assert_eq!(uri, "file:///sdcard/My%20Documents/file%20%40%23%24%25.txt");
assert_eq!(android_file_uri_to_path(&uri), path);
}
#[test]
fn test_unicode_characters() {
let path = Path::new("/sdcard/ダウンロード");
let uri = path_to_android_file_uri(path);
assert_eq!(uri, "file:///sdcard/%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89");
assert_eq!(android_file_uri_to_path(&uri), path);
}
}