lb_rs/service/
documents.rs1use 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 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 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 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}