peers gone...
This commit is contained in:
@@ -33,6 +33,7 @@ use tokio::{
|
|||||||
RwLock,
|
RwLock,
|
||||||
mpsc::{UnboundedReceiver, UnboundedSender},
|
mpsc::{UnboundedReceiver, UnboundedSender},
|
||||||
},
|
},
|
||||||
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -126,6 +127,9 @@ pub enum PeerEvent {
|
|||||||
DownloadGameFilesFailed {
|
DownloadGameFilesFailed {
|
||||||
id: String,
|
id: String,
|
||||||
},
|
},
|
||||||
|
DownloadGameFilesAllPeersGone {
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
NoPeersHaveGame {
|
NoPeersHaveGame {
|
||||||
id: String,
|
id: String,
|
||||||
},
|
},
|
||||||
@@ -1309,6 +1313,7 @@ struct Ctx {
|
|||||||
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>>>,
|
downloading_games: Arc<RwLock<HashSet<String>>>,
|
||||||
|
active_downloads: Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -1347,6 +1352,7 @@ pub async fn run_peer(
|
|||||||
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())),
|
downloading_games: Arc::new(RwLock::new(HashSet::new())),
|
||||||
|
active_downloads: Arc::new(RwLock::new(HashMap::new())),
|
||||||
};
|
};
|
||||||
|
|
||||||
let peer_ctx = PeerCtx {
|
let peer_ctx = PeerCtx {
|
||||||
@@ -1384,8 +1390,16 @@ pub async fn run_peer(
|
|||||||
// Start ping service task
|
// Start ping service task
|
||||||
let tx_notify_ui_ping = tx_notify_ui.clone();
|
let tx_notify_ui_ping = tx_notify_ui.clone();
|
||||||
let peer_game_db_ping = ctx.peer_game_db.clone();
|
let peer_game_db_ping = ctx.peer_game_db.clone();
|
||||||
|
let downloading_games_ping = ctx.downloading_games.clone();
|
||||||
|
let active_downloads_ping = ctx.active_downloads.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
run_ping_service(tx_notify_ui_ping, peer_game_db_ping).await;
|
run_ping_service(
|
||||||
|
tx_notify_ui_ping,
|
||||||
|
peer_game_db_ping,
|
||||||
|
downloading_games_ping,
|
||||||
|
active_downloads_ping,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start local game directory monitoring task
|
// Start local game directory monitoring task
|
||||||
@@ -1686,32 +1700,39 @@ async fn handle_download_game_files_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let downloading_games = ctx.downloading_games.clone();
|
let downloading_games = ctx.downloading_games.clone();
|
||||||
let tx_notify_ui = tx_notify_ui.clone();
|
let active_downloads = ctx.active_downloads.clone();
|
||||||
tokio::spawn(async move {
|
let tx_notify_ui_clone = tx_notify_ui.clone();
|
||||||
|
let download_id = id.clone();
|
||||||
|
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
let result = download_game_files(
|
let result = download_game_files(
|
||||||
&id,
|
&download_id,
|
||||||
resolved_descriptions,
|
resolved_descriptions,
|
||||||
games_folder,
|
games_folder,
|
||||||
peer_whitelist,
|
peer_whitelist,
|
||||||
file_peer_map,
|
file_peer_map,
|
||||||
tx_notify_ui.clone(),
|
tx_notify_ui_clone.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut guard = downloading_games.write().await;
|
let mut guard = downloading_games.write().await;
|
||||||
guard.remove(&id);
|
guard.remove(&download_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
log::error!("Download failed for {id}: {e}");
|
log::error!("Download failed for {download_id}: {e}");
|
||||||
if let Err(send_err) =
|
if let Err(send_err) = tx_notify_ui_clone.send(PeerEvent::DownloadGameFilesFailed {
|
||||||
tx_notify_ui.send(PeerEvent::DownloadGameFilesFailed { id: id.clone() })
|
id: download_id.clone(),
|
||||||
{
|
}) {
|
||||||
log::error!("Failed to send DownloadGameFilesFailed event: {send_err}");
|
log::error!("Failed to send DownloadGameFilesFailed event: {send_err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = active_downloads.write().await.remove(&download_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ctx.active_downloads.write().await.insert(id, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_set_game_dir_command(ctx: &Ctx, game_dir: String) {
|
async fn handle_set_game_dir_command(ctx: &Ctx, game_dir: String) {
|
||||||
@@ -2155,9 +2176,54 @@ async fn request_game_details_from_peer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_active_downloads_without_peers(
|
||||||
|
peer_game_db: &Arc<RwLock<PeerGameDB>>,
|
||||||
|
downloading_games: &Arc<RwLock<HashSet<String>>>,
|
||||||
|
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
|
||||||
|
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
||||||
|
) {
|
||||||
|
let active_ids: Vec<String> = { downloading_games.read().await.iter().cloned().collect() };
|
||||||
|
if active_ids.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in active_ids {
|
||||||
|
let has_peers = {
|
||||||
|
let guard = peer_game_db.read().await;
|
||||||
|
!guard.peers_with_game(&id).is_empty()
|
||||||
|
};
|
||||||
|
|
||||||
|
if has_peers {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let removed_from_tracking = {
|
||||||
|
let mut guard = downloading_games.write().await;
|
||||||
|
guard.remove(&id)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !removed_from_tracking {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(handle) = { active_downloads.write().await.remove(&id) } {
|
||||||
|
handle.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) =
|
||||||
|
tx_notify_ui.send(PeerEvent::DownloadGameFilesAllPeersGone { id: id.clone() })
|
||||||
|
{
|
||||||
|
log::error!("Failed to send DownloadGameFilesAllPeersGone event: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
async fn run_ping_service(
|
async fn run_ping_service(
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: UnboundedSender<PeerEvent>,
|
||||||
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
||||||
|
downloading_games: Arc<RwLock<HashSet<String>>>,
|
||||||
|
active_downloads: Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
|
||||||
) {
|
) {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Starting ping service ({PEER_PING_INTERVAL_SECS}s interval, \
|
"Starting ping service ({PEER_PING_INTERVAL_SECS}s interval, \
|
||||||
@@ -2174,6 +2240,8 @@ async fn run_ping_service(
|
|||||||
for peer_addr in peer_addresses {
|
for peer_addr in peer_addresses {
|
||||||
let tx_notify_ui_clone = tx_notify_ui.clone();
|
let tx_notify_ui_clone = tx_notify_ui.clone();
|
||||||
let peer_game_db_clone = peer_game_db.clone();
|
let peer_game_db_clone = peer_game_db.clone();
|
||||||
|
let downloading_games_clone = downloading_games.clone();
|
||||||
|
let active_downloads_clone = active_downloads.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
match ping_peer(peer_addr).await {
|
match ping_peer(peer_addr).await {
|
||||||
@@ -2208,6 +2276,13 @@ async fn run_ping_service(
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit_peer_game_list(&peer_game_db_clone, &tx_notify_ui_clone).await;
|
emit_peer_game_list(&peer_game_db_clone, &tx_notify_ui_clone).await;
|
||||||
|
handle_active_downloads_without_peers(
|
||||||
|
&peer_game_db_clone,
|
||||||
|
&downloading_games_clone,
|
||||||
|
&active_downloads_clone,
|
||||||
|
&tx_notify_ui_clone,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2233,6 +2308,13 @@ async fn run_ping_service(
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit_peer_game_list(&peer_game_db_clone, &tx_notify_ui_clone).await;
|
emit_peer_game_list(&peer_game_db_clone, &tx_notify_ui_clone).await;
|
||||||
|
handle_active_downloads_without_peers(
|
||||||
|
&peer_game_db_clone,
|
||||||
|
&downloading_games_clone,
|
||||||
|
&active_downloads_clone,
|
||||||
|
&tx_notify_ui_clone,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2266,6 +2348,13 @@ async fn run_ping_service(
|
|||||||
|
|
||||||
if removed_any {
|
if removed_any {
|
||||||
emit_peer_game_list(&peer_game_db, &tx_notify_ui).await;
|
emit_peer_game_list(&peer_game_db, &tx_notify_ui).await;
|
||||||
|
handle_active_downloads_without_peers(
|
||||||
|
&peer_game_db,
|
||||||
|
&downloading_games,
|
||||||
|
&active_downloads,
|
||||||
|
&tx_notify_ui,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,47 @@ fn cleanup_backup_folder(backup_path: &Path) -> eyre::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn cleanup_failed_download(app_handle: &AppHandle, id: &str) {
|
||||||
|
app_handle
|
||||||
|
.state::<LanSpreadState>()
|
||||||
|
.inner()
|
||||||
|
.games_in_download
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.remove(id);
|
||||||
|
|
||||||
|
let games_folder = app_handle
|
||||||
|
.state::<LanSpreadState>()
|
||||||
|
.inner()
|
||||||
|
.games_folder
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
if games_folder.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let backup_name = format!("___TO_BE_DELETE___{id}");
|
||||||
|
let backup_path = PathBuf::from(&games_folder).join(&backup_name);
|
||||||
|
let game_path = PathBuf::from(&games_folder).join(id);
|
||||||
|
|
||||||
|
if game_path.exists() {
|
||||||
|
if let Err(e) = std::fs::remove_dir_all(&game_path) {
|
||||||
|
log::error!("Failed to delete half-downloaded game folder: {e}");
|
||||||
|
} else {
|
||||||
|
log::info!(
|
||||||
|
"Deleted half-downloaded game folder: {}",
|
||||||
|
game_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = restore_game_folder(&game_path, &backup_path) {
|
||||||
|
log::error!("Failed to restore backup after download failure: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn update_game(id: String, state: tauri::State<'_, LanSpreadState>) -> tauri::Result<bool> {
|
async fn update_game(id: String, state: tauri::State<'_, LanSpreadState>) -> tauri::Result<bool> {
|
||||||
let games_in_download = state.inner().games_in_download.clone();
|
let games_in_download = state.inner().games_in_download.clone();
|
||||||
@@ -850,43 +891,23 @@ pub fn run() {
|
|||||||
log::error!("Failed to emit game-download-failed event: {e}");
|
log::error!("Failed to emit game-download-failed event: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
app_handle
|
cleanup_failed_download(&app_handle, &id).await;
|
||||||
.state::<LanSpreadState>()
|
}
|
||||||
.inner()
|
PeerEvent::DownloadGameFilesAllPeersGone { id } => {
|
||||||
.games_in_download
|
log::warn!(
|
||||||
.write()
|
"PeerEvent::DownloadGameFilesAllPeersGone received for {id}"
|
||||||
.await
|
);
|
||||||
.remove(&id.clone());
|
|
||||||
|
|
||||||
// Check if a backup exists and restore from backup if failed
|
if let Err(e) = app_handle.emit(
|
||||||
// The backup folder name follows the pattern: ___TO_BE_DELETE___{game_name}
|
"game-download-peers-gone",
|
||||||
let games_folder = app_handle
|
Some(id.clone()),
|
||||||
.state::<LanSpreadState>()
|
) {
|
||||||
.inner()
|
log::error!(
|
||||||
.games_folder
|
"Failed to emit game-download-peers-gone event: {e}"
|
||||||
.read()
|
);
|
||||||
.await
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
if !games_folder.is_empty() {
|
|
||||||
let backup_name = format!("___TO_BE_DELETE___{id}");
|
|
||||||
let backup_path = PathBuf::from(&games_folder).join(backup_name);
|
|
||||||
let game_path = PathBuf::from(&games_folder).join(&id);
|
|
||||||
|
|
||||||
// Delete the half-downloaded game folder if it exists
|
|
||||||
if game_path.exists() {
|
|
||||||
if let Err(e) = std::fs::remove_dir_all(&game_path) {
|
|
||||||
log::error!("Failed to delete half-downloaded game folder: {e}");
|
|
||||||
} else {
|
|
||||||
log::info!("Deleted half-downloaded game folder: {}", game_path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the backup
|
|
||||||
if let Err(e) = restore_game_folder(&game_path, &backup_path) {
|
|
||||||
log::error!("Failed to restore backup after download failure: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanup_failed_download(&app_handle, &id).await;
|
||||||
}
|
}
|
||||||
PeerEvent::PeerConnected(addr) => {
|
PeerEvent::PeerConnected(addr) => {
|
||||||
log::info!("Peer connected: {addr}");
|
log::info!("Peer connected: {addr}");
|
||||||
|
|||||||
@@ -136,6 +136,31 @@ const App = () => {
|
|||||||
return unlisten;
|
return unlisten;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setupPeersGoneListener = async () => {
|
||||||
|
const unlisten = await listen('game-download-peers-gone', (event) => {
|
||||||
|
const game_id = event.payload as string;
|
||||||
|
console.log(`❌ game-download-peers-gone ${game_id} event received`);
|
||||||
|
clearCheckingPeersTimeout(game_id);
|
||||||
|
setGameItems(prev => prev.map(item => item.id === game_id
|
||||||
|
? {
|
||||||
|
...item,
|
||||||
|
install_status: item.installed ? InstallStatus.Installed : InstallStatus.NotInstalled,
|
||||||
|
status_message: 'Failed: All Peers gone',
|
||||||
|
status_level: 'error',
|
||||||
|
}
|
||||||
|
: item));
|
||||||
|
|
||||||
|
const pathString = String(gameDir);
|
||||||
|
if (!pathString) {
|
||||||
|
console.error('gameDir is empty before invoke!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invoke('update_game_directory', { path: pathString })
|
||||||
|
.catch(error => console.error('❌ Error updating game directory:', error));
|
||||||
|
});
|
||||||
|
return unlisten;
|
||||||
|
};
|
||||||
|
|
||||||
const setupNoPeersListener = async () => {
|
const setupNoPeersListener = async () => {
|
||||||
const unlisten = await listen('game-no-peers', (event) => {
|
const unlisten = await listen('game-no-peers', (event) => {
|
||||||
const game_id = event.payload as string;
|
const game_id = event.payload as string;
|
||||||
@@ -154,6 +179,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setupDownloadFailedListener();
|
setupDownloadFailedListener();
|
||||||
|
setupPeersGoneListener();
|
||||||
setupNoPeersListener();
|
setupNoPeersListener();
|
||||||
|
|
||||||
const setupPeerCountListener = async () => {
|
const setupPeerCountListener = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user