Read picked import files through the fs plugin so Android content URIs work

On Android the open dialog returns a content:// URI, which std::fs can't
open, so ManaBox/library and deck imports failed with "no such file or
directory". Read them via tauri-plugin-fs, which resolves content URIs and
falls through to plain paths on desktop.

Closes #24
This commit is contained in:
2026-10-23 22:01:33 -05:00
parent 18948efd17
commit 5f19ddfa4a
4 changed files with 16 additions and 3 deletions

View File

@@ -16,6 +16,9 @@ tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-dialog = "2"
# Reads the files the dialog picks: on Android those are content:// URIs, which
# std::fs can't open.
tauri-plugin-fs = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
mtgc-core = { path = "../crates/mtgc-core" }

View File

@@ -11,6 +11,7 @@ use mtgc_core::{
};
use serde::Serialize;
use tauri::{AppHandle, Emitter, State};
use tauri_plugin_fs::{FilePath, FsExt};
use crate::state::AppState;
@@ -416,10 +417,17 @@ pub fn export_deck(db: Db, deck_id: i64, path: String) -> Res<()> {
std::fs::write(&path, text).map_err(|e| e.to_string())
}
/// Read a text file the open dialog picked. On Android that's a content:// URI
/// rather than a filesystem path, so it has to go through the fs plugin.
fn read_picked_text(app: &AppHandle, path: &str) -> Res<String> {
let path: FilePath = path.parse().unwrap();
app.fs().read_to_string(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn import_deck(db: Db, path: String, name: String) -> Res<DeckImportResult> {
pub fn import_deck(app: AppHandle, db: Db, path: String, name: String) -> Res<DeckImportResult> {
let text = read_picked_text(&app, &path)?;
let s = db.lock().unwrap();
let text = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
let (deck, unresolved) = s.catalog()?.import_deck(&name, &text).map_err(|e| e.to_string())?;
Ok(DeckImportResult { deck, unresolved })
}
@@ -452,7 +460,7 @@ pub async fn import_library_cards(
location: Option<String>,
) -> Res<LibraryImportResult> {
crate::sync::blocking(move || {
let text = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
let text = read_picked_text(&app, &path)?;
let s = crate::sync::lock(&app);
// At most one event per percent, so a big file doesn't flood the UI.
let mut last_percent = None;

View File

@@ -37,6 +37,7 @@ pub(crate) fn logged<T>(what: &str, result: Result<T, String>) -> Result<T, Stri
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let builder = tauri::Builder::default().plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_opener::init());
// Android gets sync sign-ins back through the `mtgc://` scheme; desktop
// uses a loopback port instead and needs no scheme registered.