1use crate::qapp_registry;
2use qualia_core_db::rpc::TaxRecipientSuite;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::PathBuf;
6use std::sync::atomic::{AtomicBool, AtomicU32};
7use std::sync::{Arc, Mutex, OnceLock};
8
9pub static APP_STATE: OnceLock<Arc<AppState>> = OnceLock::new();
10
11pub fn init_app_state() -> Arc<AppState> {
12 let config = load_config_from_disk();
13 init_data_directories(&config.storage_path);
14 let meta = app_meta_dir();
15 let _ = std::fs::create_dir_all(&meta);
16 let meta_str = meta.to_string_lossy().into_owned();
17 let state = Arc::new(AppState {
18 config: Mutex::new(config.clone()),
19 tax_suite: Mutex::new(load_suite_from_disk(&config.storage_path)),
20 daemon_running: Arc::new(Mutex::new(false)),
21 nym_relay_active: Arc::new(AtomicBool::new(false)),
22 stark_prover_active: Arc::new(AtomicBool::new(false)),
23 simulated_solar_watts: Arc::new(AtomicU32::new(0)),
24 download_handles: Arc::new(Mutex::new(HashMap::new())),
25 active_downloads: Arc::new(Mutex::new(HashMap::new())),
26 active_model: Arc::new(Mutex::new(None)),
27 directory_actors: Arc::new(Mutex::new(Vec::new())),
29 delegation_rules: Arc::new(Mutex::new(Vec::new())),
30 front_doors: Arc::new(Mutex::new(Vec::new())),
31 installed_qapps: Arc::new(Mutex::new(Vec::new())),
32 key_vault: Arc::new(Mutex::new(
33 qualia_core_db::key_vault::KeyVault::load_or_generate(&meta_str)
34 .unwrap_or_else(|e| panic!("KeyVault init failed ({meta_str}): {e}")),
35 )),
36 download_events: tokio::sync::broadcast::channel(100).0,
37 });
38 APP_STATE.set(state.clone()).ok();
39 let _ = crate::identity_plane::ensure_local_apparatus(None);
42 state
43}
44
45#[derive(Clone, Serialize)]
46pub struct RelayTelemetry {
47 pub packets_routed: u32,
48 pub packets_dropped: u32,
49 pub buffer_memory_mb: f64,
50 pub is_congested: bool,
51}
52
53#[derive(Clone, Serialize)]
54pub struct StarkTelemetry {
55 pub status: String,
56 pub cpu_utilization: f64,
57 pub ram_usage_mb: f64,
58 pub fragments_paged: u32,
59}
60
61#[derive(Serialize, Deserialize, Clone)]
64pub struct AgentConfig {
65 pub storage_path: String,
66 pub storage_quota_gb: u64,
67 pub base_connectivity_cost_ilp: u64,
68 pub daemon_host: String,
69 pub daemon_port: u16,
70 pub inference_backend: String,
71 pub settings_port: u16,
75}
76
77impl Default for AgentConfig {
78 fn default() -> Self {
79 Self {
80 storage_path: dirs_default_path(),
81 storage_quota_gb: 10,
82 base_connectivity_cost_ilp: 5000,
83 daemon_host: "127.0.0.1".to_string(),
84 daemon_port: 4242,
85 inference_backend: "local".to_string(),
86 settings_port: 8080,
87 }
88 }
89}
90
91pub fn dirs_default_path() -> String {
92 #[cfg(target_os = "windows")]
93 {
94 std::env::var("LOCALAPPDATA")
95 .map(|d| format!("{}\\QualiaData", d))
96 .unwrap_or_else(|_| "C:\\QualiaData".to_string())
97 }
98 #[cfg(target_os = "macos")]
99 {
100 std::env::var("HOME")
101 .map(|h| format!("{}/Library/Application Support/QualiaData", h))
102 .unwrap_or_else(|_| "/tmp/QualiaData".to_string())
103 }
104 #[cfg(not(any(target_os = "windows", target_os = "macos")))]
105 {
106 std::env::var("HOME")
107 .map(|h| format!("{}/.local/share/QualiaData", h))
108 .unwrap_or_else(|_| "/tmp/QualiaData".to_string())
109 }
110}
111
112pub fn app_meta_dir() -> PathBuf {
113 if let Ok(p) = std::env::var("QUALIA_APP_META_DIR") {
115 let trimmed = p.trim();
116 if !trimmed.is_empty() {
117 return PathBuf::from(trimmed);
118 }
119 }
120 #[cfg(target_os = "windows")]
121 {
122 PathBuf::from(
123 std::env::var("LOCALAPPDATA")
124 .unwrap_or_else(|_| "C:\\Users\\Default\\AppData\\Local".to_string()),
125 )
126 .join("Qualia")
127 }
128 #[cfg(target_os = "macos")]
129 {
130 PathBuf::from(std::env::var("HOME").unwrap_or_default())
131 .join("Library/Application Support/Qualia")
132 }
133 #[cfg(not(any(target_os = "windows", target_os = "macos")))]
134 {
135 PathBuf::from(std::env::var("HOME").unwrap_or_default()).join(".config/qualia")
136 }
137}
138
139pub fn config_file_path() -> PathBuf {
140 app_meta_dir().join("config.json")
141}
142pub fn identity_file_path() -> PathBuf {
143 app_meta_dir().join("identity.json")
144}
145
146pub fn load_config_from_disk() -> AgentConfig {
147 std::fs::read_to_string(config_file_path())
148 .ok()
149 .and_then(|s| serde_json::from_str(&s).ok())
150 .unwrap_or_else(AgentConfig::default)
151}
152
153pub fn init_data_directories(storage_path: &str) {
154 let base = PathBuf::from(storage_path);
155 for sub in &[
156 "Models",
157 "Index",
158 "qapps",
159 "library",
160 "ontologies",
161 "qlinks",
162 "SemanticLibrary",
163 "Identity",
164 "Chats",
165 ] {
166 let _ = std::fs::create_dir_all(base.join(sub));
167 }
168}
169
170#[derive(Clone, Serialize, Deserialize)]
173pub struct ProgressPayload {
174 pub id: String,
175 pub progress: f64,
176 pub downloaded_bytes: u64,
177 pub total_bytes: u64,
178 pub speed_kbps: f64,
179 pub status: String,
180}
181
182pub struct AppState {
183 pub config: Mutex<AgentConfig>,
184 pub tax_suite: Mutex<TaxRecipientSuite>,
185 pub daemon_running: Arc<Mutex<bool>>,
186 pub nym_relay_active: Arc<AtomicBool>,
187 pub stark_prover_active: Arc<AtomicBool>,
188 pub simulated_solar_watts: Arc<AtomicU32>,
189 pub download_handles: Arc<Mutex<HashMap<String, Arc<AtomicBool>>>>,
190 pub active_downloads: Arc<Mutex<HashMap<String, ProgressPayload>>>,
191 pub active_model: Arc<Mutex<Option<String>>>,
192 pub directory_actors: Arc<Mutex<Vec<Actor>>>,
194 pub delegation_rules: Arc<Mutex<Vec<DelegationRule>>>,
195 pub front_doors: Arc<Mutex<Vec<FrontDoor>>>,
196 pub installed_qapps: Arc<Mutex<Vec<qapp_registry::RegisteredQapp>>>,
197 pub key_vault: Arc<Mutex<qualia_core_db::key_vault::KeyVault>>,
198 pub download_events: tokio::sync::broadcast::Sender<ProgressPayload>,
199}
200
201#[derive(Clone, Serialize, Deserialize)]
202pub struct FrontDoor {
203 pub id: String,
204 pub did_uri: String,
205 pub label: String,
206 pub created_at: String,
207 pub routing_hints: Vec<String>,
208}
209
210#[derive(Clone, Serialize, Deserialize)]
211pub struct Actor {
212 pub id: String,
213 pub actor_type: String,
214 pub name: String,
215 pub organization: Option<String>,
216 pub qualifications: Vec<String>,
217 pub roles: Vec<String>,
218 pub verification_status: String,
219 pub pairwise_did: String,
220 pub root_did_uri: Option<String>,
221 pub routing_hints: Vec<String>,
222}
223
224#[derive(Clone, Serialize, Deserialize)]
225pub struct DelegationRule {
226 pub id: String,
227 pub actor_id: String,
228 pub granted_roles: Vec<String>,
229 pub legal_basis: String,
230 pub privacy_mode_limit: String,
231 pub allowed_record_types: Vec<String>,
232 pub restricted_records: Vec<String>,
233 pub is_active: bool,
234}
235
236pub fn suite_file_path(data_dir: &str) -> PathBuf {
237 PathBuf::from(data_dir).join("tax_suite.json")
238}
239
240pub fn load_suite_from_disk(data_dir: &str) -> TaxRecipientSuite {
241 let path = suite_file_path(data_dir);
242 std::fs::read_to_string(&path)
243 .ok()
244 .and_then(|s| serde_json::from_str(&s).ok())
245 .unwrap_or_else(TaxRecipientSuite::default_cooperative)
246}