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
+44
-16
@@ -670,30 +670,58 @@ pub struct ImportDraft {
|
||||
|
||||
/// Open a playlist file and stage it for review. Parsing and resolution only —
|
||||
/// nothing is written until `playlist_import_commit`.
|
||||
///
|
||||
/// The phone has no dialog here, so it picks the file itself (the storage
|
||||
/// plugin's document picker) and hands the path in — with the picked
|
||||
/// document's own `name` beside it, because a document that had no path of
|
||||
/// its own arrives as a copy in the app's cache, and the NAME is what
|
||||
/// `detect_format` reads the extension off and what titles the draft. The
|
||||
/// desktop passes neither and opens its own dialog.
|
||||
#[tauri::command]
|
||||
pub async fn playlist_import_probe(
|
||||
app: tauri::AppHandle,
|
||||
lib: State<'_, LibraryState>,
|
||||
path: Option<String>,
|
||||
name: Option<String>,
|
||||
) -> Result<Option<ImportDraft>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
app.dialog()
|
||||
.file()
|
||||
.add_filter(
|
||||
"Playlist",
|
||||
&["m3u", "m3u8", "pls", "xspf", "csv", "json", "txt"],
|
||||
)
|
||||
.pick_file(move |path| {
|
||||
let _ = tx.send(path.map(|p| p.to_string()));
|
||||
});
|
||||
let Some(source) = rx.await.unwrap_or(None) else {
|
||||
let picked = match path {
|
||||
Some(p) if !p.trim().is_empty() => Some(p),
|
||||
_ => {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<Option<String>>();
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
app.dialog()
|
||||
.file()
|
||||
.add_filter(
|
||||
"Playlist",
|
||||
&["m3u", "m3u8", "pls", "xspf", "csv", "json", "txt"],
|
||||
)
|
||||
.pick_file(move |path| {
|
||||
let _ = tx.send(path.map(|p| p.to_string()));
|
||||
});
|
||||
}
|
||||
#[cfg(mobile)]
|
||||
{
|
||||
let _ = &app;
|
||||
let _ = tx.send(None);
|
||||
}
|
||||
rx.await.unwrap_or(None)
|
||||
}
|
||||
};
|
||||
let Some(source) = picked else {
|
||||
return Ok(None);
|
||||
};
|
||||
let source_path = PathBuf::from(&source);
|
||||
let name = source_path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| source.clone());
|
||||
// The picked name wins over the path's own: a cache copy is named after
|
||||
// the document, but only the document's name is guaranteed to be it.
|
||||
let name = match name {
|
||||
Some(n) if !n.trim().is_empty() => n,
|
||||
_ => source_path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| source.clone()),
|
||||
};
|
||||
// Size-check before reading, and read off the async runtime — a playlist
|
||||
// picked from a dead network share must not hang the whole backend.
|
||||
let read_path = source_path.clone();
|
||||
|
||||
Reference in new issue
Block a user