Skip to main content

lb_rs/service/
documents.rs

1use std::collections::HashSet;
2
3use crate::Lb;
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::tree_like::TreeLike;
10use crate::model::validate;
11use uuid::Uuid;
12
13use super::activity;
14use super::events::Actor;
15
16impl Lb {
17    #[instrument(level = "debug", skip(self), err(Debug))]
18    pub async fn read_document(
19        &self, id: Uuid, user_activity: bool,
20    ) -> LbResult<DecryptedDocument> {
21        let (_, doc) = self.read_document_with_hmac(id, user_activity).await?;
22        Ok(doc)
23    }
24
25    #[instrument(level = "debug", skip(self, content), err(Debug))]
26    pub async fn write_document(&self, id: Uuid, content: &[u8]) -> LbResult<()> {
27        let mut tx = self.begin_tx().await;
28        let db = tx.db();
29
30        let mut tree = (&db.base_metadata)
31            .to_staged(&mut db.local_metadata)
32            .to_lazy();
33
34        let id = match tree.find(&id)?.file_type() {
35            FileType::Document | FileType::Folder => id,
36            FileType::Link { target } => target,
37        };
38        let encrypted_document = tree.update_document(&id, content, &self.keychain)?;
39        let hmac = tree.find(&id)?.document_hmac().copied();
40        self.docs.insert(id, hmac, &encrypted_document).await?;
41        tx.end();
42
43        self.events.doc_written(id, Actor::User);
44        self.add_doc_event(activity::DocEvent::Write(id, get_time().0))
45            .await?;
46
47        Ok(())
48    }
49
50    #[instrument(level = "debug", skip(self), err(Debug))]
51    pub async fn read_document_with_hmac(
52        &self, id: Uuid, user_activity: bool,
53    ) -> LbResult<(Option<DocumentHmac>, DecryptedDocument)> {
54        let tx = self.ro_tx().await;
55        let db = tx.db();
56
57        let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
58
59        let file = tree.find(&id)?;
60        validate::is_document(file)?;
61        let hmac = file.document_hmac().copied();
62
63        if tree.calculate_deleted(&id)? {
64            return Err(LbErrKind::FileNonexistent.into());
65        }
66
67        let doc = match hmac {
68            Some(hmac) => {
69                if self.docs.exists(id, Some(hmac)) {
70                    let doc = self.docs.get(id, Some(hmac)).await?;
71                    let doc = tree.decrypt_document(&id, &doc, &self.keychain)?;
72                    drop(tx);
73                    doc
74                } else {
75                    drop(tx);
76                    // todo: if document not found -- need to trigger a pull
77                    let doc = self.fetch_doc(id, hmac).await?;
78
79                    let tx = self.ro_tx().await;
80                    let db = tx.db();
81                    let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
82                    let doc = tree.decrypt_document(&id, &doc, &self.keychain)?;
83                    drop(tx);
84
85                    doc
86                }
87            }
88            None => {
89                drop(tx);
90                vec![]
91            }
92        };
93
94        if user_activity {
95            self.add_doc_event(activity::DocEvent::Read(id, get_time().0))
96                .await?;
97        }
98
99        Ok((hmac, doc))
100    }
101
102    #[instrument(level = "debug", skip(self, content), err(Debug))]
103    pub async fn safe_write(
104        &self, id: Uuid, old_hmac: Option<DocumentHmac>, content: Vec<u8>,
105    ) -> LbResult<DocumentHmac> {
106        let mut tx = self.begin_tx().await;
107        let db = tx.db();
108
109        let mut tree = (&db.base_metadata)
110            .to_staged(&mut db.local_metadata)
111            .to_lazy();
112
113        let file = tree.find(&id)?;
114        if file.document_hmac() != old_hmac.as_ref() {
115            return Err(LbErrKind::ReReadRequired.into());
116        }
117        let id = match file.file_type() {
118            FileType::Document | FileType::Folder => id,
119            FileType::Link { target } => target,
120        };
121        // todo can we not borrow here?
122        let encrypted_document = tree.update_document(&id, &content, &self.keychain)?;
123        let hmac = tree.find(&id)?.document_hmac();
124        let hmac = *hmac.ok_or_else(|| {
125            LbErrKind::Unexpected(format!("hmac missing for a document we just wrote {id}"))
126        })?;
127        self.docs
128            .insert(id, Some(hmac), &encrypted_document)
129            .await?;
130        tx.end();
131
132        // todo: when workspace isn't the only writer, this arg needs to be exposed
133        // this will happen when lb-fs is integrated into an app and shares an lb-rs with ws
134        // or it will happen when there are multiple co-operative core processes.
135        self.events.doc_written(id, Actor::User);
136        self.add_doc_event(activity::DocEvent::Write(id, get_time().0))
137            .await?;
138
139        Ok(hmac)
140    }
141
142    pub(crate) async fn cleanup(&self) -> LbResult<()> {
143        let tx = self.ro_tx().await;
144        let db = tx.db();
145
146        let tree = db.base_metadata.stage(&db.local_metadata);
147
148        let base_files = tree.base.all_files()?.into_iter();
149        let local_files = tree.staged.all_files()?.into_iter();
150
151        let file_hmacs = base_files
152            .chain(local_files)
153            .filter_map(|f| f.document_hmac().map(|hmac| (*f.id(), *hmac)))
154            .collect::<HashSet<_>>();
155
156        self.docs.retain(file_hmacs).await?;
157
158        drop(tx);
159
160        Ok(())
161    }
162}