1use std::fs;
12use std::path::{Path, PathBuf};
13
14use crate::resource_import;
15
16struct BundledOntologySpec {
17 id: &'static str,
18 rel_path: &'static str,
19}
20
21const BUNDLED_ONTOLOGIES: &[BundledOntologySpec] = &[
22 BundledOntologySpec {
23 id: "shacl",
24 rel_path: "bundled/ontologies/shacl.ttl",
25 },
26 BundledOntologySpec {
28 id: "ldp",
29 rel_path: "bundled/ontologies/w3c-archives/ldp.ttl",
30 },
31 BundledOntologySpec {
32 id: "acl",
33 rel_path: "bundled/ontologies/w3c-archives/auth-acl.ttl",
34 },
35 BundledOntologySpec {
36 id: "solid-terms",
37 rel_path: "bundled/ontologies/w3c-archives/solid-terms.ttl",
38 },
39 BundledOntologySpec {
40 id: "solid-oidc",
41 rel_path: "bundled/ontologies/w3c-archives/solid-oidc.ttl",
42 },
43 BundledOntologySpec {
44 id: "pim-space",
45 rel_path: "bundled/ontologies/w3c-archives/pim-space.ttl",
46 },
47 BundledOntologySpec {
48 id: "foaf",
49 rel_path: "bundled/ontologies/w3c-archives/foaf.ttl",
50 },
51 BundledOntologySpec {
53 id: "prov",
54 rel_path: "bundled/ontologies/w3c/prov.ttl",
55 },
56 BundledOntologySpec {
57 id: "skos",
58 rel_path: "bundled/ontologies/w3c/skos.ttl",
59 },
60 BundledOntologySpec {
61 id: "owl",
62 rel_path: "bundled/ontologies/w3c/owl.ttl",
63 },
64 BundledOntologySpec {
65 id: "rdfs",
66 rel_path: "bundled/ontologies/w3c/rdfs.ttl",
67 },
68 BundledOntologySpec {
69 id: "time",
70 rel_path: "bundled/ontologies/w3c/time.ttl",
71 },
72 BundledOntologySpec {
73 id: "sosa",
74 rel_path: "bundled/ontologies/w3c/sosa.ttl",
75 },
76 BundledOntologySpec {
77 id: "music",
78 rel_path: "bundled/ontologies/purl/music.ttl",
79 },
80 BundledOntologySpec {
81 id: "consent",
82 rel_path: "bundled/ontologies/purl/consent.ttl",
83 },
84];
85
86pub const DEFAULT_BUNDLED_ONTOLOGIES: &[&str] = &[
90 "shacl",
91 "ldp",
92 "acl",
93 "solid-terms",
94 "solid-oidc",
95 "pim-space",
96 "foaf",
97 "prov",
98 "skos",
99 "owl",
100 "rdfs",
101 "time",
102 "sosa",
103 "music",
104 "consent",
105];
106
107fn exe_dir() -> Option<PathBuf> {
108 std::env::current_exe()
109 .ok()
110 .and_then(|p| p.parent().map(|d| d.to_path_buf()))
111}
112
113fn ontology_spec(id: &str) -> Option<&'static BundledOntologySpec> {
114 BUNDLED_ONTOLOGIES.iter().find(|spec| spec.id == id)
115}
116
117fn join_rel(root: &Path, rel: &str) -> PathBuf {
118 let mut out = root.to_path_buf();
119 for segment in rel.split('/') {
120 out.push(segment);
121 }
122 out
123}
124
125pub fn resolve_bundled_ontology_source(id: &str) -> Option<PathBuf> {
127 let spec = ontology_spec(id)?;
128
129 if let Ok(extra) = std::env::var("QUALIA_BUNDLED_ONTOLOGIES_DIR") {
130 let file_name = Path::new(spec.rel_path).file_name()?;
131 let candidate = PathBuf::from(&extra).join(file_name);
132 if candidate.is_file() {
133 return Some(candidate);
134 }
135 let candidate = PathBuf::from(extra).join("w3c-archives").join(file_name);
137 if candidate.is_file() {
138 return Some(candidate);
139 }
140 }
141
142 if let Some(root) = exe_dir() {
143 for rel in [
144 spec.rel_path,
145 spec.rel_path
146 .strip_prefix("bundled/")
147 .unwrap_or(spec.rel_path),
148 ] {
149 let candidate = join_rel(&root, rel);
150 if candidate.is_file() {
151 return Some(candidate);
152 }
153 }
154 }
155
156 let repo_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
157 let candidate = join_rel(&repo_root, spec.rel_path);
158 if candidate.is_file() {
159 return Some(candidate);
160 }
161
162 None
163}
164
165fn seed_bundled_ontology_if_missing(
166 storage_path: &Path,
167 ontology_id: &str,
168) -> Result<bool, String> {
169 let q42_path = resource_import::index_dir(storage_path).join(format!("{ontology_id}.q42"));
170 if q42_path.is_file() {
171 return Ok(false);
172 }
173
174 let source = resolve_bundled_ontology_source(ontology_id)
175 .ok_or_else(|| format!("Bundled ontology source not found for {ontology_id}"))?;
176
177 let catalog = crate::api::load_workspace_catalog();
178 let ont = catalog
180 .find_ontology(ontology_id)
181 .or_else(|| catalog.find_ontology(&format!("w3c-arch-{ontology_id}")))
182 .or_else(|| {
183 if ontology_id == "acl" {
185 catalog.find_ontology("w3c-arch-auth-acl")
186 } else {
187 None
188 }
189 });
190 resource_import::ingest_local_rdf(&source, ontology_id, storage_path, ont)
191 .map_err(|e| e.to_string())?;
192 Ok(true)
193}
194
195pub fn seed_bundled_ontologies() -> Result<Vec<String>, String> {
197 let state = crate::state::APP_STATE
198 .get()
199 .ok_or("APP_STATE not initialized")?;
200 let storage = state
201 .config
202 .lock()
203 .map_err(|e| e.to_string())?
204 .storage_path
205 .clone();
206 let storage_path = PathBuf::from(storage);
207 fs::create_dir_all(resource_import::index_dir(&storage_path)).map_err(|e| e.to_string())?;
208
209 let mut seeded = Vec::new();
210 for ontology_id in DEFAULT_BUNDLED_ONTOLOGIES {
211 match seed_bundled_ontology_if_missing(&storage_path, ontology_id) {
212 Ok(true) => seeded.push((*ontology_id).to_string()),
213 Ok(false) => {}
214 Err(e) => eprintln!("[bundled_ontologies] skip {ontology_id}: {e}"),
215 }
216 }
217
218 Ok(seeded)
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn bundled_shacl_source_resolves_when_tracked() {
227 let tracked =
228 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../bundled/ontologies/shacl.ttl");
229 if tracked.is_file() {
230 let src = resolve_bundled_ontology_source("shacl");
231 assert!(src.is_some(), "expected bundled SHACL path");
232 }
233 }
234
235 #[test]
236 fn solid_stack_sources_resolve() {
237 for id in [
238 "ldp",
239 "acl",
240 "solid-terms",
241 "solid-oidc",
242 "pim-space",
243 "foaf",
244 ] {
245 let src = resolve_bundled_ontology_source(id);
246 assert!(
247 src.is_some(),
248 "expected bundled solid-stack ontology {id} under w3c-archives"
249 );
250 }
251 }
252}