Skip to main content

game_manager_lib/commands/platforms/
core.rs

1//! Funções genéricas usadas na importação de bibliotecas de plataformas externas (Steam, Epic, GOG).
2//!
3//! Fornece comandos para salvar dados dos jogos nos bancos de dados e mensagens padronizadas.
4
5use crate::constants;
6use crate::database::AppState;
7use crate::errors::AppError;
8use crate::sources::scanner::GameDiscovery;
9use crate::utils::status_logic;
10use chrono::{TimeZone, Utc};
11use rusqlite::params;
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14
15// === Estruturas de Dados ===
16
17#[derive(Serialize, Deserialize, Debug)]
18pub struct ScanResult {
19    pub success: bool,
20    pub message: String,
21    pub discoveries: Vec<GameDiscovery>,
22}
23
24#[derive(Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct ScanGameInput {
27    pub name: String,
28    pub executable_path: String,
29    pub base_path: String,
30}
31
32/// Dados mínimos de um jogo recém-inserido, usados para disparar enriquecimento automático logo após a importação.
33#[derive(Debug, Clone)]
34pub struct NewlyImportedGame {
35    pub game_id: String,
36    pub name: String,
37    pub platform: String,
38    pub platform_game_id: String,
39}
40
41// === Funções Genéricas de Persistência ===
42
43/// Persiste uma lista de jogos de uma fonte externa (como Steam) no banco de dados.
44///
45/// Retorna o número de jogos inseridos e atualizados.
46pub(crate) async fn persist_source_games(
47    state: &AppState,
48    games: Vec<crate::sources::providers::SourceGame>,
49) -> Result<(u32, u32, Vec<NewlyImportedGame>), AppError> {
50    let mut conn = state.games_db.lock().map_err(|_| AppError::MutexError)?;
51
52    // Inicia uma transação única para todo o lote
53    let tx = conn
54        .transaction()
55        .map_err(|e| AppError::DatabaseError(e.to_string()))?;
56
57    let mut inserted = 0;
58    let mut updated = 0;
59    let mut newly_imported = Vec::new();
60    let now = Utc::now().to_rfc3339();
61
62    for game in games {
63        // Verifica existência usando a transação
64        let exists: bool = tx
65            .query_row(
66                "SELECT EXISTS(SELECT 1 FROM games WHERE platform = ?1 AND platform_game_id = ?2)",
67                params![&game.platform, &game.platform_game_id],
68                |row| row.get(0),
69            )
70            .unwrap_or(false);
71
72        let status = status_logic::calculate_status(game.playtime_minutes.unwrap_or(0) as i32);
73
74        let last_played_iso = game.last_played.and_then(|ts| {
75            if ts > 0 {
76                Some(Utc.timestamp_opt(ts, 0).single().map(|dt| dt.to_rfc3339()))
77            } else {
78                None
79            }
80        });
81
82        if !exists {
83            let new_id = Uuid::new_v4().to_string();
84            let display_name = game.name.clone().unwrap_or_else(|| "Unknown".to_string());
85
86            // Define uma capa padrão da Steam se for essa a plataforma
87            let cover_url = if game.platform == "Steam" {
88                Some(format!(
89                    "{}/{}",
90                    constants::STEAM_CDN_URL,
91                    constants::STEAM_LIBRARY_IMAGE_PATH.replace("{}", &game.platform_game_id)
92                ))
93            } else {
94                None
95            };
96
97            tx.execute(
98                "INSERT INTO games (
99                    id, name, cover_url, platform, platform_game_id,
100                    installed, status, playtime, last_played, added_at,
101                    favorite, user_rating, install_path, executable_path
102                ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, 0, NULL, ?11, ?12)",
103                params![
104                    new_id,
105                    game.name.unwrap_or_else(|| "Unknown".to_string()),
106                    cover_url,
107                    game.platform,
108                    game.platform_game_id,
109                    game.installed,
110                    status,
111                    game.playtime_minutes.unwrap_or(0),
112                    last_played_iso,
113                    now,
114                    game.install_path,
115                    game.executable_path,
116                ],
117            )
118            .map_err(|e| AppError::DatabaseError(e.to_string()))?;
119
120            newly_imported.push(NewlyImportedGame {
121                game_id: new_id,
122                name: display_name,
123                platform: game.platform.clone(),
124                platform_game_id: game.platform_game_id.clone(),
125            });
126
127            inserted += 1;
128        } else {
129            tx.execute(
130                "UPDATE games SET
131                    installed = ?1,
132                    status = ?2,
133                    playtime = ?3,
134                    last_played = ?4,
135                    install_path = COALESCE(?5, install_path),
136                    executable_path = COALESCE(?6, executable_path)
137                 WHERE platform = ?7 AND platform_game_id = ?8",
138                params![
139                    game.installed,
140                    status,
141                    game.playtime_minutes.unwrap_or(0),
142                    last_played_iso,
143                    game.install_path,
144                    game.executable_path,
145                    game.platform,
146                    game.platform_game_id
147                ],
148            )
149            .map_err(|e| AppError::DatabaseError(e.to_string()))?;
150
151            updated += 1;
152        }
153    }
154
155    // Finaliza a transação
156    tx.commit()
157        .map_err(|e| AppError::DatabaseError(e.to_string()))?;
158
159    Ok((inserted, updated, newly_imported))
160}
161
162// === Funções padronizadas para mensagens ===
163
164/// Mensagem padrão de sucesso: "<Plataforma>: X adicionados, Y atualizados".
165pub fn format_import_summary(platform: &str, inserted: u32, updated: u32) -> String {
166    format!("{platform}: {inserted} adicionados, {updated} atualizados")
167}
168
169/// Mensagem padrão de biblioteca vazia: "Nenhum jogo <plataforma> encontrado."
170pub fn format_import_empty(platform: &str) -> String {
171    format!("Nenhum jogo {platform} encontrado.")
172}
173
174/// Mensagem padrão de conexão bem sucedida: "Conta <plataforma> conectada com sucesso!"
175pub fn format_login_success(platform: &str) -> String {
176    format!("Conta {platform} conectada com sucesso!")
177}