mirror of
https://github.com/Novattz/creamlinux-installer.git
synced 2025-12-05 19:45:36 -05:00
Initial changes
This commit is contained in:
@@ -1,178 +1,21 @@
|
||||
use crate::dlc_manager::DlcInfoWithState;
|
||||
use log::{info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
// This is a placeholder file - cache functionality has been removed
|
||||
// and now only exists in memory within the App state
|
||||
|
||||
// Cache entry with timestamp for expiration
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CacheEntry<T> {
|
||||
data: T,
|
||||
timestamp: u64, // Unix timestamp in seconds
|
||||
pub fn cache_dlcs(_game_id: &str, _dlcs: &[crate::dlc_manager::DlcInfoWithState]) -> std::io::Result<()> {
|
||||
// This function is kept only for compatibility, but now does nothing
|
||||
// The DLCs are only cached in memory
|
||||
log::info!("Cache functionality has been removed - DLCs are only stored in memory");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the cache directory
|
||||
fn get_cache_dir() -> io::Result<PathBuf> {
|
||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("creamlinux")
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
let cache_dir = xdg_dirs.get_cache_home();
|
||||
|
||||
// Make sure the cache directory exists
|
||||
if !cache_dir.exists() {
|
||||
fs::create_dir_all(&cache_dir)?;
|
||||
}
|
||||
|
||||
Ok(cache_dir)
|
||||
pub fn load_cached_dlcs(_game_id: &str) -> Option<Vec<crate::dlc_manager::DlcInfoWithState>> {
|
||||
// This function is kept only for compatibility, but now always returns None
|
||||
log::info!("Cache functionality has been removed - DLCs are only stored in memory");
|
||||
None
|
||||
}
|
||||
|
||||
// Save data to cache file
|
||||
pub fn save_to_cache<T>(key: &str, data: &T, _ttl_hours: u64) -> io::Result<()>
|
||||
where
|
||||
T: Serialize + ?Sized,
|
||||
{
|
||||
let cache_dir = get_cache_dir()?;
|
||||
let cache_file = cache_dir.join(format!("{}.cache", key));
|
||||
|
||||
// Get current timestamp
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
|
||||
// Create a JSON object with timestamp and data directly
|
||||
let json_data = json!({
|
||||
"timestamp": now,
|
||||
"data": data // No clone needed here
|
||||
});
|
||||
|
||||
// Serialize and write to file
|
||||
let serialized =
|
||||
serde_json::to_string(&json_data).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
fs::write(cache_file, serialized)?;
|
||||
info!("Saved cache for key: {}", key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Load data from cache file if it exists and is not expired
|
||||
pub fn load_from_cache<T>(key: &str, ttl_hours: u64) -> Option<T>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let cache_dir = match get_cache_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
warn!("Failed to get cache directory: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let cache_file = cache_dir.join(format!("{}.cache", key));
|
||||
|
||||
// Check if cache file exists
|
||||
if !cache_file.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Read and deserialize
|
||||
let cached_data = match fs::read_to_string(&cache_file) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
warn!("Failed to read cache file {}: {}", cache_file.display(), e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Parse the JSON
|
||||
let json_value: serde_json::Value = match serde_json::from_str(&cached_data) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!("Failed to parse cache file {}: {}", cache_file.display(), e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Extract timestamp
|
||||
let timestamp = match json_value.get("timestamp").and_then(|v| v.as_u64()) {
|
||||
Some(ts) => ts,
|
||||
None => {
|
||||
warn!("Invalid timestamp in cache file {}", cache_file.display());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Check expiration
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
|
||||
let age_hours = (now - timestamp) / 3600;
|
||||
|
||||
if age_hours > ttl_hours {
|
||||
info!("Cache for key {} is expired ({} hours old)", key, age_hours);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Extract data
|
||||
let data: T = match serde_json::from_value(json_value["data"].clone()) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to parse data in cache file {}: {}",
|
||||
cache_file.display(),
|
||||
e
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
info!("Using cache for key {} ({} hours old)", key, age_hours);
|
||||
Some(data)
|
||||
}
|
||||
|
||||
// Cache game scanning results
|
||||
pub fn cache_games(games: &[crate::installer::Game]) -> io::Result<()> {
|
||||
save_to_cache("games", games, 24) // Cache games for 24 hours
|
||||
}
|
||||
|
||||
// Load cached game scanning results
|
||||
pub fn load_cached_games() -> Option<Vec<crate::installer::Game>> {
|
||||
load_from_cache("games", 24)
|
||||
}
|
||||
|
||||
// Cache DLC list for a game
|
||||
pub fn cache_dlcs(game_id: &str, dlcs: &[DlcInfoWithState]) -> io::Result<()> {
|
||||
save_to_cache(&format!("dlc_{}", game_id), dlcs, 168) // Cache DLCs for 7 days (168 hours)
|
||||
}
|
||||
|
||||
// Load cached DLC list
|
||||
pub fn load_cached_dlcs(game_id: &str) -> Option<Vec<DlcInfoWithState>> {
|
||||
load_from_cache(&format!("dlc_{}", game_id), 168)
|
||||
}
|
||||
|
||||
// Clear all caches
|
||||
pub fn clear_all_caches() -> io::Result<()> {
|
||||
let cache_dir = get_cache_dir()?;
|
||||
|
||||
for entry in fs::read_dir(cache_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_file() && path.extension().map_or(false, |ext| ext == "cache") {
|
||||
if let Err(e) = fs::remove_file(&path) {
|
||||
warn!("Failed to remove cache file {}: {}", path.display(), e);
|
||||
} else {
|
||||
info!("Removed cache file: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("All caches cleared");
|
||||
Ok(())
|
||||
}
|
||||
pub fn clear_all_caches() -> std::io::Result<()> {
|
||||
// This function is kept only for compatibility, but now does nothing
|
||||
log::info!("Cache functionality has been removed - DLCs are only stored in memory");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
mod cache;
|
||||
mod dlc_manager;
|
||||
mod installer;
|
||||
mod searcher; // Keep the module for now
|
||||
mod searcher;
|
||||
|
||||
use dlc_manager::DlcInfoWithState;
|
||||
use installer::{Game, InstallerAction, InstallerType};
|
||||
@@ -19,529 +18,531 @@ use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use tauri::State;
|
||||
use tauri::{Emitter, Manager};
|
||||
use tokio::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GameAction {
|
||||
game_id: String,
|
||||
action: String,
|
||||
game_id: String,
|
||||
action: String,
|
||||
}
|
||||
|
||||
// Mark fields with # to allow unused fields
|
||||
#[derive(Debug, Clone)]
|
||||
struct DlcCache {
|
||||
data: Vec<DlcInfoWithState>,
|
||||
timestamp: Instant,
|
||||
#[allow(dead_code)]
|
||||
data: Vec<DlcInfoWithState>,
|
||||
#[allow(dead_code)]
|
||||
timestamp: Instant,
|
||||
}
|
||||
|
||||
// Structure to hold the state of installed games
|
||||
struct AppState {
|
||||
games: Mutex<HashMap<String, Game>>,
|
||||
dlc_cache: Mutex<HashMap<String, DlcCache>>,
|
||||
fetch_cancellation: Arc<AtomicBool>,
|
||||
games: Mutex<HashMap<String, Game>>,
|
||||
dlc_cache: Mutex<HashMap<String, DlcCache>>,
|
||||
fetch_cancellation: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_all_dlcs_command(game_path: String) -> Result<Vec<DlcInfoWithState>, String> {
|
||||
info!("Getting all DLCs (enabled and disabled) for: {}", game_path);
|
||||
dlc_manager::get_all_dlcs(&game_path)
|
||||
info!("Getting all DLCs (enabled and disabled) for: {}", game_path);
|
||||
dlc_manager::get_all_dlcs(&game_path)
|
||||
}
|
||||
|
||||
// Scan and get the list of Steam games
|
||||
#[tauri::command]
|
||||
async fn scan_steam_games(
|
||||
state: State<'_, AppState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<Vec<Game>, String> {
|
||||
info!("Starting Steam games scan");
|
||||
emit_scan_progress(&app_handle, "Locating Steam libraries...", 10);
|
||||
info!("Starting Steam games scan");
|
||||
emit_scan_progress(&app_handle, "Locating Steam libraries...", 10);
|
||||
|
||||
// Get default Steam paths
|
||||
let paths = searcher::get_default_steam_paths();
|
||||
// Get default Steam paths
|
||||
let paths = searcher::get_default_steam_paths();
|
||||
|
||||
// Find Steam libraries
|
||||
emit_scan_progress(&app_handle, "Finding Steam libraries...", 15);
|
||||
let libraries = searcher::find_steam_libraries(&paths);
|
||||
// Find Steam libraries
|
||||
emit_scan_progress(&app_handle, "Finding Steam libraries...", 15);
|
||||
let libraries = searcher::find_steam_libraries(&paths);
|
||||
|
||||
// Group libraries by path to avoid duplicates in logs
|
||||
let mut unique_libraries = std::collections::HashSet::new();
|
||||
for lib in &libraries {
|
||||
unique_libraries.insert(lib.to_string_lossy().to_string());
|
||||
}
|
||||
// Group libraries by path to avoid duplicates in logs
|
||||
let mut unique_libraries = std::collections::HashSet::new();
|
||||
for lib in &libraries {
|
||||
unique_libraries.insert(lib.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
info!(
|
||||
"Found {} Steam library directories:",
|
||||
unique_libraries.len()
|
||||
);
|
||||
for (i, lib) in unique_libraries.iter().enumerate() {
|
||||
info!(" Library {}: {}", i + 1, lib);
|
||||
}
|
||||
info!(
|
||||
"Found {} Steam library directories:",
|
||||
unique_libraries.len()
|
||||
);
|
||||
for (i, lib) in unique_libraries.iter().enumerate() {
|
||||
info!(" Library {}: {}", i + 1, lib);
|
||||
}
|
||||
|
||||
emit_scan_progress(
|
||||
&app_handle,
|
||||
&format!(
|
||||
"Found {} Steam libraries. Starting game scan...",
|
||||
unique_libraries.len()
|
||||
),
|
||||
20,
|
||||
);
|
||||
emit_scan_progress(
|
||||
&app_handle,
|
||||
&format!(
|
||||
"Found {} Steam libraries. Starting game scan...",
|
||||
unique_libraries.len()
|
||||
),
|
||||
20,
|
||||
);
|
||||
|
||||
// Find installed games
|
||||
let games_info = searcher::find_installed_games(&libraries).await;
|
||||
// Find installed games
|
||||
let games_info = searcher::find_installed_games(&libraries).await;
|
||||
|
||||
emit_scan_progress(
|
||||
&app_handle,
|
||||
&format!("Found {} games. Processing...", games_info.len()),
|
||||
90,
|
||||
);
|
||||
emit_scan_progress(
|
||||
&app_handle,
|
||||
&format!("Found {} games. Processing...", games_info.len()),
|
||||
90,
|
||||
);
|
||||
|
||||
// Log summary of games found
|
||||
info!("Games scan complete - Found {} games", games_info.len());
|
||||
info!(
|
||||
"Native games: {}",
|
||||
games_info.iter().filter(|g| g.native).count()
|
||||
);
|
||||
info!(
|
||||
"Proton games: {}",
|
||||
games_info.iter().filter(|g| !g.native).count()
|
||||
);
|
||||
info!(
|
||||
"Games with CreamLinux: {}",
|
||||
games_info.iter().filter(|g| g.cream_installed).count()
|
||||
);
|
||||
info!(
|
||||
"Games with SmokeAPI: {}",
|
||||
games_info.iter().filter(|g| g.smoke_installed).count()
|
||||
);
|
||||
// Log summary of games found
|
||||
info!("Games scan complete - Found {} games", games_info.len());
|
||||
info!(
|
||||
"Native games: {}",
|
||||
games_info.iter().filter(|g| g.native).count()
|
||||
);
|
||||
info!(
|
||||
"Proton games: {}",
|
||||
games_info.iter().filter(|g| !g.native).count()
|
||||
);
|
||||
info!(
|
||||
"Games with CreamLinux: {}",
|
||||
games_info.iter().filter(|g| g.cream_installed).count()
|
||||
);
|
||||
info!(
|
||||
"Games with SmokeAPI: {}",
|
||||
games_info.iter().filter(|g| g.smoke_installed).count()
|
||||
);
|
||||
|
||||
// Convert to our Game struct
|
||||
let mut result = Vec::new();
|
||||
// Convert to our Game struct
|
||||
let mut result = Vec::new();
|
||||
|
||||
info!("Processing games into application state...");
|
||||
for game_info in games_info {
|
||||
// Only log detailed game info at Debug level to keep Info logs cleaner
|
||||
debug!(
|
||||
"Processing game: {}, Native: {}, CreamLinux: {}, SmokeAPI: {}",
|
||||
game_info.title, game_info.native, game_info.cream_installed, game_info.smoke_installed
|
||||
);
|
||||
info!("Processing games into application state...");
|
||||
for game_info in games_info {
|
||||
// Only log detailed game info at Debug level to keep Info logs cleaner
|
||||
debug!(
|
||||
"Processing game: {}, Native: {}, CreamLinux: {}, SmokeAPI: {}",
|
||||
game_info.title, game_info.native, game_info.cream_installed, game_info.smoke_installed
|
||||
);
|
||||
|
||||
let game = Game {
|
||||
id: game_info.id,
|
||||
title: game_info.title,
|
||||
path: game_info.path.to_string_lossy().to_string(),
|
||||
native: game_info.native,
|
||||
api_files: game_info.api_files,
|
||||
cream_installed: game_info.cream_installed,
|
||||
smoke_installed: game_info.smoke_installed,
|
||||
installing: false,
|
||||
};
|
||||
let game = Game {
|
||||
id: game_info.id,
|
||||
title: game_info.title,
|
||||
path: game_info.path.to_string_lossy().to_string(),
|
||||
native: game_info.native,
|
||||
api_files: game_info.api_files,
|
||||
cream_installed: game_info.cream_installed,
|
||||
smoke_installed: game_info.smoke_installed,
|
||||
installing: false,
|
||||
};
|
||||
|
||||
result.push(game.clone());
|
||||
result.push(game.clone());
|
||||
|
||||
// Store in state for later use
|
||||
state.games.lock().insert(game.id.clone(), game);
|
||||
}
|
||||
// Store in state for later use
|
||||
state.games.lock().insert(game.id.clone(), game);
|
||||
}
|
||||
|
||||
emit_scan_progress(
|
||||
&app_handle,
|
||||
&format!("Scan complete. Found {} games.", result.len()),
|
||||
100,
|
||||
);
|
||||
emit_scan_progress(
|
||||
&app_handle,
|
||||
&format!("Scan complete. Found {} games.", result.len()),
|
||||
100,
|
||||
);
|
||||
|
||||
info!("Game scan completed successfully");
|
||||
Ok(result)
|
||||
info!("Game scan completed successfully");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Helper function to emit scan progress events
|
||||
fn emit_scan_progress(app_handle: &tauri::AppHandle, message: &str, progress: u32) {
|
||||
// Log first, then emit the event
|
||||
info!("Scan progress: {}% - {}", progress, message);
|
||||
// Log first, then emit the event
|
||||
info!("Scan progress: {}% - {}", progress, message);
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"message": message,
|
||||
"progress": progress
|
||||
});
|
||||
let payload = serde_json::json!({
|
||||
"message": message,
|
||||
"progress": progress
|
||||
});
|
||||
|
||||
if let Err(e) = app_handle.emit("scan-progress", payload) {
|
||||
warn!("Failed to emit scan-progress event: {}", e);
|
||||
}
|
||||
if let Err(e) = app_handle.emit("scan-progress", payload) {
|
||||
warn!("Failed to emit scan-progress event: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch game info by ID - useful for single game updates
|
||||
#[tauri::command]
|
||||
fn get_game_info(game_id: String, state: State<AppState>) -> Result<Game, String> {
|
||||
let games = state.games.lock();
|
||||
games
|
||||
.get(&game_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("Game with ID {} not found", game_id))
|
||||
let games = state.games.lock();
|
||||
games
|
||||
.get(&game_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("Game with ID {} not found", game_id))
|
||||
}
|
||||
|
||||
// Unified action handler for installation and uninstallation
|
||||
#[tauri::command]
|
||||
async fn process_game_action(
|
||||
game_action: GameAction,
|
||||
state: State<'_, AppState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
game_action: GameAction,
|
||||
state: State<'_, AppState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<Game, String> {
|
||||
// Clone the information we need from state to avoid lifetime issues
|
||||
let game = {
|
||||
let games = state.games.lock();
|
||||
games
|
||||
.get(&game_action.game_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("Game with ID {} not found", game_action.game_id))?
|
||||
};
|
||||
// Clone the information we need from state to avoid lifetime issues
|
||||
let game = {
|
||||
let games = state.games.lock();
|
||||
games
|
||||
.get(&game_action.game_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("Game with ID {} not found", game_action.game_id))?
|
||||
};
|
||||
|
||||
// Parse the action string to determine type and operation
|
||||
let (installer_type, action) = match game_action.action.as_str() {
|
||||
"install_cream" => (InstallerType::Cream, InstallerAction::Install),
|
||||
"uninstall_cream" => (InstallerType::Cream, InstallerAction::Uninstall),
|
||||
"install_smoke" => (InstallerType::Smoke, InstallerAction::Install),
|
||||
"uninstall_smoke" => (InstallerType::Smoke, InstallerAction::Uninstall),
|
||||
_ => return Err(format!("Invalid action: {}", game_action.action)),
|
||||
};
|
||||
// Parse the action string to determine type and operation
|
||||
let (installer_type, action) = match game_action.action.as_str() {
|
||||
"install_cream" => (InstallerType::Cream, InstallerAction::Install),
|
||||
"uninstall_cream" => (InstallerType::Cream, InstallerAction::Uninstall),
|
||||
"install_smoke" => (InstallerType::Smoke, InstallerAction::Install),
|
||||
"uninstall_smoke" => (InstallerType::Smoke, InstallerAction::Uninstall),
|
||||
_ => return Err(format!("Invalid action: {}", game_action.action)),
|
||||
};
|
||||
|
||||
// Execute the action
|
||||
installer::process_action(
|
||||
game_action.game_id.clone(),
|
||||
installer_type,
|
||||
action,
|
||||
game.clone(),
|
||||
app_handle.clone(),
|
||||
)
|
||||
.await?;
|
||||
// Execute the action
|
||||
installer::process_action(
|
||||
game_action.game_id.clone(),
|
||||
installer_type,
|
||||
action,
|
||||
game.clone(),
|
||||
app_handle.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Update game status in state based on the action
|
||||
let updated_game = {
|
||||
let mut games_map = state.games.lock();
|
||||
let game = games_map.get_mut(&game_action.game_id).ok_or_else(|| {
|
||||
format!(
|
||||
"Game with ID {} not found after action",
|
||||
game_action.game_id
|
||||
)
|
||||
})?;
|
||||
// Update game status in state based on the action
|
||||
let updated_game = {
|
||||
let mut games_map = state.games.lock();
|
||||
let game = games_map.get_mut(&game_action.game_id).ok_or_else(|| {
|
||||
format!(
|
||||
"Game with ID {} not found after action",
|
||||
game_action.game_id
|
||||
)
|
||||
})?;
|
||||
|
||||
// Update installation status
|
||||
match (installer_type, action) {
|
||||
(InstallerType::Cream, InstallerAction::Install) => {
|
||||
game.cream_installed = true;
|
||||
}
|
||||
(InstallerType::Cream, InstallerAction::Uninstall) => {
|
||||
game.cream_installed = false;
|
||||
}
|
||||
(InstallerType::Smoke, InstallerAction::Install) => {
|
||||
game.smoke_installed = true;
|
||||
}
|
||||
(InstallerType::Smoke, InstallerAction::Uninstall) => {
|
||||
game.smoke_installed = false;
|
||||
}
|
||||
}
|
||||
// Update installation status
|
||||
match (installer_type, action) {
|
||||
(InstallerType::Cream, InstallerAction::Install) => {
|
||||
game.cream_installed = true;
|
||||
}
|
||||
(InstallerType::Cream, InstallerAction::Uninstall) => {
|
||||
game.cream_installed = false;
|
||||
}
|
||||
(InstallerType::Smoke, InstallerAction::Install) => {
|
||||
game.smoke_installed = true;
|
||||
}
|
||||
(InstallerType::Smoke, InstallerAction::Uninstall) => {
|
||||
game.smoke_installed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset installing flag
|
||||
game.installing = false;
|
||||
// Reset installing flag
|
||||
game.installing = false;
|
||||
|
||||
// Return updated game info
|
||||
game.clone()
|
||||
};
|
||||
// Return updated game info
|
||||
game.clone()
|
||||
};
|
||||
|
||||
// Emit an event to update the UI for this specific game
|
||||
if let Err(e) = app_handle.emit("game-updated", &updated_game) {
|
||||
warn!("Failed to emit game-updated event: {}", e);
|
||||
}
|
||||
// Emit an event to update the UI for this specific game
|
||||
if let Err(e) = app_handle.emit("game-updated", &updated_game) {
|
||||
warn!("Failed to emit game-updated event: {}", e);
|
||||
}
|
||||
|
||||
Ok(updated_game)
|
||||
Ok(updated_game)
|
||||
}
|
||||
|
||||
// Fetch DLC list for a game
|
||||
#[tauri::command]
|
||||
async fn fetch_game_dlcs(
|
||||
game_id: String,
|
||||
app_handle: tauri::AppHandle,
|
||||
game_id: String,
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<Vec<DlcInfoWithState>, String> {
|
||||
info!("Fetching DLCs for game ID: {}", game_id);
|
||||
info!("Fetching DLCs for game ID: {}", game_id);
|
||||
|
||||
// Fetch DLC data
|
||||
match installer::fetch_dlc_details(&game_id).await {
|
||||
Ok(dlcs) => {
|
||||
// Convert to DlcInfoWithState
|
||||
let dlcs_with_state = dlcs
|
||||
.into_iter()
|
||||
.map(|dlc| DlcInfoWithState {
|
||||
appid: dlc.appid,
|
||||
name: dlc.name,
|
||||
enabled: true,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// Fetch DLC data
|
||||
match installer::fetch_dlc_details(&game_id).await {
|
||||
Ok(dlcs) => {
|
||||
// Convert to DlcInfoWithState
|
||||
let dlcs_with_state = dlcs
|
||||
.into_iter()
|
||||
.map(|dlc| DlcInfoWithState {
|
||||
appid: dlc.appid,
|
||||
name: dlc.name,
|
||||
enabled: true,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Cache in memory for this session (but not on disk)
|
||||
let state = app_handle.state::<AppState>();
|
||||
let mut cache = state.dlc_cache.lock();
|
||||
cache.insert(
|
||||
game_id.clone(),
|
||||
DlcCache {
|
||||
data: dlcs_with_state.clone(),
|
||||
timestamp: Instant::now(),
|
||||
},
|
||||
);
|
||||
// Cache in memory for this session (but not on disk)
|
||||
let state = app_handle.state::<AppState>();
|
||||
let mut cache = state.dlc_cache.lock();
|
||||
cache.insert(
|
||||
game_id.clone(),
|
||||
DlcCache {
|
||||
data: dlcs_with_state.clone(),
|
||||
timestamp: Instant::now(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(dlcs_with_state)
|
||||
}
|
||||
Err(e) => Err(format!("Failed to fetch DLC details: {}", e)),
|
||||
}
|
||||
Ok(dlcs_with_state)
|
||||
}
|
||||
Err(e) => Err(format!("Failed to fetch DLC details: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn abort_dlc_fetch(game_id: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||
info!("Request to abort DLC fetch for game ID: {}", game_id);
|
||||
info!("Request to abort DLC fetch for game ID: {}", game_id);
|
||||
|
||||
let state = app_handle.state::<AppState>();
|
||||
state.fetch_cancellation.store(true, Ordering::SeqCst);
|
||||
let state = app_handle.state::<AppState>();
|
||||
state.fetch_cancellation.store(true, Ordering::SeqCst);
|
||||
|
||||
// Reset after a short delay
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
let state = app_handle.state::<AppState>();
|
||||
state.fetch_cancellation.store(false, Ordering::SeqCst);
|
||||
});
|
||||
// Reset after a short delay
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
let state = app_handle.state::<AppState>();
|
||||
state.fetch_cancellation.store(false, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Fetch DLC list with progress updates (streaming)
|
||||
#[tauri::command]
|
||||
async fn stream_game_dlcs(game_id: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||
info!("Streaming DLCs for game ID: {}", game_id);
|
||||
info!("Streaming DLCs for game ID: {}", game_id);
|
||||
|
||||
// Fetch DLC data from API
|
||||
match installer::fetch_dlc_details_with_progress(&game_id, &app_handle).await {
|
||||
Ok(dlcs) => {
|
||||
info!(
|
||||
"Successfully streamed {} DLCs for game {}",
|
||||
dlcs.len(),
|
||||
game_id
|
||||
);
|
||||
// Fetch DLC data from API
|
||||
match installer::fetch_dlc_details_with_progress(&game_id, &app_handle).await {
|
||||
Ok(dlcs) => {
|
||||
info!(
|
||||
"Successfully streamed {} DLCs for game {}",
|
||||
dlcs.len(),
|
||||
game_id
|
||||
);
|
||||
|
||||
// Convert to DLCInfoWithState for in-memory caching only
|
||||
let dlcs_with_state = dlcs
|
||||
.into_iter()
|
||||
.map(|dlc| DlcInfoWithState {
|
||||
appid: dlc.appid,
|
||||
name: dlc.name,
|
||||
enabled: true,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// Convert to DLCInfoWithState for in-memory caching only
|
||||
let dlcs_with_state = dlcs
|
||||
.into_iter()
|
||||
.map(|dlc| DlcInfoWithState {
|
||||
appid: dlc.appid,
|
||||
name: dlc.name,
|
||||
enabled: true,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Update in-memory cache without storing to disk
|
||||
let state = app_handle.state::<AppState>();
|
||||
let mut dlc_cache = state.dlc_cache.lock();
|
||||
dlc_cache.insert(
|
||||
game_id.clone(),
|
||||
DlcCache {
|
||||
data: dlcs_with_state,
|
||||
timestamp: tokio::time::Instant::now(),
|
||||
},
|
||||
);
|
||||
// Update in-memory cache without storing to disk
|
||||
let state = app_handle.state::<AppState>();
|
||||
let mut dlc_cache = state.dlc_cache.lock();
|
||||
dlc_cache.insert(
|
||||
game_id.clone(),
|
||||
DlcCache {
|
||||
data: dlcs_with_state,
|
||||
timestamp: tokio::time::Instant::now(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to stream DLC details: {}", e);
|
||||
// Emit error event
|
||||
let error_payload = serde_json::json!({
|
||||
"error": format!("Failed to fetch DLC details: {}", e)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to stream DLC details: {}", e);
|
||||
// Emit error event
|
||||
let error_payload = serde_json::json!({
|
||||
"error": format!("Failed to fetch DLC details: {}", e)
|
||||
});
|
||||
|
||||
if let Err(emit_err) = app_handle.emit("dlc-error", error_payload) {
|
||||
warn!("Failed to emit dlc-error event: {}", emit_err);
|
||||
}
|
||||
if let Err(emit_err) = app_handle.emit("dlc-error", error_payload) {
|
||||
warn!("Failed to emit dlc-error event: {}", emit_err);
|
||||
}
|
||||
|
||||
Err(format!("Failed to fetch DLC details: {}", e))
|
||||
}
|
||||
}
|
||||
Err(format!("Failed to fetch DLC details: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear caches command renamed to flush_data for clarity
|
||||
#[tauri::command]
|
||||
fn clear_caches() -> Result<(), String> {
|
||||
info!("Data flush requested - cleaning in-memory state only");
|
||||
Ok(())
|
||||
info!("Data flush requested - cleaning in-memory state only");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the list of enabled DLCs for a game
|
||||
#[tauri::command]
|
||||
fn get_enabled_dlcs_command(game_path: String) -> Result<Vec<String>, String> {
|
||||
info!("Getting enabled DLCs for: {}", game_path);
|
||||
dlc_manager::get_enabled_dlcs(&game_path)
|
||||
info!("Getting enabled DLCs for: {}", game_path);
|
||||
dlc_manager::get_enabled_dlcs(&game_path)
|
||||
}
|
||||
|
||||
// Update the DLC configuration for a game
|
||||
#[tauri::command]
|
||||
fn update_dlc_configuration_command(
|
||||
game_path: String,
|
||||
dlcs: Vec<DlcInfoWithState>,
|
||||
game_path: String,
|
||||
dlcs: Vec<DlcInfoWithState>,
|
||||
) -> Result<(), String> {
|
||||
info!("Updating DLC configuration for: {}", game_path);
|
||||
dlc_manager::update_dlc_configuration(&game_path, dlcs)
|
||||
info!("Updating DLC configuration for: {}", game_path);
|
||||
dlc_manager::update_dlc_configuration(&game_path, dlcs)
|
||||
}
|
||||
|
||||
// Install CreamLinux with selected DLCs
|
||||
#[tauri::command]
|
||||
async fn install_cream_with_dlcs_command(
|
||||
game_id: String,
|
||||
selected_dlcs: Vec<DlcInfoWithState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
game_id: String,
|
||||
selected_dlcs: Vec<DlcInfoWithState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<Game, String> {
|
||||
info!(
|
||||
"Installing CreamLinux with selected DLCs for game: {}",
|
||||
game_id
|
||||
);
|
||||
info!(
|
||||
"Installing CreamLinux with selected DLCs for game: {}",
|
||||
game_id
|
||||
);
|
||||
|
||||
// Clone selected_dlcs for later use
|
||||
let selected_dlcs_clone = selected_dlcs.clone();
|
||||
// Clone selected_dlcs for later use
|
||||
let selected_dlcs_clone = selected_dlcs.clone();
|
||||
|
||||
// Install CreamLinux with the selected DLCs
|
||||
match dlc_manager::install_cream_with_dlcs(game_id.clone(), app_handle.clone(), selected_dlcs)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Return updated game info
|
||||
let state = app_handle.state::<AppState>();
|
||||
// Install CreamLinux with the selected DLCs
|
||||
match dlc_manager::install_cream_with_dlcs(game_id.clone(), app_handle.clone(), selected_dlcs)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Return updated game info
|
||||
let state = app_handle.state::<AppState>();
|
||||
|
||||
// Get a mutable reference and update the game
|
||||
let game = {
|
||||
let mut games_map = state.games.lock();
|
||||
let game = games_map.get_mut(&game_id).ok_or_else(|| {
|
||||
format!("Game with ID {} not found after installation", game_id)
|
||||
})?;
|
||||
// Get a mutable reference and update the game
|
||||
let game = {
|
||||
let mut games_map = state.games.lock();
|
||||
let game = games_map.get_mut(&game_id).ok_or_else(|| {
|
||||
format!("Game with ID {} not found after installation", game_id)
|
||||
})?;
|
||||
|
||||
// Update installation status
|
||||
game.cream_installed = true;
|
||||
game.installing = false;
|
||||
// Update installation status
|
||||
game.cream_installed = true;
|
||||
game.installing = false;
|
||||
|
||||
// Clone the game for returning later
|
||||
game.clone()
|
||||
};
|
||||
// Clone the game for returning later
|
||||
game.clone()
|
||||
};
|
||||
|
||||
// Emit an event to update the UI
|
||||
if let Err(e) = app_handle.emit("game-updated", &game) {
|
||||
warn!("Failed to emit game-updated event: {}", e);
|
||||
}
|
||||
// Emit an event to update the UI
|
||||
if let Err(e) = app_handle.emit("game-updated", &game) {
|
||||
warn!("Failed to emit game-updated event: {}", e);
|
||||
}
|
||||
|
||||
// Show installation complete dialog with instructions
|
||||
let instructions = installer::InstallationInstructions {
|
||||
type_: "cream_install".to_string(),
|
||||
command: "sh ./cream.sh %command%".to_string(),
|
||||
game_title: game.title.clone(),
|
||||
dlc_count: Some(selected_dlcs_clone.iter().filter(|dlc| dlc.enabled).count()),
|
||||
};
|
||||
// Show installation complete dialog with instructions
|
||||
let instructions = installer::InstallationInstructions {
|
||||
type_: "cream_install".to_string(),
|
||||
command: "sh ./cream.sh %command%".to_string(),
|
||||
game_title: game.title.clone(),
|
||||
dlc_count: Some(selected_dlcs_clone.iter().filter(|dlc| dlc.enabled).count()),
|
||||
};
|
||||
|
||||
installer::emit_progress(
|
||||
&app_handle,
|
||||
&format!("Installation Completed: {}", game.title),
|
||||
"CreamLinux has been installed successfully!",
|
||||
100.0,
|
||||
true,
|
||||
true,
|
||||
Some(instructions),
|
||||
);
|
||||
installer::emit_progress(
|
||||
&app_handle,
|
||||
&format!("Installation Completed: {}", game.title),
|
||||
"CreamLinux has been installed successfully!",
|
||||
100.0,
|
||||
true,
|
||||
true,
|
||||
Some(instructions),
|
||||
);
|
||||
|
||||
Ok(game)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to install CreamLinux with selected DLCs: {}", e);
|
||||
Err(format!(
|
||||
"Failed to install CreamLinux with selected DLCs: {}",
|
||||
e
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(game)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to install CreamLinux with selected DLCs: {}", e);
|
||||
Err(format!(
|
||||
"Failed to install CreamLinux with selected DLCs: {}",
|
||||
e
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup logging
|
||||
fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use log::LevelFilter;
|
||||
use log4rs::append::file::FileAppender;
|
||||
use log4rs::config::{Appender, Config, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use std::fs;
|
||||
use log::LevelFilter;
|
||||
use log4rs::append::file::FileAppender;
|
||||
use log4rs::config::{Appender, Config, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use std::fs;
|
||||
|
||||
// Get XDG cache directory
|
||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("creamlinux")?;
|
||||
let log_path = xdg_dirs.place_cache_file("creamlinux.log")?;
|
||||
// Get XDG cache directory
|
||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("creamlinux")?;
|
||||
let log_path = xdg_dirs.place_cache_file("creamlinux.log")?;
|
||||
|
||||
// Clear the log file on startup
|
||||
if log_path.exists() {
|
||||
if let Err(e) = fs::write(&log_path, "") {
|
||||
eprintln!("Warning: Failed to clear log file: {}", e);
|
||||
}
|
||||
}
|
||||
// Clear the log file on startup
|
||||
if log_path.exists() {
|
||||
if let Err(e) = fs::write(&log_path, "") {
|
||||
eprintln!("Warning: Failed to clear log file: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a file appender
|
||||
let file = FileAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new(
|
||||
"[{d(%Y-%m-%d %H:%M:%S)}] {l}: {m}\n",
|
||||
)))
|
||||
.build(log_path)?;
|
||||
// Create a file appender
|
||||
let file = FileAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new(
|
||||
"[{d(%Y-%m-%d %H:%M:%S)}] {l}: {m}\n",
|
||||
)))
|
||||
.build(log_path)?;
|
||||
|
||||
// Build the config
|
||||
let config = Config::builder()
|
||||
.appender(Appender::builder().build("file", Box::new(file)))
|
||||
.build(Root::builder().appender("file").build(LevelFilter::Info))?;
|
||||
// Build the config
|
||||
let config = Config::builder()
|
||||
.appender(Appender::builder().build("file", Box::new(file)))
|
||||
.build(Root::builder().appender("file").build(LevelFilter::Info))?;
|
||||
|
||||
// Initialize log4rs with this config
|
||||
log4rs::init_config(config)?;
|
||||
// Initialize log4rs with this config
|
||||
log4rs::init_config(config)?;
|
||||
|
||||
info!("CreamLinux started with a clean log file");
|
||||
Ok(())
|
||||
info!("CreamLinux started with a clean log file");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Set up logging first
|
||||
if let Err(e) = setup_logging() {
|
||||
eprintln!("Warning: Failed to initialize logging: {}", e);
|
||||
}
|
||||
// Set up logging first
|
||||
if let Err(e) = setup_logging() {
|
||||
eprintln!("Warning: Failed to initialize logging: {}", e);
|
||||
}
|
||||
|
||||
info!("Initializing CreamLinux application");
|
||||
info!("Initializing CreamLinux application");
|
||||
|
||||
let app_state = AppState {
|
||||
games: Mutex::new(HashMap::new()),
|
||||
dlc_cache: Mutex::new(HashMap::new()),
|
||||
fetch_cancellation: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
let app_state = AppState {
|
||||
games: Mutex::new(HashMap::new()),
|
||||
dlc_cache: Mutex::new(HashMap::new()),
|
||||
fetch_cancellation: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.manage(app_state)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
scan_steam_games,
|
||||
get_game_info,
|
||||
process_game_action,
|
||||
fetch_game_dlcs,
|
||||
stream_game_dlcs,
|
||||
get_enabled_dlcs_command,
|
||||
update_dlc_configuration_command,
|
||||
install_cream_with_dlcs_command,
|
||||
get_all_dlcs_command,
|
||||
clear_caches,
|
||||
abort_dlc_fetch,
|
||||
])
|
||||
.setup(|app| {
|
||||
// Add a setup handler to do any initialization work
|
||||
info!("Tauri application setup");
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.manage(app_state)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
scan_steam_games,
|
||||
get_game_info,
|
||||
process_game_action,
|
||||
fetch_game_dlcs,
|
||||
stream_game_dlcs,
|
||||
get_enabled_dlcs_command,
|
||||
update_dlc_configuration_command,
|
||||
install_cream_with_dlcs_command,
|
||||
get_all_dlcs_command,
|
||||
clear_caches,
|
||||
abort_dlc_fetch,
|
||||
])
|
||||
.setup(|app| {
|
||||
// Add a setup handler to do any initialization work
|
||||
info!("Tauri application setup");
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if std::env::var("OPEN_DEVTOOLS").ok().as_deref() == Some("1") {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
window.open_devtools();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if std::env::var("OPEN_DEVTOOLS").ok().as_deref() == Some("1") {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
window.open_devtools();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
Reference in New Issue
Block a user