flams_system/backend/
mod.rs

1pub mod archives;
2mod cache;
3pub mod docfile;
4pub mod rdf;
5
6use crate::{
7    formats::{HTMLData, SourceFormatId},
8    settings::Settings,
9};
10use archives::{
11    manager::ArchiveManager, source_files::FileState, Archive, ArchiveGroup, ArchiveOrGroup,
12    ArchiveTree, LocalArchive,
13};
14use cache::BackendCache;
15use eyre::Context;
16use flams_ontology::{
17    content::{
18        checking::ModuleChecker,
19        declarations::{Declaration, DeclarationTrait, OpenDeclaration},
20        modules::Module,
21        terms::Term,
22        ContentReference, ModuleLike,
23    },
24    languages::Language,
25    narration::{
26        checking::DocumentChecker,
27        documents::Document,
28        notations::{Notation, PresentationError, Presenter},
29        paragraphs::LogicalParagraph,
30        problems::Problem,
31        sections::Section,
32        DocumentElement, LazyDocRef, NarrationTrait, NarrativeReference,
33    },
34    uris::{
35        ArchiveId, ArchiveURI, ArchiveURITrait, BaseURI, ContentURITrait, DocumentElementURI,
36        DocumentURI, ModuleURI, NameStep, PathURIRef, PathURITrait, SymbolURI, URIOrRefTrait,
37        URIRefTrait, URIWithLanguage,
38    },
39    Checked, DocumentRange, LocalBackend, Unchecked,
40};
41use flams_utils::{
42    prelude::{HMap, TreeLike},
43    triomphe, unwrap,
44    vecmap::{VecMap, VecSet},
45    PathExt, CSS,
46};
47use lazy_static::lazy_static;
48use parking_lot::RwLock;
49use rdf::RDFStore;
50use std::{
51    ops::Deref,
52    path::{Path, PathBuf},
53    rc::Rc,
54};
55
56#[derive(Clone, Debug)]
57pub enum BackendChange {
58    NewArchive(ArchiveURI),
59    ArchiveUpdate(ArchiveURI),
60    ArchiveDeleted(ArchiveURI),
61    FileChange {
62        archive: ArchiveURI,
63        relative_path: String,
64        format: SourceFormatId,
65        old: Option<FileState>,
66        new: FileState,
67    },
68}
69
70pub trait Backend {
71    type ArchiveIter<'a>: Iterator<Item = &'a Archive>
72    where
73        Self: Sized;
74
75    #[inline]
76    fn presenter(&self) -> StringPresenter<'_, Self>
77    where
78        Self: Sized,
79    {
80        StringPresenter::new(self, false)
81    }
82
83    fn to_any(&self) -> AnyBackend;
84    fn get_document(&self, uri: &DocumentURI) -> Option<Document>;
85    fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike>;
86    fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf>;
87    fn get_declaration<T: DeclarationTrait>(&self, uri: &SymbolURI) -> Option<ContentReference<T>>
88    where
89        Self: Sized,
90    {
91        let m = self.get_module(uri.module())?;
92        // TODO this unnecessarily clones
93        ContentReference::new(&m, uri.name())
94    }
95    fn get_document_element<T: NarrationTrait>(
96        &self,
97        uri: &DocumentElementURI,
98    ) -> Option<NarrativeReference<T>>
99    where
100        Self: Sized,
101    {
102        let d = self.get_document(uri.document())?;
103        // TODO this unnecessarily clones
104        NarrativeReference::new(&d, uri.name())
105    }
106    fn with_archive_or_group<R>(
107        &self,
108        id: &ArchiveId,
109        f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
110    ) -> R
111    where
112        Self: Sized;
113
114    fn uri_of(&self, p: &Path) -> Option<DocumentURI>
115    where
116        Self: Sized,
117    {
118        #[cfg(windows)]
119        const PREFIX: &str = "\\source\\";
120        #[cfg(not(windows))]
121        const PREFIX: &str = "/source/";
122        self.archive_of(p, |a: &LocalArchive, rp: &str| {
123            DocumentURI::from_archive_relpath(a.uri().owned(), rp.strip_prefix(PREFIX)?).ok()
124        })
125        .flatten()
126    }
127
128    #[allow(irrefutable_let_patterns)]
129    fn archive_of<R>(&self, p: &Path, mut f: impl FnMut(&LocalArchive, &str) -> R) -> Option<R>
130    where
131        Self: Sized,
132    {
133        fn strip<'a>(base: &'a str, ap: &str) -> Option<&'a str> {
134            #[cfg(windows)]
135            {
136                if base.is_empty()
137                    || ap.is_empty()
138                    || !(&base[0..1]).eq_ignore_ascii_case(&ap[0..1])
139                {
140                    return None;
141                }
142                (&base[1..]).strip_prefix(&ap[1..])
143            }
144            #[cfg(not(windows))]
145            {
146                base.strip_prefix(ap)
147            }
148        }
149        let base = p.as_os_str().to_str()?;
150        self.with_archives(|mut a| {
151            a.find_map(|a| {
152                let Archive::Local(a) = a else { return None };
153                let ap = a.path().as_os_str().to_str()?;
154                if let Some(r) = strip(base, ap) {
155                    if r.starts_with(std::path::PathBuf::PATH_SEPARATOR) || r.is_empty() {
156                        return Some(f(a, r));
157                    }
158                }
159                None
160            })
161        })
162    }
163
164    fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
165    where
166        Self: Sized;
167
168    //fn with_archive_tree<R>(&self,f:impl FnOnce(&ArchiveTree) -> R) -> R where Self:Sized;
169
170    fn submit_triples(
171        &self,
172        in_doc: &DocumentURI,
173        rel_path: &str,
174        iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
175    ) where
176        Self: Sized;
177
178    fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R
179    where
180        Self: Sized;
181
182    fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)>;
183
184    fn get_html_full(&self, d: &DocumentURI) -> Option<String>;
185
186    fn get_html_fragment(
187        &self,
188        d: &DocumentURI,
189        range: DocumentRange,
190    ) -> Option<(Vec<CSS>, String)>;
191
192    fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T>
193    where
194        Self: Sized;
195
196    #[allow(unreachable_patterns)]
197    fn with_local_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&LocalArchive>) -> R) -> R
198    where
199        Self: Sized,
200    {
201        self.with_archive(id, |a| {
202            f(a.and_then(|a| match a {
203                Archive::Local(a) => Some(a),
204                _ => None,
205            }))
206        })
207    }
208
209    fn get_notations(&self, uri: &SymbolURI) -> Option<VecSet<(DocumentElementURI, Notation)>>
210    where
211        Self: Sized,
212    {
213        use flams_ontology::rdf::ontologies::ulo2;
214        use rdf::sparql::{Select, Var};
215        let iri = uri.to_iri();
216        let q = Select {
217            subject: Var('n'),
218            pred: ulo2::NOTATION_FOR.into_owned(),
219            object: iri,
220        };
221        let ret: VecSet<_> = GlobalBackend::get()
222            .triple_store()
223            .query(q.into())
224            .ok()?
225            .into_uris()
226            .filter_map(|uri| {
227                let elem = self.get_document_element::<DocumentElement<Checked>>(&uri)?;
228                let DocumentElement::Notation { notation, .. } = elem.as_ref() else {
229                    return None;
230                };
231                self.get_reference(notation).ok().map(|n| (uri, n))
232            })
233            .collect();
234        if ret.is_empty() {
235            None
236        } else {
237            Some(ret)
238        }
239    }
240
241    fn get_var_notations(
242        &self,
243        uri: &DocumentElementURI,
244    ) -> Option<VecSet<(DocumentElementURI, Notation)>>
245    where
246        Self: Sized,
247    {
248        let parent = uri.parent();
249        let parent = self.get_document_element::<DocumentElement<Checked>>(&parent)?;
250        let mut ch = parent.as_ref().children().iter();
251        let mut stack = Vec::new();
252        let mut ret = VecSet::new();
253        loop {
254            let Some(next) = ch.next() else {
255                if let Some(n) = stack.pop() {
256                    ch = n;
257                    continue;
258                }
259                break;
260            };
261            let (uri, not) = match next {
262                DocumentElement::Module { children, .. }
263                | DocumentElement::Section(Section { children, .. })
264                | DocumentElement::Morphism { children, .. }
265                | DocumentElement::MathStructure { children, .. }
266                | DocumentElement::Extension { children, .. }
267                | DocumentElement::Paragraph(LogicalParagraph { children, .. })
268                | DocumentElement::Problem(Problem { children, .. }) => {
269                    let old = std::mem::replace(&mut ch, children.iter());
270                    stack.push(old);
271                    continue;
272                }
273                DocumentElement::VariableNotation {
274                    variable,
275                    id,
276                    notation,
277                } if variable == uri => (id, notation),
278                _ => continue,
279            };
280            let Some(r) = self.get_reference(not).ok() else {
281                continue;
282            };
283            ret.insert((uri.clone(), r));
284        }
285        if ret.is_empty() {
286            None
287        } else {
288            Some(ret)
289        }
290    }
291
292    /*fn get_archive_for_path(p:&Path) -> Option<(ArchiveURI,String)> {
293
294    }*/
295
296    #[inline]
297    fn as_checker(&self) -> AsChecker<Self>
298    where
299        Self: Sized,
300    {
301        AsChecker(self)
302    }
303}
304
305#[derive(Clone, Debug)]
306pub enum AnyBackend {
307    Global(&'static GlobalBackend),
308    Temp(TemporaryBackend),
309    Sandbox(SandboxedBackend),
310}
311impl AnyBackend {
312    #[must_use]
313    pub fn mathhubs(&self) -> Vec<PathBuf> {
314        let mut global: Vec<PathBuf> = Settings::get()
315            .mathhubs
316            .iter()
317            .map(|p| p.to_path_buf())
318            .collect();
319        match self {
320            Self::Global(_) | Self::Temp(_) => global,
321            Self::Sandbox(s) => {
322                global.insert(0, s.0.path.to_path_buf());
323                global
324            }
325        }
326    }
327}
328
329pub enum EitherArchiveIter<'a> {
330    Global(std::slice::Iter<'a, Archive>),
331    Sandbox(std::iter::Chain<std::slice::Iter<'a, Archive>, std::slice::Iter<'a, Archive>>),
332}
333impl<'a> From<std::slice::Iter<'a, Archive>> for EitherArchiveIter<'a> {
334    #[inline]
335    fn from(value: std::slice::Iter<'a, Archive>) -> Self {
336        Self::Global(value)
337    }
338}
339impl<'a> From<std::iter::Chain<std::slice::Iter<'a, Archive>, std::slice::Iter<'a, Archive>>>
340    for EitherArchiveIter<'a>
341{
342    #[inline]
343    fn from(
344        value: std::iter::Chain<std::slice::Iter<'a, Archive>, std::slice::Iter<'a, Archive>>,
345    ) -> Self {
346        Self::Sandbox(value)
347    }
348}
349impl<'a> Iterator for EitherArchiveIter<'a> {
350    type Item = &'a Archive;
351    #[inline]
352    fn next(&mut self) -> Option<Self::Item> {
353        match self {
354            Self::Global(i) => i.next(),
355            Self::Sandbox(i) => i.next(),
356        }
357    }
358}
359
360impl Backend for AnyBackend {
361    type ArchiveIter<'a> = EitherArchiveIter<'a>;
362    #[inline]
363    fn to_any(&self) -> AnyBackend {
364        self.clone()
365    }
366
367    #[inline]
368    fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
369    where
370        Self: Sized,
371    {
372        match self {
373            Self::Global(b) => b.with_archives(|i| f(i.into())),
374            Self::Temp(b) => b.with_archives(f),
375            Self::Sandbox(b) => b.with_archives(|i| f(i.into())),
376        }
377    }
378
379    #[inline]
380    fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
381        match self {
382            Self::Global(b) => b.get_reference(rf),
383            Self::Temp(b) => b.get_reference(rf),
384            Self::Sandbox(b) => b.get_reference(rf),
385        }
386    }
387
388    #[inline]
389    fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
390        match self {
391            Self::Global(b) => b.get_html_body(d, full),
392            Self::Temp(b) => b.get_html_body(d, full),
393            Self::Sandbox(b) => b.get_html_body(d, full),
394        }
395    }
396
397    #[inline]
398    fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
399        match self {
400            Self::Global(b) => b.get_html_full(d),
401            Self::Temp(b) => b.get_html_full(d),
402            Self::Sandbox(b) => b.get_html_full(d),
403        }
404    }
405
406    #[inline]
407    fn get_html_fragment(
408        &self,
409        d: &DocumentURI,
410        range: DocumentRange,
411    ) -> Option<(Vec<CSS>, String)> {
412        match self {
413            Self::Global(b) => b.get_html_fragment(d, range),
414            Self::Temp(b) => b.get_html_fragment(d, range),
415            Self::Sandbox(b) => b.get_html_fragment(d, range),
416        }
417    }
418
419    #[inline]
420    fn submit_triples(
421        &self,
422        in_doc: &DocumentURI,
423        rel_path: &str,
424        iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
425    ) {
426        match self {
427            Self::Global(b) => b.submit_triples(in_doc, rel_path, iter),
428            Self::Temp(b) => b.submit_triples(in_doc, rel_path, iter),
429            Self::Sandbox(b) => b.submit_triples(in_doc, rel_path, iter),
430        }
431    }
432
433    #[inline]
434    fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
435        match self {
436            Self::Global(b) => b.get_document(uri),
437            Self::Temp(b) => b.get_document(uri),
438            Self::Sandbox(b) => b.get_document(uri),
439        }
440    }
441
442    #[inline]
443    fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
444        match self {
445            Self::Global(b) => b.get_module(uri),
446            Self::Temp(b) => b.get_module(uri),
447            Self::Sandbox(b) => b.get_module(uri),
448        }
449    }
450
451    #[inline]
452    fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
453        match self {
454            Self::Global(b) => b.get_base_path(id),
455            Self::Temp(b) => b.get_base_path(id),
456            Self::Sandbox(b) => b.get_base_path(id),
457        }
458    }
459
460    #[inline]
461    fn get_declaration<T: DeclarationTrait>(&self, uri: &SymbolURI) -> Option<ContentReference<T>>
462    where
463        Self: Sized,
464    {
465        match self {
466            Self::Global(b) => b.get_declaration(uri),
467            Self::Temp(b) => b.get_declaration(uri),
468            Self::Sandbox(b) => b.get_declaration(uri),
469        }
470    }
471
472    #[inline]
473    fn with_archive_or_group<R>(
474        &self,
475        id: &ArchiveId,
476        f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
477    ) -> R
478    where
479        Self: Sized,
480    {
481        match self {
482            Self::Global(b) => b.with_archive_or_group(id, f),
483            Self::Temp(b) => b.with_archive_or_group(id, f),
484            Self::Sandbox(b) => b.with_archive_or_group(id, f),
485        }
486    }
487
488    #[inline]
489    fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R
490    where
491        Self: Sized,
492    {
493        match self {
494            Self::Global(b) => b.with_archive(id, f),
495            Self::Temp(b) => b.with_archive(id, f),
496            Self::Sandbox(b) => b.with_archive(id, f),
497        }
498    }
499
500    #[inline]
501    fn with_local_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&LocalArchive>) -> R) -> R
502    where
503        Self: Sized,
504    {
505        match self {
506            Self::Global(b) => b.with_local_archive(id, f),
507            Self::Temp(b) => b.with_local_archive(id, f),
508            Self::Sandbox(b) => b.with_local_archive(id, f),
509        }
510    }
511}
512
513#[derive(Debug)]
514pub struct GlobalBackend {
515    archives: ArchiveManager,
516    cache: RwLock<cache::BackendCache>,
517    triple_store: RDFStore,
518}
519
520lazy_static! {
521    static ref GLOBAL: GlobalBackend = GlobalBackend {
522        archives: ArchiveManager::default(),
523        cache: RwLock::new(cache::BackendCache::default()),
524        triple_store: RDFStore::default()
525    };
526}
527
528impl GlobalBackend {
529    #[inline]
530    #[must_use]
531    pub fn get() -> &'static Self
532    where
533        Self: Sized,
534    {
535        &GLOBAL
536    }
537
538    pub fn initialize() {
539        let settings = crate::settings::Settings::get();
540        let archives = Self::get().manager();
541        for p in settings.mathhubs.iter().rev() {
542            archives.load(p);
543        }
544        let f = || {
545            let backend = Self::get();
546            backend
547                .triple_store()
548                .load_archives(&backend.all_archives());
549        };
550        #[cfg(feature = "tokio")]
551        flams_utils::background(f);
552        #[cfg(not(feature = "tokio"))]
553        f();
554        #[cfg(feature = "tantivy")]
555        {
556            #[cfg(feature = "tokio")]
557            flams_utils::background(|| crate::search::Searcher::get().reload());
558            #[cfg(not(feature = "tokio"))]
559            crate::search::Searcher::get().reload();
560        }
561    }
562
563    pub fn new_archive(
564        &self,
565        id: &ArchiveId,
566        base_uri: &BaseURI,
567        format: &str,
568        default_file: &str,
569        content: &str,
570    ) -> Result<PathBuf, eyre::Report> {
571        let settings = crate::settings::Settings::get();
572        // should be impossible
573        let mh = settings
574            .mathhubs
575            .first()
576            .expect("No mathhub directories found!");
577        let meta_inf = id
578            .steps()
579            .fold(mh.to_path_buf(), |p, s| p.join(s))
580            .join("META-INF");
581
582        std::fs::create_dir_all(&meta_inf)
583            .wrap_err_with(|| format!("Failed to create directory {}", meta_inf.display()))?;
584        std::fs::write(
585            meta_inf.join("MANIFEST.MF"),
586            &format!("id: {id}\nurl-base: {base_uri}\nformat: {format}"),
587        )
588        .wrap_err_with(|| format!("Failed to create file {}/MANIFEST.MF", meta_inf.display()))?;
589
590        let parent = unwrap!(meta_inf.parent());
591        std::fs::write(
592            parent.join(".gitignore"),
593            include_str!("gitignore_template.txt"),
594        )
595        .wrap_err_with(|| format!("Failed to create file {}/.gitignore", parent.display()))?;
596
597        let lib = parent.join("lib");
598        std::fs::create_dir_all(&lib)
599            .wrap_err_with(|| format!("Failed to create directory {}", lib.display()))?;
600        std::fs::write(
601            lib.join("preamble.tex"),
602            &format!("% preamble code for {id}"),
603        )
604        .wrap_err_with(|| format!("Failed to create file {}/preamble.tex", lib.display()))?;
605
606        let source = parent.join("source");
607        std::fs::create_dir_all(&source)
608            .wrap_err_with(|| format!("Failed to create directory {}", source.display()))?;
609        let dflt = source.join(default_file);
610        std::fs::write(&dflt, content)
611            .wrap_err_with(|| format!("Failed to create file {}/{default_file}", lib.display()))?;
612        self.manager().load(parent);
613
614        Ok(dflt)
615    }
616
617    pub fn artifact_path(&self, uri: &DocumentURI, format: &str) -> Option<PathBuf> {
618        let id = uri.archive_id();
619        let language = uri.language();
620        let name = uri.name().first_name();
621        self.with_local_archive(id, |a| {
622            a.and_then(|a| a.get_filepath(uri.path(), name, language, format))
623        })
624    }
625
626    #[inline]
627    pub fn with_archive_tree<R>(&self, f: impl FnOnce(&ArchiveTree) -> R) -> R {
628        self.archives.with_tree(f)
629    }
630
631    pub fn reset(&self) {
632        self.cache.write().clear();
633        self.archives.reinit(
634            |_| (),
635            crate::settings::Settings::get()
636                .mathhubs
637                .iter()
638                .map(|b| &**b),
639        );
640        self.triple_store.clear();
641        let f = || {
642            let global = Self::get();
643            global.triple_store.load_archives(&global.all_archives());
644        };
645        #[cfg(feature = "tokio")]
646        flams_utils::background(f);
647        #[cfg(not(feature = "tokio"))]
648        f();
649        #[cfg(feature = "tantivy")]
650        {
651            #[cfg(feature = "tokio")]
652            flams_utils::background(|| crate::search::Searcher::get().reload());
653            #[cfg(not(feature = "tokio"))]
654            crate::search::Searcher::get().reload();
655        }
656    }
657
658    #[cfg(feature = "tokio")]
659    pub async fn get_html_body_async(
660        &self,
661        d: &DocumentURI,
662        full: bool,
663    ) -> Option<(Vec<CSS>, String)> {
664        let f = self.manager().with_archive(d.archive_id(), move |a| {
665            a.map(move |a| {
666                a.load_html_body_async(d.path(), d.name().first_name(), d.language(), full)
667            })
668        })??;
669        f.await
670    }
671
672    #[cfg(feature = "tokio")]
673    pub async fn get_html_full_async(&self, d: &DocumentURI) -> Option<String> {
674        let f = self.manager().with_archive(d.archive_id(), move |a| {
675            a.map(move |a| a.load_html_full_async(d.path(), d.name().first_name(), d.language()))
676        })??;
677        f.await
678    }
679
680    #[cfg(feature = "tokio")]
681    pub async fn get_html_fragment_async(
682        &self,
683        d: &DocumentURI,
684        range: DocumentRange,
685    ) -> Option<(Vec<CSS>, String)> {
686        let f = self.manager().with_archive(d.archive_id(), move |a| {
687            a.map(move |a| {
688                a.load_html_fragment_async(d.path(), d.name().first_name(), d.language(), range)
689            })
690        })??;
691        f.await
692    }
693
694    #[inline]
695    pub const fn manager(&self) -> &ArchiveManager {
696        &self.archives
697    }
698
699    #[inline]
700    pub const fn triple_store(&self) -> &RDFStore {
701        &self.triple_store
702    }
703
704    #[inline]
705    pub fn all_archives(&self) -> impl Deref<Target = [Archive]> + '_ {
706        self.archives.all_archives()
707    }
708
709    #[cfg(feature = "tokio")]
710    #[allow(clippy::similar_names)]
711    #[allow(clippy::significant_drop_tightening)]
712    pub async fn get_document_async(&self, uri: &DocumentURI) -> Option<Document> {
713        {
714            let lock = self.cache.read();
715            if let Some(doc) = lock.has_document(uri) {
716                return Some(doc.clone());
717            }
718        }
719        let uri = uri.clone();
720        tokio::task::spawn_blocking(move || {
721            let slf = Self::get();
722            let mut cache = slf.cache.write();
723            let mut flattener = GlobalFlattener(&mut cache, &slf.archives);
724            flattener.load_document(uri.as_path(), uri.language(), uri.name().first_name())
725        })
726        .await
727        .ok()
728        .flatten()
729    }
730
731    #[cfg(feature = "tokio")]
732    #[allow(clippy::similar_names)]
733    #[allow(clippy::significant_drop_tightening)]
734    pub async fn get_module_async(&self, uri: &ModuleURI) -> Option<ModuleLike> {
735        {
736            let lock = self.cache.read();
737            if uri.name().is_simple() {
738                if let Some(m) = lock.has_module(uri) {
739                    return Some(ModuleLike::Module(m.clone()));
740                }
741            } else {
742                let top_uri = !uri.clone();
743                if let Some(m) = lock.has_module(&top_uri) {
744                    return ModuleLike::in_module(m, uri.name());
745                }
746            }
747        }
748
749        let top = !uri.clone();
750        let m = tokio::task::spawn_blocking(move || {
751            let slf = Self::get();
752            let mut cache = slf.cache.write();
753            let mut flattener = GlobalFlattener(&mut cache, &slf.archives);
754            flattener.load_module(top.as_path(), top.name().first_name())
755        })
756        .await
757        .ok()??;
758        ModuleLike::in_module(&m, uri.name())
759    }
760
761    #[cfg(feature = "tokio")]
762    pub async fn get_declaration_async<T: DeclarationTrait>(
763        &self,
764        uri: &SymbolURI,
765    ) -> Option<ContentReference<T>> {
766        let m = self.get_module_async(uri.module()).await?;
767        // TODO this unnecessarily clones
768        ContentReference::new(&m, uri.name())
769    }
770
771    #[cfg(feature = "tokio")]
772    pub async fn get_document_element_async<T: NarrationTrait>(
773        &self,
774        uri: &DocumentElementURI,
775    ) -> Option<NarrativeReference<T>> {
776        let d = self.get_document_async(uri.document()).await?;
777        // TODO this unnecessarily clones
778        NarrativeReference::new(&d, uri.name())
779    }
780}
781
782impl Backend for &'static GlobalBackend {
783    type ArchiveIter<'a> = std::slice::Iter<'a, Archive>;
784
785    #[inline]
786    fn to_any(&self) -> AnyBackend {
787        AnyBackend::Global(self)
788    }
789
790    #[inline]
791    fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
792        GlobalBackend::get_html_body(self, d, full)
793    }
794
795    #[inline]
796    fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
797        GlobalBackend::get_html_full(self, d)
798    }
799
800    #[inline]
801    fn get_html_fragment(
802        &self,
803        d: &DocumentURI,
804        range: DocumentRange,
805    ) -> Option<(Vec<CSS>, String)> {
806        GlobalBackend::get_html_fragment(self, d, range)
807    }
808
809    #[inline]
810    fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
811        GlobalBackend::get_reference(self, rf)
812    }
813
814    #[inline]
815    fn submit_triples(
816        &self,
817        in_doc: &DocumentURI,
818        rel_path: &str,
819        iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
820    ) {
821        GlobalBackend::submit_triples(self, in_doc, rel_path, iter);
822    }
823
824    fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
825    where
826        Self: Sized,
827    {
828        GlobalBackend::with_archives(self, f)
829    }
830
831    #[inline]
832    fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
833        GlobalBackend::with_archive(self, id, f)
834    }
835
836    #[inline]
837    fn with_local_archive<R>(
838        &self,
839        id: &ArchiveId,
840        f: impl FnOnce(Option<&LocalArchive>) -> R,
841    ) -> R {
842        GlobalBackend::with_local_archive(self, id, f)
843    }
844    #[inline]
845    fn with_archive_or_group<R>(
846        &self,
847        id: &ArchiveId,
848        f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
849    ) -> R {
850        GlobalBackend::with_archive_or_group(self, id, f)
851    }
852    #[inline]
853    fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
854        GlobalBackend::get_document(self, uri)
855    }
856    #[inline]
857    fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
858        GlobalBackend::get_module(self, uri)
859    }
860    #[inline]
861    fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
862        GlobalBackend::get_base_path(self, id)
863    }
864    #[inline]
865    fn get_declaration<T: DeclarationTrait>(&self, uri: &SymbolURI) -> Option<ContentReference<T>> {
866        GlobalBackend::get_declaration(self, uri)
867    }
868}
869
870impl Backend for GlobalBackend {
871    type ArchiveIter<'a> = std::slice::Iter<'a, Archive>;
872
873    #[inline]
874    fn to_any(&self) -> AnyBackend {
875        AnyBackend::Global(Self::get())
876    }
877
878    fn get_html_fragment(
879        &self,
880        d: &DocumentURI,
881        range: DocumentRange,
882    ) -> Option<(Vec<CSS>, String)> {
883        self.archives.with_archive(d.archive_id(), |a| {
884            a.and_then(|a| {
885                a.load_html_fragment(d.path(), d.name().first_name(), d.language(), range)
886            })
887        })
888    }
889
890    fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
891        self.archives.with_archive(rf.in_doc.archive_id(), |a| {
892            let Some(a) = a else {
893                return Err(eyre::eyre!("Archive {} not found", rf.in_doc.archive_id()));
894            };
895            a.load_reference(
896                rf.in_doc.path(),
897                rf.in_doc.name().first_name(),
898                rf.in_doc.language(),
899                DocumentRange {
900                    start: rf.start,
901                    end: rf.end,
902                },
903            )
904        })
905    }
906
907    #[inline]
908    fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
909    where
910        Self: Sized,
911    {
912        self.archives.with_tree(|t| f(t.archives.iter()))
913    }
914
915    fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
916        self.archives.with_archive(d.archive_id(), |a| {
917            a.and_then(|a| a.load_html_body(d.path(), d.name().first_name(), d.language(), full))
918        })
919    }
920
921    #[inline]
922    fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
923        self.archives.with_archive(d.archive_id(), |a| {
924            a.and_then(|a| a.load_html_full(d.path(), d.name().first_name(), d.language()))
925        })
926    }
927
928    fn submit_triples(
929        &self,
930        in_doc: &DocumentURI,
931        rel_path: &str,
932        iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
933    ) {
934        self.archives.with_archive(in_doc.archive_id(), |a| {
935            if let Some(a) = a {
936                a.submit_triples(in_doc, rel_path, self.triple_store(), true, iter);
937            }
938        });
939    }
940
941    #[inline]
942    fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
943        let archives = &*self.all_archives();
944        f(archives.iter().find(|a| a.uri().archive_id() == id))
945    }
946
947    fn with_archive_or_group<R>(
948        &self,
949        id: &ArchiveId,
950        f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
951    ) -> R {
952        self.with_archive_tree(|t| f(t.find(id)))
953    }
954
955    fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
956        self.with_local_archive(id, |a| a.map(|a| a.path().to_path_buf()))
957    }
958
959    #[allow(clippy::significant_drop_tightening)]
960    fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
961        {
962            let lock = self.cache.read();
963            if let Some(doc) = lock.has_document(uri) {
964                return Some(doc.clone());
965            }
966        }
967        let mut cache = self.cache.write();
968        let mut flattener = GlobalFlattener(&mut cache, &self.archives);
969        flattener.load_document(uri.as_path(), uri.language(), uri.name().first_name())
970    }
971
972    #[allow(clippy::significant_drop_tightening)]
973    fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
974        {
975            let lock = self.cache.read();
976            if uri.name().is_simple() {
977                if let Some(m) = lock.has_module(uri) {
978                    return Some(ModuleLike::Module(m.clone()));
979                }
980            } else {
981                let top_uri = !uri.clone();
982                if let Some(m) = lock.has_module(&top_uri) {
983                    return ModuleLike::in_module(m, uri.name());
984                }
985            }
986        }
987        let m = {
988            let mut cache = self.cache.write();
989            let mut flattener = GlobalFlattener(&mut cache, &self.archives);
990            flattener.load_module(uri.as_path(), uri.name().first_name())?
991        };
992        // TODO: this unnecessarily clones
993        ModuleLike::in_module(&m, uri.name())
994    }
995}
996
997#[derive(Debug)]
998struct TemporaryBackendI {
999    modules: parking_lot::Mutex<HMap<ModuleURI, Module>>,
1000    documents: parking_lot::Mutex<HMap<DocumentURI, Document>>,
1001    html: parking_lot::Mutex<HMap<DocumentURI, HTMLData>>,
1002    parent: AnyBackend,
1003}
1004
1005#[derive(Clone, Debug)]
1006pub struct TemporaryBackend {
1007    inner: triomphe::Arc<TemporaryBackendI>,
1008}
1009impl Default for TemporaryBackend {
1010    #[inline]
1011    fn default() -> Self {
1012        Self::new(GlobalBackend::get().to_any())
1013    }
1014}
1015
1016impl TemporaryBackend {
1017    pub fn reset(&self) {
1018        self.inner.modules.lock().clear();
1019        self.inner.documents.lock().clear();
1020        let global = GlobalBackend::get();
1021        global.reset();
1022    }
1023
1024    #[must_use]
1025    pub fn new(parent: AnyBackend) -> Self {
1026        Self {
1027            inner: triomphe::Arc::new(TemporaryBackendI {
1028                modules: parking_lot::Mutex::new(HMap::default()),
1029                documents: parking_lot::Mutex::new(HMap::default()),
1030                html: parking_lot::Mutex::new(HMap::default()),
1031                parent,
1032            }),
1033        }
1034    }
1035    pub fn add_module(&self, m: Module) {
1036        self.inner.modules.lock().insert(m.uri().clone(), m);
1037    }
1038    pub fn add_document(&self, d: Document) {
1039        self.inner.documents.lock().insert(d.uri().clone(), d);
1040    }
1041    pub fn add_html(&self, uri: DocumentURI, d: HTMLData) {
1042        self.inner.html.lock().insert(uri, d);
1043    }
1044}
1045
1046impl Backend for TemporaryBackend {
1047    type ArchiveIter<'a> = EitherArchiveIter<'a>;
1048
1049    #[inline]
1050    fn to_any(&self) -> AnyBackend {
1051        AnyBackend::Temp(self.clone())
1052    }
1053    fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
1054        self.inner
1055            .documents
1056            .lock()
1057            .get(uri)
1058            .cloned()
1059            .or_else(|| self.inner.parent.get_document(uri))
1060    }
1061
1062    #[inline]
1063    fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
1064    where
1065        Self: Sized,
1066    {
1067        self.inner.parent.with_archives(f)
1068    }
1069
1070    fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
1071        self.inner.html.lock().get(d).map_or_else(
1072            || self.inner.parent.get_html_body(d, full),
1073            |html| {
1074                Some((
1075                    html.css.clone(),
1076                    if full {
1077                        html.html[html.body.start..html.body.end].to_string()
1078                    } else {
1079                        html.html[html.body.start + html.inner_offset..html.body.end].to_string()
1080                    },
1081                ))
1082            },
1083        )
1084    }
1085
1086    #[inline]
1087    fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
1088        self.inner.html.lock().get(d).map_or_else(
1089            || self.inner.parent.get_html_full(d),
1090            |html| Some(html.html.clone()),
1091        )
1092    }
1093
1094    fn get_html_fragment(
1095        &self,
1096        d: &DocumentURI,
1097        range: DocumentRange,
1098    ) -> Option<(Vec<CSS>, String)> {
1099        self.inner.html.lock().get(d).map_or_else(
1100            || self.inner.parent.get_html_fragment(d, range),
1101            |html| {
1102                Some((
1103                    html.css.clone(),
1104                    html.html[range.start..range.end].to_string(),
1105                ))
1106            },
1107        )
1108    }
1109
1110    fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
1111        let lock = self.inner.html.lock();
1112        let Some(html) = lock.get(&rf.in_doc) else {
1113            return self.inner.parent.get_reference(rf);
1114        };
1115
1116        let Some(bytes) = html.refs.as_slice().get(rf.start..rf.end) else {
1117            return Err(eyre::eyre!("reference has invalid start/end points"));
1118        };
1119        let (r, _) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())?;
1120        Ok(r)
1121    }
1122
1123    fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
1124        if uri.name().is_simple() {
1125            return self
1126                .inner
1127                .modules
1128                .lock()
1129                .get(uri)
1130                .cloned()
1131                .map(ModuleLike::Module)
1132                .or_else(|| self.inner.parent.get_module(uri));
1133        }
1134        let top_uri = !uri.clone();
1135        let top = self
1136            .inner
1137            .modules
1138            .lock()
1139            .get(&top_uri)
1140            .cloned()
1141            .or_else(|| match self.inner.parent.get_module(&top_uri) {
1142                Some(ModuleLike::Module(m)) => Some(m),
1143                _ => None,
1144            })?;
1145        ModuleLike::in_module(&top, uri.name())
1146    }
1147    #[inline]
1148    fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
1149        self.inner.parent.get_base_path(id)
1150    }
1151
1152    #[inline]
1153    fn with_archive_or_group<R>(
1154        &self,
1155        id: &ArchiveId,
1156        f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
1157    ) -> R
1158    where
1159        Self: Sized,
1160    {
1161        self.inner.parent.with_archive_or_group(id, f)
1162    }
1163
1164    #[inline]
1165    fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
1166        self.inner.parent.with_archive(id, f)
1167    }
1168
1169    #[inline]
1170    fn submit_triples(
1171        &self,
1172        in_doc: &DocumentURI,
1173        rel_path: &str,
1174        iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
1175    ) where
1176        Self: Sized,
1177    {
1178        self.inner.parent.submit_triples(in_doc, rel_path, iter);
1179    }
1180}
1181
1182#[derive(Debug, Clone)]
1183pub enum SandboxedRepository {
1184    Copy(ArchiveId),
1185    Git {
1186        id: ArchiveId,
1187        branch: Box<str>,
1188        commit: flams_git::Commit,
1189        remote: Box<str>,
1190    },
1191}
1192impl SandboxedRepository {
1193    #[inline]
1194    #[must_use]
1195    pub const fn id(&self) -> &ArchiveId {
1196        match self {
1197            Self::Copy(id) | Self::Git { id, .. } => id,
1198        }
1199    }
1200}
1201
1202#[derive(Debug)]
1203pub(super) struct SandboxedBackendI {
1204    path: Box<Path>,
1205    span: tracing::Span,
1206    pub(super) repos: parking_lot::RwLock<Vec<SandboxedRepository>>,
1207    manager: ArchiveManager,
1208    cache: RwLock<cache::BackendCache>,
1209}
1210#[derive(Debug, Clone)]
1211pub struct SandboxedBackend(pub(super) triomphe::Arc<SandboxedBackendI>);
1212impl Drop for SandboxedBackendI {
1213    fn drop(&mut self) {
1214        let _ = std::fs::remove_dir_all(&self.path);
1215    }
1216}
1217impl SandboxedBackend {
1218    #[inline]
1219    #[must_use]
1220    pub fn get_repos(&self) -> Vec<SandboxedRepository> {
1221        self.0.repos.read().clone()
1222    }
1223
1224    #[inline]
1225    pub fn with_repos<R>(&self, f: impl FnOnce(&[SandboxedRepository]) -> R) -> R {
1226        let inner = self.0.repos.read();
1227        f(inner.as_slice())
1228    }
1229
1230    #[inline]
1231    #[must_use]
1232    pub fn path_for(&self, id: &ArchiveId) -> PathBuf {
1233        self.0.path.join(id.as_ref())
1234    }
1235
1236    pub fn new(name: &str) -> Self {
1237        let p = crate::settings::Settings::get().temp_dir().join(name);
1238        let i = SandboxedBackendI {
1239            span: tracing::info_span!(target:"sandbox","sandbox",path=%p.display()),
1240            path: p.into(),
1241            repos: parking_lot::RwLock::new(Vec::new()),
1242            manager: ArchiveManager::default(),
1243            cache: RwLock::new(cache::BackendCache::default()),
1244        };
1245        Self(triomphe::Arc::new(i))
1246    }
1247
1248    #[cfg(feature = "tokio")]
1249    #[tracing::instrument(level = "info",
1250        parent = &self.0.span,
1251        target = "sandbox",
1252        name = "migrating",
1253        fields(path = %self.0.path.display()),
1254        skip_all
1255    )]
1256    pub fn migrate(&self) -> eyre::Result<usize> {
1257        use eyre::Context;
1258        use flams_utils::{impossible, PathExt};
1259
1260        let mut count = 0;
1261        let cnt = &mut count;
1262        let global = GlobalBackend::get();
1263        let mut global_cache = global.cache.write();
1264        let mut sandbox_cache = self.0.cache.write();
1265        self.0.manager.reinit::<eyre::Result<()>>(
1266            move |sandbox| {
1267                global.archives.reinit::<eyre::Result<()>>(
1268                    |_| {
1269                        sandbox.groups.clear();
1270                        let Some(main) = Settings::get().mathhubs.first() else {
1271                            unreachable!()
1272                        };
1273                        for a in std::mem::take(&mut sandbox.archives) {
1274                            *cnt += 1;
1275                            #[allow(irrefutable_let_patterns)]
1276                            let Archive::Local(a) = a
1277                            else {
1278                                impossible!()
1279                            };
1280                            let source = a.path();
1281                            let target = main.join(a.id().as_ref());
1282
1283                            if let Some(p) = target.parent() {
1284                                std::fs::create_dir_all(p).wrap_err_with(|| {
1285                                    format!("Failed to create parent directory for {}", a.id())
1286                                })?;
1287                            }
1288                            let safe_target = unwrap!(target.parent())
1289                                .join(format!(".{}.tmp", unwrap!(target.file_name()).display()));
1290                            if safe_target.exists() {
1291                                std::fs::remove_dir_all(&safe_target).wrap_err_with(|| {
1292                                    format!(
1293                                        "Failed to remove existing taget dir {}",
1294                                        safe_target.display()
1295                                    )
1296                                })?;
1297                            }
1298                            if let Err(e) = source.rename_safe(&safe_target) {
1299                                let e = e.wrap_err(format!("failed to migrate {}", a.id()));
1300                                let _ = std::fs::remove_dir_all(safe_target);
1301                                return Err(e);
1302                            }
1303                            if target.exists() {
1304                                std::fs::remove_dir_all(&target).wrap_err_with(|| {
1305                                    format!("Failed to remove original archive {}", a.id())
1306                                })?;
1307                            }
1308                            std::fs::rename(safe_target, target).wrap_err_with(|| {
1309                                format!("Failed to install updated archive {}", a.id())
1310                            })?;
1311                        }
1312                        Ok(())
1313                    },
1314                    Settings::get().mathhubs.iter().map(|p| &**p),
1315                )
1316            },
1317            [&*self.0.path],
1318        )?;
1319        global.triple_store.clear();
1320        global_cache.clear();
1321        sandbox_cache.clear();
1322        drop(global_cache);
1323        drop(sandbox_cache);
1324        flams_utils::background(|| {
1325            let global = GlobalBackend::get();
1326            global.triple_store.load_archives(&global.all_archives());
1327        });
1328        Ok(count)
1329    }
1330
1331    #[tracing::instrument(level = "info",
1332        parent = &self.0.span,
1333        target = "sandbox",
1334        name = "adding",
1335        fields(repository = ?sb),
1336        skip_all
1337    )]
1338    pub fn add(&self, sb: SandboxedRepository, then: impl FnOnce()) {
1339        let mut repos = self.0.repos.write();
1340        let id = sb.id();
1341        if let Some(i) = repos.iter().position(|r| r.id() == id) {
1342            repos.remove(i);
1343        }
1344        self.require_meta_infs(
1345            id,
1346            &mut repos,
1347            |_, _| {},
1348            |_, _, _| {
1349                tracing::error!(target:"sandbox","A group with id {id} already exists!");
1350            },
1351            || {},
1352        );
1353        repos.push(sb);
1354        drop(repos);
1355        then();
1356        self.0.manager.load(&self.0.path);
1357    }
1358
1359    fn require_meta_infs(
1360        &self,
1361        id: &ArchiveId,
1362        repos: &mut Vec<SandboxedRepository>,
1363        then: impl FnOnce(&LocalArchive, &mut Vec<SandboxedRepository>),
1364        group: impl FnOnce(&ArchiveGroup, &ArchiveTree, &mut Vec<SandboxedRepository>),
1365        else_: impl FnOnce(),
1366    ) {
1367        if repos.iter().any(|r| r.id() == id) {
1368            return;
1369        }
1370        let backend = GlobalBackend::get();
1371        backend.manager().with_tree(move |t| {
1372            let mut steps = id.steps();
1373            let Some(mut current) = steps.next() else {
1374                tracing::error!("empty archive ID");
1375                return;
1376            };
1377            let mut ls = &t.groups;
1378            loop {
1379                let Some(a) = ls.iter().find(|a| a.id().last_name() == current) else {
1380                    else_();
1381                    return;
1382                };
1383                match a {
1384                    ArchiveOrGroup::Archive(_) => {
1385                        if steps.next().is_some() {
1386                            else_();
1387                            return;
1388                        }
1389                        let Some(Archive::Local(a)) = t.get(id) else {
1390                            else_();
1391                            return;
1392                        };
1393                        then(a, repos);
1394                        return;
1395                    }
1396                    ArchiveOrGroup::Group(g) => {
1397                        let Some(next) = steps.next() else {
1398                            group(g, t, repos);
1399                            return;
1400                        };
1401                        if let Some(ArchiveOrGroup::Archive(a)) =
1402                            g.children.iter().find(|a| a.id().is_meta())
1403                        {
1404                            if !repos.iter().any(|r| r.id() == a) {
1405                                let Some(Archive::Local(a)) = t.get(a) else {
1406                                    else_();
1407                                    return;
1408                                };
1409                                repos.push(SandboxedRepository::Copy(a.id().clone()));
1410                                self.copy_archive(a);
1411                            }
1412                        }
1413                        current = next;
1414                        ls = &g.children;
1415                    }
1416                }
1417            }
1418        });
1419    }
1420
1421    #[tracing::instrument(level = "info",
1422        parent = &self.0.span,
1423        target = "sandbox",
1424        name = "require",
1425        skip(self)
1426    )]
1427    pub fn require(&self, id: &ArchiveId) {
1428        // TODO this can be massively optimized
1429        let mut repos = self.0.repos.write();
1430        self.require_meta_infs(
1431            id,
1432            &mut repos,
1433            |a, repos| {
1434                if !repos.iter().any(|r| r.id() == id) {
1435                    repos.push(SandboxedRepository::Copy(id.clone()));
1436                    self.copy_archive(a);
1437                }
1438            },
1439            |g, t, repos| {
1440                for a in unwrap!(g.dfs()) {
1441                    if let ArchiveOrGroup::Archive(id) = a {
1442                        if let Some(Archive::Local(a)) = t.get(id) {
1443                            if !repos.iter().any(|r| r.id() == id) {
1444                                repos.push(SandboxedRepository::Copy(id.clone()));
1445                                self.copy_archive(a);
1446                            }
1447                        }
1448                    }
1449                }
1450            },
1451            || tracing::error!("could not find archive {id}"),
1452        );
1453        drop(repos);
1454        self.0.manager.load(&self.0.path);
1455    }
1456
1457    pub(super) fn copy_archive(&self, a: &LocalArchive) {
1458        let path = a.path();
1459        let target = self.0.path.join(a.id().as_ref());
1460        if target.exists() {
1461            return;
1462        }
1463        tracing::info!("copying archive {} to {}", a.id(), target.display());
1464        if let Err(e) = flams_utils::fs::copy_dir_all(path, &target) {
1465            tracing::error!("could not copy archive {}: {e}", a.id());
1466        }
1467        if !target.exists() || !target.is_dir() {
1468            tracing::error!(
1469                "could not copy archive {}: Target directory does not exist",
1470                a.id()
1471            );
1472        }
1473    }
1474}
1475
1476impl Backend for SandboxedBackend {
1477    type ArchiveIter<'a> =
1478        std::iter::Chain<std::slice::Iter<'a, Archive>, std::slice::Iter<'a, Archive>>;
1479
1480    #[inline]
1481    fn to_any(&self) -> AnyBackend {
1482        AnyBackend::Sandbox(self.clone())
1483    }
1484
1485    fn get_html_fragment(
1486        &self,
1487        d: &DocumentURI,
1488        range: DocumentRange,
1489    ) -> Option<(Vec<CSS>, String)> {
1490        self.with_archive(d.archive_id(), |a| {
1491            a.and_then(|a| {
1492                a.load_html_fragment(d.path(), d.name().first_name(), d.language(), range)
1493            })
1494        })
1495    }
1496    fn get_reference<T: flams_ontology::Resourcable>(&self, rf: &LazyDocRef<T>) -> eyre::Result<T> {
1497        self.with_archive(rf.in_doc.archive_id(), |a| {
1498            let Some(a) = a else {
1499                return Err(eyre::eyre!("Archive {} not found", rf.in_doc.archive_id()));
1500            };
1501            a.load_reference(
1502                rf.in_doc.path(),
1503                rf.in_doc.name().first_name(),
1504                rf.in_doc.language(),
1505                DocumentRange {
1506                    start: rf.start,
1507                    end: rf.end,
1508                },
1509            )
1510        })
1511    }
1512
1513    #[inline]
1514    fn with_archives<R>(&self, f: impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R
1515    where
1516        Self: Sized,
1517    {
1518        self.0.manager.with_tree(|t1| {
1519            GlobalBackend::get()
1520                .with_archive_tree(|t2| f(t1.archives.iter().chain(t2.archives.iter())))
1521        })
1522    }
1523
1524    fn get_html_body(&self, d: &DocumentURI, full: bool) -> Option<(Vec<CSS>, String)> {
1525        self.with_archive(d.archive_id(), |a| {
1526            a.and_then(|a| a.load_html_body(d.path(), d.name().first_name(), d.language(), full))
1527        })
1528    }
1529
1530    #[inline]
1531    fn get_html_full(&self, d: &DocumentURI) -> Option<String> {
1532        self.with_archive(d.archive_id(), |a| {
1533            a.and_then(|a| a.load_html_full(d.path(), d.name().first_name(), d.language()))
1534        })
1535    }
1536
1537    fn submit_triples(
1538        &self,
1539        in_doc: &DocumentURI,
1540        rel_path: &str,
1541        iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
1542    ) {
1543        self.0.manager.with_archive(in_doc.archive_id(), |a| {
1544            if let Some(a) = a {
1545                a.submit_triples(
1546                    in_doc,
1547                    rel_path,
1548                    GlobalBackend::get().triple_store(),
1549                    false,
1550                    iter,
1551                );
1552            }
1553        });
1554    }
1555
1556    fn with_archive<R>(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R {
1557        if let Some(r) = self
1558            .0
1559            .manager
1560            .all_archives()
1561            .iter()
1562            .find(|a| a.uri().archive_id() == id)
1563        {
1564            return f(Some(r));
1565        };
1566        GlobalBackend::get().with_archive(id, f)
1567    }
1568
1569    fn with_archive_or_group<R>(
1570        &self,
1571        id: &ArchiveId,
1572        f: impl FnOnce(Option<&ArchiveOrGroup>) -> R,
1573    ) -> R {
1574        let cell = std::cell::Cell::new(Some(f));
1575        if let Some(r) = self.0.manager.with_tree(|t| {
1576            t.find(id)
1577                .map(|a| (cell.take().unwrap_or_else(|| unreachable!()))(Some(a)))
1578        }) {
1579            return r;
1580        };
1581        let f = cell.take().unwrap_or_else(|| unreachable!());
1582        GlobalBackend::get().with_archive_or_group(id, f)
1583    }
1584
1585    fn get_base_path(&self, id: &ArchiveId) -> Option<PathBuf> {
1586        self.with_local_archive(id, |a| a.map(|a| a.path().to_path_buf()))
1587    }
1588
1589    fn get_document(&self, uri: &DocumentURI) -> Option<Document> {
1590        let id = uri.archive_id();
1591        if self.0.manager.with_archive(id, |a| a.is_none()) {
1592            return GlobalBackend::get().get_document(uri);
1593        }
1594        {
1595            let lock = self.0.cache.read();
1596            if let Some(doc) = lock.has_document(uri) {
1597                return Some(doc.clone());
1598            }
1599        }
1600        let mut cache = self.0.cache.write();
1601        let mut flattener =
1602            SandboxFlattener(&mut cache, &self.0.manager, &GlobalBackend::get().archives);
1603        let r = flattener.load_document(uri.as_path(), uri.language(), uri.name().first_name());
1604        drop(cache);
1605        r
1606    }
1607
1608    #[allow(clippy::significant_drop_tightening)]
1609    fn get_module(&self, uri: &ModuleURI) -> Option<ModuleLike> {
1610        let id = uri.archive_id();
1611        if self.0.manager.with_archive(id, |a| a.is_none()) {
1612            return GlobalBackend::get().get_module(uri);
1613        }
1614        {
1615            let lock = self.0.cache.read();
1616            if uri.name().is_simple() {
1617                if let Some(m) = lock.has_module(uri) {
1618                    return Some(ModuleLike::Module(m.clone()));
1619                }
1620            } else {
1621                let top_uri = !uri.clone();
1622                if let Some(m) = lock.has_module(&top_uri) {
1623                    return ModuleLike::in_module(m, uri.name());
1624                }
1625            }
1626        }
1627        let m = {
1628            let mut cache = self.0.cache.write();
1629            let mut flattener =
1630                SandboxFlattener(&mut cache, &self.0.manager, &GlobalBackend::get().archives);
1631            flattener.load_module(uri.as_path(), uri.name().first_name())?
1632        };
1633        // TODO: this unnecessarily clones
1634        ModuleLike::in_module(&m, uri.name())
1635    }
1636}
1637
1638pub struct AsChecker<'a, B: Backend>(&'a B);
1639
1640impl<B: Backend> LocalBackend for AsChecker<'_, B> {
1641    #[inline]
1642    fn get_document(&mut self, uri: &DocumentURI) -> Option<Document> {
1643        self.0.get_document(uri)
1644    }
1645    #[inline]
1646    fn get_declaration<T: DeclarationTrait>(
1647        &mut self,
1648        uri: &SymbolURI,
1649    ) -> Option<ContentReference<T>> {
1650        self.0.get_declaration(uri)
1651    }
1652    #[inline]
1653    fn get_module(&mut self, uri: &ModuleURI) -> Option<ModuleLike> {
1654        self.0.get_module(uri)
1655    }
1656}
1657
1658impl<B: Backend> DocumentChecker for AsChecker<'_, B> {
1659    #[inline]
1660    fn open(&mut self, _elem: &mut DocumentElement<Unchecked>) {}
1661    #[inline]
1662    fn close(&mut self, _elem: &mut DocumentElement<Checked>) {}
1663}
1664
1665impl<B: Backend> ModuleChecker for AsChecker<'_, B> {
1666    #[inline]
1667    fn open(&mut self, _elem: &mut OpenDeclaration<Unchecked>) {}
1668    #[inline]
1669    fn close(&mut self, _elem: &mut Declaration) {}
1670}
1671
1672struct GlobalFlattener<'a>(&'a mut BackendCache, &'a ArchiveManager);
1673impl GlobalFlattener<'_> {
1674    fn load_document(
1675        &mut self,
1676        path: PathURIRef,
1677        language: Language,
1678        name: &NameStep,
1679    ) -> Option<Document> {
1680        //println!("Document {path}&d={name}&l={language}");
1681        let pre = self.1.load_document(path, language, name)?;
1682        let doc_file = pre.check(self);
1683        let doc = doc_file.clone();
1684        self.0.insert_document(doc_file);
1685        Some(doc)
1686    }
1687    fn load_module(&mut self, path: PathURIRef, name: &NameStep) -> Option<Module> {
1688        //println!("Module {path}&m={name}&l={language}");
1689        let pre = self.1.load_module(path, name)?;
1690        let module = pre.check(self);
1691        self.0.insert_module(module.clone());
1692        Some(module)
1693    }
1694}
1695
1696impl LocalBackend for GlobalFlattener<'_> {
1697    #[allow(clippy::option_if_let_else)]
1698    fn get_document(&mut self, uri: &DocumentURI) -> Option<Document> {
1699        if let Some(doc) = self.0.has_document(uri) {
1700            Some(doc.clone())
1701        } else {
1702            self.load_document(uri.as_path(), uri.language(), uri.name().first_name())
1703        }
1704    }
1705
1706    fn get_module(&mut self, uri: &ModuleURI) -> Option<ModuleLike> {
1707        if uri.name().is_simple() {
1708            if let Some(m) = self.0.has_module(uri) {
1709                return Some(ModuleLike::Module(m.clone()));
1710            }
1711        } else {
1712            let top_uri = !uri.clone();
1713            if let Some(m) = self.0.has_module(&top_uri) {
1714                return ModuleLike::in_module(m, uri.name());
1715            }
1716        }
1717        let m = self.load_module(uri.as_path(), uri.name().first_name())?;
1718        // TODO this unnecessarily clones
1719        ModuleLike::in_module(&m, uri.name())
1720    }
1721
1722    fn get_declaration<T: DeclarationTrait>(
1723        &mut self,
1724        uri: &SymbolURI,
1725    ) -> Option<flams_ontology::content::ContentReference<T>> {
1726        let m = self.get_module(uri.module())?;
1727        // TODO this unnecessarily clones
1728        ContentReference::new(&m, uri.name())
1729    }
1730}
1731
1732impl DocumentChecker for GlobalFlattener<'_> {
1733    #[inline]
1734    fn open(&mut self, _elem: &mut DocumentElement<Unchecked>) {}
1735    #[inline]
1736    fn close(&mut self, _elem: &mut DocumentElement<Checked>) {}
1737}
1738
1739impl ModuleChecker for GlobalFlattener<'_> {
1740    #[inline]
1741    fn open(&mut self, _elem: &mut OpenDeclaration<Unchecked>) {}
1742    #[inline]
1743    fn close(&mut self, _elem: &mut Declaration) {}
1744}
1745
1746struct SandboxFlattener<'a>(&'a mut BackendCache, &'a ArchiveManager, &'a ArchiveManager);
1747impl SandboxFlattener<'_> {
1748    fn load_document(
1749        &mut self,
1750        path: PathURIRef,
1751        language: Language,
1752        name: &NameStep,
1753    ) -> Option<Document> {
1754        let be = if self.1.with_archive(path.archive_id(), |a| a.is_some()) {
1755            self.1
1756        } else {
1757            self.2
1758        };
1759        //println!("Document {path}&d={name}&l={language}");
1760        let pre = be.load_document(path, language, name)?;
1761        let doc_file = pre.check(self);
1762        let doc = doc_file.clone();
1763        self.0.insert_document(doc_file);
1764        Some(doc)
1765    }
1766    fn load_module(&mut self, path: PathURIRef, name: &NameStep) -> Option<Module> {
1767        let be = if self.1.with_archive(path.archive_id(), |a| a.is_some()) {
1768            self.1
1769        } else {
1770            self.2
1771        };
1772        //println!("Module {path}&m={name}&l={language}");
1773        let pre = be.load_module(path, name)?;
1774        let module = pre.check(self);
1775        self.0.insert_module(module.clone());
1776        Some(module)
1777    }
1778}
1779
1780impl LocalBackend for SandboxFlattener<'_> {
1781    #[allow(clippy::option_if_let_else)]
1782    fn get_document(&mut self, uri: &DocumentURI) -> Option<Document> {
1783        if let Some(doc) = self.0.has_document(uri) {
1784            Some(doc.clone())
1785        } else {
1786            self.load_document(uri.as_path(), uri.language(), uri.name().first_name())
1787        }
1788    }
1789
1790    fn get_module(&mut self, uri: &ModuleURI) -> Option<ModuleLike> {
1791        if uri.name().is_simple() {
1792            if let Some(m) = self.0.has_module(uri) {
1793                return Some(ModuleLike::Module(m.clone()));
1794            }
1795        } else {
1796            let top_uri = !uri.clone();
1797            if let Some(m) = self.0.has_module(&top_uri) {
1798                return ModuleLike::in_module(m, uri.name());
1799            }
1800        }
1801        let m = self.load_module(uri.as_path(), uri.name().first_name())?;
1802        // TODO this unnecessarily clones
1803        ModuleLike::in_module(&m, uri.name())
1804    }
1805
1806    fn get_declaration<T: DeclarationTrait>(
1807        &mut self,
1808        uri: &SymbolURI,
1809    ) -> Option<flams_ontology::content::ContentReference<T>> {
1810        let m = self.get_module(uri.module())?;
1811        // TODO this unnecessarily clones
1812        ContentReference::new(&m, uri.name())
1813    }
1814}
1815
1816impl DocumentChecker for SandboxFlattener<'_> {
1817    #[inline]
1818    fn open(&mut self, _elem: &mut DocumentElement<Unchecked>) {}
1819    #[inline]
1820    fn close(&mut self, _elem: &mut DocumentElement<Checked>) {}
1821}
1822
1823impl ModuleChecker for SandboxFlattener<'_> {
1824    #[inline]
1825    fn open(&mut self, _elem: &mut OpenDeclaration<Unchecked>) {}
1826    #[inline]
1827    fn close(&mut self, _elem: &mut Declaration) {}
1828}
1829
1830pub struct TermPresenter<'a, W: std::fmt::Write, B: Backend> {
1831    out: W,
1832    backend: &'a B,
1833    in_text: bool,
1834    cache: VecMap<SymbolURI, Option<Rc<Notation>>>,
1835    op_cache: VecMap<SymbolURI, Option<Rc<Notation>>>,
1836    var_cache: VecMap<DocumentElementURI, Option<Rc<Notation>>>,
1837    var_op_cache: VecMap<DocumentElementURI, Option<Rc<Notation>>>,
1838}
1839impl<'a, W: std::fmt::Write, B: Backend> TermPresenter<'a, W, B> {
1840    #[inline]
1841    pub fn new_with_writer(out: W, backend: &'a B, in_text: bool) -> Self {
1842        Self {
1843            out,
1844            backend,
1845            in_text,
1846            cache: VecMap::default(),
1847            op_cache: VecMap::default(),
1848            var_cache: VecMap::default(),
1849            var_op_cache: VecMap::default(),
1850        }
1851    }
1852    #[inline]
1853    pub fn close(self) -> W {
1854        self.out
1855    }
1856
1857    #[inline]
1858    pub const fn backend(&self) -> &'a B {
1859        self.backend
1860    }
1861
1862    fn load_notation(backend: &B, uri: &SymbolURI, needs_op: bool) -> Option<Notation> {
1863        use flams_ontology::rdf::ontologies::ulo2;
1864        use rdf::sparql::{Select, Var};
1865        let iri = uri.to_iri();
1866        let q = Select {
1867            subject: Var('n'),
1868            pred: ulo2::NOTATION_FOR.into_owned(),
1869            object: iri,
1870        };
1871        let iter = GlobalBackend::get().triple_store().query(q.into()).ok()?;
1872        iter.into_uris().find_map(|uri| {
1873            let elem = backend.get_document_element::<DocumentElement<Checked>>(&uri)?;
1874            let DocumentElement::Notation { notation, .. } = elem.as_ref() else {
1875                return None;
1876            };
1877            //println!("Found notation {notation:?}");
1878            let r = backend.get_reference(notation).ok()?;
1879            if r.is_op() || !needs_op {
1880                Some(r)
1881            } else {
1882                None
1883            }
1884        })
1885    }
1886
1887    fn load_var_notation(
1888        backend: &B,
1889        uri: &DocumentElementURI,
1890        needs_op: bool,
1891    ) -> Option<Notation> {
1892        let parent = uri.parent();
1893        //println!("Looking for {uri} in {parent}");
1894        let parent = backend.get_document_element::<DocumentElement<Checked>>(&parent)?;
1895        let mut ch = parent.as_ref().children().iter();
1896        let mut stack = Vec::new();
1897        loop {
1898            let Some(next) = ch.next() else {
1899                if let Some(n) = stack.pop() {
1900                    ch = n;
1901                    continue;
1902                }
1903                return None;
1904            };
1905            let not = match next {
1906                DocumentElement::Module { children, .. }
1907                | DocumentElement::Section(Section { children, .. })
1908                | DocumentElement::Morphism { children, .. }
1909                | DocumentElement::MathStructure { children, .. }
1910                | DocumentElement::Extension { children, .. }
1911                | DocumentElement::Paragraph(LogicalParagraph { children, .. })
1912                | DocumentElement::Problem(Problem { children, .. }) => {
1913                    let old = std::mem::replace(&mut ch, children.iter());
1914                    stack.push(old);
1915                    continue;
1916                }
1917                DocumentElement::VariableNotation {
1918                    variable, notation, ..
1919                } if variable == uri => notation,
1920                _ => continue,
1921            };
1922            let Some(r) = backend.get_reference(not).ok() else {
1923                continue;
1924            };
1925            if r.is_op() || !needs_op {
1926                return Some(r);
1927            }
1928        }
1929    }
1930}
1931
1932impl<W: std::fmt::Write, B: Backend> std::fmt::Write for TermPresenter<'_, W, B> {
1933    #[inline]
1934    fn write_str(&mut self, s: &str) -> std::fmt::Result {
1935        self.out.write_str(s)
1936    }
1937}
1938impl<W: std::fmt::Write, B: Backend> Presenter for TermPresenter<'_, W, B> {
1939    type N = Rc<Notation>;
1940    #[inline]
1941    fn cont(&mut self, tm: &flams_ontology::content::terms::Term) -> Result<(), PresentationError> {
1942        tm.present(self)
1943    }
1944
1945    #[inline]
1946    fn in_text(&self) -> bool {
1947        self.in_text
1948    }
1949
1950    fn get_notation(&mut self, uri: &SymbolURI) -> Option<Self::N> {
1951        //println!("Getting notation for {uri:?}");
1952        if let Some(n) = self.cache.get(uri) {
1953            //println!("Returning from cache {n:?}");
1954            return n.clone();
1955        };
1956        let r = Self::load_notation(self.backend, uri, false).map(Rc::new);
1957        self.cache.insert(uri.clone(), r.clone());
1958        //println!("Returning {r:?}");
1959        if let Some(r) = &r {
1960            if r.is_op() {
1961                self.op_cache.insert(uri.clone(), Some(r.clone()));
1962            }
1963        }
1964        r
1965    }
1966
1967    fn get_op_notation(&mut self, uri: &SymbolURI) -> Option<Self::N> {
1968        //println!("Getting op notation for {uri:?}");
1969        if let Some(n) = self.op_cache.get(uri) {
1970            //println!("Returning from cache {n:?}");
1971            return n.clone();
1972        };
1973        let r = Self::load_notation(self.backend, uri, true).map(Rc::new);
1974        self.op_cache.insert(uri.clone(), r.clone());
1975        //println!("Returning {r:?}");
1976        if self.cache.get(uri).is_none() {
1977            self.cache.insert(uri.clone(), r.clone());
1978        }
1979        r
1980    }
1981
1982    #[inline]
1983    fn get_variable_notation(&mut self, uri: &DocumentElementURI) -> Option<Self::N> {
1984        if let Some(n) = self.var_cache.get(uri) {
1985            return n.clone();
1986        };
1987        let r = Self::load_var_notation(self.backend, uri, false).map(Rc::new);
1988        self.var_cache.insert(uri.clone(), r.clone());
1989        if let Some(r) = &r {
1990            if r.is_op() {
1991                self.var_op_cache.insert(uri.clone(), Some(r.clone()));
1992            }
1993        }
1994        r
1995    }
1996    #[inline]
1997    fn get_variable_op_notation(&mut self, uri: &DocumentElementURI) -> Option<Self::N> {
1998        if let Some(n) = self.var_op_cache.get(uri) {
1999            return n.clone();
2000        };
2001        let r = Self::load_var_notation(self.backend, uri, true).map(Rc::new);
2002        self.var_op_cache.insert(uri.clone(), r.clone());
2003        if self.var_cache.get(uri).is_none() {
2004            self.var_cache.insert(uri.clone(), r.clone());
2005        }
2006        r
2007    }
2008}
2009
2010pub type StringPresenter<'a, B> = TermPresenter<'a, String, B>;
2011
2012impl<'a, B: Backend> StringPresenter<'a, B> {
2013    #[inline]
2014    pub fn new(backend: &'a B, in_text: bool) -> Self {
2015        Self::new_with_writer(String::new(), backend, in_text)
2016    }
2017    #[inline]
2018    pub fn take(&mut self) -> String {
2019        std::mem::take(&mut self.out)
2020    }
2021
2022    /// #### Errors
2023    pub fn present(&mut self, term: &Term) -> Result<String, PresentationError> {
2024        self.out.clear();
2025        //println!("Presenting: {term}");
2026        let r = term.present(self);
2027        let s = std::mem::take(&mut self.out);
2028        //println!("Returning: {s}");
2029        r.map(|()| s)
2030    }
2031}