game_manager_lib\commands\metadata/
covers.rs

1//! Comandos relacionados ao enriquecimento de capas do jogo
2//!
3//! Permite buscar capas faltantes para jogos na biblioteca usando a API RAWG, com cache de metadados.
4
5use super::shared::{fetch_rawg_metadata, EnrichProgress};
6use crate::constants::RAWG_RATE_LIMIT_MS;
7use crate::database;
8use crate::database::AppState;
9use crate::errors::AppError;
10use rusqlite::params;
11use std::time::Duration;
12use tauri::{AppHandle, Emitter, Manager, State};
13use tokio::time::sleep;
14use tracing::info;
15
16/// Busca capas faltantes via RAWG (COM CACHE)
17#[tauri::command]
18pub async fn fetch_missing_covers(app: AppHandle) -> Result<(), AppError> {
19    let app_handle = app.clone();
20    let api_key = database::get_secret(&app, "rawg_api_key")?;
21    if api_key.is_empty() {
22        return Err(AppError::ValidationError(
23            "API Key da RAWG não configurada.".to_string(),
24        ));
25    }
26
27    tauri::async_runtime::spawn(async move {
28        info!("Iniciando busca de capas faltantes com cache...");
29
30        let state: State<AppState> = app_handle.state();
31        let mut total_updated = 0;
32        let mut total_failed = 0;
33
34        let games_without_cover: Vec<(String, String)> = {
35            let conn = state.library_db.lock().unwrap();
36            let mut stmt = conn
37                .prepare("SELECT id, name FROM games WHERE cover_url IS NULL OR cover_url = ''")
38                .unwrap();
39
40            stmt.query_map([], |row| -> rusqlite::Result<(String, String)> {
41                Ok((row.get(0)?, row.get(1)?))
42            })
43            .unwrap()
44            .flatten()
45            .collect()
46        };
47
48        if !games_without_cover.is_empty() {
49            let count = games_without_cover.len();
50
51            for (index, (game_id, name)) in games_without_cover.into_iter().enumerate() {
52                let _ = app_handle.emit(
53                    "enrich_progress",
54                    EnrichProgress {
55                        current: (index + 1) as i32,
56                        total_found: count as i32,
57                        last_game: format!("Capa: {}", name),
58                        status: "running".to_string(),
59                    },
60                );
61
62                // Busca com cache usando block_in_place
63                let img_url = {
64                    let cache_conn = state.metadata_db.lock().unwrap();
65                    let result = tokio::task::block_in_place(|| {
66                        let rt = tokio::runtime::Handle::current();
67                        rt.block_on(async {
68                            fetch_rawg_metadata(&api_key, &name, &cache_conn)
69                                .await
70                                .and_then(|d| d.background_image)
71                        })
72                    });
73                    result
74                };
75
76                if let Some(img) = img_url {
77                    let conn = state.library_db.lock().unwrap();
78                    if conn
79                        .execute(
80                            "UPDATE games SET cover_url = ?1 WHERE id = ?2",
81                            params![img, game_id],
82                        )
83                        .is_ok()
84                    {
85                        total_updated += 1;
86                    } else {
87                        total_failed += 1;
88                    }
89                } else {
90                    total_failed += 1;
91                }
92
93                sleep(Duration::from_millis(RAWG_RATE_LIMIT_MS)).await;
94            }
95        }
96
97        info!(
98            "Busca de capas finalizada: {} sucesso, {} falhas",
99            total_updated, total_failed
100        );
101        let _ = app_handle.emit("enrich_complete", "Busca de capas finalizada.");
102    });
103
104    Ok(())
105}