use crate::types::SearchResponse;
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
const CACHE_TTL_SECS: u64 = 300;
fn cache_dir() -> PathBuf {
if let Some(proj) = ProjectDirs::from("", "", "search") {
proj.cache_dir().to_path_buf()
} else {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
PathBuf::from(home).join(".cache").join("search")
}
}
fn last_path() -> PathBuf {
cache_dir().join("last.json")
}
fn stable_hash(query: &str, mode: &str) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
let feed = |h: &mut u64, bytes: &[u8]| {
for &b in bytes {
*h ^= b as u64;
*h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
};
feed(&mut h, query.to_lowercase().as_bytes());
feed(&mut h, &[0]); feed(&mut h, mode.as_bytes());
h
}
fn query_cache_path(query: &str, mode: &str) -> PathBuf {
cache_dir().join(format!("q2_{:016x}.json", stable_hash(query, mode)))
}
pub fn save_last(response: &SearchResponse) {
let dir = cache_dir();
let _ = std::fs::create_dir_all(&dir);
if let Ok(json) = serde_json::to_string(response) {
let _ = std::fs::write(last_path(), &json);
}
}
pub fn load_last() -> Option<SearchResponse> {
let content = std::fs::read_to_string(last_path()).ok()?;
serde_json::from_str(&content).ok()
}
#[derive(Serialize, Deserialize)]
struct CachedEntry {
timestamp: u64,
#[serde(default)]
count: usize,
response: SearchResponse,
}
fn now_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
pub fn save_query(query: &str, mode: &str, count: usize, response: &SearchResponse) {
let dir = cache_dir();
let _ = std::fs::create_dir_all(&dir);
let entry = CachedEntry {
timestamp: now_secs(),
count,
response: response.clone(),
};
if let Ok(json) = serde_json::to_string(&entry) {
let _ = std::fs::write(query_cache_path(query, mode), json);
}
}
pub fn load_query(query: &str, mode: &str, count: usize) -> Option<SearchResponse> {
let path = query_cache_path(query, mode);
let content = std::fs::read_to_string(path).ok()?;
let entry: CachedEntry = serde_json::from_str(&content).ok()?;
let fresh = now_secs().saturating_sub(entry.timestamp) < CACHE_TTL_SECS;
if fresh && entry.count >= count {
let mut resp = entry.response;
resp.results.truncate(count);
resp.metadata.result_count = resp.results.len();
Some(resp)
} else {
None
}
}