//! Storage on the phone. //! //! The library on Android lives in the app's private folder unless the user //! points it somewhere else. Pointing it somewhere else needs two things the //! WebView cannot do: a folder picker (Android's document tree picker, whose //! answer is a content URI — resolved here to a real path when the volume //! allows, which the Rust scanner and the download pipeline work on), and //! for a folder in shared storage, Android's all-files access, which only //! the user can grant on a system screen. //! //! Three commands: `pick_folder` → `{ uri, path }` (path null when the tree //! is not on a volume with a path), `all_files_access` → `{ granted }`, and //! `request_all_files_access`, which opens the system screen. On the desktop //! the plugin is registered so the permission set resolves, and every //! command answers "not here". use serde::{Deserialize, Serialize}; use tauri::{ plugin::{Builder, TauriPlugin}, Runtime, }; #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct PickedFolder { pub uri: String, pub path: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct AccessState { pub granted: bool, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct PickedImage { pub path: Option, } #[cfg(mobile)] pub struct Storage(tauri::plugin::PluginHandle); #[tauri::command] async fn pick_folder(app: tauri::AppHandle) -> Result { #[cfg(mobile)] { use tauri::Manager; let storage = app.state::>(); storage .0 .run_mobile_plugin::("pickFolder", ()) .map_err(|e| e.to_string()) } #[cfg(not(mobile))] { let _ = app; Err("the folder picker here is the desktop's dialog".into()) } } #[tauri::command] fn all_files_access(app: tauri::AppHandle) -> Result { #[cfg(mobile)] { use tauri::Manager; let storage = app.state::>(); storage .0 .run_mobile_plugin::("allFilesAccess", ()) .map_err(|e| e.to_string()) } #[cfg(not(mobile))] { let _ = app; Ok(AccessState { granted: true }) } } #[tauri::command] fn request_all_files_access(app: tauri::AppHandle) -> Result<(), String> { #[cfg(mobile)] { use tauri::Manager; let storage = app.state::>(); storage .0 .run_mobile_plugin::("requestAllFilesAccess", ()) .map(|_| ()) .map_err(|e| e.to_string()) } #[cfg(not(mobile))] { let _ = app; Ok(()) } } /// A photo from the phone: the document picker limited to images, the /// picked content copied into the app's cache — a plain path for the Rust /// side (a playlist's cover, for one). #[tauri::command] async fn pick_image(app: tauri::AppHandle) -> Result { #[cfg(mobile)] { use tauri::Manager; let storage = app.state::>(); storage .0 .run_mobile_plugin::("pickImage", ()) .map_err(|e| e.to_string()) } #[cfg(not(mobile))] { let _ = app; Err("the image picker here is the desktop's dialog".into()) } } pub fn init() -> TauriPlugin { Builder::new("storage") .invoke_handler(tauri::generate_handler![pick_folder, all_files_access, request_all_files_access, pick_image]) .setup(|app, api| { #[cfg(target_os = "android")] { use tauri::Manager; let handle = api.register_android_plugin("com.soulstream.storage", "StoragePlugin")?; app.manage(Storage(handle)); } #[cfg(not(target_os = "android"))] { let _ = (app, api); } Ok(()) }) .build() }