lb_rs/service/
integrity.rs1use 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)?; }
32 if name.contains('/') {
33 return Err(LbErrKind::FileNameContainsSlash)?; }
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}