Files
mtgc/src-tauri/src/libraries.rs

191 lines
6.7 KiB
Rust
Raw Normal View History

//! The libraries on this device: listing, starting, opening, renaming and
//! deleting them.
//!
//! Each is a separate working copy (see [`AppState`]), and one is open at a
//! time. Linking them up with the account's libraries on a sync server is
//! `sync.rs`'s job; the commands here only touch the server for what the user
//! asks for directly — a rename, or deleting a library everywhere — and
//! otherwise leave it to the next sync.
use mtgc_core::sync::remote::{HttpRemote, NOT_FOUND};
use serde::Serialize;
use tauri::AppHandle;
use crate::state::{AppState, LocalLibrary};
use crate::sync::{blocking, lock, sync_now};
/// A library, as the UI lists it.
#[derive(Serialize)]
pub struct LibraryInfo {
pub id: String,
pub name: String,
/// The one that's open.
pub active: bool,
/// The library the device started with, which can't be deleted.
pub is_default: bool,
/// Whether it should be on the sync server.
pub sync: bool,
/// Whether it's linked to a library on the server yet.
pub linked: bool,
/// Another device deleted it from the server; this copy is all that's left.
pub server_deleted: bool,
}
fn info(s: &AppState, lib: &LocalLibrary) -> LibraryInfo {
LibraryInfo {
id: lib.id.clone(),
name: lib.name.clone(),
active: s.active_library().id == lib.id,
is_default: lib.is_default(),
sync: lib.sync,
linked: lib.remote_id.is_some(),
server_deleted: lib.server_deleted,
}
}
fn list(s: &AppState) -> Vec<LibraryInfo> {
s.libraries().iter().map(|l| info(s, l)).collect()
}
/// The signed-in account's server, if any.
fn account_remote(s: &AppState) -> Option<HttpRemote> {
let a = s.sync_account()?;
Some(HttpRemote::new(&a.server_url, a.token.as_deref()?))
}
/// Let the next sync put a change on the server now rather than at the next
/// trigger, without making the user wait for it.
fn sync_soon(app: &AppHandle) {
let app = app.clone();
tauri::async_runtime::spawn(async move {
let _ = sync_now(app).await;
});
}
#[tauri::command]
pub async fn list_libraries(app: AppHandle) -> Result<Vec<LibraryInfo>, String> {
blocking(move || Ok(list(&lock(&app)))).await
}
/// Start a new, empty library and open it. With `sync`, the next sync puts it
/// on the server.
#[tauri::command]
pub async fn create_library(app: AppHandle, name: String, sync: bool) -> Result<Vec<LibraryInfo>, String> {
let app2 = app.clone();
let libs = blocking(move || {
let mut s = lock(&app2);
let lib = s.create_library(&name, sync, None)?;
s.switch_library(&lib.id)?;
crate::mlog!("create_library: {} (sync={sync})", lib.name);
Ok(list(&s))
})
.await;
let libs = crate::logged("create_library", libs)?;
if sync {
sync_soon(&app);
}
Ok(libs)
}
/// Open another library.
#[tauri::command]
pub async fn switch_library(app: AppHandle, id: String) -> Result<Vec<LibraryInfo>, String> {
let libs = blocking(move || {
let mut s = lock(&app);
s.switch_library(&id)?;
crate::mlog!("switch_library: {}", s.active_library().name);
Ok(list(&s))
})
.await;
crate::logged("switch_library", libs)
}
/// Rename a library — on the server too when it's there, so every device
/// picks the new name up.
#[tauri::command]
pub async fn rename_library(app: AppHandle, id: String, name: String) -> Result<Vec<LibraryInfo>, String> {
let libs = blocking(move || {
let (name, remote_id, remote) = {
let s = lock(&app);
let lib = s.library(&id).ok_or("There's no such library on this device.")?;
(s.check_name(&name, Some(&id))?, lib.remote_id.clone(), account_remote(&s))
};
if let Some(rid) = &remote_id {
let remote = remote.ok_or("Sign in to the sync server to rename a library that's on it.")?;
remote.rename_library(rid, &name).map_err(|e| format!("Couldn't rename it on the server: {e}"))?;
}
let mut s = lock(&app);
s.update_library(&id, |l| l.name = name);
Ok(list(&s))
})
.await;
crate::logged("rename_library", libs)
}
/// Delete a library from this device. With `everywhere`, from the sync
/// server too — and so, at their next sync, from every other device's list
/// of what's on the server (they keep their own copies until removed).
#[tauri::command]
pub async fn delete_library(app: AppHandle, id: String, everywhere: bool) -> Result<Vec<LibraryInfo>, String> {
let libs = blocking(move || {
let (remote_id, remote) = {
let s = lock(&app);
let lib = s.library(&id).ok_or("There's no such library on this device.")?;
(lib.remote_id.clone(), account_remote(&s))
};
if let (true, Some(rid)) = (everywhere, &remote_id) {
let remote = remote.ok_or("Sign in to the sync server to delete a library from it.")?;
match remote.delete_library(rid) {
Ok(()) => {}
// Someone got there first.
Err(e) if e.to_string() == NOT_FOUND => {}
Err(e) => return Err(format!("Couldn't delete it from the server: {e}")),
}
}
let mut s = lock(&app);
let lib = s.delete_library(&id)?;
if let (false, Some(rid)) = (everywhere, &remote_id) {
// Still on the server: don't download it straight back.
s.dismiss_remote(rid);
}
crate::mlog!("delete_library: {} (everywhere={everywhere})", lib.name);
Ok(list(&s))
})
.await;
crate::logged("delete_library", libs)
}
/// Start or stop syncing a library. Stopping leaves the server's copy where
/// it is, for the other devices; starting again links back up with it if it's
/// still there (by name), or puts this one on the server.
#[tauri::command]
pub async fn set_library_sync(app: AppHandle, id: String, sync: bool) -> Result<Vec<LibraryInfo>, String> {
let app2 = app.clone();
let libs = blocking(move || {
let mut s = lock(&app2);
let lib = s.library(&id).cloned().ok_or("There's no such library on this device.")?;
if s.sync_running {
return Err("Wait for the sync to finish, then try again.".into());
}
if !sync {
if let Some(rid) = &lib.remote_id {
s.dismiss_remote(rid);
}
}
s.update_library(&id, |l| {
l.sync = sync;
l.server_deleted = false;
if !sync {
l.remote_id = None;
}
});
Ok(list(&s))
})
.await;
let libs = crate::logged("set_library_sync", libs)?;
if sync {
sync_soon(&app);
}
Ok(libs)
}