use crate::constants::{DATABASE_FILE_NAME, METIS_DIR_NAME};
use crate::{Application, Database};
use anyhow::Result;
use std::path::{Path, PathBuf};
pub struct WorkspaceDetectionService;
impl WorkspaceDetectionService {
pub fn new() -> Self {
Self
}
pub fn find_workspace(&self) -> Result<Option<PathBuf>> {
let mut current_dir = std::env::current_dir()?;
loop {
let metis_dir = current_dir.join(METIS_DIR_NAME);
if let Some(validated_dir) = self.validate_workspace(&metis_dir)? {
return Ok(Some(validated_dir));
}
match current_dir.parent() {
Some(parent) => current_dir = parent.to_path_buf(),
None => break, }
}
Ok(None)
}
pub fn find_workspace_from(&self, start_path: &Path) -> Result<Option<PathBuf>> {
let mut current_dir = start_path.to_path_buf();
loop {
let metis_dir = current_dir.join(METIS_DIR_NAME);
if let Some(validated_dir) = self.validate_workspace(&metis_dir)? {
return Ok(Some(validated_dir));
}
match current_dir.parent() {
Some(parent) => current_dir = parent.to_path_buf(),
None => break, }
}
Ok(None)
}
pub fn validate_workspace(&self, metis_dir: &Path) -> Result<Option<PathBuf>> {
if !metis_dir.exists() || !metis_dir.is_dir() {
return Ok(None);
}
Ok(Some(metis_dir.to_path_buf()))
}
pub fn is_in_workspace(&self, path: &Path) -> Result<bool> {
Ok(self.find_workspace_from(path)?.is_some())
}
pub fn get_workspace_root(&self, path: &Path) -> Result<Option<PathBuf>> {
if let Some(metis_dir) = self.find_workspace_from(path)? {
if let Some(parent) = metis_dir.parent() {
return Ok(Some(parent.to_path_buf()));
}
}
Ok(None)
}
pub async fn prepare_workspace(&self, metis_dir: &Path) -> Result<Database> {
if self.validate_workspace(metis_dir)?.is_none() {
anyhow::bail!("Not a valid Metis workspace: {}", metis_dir.display());
}
let db_path = metis_dir.join(DATABASE_FILE_NAME);
let database = Database::new(db_path.to_str().unwrap())
.map_err(|e| anyhow::anyhow!("Failed to initialize database: {}", e))?;
let app = Application::new(database);
app.sync_directory(metis_dir).await?;
let database = Database::new(db_path.to_str().unwrap())
.map_err(|e| anyhow::anyhow!("Failed to reconnect to database: {}", e))?;
Ok(database)
}
pub async fn find_and_prepare_workspace(&self) -> Result<Option<(PathBuf, Database)>> {
if let Some(metis_dir) = self.find_workspace()? {
let db = self.prepare_workspace(&metis_dir).await?;
Ok(Some((metis_dir, db)))
} else {
Ok(None)
}
}
}
impl Default for WorkspaceDetectionService {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_validate_workspace_missing_directory() {
let service = WorkspaceDetectionService::new();
let temp_dir = TempDir::new().unwrap();
let metis_dir = temp_dir.path().join(METIS_DIR_NAME);
let result = service.validate_workspace(&metis_dir).unwrap();
assert!(result.is_none());
}
#[test]
fn test_validate_workspace_with_metis_directory() {
let service = WorkspaceDetectionService::new();
let temp_dir = TempDir::new().unwrap();
let metis_dir = temp_dir.path().join(METIS_DIR_NAME);
fs::create_dir_all(&metis_dir).unwrap();
let result = service.validate_workspace(&metis_dir);
assert!(result.is_ok());
assert!(result.unwrap().is_some());
}
#[test]
fn test_find_workspace_traversal() {
let service = WorkspaceDetectionService::new();
let temp_dir = TempDir::new().unwrap();
let project_root = temp_dir.path();
let metis_dir = project_root.join(".metis");
let nested_dir = project_root.join("src").join("deep").join("nested");
fs::create_dir_all(&metis_dir).unwrap();
fs::create_dir_all(&nested_dir).unwrap();
let result = service.find_workspace_from(&nested_dir).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap(), metis_dir);
}
#[test]
fn test_get_workspace_root() {
let service = WorkspaceDetectionService::new();
let temp_dir = TempDir::new().unwrap();
let project_root = temp_dir.path();
let metis_dir = project_root.join(".metis");
let nested_dir = project_root.join("src").join("deep");
fs::create_dir_all(&metis_dir).unwrap();
fs::create_dir_all(&nested_dir).unwrap();
let result = service.get_workspace_root(&nested_dir).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap(), project_root);
}
}