game_manager_lib/commands/platforms/
legacy.rs1use crate::commands::platforms::core::{spawn_import_custom, ImportOutcome, NewlyImportedGame};
4use crate::database::AppState;
5use crate::errors::AppError;
6use crate::sources::legacy::LegacySource;
7use crate::utils::status_logic;
8use chrono::Utc;
9use rusqlite::params;
10use tauri::{AppHandle, Manager};
11use uuid::Uuid;
12
13async fn persist_legacy_games(
18 state: &AppState,
19 games: Vec<crate::sources::legacy::LegacyGame>,
20) -> Result<(u32, u32, Vec<NewlyImportedGame>), AppError> {
21 let mut newly_imported = Vec::new();
22 let mut conn = state.games_db.lock().map_err(|_| AppError::MutexError)?;
23 let tx = conn
24 .transaction()
25 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
26
27 let mut inserted = 0u32;
28 let mut updated = 0u32;
29 let now = Utc::now().to_rfc3339();
30
31 for legacy_game in games {
32 let game = &legacy_game.source;
33
34 let exists: bool = tx
35 .query_row(
36 "SELECT EXISTS(SELECT 1 FROM games WHERE platform = ?1 AND platform_game_id = ?2)",
37 params![&game.platform, &game.platform_game_id],
38 |row| row.get(0),
39 )
40 .unwrap_or(false);
41
42 let status = status_logic::calculate_status(game.playtime_minutes.unwrap_or(0) as i32);
43
44 if !exists {
45 let new_id = Uuid::new_v4().to_string();
46 let display_name = game.name.clone().unwrap_or_else(|| "Unknown".to_string());
47 let slug = crate::utils::text::slugify(&display_name);
48
49 tx.execute(
50 "INSERT INTO games (
51 id, name, slug, cover_url, platform, platform_game_id,
52 installed, status, playtime, playtime_source, last_played, added_at,
53 favorite, user_rating, install_path, executable_path
54 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, NULL, NULL, ?10, 0, NULL, ?11, ?12)",
55 params![
56 new_id,
57 display_name,
58 slug,
59 legacy_game.cover_url,
60 game.platform,
61 game.platform_game_id,
62 game.installed,
63 status,
64 game.playtime_minutes.unwrap_or(0),
65 now,
66 game.install_path,
67 game.executable_path,
68 ],
69 )
70 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
71
72 if legacy_game.description_raw.is_some() {
74 tx.execute(
75 "INSERT OR IGNORE INTO game_details (game_id, description_raw)
76 VALUES (?1, ?2)",
77 params![new_id, legacy_game.description_raw],
78 )
79 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
80 }
81
82 newly_imported.push(NewlyImportedGame {
83 game_id: new_id,
84 name: display_name,
85 platform: game.platform.clone(),
86 platform_game_id: game.platform_game_id.clone(),
87 });
88
89 inserted += 1;
90 } else {
91 tx.execute(
92 "UPDATE games SET
93 installed = ?1,
94 status = ?2,
95 install_path = COALESCE(?3, install_path),
96 executable_path = COALESCE(?4, executable_path)
97 WHERE platform = ?5 AND platform_game_id = ?6",
98 params![
99 game.installed,
100 status,
101 game.install_path,
102 game.executable_path,
103 game.platform,
104 game.platform_game_id,
105 ],
106 )
107 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
108
109 updated += 1;
110 }
111 }
112
113 tx.commit()
114 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
115
116 Ok((inserted, updated, newly_imported))
117}
118
119#[tauri::command]
130pub async fn import_legacy_games(
131 app: AppHandle,
132 app_state_path: Option<String>,
133 wine_prefix: Option<String>,
134) -> Result<(), AppError> {
135 let path = app_state_path
136 .filter(|s| !s.trim().is_empty())
137 .map(std::path::PathBuf::from);
138 let prefix = wine_prefix
139 .filter(|s| !s.trim().is_empty())
140 .map(std::path::PathBuf::from);
141
142 spawn_import_custom(app, "LegacyGames", |app| async move {
143 let state: tauri::State<crate::database::AppState> = app.state();
144 let source = LegacySource::new_with_wine(path, prefix);
145 let games = source.fetch_games_detailed().await?;
146
147 if games.is_empty() {
148 return Ok(ImportOutcome::Empty);
149 }
150
151 let (inserted, updated, newly_imported) = persist_legacy_games(&state, games).await?;
152 Ok(ImportOutcome::Persisted {
153 inserted,
154 updated,
155 newly_imported,
156 })
157 });
158
159 Ok(())
160}