[client] unpack game works!
This commit is contained in:
parent
f9cd8471b4
commit
bc70d6300b
@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
use std::{fs::File, io::Write as _, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
|
use std::{fs::File, io::Write as _, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::BytesMut;
|
||||||
use lanspread_db::db::{Game, GameFileDescription};
|
use lanspread_db::db::{Game, GameFileDescription};
|
||||||
use lanspread_proto::{Message as _, Request, Response};
|
use lanspread_proto::{Message as _, Request, Response};
|
||||||
use lanspread_utils::maybe_addr;
|
use lanspread_utils::maybe_addr;
|
||||||
use s2n_quic::{client::Connect, provider::limits::Limits, Client as QuicClient, Connection};
|
use s2n_quic::{client::Connect, provider::limits::Limits, Client as QuicClient, Connection};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::AsyncWriteExt,
|
io::AsyncWriteExt,
|
||||||
stream,
|
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{UnboundedReceiver, UnboundedSender},
|
mpsc::{UnboundedReceiver, UnboundedSender},
|
||||||
Mutex,
|
Mutex,
|
||||||
|
Binary file not shown.
BIN
crates/lanspread-tauri-deno-ts/src-tauri/binaries/unrar-x86_64-unknown-linux-gnu
Executable file
BIN
crates/lanspread-tauri-deno-ts/src-tauri/binaries/unrar-x86_64-unknown-linux-gnu
Executable file
Binary file not shown.
@ -1,14 +1,17 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
fs::File,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use eyre::bail;
|
||||||
use lanspread_client::{ClientCommand, ClientEvent};
|
use lanspread_client::{ClientCommand, ClientEvent};
|
||||||
use lanspread_db::db::{Game, GameDB};
|
use lanspread_db::db::{Game, GameDB};
|
||||||
use lanspread_mdns::{discover_service, LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE};
|
use lanspread_mdns::{discover_service, LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE};
|
||||||
use tauri::{AppHandle, Emitter as _, Manager};
|
use tauri::{AppHandle, Emitter as _, Manager};
|
||||||
|
use tauri_plugin_shell::{process::Command, ShellExt};
|
||||||
use tokio::sync::{mpsc::UnboundedSender, Mutex};
|
use tokio::sync::{mpsc::UnboundedSender, Mutex};
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
@ -18,6 +21,7 @@ struct LanSpreadState {
|
|||||||
client_ctrl: UnboundedSender<ClientCommand>,
|
client_ctrl: UnboundedSender<ClientCommand>,
|
||||||
games: Arc<Mutex<GameDB>>,
|
games: Arc<Mutex<GameDB>>,
|
||||||
games_in_download: Arc<Mutex<HashSet<String>>>,
|
games_in_download: Arc<Mutex<HashSet<String>>>,
|
||||||
|
games_dir: Arc<Mutex<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -57,7 +61,7 @@ fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed
|
|||||||
if let Some(file_name) = file_name.to_str() {
|
if let Some(file_name) = file_name.to_str() {
|
||||||
if let Some(game) = game_db.get_mut_game_by_id(file_name) {
|
if let Some(game) = game_db.get_mut_game_by_id(file_name) {
|
||||||
if installed {
|
if installed {
|
||||||
log::error!("Game is installed: {game}");
|
log::info!("Game is installed: {game}");
|
||||||
} else {
|
} else {
|
||||||
log::error!("Game is missing: {game}");
|
log::error!("Game is missing: {game}");
|
||||||
}
|
}
|
||||||
@ -69,12 +73,27 @@ fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
|
fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
|
||||||
|
log::info!("update_game_directory: {path}");
|
||||||
|
|
||||||
app_handle
|
app_handle
|
||||||
.state::<LanSpreadState>()
|
.state::<LanSpreadState>()
|
||||||
.client_ctrl
|
.client_ctrl
|
||||||
.send(ClientCommand::SetGameDir(path.clone()))
|
.send(ClientCommand::SetGameDir(path.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
tauri::async_runtime::block_on(async {
|
||||||
|
let mut games_dir = app_handle
|
||||||
|
.state::<LanSpreadState>()
|
||||||
|
.inner()
|
||||||
|
.games_dir
|
||||||
|
.lock()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
*games_dir = path.clone();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
log::error!("game dir {path:?} does not exist");
|
log::error!("game dir {path:?} does not exist");
|
||||||
@ -165,6 +184,70 @@ async fn update_game_db(games: Vec<Game>, app: AppHandle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_final_slash(path: &str) -> String {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
const SLASH_CHAR: char = '\\';
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
const SLASH_CHAR: char = '/';
|
||||||
|
|
||||||
|
if path.ends_with(SLASH_CHAR) {
|
||||||
|
path.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{path}{SLASH_CHAR}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_unrar(sidecar: Command, rar_file: &Path, dest_dir: &Path) -> eyre::Result<()> {
|
||||||
|
if let Ok(()) = std::fs::create_dir_all(dest_dir) {
|
||||||
|
if let Ok(rar_file) = rar_file.canonicalize() {
|
||||||
|
if let Ok(dest_dir) = dest_dir.canonicalize() {
|
||||||
|
let dest_dir = dest_dir
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| eyre::eyre!("failed to get str of dest_dir"))?;
|
||||||
|
|
||||||
|
log::error!("SIDECARE: {:?}", &sidecar);
|
||||||
|
|
||||||
|
sidecar
|
||||||
|
.arg("x") // extract files
|
||||||
|
.arg(rar_file.canonicalize()?)
|
||||||
|
.arg("-y") // Assume Yes on all queries
|
||||||
|
.arg("-o") // Set overwrite mode
|
||||||
|
.arg(add_final_slash(dest_dir))
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
log::error!("dest_dir canonicalize failed: {:?}", &dest_dir);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::error!("rar_file canonicalize failed: {:?}", &rar_file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::error!("failed to create dest_dir: {:?}", &dest_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("failed to create directory: {dest_dir:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unpack_game(id: &str, sidecar: Command, games_dir: String) {
|
||||||
|
let game_path = PathBuf::from(games_dir).join(id);
|
||||||
|
let eti_rar = game_path.join(format!("{id}.eti"));
|
||||||
|
let local_path = game_path.join("local");
|
||||||
|
|
||||||
|
if let Err(e) = do_unrar(sidecar, &eti_rar, &local_path).await {
|
||||||
|
log::error!("{eti_rar:?} -> {local_path:?}: {e}");
|
||||||
|
} else {
|
||||||
|
let game_installed_file = game_path.join(".softlan_game_installed");
|
||||||
|
if let Err(e) = File::create(game_installed_file) {
|
||||||
|
log::error!("failed to create game_installed_file: {e}");
|
||||||
|
} else {
|
||||||
|
log::info!("game unpacked: {id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let tauri_logger_builder = tauri_plugin_log::Builder::new()
|
let tauri_logger_builder = tauri_plugin_log::Builder::new()
|
||||||
@ -190,6 +273,7 @@ pub fn run() {
|
|||||||
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())),
|
games_in_download: Arc::new(Mutex::new(HashSet::new())),
|
||||||
|
games_dir: Arc::new(Mutex::new("".to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
@ -257,9 +341,35 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
ClientEvent::DownloadGameFilesFinished { id } => {
|
ClientEvent::DownloadGameFilesFinished { id } => {
|
||||||
log::info!("ClientEvent::DownloadGameFilesFinished received");
|
log::info!("ClientEvent::DownloadGameFilesFinished received");
|
||||||
if let Err(e) = app_handle.emit("game-download-finished", Some(id)) {
|
if let Err(e) = app_handle.emit("game-download-finished", Some(id.clone())) {
|
||||||
log::error!("ClientEvent::DownloadGameFilesFinished: Failed to emit game-download-finished event: {e}");
|
log::error!("ClientEvent::DownloadGameFilesFinished: Failed to emit game-download-finished event: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app_handle
|
||||||
|
.state::<LanSpreadState>()
|
||||||
|
.inner()
|
||||||
|
.games_in_download
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.remove(&id.clone());
|
||||||
|
|
||||||
|
|
||||||
|
let games_dir = app_handle
|
||||||
|
.state::<LanSpreadState>()
|
||||||
|
.inner()
|
||||||
|
.games_dir
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
if let Ok(sidecar) = app_handle.shell().sidecar("unrar") {
|
||||||
|
unpack_game(&id, sidecar, games_dir).await;
|
||||||
|
|
||||||
|
log::info!("ClientEvent::UnpackGameFinished received");
|
||||||
|
if let Err(e) = app_handle.emit("game-unpack-finished", Some(id.clone())) {
|
||||||
|
log::error!("ClientEvent::UnpackGameFinished: Failed to emit game-unpack-finished event: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,9 @@
|
|||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"externalBin": [
|
||||||
|
"binaries/unrar"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,32 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Listen for game-unpack-finished events specifically
|
||||||
|
const setupUnpackListener = async () => {
|
||||||
|
const unlisten = await listen('game-unpack-finished', (event) => {
|
||||||
|
const game_id = event.payload as string;
|
||||||
|
console.log(`🗲 game-unpack-finished ${game_id} event received`);
|
||||||
|
console.log('Current gameDir in listener:', gameDir); // Add this log
|
||||||
|
setGameItems(prev => prev.map(item => item.id === game_id
|
||||||
|
? {...item, install_status: InstallStatus.Installed}
|
||||||
|
: item));
|
||||||
|
|
||||||
|
// Convert to string explicitly and verify it's not empty
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
setupUnpackListener();
|
||||||
|
}, [gameDir]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gameDir) {
|
if (gameDir) {
|
||||||
// store game directory in persistent storage
|
// store game directory in persistent storage
|
||||||
|
Loading…
x
Reference in New Issue
Block a user