[backup] on the way

This commit is contained in:
ddidderr 2024-11-15 08:59:53 +01:00
parent 2b64d1e4ba
commit f9cd8471b4
Signed by: ddidderr
GPG Key ID: 3841F1C27E6F0E14
3 changed files with 142 additions and 22 deletions

View File

@ -22,6 +22,8 @@ static CERT_PEM: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../..
pub enum ClientEvent { pub enum ClientEvent {
ListGames(Vec<Game>), ListGames(Vec<Game>),
GotGameFiles(Vec<GameFileDescription>), GotGameFiles(Vec<GameFileDescription>),
DownloadGameFilesBegin { id: String },
DownloadGameFilesFinished { id: String },
} }
#[derive(Debug)] #[derive(Debug)]
@ -69,6 +71,7 @@ async fn download_game_files(
game_file_descs: Vec<GameFileDescription>, game_file_descs: Vec<GameFileDescription>,
games_dir: String, games_dir: String,
server_addr: SocketAddr, server_addr: SocketAddr,
tx_notify_ui: UnboundedSender<ClientEvent>,
) -> eyre::Result<()> { ) -> eyre::Result<()> {
let limits = Limits::default() let limits = Limits::default()
.with_max_handshake_duration(Duration::from_secs(3))? .with_max_handshake_duration(Duration::from_secs(3))?
@ -89,6 +92,21 @@ async fn download_game_files(
.filter(|desc| !desc.is_dir) .filter(|desc| !desc.is_dir)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if game_file_descs.is_empty() {
log::error!("game_file_descs empty: no game files to download");
return Ok(());
}
let game_id = game_file_descs
.first()
.expect("game_file_descs empty: 2nd case CANNOT HAPPEN")
.game_id
.clone();
tx_notify_ui.send(ClientEvent::DownloadGameFilesBegin {
id: game_id.clone(),
})?;
for file_desc in game_file_descs { for file_desc in game_file_descs {
log::info!("downloading file: {}", file_desc.relative_path); log::info!("downloading file: {}", file_desc.relative_path);
@ -125,6 +143,8 @@ async fn download_game_files(
} }
} }
log::info!("all files downloaded for game: {game_id}");
tx_notify_ui.send(ClientEvent::DownloadGameFilesFinished { id: game_id })?;
Ok(()) Ok(())
} }
@ -205,9 +225,15 @@ pub async fn run(
let games_dir = { ctx.game_dir.lock().await.clone() }; let games_dir = { ctx.game_dir.lock().await.clone() };
if let Some(games_dir) = games_dir { if let Some(games_dir) = games_dir {
let tx_notify_ui = tx_notify_ui.clone();
tokio::task::spawn(async move { tokio::task::spawn(async move {
if let Err(e) = if let Err(e) = download_game_files(
download_game_files(game_file_descs, games_dir, server_addr).await game_file_descs,
games_dir,
server_addr,
tx_notify_ui,
)
.await
{ {
log::error!("failed to download game files: {e}"); log::error!("failed to download game files: {e}");
} }

View File

@ -1,4 +1,5 @@
use std::{ use std::{
collections::HashSet,
net::SocketAddr, net::SocketAddr,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
@ -16,6 +17,7 @@ struct LanSpreadState {
server_addr: Mutex<Option<SocketAddr>>, server_addr: Mutex<Option<SocketAddr>>,
client_ctrl: UnboundedSender<ClientCommand>, client_ctrl: UnboundedSender<ClientCommand>,
games: Arc<Mutex<GameDB>>, games: Arc<Mutex<GameDB>>,
games_in_download: Arc<Mutex<HashSet<String>>>,
} }
#[tauri::command] #[tauri::command]
@ -28,22 +30,26 @@ fn request_games(state: tauri::State<LanSpreadState>) {
} }
#[tauri::command] #[tauri::command]
fn install_game(id: String, state: tauri::State<LanSpreadState>) -> String { fn install_game(id: String, state: tauri::State<LanSpreadState>) -> bool {
log::error!("Running game with id {id}"); log::error!("Running game with id {id}");
// let result = Command::new(r#"C:\Users\ddidderr\scoop\apps\mpv\0.39.0\mpv.exe"#).spawn(); let already_in_download = tauri::async_runtime::block_on(async {
if state.inner().games_in_download.lock().await.contains(&id) {
log::error!("Game is already downloading: {id}");
return true;
}
false
});
if already_in_download {
return false;
}
if let Err(e) = state.inner().client_ctrl.send(ClientCommand::GetGame(id)) { if let Err(e) = state.inner().client_ctrl.send(ClientCommand::GetGame(id)) {
log::error!("Failed to send message to client: {e:?}"); log::error!("Failed to send message to client: {e:?}");
} }
"TODO".to_string() true
// if result.is_ok() {
// "Ok".to_string()
// } else {
// "Failed to run game".to_string()
// }
} }
fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) { fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) {
@ -183,6 +189,7 @@ pub fn run() {
server_addr: Mutex::new(None), server_addr: Mutex::new(None),
client_ctrl: tx_client_control, client_ctrl: tx_client_control,
games: Arc::new(Mutex::new(GameDB::empty())), games: Arc::new(Mutex::new(GameDB::empty())),
games_in_download: Arc::new(Mutex::new(HashSet::new())),
}; };
tauri::Builder::default() tauri::Builder::default()
@ -209,13 +216,18 @@ pub fn run() {
while let Some(event) = rx_client_event.recv().await { while let Some(event) = rx_client_event.recv().await {
match event { match event {
ClientEvent::ListGames(games) => { ClientEvent::ListGames(games) => {
log::debug!("Received client event: ListGames"); log::info!("ClientEvent::ListGames received");
update_game_db(games, app_handle.clone()).await; update_game_db(games, app_handle.clone()).await;
} }
ClientEvent::GotGameFiles(game_file_descs) => { ClientEvent::GotGameFiles(game_file_descs) => {
log::debug!("Received client event: GotGameFiles"); log::info!("ClientEvent::GotGameFiles received");
if let Err(e) = app_handle.emit("game-download-in-progress", Some(())) {
log::error!("Failed to emit game-files event: {e}"); if let Some(first_desc_file) = game_file_descs.first() {
if let Err(e) = app_handle.emit(
"game-download-pre",
Some(first_desc_file.game_id.clone()),
) {
log::error!("ClientEvent::GotGameFiles: Failed to emit game-download-pre event: {e}");
} }
app_handle app_handle
@ -224,6 +236,30 @@ pub fn run() {
.client_ctrl .client_ctrl
.send(ClientCommand::DownloadGameFiles(game_file_descs)) .send(ClientCommand::DownloadGameFiles(game_file_descs))
.unwrap(); .unwrap();
} else {
log::error!("ClientEvent::GotGameFiles: Got empty game files list");
}
}
ClientEvent::DownloadGameFilesBegin { id } => {
log::info!("ClientEvent::DownloadGameFilesBegin received");
app_handle
.state::<LanSpreadState>()
.inner()
.games_in_download
.lock()
.await
.insert(id.clone());
if let Err(e) = app_handle.emit("game-download-begin", Some(id)) {
log::error!("ClientEvent::DownloadGameFilesBegin: Failed to emit game-download-begin event: {e}");
}
}
ClientEvent::DownloadGameFilesFinished { id } => {
log::info!("ClientEvent::DownloadGameFilesFinished received");
if let Err(e) = app_handle.emit("game-download-finished", Some(id)) {
log::error!("ClientEvent::DownloadGameFilesFinished: Failed to emit game-download-finished event: {e}");
}
} }
} }
} }

View File

@ -9,6 +9,15 @@ import "./App.css";
const FILE_STORAGE = 'launcher-settings.json'; const FILE_STORAGE = 'launcher-settings.json';
const GAME_DIR_KEY = 'game-directory'; const GAME_DIR_KEY = 'game-directory';
// enum with install status
enum InstallStatus {
NotInstalled = 'NotInstalled',
CheckingServer = 'CheckingServer',
Downloading = 'Downloading',
Unpacking = 'Unpacking',
Installed = 'Installed',
}
interface Game { interface Game {
id: string; id: string;
name: string; name: string;
@ -16,6 +25,7 @@ interface Game {
size: number; size: number;
thumbnail: Uint8Array; thumbnail: Uint8Array;
installed: boolean; installed: boolean;
install_status: InstallStatus;
} }
const App = () => { const App = () => {
@ -64,7 +74,7 @@ const App = () => {
const setupEventListener = async () => { const setupEventListener = async () => {
try { try {
// Listen for events that update the game list // Listen for games-list-updated events
const unlisten_games = await listen('games-list-updated', (event) => { const unlisten_games = await listen('games-list-updated', (event) => {
console.log('🗲 Received games-list-updated event'); console.log('🗲 Received games-list-updated event');
const games = event.payload as Game[]; const games = event.payload as Game[];
@ -73,6 +83,24 @@ const App = () => {
getInitialGameDir(); getInitialGameDir();
}); });
// Listen for game-download-begin events
const unlisten_game_download_begin = await listen('game-download-begin', (event) => {
const game_id = event.payload as string;
console.log(`🗲 game-download-begin ${game_id} event received`);
setGameItems(prev => prev.map(item => item.id === game_id
? {...item, install_status: InstallStatus.Downloading}
: item));
});
// Listen for game-download-finished events
const unlisten_game_download_finished = await listen('game-download-finished', (event) => {
const game_id = event.payload as string;
console.log(`🗲 game-download-finished ${game_id} event received`);
setGameItems(prev => prev.map(item => item.id === game_id
? {...item, install_status: InstallStatus.Unpacking}
: item));
});
// Initial request for games // Initial request for games
console.log('📤 Requesting initial games list'); console.log('📤 Requesting initial games list');
await invoke('request_games'); await invoke('request_games');
@ -81,6 +109,8 @@ const App = () => {
return () => { return () => {
console.log('🧹 Cleaning up - removing listener'); console.log('🧹 Cleaning up - removing listener');
unlisten_games(); unlisten_games();
unlisten_game_download_begin();
unlisten_game_download_finished();
}; };
} catch (error) { } catch (error) {
console.error('❌ Error in setup:', error); console.error('❌ Error in setup:', error);
@ -98,13 +128,32 @@ const App = () => {
const runGame = async (id: string) => { const runGame = async (id: string) => {
console.log(`🎯 Running game with id=${id}`); console.log(`🎯 Running game with id=${id}`);
try { try {
const result = await invoke('install_game', {id}); const result = await invoke('run_game', {id});
console.log(`✅ Game started, result=${result}`); console.log(`✅ Game started, result=${result}`);
} catch (error) { } catch (error) {
console.error('❌ Error running game:', error); console.error('❌ Error running game:', error);
} }
}; };
const installGame = async (id: string) => {
console.log(`🎯 Installing game with id=${id}`);
try {
const success = await invoke('install_game', {id});
if (success) {
console.log(`✅ Game install for id=${id} started...`);
// update install status in gameItems for this game
setGameItems(prev => prev.map(item => item.id === id
? {...item, install_status: InstallStatus.CheckingServer}
: item));
} else {
// game is already being installed
console.warn(`🚧 Game with id=${id} is already being installed`);
}
} catch (error) {
console.error('❌ Error installing game:', error);
}
};
const dialogGameDir = async () => { const dialogGameDir = async () => {
const file = await open({ const file = await open({
multiple: false, multiple: false,
@ -146,14 +195,23 @@ const App = () => {
const binaryString = uint8Array.reduce((acc, byte) => acc + String.fromCharCode(byte), ''); const binaryString = uint8Array.reduce((acc, byte) => acc + String.fromCharCode(byte), '');
const thumbnailUrl = `data:image/jpeg;base64,${btoa(binaryString)}`; const thumbnailUrl = `data:image/jpeg;base64,${btoa(binaryString)}`;
return ( return (
<div key={item.id} className="item" onClick={() => runGame(item.id)}> <div key={item.id} className="item">
<img src={thumbnailUrl} alt={`${item.name} thumbnail`} /> <img src={thumbnailUrl} alt={`${item.name} thumbnail`} />
<div className="item-name">{item.name}</div> <div className="item-name">{item.name}</div>
<div className="description"> <div className="description">
<span className="desc-text">{item.description.slice(0, 10)}</span> <span className="desc-text">{item.description.slice(0, 10)}</span>
<span className="size-text">{item.size.toString()}</span> <span className="size-text">{item.size.toString()}</span>
</div> </div>
<div className="play-button">{item.installed ? 'Play' : 'Install'}</div> <div className="play-button"
onClick={() => item.installed
? runGame(item.id)
: installGame(item.id)}>
{item.installed ? 'Play'
: item.install_status === InstallStatus.CheckingServer ? 'Checking server...'
: item.install_status === InstallStatus.Downloading ? 'Downloading...'
: item.install_status === InstallStatus.Unpacking ? 'Unpacking...'
: 'Install'}
</div>
</div> </div>
); );
})} })}