use super::{VaultDb, schema};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use walkdir;
use crate::file_types::{detect_file_type, FileType};
pub fn index_file(vault_db: &VaultDb, file_path: &Path, vault_root: &Path) -> Result<(), String> {
if !file_path.is_file() {
return Err(format!("File not found: {}", file_path.display()));
}
let rel_path = file_path
.strip_prefix(vault_root)
.map_err(|_| "File is outside vault root".to_string())?
.to_string_lossy()
.into_owned();
let metadata = std::fs::metadata(file_path)
.map_err(|e| format!("Failed to read metadata: {}", e))?;
let file_size = metadata.len();
let modified_time = metadata
.modified()
.unwrap_or(SystemTime::now())
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let (file_type_str, language) = match detect_file_type(file_path) {
FileType::Markdown => ("markdown".to_string(), None),
FileType::Code { language } => ("code".to_string(), Some(language)),
FileType::Image { format } => (format!("image/{}", format), None),
FileType::Hex => ("binary".to_string(), None),
};
let file_name = file_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
let extension = file_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_string();
vault_db
.conn
.execute(
"INSERT OR REPLACE INTO files (path, name, extension, file_type, language, size, modified_at, created_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
rusqlite::params![
&rel_path,
&file_name,
&extension,
&file_type_str,
&language,
file_size as i64,
modified_time,
modified_time,
],
)
.map_err(|e| format!("Failed to insert file into database: {}", e))?;
Ok(())
}
pub fn index_vault(vault_db: &VaultDb) -> Result<usize, String> {
let vault_root = vault_db.vault_root.clone();
let start_time = std::time::Instant::now();
tracing::info!("Starting vault indexing for: {}", vault_root.display());
const SKIP_DIRS: &[&str] = &[".git", ".obsidian", ".tomarkdown", "node_modules", "target"];
let mut file_count = 0;
let walker = walkdir::WalkDir::new(&vault_root)
.into_iter()
.filter_entry(|entry| {
if entry.file_type().is_dir() {
if let Some(name) = entry.file_name().to_str() {
return !SKIP_DIRS.contains(&name);
}
}
true
})
.filter_map(|e| e.ok());
for entry in walker {
let path = entry.path();
if path.is_dir() {
continue;
}
match index_file(vault_db, path, &vault_root) {
Ok(_) => {
file_count += 1;
tracing::debug!("Indexed file: {}", path.display());
}
Err(e) => {
tracing::warn!("Failed to index {}: {}", path.display(), e);
}
}
}
let elapsed = start_time.elapsed();
tracing::info!(
"Vault indexing complete: {} files indexed in {:.2}s",
file_count,
elapsed.as_secs_f64()
);
Ok(file_count)
}