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