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>(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            // SAFETY: !uri.is_top()
342            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            // SAFETY: !uri.is_top()
392            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        // TODO: a.document_file blocks; avoid!
529        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        //get_ref: impl Fn(&DocDataRef<Notation>) -> Result<Notation, BackendError>,
588    ) -> 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                //tracing::warn!("Found {uri}");
603                let notation = backend.get_typed_document_element::<T>(&uri).ok()?;
604                //tracing::warn!("Found {notation:?}");
605                backend
606                    .get_reference(&get_not(&notation).with_doc(uri.document.clone()))
607                    //self.get_reference(&get_not(&notation).with_doc(uri.document.clone()))
608                    .map_err(|e| tracing::error!("Error getting notation {uri}: {e}"))
609                    .ok()
610                    .map(|n| (uri, n))
611            })
612    }
613}