flams_math_archives/backend/
global.rs1use std::path::Path;
2
3#[cfg(feature = "rdf")]
4use ftml_ontology::narrative::elements::Notation;
5use ftml_ontology::{
6 domain::modules::{Module, ModuleLike},
7 narrative::{DocDataRef, DocumentRange, documents::Document},
8 utils::Css,
9};
10#[cfg(feature = "rdf")]
11use ftml_uris::DocumentElementUri;
12use ftml_uris::{
13 ArchiveId, DocumentUri, IsNarrativeUri, ModuleUri, NamedUri, SymbolUri, UriPath,
14 UriWithArchive, UriWithPath,
15};
16use futures_util::TryFutureExt;
17
18use crate::{
19 Archive, ExternalArchive, LocallyBuilt,
20 backend::LocalBackend,
21 document_file::DocumentFile,
22 manager::{ArchiveManager, ArchiveOrGroup},
23 utils::{
24 AsyncEngine,
25 errors::{ArtifactSaveError, BackendError},
26 },
27};
28
29#[cfg(feature = "rocksdb")]
30static RDF_PATH: std::sync::Mutex<Option<Box<Path>>> = std::sync::Mutex::new(None);
31
32#[cfg(not(feature = "rocksdb"))]
33static GLOBAL: std::sync::LazyLock<ArchiveManager> =
34 std::sync::LazyLock::new(ArchiveManager::default);
35
36#[cfg(feature = "rocksdb")]
37static GLOBAL: std::sync::LazyLock<ArchiveManager> = std::sync::LazyLock::new(|| {
38 if let Some(p) = RDF_PATH.lock().expect("could not access RDF_PATH").as_ref() {
39 ArchiveManager::new(p)
40 } else {
41 ArchiveManager::default()
42 }
43});
44
45#[cfg(feature = "rocksdb")]
46pub fn set_global(rdf_path: &Path) {
47 *RDF_PATH.lock().expect("could not access RDF_PATH") =
48 Some(rdf_path.to_path_buf().into_boxed_path());
49}
50
51#[derive(Debug, Copy, Clone)]
52pub struct GlobalBackend;
53impl std::ops::Deref for GlobalBackend {
54 type Target = ArchiveManager;
55 #[inline]
56 fn deref(&self) -> &Self::Target {
57 &GLOBAL
58 }
59}
60
61impl GlobalBackend {
62 #[inline]
63 #[must_use]
64 pub fn get(&self) -> &'static ArchiveManager {
65 &GLOBAL
66 }
67 pub fn initialize<A: AsyncEngine>() {
68 Self.load(crate::mathhub::mathhubs());
69 #[cfg(feature = "rdf")]
70 {
71 A::background(|| Self.triple_store().load_archives(&Self.all_archives()));
72 }
73 }
74
75 pub fn reset<A: AsyncEngine>(self) {
76 self.reinit(|_| (), crate::mathhub::mathhubs());
77 #[cfg(feature = "rdf")]
78 {
79 A::background(|| Self.triple_store().load_archives(&Self.all_archives()));
80 }
81 }
82}
83
84impl LocalBackend for ArchiveManager {
85 type ArchiveIter<'a>
86 = &'a [Archive]
87 where
88 Self: Sized;
89
90 fn save(
91 &self,
92 in_doc: &ftml_uris::DocumentUri,
93 rel_path: Option<&UriPath>,
94 log: crate::artifacts::FileOrString,
95 from: crate::formats::BuildTargetId,
96 result: Option<Box<dyn crate::artifacts::Artifact>>,
97 ) -> std::result::Result<(), crate::utils::errors::ArtifactSaveError> {
98 self.with_buildable_archive(in_doc.archive_id(), |a| {
99 let Some(a) = a else {
100 return Err(ArtifactSaveError::NoArchive);
101 };
102 a.save(
103 in_doc,
104 rel_path,
105 log,
106 from,
107 result,
108 #[cfg(feature = "rdf")]
109 self.triple_store(),
110 #[cfg(feature = "rdf")]
111 true,
112 )
113 })
114 }
115
116 fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
117 let tree = self.tree.read();
118 f(tree.get(id))
119 }
120
121 fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
122 where
123 Self: Sized,
124 {
125 f(&self.all_archives())
126 }
127
128 fn with_archive_or_group<R>(
129 &self,
130 id: &ArchiveId,
131 f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
132 ) -> R
133 where
134 Self: Sized,
135 {
136 self.with_tree(|t| f(t.get_group_or_archive(id)))
137 }
138
139 fn get_document(&self, uri: &DocumentUri) -> Result<Document, BackendError> {
140 self.with_doc(
141 uri,
142 |docfile| docfile.get_document().map_err(Into::into),
143 |o| todo!(),
144 )
145 }
146
147 fn get_document_async<A: AsyncEngine>(
148 &self,
149 uri: &DocumentUri,
150 ) -> impl Future<Output = Result<Document, BackendError>> + Send + use<A>
151 where
152 Self: Sized,
153 {
154 self.with_doc_async::<A, _, _, _, _, _>(
155 uri,
156 |docfile| async move { docfile.get_document_async::<A>().await.map_err(Into::into) },
157 |o| std::future::ready(todo!()),
158 )
159 }
160
161 fn get_html_full(&self, uri: &DocumentUri) -> Result<Box<str>, BackendError> {
162 self.with_doc(
163 uri,
164 |docfile| docfile.get_html().map_err(Into::into),
165 |o| todo!(),
166 )
167 }
168
169 fn get_html_body(&self, uri: &DocumentUri) -> Result<(Box<[Css]>, Box<str>), BackendError> {
170 self.with_doc(
171 uri,
172 |docfile| {
173 docfile
174 .get_html_body()
175 .map_err(Into::into)
176 .map(|s| (docfile.get_css(), s))
177 },
178 |o| todo!(),
179 )
180 }
181
182 fn get_html_body_async<A: AsyncEngine>(
183 &self,
184 uri: &ftml_uris::DocumentUri,
185 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
186 + Send
187 + use<A>
188 where
189 Self: Sized,
190 {
191 self.with_doc_async::<A, _, _, _, _, _>(
192 uri,
193 |docfile| {
194 A::block_on(move || {
195 docfile
196 .get_html_body()
197 .map_err(Into::into)
198 .map(|s| (docfile.get_css(), s))
199 })
200 },
201 |o| std::future::ready(todo!()),
202 )
203 }
204
205 fn get_html_body_inner(
206 &self,
207 uri: &DocumentUri,
208 ) -> Result<(Box<[Css]>, Box<str>), BackendError> {
209 self.with_doc(
210 uri,
211 |docfile| {
212 docfile
213 .get_html_body_inner()
214 .map_err(Into::into)
215 .map(|s| (docfile.get_css(), s))
216 },
217 |o| todo!(),
218 )
219 }
220
221 fn get_html_body_inner_async<A: AsyncEngine>(
222 &self,
223 uri: &ftml_uris::DocumentUri,
224 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
225 + Send
226 + use<A>
227 where
228 Self: Sized,
229 {
230 self.with_doc_async::<A, _, _, _, _, _>(
231 uri,
232 |docfile| {
233 A::block_on(move || {
234 docfile
235 .get_html_body_inner()
236 .map_err(Into::into)
237 .map(|s| (docfile.get_css(), s))
238 })
239 },
240 |o| std::future::ready(todo!()),
241 )
242 }
243
244 fn get_html_fragment(
245 &self,
246 uri: &DocumentUri,
247 range: DocumentRange,
248 ) -> Result<(Box<[Css]>, Box<str>), BackendError> {
249 self.with_doc(
250 uri,
251 |docfile| {
252 docfile
253 .get_html_range(range)
254 .map_err(Into::into)
255 .map(|s| (docfile.get_css(), s))
256 },
257 |o| todo!(),
258 )
259 }
260
261 fn get_html_fragment_async<A: AsyncEngine>(
262 &self,
263 uri: &ftml_uris::DocumentUri,
264 range: ftml_ontology::narrative::DocumentRange,
265 ) -> impl Future<Output = Result<(Box<[ftml_ontology::utils::Css]>, Box<str>), BackendError>>
266 + Send
267 + use<A> {
268 self.with_doc_async::<A, _, _, _, _, _>(
269 uri,
270 move |docfile| {
271 A::block_on(move || {
272 docfile
273 .get_html_range(range)
274 .map_err(Into::into)
275 .map(|s| (docfile.get_css(), s))
276 })
277 },
278 |o| std::future::ready(todo!()),
279 )
280 }
281
282 fn get_reference<T: bincode::Decode<()>>(&self, rf: &DocDataRef<T>) -> Result<T, BackendError>
283 where
284 Self: Sized,
285 {
286 let DocDataRef {
287 start,
288 end,
289 in_doc: uri,
290 ..
291 } = rf;
292 self.with_doc(
293 uri,
294 |docfile| docfile.get_data(*start, *end).map_err(Into::into),
295 |o| todo!(),
296 )
297 }
298
299 fn get_module(&self, uri: &ModuleUri) -> Result<ModuleLike, BackendError> {
300 if uri.is_top() {
301 self.load_module(uri.archive_uri(), uri.path(), uri.name().as_ref())
306 .map(ModuleLike::Module)
307 } else {
308 let SymbolUri { name, module } =
310 unsafe { uri.clone().into_symbol().unwrap_unchecked() };
311 let m = self.load_module(module.archive_uri(), module.path(), module.name().as_ref())?;
315
316 m.as_module_like(&name)
317 .ok_or(BackendError::NotFound(ftml_uris::UriKind::Symbol))
318 }
319 }
320
321 fn get_module_async<A: AsyncEngine>(
322 &self,
323 uri: &ModuleUri,
324 ) -> impl Future<Output = Result<ModuleLike, BackendError>> + Send + use<A>
325 where
326 Self: Sized,
327 {
328 if uri.is_top() {
329 either::Left(self.load_module_async::<A>(
341 uri.archive_uri(),
342 uri.path(),
343 uri.name().as_ref(),
344 ).map_ok(ModuleLike::Module))
345 } else {
346 let SymbolUri { name, module } =
348 unsafe { uri.clone().into_symbol().unwrap_unchecked() };
349 let m = self.load_module_async::<A>(
353 module.archive_uri(),
354 module.path(),
355 module.name().as_ref(),
356 );either::Right(m.and_then(move |m| {
359 std::future::ready(
360 m.as_module_like(&name)
361 .ok_or(BackendError::NotFound(ftml_uris::UriKind::Symbol)),
362 )
363 }))
364 }
365 }
366
367 #[cfg(feature = "rdf")]
368 fn get_notations<E: AsyncEngine>(
369 &self,
370 uri: &SymbolUri,
371 ) -> impl Iterator<Item = (DocumentElementUri, Notation)>
372 where
373 Self: Sized,
374 {
375 use ftml_uris::FtmlUri;
376 self.do_notations::<E>(uri.to_iri())
377 }
378
379 #[cfg(feature = "rdf")]
380 fn get_var_notations<E: AsyncEngine>(
381 &self,
382 uri: &DocumentElementUri,
383 ) -> impl Iterator<Item = (DocumentElementUri, Notation)>
384 where
385 Self: Sized,
386 {
387 use ftml_uris::FtmlUri;
388 self.do_var_notations::<E>(uri.to_iri())
389 }
390}
391
392impl ArchiveManager {
393 fn with_doc<R>(
394 &self,
395 uri: &DocumentUri,
396 then: impl FnOnce(&DocumentFile) -> Result<R, BackendError>,
397 other: impl FnOnce(&dyn ExternalArchive) -> Result<R, BackendError>,
398 ) -> Result<R, BackendError> {
399 let file_or_other = self.with_archive(uri.archive_id(), |a| {
404 let Some(a) = a else {
405 return Err(BackendError::ArchiveNotFound);
406 };
407 match a {
408 Archive::Local(a) => Ok(either::Left(a.document_file(
409 uri.path(),
410 None,
411 &uri.name,
412 uri.language(),
413 ))),
414 Archive::Ext(_, ext) => other(&**ext).map(either::Right),
415 }
416 })?;
417 match file_or_other {
418 either::Left(file) => {
419 let docfile = DocumentFile::from_file(file)
421 .map(triomphe::Arc::new)
422 ?;
425 then(&docfile)
426 }
427 either::Right(r) => Ok(r),
428 }
429 }
430
431 fn with_doc_async<
432 A: AsyncEngine,
433 R: Send,
434 T: Future<Output = Result<R, BackendError>> + Send,
435 O: Future<Output = Result<R, BackendError>> + Send,
436 Then: FnOnce(triomphe::Arc<DocumentFile>) -> T + Send,
437 Other: FnOnce(&dyn ExternalArchive) -> O,
438 >(
439 &self,
440 uri: &DocumentUri,
441 then: Then,
442 other: Other,
443 ) -> impl Future<Output = Result<R, BackendError>> + Send + use<A, R, T, O, Then, Other> {
444 let file_or_other = match self.with_archive(uri.archive_id(), |a| {
454 let Some(a) = a else {
455 return Err(BackendError::ArchiveNotFound);
456 };
457 match a {
458 Archive::Local(a) => Ok(either::Left(a.document_file(
459 uri.path(),
460 None,
461 &uri.name,
462 uri.language(),
463 ))),
464 Archive::Ext(_, ext) => Ok(either::Right(other(&**ext))),
465 }
466 }) {
467 Ok(v) => v,
468 Err(e) => return either::Left(std::future::ready(Err(e))),
469 };
470 match file_or_other {
471 either::Left(file) => {
472 let docfile = A::block_on(move || {
474 DocumentFile::from_file(file)
475 .map(triomphe::Arc::new)
476 });
479 either::Right(either::Left(async move {
480 let docfile = docfile.await?;
481 then(docfile).await
482 }))}
484 either::Right(r) => either::Right(either::Right(r))}
486 }
487
488 #[cfg(feature = "rdf")]
489 fn do_notations<E: AsyncEngine>(
490 &self,
491 iri: ulo::rdf_types::NamedNode,
492 ) -> impl Iterator<Item = (DocumentElementUri, Notation)> {
493 let q = crate::sparql!(SELECT DISTINCT ?n WHERE { ?n ulo:notation_for iri. });
494 self.triple_store()
495 .query::<E>(q)
496 .expect("Notations query should be valid")
497 .into_uris::<DocumentElementUri>()
498 .filter_map(|uri| {
499 use ftml_ontology::narrative::elements::notations::NotationReference;
500 let notation = self
502 .get_typed_document_element::<NotationReference>(&uri)
503 .ok()?;
504 self.get_reference(¬ation.notation.with_doc(uri.document.clone()))
506 .map_err(|e| tracing::error!("Error getting notation {uri}: {e}"))
507 .ok()
508 .map(|n| (uri, n))
509 })
510 }
511
512 #[cfg(feature = "rdf")]
513 fn do_var_notations<E: AsyncEngine>(
514 &self,
515 iri: ulo::rdf_types::NamedNode,
516 ) -> impl Iterator<Item = (DocumentElementUri, Notation)> {
517 let q = crate::sparql!(SELECT DISTINCT ?n WHERE { ?n ulo:notation_for iri. });
518 self.triple_store()
519 .query::<E>(q)
520 .expect("Notations query should be valid")
521 .into_uris::<DocumentElementUri>()
522 .filter_map(|uri| {
523 use ftml_ontology::narrative::elements::notations::VariableNotationReference;
524 let notation = self
526 .get_typed_document_element::<VariableNotationReference>(&uri)
527 .ok()?;
528 self.get_reference(¬ation.notation.with_doc(uri.document.clone()))
530 .map_err(|e| tracing::error!("Error getting variable notation {uri}: {e}"))
531 .ok()
532 .map(|n| (uri, n))
533 })
534 }
535}