use std::path::PathBuf;
use std::sync::Arc;
use chrono::{DateTime, TimeZone, Utc};
use crate::error::{Result, VfsError};
use crate::runtime::workspace::{dir_size, validate_workspace_name, WorkspaceService};
#[cfg(feature = "lmdb-backend")]
use crate::storage::LmdbBackend;
#[cfg(feature = "sled-backend")]
use crate::storage::SledBackend;
use crate::storage::SqliteBackend;
use crate::storage::{BackendType, VaultBackend};
use crate::vault::Config;
#[derive(Debug, Clone)]
pub struct VaultInfo {
pub name: String,
pub is_current: bool,
pub size: u64,
pub created_at: Option<DateTime<Utc>>,
pub backend: BackendType,
}
#[derive(Debug, Clone)]
pub struct ForkInfo {
pub source: String,
pub name: String,
pub backend: BackendType,
pub path: PathBuf,
pub copy_on_write: bool,
}
pub struct VaultManager {
config: Config,
}
impl VaultManager {
pub fn new() -> Result<Self> {
Ok(Self {
config: Config::new()?,
})
}
pub fn with_config(config: Config) -> Self {
Self { config }
}
pub fn create_with_backend(&self, name: &str, backend: BackendType) -> Result<()> {
validate_workspace_name(name)?;
if self.config.vault_exists(name) {
return Err(VfsError::VaultExists(name.to_string()));
}
let path = self.config.vault_path_with_backend(name, backend);
match backend {
BackendType::Sqlite => {
let _backend = SqliteBackend::open(&path)?;
}
#[cfg(feature = "sled-backend")]
BackendType::Sled => {
let _backend = SledBackend::open(&path)?;
}
#[cfg(feature = "lmdb-backend")]
BackendType::Lmdb => {
let _backend = LmdbBackend::open(&path)?;
}
}
if self.config.current_vault()?.is_none() {
self.config.set_current_vault(name)?;
}
Ok(())
}
pub fn create(&self, name: &str) -> Result<()> {
self.create_with_backend(name, BackendType::Sqlite)
}
pub fn list(&self) -> Result<Vec<VaultInfo>> {
let names = self.config.list_vaults()?;
let mut vaults = Vec::new();
for name in names {
let info = self.info(&name)?;
vaults.push(info);
}
Ok(vaults)
}
pub fn info(&self, name: &str) -> Result<VaultInfo> {
let backend_type = self
.config
.vault_backend(name)
.ok_or_else(|| VfsError::VaultNotFound(name.to_string()))?;
let current = self.config.current_vault()?;
let path = self.config.vault_path_with_backend(name, backend_type);
let size = if path.is_dir() {
dir_size(&path)
} else {
std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0)
};
let created_at = match backend_type {
BackendType::Sqlite => {
if let Ok(backend) = SqliteBackend::open(&path) {
if let Ok(Some(ts_str)) = backend.get_setting("created_at") {
if let Ok(ts) = ts_str.parse::<i64>() {
Utc.timestamp_opt(ts, 0).single()
} else {
None
}
} else {
None
}
} else {
None
}
}
#[cfg(feature = "sled-backend")]
BackendType::Sled => {
if let Ok(backend) = SledBackend::open(&path) {
if let Ok(Some(ts_str)) = backend.get_setting("created_at") {
if let Ok(ts) = ts_str.parse::<i64>() {
Utc.timestamp_opt(ts, 0).single()
} else {
None
}
} else {
None
}
} else {
None
}
}
#[cfg(feature = "lmdb-backend")]
BackendType::Lmdb => {
if let Ok(backend) = LmdbBackend::open(&path) {
if let Ok(Some(ts_str)) = backend.get_setting("created_at") {
if let Ok(ts) = ts_str.parse::<i64>() {
Utc.timestamp_opt(ts, 0).single()
} else {
None
}
} else {
None
}
} else {
None
}
}
};
Ok(VaultInfo {
name: name.to_string(),
is_current: current.as_deref() == Some(name),
size,
created_at,
backend: backend_type,
})
}
pub fn use_vault(&self, name: &str) -> Result<()> {
if !self.config.vault_exists(name) {
return Err(VfsError::VaultNotFound(name.to_string()));
}
self.config.set_current_vault(name)?;
Ok(())
}
pub fn delete(&self, name: &str) -> Result<()> {
self.config.delete_vault_file(name)
}
pub fn fork(&self, source: &str, name: &str) -> Result<ForkInfo> {
WorkspaceService::with_config(self.config.clone()).fork(source, name)
}
pub fn current(&self) -> Result<Option<String>> {
self.config.current_vault()
}
pub fn open_current(&self) -> Result<Arc<VaultBackend>> {
let name = self
.config
.current_vault()?
.ok_or(VfsError::NoActiveVault)?;
self.open(&name)
}
pub fn open(&self, name: &str) -> Result<Arc<VaultBackend>> {
let backend = self
.config
.vault_backend(name)
.ok_or_else(|| VfsError::VaultNotFound(name.to_string()))?;
let path = self.config.vault_path_with_backend(name, backend);
Ok(Arc::new(VaultBackend::open(&path, backend)?))
}
}
impl Default for VaultManager {
fn default() -> Self {
Self::new().expect("failed to create vault manager")
}
}