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