lb_rs/service/
documents.rs

1use 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        // todo can we not borrow here?
107        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        // todo: when workspace isn't the only writer, this arg needs to be exposed
118        // this will happen when lb-fs is integrated into an app and shares an lb-rs with ws
119        // or it will happen when there are multiple co-operative core processes.
120        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        // there is a risk that dont_delete is set to true after we check it
129        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    /// This fn is what will fetch the document remotely if it's not present locally
155    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}