feat: upgrade needed ressources and fight for each quest

This commit is contained in:
2026-04-25 14:39:16 +02:00
parent de6550cee4
commit 0e577b8efd
13 changed files with 560 additions and 2 deletions

View File

@ -2,6 +2,7 @@ use tauri::{AppHandle, Emitter, Manager, State};
use tauri::window::Color;
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use std::collections::HashMap;
use rusqlite::Connection;
use crate::{db, parser};
@ -556,6 +557,112 @@ pub async fn open_image_viewer(
Ok(())
}
/// Lit le cache SQLite pour une liste d'URLs et retourne les indicateurs déjà stockés.
/// Synchrone et instantané — utilisé au chargement de la vue pour afficher les données en cache.
#[tauri::command]
pub fn get_cached_previews(
state: State<DbState>,
quest_urls: Vec<String>,
) -> Result<HashMap<String, Vec<parser::CombatIndicator>>, String> {
let conn = state.0.lock().map_err(|e| e.to_string())?;
Ok(db::get_cached_previews(&conn, &quest_urls))
}
/// Scrape toutes les quêtes d'un guide qui ne sont pas encore en cache, stocke les résultats
/// en DB et retourne l'ensemble `url → indicateurs` pour le guide demandé.
#[tauri::command]
pub async fn fetch_guide_previews(
state: State<'_, DbState>,
gid: String,
) -> Result<HashMap<String, Vec<parser::CombatIndicator>>, String> {
// 1. Charge le guide depuis la DB (section critique minimale)
let guide = {
let conn = state.0.lock().map_err(|e| e.to_string())?;
db::get_guide(&conn, &gid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Guide {} introuvable", gid))?
};
// 2. Collecte toutes les URLs des quêtes du guide
let all_urls: Vec<String> = collect_quest_urls(&guide);
// 3. Détermine quelles URLs ne sont pas encore en cache
let cached_urls = {
let conn = state.0.lock().map_err(|e| e.to_string())?;
db::get_cached_urls(&conn, &all_urls)
};
let urls_to_fetch: Vec<String> = all_urls
.iter()
.filter(|u| !cached_urls.contains(*u))
.cloned()
.collect();
// 4. Scrape les pages manquantes (bloquant → spawn_blocking)
for url in urls_to_fetch {
let url_clone = url.clone();
let result = tokio::task::spawn_blocking(move || -> Result<Vec<parser::CombatIndicator>, String> {
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(20))
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.build()
.map_err(|e| e.to_string())?;
let html = client
.get(&url_clone)
.send()
.map_err(|e| format!("Erreur réseau {} : {}", url_clone, e))?
.text()
.map_err(|e| e.to_string())?;
Ok(parser::extract_a_prevoir(&html))
})
.await
.map_err(|e| e.to_string())?;
// On persiste même si le résultat est vide (évite de re-scraper une page sans section)
match result {
Ok(indicators) => {
let conn = state.0.lock().map_err(|e| e.to_string())?;
db::upsert_preview(&conn, &url, &indicators).map_err(|e| e.to_string())?;
}
Err(e) => {
// Erreur réseau non fatale : on log et on continue
eprintln!("[fetch_guide_previews] Erreur pour {} : {}", url, e);
}
}
}
// 5. Retourne l'ensemble du cache pour toutes les URLs du guide
let conn = state.0.lock().map_err(|e| e.to_string())?;
Ok(db::get_cached_previews(&conn, &all_urls))
}
/// Extrait toutes les URLs de quêtes depuis un `GuideData` (Quest + Group.quests).
fn collect_quest_urls(data: &parser::GuideData) -> Vec<String> {
let mut urls = Vec::new();
for section in &data.sections {
for item in &section.items {
match item {
parser::SectionItem::Quest(q) => {
if let Some(url) = &q.url {
urls.push(url.clone());
}
}
parser::SectionItem::Group(g) => {
for q in &g.quests {
if let Some(url) = &q.url {
urls.push(url.clone());
}
}
}
parser::SectionItem::Instruction(_) => {}
}
}
}
urls
}
fn collect_quest_names(data: &parser::GuideData) -> Vec<String> {
let mut names = Vec::new();
for section in &data.sections {