[backup] on the way
This commit is contained in:
parent
2b64d1e4ba
commit
f9cd8471b4
@ -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}");
|
||||||
}
|
}
|
||||||
|
@ -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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user