Files
SoulStream/src-tauri/src/community.rs
T

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))
}