flams_system/backend/archives/
mod.rs

1mod ignore_regex;
2mod iter;
3pub mod manager;
4pub mod source_files;
5
6use std::path::{Path, PathBuf};
7
8use either::Either;
9use flams_ontology::{
10    archive_json::{ArchiveIndex, Institution},
11    content::modules::OpenModule,
12    file_states::FileStateSummary,
13    languages::Language,
14    narration::documents::UncheckedDocument,
15    uris::{
16        ArchiveId, ArchiveURI, ArchiveURIRef, ArchiveURITrait, DocumentURI, Name, NameStep,
17        PathURITrait, URIOrRefTrait, URIRefTrait,
18    },
19    DocumentRange, Unchecked,
20};
21use flams_utils::{
22    change_listener::ChangeSender,
23    prelude::{TreeChild, TreeLike},
24    vecmap::{VecMap, VecSet},
25    CSS,
26};
27use ignore_regex::IgnoreSource;
28use iter::ArchiveIterator;
29use manager::MaybeQuads;
30use rayon::iter::{IntoParallelIterator, ParallelIterator};
31use source_files::{FileStates, SourceDir};
32use spliter::ParallelSpliterator;
33use tracing::instrument;
34
35use crate::{
36    building::{BuildArtifact, BuildResultArtifact},
37    formats::{BuildTargetId, OMDocResult, SourceFormatId},
38};
39
40use super::{docfile::PreDocFile, rdf::RDFStore, BackendChange};
41
42#[derive(Debug)]
43pub(super) struct RepositoryData {
44    pub(super) uri: ArchiveURI,
45    pub(super) attributes: VecMap<Box<str>, Box<str>>,
46    pub(super) formats: VecSet<SourceFormatId>,
47    pub(super) dependencies: Box<[ArchiveId]>,
48    pub(super) institutions: Box<[Institution]>,
49    pub(super) index: Box<[ArchiveIndex]>,
50}
51
52/*
53#[cfg(feature="zip")]
54#[derive(Debug)]
55pub(super) struct ZipFile {
56    path:Option<std::path::PathBuf>
57}
58
59#[cfg(feature="zip")]
60impl Drop for ZipFile {
61    fn drop(&mut self) {
62        if let Some(p) = self.path.take() {
63            let _ = std::fs::remove_file(p);
64        }
65    }
66}
67*/
68
69#[cfg(feature = "zip")]
70mod zip {
71    use std::path::PathBuf;
72
73    use tokio::io::AsyncWriteExt;
74
75    pub(super) struct ZipStream {
76        handle: tokio::task::JoinHandle<()>,
77        stream: tokio_util::io::ReaderStream<tokio::io::ReadHalf<tokio::io::SimplexStream>>,
78    }
79    impl ZipStream {
80        pub(super) fn new(p: PathBuf) -> Self {
81            let (reader, writer) = tokio::io::simplex(1024);
82            let stream = tokio_util::io::ReaderStream::new(reader);
83            let handle = tokio::task::spawn(Self::zip(p, writer));
84            Self { handle, stream }
85        }
86        async fn zip(p: PathBuf, writer: tokio::io::WriteHalf<tokio::io::SimplexStream>) {
87            let comp = async_compression::tokio::write::GzipEncoder::with_quality(
88                writer,
89                async_compression::Level::Best,
90            );
91            let mut tar = tokio_tar::Builder::new(comp);
92            let _ = tar.append_dir_all(".", &p).await;
93            let mut comp = match tar.into_inner().await {
94                Ok(r) => r,
95                Err(e) => {
96                    tracing::error!("Failed to zip: {e}");
97                    return;
98                }
99            };
100            //let _ = comp.flush().await;
101            let _ = comp.shutdown().await;
102            tracing::info!("Finished zipping {}", p.display());
103        }
104    }
105    impl Drop for ZipStream {
106        fn drop(&mut self) {
107            tracing::info!("Dropping");
108            self.handle.abort();
109        }
110    }
111    impl futures::Stream for ZipStream {
112        type Item = std::io::Result<tokio_util::bytes::Bytes>;
113        #[inline]
114        fn poll_next(
115            self: std::pin::Pin<&mut Self>,
116            cx: &mut std::task::Context<'_>,
117        ) -> std::task::Poll<Option<Self::Item>> {
118            unsafe { self.map_unchecked_mut(|f| &mut f.stream).poll_next(cx) }
119        }
120        #[inline]
121        fn size_hint(&self) -> (usize, Option<usize>) {
122            self.stream.size_hint()
123        }
124    }
125
126    pub(super) trait ZipExt {
127        async fn unpack_with_callback<P: AsRef<std::path::Path>>(
128            &mut self,
129            dst: P,
130            cont: impl FnMut(&std::path::Path),
131        ) -> tokio::io::Result<()>;
132    }
133    impl<R: tokio::io::AsyncRead + Unpin> ZipExt for tokio_tar::Archive<R> {
134        async fn unpack_with_callback<P: AsRef<std::path::Path>>(
135            &mut self,
136            dst: P,
137            mut cont: impl FnMut(&std::path::Path),
138        ) -> tokio::io::Result<()> {
139            use rustc_hash::FxHashSet;
140            use std::pin::Pin;
141            use tokio::fs;
142            use tokio_stream::StreamExt;
143            let mut entries = self.entries()?;
144            let mut pinned = Pin::new(&mut entries);
145            let dst = dst.as_ref();
146
147            if fs::symlink_metadata(dst).await.is_err() {
148                fs::create_dir_all(&dst).await?;
149            }
150
151            let dst = fs::canonicalize(dst).await?;
152
153            let mut targets = FxHashSet::default();
154
155            let mut directories = Vec::new();
156            while let Some(entry) = pinned.next().await {
157                let mut file = entry?;
158                if file.header().entry_type() == tokio_tar::EntryType::Directory {
159                    directories.push(file);
160                } else {
161                    if let Ok(p) = file.path() {
162                        cont(&p)
163                    }
164                    file.unpack_in_raw(&dst, &mut targets).await?;
165                }
166            }
167
168            directories.sort_by(|a, b| b.path_bytes().cmp(&a.path_bytes()));
169            for mut dir in directories {
170                dir.unpack_in_raw(&dst, &mut targets).await?;
171            }
172
173            Ok(())
174        }
175    }
176}
177
178#[derive(Debug)]
179pub struct LocalArchive {
180    pub(super) data: RepositoryData,
181    pub(super) out_path: std::sync::Arc<Path>,
182    pub(super) ignore: IgnoreSource,
183    pub(super) file_state: parking_lot::RwLock<SourceDir>,
184    #[cfg(feature = "gitlab")]
185    pub(super) is_managed: std::sync::OnceLock<Option<git_url_parse::GitUrl>>,
186    //#[cfg(feature="zip")]
187    //pub(super) zip_file: std::sync::Arc<std::sync::OnceLock<Option<ZipFile>>>
188}
189impl LocalArchive {
190    #[inline]
191    #[must_use]
192    pub fn out_dir_of(p: &Path) -> PathBuf {
193        p.join(".flams")
194    }
195
196    #[cfg(feature = "zip")]
197    /// #### Errors
198    pub async fn unzip_from_remote(
199        id: ArchiveId,
200        url: &str,
201        cont: impl FnMut(&Path),
202    ) -> Result<(), ()> {
203        use flams_utils::PathExt;
204        use futures::TryStreamExt;
205        use zip::ZipExt;
206        let resp = match reqwest::get(url).await {
207            Ok(r) => r,
208            Err(e) => {
209                tracing::error!("Error contacting remote: {e}");
210                return Err(());
211            }
212        };
213        let status = resp.status().as_u16();
214        if (400..=599).contains(&status) {
215            let text = resp.text().await;
216            tracing::error!("Error response from remote: {text:?}");
217            return Err(());
218        }
219        let stream = resp.bytes_stream().map_err(std::io::Error::other);
220        let stream = tokio_util::io::StreamReader::new(stream);
221        let decomp = async_compression::tokio::bufread::GzipDecoder::new(stream);
222        let dest = crate::settings::Settings::get()
223            .temp_dir()
224            .join(flams_utils::hashstr("download", &id));
225
226        let mut tar = tokio_tar::Archive::new(decomp);
227        if let Err(e) = tar.unpack_with_callback(&dest, cont).await {
228            tracing::error!("Error unpacking stream: {e}");
229            let _ = tokio::fs::remove_dir_all(dest).await;
230            return Err(());
231        };
232        let mh = flams_utils::unwrap!(crate::settings::Settings::get().mathhubs.first());
233        let mhdest = mh.join(id.as_ref());
234        if let Err(e) = tokio::fs::create_dir_all(&mhdest).await {
235            tracing::error!("Error moving to MathHub: {e}");
236            return Err(());
237        }
238        if mhdest.exists() {
239            let _ = tokio::fs::remove_dir_all(&mhdest).await;
240        }
241        match tokio::task::spawn_blocking(move || dest.rename_safe(&mhdest)).await {
242            Ok(Ok(())) => Ok(()),
243            Err(e) => {
244                tracing::error!("Error moving to MathHub: {e}");
245                Err(())
246            }
247            Ok(Err(e)) => {
248                tracing::error!("Error moving to MathHub: {e:#}");
249                Err(())
250            }
251        }
252    }
253
254    #[cfg(feature = "zip")]
255    pub fn zip(&self) -> impl futures::Stream<Item = std::io::Result<tokio_util::bytes::Bytes>> {
256        let dir_path = flams_utils::unwrap!(self.out_path.parent()).to_path_buf();
257        zip::ZipStream::new(dir_path)
258    }
259
260    #[cfg(not(feature = "gitlab"))]
261    #[inline]
262    pub const fn is_managed(&self) -> Option<&str> {
263        None
264    }
265
266    #[cfg(feature = "gitlab")]
267    pub fn is_managed(&self) -> Option<&git_url_parse::GitUrl> {
268        let gl = crate::settings::Settings::get().gitlab_url.as_ref()?;
269        self.is_managed
270            .get_or_init(|| {
271                let Ok(repo) = flams_git::repos::GitRepo::open(self.path()) else {
272                    return None;
273                };
274                gl.host_str().and_then(|s| repo.is_managed(s))
275            })
276            .as_ref()
277    }
278
279    #[inline]
280    #[must_use]
281    pub fn source_dir_of(p: &Path) -> PathBuf {
282        p.join("source")
283    }
284
285    #[inline]
286    #[must_use]
287    pub fn path(&self) -> &Path {
288        self.out_path.parent().unwrap_or_else(|| unreachable!())
289    }
290
291    #[inline]
292    pub fn file_state(&self) -> FileStates {
293        self.file_state.read().state().clone()
294    }
295
296    #[inline]
297    pub fn state_summary(&self) -> FileStateSummary {
298        self.file_state.read().state().summarize()
299    }
300
301    #[inline]
302    #[must_use]
303    pub fn out_dir(&self) -> &Path {
304        &self.out_path
305    } //self.path().join(".flams") }
306
307    #[inline]
308    #[must_use]
309    pub fn source_dir(&self) -> PathBuf {
310        Self::source_dir_of(self.path())
311    }
312
313    #[inline]
314    #[must_use]
315    pub fn is_meta(&self) -> bool {
316        self.data.uri.archive_id().is_meta()
317    }
318
319    #[inline]
320    #[must_use]
321    pub fn uri(&self) -> ArchiveURIRef {
322        self.data.uri.archive_uri()
323    }
324
325    #[inline]
326    #[must_use]
327    pub fn id(&self) -> &ArchiveId {
328        self.data.uri.archive_id()
329    }
330
331    #[inline]
332    #[must_use]
333    pub fn formats(&self) -> &[SourceFormatId] {
334        self.data.formats.0.as_slice()
335    }
336
337    #[inline]
338    #[must_use]
339    pub const fn attributes(&self) -> &VecMap<Box<str>, Box<str>> {
340        &self.data.attributes
341    }
342
343    #[inline]
344    #[must_use]
345    pub const fn dependencies(&self) -> &[ArchiveId] {
346        &self.data.dependencies
347    }
348
349    #[inline]
350    pub fn with_sources<R>(&self, f: impl FnOnce(&SourceDir) -> R) -> R {
351        f(&self.file_state.read())
352    }
353
354    pub(crate) fn update_sources(&self, sender: &ChangeSender<BackendChange>) {
355        let mut state = self.file_state.write();
356        state.update(
357            self.uri(),
358            self.path(),
359            sender,
360            &self.ignore,
361            self.formats(),
362        );
363    }
364
365    fn load_module(&self, path: Option<&Name>, name: &NameStep) -> Option<OpenModule<Unchecked>> {
366        let out = path.map_or_else(
367            || self.out_dir().join(".modules"),
368            |n| {
369                n.steps()
370                    .iter()
371                    .fold(self.out_dir().to_path_buf(), |p, n| p.join(n.as_ref()))
372                    .join(".modules")
373            },
374        );
375
376        let out = Self::escape_module_name(&out, name);
377        //.join(Into::<&'static str>::into(language));
378        macro_rules! err {
379            ($e:expr) => {
380                match $e {
381                    Ok(e) => e,
382                    Err(e) => {
383                        tracing::error!("Error loading {}: {e}", out.display());
384                        return None;
385                    }
386                }
387            };
388        }
389        if out.exists() {
390            let file = err!(std::fs::File::open(&out));
391            let file = std::io::BufReader::new(file);
392            Some(err!(bincode::serde::decode_from_reader(
393                file,
394                bincode::config::standard()
395            )))
396            //OpenModule::from_byte_stream(&mut file).ok()
397        } else {
398            None
399        }
400    }
401
402    fn submit_triples(
403        &self,
404        in_doc: &DocumentURI,
405        rel_path: &str,
406        relational: &RDFStore,
407        load: bool,
408        iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
409    ) {
410        let out = rel_path
411            .split('/')
412            .fold(self.out_dir().to_path_buf(), |p, s| p.join(s));
413        let _ = std::fs::create_dir_all(&out);
414        let out = out.join("index.ttl");
415        relational.export(iter, &out, in_doc);
416        if load {
417            relational.load(&out, in_doc.to_iri());
418        }
419    }
420
421    pub(super) fn get_filepath(
422        &self,
423        path: Option<&Name>,
424        name: &NameStep,
425        language: Language,
426        filename: &str,
427    ) -> Option<PathBuf> {
428        let out = path.map_or_else(
429            || self.out_dir().to_path_buf(),
430            |n| {
431                n.steps()
432                    .iter()
433                    .fold(self.out_dir().to_path_buf(), |p, n| p.join(n.as_ref()))
434            },
435        );
436        let name = name.as_ref();
437
438        for d in std::fs::read_dir(&out).ok()? {
439            let Ok(dir) = d else { continue };
440            let Ok(m) = dir.metadata() else { continue };
441            if !m.is_dir() {
442                continue;
443            }
444            let dname = dir.file_name();
445            let Some(d) = dname.to_str() else { continue };
446            if !d.starts_with(name) {
447                continue;
448            }
449            let rest = &d[name.len()..];
450            if !rest.is_empty() && !rest.starts_with('.') {
451                continue;
452            }
453            let rest = rest.strip_prefix('.').unwrap_or(rest);
454            if rest.contains('.') {
455                let lang: &'static str = language.into();
456                if !rest.starts_with(lang) {
457                    continue;
458                }
459            }
460            let p = dir.path().join(filename);
461            if p.exists() {
462                return Some(p);
463            }
464        }
465        None
466    }
467
468    fn load_document(
469        &self,
470        path: Option<&Name>,
471        name: &NameStep,
472        language: Language,
473    ) -> Option<UncheckedDocument> {
474        self.get_filepath(path, name, language, "doc")
475            .and_then(|p| PreDocFile::read_from_file(&p))
476    }
477
478    pub fn load_html_body(
479        &self,
480        path: Option<&Name>,
481        name: &NameStep,
482        language: Language,
483        full: bool,
484    ) -> Option<(Vec<CSS>, String)> {
485        self.get_filepath(path, name, language, "ftml")
486            .and_then(|p| OMDocResult::load_html_body(&p, full))
487    }
488
489    #[cfg(feature = "tokio")]
490    pub fn load_html_body_async<'a>(
491        &self,
492        path: Option<&'a Name>,
493        name: &'a NameStep,
494        language: Language,
495        full: bool,
496    ) -> Option<impl std::future::Future<Output = Option<(Vec<CSS>, String)>> + 'a> {
497        let p = self.get_filepath(path, name, language, "ftml")?;
498        Some(OMDocResult::load_html_body_async(p, full))
499    }
500
501    #[cfg(feature = "tokio")]
502    pub fn load_html_full_async<'a>(
503        &self,
504        path: Option<&'a Name>,
505        name: &'a NameStep,
506        language: Language,
507    ) -> Option<impl std::future::Future<Output = Option<String>> + 'a> {
508        let p = self.get_filepath(path, name, language, "ftml")?;
509        Some(OMDocResult::load_html_full_async(p))
510    }
511
512    pub fn load_html_full(
513        &self,
514        path: Option<&Name>,
515        name: &NameStep,
516        language: Language,
517    ) -> Option<String> {
518        let p = self.get_filepath(path, name, language, "ftml")?;
519        OMDocResult::load_html_full(p)
520    }
521
522    pub fn load_html_fragment(
523        &self,
524        path: Option<&Name>,
525        name: &NameStep,
526        language: Language,
527        range: DocumentRange,
528    ) -> Option<(Vec<CSS>, String)> {
529        self.get_filepath(path, name, language, "ftml")
530            .and_then(|p| OMDocResult::load_html_fragment(&p, range))
531    }
532    pub fn load_reference<T: flams_ontology::Resourcable>(
533        &self,
534        path: Option<&Name>,
535        name: &NameStep,
536        language: Language,
537        range: DocumentRange,
538    ) -> eyre::Result<T> {
539        let Some(p) = self.get_filepath(path, name, language, "ftml") else {
540            return Err(eyre::eyre!("File not found"));
541        };
542        OMDocResult::load_reference(&p, range)
543    }
544
545    #[cfg(feature = "tokio")]
546    pub fn load_html_fragment_async<'a>(
547        &self,
548        path: Option<&'a Name>,
549        name: &'a NameStep,
550        language: Language,
551        range: DocumentRange,
552    ) -> Option<impl std::future::Future<Output = Option<(Vec<CSS>, String)>> + 'a> {
553        let p = self.get_filepath(path, name, language, "ftml")?;
554        Some(OMDocResult::load_html_fragment_async(p, range))
555    }
556
557    /// ### Errors
558    pub fn load<D: BuildArtifact>(&self, relative_path: &str) -> Result<D, std::io::Error> {
559        let p = self
560            .out_dir()
561            .join(relative_path)
562            .join(D::get_type_id().name());
563        if p.exists() {
564            D::load(&p)
565        } else {
566            Err(std::io::ErrorKind::NotFound.into())
567        }
568    }
569
570    fn escape_module_name(in_path: &Path, name: &NameStep) -> PathBuf {
571        static REPLACER: flams_utils::escaping::Escaper<u8, 1> =
572            flams_utils::escaping::Escaper([(b'*', "__AST__")]);
573        in_path.join(REPLACER.escape(name).to_string())
574    }
575
576    #[allow(clippy::cast_possible_truncation)]
577    #[allow(clippy::cognitive_complexity)]
578    fn save_omdoc_result(&self, top: &Path, result: &OMDocResult) {
579        macro_rules! err {
580            ($e:expr) => {
581                match $e {
582                    Ok(r) => r,
583                    Err(e) => {
584                        tracing::error!("Failed to save {}: {}", top.display(), e);
585                        return;
586                    }
587                }
588            };
589        }
590        macro_rules! er {
591            ($e:expr) => {
592                if let Err(e) = $e {
593                    tracing::error!("Failed to save {}: {}", top.display(), e);
594                    return;
595                }
596            };
597        }
598        let p = top.join("ftml");
599        result.write(&p);
600        let OMDocResult {
601            document,
602            modules,
603            html,
604        } = result;
605        let p = top.join("doc");
606        let file = err!(std::fs::File::create(&p));
607        let mut buf = std::io::BufWriter::new(file);
608
609        er!(bincode::serde::encode_into_std_write(
610            document,
611            &mut buf,
612            bincode::config::standard()
613        ));
614        //er!(document.into_byte_stream(&mut buf));
615
616        #[cfg(feature = "tantivy")]
617        {
618            let p = top.join("tantivy");
619            let file = err!(std::fs::File::create(&p));
620            let mut buf = std::io::BufWriter::new(file);
621            let ret = document.all_searches(&html.html);
622            er!(bincode::serde::encode_into_std_write(
623                ret,
624                &mut buf,
625                bincode::config::standard()
626            ));
627        }
628
629        for m in modules {
630            let path = m.uri.path();
631            let name = m.uri.name();
632            //let language = m.uri.language();
633            let out = path.map_or_else(
634                || self.out_dir().join(".modules"),
635                |n| {
636                    n.steps()
637                        .iter()
638                        .fold(self.out_dir().to_path_buf(), |p, n| p.join(n.as_ref()))
639                        .join(".modules")
640                },
641            );
642            //.join(name.to_string());
643            err!(std::fs::create_dir_all(&out));
644            let out = Self::escape_module_name(&out, name.first_name());
645            let file = err!(std::fs::File::create(&out));
646            let mut buf = std::io::BufWriter::new(file);
647            //er!(m.into_byte_stream(&mut buf));
648            er!(bincode::serde::encode_into_std_write(
649                m,
650                &mut buf,
651                bincode::config::standard()
652            ));
653        }
654    }
655
656    pub fn get_log(&self, relative_path: &str, target: BuildTargetId) -> PathBuf {
657        self.out_dir()
658            .join(relative_path)
659            .join(target.name())
660            .with_extension("log")
661    }
662
663    #[allow(clippy::cognitive_complexity)]
664    pub fn save(
665        &self,
666        relative_path: &str,
667        log: Either<String, PathBuf>,
668        from: BuildTargetId,
669        result: Option<BuildResultArtifact>,
670    ) {
671        macro_rules! err {
672            ($e:expr) => {
673                if let Err(e) = $e {
674                    tracing::error!("Failed to save [{}]{}: {}", self.id(), relative_path, e);
675                    return;
676                }
677            };
678        }
679        let top = self.out_dir().join(relative_path);
680        err!(std::fs::create_dir_all(&top));
681        let logfile = top.join(from.name()).with_extension("log");
682        match log {
683            Either::Left(s) => {
684                err!(std::fs::write(&logfile, s));
685            }
686            Either::Right(f) => {
687                err!(std::fs::rename(&f, &logfile));
688            }
689        }
690        match result {
691            Some(BuildResultArtifact::File(t, f)) => {
692                let p = top.join(t.name());
693                err!(std::fs::rename(&f, &p));
694            }
695            Some(BuildResultArtifact::Data(d)) => {
696                if let Some(e) = d.as_any().downcast_ref::<OMDocResult>() {
697                    self.save_omdoc_result(&top, e);
698                    return;
699                }
700                let p = top.join(d.get_type().name());
701                err!(d.write(&p));
702            }
703            None | Some(BuildResultArtifact::None) => (),
704        }
705    }
706}
707
708#[non_exhaustive]
709pub enum Archive {
710    Local(LocalArchive),
711}
712impl std::fmt::Debug for Archive {
713    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
714        match self {
715            Self::Local(a) => a.id().fmt(f),
716        }
717    }
718}
719impl Archive {
720    #[inline]
721    pub fn get_log(&self, relative_path: &str, target: BuildTargetId) -> PathBuf {
722        match self {
723            Self::Local(a) => a.get_log(relative_path, target),
724        }
725    }
726
727    #[inline]
728    pub fn with_sources<R>(&self, f: impl FnOnce(&SourceDir) -> R) -> R {
729        match self {
730            Self::Local(a) => a.with_sources(f),
731        }
732    }
733
734    pub fn submit_triples(
735        &self,
736        in_doc: &DocumentURI,
737        rel_path: &str,
738        relational: &RDFStore,
739        load: bool,
740        iter: impl Iterator<Item = flams_ontology::rdf::Triple>,
741    ) {
742        match self {
743            Self::Local(a) => a.submit_triples(in_doc, rel_path, relational, load, iter),
744        }
745    }
746
747    #[inline]
748    #[must_use]
749    const fn data(&self) -> &RepositoryData {
750        match self {
751            Self::Local(a) => &a.data,
752        }
753    }
754
755    #[inline]
756    #[must_use]
757    pub fn uri(&self) -> ArchiveURIRef {
758        self.data().uri.archive_uri()
759    }
760    #[inline]
761    #[must_use]
762    pub fn id(&self) -> &ArchiveId {
763        self.data().uri.archive_id()
764    }
765
766    #[inline]
767    #[must_use]
768    pub fn formats(&self) -> &[SourceFormatId] {
769        self.data().formats.0.as_slice()
770    }
771
772    #[inline]
773    #[must_use]
774    pub const fn attributes(&self) -> &VecMap<Box<str>, Box<str>> {
775        &self.data().attributes
776    }
777
778    #[inline]
779    #[must_use]
780    pub const fn dependencies(&self) -> &[ArchiveId] {
781        &self.data().dependencies
782    }
783
784    pub fn load_html_body(
785        &self,
786        path: Option<&Name>,
787        name: &NameStep,
788        language: Language,
789        full: bool,
790    ) -> Option<(Vec<CSS>, String)> {
791        match self {
792            Self::Local(a) => a.load_html_body(path, name, language, full),
793        }
794    }
795
796    #[cfg(feature = "tokio")]
797    pub fn load_html_body_async<'a>(
798        &self,
799        path: Option<&'a Name>,
800        name: &'a NameStep,
801        language: Language,
802        full: bool,
803    ) -> Option<impl std::future::Future<Output = Option<(Vec<CSS>, String)>> + 'a> {
804        match self {
805            Self::Local(a) => a.load_html_body_async(path, name, language, full),
806        }
807    }
808    #[cfg(feature = "tokio")]
809    pub fn load_html_full_async<'a>(
810        &self,
811        path: Option<&'a Name>,
812        name: &'a NameStep,
813        language: Language,
814    ) -> Option<impl std::future::Future<Output = Option<String>> + 'a> {
815        match self {
816            Self::Local(a) => a.load_html_full_async(path, name, language),
817        }
818    }
819    pub fn load_html_full(
820        &self,
821        path: Option<&Name>,
822        name: &NameStep,
823        language: Language,
824    ) -> Option<String> {
825        match self {
826            Self::Local(a) => a.load_html_full(path, name, language),
827        }
828    }
829
830    pub fn load_html_fragment(
831        &self,
832        path: Option<&Name>,
833        name: &NameStep,
834        language: Language,
835        range: DocumentRange,
836    ) -> Option<(Vec<CSS>, String)> {
837        match self {
838            Self::Local(a) => a.load_html_fragment(path, name, language, range),
839        }
840    }
841
842    pub fn load_reference<T: flams_ontology::Resourcable>(
843        &self,
844        path: Option<&Name>,
845        name: &NameStep,
846        language: Language,
847        range: DocumentRange,
848    ) -> eyre::Result<T> {
849        match self {
850            Self::Local(a) => a.load_reference(path, name, language, range),
851        }
852    }
853
854    #[cfg(feature = "tokio")]
855    pub fn load_html_fragment_async<'a>(
856        &self,
857        path: Option<&'a Name>,
858        name: &'a NameStep,
859        language: Language,
860        range: DocumentRange,
861    ) -> Option<impl std::future::Future<Output = Option<(Vec<CSS>, String)>> + 'a> {
862        match self {
863            Self::Local(a) => a.load_html_fragment_async(path, name, language, range),
864        }
865    }
866
867    fn load_document(
868        &self,
869        path: Option<&Name>,
870        name: &NameStep,
871        language: Language,
872    ) -> Option<UncheckedDocument> {
873        match self {
874            Self::Local(a) => a.load_document(path, name, language),
875        }
876    }
877    fn load_module(&self, path: Option<&Name>, name: &NameStep) -> Option<OpenModule<Unchecked>> {
878        match self {
879            Self::Local(a) => a.load_module(path, name),
880        }
881    }
882
883    /// ### Errors
884    #[inline]
885    pub fn load<D: BuildArtifact>(&self, relative_path: &str) -> Result<D, std::io::Error> {
886        match self {
887            Self::Local(a) => a.load(relative_path),
888        }
889    }
890
891    pub fn save(
892        &self,
893        relative_path: &str,
894        log: Either<String, PathBuf>,
895        from: BuildTargetId,
896        result: Option<BuildResultArtifact>,
897    ) {
898        match self {
899            Self::Local(a) => a.save(relative_path, log, from, result),
900        }
901    }
902}
903
904#[derive(Debug, Default)]
905pub struct ArchiveTree {
906    pub archives: Vec<Archive>,
907    pub groups: Vec<ArchiveOrGroup>,
908    pub index: (VecSet<Institution>, VecSet<ArchiveIndex>),
909}
910
911#[derive(Debug)]
912pub enum ArchiveOrGroup {
913    Archive(ArchiveId),
914    Group(ArchiveGroup),
915}
916
917impl ArchiveOrGroup {
918    #[inline]
919    #[must_use]
920    pub const fn id(&self) -> &ArchiveId {
921        match self {
922            Self::Archive(id) => id,
923            Self::Group(g) => &g.id,
924        }
925    }
926}
927
928#[derive(Debug)]
929pub struct ArchiveGroup {
930    pub id: ArchiveId,
931    pub children: Vec<ArchiveOrGroup>,
932    pub state: FileStates,
933}
934
935impl TreeLike for ArchiveTree {
936    type RefIter<'a> = std::slice::Iter<'a, ArchiveOrGroup>;
937    type Child<'a> = &'a ArchiveOrGroup;
938    fn children(&self) -> Option<Self::RefIter<'_>> {
939        Some(self.groups.iter())
940    }
941}
942
943impl TreeLike for &ArchiveGroup {
944    type RefIter<'a>
945        = std::slice::Iter<'a, ArchiveOrGroup>
946    where
947        Self: 'a;
948    type Child<'a>
949        = &'a ArchiveOrGroup
950    where
951        Self: 'a;
952    fn children(&self) -> Option<Self::RefIter<'_>> {
953        Some(self.children.iter())
954    }
955}
956
957impl TreeChild<ArchiveTree> for &ArchiveOrGroup {
958    fn children<'a>(&self) -> Option<<ArchiveTree as TreeLike>::RefIter<'a>>
959    where
960        Self: 'a,
961    {
962        if let ArchiveOrGroup::Group(a) = self {
963            Some(a.children.iter())
964        } else {
965            None
966        }
967    }
968}
969
970impl TreeChild<&ArchiveGroup> for &ArchiveOrGroup {
971    fn children<'a>(&self) -> Option<std::slice::Iter<'a, ArchiveOrGroup>>
972    where
973        Self: 'a,
974    {
975        if let ArchiveOrGroup::Group(a) = self {
976            Some(a.children.iter())
977        } else {
978            None
979        }
980    }
981}
982
983impl ArchiveTree {
984    #[must_use]
985    pub fn find(&self, id: &ArchiveId) -> Option<&ArchiveOrGroup> {
986        let mut steps = id.steps().peekable();
987        let mut curr = &self.groups;
988        while let Some(step) = steps.next() {
989            let e = curr.iter().find(|e| e.id().last_name() == step)?;
990            /*let Ok(i) = curr.binary_search_by_key(&step, |v| v.id().last_name()) else {
991                return None;
992            };*/
993            if steps.peek().is_none() {
994                return Some(e);
995            } //{ return Some(&curr[i]); }
996            if let ArchiveOrGroup::Group(g) = e {
997                //&curr[i] {
998                curr = &g.children;
999            } else {
1000                return None;
1001            }
1002        }
1003        None
1004    }
1005
1006    #[must_use]
1007    pub fn get(&self, id: &ArchiveId) -> Option<&Archive> {
1008        self.archives.iter().find(|a| a.uri().archive_id() == id)
1009        //self.archives.binary_search_by_key(&id, Archive::id).ok()
1010        //    .map(|i| &self.archives[i])
1011    }
1012
1013    pub fn state(&self) -> FileStates {
1014        let mut r = FileStates::default();
1015        for aog in &self.groups {
1016            match aog {
1017                ArchiveOrGroup::Archive(a) => {
1018                    if let Some(Archive::Local(a)) = self.get(a) {
1019                        r.merge_all(&a.file_state.read().state);
1020                    }
1021                }
1022                ArchiveOrGroup::Group(g) => r.merge_all(&g.state),
1023            }
1024        }
1025        r
1026    }
1027
1028    #[instrument(level = "info",
1029    target = "archives",
1030    name = "Loading archives",
1031    fields(path = %path.display()),
1032    skip_all
1033    )]
1034    pub(crate) fn load(
1035        &mut self,
1036        path: &Path,
1037        sender: &ChangeSender<BackendChange>,
1038        f: impl MaybeQuads,
1039    ) {
1040        tracing::info!(target:"archives","Searching for archives");
1041        let old = std::mem::take(self);
1042        let old_new_f = parking_lot::Mutex::new((old, Self::default(), f));
1043
1044        ArchiveIterator::new(path)
1045            .par_split()
1046            .into_par_iter()
1047            .for_each(|a| {
1048                a.update_sources(sender);
1049                let mut lock = old_new_f.lock();
1050                let (old, new, f) = &mut *lock;
1051                if old.remove_from_list(a.id()).is_none() {
1052                    sender.lazy_send(|| BackendChange::NewArchive(URIRefTrait::owned(a.uri())));
1053                }
1054                new.insert(Archive::Local(a), f);
1055                drop(lock);
1056                // todo
1057            });
1058        let (_old, new, _) = old_new_f.into_inner();
1059        //news.sort_by_key(|a| a.id()); <- alternative
1060        *self = new;
1061        for a in &self.archives {
1062            for i in &a.data().institutions {
1063                self.index.0.insert_clone(i);
1064            }
1065            for doc in &a.data().index {
1066                self.index.1.insert_clone(doc);
1067            }
1068        }
1069        // TODO olds
1070    }
1071
1072    #[inline]
1073    fn remove_from_list(&mut self, id: &ArchiveId) -> Option<Archive> {
1074        if let Ok(i) = self
1075            .archives
1076            .binary_search_by_key(&id, |a: &Archive| a.id())
1077        {
1078            Some(self.archives.remove(i))
1079        } else {
1080            None
1081        }
1082    }
1083
1084    fn _remove(&mut self, id: &ArchiveId) -> Option<Archive> {
1085        let mut curr = &mut self.groups;
1086        let mut steps = id.steps();
1087        while let Some(step) = steps.next() {
1088            let Ok(i) = curr.binary_search_by_key(&step, |v| v.id().last_name()) else {
1089                return None;
1090            };
1091            if matches!(curr[i], ArchiveOrGroup::Group(_)) {
1092                let ArchiveOrGroup::Group(g) = &mut curr[i] else {
1093                    unreachable!()
1094                };
1095                curr = &mut g.children;
1096                continue;
1097            }
1098            if steps.next().is_some() {
1099                return None;
1100            }
1101            let ArchiveOrGroup::Archive(a) = curr.remove(i) else {
1102                unreachable!()
1103            };
1104            let Ok(i) = self
1105                .archives
1106                .binary_search_by_key(&&a, |a: &Archive| a.id())
1107            else {
1108                unreachable!()
1109            };
1110            return Some(self.archives.remove(i));
1111        }
1112        None
1113    }
1114
1115    #[allow(clippy::needless_pass_by_ref_mut)]
1116    #[allow(irrefutable_let_patterns)]
1117    fn insert(&mut self, archive: Archive, _f: &mut impl MaybeQuads) {
1118        let id = archive.id().clone();
1119        let steps = if let Some((group, _)) = id.as_ref().rsplit_once('/') {
1120            group.split('/')
1121        } else {
1122            match self
1123                .archives
1124                .binary_search_by_key(&&id, |a: &Archive| a.id())
1125            {
1126                Ok(i) => self.archives[i] = archive,
1127                Err(i) => self.archives.insert(i, archive),
1128            };
1129            match self
1130                .groups
1131                .binary_search_by_key(&id.as_ref(), |v| v.id().last_name())
1132            {
1133                Ok(i) => self.groups[i] = ArchiveOrGroup::Archive(id),
1134                Err(i) => self.groups.insert(i, ArchiveOrGroup::Archive(id)),
1135            }
1136            return;
1137        };
1138        let mut curr = &mut self.groups;
1139        let mut curr_name = String::new();
1140        for step in steps {
1141            if curr_name.is_empty() {
1142                curr_name = step.to_string();
1143            } else {
1144                curr_name = format!("{curr_name}/{step}");
1145            }
1146            match curr.binary_search_by_key(&step, |v| v.id().last_name()) {
1147                Ok(i) => {
1148                    let ArchiveOrGroup::Group(g) = &mut curr[i]
1149                    // TODO maybe reachable?
1150                    else {
1151                        unreachable!()
1152                    };
1153                    if let Archive::Local(a) = &archive {
1154                        g.state.merge_all(a.file_state.read().state());
1155                    }
1156                    curr = &mut g.children;
1157                }
1158                Err(i) => {
1159                    let mut state = FileStates::default();
1160                    if let Archive::Local(a) = &archive {
1161                        state.merge_all(a.file_state.read().state());
1162                    }
1163                    let g = ArchiveGroup {
1164                        id: ArchiveId::new(&curr_name),
1165                        children: Vec::new(),
1166                        state,
1167                    };
1168                    curr.insert(i, ArchiveOrGroup::Group(g));
1169                    let ArchiveOrGroup::Group(g) = &mut curr[i] else {
1170                        unreachable!()
1171                    };
1172                    curr = &mut g.children;
1173                }
1174            }
1175        }
1176
1177        match self
1178            .archives
1179            .binary_search_by_key(&&id, |a: &Archive| a.id())
1180        {
1181            Ok(i) => self.archives[i] = archive,
1182            Err(i) => self.archives.insert(i, archive),
1183        };
1184        match curr.binary_search_by_key(&id.last_name(), |v| v.id().last_name()) {
1185            Ok(i) => curr[i] = ArchiveOrGroup::Archive(id),
1186            Err(i) => curr.insert(i, ArchiveOrGroup::Archive(id)),
1187        }
1188    }
1189}