agtrace_runtime/client/
workspace.rs

1use crate::client::{InsightOps, MonitorBuilder, ProjectOps, SessionOps, WatchService};
2use crate::config::Config;
3use crate::init::{InitConfig, InitProgress, InitResult, InitService};
4use crate::ops::{CheckResult, DoctorService, InspectResult};
5use crate::{Error, Result};
6use agtrace_engine::DiagnoseResult;
7use agtrace_index::Database;
8use agtrace_providers::ProviderAdapter;
9use std::path::PathBuf;
10use std::sync::{Arc, Mutex};
11use tokio::task;
12
13pub struct AgTrace {
14    db: Arc<Mutex<Database>>,
15    config: Arc<Config>,
16    provider_configs: Arc<Vec<(String, PathBuf)>>,
17}
18
19impl AgTrace {
20    pub fn setup<F>(config: InitConfig, progress_fn: Option<F>) -> Result<InitResult>
21    where
22        F: FnMut(InitProgress),
23    {
24        InitService::run(config, progress_fn)
25    }
26
27    pub async fn connect_or_create(data_dir: PathBuf) -> Result<Self> {
28        let db_path = data_dir.join("agtrace.db");
29        let config_path = data_dir.join("config.toml");
30
31        if !data_dir.exists() {
32            std::fs::create_dir_all(&data_dir)?;
33        }
34
35        let config = if tokio::fs::try_exists(&config_path).await? {
36            Config::load_from(&config_path)?
37        } else {
38            let detected = Config::detect_providers()?;
39            detected.save_to(&config_path)?;
40            detected
41        };
42
43        let db = task::spawn_blocking(move || Database::open(&db_path).map_err(Error::Index))
44            .await
45            .map_err(|e| Error::InvalidOperation(format!("Task join error: {}", e)))??;
46
47        let provider_configs: Vec<(String, PathBuf)> = config
48            .enabled_providers()
49            .into_iter()
50            .map(|(name, cfg)| (name.clone(), cfg.log_root.clone()))
51            .collect();
52
53        Ok(Self {
54            db: Arc::new(Mutex::new(db)),
55            config: Arc::new(config),
56            provider_configs: Arc::new(provider_configs),
57        })
58    }
59
60    pub async fn open(data_dir: PathBuf) -> Result<Self> {
61        let db_path = data_dir.join("agtrace.db");
62        let config_path = data_dir.join("config.toml");
63
64        // Database operations wrapped in spawn_blocking
65        let db = task::spawn_blocking(move || {
66            Database::open(&db_path).map_err(|e| {
67                if !db_path.exists() {
68                    Error::NotInitialized(format!(
69                        "Database not found. Please run 'agtrace init' to initialize the workspace.\n\
70                         Database path: {}",
71                        db_path.display()
72                    ))
73                } else {
74                    Error::Index(e)
75                }
76            })
77        })
78        .await
79        .map_err(|e| Error::InvalidOperation(format!("Task join error: {}", e)))??;
80
81        // File I/O can use tokio::fs
82        let config = if tokio::fs::try_exists(&config_path).await? {
83            Config::load_from(&config_path)?
84        } else {
85            let detected = Config::detect_providers()?;
86            detected.save_to(&config_path)?;
87            detected
88        };
89
90        let provider_configs: Vec<(String, PathBuf)> = config
91            .enabled_providers()
92            .into_iter()
93            .map(|(name, cfg)| (name.clone(), cfg.log_root.clone()))
94            .collect();
95
96        Ok(Self {
97            db: Arc::new(Mutex::new(db)),
98            config: Arc::new(config),
99            provider_configs: Arc::new(provider_configs),
100        })
101    }
102
103    pub fn diagnose(&self) -> Result<Vec<DiagnoseResult>> {
104        let providers: Vec<(ProviderAdapter, PathBuf)> = self
105            .provider_configs
106            .iter()
107            .filter_map(|(name, path)| {
108                agtrace_providers::create_adapter(name)
109                    .ok()
110                    .map(|p| (p, path.clone()))
111            })
112            .collect();
113        DoctorService::diagnose_all(&providers)
114    }
115
116    pub fn projects(&self) -> ProjectOps {
117        ProjectOps::new(self.db.clone(), self.provider_configs.clone())
118    }
119
120    pub fn sessions(&self) -> SessionOps {
121        SessionOps::new(self.db.clone(), self.provider_configs.clone())
122    }
123
124    pub fn insights(&self) -> InsightOps {
125        InsightOps::new(self.db.clone(), self.provider_configs.clone())
126    }
127
128    pub fn watch_service(&self) -> WatchService {
129        WatchService::new(
130            self.db.clone(),
131            self.config.clone(),
132            self.provider_configs.clone(),
133        )
134    }
135
136    pub fn workspace_monitor(&self) -> Result<MonitorBuilder> {
137        Ok(MonitorBuilder::new(
138            self.db.clone(),
139            self.provider_configs.clone(),
140        ))
141    }
142
143    pub fn database(&self) -> Arc<Mutex<Database>> {
144        self.db.clone()
145    }
146
147    pub fn config(&self) -> &Config {
148        &self.config
149    }
150
151    pub fn check_file(
152        file_path: &str,
153        provider: &ProviderAdapter,
154        provider_name: &str,
155    ) -> Result<CheckResult> {
156        DoctorService::check_file(file_path, provider, provider_name)
157    }
158
159    pub fn inspect_file(file_path: &str, lines: usize, json_format: bool) -> Result<InspectResult> {
160        DoctorService::inspect_file(file_path, lines, json_format)
161    }
162}