Skip to main content

lb_rs/service/
integrity.rs

1use std::num::NonZeroUsize;
2use std::thread;
3
4use futures::{StreamExt, stream};
5
6use crate::model::file_metadata::Owner;
7use crate::model::filename::DocumentType;
8use crate::model::tree_like::TreeLike;
9
10use crate::Lb;
11use crate::model::errors::{LbErrKind, LbResult, Warning};
12
13impl Lb {
14    #[instrument(level = "debug", skip(self), err(Debug))]
15    pub async fn test_repo_integrity(&self, check_docs: bool) -> LbResult<Vec<Warning>> {
16        let tx = self.ro_tx().await;
17        let db = tx.db();
18
19        let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
20
21        if db.last_synced.get().unwrap_or(&0) != &0 && db.root.get().is_none() {
22            return Err(LbErrKind::RootNonexistent)?;
23        }
24
25        tree.validate(Owner(self.keychain.get_pk()?))?;
26
27        for id in tree.ids() {
28            let name = tree.name(&id, &self.keychain)?;
29            if name.is_empty() {
30                return Err(LbErrKind::FileNameEmpty)?; // todo: context candidate
31            }
32            if name.contains('/') {
33                return Err(LbErrKind::FileNameContainsSlash)?; // todo: context candidate
34            }
35        }
36
37        drop(tx);
38
39        if !check_docs {
40            return Ok(vec![]);
41        }
42
43        let mut warnings = Vec::new();
44        let mut tasks = vec![];
45        for file in self.list_metadatas().await? {
46            if file.is_document() {
47                let is_text =
48                    DocumentType::from_file_name_using_extension(&file.name) == DocumentType::Text;
49
50                if is_text {
51                    tasks.push(async move { (file.id, self.read_document(file.id, false).await) });
52                }
53            }
54        }
55
56        let mut results = stream::iter(tasks).buffer_unordered(
57            thread::available_parallelism()
58                .unwrap_or(NonZeroUsize::new(4).unwrap())
59                .into(),
60        );
61
62        while let Some((id, res)) = results.next().await {
63            let doc = res?;
64            if doc.is_empty() {
65                warnings.push(Warning::EmptyFile(id));
66                continue;
67            }
68
69            if String::from_utf8(doc).is_err() {
70                warnings.push(Warning::InvalidUTF8(id));
71                continue;
72            }
73        }
74
75        Ok(warnings)
76    }
77}