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