game_manager_lib/commands/platforms/
indiegala.rs1use crate::commands::platforms::core::format_import_summary;
4use crate::database::AppState;
5use crate::errors::AppError;
6use crate::sources::indiegala::IndiegalaSource;
7use crate::utils::status_logic;
8use chrono::Utc;
9use rusqlite::params;
10use tauri::{AppHandle, Emitter, State};
11use tracing::info;
12use uuid::Uuid;
13
14async fn persist_indiegala_games(
24 state: &AppState,
25 games: Vec<crate::sources::indiegala::IndiegalaGame>,
26) -> Result<(u32, u32), AppError> {
27 let mut conn = state.games_db.lock().map_err(|_| AppError::MutexError)?;
28 let tx = conn
29 .transaction()
30 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
31
32 let mut inserted = 0u32;
33 let mut updated = 0u32;
34 let now = Utc::now().to_rfc3339();
35
36 for indiegala_game in games {
37 let game = &indiegala_game.source;
38
39 let exists: bool = tx
40 .query_row(
41 "SELECT EXISTS(SELECT 1 FROM games WHERE platform = ?1 AND platform_game_id = ?2)",
42 params![&game.platform, &game.platform_game_id],
43 |row| row.get(0),
44 )
45 .unwrap_or(false);
46
47 let status = status_logic::calculate_status(game.playtime_minutes.unwrap_or(0) as i32);
48
49 if !exists {
50 let new_id = Uuid::new_v4().to_string();
51
52 tx.execute(
53 "INSERT INTO games (
54 id, name, cover_url, platform, platform_game_id,
55 installed, status, playtime, last_played, added_at,
56 favorite, user_rating, install_path, executable_path
57 ) VALUES (?1, ?2, NULL, ?3, ?4, ?5, ?6, ?7, NULL, ?8, 0, NULL, ?9, ?10)",
58 params![
59 new_id,
60 game.name.as_deref().unwrap_or("Unknown"),
61 game.platform,
62 game.platform_game_id,
63 game.installed,
64 status,
65 game.playtime_minutes.unwrap_or(0),
66 now,
67 game.install_path,
68 game.executable_path,
69 ],
70 )
71 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
72
73 let tags_json = indiegala_game
74 .tags
75 .as_ref()
76 .and_then(|tags| crate::database::serialize_tags(tags).ok());
77
78 if indiegala_game.description_raw.is_some() || tags_json.is_some() {
79 tx.execute(
80 "INSERT OR IGNORE INTO game_details (game_id, description_raw, tags)
81 VALUES (?1, ?2, ?3)",
82 params![new_id, indiegala_game.description_raw, tags_json],
83 )
84 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
85 }
86
87 inserted += 1;
88 } else {
89 tx.execute(
90 "UPDATE games SET
91 installed = ?1,
92 status = ?2,
93 playtime = COALESCE(?3, playtime),
94 install_path = COALESCE(?4, install_path),
95 executable_path = COALESCE(?5, executable_path)
96 WHERE platform = ?6 AND platform_game_id = ?7",
97 params![
98 game.installed,
99 status,
100 game.playtime_minutes, 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))
117}
118
119#[tauri::command]
130pub async fn import_indiegala_games(
131 app: AppHandle,
132 state: State<'_, AppState>,
133 full: bool,
134 installed_json_path: Option<String>,
135 config_json_path: Option<String>,
136) -> Result<String, AppError> {
137 let installed_path = installed_json_path
138 .filter(|s| !s.trim().is_empty())
139 .map(std::path::PathBuf::from);
140 let config_path = config_json_path
141 .filter(|s| !s.trim().is_empty())
142 .map(std::path::PathBuf::from);
143
144 let source = IndiegalaSource::new(installed_path);
145
146 let games = if full {
147 source.fetch_full_library_detailed(config_path).await?
148 } else {
149 source.fetch_installed_detailed().await?
150 };
151
152 if games.is_empty() {
153 let msg = if full {
154 "Nenhum jogo IndieGala encontrado na biblioteca."
155 } else {
156 "Nenhum jogo IndieGala instalado encontrado."
157 };
158 return Ok(msg.to_string());
159 }
160
161 let (inserted, updated) = persist_indiegala_games(&state, games).await?;
162 let message = format_import_summary("IndieGala", inserted, updated);
163 info!("{}", message);
164
165 let _ = app.emit("library_updated", ());
166
167 Ok(message)
168}