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