lb_rs/service/
documents.rs

1use 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        // todo can we not borrow here?
119        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        // there is a risk that dont_delete is set to true after we check it
144        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    /// This fn is what will fetch the document remotely if it's not present locally
170    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}