use std::fs;
use std::path::{Path, PathBuf};
use crate::error::{Result, VfsError};
use crate::storage::BackendType;
#[derive(Clone)]
pub struct Config {
base_dir: PathBuf,
}
impl Config {
pub fn new() -> Result<Self> {
let base_dir = Self::default_base_dir()?;
let config = Self { base_dir };
config.ensure_dirs()?;
Ok(config)
}
pub fn with_base_dir(base_dir: PathBuf) -> Result<Self> {
let config = Self { base_dir };
config.ensure_dirs()?;
Ok(config)
}
fn default_base_dir() -> Result<PathBuf> {
let home = dirs::home_dir().ok_or_else(|| {
VfsError::Internal("could not determine home directory".to_string())
})?;
Ok(home.join(".avfs"))
}
fn ensure_dirs(&self) -> Result<()> {
fs::create_dir_all(self.vaults_dir())?;
Ok(())
}
pub fn base_dir(&self) -> &Path {
&self.base_dir
}
pub fn vaults_dir(&self) -> PathBuf {
self.base_dir.join("vaults")
}
pub fn vault_path_with_backend(&self, name: &str, backend: BackendType) -> PathBuf {
self.vaults_dir()
.join(format!("{}.{}", name, backend.extension()))
}
pub fn vault_path(&self, name: &str) -> PathBuf {
for backend in BackendType::available() {
let path = self.vault_path_with_backend(name, backend);
if path.exists() {
return path;
}
}
self.vault_path_with_backend(name, BackendType::Sqlite)
}
pub fn vault_backend(&self, name: &str) -> Option<BackendType> {
for backend in BackendType::available() {
let path = self.vault_path_with_backend(name, backend);
if path.exists() {
return Some(backend);
}
}
None
}
pub fn current_vault_file(&self) -> PathBuf {
self.base_dir.join("current")
}
pub fn current_vault(&self) -> Result<Option<String>> {
let path = self.current_vault_file();
if !path.exists() {
return Ok(None);
}
let content = fs::read_to_string(&path)?;
let name = content.trim();
if name.is_empty() {
return Ok(None);
}
if !self.vault_path(name).exists() {
fs::remove_file(&path)?;
return Ok(None);
}
Ok(Some(name.to_string()))
}
pub fn set_current_vault(&self, name: &str) -> Result<()> {
if !self.vault_path(name).exists() {
return Err(VfsError::VaultNotFound(name.to_string()));
}
fs::write(self.current_vault_file(), name)?;
Ok(())
}
pub fn clear_current_vault(&self) -> Result<()> {
let path = self.current_vault_file();
if path.exists() {
fs::remove_file(&path)?;
}
Ok(())
}
pub fn list_vaults(&self) -> Result<Vec<String>> {
let mut vaults = Vec::new();
let vaults_dir = self.vaults_dir();
if !vaults_dir.exists() {
return Ok(vaults);
}
let valid_extensions: Vec<&str> = BackendType::available()
.iter()
.map(|b| b.extension())
.collect();
for entry in fs::read_dir(&vaults_dir)? {
let entry = entry?;
let path = entry.path();
if let Some(ext) = path.extension() {
let ext_str = ext.to_string_lossy();
if valid_extensions.contains(&ext_str.as_ref()) {
if let Some(stem) = path.file_stem() {
let name = stem.to_string_lossy().to_string();
if !vaults.contains(&name) {
vaults.push(name);
}
}
}
}
}
vaults.sort();
Ok(vaults)
}
pub fn vault_exists(&self, name: &str) -> bool {
self.vault_backend(name).is_some()
}
pub fn vault_exists_with_backend(&self, name: &str, backend: BackendType) -> bool {
self.vault_path_with_backend(name, backend).exists()
}
pub fn delete_vault_file(&self, name: &str) -> Result<()> {
let backend = self.vault_backend(name).ok_or_else(|| {
VfsError::VaultNotFound(name.to_string())
})?;
let path = self.vault_path_with_backend(name, backend);
match backend {
BackendType::Sqlite => {
let wal_path = path.with_extension("avfs-wal");
let shm_path = path.with_extension("avfs-shm");
fs::remove_file(&path)?;
let _ = fs::remove_file(&wal_path);
let _ = fs::remove_file(&shm_path);
}
#[cfg(feature = "sled-backend")]
BackendType::Sled => {
if path.is_dir() {
fs::remove_dir_all(&path)?;
} else {
fs::remove_file(&path)?;
}
let index_path = path.with_extension("tantivy");
let _ = fs::remove_dir_all(&index_path);
}
#[cfg(feature = "lmdb-backend")]
BackendType::Lmdb => {
if path.is_dir() {
fs::remove_dir_all(&path)?;
} else {
fs::remove_file(&path)?;
}
let index_path = path.with_extension("lmdb.tantivy");
let _ = fs::remove_dir_all(&index_path);
}
}
if let Ok(Some(current)) = self.current_vault() {
if current == name {
self.clear_current_vault()?;
}
}
Ok(())
}
}
impl Default for Config {
fn default() -> Self {
Self::new().expect("failed to initialize config")
}
}