Skip to main content

qualia_client_core/
state.rs

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        // rqbit_session: Arc::new(tokio::sync::Mutex::new(None)),
28        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    // Person ≠ machine: ensure person principal + local apparatus exist early.
40    // Failures are non-fatal at boot (e.g. read-only meta dir); setup/finish retries.
41    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// ── Config ────────────────────────────────────────────────────────────────────
62
63#[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    /// User-configurable port for the local web server (settings portal +
72    /// Studio WASM + companion gateway). Default: 8080. If 0, auto-finds
73    /// an open port starting from 8080.
74    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    // Tests and multi-profile installs may override the meta root without changing code.
114    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// ── App state ─────────────────────────────────────────────────────────────────
171
172#[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 rqbit_session: Arc<tokio::sync::Mutex<Option<std::sync::Arc<librqbit::Session>>>>,
193    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}