flams_math_archives/backend/
global.rs

1use 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.modules
302            .get_sync(uri.clone(), |uri| {
303                self.load_module(uri.archive_uri(), uri.path(), uri.name().as_ref())
304            })*/
305            self.load_module(uri.archive_uri(), uri.path(), uri.name().as_ref())
306                .map(ModuleLike::Module)
307        } else {
308            // SAFETY: !uri.is_top()
309            let SymbolUri { name, module } =
310                unsafe { uri.clone().into_symbol().unwrap_unchecked() };
311            let m = /*self.modules.get_sync(module, |uri| {
312                self.load_module(uri.archive_uri(), uri.path(), uri.name().as_ref())
313            })?;*/
314                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            /*
330            if let Some(m) = self.modules.has_async(uri) {
331                return either::Left(either::Left(m.map_ok(ModuleLike::Module)));
332            }
333            let lm =
334                self.load_module_async::<A>(uri.archive_uri(), uri.path(), uri.name().as_ref());
335            either::Left(either::Right(
336                self.modules
337                    .get(uri.clone(), |_| lm)
338                    .map_ok(ModuleLike::Module),
339            )) */
340            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            // SAFETY: !uri.is_top()
347            let SymbolUri { name, module } =
348                unsafe { uri.clone().into_symbol().unwrap_unchecked() };
349            let m = /*if let Some(m) = self.modules.has_async(&module) {
350                either::Left(m)
351            } else {
352                either::Right(*/self.load_module_async::<A>(
353                    module.archive_uri(),
354                    module.path(),
355                    module.name().as_ref(),
356                );//)
357            //};
358            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        /*if let Some(v) = self.documents.has(uri) {
400            let docfile = v?;
401            return then(&docfile);
402        }*/
403        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 = //self.documents.get_sync(uri.clone(), |_| {
420                    DocumentFile::from_file(file)
421                        .map(triomphe::Arc::new)
422                    //.map_err(Into::into)
423                //})?;
424                ?;
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        /*if let Some(v) = self.documents.has_async(uri) {
445            return either::Right(either::Left(async move {
446                match v.await {
447                    Ok(f) => then(f).await,
448                    Err(e) => Err(e),
449                }
450            }));
451        }*/
452        // TODO: a.document_file blocks; avoid!
453        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 = //self.documents.get(uri.clone(), |_| {
473                    A::block_on(move || {
474                        DocumentFile::from_file(file)
475                            .map(triomphe::Arc::new)
476                        //.map_err(Into::into)
477                        //})
478                });
479                either::Right(/*either::Right(*/either::Left(async move {
480                    let docfile = docfile.await?;
481                    then(docfile).await
482                }))//)
483            }
484            either::Right(r) => either::Right(either::Right(r))//either::Right(r))),
485        }
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                //tracing::warn!("Found {uri}");
501                let notation = self
502                    .get_typed_document_element::<NotationReference>(&uri)
503                    .ok()?;
504                //tracing::warn!("Found {notation:?}");
505                self.get_reference(&notation.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                //tracing::warn!("Found {uri}");
525                let notation = self
526                    .get_typed_document_element::<VariableNotationReference>(&uri)
527                    .ok()?;
528                //tracing::warn!("Found {notation:?}");
529                self.get_reference(&notation.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}