pub mod dlsite_provider;
pub mod igdb_provider;
pub mod thegamesdb_provider;
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{RwLock, Semaphore};
use serde::{Serialize, Deserialize};
use crate::models::game_meta_data::GameMetadata;
use crate::logger::{get_logger, LogEvent, LogLevel};
fn string_similarity(s1: &str, s2: &str) -> f32 {
let len1 = s1.chars().count();
let len2 = s2.chars().count();
if len1 == 0 && len2 == 0 {
return 1.0;
}
if len1 == 0 || len2 == 0 {
return 0.0;
}
let max_len = len1.max(len2);
let distance = levenshtein_distance(s1, s2);
1.0 - (distance as f32 / max_len as f32)
}
fn levenshtein_distance(s1: &str, s2: &str) -> usize {
let s1_chars: Vec<char> = s1.chars().collect();
let s2_chars: Vec<char> = s2.chars().collect();
let len1 = s1_chars.len();
let len2 = s2_chars.len();
if len1 == 0 {
return len2;
}
if len2 == 0 {
return len1;
}
let mut prev_row = vec![0; len2 + 1];
let mut curr_row = vec![0; len2 + 1];
for j in 0..=len2 {
prev_row[j] = j;
}
for i in 1..=len1 {
curr_row[0] = i;
for j in 1..=len2 {
let cost = if s1_chars[i - 1] == s2_chars[j - 1] { 0 } else { 1 };
curr_row[j] = (prev_row[j] + 1) .min(curr_row[j - 1] + 1) .min(prev_row[j - 1] + cost); }
std::mem::swap(&mut prev_row, &mut curr_row);
}
prev_row[len2]
}
fn calculate_confidence(search_title: &str, metadata: &GameMetadata) -> f32 {
let mut confidence = 0.0;
if let Some(title) = &metadata.title {
let search_lower = search_title.to_lowercase();
let title_lower = title.to_lowercase();
if search_lower == title_lower {
confidence += 0.7;
}
else if title_lower.contains(&search_lower) {
let ratio = search_lower.len() as f32 / title_lower.len() as f32;
confidence += 0.5 + (ratio * 0.2);
}
else if search_lower.contains(&title_lower) {
let ratio = title_lower.len() as f32 / search_lower.len() as f32;
confidence += 0.4 + (ratio * 0.2);
}
else {
let similarity = string_similarity(&search_lower, &title_lower);
if similarity > 0.8 {
confidence += 0.5 * similarity;
} else if similarity > 0.5 {
confidence += 0.3 * similarity;
} else {
let search_words: Vec<&str> = search_lower.split_whitespace().collect();
let title_words: Vec<&str> = title_lower.split_whitespace().collect();
let mut matches = 0;
let mut total_match_len = 0;
for sw in &search_words {
for tw in &title_words {
if tw.contains(sw) || sw.contains(tw) {
matches += 1;
total_match_len += sw.len().min(tw.len());
break;
}
}
}
if !search_words.is_empty() {
let match_ratio = matches as f32 / search_words.len() as f32;
let length_ratio = total_match_len as f32 / search_lower.len() as f32;
confidence += 0.2 * match_ratio + 0.1 * length_ratio;
}
}
}
}
let mut completeness = 0.0;
if metadata.title.is_some() { completeness += 0.08; }
if metadata.cover_url.is_some() { completeness += 0.05; }
if metadata.description.is_some() { completeness += 0.04; }
if metadata.release_date.is_some() { completeness += 0.04; }
if metadata.developer.is_some() { completeness += 0.04; }
if metadata.publisher.is_some() { completeness += 0.03; }
if metadata.genres.is_some() { completeness += 0.01; }
if metadata.tags.is_some() { completeness += 0.01; }
confidence += completeness;
confidence.max(0.0).min(1.0)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GameQueryResult {
pub info: GameMetadata,
pub source: String,
pub confidence: f32,
}
#[async_trait]
pub trait GameDatabaseProvider: Send + Sync {
fn name(&self) -> &str;
async fn search(&self, title: &str) -> Result<Vec<GameMetadata>, Box<dyn std::error::Error + Send + Sync>>;
async fn get_by_id(&self, _id: &str) -> Result<GameMetadata, Box<dyn std::error::Error + Send + Sync>> {
Err("Not implemented".into())
}
fn priority(&self) -> u32 {
50
}
fn supports_game_type(&self, _game_type: &str) -> bool {
true
}
}
pub struct GameDatabaseMiddleware {
providers: Arc<RwLock<Vec<Arc<dyn GameDatabaseProvider>>>>,
cache: Arc<RwLock<HashMap<String, Vec<GameQueryResult>>>>, cache_ttl: std::time::Duration,
rate_limiter: Arc<Semaphore>,
}
impl GameDatabaseMiddleware {
pub fn new() -> Self {
GameDatabaseMiddleware {
providers: Arc::new(RwLock::new(Vec::new())),
cache: Arc::new(RwLock::new(HashMap::new())),
cache_ttl: std::time::Duration::from_secs(3600), rate_limiter: Arc::new(Semaphore::new(5)), }
}
pub async fn register_provider(&self, provider: Arc<dyn GameDatabaseProvider>) {
let mut providers = self.providers.write().await;
providers.push(provider);
providers.sort_by(|a, b| b.priority().cmp(&a.priority()));
}
pub async fn unregister_provider(&self, name: &str) {
let mut providers = self.providers.write().await;
providers.retain(|p| p.name() != name);
}
pub async fn search(&self, title: &str) -> Result<Vec<GameQueryResult>, Box<dyn std::error::Error + Send + Sync>> {
self.search_with_timeout(title, std::time::Duration::from_secs(30)).await
}
pub async fn search_with_timeout(
&self,
title: &str,
timeout: std::time::Duration
) -> Result<Vec<GameQueryResult>, Box<dyn std::error::Error + Send + Sync>> {
let logger = get_logger();
let cache = self.cache.read().await;
if let Some(cached_results) = cache.get(title) {
logger.log(&LogEvent::new(
LogLevel::Info,
format!("从缓存获取: {} 条结果", cached_results.len())
));
return Ok(cached_results.clone()); }
drop(cache);
let providers = self.providers.read().await;
let mut results = Vec::new();
let mut futures = Vec::new();
for provider in providers.iter() {
let provider = Arc::clone(provider);
let title_clone = title.to_string();
let provider_name = provider.name().to_string();
let rate_limiter = Arc::clone(&self.rate_limiter);
futures.push(async move {
let _permit = rate_limiter.acquire().await.unwrap();
match provider.search(&title_clone).await {
Ok(games) => {
games.into_iter().map(|info| {
let confidence = calculate_confidence(&title_clone, &info);
GameQueryResult {
info,
source: provider_name.clone(),
confidence,
}
}).collect::<Vec<_>>()
},
Err(_e) => {
Vec::new()
},
}
});
}
let query_future = futures::future::join_all(futures);
let query_results = match tokio::time::timeout(timeout, query_future).await {
Ok(results) => results,
Err(_) => {
logger.log(&LogEvent::new(
LogLevel::Warning,
"查询超时"
));
return Err("查询超时".into());
}
};
for query_result in query_results {
results.extend(query_result);
}
results.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal));
if !results.is_empty() {
let mut cache = self.cache.write().await;
cache.insert(title.to_string(), results.clone());
}
Ok(results)
}
pub async fn get_by_id(&self, id: &str) -> Result<GameQueryResult, Box<dyn std::error::Error + Send + Sync>> {
let providers = self.providers.read().await;
for provider in providers.iter() {
match provider.get_by_id(id).await {
Ok(info) => {
return Ok(GameQueryResult {
info,
source: provider.name().to_string(),
confidence: 0.95,
});
},
Err(_) => continue,
}
}
Err("Game not found".into())
}
pub async fn list_providers(&self) -> Vec<String> {
let providers = self.providers.read().await;
providers.iter().map(|p| p.name().to_string()).collect()
}
pub async fn clear_cache(&self) {
let mut cache = self.cache.write().await;
cache.clear();
}
pub async fn cache_size(&self) -> usize {
let cache = self.cache.read().await;
cache.len()
}
}