community: every ledger takes its write lock, so the inbox check can no longer land on top of a friend accept

This commit is contained in:
KeelrLeek committed 2026-09-03 11:51:35 -05:00
1 parent 07227a90a7
commit 4d03cf849b
6 files changed
+89 -29

No files matched your search

+20 -10
View File
@@ -29,6 +29,7 @@ use serde::{Deserialize, Serialize};
use tauri::{Emitter, Manager, State};
use crate::inbox::{self, Cargo, ShareCargo};
use crate::ledger as ledger_lock;
use crate::persona;
use crate::pipeline::LibraryState;
use crate::playlist_formats::{ParsedEntry, ParsedPlaylist};
@@ -170,8 +171,11 @@ fn path(app: &tauri::AppHandle) -> Result<PathBuf, String> {
.app_data_dir()
.map_err(|e| e.to_string())?
.join("freenet")
.join("playlists-ledger.json"))
.join(LEDGER_FILE))
}
/// Both the file the ledger lives in and the key its write lock is taken on.
const LEDGER_FILE: &str = "playlists-ledger.json";
fn load(app: &tauri::AppHandle) -> Ledger {
path(app)
.ok()
@@ -231,6 +235,7 @@ pub async fn publish(app: &tauri::AppHandle, lib: &LibraryState, playlist_id: i6
if items.len() > pl::MAX_ENTRIES {
return Err(format!("{} entries is over the {} cap", items.len(), pl::MAX_ENTRIES));
}
let _guard = ledger_lock::hold(LEDGER_FILE).await;
let mut ledger = load(app);
let key = playlist_id.to_string();
let prev = ledger.published.get(&key).cloned();
@@ -331,6 +336,7 @@ pub async fn publish(app: &tauri::AppHandle, lib: &LibraryState, playlist_id: i6
pub async fn withdraw(app: &tauri::AppHandle, playlist_id: i64) -> Result<Published, String> {
let adapter = app.state::<AppState>().freenet.clone();
let owner = own_key(app).await?;
let _guard = ledger_lock::hold(LEDGER_FILE).await;
let mut ledger = load(app);
let key = playlist_id.to_string();
let prev = ledger.published.get(&key).cloned().ok_or("that playlist was never published")?;
@@ -564,8 +570,9 @@ pub async fn send(app: &tauri::AppHandle, username: &str, playlist_id: i64) -> R
Ok(())
}
fn keep_card(app: &tauri::AppHandle, card: ShareCard) {
async fn keep_card(app: &tauri::AppHandle, card: ShareCard) {
let who = if card.from_username.is_empty() { card.from_key.clone() } else { card.from_username.clone() };
let _guard = ledger_lock::hold(LEDGER_FILE).await;
let mut ledger = load(app);
ledger.inbox.retain(|c| c.id != card.id);
ledger.inbox.push(card);
@@ -577,7 +584,7 @@ fn keep_card(app: &tauri::AppHandle, card: ShareCard) {
}
/// Sealed cargo from inbox.rs: the envelope's signer is the sender.
pub(crate) fn on_inbox_share(app: &tauri::AppHandle, sender: [u8; 32], s: ShareCargo) {
pub(crate) async fn on_inbox_share(app: &tauri::AppHandle, sender: [u8; 32], s: ShareCargo) {
let from_key = b58(&sender);
let from_username = crate::slsk_link::username_for_key(app, &from_key).unwrap_or_default();
keep_card(
@@ -593,7 +600,8 @@ pub(crate) fn on_inbox_share(app: &tauri::AppHandle, sender: [u8; 32], s: ShareC
entry_count: s.entry_count,
received_at: now_secs(),
},
);
)
.await;
}
/// The event pump hands private messages here after the link and friend
@@ -603,9 +611,7 @@ pub fn intercept_pm(app: &tauri::AppHandle, from: &str, message: &str) -> bool {
use base64::Engine;
let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(rest.trim()) else { return true };
let Ok(r) = bincode::deserialize::<ShareRef>(&bytes) else { return true };
keep_card(
app,
ShareCard {
let card = ShareCard {
id: format!("{}:{}:{}", normalize_username(from), b58(&r.owner), b58(&r.playlist_uuid)),
from_username: from.to_string(),
from_key: String::new(),
@@ -615,8 +621,11 @@ pub fn intercept_pm(app: &tauri::AppHandle, from: &str, message: &str) -> bool {
name: cap(&r.name, pl::MAX_NAME_BYTES),
entry_count: r.entry_count,
received_at: now_secs(),
},
);
};
// Its own task: keeping a card now takes the ledger, and an inline await
// here would block the event pump.
let app = app.clone();
tauri::async_runtime::spawn(async move { keep_card(&app, card).await });
true
}
@@ -664,7 +673,8 @@ pub fn shares_inbox(app: tauri::AppHandle) -> Vec<ShareCard> {
v
}
#[tauri::command]
pub fn share_dismiss(app: tauri::AppHandle, id: String) -> Result<(), String> {
pub async fn share_dismiss(app: tauri::AppHandle, id: String) -> Result<(), String> {
let _guard = ledger_lock::hold(LEDGER_FILE).await;
let mut ledger = load(&app);
ledger.inbox.retain(|c| c.id != id);
save(&app, &ledger)