This commit is contained in:
2025-11-14 01:59:07 +01:00
parent da8457edfc
commit fe7444be4f
+61 -17
View File
@@ -1281,7 +1281,16 @@ async fn calculate_directory_size(dir: &Path) -> eyre::Result<u64> {
Ok(total_size) Ok(total_size)
} }
async fn local_download_available(game_dir: &str, game_id: &str) -> bool { async fn local_download_available(
game_dir: &str,
game_id: &str,
downloading_games: &Arc<RwLock<HashSet<String>>>,
) -> bool {
if downloading_games.read().await.contains(game_id) {
log::debug!("Not serving game {game_id} locally because it is still downloading");
return false;
}
let game_path = PathBuf::from(game_dir).join(game_id); let game_path = PathBuf::from(game_dir).join(game_id);
let eti_path = game_path.join(format!("{game_id}.eti")); let eti_path = game_path.join(format!("{game_id}.eti"));
@@ -1299,6 +1308,7 @@ struct Ctx {
local_game_db: Arc<RwLock<Option<GameDB>>>, local_game_db: Arc<RwLock<Option<GameDB>>>,
peer_game_db: Arc<RwLock<PeerGameDB>>, peer_game_db: Arc<RwLock<PeerGameDB>>,
local_peer_addr: Arc<RwLock<Option<SocketAddr>>>, local_peer_addr: Arc<RwLock<Option<SocketAddr>>>,
downloading_games: Arc<RwLock<HashSet<String>>>,
} }
#[derive(Clone)] #[derive(Clone)]
@@ -1306,6 +1316,7 @@ struct PeerCtx {
game_dir: Arc<RwLock<Option<String>>>, game_dir: Arc<RwLock<Option<String>>>,
local_game_db: Arc<RwLock<Option<GameDB>>>, local_game_db: Arc<RwLock<Option<GameDB>>>,
local_peer_addr: Arc<RwLock<Option<SocketAddr>>>, local_peer_addr: Arc<RwLock<Option<SocketAddr>>>,
downloading_games: Arc<RwLock<HashSet<String>>>,
} }
impl std::fmt::Debug for PeerCtx { impl std::fmt::Debug for PeerCtx {
@@ -1314,6 +1325,7 @@ impl std::fmt::Debug for PeerCtx {
.field("game_dir", &"...") .field("game_dir", &"...")
.field("local_game_db", &"...") .field("local_game_db", &"...")
.field("local_peer_addr", &"...") .field("local_peer_addr", &"...")
.field("downloading_games", &"...")
.finish() .finish()
} }
} }
@@ -1334,12 +1346,14 @@ pub async fn run_peer(
local_game_db: Arc::new(RwLock::new(None)), local_game_db: Arc::new(RwLock::new(None)),
peer_game_db: Arc::new(RwLock::new(PeerGameDB::new())), peer_game_db: Arc::new(RwLock::new(PeerGameDB::new())),
local_peer_addr: Arc::new(RwLock::new(None)), local_peer_addr: Arc::new(RwLock::new(None)),
downloading_games: Arc::new(RwLock::new(HashSet::new())),
}; };
let peer_ctx = PeerCtx { let peer_ctx = PeerCtx {
game_dir: ctx.game_dir.clone(), game_dir: ctx.game_dir.clone(),
local_game_db: ctx.local_game_db.clone(), local_game_db: ctx.local_game_db.clone(),
local_peer_addr: ctx.local_peer_addr.clone(), local_peer_addr: ctx.local_peer_addr.clone(),
downloading_games: ctx.downloading_games.clone(),
}; };
// Start server component // Start server component
@@ -1519,7 +1533,7 @@ async fn try_serve_local_game(
return false; return false;
}; };
if !local_download_available(&game_dir, id).await { if !local_download_available(&game_dir, id, &ctx.downloading_games).await {
return false; return false;
} }
@@ -1646,7 +1660,7 @@ async fn handle_download_game_files_command(
} }
if peer_whitelist.is_empty() { if peer_whitelist.is_empty() {
if local_download_available(&games_folder, &id).await { if local_download_available(&games_folder, &id, &ctx.downloading_games).await {
log::info!("Using locally downloaded files for game {id}; skipping peer transfer"); log::info!("Using locally downloaded files for game {id}; skipping peer transfer");
if let Err(e) = tx_notify_ui.send(PeerEvent::DownloadGameFilesBegin { id: id.clone() }) if let Err(e) = tx_notify_ui.send(PeerEvent::DownloadGameFilesBegin { id: id.clone() })
{ {
@@ -1663,9 +1677,18 @@ async fn handle_download_game_files_command(
return; return;
} }
{
let mut in_progress = ctx.downloading_games.write().await;
if !in_progress.insert(id.clone()) {
log::warn!("Download for {id} already in progress; ignoring new request");
return;
}
}
let downloading_games = ctx.downloading_games.clone();
let tx_notify_ui = tx_notify_ui.clone(); let tx_notify_ui = tx_notify_ui.clone();
tokio::spawn(async move { tokio::spawn(async move {
match download_game_files( let result = download_game_files(
&id, &id,
resolved_descriptions, resolved_descriptions,
games_folder, games_folder,
@@ -1673,16 +1696,19 @@ async fn handle_download_game_files_command(
file_peer_map, file_peer_map,
tx_notify_ui.clone(), tx_notify_ui.clone(),
) )
.await .await;
{ {
Ok(()) => {} let mut guard = downloading_games.write().await;
Err(e) => { guard.remove(&id);
log::error!("Download failed for {id}: {e}"); }
if let Err(send_err) =
tx_notify_ui.send(PeerEvent::DownloadGameFilesFailed { id: id.clone() }) if let Err(e) = result {
{ log::error!("Download failed for {id}: {e}");
log::error!("Failed to send DownloadGameFilesFailed event: {send_err}"); if let Err(send_err) =
} tx_notify_ui.send(PeerEvent::DownloadGameFilesFailed { id: id.clone() })
{
log::error!("Failed to send DownloadGameFilesFailed event: {send_err}");
} }
} }
}); });
@@ -1780,10 +1806,22 @@ async fn handle_peer_stream(
Request::ListGames => { Request::ListGames => {
// Return list of games from this peer // Return list of games from this peer
log::info!("Received ListGames request from peer"); log::info!("Received ListGames request from peer");
let games = if let Some(ref db) = *ctx.local_game_db.read().await { let snapshot = {
db.all_games().into_iter().cloned().collect() let db_guard = ctx.local_game_db.read().await;
if let Some(ref db) = *db_guard {
db.all_games().into_iter().cloned().collect::<Vec<Game>>()
} else {
Vec::new()
}
};
let games = if snapshot.is_empty() {
snapshot
} else { } else {
Vec::new() let downloading = ctx.downloading_games.read().await;
snapshot
.into_iter()
.filter(|game| !downloading.contains(&game.id))
.collect()
}; };
if let Err(e) = tx.send(Response::ListGames(games).encode()).await { if let Err(e) = tx.send(Response::ListGames(games).encode()).await {
@@ -1792,7 +1830,13 @@ async fn handle_peer_stream(
} }
Request::GetGame { id } => { Request::GetGame { id } => {
log::info!("Received GetGame request for {id} from peer"); log::info!("Received GetGame request for {id} from peer");
let response = if let Some(ref game_dir) = *ctx.game_dir.read().await { let downloading = ctx.downloading_games.read().await.contains(&id);
let response = if downloading {
log::info!(
"Declining to serve GetGame for {id} because download is in progress"
);
Response::GameNotFound(id)
} else if let Some(ref game_dir) = *ctx.game_dir.read().await {
if let Some(ref db) = *ctx.local_game_db.read().await { if let Some(ref db) = *ctx.local_game_db.read().await {
if db.get_game_by_id(&id).is_some() { if db.get_game_by_id(&id).is_some() {
match get_game_file_descriptions(&id, game_dir).await { match get_game_file_descriptions(&id, game_dir).await {