1use crate::models::{GameDataPath, GameExtras, PcgwScrapedData, SystemRequirements};
8use rusqlite::{params, Connection};
9use tracing::{info, warn};
10
11pub fn initialize_pcgamingwiki_tables(conn: &Connection) -> Result<(), String> {
17 conn.execute_batch(
18 "CREATE TABLE IF NOT EXISTS game_extras (
19 steam_app_id TEXT PRIMARY KEY,
20 pcgw_page_id TEXT,
21 pcgw_page_name TEXT,
22 engine TEXT,
23 available_on TEXT,
24 dx_versions TEXT,
25 vulkan_versions TEXT,
26 opengl_versions TEXT,
27 win64 TEXT,
28 linux64 TEXT,
29 macos_arm TEXT,
30 macos_intel64 TEXT,
31 ray_tracing TEXT,
32 upscaling TEXT,
33 frame_gen TEXT,
34 ultrawidescreen TEXT,
35 four_k_support TEXT,
36 hdr TEXT,
37 high_fps TEXT,
38 fov TEXT,
39 borderless_windowed TEXT,
40 color_blind TEXT,
41 controller_support TEXT,
42 full_controller TEXT,
43 playstation_controllers TEXT,
44 xinput_controllers TEXT,
45 surround_sound TEXT,
46 subtitles TEXT,
47 closed_captions TEXT,
48 has_save_data TEXT,
49 has_config_data TEXT,
50 languages_interface TEXT,
51 languages_audio TEXT,
52 languages_subtitles TEXT,
53 fetched_at TEXT
54 );
55
56 CREATE INDEX IF NOT EXISTS idx_game_extras_fetched_at
57 ON game_extras(fetched_at);
58
59 CREATE TABLE IF NOT EXISTS system_requirements (
60 id INTEGER PRIMARY KEY AUTOINCREMENT,
61 steam_app_id TEXT NOT NULL,
62 os_family TEXT NOT NULL,
63 tier_title TEXT,
64 target TEXT,
65 min_os TEXT,
66 min_cpu TEXT,
67 min_cpu2 TEXT,
68 min_ram TEXT,
69 min_gpu TEXT,
70 min_gpu2 TEXT,
71 min_vram TEXT,
72 min_dx TEXT,
73 min_storage TEXT,
74 rec_os TEXT,
75 rec_cpu TEXT,
76 rec_cpu2 TEXT,
77 rec_ram TEXT,
78 rec_gpu TEXT,
79 rec_gpu2 TEXT,
80 rec_vram TEXT,
81 rec_dx TEXT,
82 rec_storage TEXT,
83 fetched_at TEXT NOT NULL
84 );
85
86 CREATE INDEX IF NOT EXISTS idx_sysreq_app_id
87 ON system_requirements(steam_app_id);
88
89 CREATE TABLE IF NOT EXISTS game_data_paths (
90 id INTEGER PRIMARY KEY AUTOINCREMENT,
91 steam_app_id TEXT NOT NULL,
92 kind TEXT NOT NULL,
93 os TEXT NOT NULL,
94 raw_path TEXT NOT NULL,
95 fetched_at TEXT NOT NULL
96 );
97
98 CREATE INDEX IF NOT EXISTS idx_gamedata_app_id
99 ON game_data_paths(steam_app_id);",
100 )
101 .map_err(|e| format!("Erro ao criar tabelas do PCGamingWiki: {}", e))?;
102
103 Ok(())
104}
105
106pub fn get_pcgw_data(conn: &Connection, steam_app_id: &str) -> Option<GameExtras> {
113 let result = conn.query_row(
114 "SELECT
115 steam_app_id,
116 pcgw_page_id,
117 pcgw_page_name,
118 engine,
119 available_on,
120
121 dx_versions,
122 vulkan_versions,
123 opengl_versions,
124
125 win64,
126 linux64,
127 macos_arm,
128 macos_intel64,
129
130 ray_tracing,
131 upscaling,
132 frame_gen,
133 ultrawidescreen,
134 four_k_support,
135 hdr,
136 high_fps,
137 fov,
138 borderless_windowed,
139 color_blind,
140
141 controller_support,
142 full_controller,
143 playstation_controllers,
144 xinput_controllers,
145
146 surround_sound,
147 subtitles,
148 closed_captions,
149
150 has_save_data,
151 has_config_data,
152
153 languages_interface,
154 languages_audio,
155 languages_subtitles,
156
157 fetched_at
158 FROM game_extras
159 WHERE steam_app_id = ?1
160 AND fetched_at IS NOT NULL",
161 params![steam_app_id],
162 |row| {
163 let parse_json_vec = |s: Option<String>| -> Option<Vec<String>> {
164 s.and_then(|v| serde_json::from_str(&v).ok())
165 };
166
167 Ok(GameExtras {
168 steam_app_id: row.get(0)?,
169 pcgw_page_id: row.get(1)?,
170 pcgw_page_name: row.get(2)?,
171 engine: row.get(3)?,
172 available_on: row.get(4)?,
173
174 dx_versions: row.get(5)?,
176 vulkan_versions: row.get(6)?,
177 opengl_versions: row.get(7)?,
178
179 win64: row.get(8)?,
180 linux64: row.get(9)?,
181 macos_arm: row.get(10)?,
182 macos_intel64: row.get(11)?,
183
184 ray_tracing: row.get(12)?,
186 upscaling: row.get(13)?,
187 frame_gen: row.get(14)?,
188 ultrawidescreen: row.get(15)?,
189 four_k_support: row.get(16)?,
190 hdr: row.get(17)?,
191 high_fps: row.get(18)?,
192 fov: row.get(19)?,
193 borderless_windowed: row.get(20)?,
194 color_blind: row.get(21)?,
195
196 controller_support: row.get(22)?,
198 full_controller: row.get(23)?,
199 playstation_controllers: row.get(24)?,
200 xinput_controllers: row.get(25)?,
201
202 surround_sound: row.get(26)?,
204 subtitles: row.get(27)?,
205 closed_captions: row.get(28)?,
206
207 has_save_data: row.get(29)?,
209 has_config_data: row.get(30)?,
210
211 languages_interface: parse_json_vec(row.get(31)?),
213 languages_audio: parse_json_vec(row.get(32)?),
214 languages_subtitles: parse_json_vec(row.get(33)?),
215
216 fetched_at: row.get(34)?,
217 })
218 },
219 );
220
221 match result {
222 Ok(data) => Some(data),
223 Err(rusqlite::Error::QueryReturnedNoRows) => None,
224 Err(e) => {
225 warn!("Erro ao buscar game_extras para {}: {}", steam_app_id, e);
226 None
227 }
228 }
229}
230
231pub fn save_pcgw_data(conn: &Connection, data: &GameExtras) -> Result<(), String> {
233 let serialize_vec = |v: &Option<Vec<String>>| -> Option<String> {
234 v.as_ref().and_then(|list| serde_json::to_string(list).ok())
235 };
236
237 conn.execute(
238 "INSERT OR REPLACE INTO game_extras (
239 steam_app_id,
240 pcgw_page_id,
241 pcgw_page_name,
242 engine,
243 available_on,
244
245 dx_versions,
246 vulkan_versions,
247 opengl_versions,
248
249 win64,
250 linux64,
251 macos_arm,
252 macos_intel64,
253
254 ray_tracing,
255 upscaling,
256 frame_gen,
257 ultrawidescreen,
258 four_k_support,
259 hdr,
260 high_fps,
261 fov,
262 borderless_windowed,
263 color_blind,
264
265 controller_support,
266 full_controller,
267 playstation_controllers,
268 xinput_controllers,
269
270 surround_sound,
271 subtitles,
272 closed_captions,
273
274 has_save_data,
275 has_config_data,
276
277 languages_interface,
278 languages_audio,
279 languages_subtitles,
280
281 fetched_at
282 ) VALUES (
283 ?1, ?2, ?3, ?4, ?5,
284 ?6, ?7, ?8,
285 ?9, ?10, ?11, ?12,
286 ?13, ?14, ?15, ?16, ?17,
287 ?18, ?19, ?20, ?21, ?22,
288 ?23, ?24, ?25, ?26,
289 ?27, ?28, ?29,
290 ?30, ?31,
291 ?32, ?33, ?34,
292 ?35
293 )",
294 params![
295 data.steam_app_id,
296 data.pcgw_page_id,
297 data.pcgw_page_name,
298 data.engine,
299 data.available_on,
300 data.dx_versions,
301 data.vulkan_versions,
302 data.opengl_versions,
303 data.win64,
304 data.linux64,
305 data.macos_arm,
306 data.macos_intel64,
307 data.ray_tracing,
308 data.upscaling,
309 data.frame_gen,
310 data.ultrawidescreen,
311 data.four_k_support,
312 data.hdr,
313 data.high_fps,
314 data.fov,
315 data.borderless_windowed,
316 data.color_blind,
317 data.controller_support,
318 data.full_controller,
319 data.playstation_controllers,
320 data.xinput_controllers,
321 data.surround_sound,
322 data.subtitles,
323 data.closed_captions,
324 data.has_save_data,
325 data.has_config_data,
326 serialize_vec(&data.languages_interface),
327 serialize_vec(&data.languages_audio),
328 serialize_vec(&data.languages_subtitles),
329 data.fetched_at,
330 ],
331 )
332 .map_err(|e| format!("Erro ao salvar game_extras: {}", e))?;
333
334 Ok(())
335}
336
337pub fn invalidate_pcgw_data(conn: &Connection, steam_app_id: &str) -> Result<(), String> {
342 conn.execute(
343 "UPDATE game_extras SET fetched_at = NULL WHERE steam_app_id = ?1",
344 params![steam_app_id],
345 )
346 .map_err(|e| format!("Erro ao invalidar game_extras: {}", e))?;
347
348 info!("game_extras invalidado para steam_app_id={}", steam_app_id);
349 Ok(())
350}
351
352pub fn save_scraped_data(
359 conn: &Connection,
360 steam_app_id: &str,
361 data: &PcgwScrapedData,
362) -> Result<(), String> {
363 use chrono::Utc;
364 use rusqlite::params;
365
366 let now = Utc::now().to_rfc3339();
367
368 conn.execute(
370 "DELETE FROM system_requirements WHERE steam_app_id = ?1",
371 params![steam_app_id],
372 )
373 .map_err(|e| format!("Erro ao limpar system_requirements: {}", e))?;
374
375 conn.execute(
376 "DELETE FROM game_data_paths WHERE steam_app_id = ?1",
377 params![steam_app_id],
378 )
379 .map_err(|e| format!("Erro ao limpar game_data_paths: {}", e))?;
380
381 let mut sysreq_stmt = conn
383 .prepare(
384 "INSERT INTO system_requirements (
385 steam_app_id, os_family, tier_title, target,
386 min_os, min_cpu, min_cpu2, min_ram, min_gpu, min_gpu2, min_vram, min_dx, min_storage,
387 rec_os, rec_cpu, rec_cpu2, rec_ram, rec_gpu, rec_gpu2, rec_vram, rec_dx, rec_storage,
388 fetched_at
389 ) VALUES (
390 ?1, ?2, ?3, ?4,
391 ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13,
392 ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22,
393 ?23
394 )",
395 )
396 .map_err(|e| format!("Erro ao preparar insert de system_requirements: {}", e))?;
397
398 for req in &data.system_requirements {
399 sysreq_stmt
400 .execute(params![
401 steam_app_id,
402 req.os_family,
403 req.tier_title,
404 req.target,
405 req.min_os,
406 req.min_cpu,
407 req.min_cpu2,
408 req.min_ram,
409 req.min_gpu,
410 req.min_gpu2,
411 req.min_vram,
412 req.min_dx,
413 req.min_storage,
414 req.rec_os,
415 req.rec_cpu,
416 req.rec_cpu2,
417 req.rec_ram,
418 req.rec_gpu,
419 req.rec_gpu2,
420 req.rec_vram,
421 req.rec_dx,
422 req.rec_storage,
423 now,
424 ])
425 .map_err(|e| {
426 let msg = format!("Erro ao inserir system_requirement: {}", e);
427 tracing::error!("{}", msg);
428 msg
429 })?;
430 }
431
432 let mut path_stmt = conn
434 .prepare(
435 "INSERT INTO game_data_paths (steam_app_id, kind, os, raw_path, fetched_at)
436 VALUES (?1, ?2, ?3, ?4, ?5)",
437 )
438 .map_err(|e| format!("Erro ao preparar insert de game_data_paths: {}", e))?;
439
440 for path in &data.game_data_paths {
441 path_stmt
442 .execute(params![
443 steam_app_id,
444 path.kind,
445 path.os,
446 path.raw_path,
447 now,
448 ])
449 .map_err(|e| format!("Erro ao inserir game_data_path: {}", e))?;
450 }
451
452 Ok(())
453}
454
455pub fn get_system_requirements(conn: &Connection, steam_app_id: &str) -> Vec<SystemRequirements> {
458 let mut stmt = match conn.prepare(
459 "SELECT os_family, tier_title, target,
460 min_os, min_cpu, min_cpu2, min_ram, min_gpu, min_gpu2, min_vram, min_dx, min_storage,
461 rec_os, rec_cpu, rec_cpu2, rec_ram, rec_gpu, rec_gpu2, rec_vram, rec_dx, rec_storage
462 FROM system_requirements
463 WHERE steam_app_id = ?1
464 ORDER BY id ASC",
465 ) {
466 Ok(s) => s,
467 Err(_) => return Vec::new(),
468 };
469
470 let iter = stmt.query_map(rusqlite::params![steam_app_id], |row| {
471 Ok(SystemRequirements {
472 steam_app_id: steam_app_id.to_string(),
473 os_family: row.get(0)?,
474 tier_title: row.get(1)?,
475 target: row.get(2)?,
476 min_os: row.get(3)?,
477 min_cpu: row.get(4)?,
478 min_cpu2: row.get(5)?,
479 min_ram: row.get(6)?,
480 min_gpu: row.get(7)?,
481 min_gpu2: row.get(8)?,
482 min_vram: row.get(9)?,
483 min_dx: row.get(10)?,
484 min_storage: row.get(11)?,
485 rec_os: row.get(12)?,
486 rec_cpu: row.get(13)?,
487 rec_cpu2: row.get(14)?,
488 rec_ram: row.get(15)?,
489 rec_gpu: row.get(16)?,
490 rec_gpu2: row.get(17)?,
491 rec_vram: row.get(18)?,
492 rec_dx: row.get(19)?,
493 rec_storage: row.get(20)?,
494 })
495 });
496
497 match iter {
498 Ok(rows) => rows.filter_map(|r| r.ok()).collect(),
499 Err(_) => Vec::new(),
500 }
501}
502
503pub fn get_game_data_paths(conn: &rusqlite::Connection, steam_app_id: &str) -> Vec<GameDataPath> {
506 let mut stmt = match conn.prepare(
507 "SELECT kind, os, raw_path
508 FROM game_data_paths
509 WHERE steam_app_id = ?1
510 ORDER BY id ASC",
511 ) {
512 Ok(s) => s,
513 Err(_) => return Vec::new(),
514 };
515
516 let iter = stmt.query_map(rusqlite::params![steam_app_id], |row| {
517 Ok(GameDataPath {
518 steam_app_id: steam_app_id.to_string(),
519 kind: row.get(0)?,
520 os: row.get(1)?,
521 raw_path: row.get(2)?,
522 expanded_path: None,
523 })
524 });
525
526 match iter {
527 Ok(rows) => rows.filter_map(|r| r.ok()).collect(),
528 Err(_) => Vec::new(),
529 }
530}