android: a playlist file, picked
The storage plugin grew `pick_file` beside `pick_folder` and `pick_image`: the document sheet with the caller's own filter, answered with a path Rust can open. A document already on this phone hands its path over as it lies — the document sibling of the same volume:relative id the folder picker resolves, plus the Downloads provider's raw: — so a 300 MB pick copies nothing; one that has no path is copied into cacheDir/picked under its own name, and only as far as the caller allows, because a playlist is kilobytes and a DJ set is not. Those copies are swept at the start of every pick, which pick_image's never were. The import sheet has its fifth door, on round 24's screen, wait card and review, and the note that apologised for its absence says the one true thing about it instead. `playlist_import_probe` took a path and a name the way `playlist_import_folder` took a path, and the name is the load-bearing half: the parser picks its format off the extension and titles the draft from the stem, so a cache copy under an invented name would have read every playlist as plain text. The mock walk drives the door and both of the picker's refusals through round 31's fail door; the phone walk opens the real sheet and dismisses it with BACK, reading nothing and writing nothing. The brief asked for a second consumer — sending a file in a chat — and there is nothing behind it: `community_pick_file` is the persona layer's three native dialogs, `soulseek-client` has `say_room` and `message_user` and nothing that sends a file, and both frontends' thread composers are text only. Nothing was invented for it. The plugin command is general enough — a caller's own filter, a zero-copy path, a bounded copy — that the one reader still waiting, the local set listener the phone puts off by design, needs no further plugin change. Playlist export is not on that list and never was: writing a document out is the share plugin's errand, not a picker's. The Kotlin is unbuilt — nothing on this machine compiles it.
This commit is contained in:
1 parent
04c87e0db1
commit
defd8c5e6d
16 files changed
+559
-57
No files matched your search
@@ -8,11 +8,26 @@
|
||||
//! 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".
|
||||
//! Five commands: `pick_folder` → `{ uri, path }` (path null when the tree
|
||||
//! is not on a volume with a path), `all_files_access` → `{ granted }`,
|
||||
//! `request_all_files_access`, which opens the system screen, `pick_image`
|
||||
//! → `{ path }` (a photo, copied into the app's cache), and `pick_file` →
|
||||
//! `{ path, name, size, copied }` — the document picker with the caller's
|
||||
//! own MIME filter.
|
||||
//!
|
||||
//! `pick_file` copies as little as it can, because an image is small and an
|
||||
//! audio file is not. A document already on this phone's storage hands its
|
||||
//! own path over as it lies, so picking a 300 MB file costs nothing; one
|
||||
//! with no path of its own is copied into `cacheDir/picked` under its own
|
||||
//! display name, and only as far as `maxCopyBytes` allows — zero refuses
|
||||
//! the copy outright. Those copies are swept at the start of every pick, so
|
||||
//! a path from one pick is gone by the next: read it in the same breath.
|
||||
//! The display name travels beside the path because it is load-bearing —
|
||||
//! the playlist parser picks its format off the extension and titles the
|
||||
//! draft from the stem.
|
||||
//!
|
||||
//! On the desktop the plugin is registered so the permission set resolves,
|
||||
//! and every command answers "not here".
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{
|
||||
@@ -33,6 +48,32 @@ pub struct AccessState {
|
||||
pub granted: bool,
|
||||
}
|
||||
|
||||
/// What a pick offers and what it may copy. `mime_types` is the document
|
||||
/// sheet's own filter, comma-separated; empty offers every type.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PickFileArgs {
|
||||
pub mime_types: Option<String>,
|
||||
/// How big a document may be COPIED into the app's cache when it has no
|
||||
/// path of its own on this phone. Zero refuses the copy.
|
||||
pub max_copy_bytes: u32,
|
||||
}
|
||||
|
||||
/// A picked document. `path` is the document's own when this phone has one
|
||||
/// for it, else the cache copy's (`copied`); `name` is the display name the
|
||||
/// provider gave it, which is what decides a playlist's format and title.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PickedFile {
|
||||
pub path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub size: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub copied: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PickedImage {
|
||||
@@ -97,6 +138,40 @@ fn request_all_files_access<R: Runtime>(app: tauri::AppHandle<R>) -> Result<(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A FILE from the phone: the document picker with the caller's own MIME
|
||||
/// filter, answered with a path the Rust side can open. A document already
|
||||
/// on this phone's storage is handed over as it lies; one that has no path
|
||||
/// is copied into the app's cache under its own name, and only as far as
|
||||
/// `max_copy_bytes` allows — zero refuses the copy, which is what a pick
|
||||
/// that could be hundreds of megabytes wants.
|
||||
#[tauri::command]
|
||||
async fn pick_file<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
mime_types: Option<String>,
|
||||
max_copy_bytes: Option<u32>,
|
||||
) -> Result<PickedFile, String> {
|
||||
#[cfg(mobile)]
|
||||
{
|
||||
use tauri::Manager;
|
||||
let storage = app.state::<Storage<R>>();
|
||||
storage
|
||||
.0
|
||||
.run_mobile_plugin::<PickedFile>(
|
||||
"pickFile",
|
||||
PickFileArgs {
|
||||
mime_types,
|
||||
max_copy_bytes: max_copy_bytes.unwrap_or(0),
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
#[cfg(not(mobile))]
|
||||
{
|
||||
let _ = (app, mime_types, max_copy_bytes);
|
||||
Err("the file picker here is the desktop's dialog".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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).
|
||||
@@ -180,7 +255,7 @@ pub fn create_document<R: Runtime>(
|
||||
|
||||
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])
|
||||
.invoke_handler(tauri::generate_handler![pick_folder, all_files_access, request_all_files_access, pick_image, pick_file])
|
||||
.setup(|app, api| {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user