Files
SoulStream/crates/tauri-plugin-storage/src/lib.rs
T
KeelrLeek b50577fc81 android: a playlist's cover from a photo on the phone
The storage plugin gains pick_image — the document picker limited to
images, the picked content copied into the app's cache so the Rust side
gets a plain path. playlist_cover_set(id, path) is the desktop pick's
install step split out and shared (squared, the master cover, the row's
revision moved). A playlist's sheet offers Choose cover photo and Remove
custom cover; the mock walk exercises both.
2026-09-13 21:20:11 -05:00

139 lines
4.1 KiB
Rust

//! 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<String>,
}
#[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<String>,
}
#[cfg(mobile)]
pub struct Storage<R: Runtime>(tauri::plugin::PluginHandle<R>);
#[tauri::command]
async fn pick_folder<R: Runtime>(app: tauri::AppHandle<R>) -> Result<PickedFolder, String> {
#[cfg(mobile)]
{
use tauri::Manager;
let storage = app.state::<Storage<R>>();
storage
.0
.run_mobile_plugin::<PickedFolder>("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<R: Runtime>(app: tauri::AppHandle<R>) -> Result<AccessState, String> {
#[cfg(mobile)]
{
use tauri::Manager;
let storage = app.state::<Storage<R>>();
storage
.0
.run_mobile_plugin::<AccessState>("allFilesAccess", ())
.map_err(|e| e.to_string())
}
#[cfg(not(mobile))]
{
let _ = app;
Ok(AccessState { granted: true })
}
}
#[tauri::command]
fn request_all_files_access<R: Runtime>(app: tauri::AppHandle<R>) -> Result<(), String> {
#[cfg(mobile)]
{
use tauri::Manager;
let storage = app.state::<Storage<R>>();
storage
.0
.run_mobile_plugin::<serde_json::Value>("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<R: Runtime>(app: tauri::AppHandle<R>) -> Result<PickedImage, String> {
#[cfg(mobile)]
{
use tauri::Manager;
let storage = app.state::<Storage<R>>();
storage
.0
.run_mobile_plugin::<PickedImage>("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<R: Runtime>() -> TauriPlugin<R> {
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()
}