lb_rs/service/
documents.rs1use std::collections::HashSet;
2use std::sync::atomic::Ordering;
3
4use crate::model::clock::get_time;
5use crate::model::crypto::DecryptedDocument;
6use crate::model::errors::{LbErrKind, LbResult};
7use crate::model::file_like::FileLike;
8use crate::model::file_metadata::{DocumentHmac, FileType};
9use crate::model::lazy::LazyTree;
10use crate::model::signed_file::SignedFile;
11use crate::model::tree_like::TreeLike;
12use crate::model::validate;
13use crate::Lb;
14use uuid::Uuid;
15
16use super::activity;
17
18impl Lb {
19 #[instrument(level = "debug", skip(self), err(Debug))]
20 pub async fn read_document(
21 &self, id: Uuid, user_activity: bool,
22 ) -> LbResult<DecryptedDocument> {
23 let tx = self.ro_tx().await;
24 let db = tx.db();
25
26 let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
27
28 let doc = self.read_document_helper(id, &mut tree).await?;
29
30 let bg_lb = self.clone();
31 if user_activity {
32 tokio::spawn(async move {
33 bg_lb
34 .add_doc_event(activity::DocEvent::Read(id, get_time().0))
35 .await
36 .unwrap();
37 });
38 }
39
40 Ok(doc)
41 }
42
43 #[instrument(level = "debug", skip(self, content), err(Debug))]
44 pub async fn write_document(&self, id: Uuid, content: &[u8]) -> LbResult<()> {
45 let mut tx = self.begin_tx().await;
46 let db = tx.db();
47
48 let mut tree = (&db.base_metadata)
49 .to_staged(&mut db.local_metadata)
50 .to_lazy();
51
52 let id = match tree.find(&id)?.file_type() {
53 FileType::Document | FileType::Folder => id,
54 FileType::Link { target } => target,
55 };
56 let encrypted_document = tree.update_document(&id, content, &self.keychain)?;
57 let hmac = tree.find(&id)?.document_hmac().copied();
58 self.docs.insert(id, hmac, &encrypted_document).await?;
59 tx.end();
60
61 self.events.doc_written(id);
62 let bg_lb = self.clone();
63 tokio::spawn(async move {
64 bg_lb
65 .add_doc_event(activity::DocEvent::Write(id, get_time().0))
66 .await
67 .unwrap();
68 bg_lb.cleanup().await.unwrap();
69 });
70
71 Ok(())
72 }
73
74 #[instrument(level = "debug", skip(self), err(Debug))]
75 pub async fn read_document_with_hmac(
76 &self, id: Uuid, user_activity: bool,
77 ) -> LbResult<(Option<DocumentHmac>, DecryptedDocument)> {
78 let tx = self.ro_tx().await;
79 let db = tx.db();
80
81 let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
82
83 let doc = self.read_document_helper(id, &mut tree).await?;
84 let hmac = tree.find(&id)?.document_hmac().copied();
85
86 if user_activity {
87 let bg_lb = self.clone();
88 tokio::spawn(async move {
89 bg_lb
90 .add_doc_event(activity::DocEvent::Read(id, get_time().0))
91 .await
92 .unwrap();
93 });
94 }
95
96 Ok((hmac, doc))
97 }
98
99 #[instrument(level = "debug", skip(self, content), err(Debug))]
100 pub async fn safe_write(
101 &self, id: Uuid, old_hmac: Option<DocumentHmac>, content: Vec<u8>,
102 ) -> LbResult<DocumentHmac> {
103 let mut tx = self.begin_tx().await;
104 let db = tx.db();
105
106 let mut tree = (&db.base_metadata)
107 .to_staged(&mut db.local_metadata)
108 .to_lazy();
109
110 let file = tree.find(&id)?;
111 if file.document_hmac() != old_hmac.as_ref() {
112 return Err(LbErrKind::ReReadRequired.into());
113 }
114 let id = match file.file_type() {
115 FileType::Document | FileType::Folder => id,
116 FileType::Link { target } => target,
117 };
118 let encrypted_document = tree.update_document(&id, &content, &self.keychain)?;
120 let hmac = tree.find(&id)?.document_hmac();
121 let hmac = *hmac.ok_or_else(|| {
122 LbErrKind::Unexpected(format!("hmac missing for a document we just wrote {}", id))
123 })?;
124 self.docs
125 .insert(id, Some(hmac), &encrypted_document)
126 .await?;
127 tx.end();
128 self.events.doc_written(id);
129
130 let bg_lb = self.clone();
131 tokio::spawn(async move {
132 bg_lb
133 .add_doc_event(activity::DocEvent::Write(id, get_time().0))
134 .await
135 .unwrap();
136 bg_lb.cleanup().await.unwrap();
137 });
138
139 Ok(hmac)
140 }
141
142 pub(crate) async fn cleanup(&self) -> LbResult<()> {
143 if self.docs.dont_delete.load(Ordering::SeqCst) {
145 debug!("skipping doc cleanup due to active sync");
146 return Ok(());
147 }
148
149 let tx = self.ro_tx().await;
150 let db = tx.db();
151
152 let tree = db.base_metadata.stage(&db.local_metadata);
153
154 let base_files = tree.base.all_files()?.into_iter();
155 let local_files = tree.staged.all_files()?.into_iter();
156
157 let file_hmacs = base_files
158 .chain(local_files)
159 .filter_map(|f| f.document_hmac().map(|hmac| (*f.id(), *hmac)))
160 .collect::<HashSet<_>>();
161
162 drop(tx);
163
164 self.docs.retain(file_hmacs).await?;
165
166 Ok(())
167 }
168
169 pub(crate) async fn read_document_helper<T>(
171 &self, id: Uuid, tree: &mut LazyTree<T>,
172 ) -> LbResult<DecryptedDocument>
173 where
174 T: TreeLike<F = SignedFile>,
175 {
176 let file = tree.find(&id)?;
177 validate::is_document(file)?;
178 let hmac = file.document_hmac().copied();
179
180 if tree.calculate_deleted(&id)? {
181 return Err(LbErrKind::FileNonexistent.into());
182 }
183
184 let doc = match hmac {
185 Some(hmac) => {
186 let doc = self.docs.get(id, Some(hmac)).await?;
187 tree.decrypt_document(&id, &doc, &self.keychain)?
188 }
189 None => vec![],
190 };
191
192 Ok(doc)
193 }
194}