game_manager_lib/commands/platforms/
itch.rs1use crate::commands::platforms::core::format_import_summary;
4use crate::database::AppState;
5use crate::errors::AppError;
6use crate::sources::itch::{ItchioGame, ItchioSource};
7use crate::utils::status_logic;
8use chrono::{TimeZone, Utc};
9use rusqlite::params;
10use tauri::{AppHandle, Emitter, State};
11use tracing::info;
12use uuid::Uuid;
13
14async fn persist_itch_games(
18 state: &AppState,
19 games: Vec<ItchioGame>,
20) -> Result<(u32, u32), AppError> {
21 let mut conn = state.games_db.lock().map_err(|_| AppError::MutexError)?;
22 let tx = conn
23 .transaction()
24 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
25
26 let mut inserted = 0u32;
27 let mut updated = 0u32;
28 let now = Utc::now().to_rfc3339();
29
30 for itchio_game in games {
31 let game = &itchio_game.source;
32
33 let exists: bool = tx
34 .query_row(
35 "SELECT EXISTS(SELECT 1 FROM games WHERE platform = ?1 AND platform_game_id = ?2)",
36 params![&game.platform, &game.platform_game_id],
37 |row| row.get(0),
38 )
39 .unwrap_or(false);
40
41 let status = status_logic::calculate_status(game.playtime_minutes.unwrap_or(0) as i32);
42
43 let last_played_iso = game.last_played.and_then(|ts| {
45 if ts > 0 {
46 Utc.timestamp_opt(ts, 0).single().map(|dt| dt.to_rfc3339())
47 } else {
48 None
49 }
50 });
51
52 if !exists {
53 let new_id = Uuid::new_v4().to_string();
54
55 tx.execute(
56 "INSERT INTO games (
57 id, name, cover_url, platform, platform_game_id,
58 installed, status, playtime, last_played, added_at,
59 favorite, user_rating, install_path, executable_path
60 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, 0, NULL, ?11, ?12)",
61 params![
62 new_id,
63 game.name.as_deref().unwrap_or("Unknown"),
64 itchio_game.cover_url, game.platform,
66 game.platform_game_id,
67 game.installed,
68 status,
69 game.playtime_minutes.unwrap_or(0),
70 last_played_iso,
71 now,
72 game.install_path,
73 game.executable_path,
74 ],
75 )
76 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
77
78 if itchio_game.description_raw.is_some() {
80 tx.execute(
81 "INSERT OR IGNORE INTO game_details (game_id, description_raw)
82 VALUES (?1, ?2)",
83 params![new_id, itchio_game.description_raw],
84 )
85 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
86 }
87
88 inserted += 1;
89 } else {
90 tx.execute(
91 "UPDATE games SET
92 installed = ?1,
93 status = ?2,
94 playtime = COALESCE(?3, playtime),
95 last_played = COALESCE(?4, last_played),
96 install_path = COALESCE(?5, install_path),
97 executable_path = COALESCE(?6, executable_path),
98 cover_url = COALESCE(?7, cover_url)
99 WHERE platform = ?8 AND platform_game_id = ?9",
100 params![
101 game.installed,
102 status,
103 game.playtime_minutes,
104 last_played_iso,
105 game.install_path,
106 game.executable_path,
107 itchio_game.cover_url,
108 game.platform,
109 game.platform_game_id,
110 ],
111 )
112 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
113
114 if let Some(desc) = &itchio_game.description_raw {
116 tx.execute(
117 "INSERT INTO game_details (game_id, description_raw)
118 VALUES ((SELECT id FROM games WHERE platform = ?1 AND platform_game_id = ?2), ?3)
119 ON CONFLICT(game_id) DO UPDATE SET description_raw = excluded.description_raw",
120 params![game.platform, game.platform_game_id, desc],
121 ).map_err(|e| AppError::DatabaseError(e.to_string()))?;
122 }
123
124 updated += 1;
125 }
126 }
127
128 tx.commit()
129 .map_err(|e| AppError::DatabaseError(e.to_string()))?;
130
131 Ok((inserted, updated))
132}
133
134#[tauri::command]
141pub async fn import_itch_games(
142 app: AppHandle,
143 state: State<'_, AppState>,
144 full: bool,
145 butler_db_path: Option<String>,
146) -> Result<String, AppError> {
147 let db_path = butler_db_path
148 .filter(|s| !s.trim().is_empty())
149 .map(std::path::PathBuf::from);
150
151 let source = ItchioSource::new(db_path);
152
153 let games = if full {
154 source.fetch_full_library_detailed().await?
155 } else {
156 source.fetch_installed_detailed().await?
157 };
158
159 if games.is_empty() {
160 let msg = if full {
161 "Nenhum jogo Itch.io encontrado na biblioteca."
162 } else {
163 "Nenhum jogo Itch.io instalado encontrado."
164 };
165 return Ok(msg.to_string());
166 }
167
168 let (inserted, updated) = persist_itch_games(&state, games).await?;
169 let message = format_import_summary("Itch.io", inserted, updated);
170 info!("{}", message);
171
172 let _ = app.emit("library_updated", ());
173
174 Ok(message)
175}