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