algocline_app/service/
mod.rs1pub(crate) mod alc_toml;
2mod card;
3mod config;
4mod dist;
5mod engine_api_impl;
6mod eval;
7mod eval_store;
8mod gendoc;
9mod hub;
10pub mod hub_dist_preset;
11mod init;
12pub(crate) mod list_opts;
13pub(crate) mod lock;
14pub(crate) mod lockfile;
15mod logging;
16pub(crate) mod manifest;
17mod migrate;
18pub(crate) mod path;
19mod pkg;
20mod pkg_link;
21mod pkg_unlink;
22pub(crate) mod project;
23pub mod resolve;
24mod run;
25mod scenario;
26pub(crate) mod source;
27mod status;
28mod transcript;
29mod update;
30
31#[cfg(test)]
32mod test_support;
33#[cfg(test)]
34mod tests;
35
36use std::path::Path;
37use std::sync::Arc;
38
39use algocline_engine::{Executor, FileCardStore, JsonFileStore, SessionRegistry, VariantPkg};
40
41pub use algocline_core::{EngineApi, TokenUsage};
42pub use config::{AppConfig, LogDirSource};
43pub use resolve::{QueryResponse, SearchPath};
44
45type EvalSessions = std::sync::Mutex<std::collections::HashMap<String, String>>;
58
59type SessionStrategies = std::sync::Mutex<std::collections::HashMap<String, String>>;
65
66#[derive(Clone)]
67pub struct AppService {
68 executor: Arc<Executor>,
69 registry: Arc<SessionRegistry>,
70 log_config: AppConfig,
71 search_paths: Vec<resolve::SearchPath>,
73 state_store: Arc<JsonFileStore>,
78 card_store: Arc<FileCardStore>,
82 eval_sessions: Arc<EvalSessions>,
84 session_strategies: Arc<SessionStrategies>,
86}
87
88impl AppService {
89 pub fn new(
90 executor: Arc<Executor>,
91 log_config: AppConfig,
92 search_paths: Vec<resolve::SearchPath>,
93 ) -> Self {
94 let registry = Arc::new(SessionRegistry::new());
95 registry.spawn_gc_task(std::time::Duration::from_secs(10800));
98 let app_dir = log_config.app_dir();
99 let state_store = Arc::new(JsonFileStore::new(app_dir.state_dir()));
100 let card_store = Arc::new(FileCardStore::new(app_dir.cards_dir()));
101 Self {
102 executor,
103 registry,
104 log_config,
105 search_paths,
106 state_store,
107 card_store,
108 eval_sessions: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
109 session_strategies: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
110 }
111 }
112
113 fn require_log_dir(&self) -> Result<&Path, String> {
115 self.log_config
116 .log_dir
117 .as_deref()
118 .ok_or_else(|| "File logging is not available (no writable log directory)".to_string())
119 }
120
121 pub(crate) fn resolve_extra_lib_paths(
134 &self,
135 project_root: Option<&str>,
136 ) -> Vec<std::path::PathBuf> {
137 let Some(root) = project::resolve_project_root(project_root) else {
138 return vec![];
139 };
140
141 let local_paths: Vec<std::path::PathBuf> = match alc_toml::load_alc_local_toml(&root) {
144 Ok(Some(local)) => alc_toml::resolve_local_path_entries(&root, &local),
145 Ok(None) => Vec::new(),
146 Err(e) => {
147 tracing::warn!("failed to load alc.local.toml at {}: {e}", root.display());
148 Vec::new()
149 }
150 };
151
152 let lock_paths: Vec<std::path::PathBuf> = match lockfile::load_lockfile(&root) {
154 Ok(Some(lock)) => {
155 self.warn_toml_lock_mismatch(&root, &lock);
156 lockfile::resolve_path_entries(&root, &lock)
157 }
158 Ok(None) => Vec::new(),
159 Err(e) => {
160 tracing::warn!("failed to load alc.lock at {}: {e}", root.display());
161 Vec::new()
162 }
163 };
164
165 let mut merged = local_paths;
166 merged.extend(lock_paths);
167 merged
168 }
169
170 pub(crate) fn resolve_variant_pkgs(&self, project_root: Option<&str>) -> Vec<VariantPkg> {
178 let Some(root) = project::resolve_project_root(project_root) else {
179 return vec![];
180 };
181
182 match alc_toml::load_alc_local_toml(&root) {
183 Ok(Some(local)) => alc_toml::resolve_local_variant_pkgs(&root, &local),
184 Ok(None) => Vec::new(),
185 Err(e) => {
186 tracing::warn!("failed to load alc.local.toml at {}: {e}", root.display());
187 Vec::new()
188 }
189 }
190 }
191
192 fn warn_toml_lock_mismatch(&self, root: &Path, lock: &lockfile::LockFile) {
193 let toml = match alc_toml::load_alc_toml(root) {
194 Ok(Some(t)) => t,
195 _ => return,
196 };
197
198 use std::collections::BTreeSet;
199 let toml_names: BTreeSet<&str> = toml.packages.keys().map(|s| s.as_str()).collect();
200 let lock_names: BTreeSet<&str> = lock.packages.iter().map(|p| p.name.as_str()).collect();
201
202 for name in toml_names.difference(&lock_names) {
203 eprintln!(
204 "warning: '{name}' is declared in alc.toml but missing from alc.lock. Run `alc_update` to sync."
205 );
206 }
207 for name in lock_names.difference(&toml_names) {
208 eprintln!("warning: '{name}' is in alc.lock but not declared in alc.toml.");
209 }
210 }
211}