90 lines
3.0 KiB
Rust
90 lines
3.0 KiB
Rust
//! The Community area's own small backend: the local contact metadata
|
|
//! (petnames, groups, notes, the two local marks) and the file dialogs the
|
|
//! You section needs. Everything Soulseek lives in commands.rs, everything
|
|
//! Freenet in freenet.rs / persona.rs / profile.rs — this is the glue that
|
|
//! is neither.
|
|
|
|
use serde::Deserialize;
|
|
|
|
use crate::config::{self, ContactMeta};
|
|
|
|
#[tauri::command]
|
|
pub fn contacts_meta_list(app: tauri::AppHandle) -> Vec<ContactMeta> {
|
|
config::load(&app).contacts
|
|
}
|
|
|
|
/// Upsert one contact's local metadata. An entry with nothing set is
|
|
/// dropped rather than stored empty, so the list only ever holds people the
|
|
/// user actually said something about.
|
|
#[tauri::command]
|
|
pub fn contact_meta_set(app: tauri::AppHandle, meta: ContactMeta) -> Vec<ContactMeta> {
|
|
let blank = |s: &Option<String>| s.as_deref().map_or(true, |v| v.trim().is_empty());
|
|
let mut cfg = config::load(&app);
|
|
cfg.contacts.retain(|c| c.username != meta.username);
|
|
let empty = blank(&meta.petname)
|
|
&& blank(&meta.group)
|
|
&& blank(&meta.note)
|
|
&& !meta.trusted
|
|
&& !meta.favorite;
|
|
if !empty {
|
|
let trim = |s: Option<String>| s.map(|v| v.trim().to_string()).filter(|v| !v.is_empty());
|
|
cfg.contacts.push(ContactMeta {
|
|
username: meta.username,
|
|
petname: trim(meta.petname),
|
|
group: trim(meta.group),
|
|
note: trim(meta.note),
|
|
trusted: meta.trusted,
|
|
favorite: meta.favorite,
|
|
});
|
|
}
|
|
config::save(&app, &cfg);
|
|
cfg.contacts
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum PickKind {
|
|
/// A source image for the avatar pipeline.
|
|
Image,
|
|
/// An existing recovery artifact to import.
|
|
RecoveryOpen,
|
|
/// Where to write a new recovery artifact.
|
|
RecoverySave,
|
|
}
|
|
|
|
const RECOVERY_EXT: &str = "ssrecovery";
|
|
|
|
/// Native file dialogs for the You section. Paths only — the bytes behind
|
|
/// them are read and written by Rust (persona.rs, profile.rs), never by the
|
|
/// webview.
|
|
#[tauri::command]
|
|
pub async fn community_pick_file(
|
|
app: tauri::AppHandle,
|
|
kind: PickKind,
|
|
) -> Result<Option<String>, String> {
|
|
use tauri_plugin_dialog::DialogExt;
|
|
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
let done = move |p: Option<tauri_plugin_dialog::FilePath>| {
|
|
let _ = tx.send(p.map(|p| p.to_string()));
|
|
};
|
|
match kind {
|
|
PickKind::Image => app
|
|
.dialog()
|
|
.file()
|
|
.add_filter("Image", &["jpg", "jpeg", "png", "webp"])
|
|
.pick_file(done),
|
|
PickKind::RecoveryOpen => app
|
|
.dialog()
|
|
.file()
|
|
.add_filter("SoulStream recovery file", &[RECOVERY_EXT])
|
|
.pick_file(done),
|
|
PickKind::RecoverySave => app
|
|
.dialog()
|
|
.file()
|
|
.add_filter("SoulStream recovery file", &[RECOVERY_EXT])
|
|
.set_file_name(&format!("soulstream-identity.{RECOVERY_EXT}"))
|
|
.save_file(done),
|
|
}
|
|
Ok(rx.await.unwrap_or(None))
|
|
}
|