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