use crate::apps::AppScanner;
use crate::search::SearchEngine;
use crate::types::{EntryType, FileEntry, SearchResult};
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use walkdir::WalkDir;
pub struct FileIndex {
entries: Vec<FileEntry>,
cache_file: PathBuf,
include_apps: bool,
search_engine: SearchEngine,
app_scanner: AppScanner,
}
impl FileIndex {
pub fn new(include_apps: bool) -> Self {
let cache_dir = dirs::cache_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("listary");
if !cache_dir.exists() {
fs::create_dir_all(&cache_dir).ok();
}
Self {
entries: Vec::new(),
cache_file: cache_dir.join("file_index.json"),
include_apps,
search_engine: SearchEngine::new(),
app_scanner: AppScanner::new(),
}
}
pub fn build_index(&mut self, paths: &[PathBuf]) {
self.entries.clear();
self.scan_files_and_directories(paths);
if self.include_apps {
self.app_scanner.scan_applications(&mut self.entries);
}
self.save_cache();
}
pub fn search(&self, query: &str, limit: usize) -> Vec<SearchResult> {
self.search_engine.search(&self.entries, query, limit)
}
pub fn find_single_application_match(&self, query: &str) -> Option<&FileEntry> {
self.search_engine.find_single_application_match(&self.entries, query)
}
pub fn find_exact_application_match(&self, query: &str) -> Option<&FileEntry> {
self.search_engine.find_exact_application_match(&self.entries, query)
}
pub fn launch_application(&self, entry: &FileEntry) -> Result<(), String> {
self.app_scanner.launch_application(entry)
}
pub fn open_entry(&self, entry: &FileEntry) -> Result<(), String> {
self.app_scanner.open_entry(entry)
}
pub fn load_cache(&mut self) -> bool {
if let Ok(json) = fs::read_to_string(&self.cache_file) {
if let Ok(entries) = serde_json::from_str::<Vec<FileEntry>>(&json) {
self.entries = entries;
return true;
}
}
false
}
pub fn entries_count(&self) -> usize {
self.entries.len()
}
fn scan_files_and_directories(&mut self, paths: &[PathBuf]) {
for base_path in paths {
if !base_path.exists() {
continue;
}
for entry in WalkDir::new(base_path)
.follow_links(false)
.max_depth(10)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if self.should_skip_file(path) {
continue;
}
let metadata = match entry.metadata() {
Ok(m) => m,
Err(_) => continue,
};
let modified = self.get_modified_time(&metadata);
let name = self.get_file_name(path);
if !name.is_empty() {
self.entries.push(FileEntry {
path: path.to_path_buf(),
name,
size: metadata.len(),
modified,
entry_type: if metadata.is_dir() {
EntryType::Directory
} else {
EntryType::File
},
executable_path: None,
description: None,
});
}
}
}
}
fn should_skip_file(&self, path: &std::path::Path) -> bool {
path.file_name()
.and_then(|n| n.to_str())
.map(|n| n.starts_with('.'))
.unwrap_or(false)
}
fn get_modified_time(&self, metadata: &std::fs::Metadata) -> u64 {
metadata
.modified()
.unwrap_or(SystemTime::UNIX_EPOCH)
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
fn get_file_name(&self, path: &std::path::Path) -> String {
path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string()
}
fn save_cache(&self) {
if let Ok(json) = serde_json::to_string(&self.entries) {
fs::write(&self.cache_file, json).ok();
}
}
}