Skip to main content

flams_math_archives/backend/
global.rs

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            // SAFETY: !uri.is_top()
338            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            // SAFETY: !uri.is_top()
388            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        // TODO: a.document_file blocks; avoid!
525        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        //get_ref: impl Fn(&DocDataRef<Notation>) -> Result<Notation, BackendError>,
584    ) -> 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                //tracing::warn!("Found {uri}");
592                let notation = backend.get_typed_document_element::<T>(&uri).ok()?;
593                //tracing::warn!("Found {notation:?}");
594                backend
595                    .get_reference(&get_not(&notation).with_doc(uri.document.clone()))
596                    //self.get_reference(&get_not(&notation).with_doc(uri.document.clone()))
597                    .map_err(|e| tracing::error!("Error getting notation {uri}: {e}"))
598                    .ok()
599                    .map(|n| (uri, n))
600            })
601    }
602
603    /*
604    #[cfg(feature = "rdf")]
605    fn do_notations<E: AsyncEngine>(
606        &self,
607        iri: ulo::rdf_types::NamedNode,
608    ) -> impl Iterator<Item = (DocumentElementUri, Notation)> {
609        let q = crate::sparql!(SELECT DISTINCT ?n WHERE { ?n ulo:notation_for iri. });
610        self.triple_store()
611            .query::<E>(q)
612            .expect("Notations query should be valid")
613            .into_uris::<DocumentElementUri>()
614            .filter_map(|uri| {
615                use ftml_ontology::narrative::elements::notations::NotationReference;
616                //tracing::warn!("Found {uri}");
617                let notation = self
618                    .get_typed_document_element::<NotationReference>(&uri)
619                    .ok()?;
620                //tracing::warn!("Found {notation:?}");
621                self.get_reference(&notation.notation.with_doc(uri.document.clone()))
622                    .map_err(|e| tracing::error!("Error getting notation {uri}: {e}"))
623                    .ok()
624                    .map(|n| (uri, n))
625            })
626    }
627
628    #[cfg(feature = "rdf")]
629    fn do_var_notations<E: AsyncEngine>(
630        &self,
631        iri: ulo::rdf_types::NamedNode,
632    ) -> impl Iterator<Item = (DocumentElementUri, Notation)> {
633        let q = crate::sparql!(SELECT DISTINCT ?n WHERE { ?n ulo:notation_for iri. });
634        self.triple_store()
635            .query::<E>(q)
636            .expect("Notations query should be valid")
637            .into_uris::<DocumentElementUri>()
638            .filter_map(|uri| {
639                use ftml_ontology::narrative::elements::notations::VariableNotationReference;
640                //tracing::warn!("Found {uri}");
641                let notation = self
642                    .get_typed_document_element::<VariableNotationReference>(&uri)
643                    .ok()?;
644                //tracing::warn!("Found {notation:?}");
645                self.get_reference(&notation.notation.with_doc(uri.document.clone()))
646                    .map_err(|e| tracing::error!("Error getting variable notation {uri}: {e}"))
647                    .ok()
648                    .map(|n| (uri, n))
649            })
650    }
651     */
652}