algocline_app/service/
mod.rs1pub(crate) mod alc_toml;
2mod error;
3pub(crate) use error::{
4 HubRegistriesError, PkgListError, ProjectFilesError, ServiceError, TranscriptError,
5};
6mod card;
7mod config;
8mod dist;
9mod engine_api_impl;
10mod eval;
11mod eval_store;
12pub(crate) mod gendoc;
13mod hub;
14pub mod hub_dist_preset;
15mod init;
16pub(crate) mod list_opts;
17pub(crate) mod lock;
18pub(crate) mod lockfile;
19mod logging;
20pub(crate) mod manifest;
21mod migrate;
22pub(crate) mod path;
23mod pkg;
24mod pkg_link;
25mod pkg_scaffold;
26mod pkg_unlink;
27pub(crate) mod project;
28pub mod resolve;
29mod run;
30mod scenario;
31pub(crate) mod source;
32mod status;
33mod transcript;
34mod update;
35
36#[cfg(test)]
37mod test_support;
38#[cfg(test)]
39mod tests;
40
41use std::path::Path;
42use std::sync::Arc;
43
44use algocline_engine::{Executor, FileCardStore, JsonFileStore, SessionRegistry, VariantPkg};
45
46pub use algocline_core::{EngineApi, TokenUsage};
47pub use config::{AppConfig, LogDirSource};
48pub use resolve::{QueryResponse, SearchPath};
49
50type EvalSessions = std::sync::Mutex<std::collections::HashMap<String, String>>;
63
64type SessionStrategies = std::sync::Mutex<std::collections::HashMap<String, String>>;
70
71#[derive(Clone)]
72pub struct AppService {
73 executor: Arc<Executor>,
74 registry: Arc<SessionRegistry>,
75 log_config: AppConfig,
76 search_paths: Vec<resolve::SearchPath>,
78 state_store: Arc<JsonFileStore>,
83 card_store: Arc<FileCardStore>,
87 eval_sessions: Arc<EvalSessions>,
89 session_strategies: Arc<SessionStrategies>,
91}
92
93impl AppService {
94 pub fn new(
95 executor: Arc<Executor>,
96 log_config: AppConfig,
97 search_paths: Vec<resolve::SearchPath>,
98 ) -> Self {
99 let registry = Arc::new(SessionRegistry::new());
100 registry.spawn_gc_task(std::time::Duration::from_secs(10800));
103 let app_dir = log_config.app_dir();
104 let state_store = Arc::new(JsonFileStore::new(app_dir.state_dir()));
105 let card_store = Arc::new(FileCardStore::new(app_dir.cards_dir()));
106 Self {
107 executor,
108 registry,
109 log_config,
110 search_paths,
111 state_store,
112 card_store,
113 eval_sessions: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
114 session_strategies: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
115 }
116 }
117
118 fn require_log_dir(&self) -> Result<&Path, String> {
120 self.log_config
121 .log_dir
122 .as_deref()
123 .ok_or_else(|| "File logging is not available (no writable log directory)".to_string())
124 }
125
126 pub(crate) fn resolve_extra_lib_paths(
141 &self,
142 project_root: Option<&str>,
143 ) -> (Vec<std::path::PathBuf>, Vec<String>) {
144 let Some(root) = project::resolve_project_root(project_root) else {
145 return (vec![], vec![]);
146 };
147
148 let mut warnings: Vec<String> = Vec::new();
149
150 let local_paths: Vec<std::path::PathBuf> = match alc_toml::load_alc_local_toml(&root) {
153 Ok(Some(local)) => alc_toml::resolve_local_path_entries(&root, &local),
154 Ok(None) => Vec::new(),
155 Err(e) => {
156 warnings.push(format!(
157 "failed to load alc.local.toml at {}: {e}",
158 root.display()
159 ));
160 Vec::new()
161 }
162 };
163
164 let lock_paths: Vec<std::path::PathBuf> = match lockfile::load_lockfile(&root) {
166 Ok(Some(lock)) => {
167 self.warn_toml_lock_mismatch(&root, &lock);
168 let (paths, path_warnings) = lockfile::resolve_path_entries(&root, &lock);
169 warnings.extend(path_warnings);
170 paths
171 }
172 Ok(None) => Vec::new(),
173 Err(e) => {
174 warnings.push(format!(
175 "failed to load alc.lock at {}: {e}",
176 root.display()
177 ));
178 Vec::new()
179 }
180 };
181
182 let mut merged = local_paths;
183 merged.extend(lock_paths);
184 (merged, warnings)
185 }
186
187 pub(crate) fn resolve_variant_pkgs(
196 &self,
197 project_root: Option<&str>,
198 ) -> (Vec<VariantPkg>, Vec<String>) {
199 let Some(root) = project::resolve_project_root(project_root) else {
200 return (vec![], vec![]);
201 };
202
203 match alc_toml::load_alc_local_toml(&root) {
204 Ok(Some(local)) => (alc_toml::resolve_local_variant_pkgs(&root, &local), vec![]),
205 Ok(None) => (Vec::new(), vec![]),
206 Err(e) => {
207 let msg = format!("failed to load alc.local.toml at {}: {e}", root.display());
208 (Vec::new(), vec![msg])
209 }
210 }
211 }
212
213 fn warn_toml_lock_mismatch(&self, root: &Path, lock: &lockfile::LockFile) {
214 let toml = match alc_toml::load_alc_toml(root) {
215 Ok(Some(t)) => t,
216 _ => return,
217 };
218
219 use std::collections::BTreeSet;
220 let toml_names: BTreeSet<&str> = toml.packages.keys().map(|s| s.as_str()).collect();
221 let lock_names: BTreeSet<&str> = lock.packages.iter().map(|p| p.name.as_str()).collect();
222
223 for name in toml_names.difference(&lock_names) {
224 eprintln!(
225 "warning: '{name}' is declared in alc.toml but missing from alc.lock. Run `alc_update` to sync."
226 );
227 }
228 for name in lock_names.difference(&toml_names) {
229 eprintln!("warning: '{name}' is in alc.lock but not declared in alc.toml.");
230 }
231 }
232}