Skip to main content

lb_rs/model/
lazy.rs

1use crate::model::access_info::UserAccessMode;
2use crate::model::api::METADATA_FEE;
3use crate::model::crypto::AESKey;
4use crate::model::errors::{LbErrKind, LbResult};
5use crate::model::file_like::FileLike;
6use crate::model::file_metadata::{FileType, Owner};
7use crate::model::staged::StagedTree;
8use crate::model::symkey;
9use crate::model::tree_like::{TreeLike, TreeLikeMut};
10use crate::service::keychain::Keychain;
11use db_rs::hasher::UuidIdentityHasherBuilder;
12use serde::{Deserialize, Serialize};
13use std::collections::{HashMap, HashSet};
14use uuid::Uuid;
15
16#[derive(Clone, Debug)]
17pub struct LazyTree<T: TreeLike> {
18    pub tree: T,
19    pub name: HashMap<Uuid, String, UuidIdentityHasherBuilder>,
20    pub implicit_deleted: HashMap<Uuid, bool, UuidIdentityHasherBuilder>,
21    pub linked_by: HashMap<Uuid, Uuid, UuidIdentityHasherBuilder>,
22    pub children: HashMap<Uuid, Vec<Uuid>, UuidIdentityHasherBuilder>,
23}
24
25impl<T: TreeLike> LazyTree<T> {
26    pub fn new(tree: T) -> Self {
27        Self {
28            name: uuid_map(),
29            implicit_deleted: uuid_map(),
30            children: uuid_map(),
31            linked_by: uuid_map(),
32            tree,
33        }
34    }
35}
36
37impl<T: TreeLike> LazyTree<T> {
38    pub fn access_mode(&self, owner: Owner, id: &Uuid) -> LbResult<Option<UserAccessMode>> {
39        let mut file = self.find(id)?;
40        let mut max_access_mode = None;
41        let mut visited_ids = vec![];
42        while !visited_ids.contains(file.id()) {
43            visited_ids.push(*file.id());
44            let access_mode = file.access_mode(&owner);
45            if access_mode > max_access_mode {
46                max_access_mode = access_mode;
47            }
48            if file.parent() == file.id() {
49                break; // root
50            } else if let Some(parent) = self.maybe_find(file.parent()) {
51                file = parent
52            } else {
53                break; // share root
54            }
55        }
56        Ok(max_access_mode)
57    }
58
59    pub fn in_pending_share(&mut self, id: &Uuid) -> LbResult<bool> {
60        let mut id = *id;
61        loop {
62            if self.find(&id)?.parent() == self.find(&id)?.id() {
63                return Ok(false); // root
64            } else if let Some(link) = self.linked_by(&id)? {
65                id = link;
66            } else if self.maybe_find(self.find(&id)?.parent()).is_some() {
67                id = *self.find(&id)?.parent();
68            } else {
69                return Ok(true); // share root
70            }
71        }
72    }
73
74    pub fn all_children(
75        &mut self,
76    ) -> LbResult<&HashMap<Uuid, Vec<Uuid>, UuidIdentityHasherBuilder>> {
77        if self.children.is_empty() {
78            let mut all_children = uuid_map();
79            for file in self.all_files()? {
80                if !file.is_root() {
81                    let mut children: Vec<Uuid> =
82                        all_children.remove(file.parent()).unwrap_or_default();
83                    children.push(*file.id());
84                    all_children.insert(*file.parent(), children);
85                }
86            }
87            self.children = all_children;
88        }
89
90        Ok(&self.children)
91    }
92
93    pub fn calculate_deleted(&mut self, id: &Uuid) -> LbResult<bool> {
94        let (visited_ids, deleted) = {
95            let mut file = self.find(id)?;
96            let mut visited_ids = vec![];
97            let mut deleted = false;
98
99            while !file.is_root()
100                && self.maybe_find(file.parent()).is_some()
101                && !visited_ids.contains(file.id())
102            {
103                visited_ids.push(*file.id());
104                if let Some(&implicit) = self.implicit_deleted.get(file.id()) {
105                    deleted = implicit;
106                    break;
107                }
108
109                if file.explicitly_deleted() {
110                    deleted = true;
111                    break;
112                }
113
114                file = match self.maybe_find_parent(file) {
115                    Some(file) => file,
116                    None => {
117                        if !file.user_access_keys().is_empty() {
118                            break;
119                        } else {
120                            return Err(LbErrKind::FileParentNonexistent)?;
121                        }
122                    }
123                }
124            }
125
126            (visited_ids, deleted)
127        };
128
129        for id in visited_ids {
130            self.implicit_deleted.insert(id, deleted);
131        }
132
133        Ok(deleted)
134    }
135
136    /// Calculates the total usage in bytes for all non-deleted files owned by the owner.
137    /// This includes the document size plus METADATA_FEE for each file.
138    /// Files shared with this owner (but owned by others) are excluded.
139    pub fn calculate_usage(&mut self, owner: Owner) -> LbResult<u64> {
140        let mut total: u64 = 0;
141        for id in self.ids() {
142            // Check owner first
143            let file_owner = self.find(&id)?.owner();
144            if file_owner != owner {
145                continue;
146            }
147            // Skip deleted files
148            if self.calculate_deleted(&id)? {
149                continue;
150            }
151            let file_size = self.find(&id)?.doc_size().unwrap_or(0) as u64;
152            total += file_size + METADATA_FEE;
153        }
154        Ok(total)
155    }
156
157    pub fn decrypt_key(&mut self, id: &Uuid, keychain: &Keychain) -> LbResult<AESKey> {
158        let mut file_id = *self.find(id)?.id();
159        let mut visited_ids = vec![];
160
161        loop {
162            if keychain.contains_aes_key(&file_id)? {
163                break;
164            }
165
166            let my_pk = keychain.get_pk()?;
167
168            let maybe_file_key = if let Some(user_access) = self
169                .find(&file_id)?
170                .user_access_keys()
171                .iter()
172                .find(|access| access.encrypted_for == my_pk)
173            {
174                Some(user_access.decrypt(keychain.get_account()?)?)
175            } else {
176                None
177            };
178            if let Some(file_key) = maybe_file_key {
179                keychain.insert_aes_key(file_id, file_key)?;
180                break;
181            }
182
183            visited_ids.push(file_id);
184            file_id = *self.find_parent(self.find(&file_id)?)?.id();
185        }
186
187        for id in visited_ids.iter().rev() {
188            let decrypted_key = {
189                let file = self.find(id)?;
190                let parent = self.find_parent(file)?;
191                let parent_key =
192                    keychain
193                        .get_aes_key(parent.id())?
194                        .ok_or(LbErrKind::Unexpected(
195                            "parent key should have been populated by prior routine".to_string(),
196                        ))?;
197                let encrypted_key = file.folder_access_key();
198                symkey::decrypt(&parent_key, encrypted_key)?
199            };
200            keychain.insert_aes_key(*id, decrypted_key)?;
201        }
202
203        Ok(keychain.get_aes_key(id)?.ok_or(LbErrKind::Unexpected(
204            "parent key should have been populated by prior routine (2)".to_string(),
205        ))?)
206    }
207
208    pub fn name(&mut self, id: &Uuid, keychain: &Keychain) -> LbResult<String> {
209        if let Some(name) = self.name.get(id) {
210            return Ok(name.clone());
211        }
212        let key = self.decrypt_key(id, keychain)?;
213        let name = self.find(id)?.secret_name().to_string(&key)?;
214        self.name.insert(*id, name.clone());
215        Ok(name)
216    }
217
218    pub fn name_using_links(&mut self, id: &Uuid, keychain: &Keychain) -> LbResult<String> {
219        let id = if let Some(link) = self.linked_by(id)? { link } else { *id };
220        self.name(&id, keychain)
221    }
222
223    pub fn parent_using_links(&mut self, id: &Uuid) -> LbResult<Uuid> {
224        let id = if let Some(link) = self.linked_by(id)? { link } else { *id };
225        Ok(*self.find(&id)?.parent())
226    }
227
228    pub fn linked_by(&mut self, id: &Uuid) -> LbResult<Option<Uuid>> {
229        if self.linked_by.is_empty() {
230            for link_id in self.ids() {
231                if let FileType::Link { target } = self.find(&link_id)?.file_type() {
232                    if !self.calculate_deleted(&link_id)? {
233                        self.linked_by.insert(target, link_id);
234                    }
235                }
236            }
237        }
238
239        Ok(self.linked_by.get(id).copied())
240    }
241
242    /// Returns ids of files whose parent is the argument. Does not include the argument.
243    /// TODO could consider returning a reference to the underlying cached value
244    pub fn children(&mut self, id: &Uuid) -> LbResult<Vec<Uuid>> {
245        // Check cache
246        if let Some(children) = self.children.get(id) {
247            return Ok(children.clone());
248        }
249
250        // Confirm file exists
251        let file = self.find(id)?;
252        if !file.is_folder() {
253            return Ok(vec![]);
254        }
255
256        // Populate cache
257        self.all_children()?;
258
259        // Return value from cache
260        if let Some(children) = self.children.get(id) {
261            return Ok(children.clone());
262        }
263
264        Ok(vec![])
265    }
266
267    // todo: cache?
268    // todo: use vec?
269    pub fn children_using_links(&mut self, id: &Uuid) -> LbResult<HashSet<Uuid>> {
270        let mut children = HashSet::new();
271        for child in self.children(id)? {
272            if let FileType::Link { target } = self.find(&child)?.file_type() {
273                if !self.calculate_deleted(&child)? {
274                    children.insert(target);
275                }
276            } else {
277                children.insert(child);
278            }
279        }
280
281        Ok(children)
282    }
283
284    /// Returns ids of files for which the argument is an ancestor—the files' children, recursively.
285    /// Does not include the argument.
286    /// This function tolerates cycles.
287    pub fn descendants(&mut self, id: &Uuid) -> LbResult<HashSet<Uuid>> {
288        // todo: caching?
289        let mut result = HashSet::new();
290        let mut to_process = vec![*id];
291        let mut i = 0;
292        while i < to_process.len() {
293            let new_descendents = self
294                .children(&to_process[i])?
295                .into_iter()
296                .filter(|f| !result.contains(f))
297                .collect::<Vec<Uuid>>();
298            // TODO could consider optimizing by not exploring documents
299            to_process.extend(new_descendents.iter());
300            result.extend(new_descendents.into_iter());
301            i += 1;
302        }
303        Ok(result)
304    }
305
306    pub fn descendants_using_links(&mut self, id: &Uuid) -> LbResult<HashSet<Uuid>> {
307        // todo: caching?
308        let mut result = HashSet::new();
309        let mut to_process = vec![*id];
310        let mut i = 0;
311        while i < to_process.len() {
312            let new_descendents = self
313                .children_using_links(&to_process[i])?
314                .into_iter()
315                .filter(|f| !result.contains(f))
316                .collect::<Vec<Uuid>>();
317            // TODO could consider optimizing by not exploring documents
318            to_process.extend(new_descendents.iter());
319            result.extend(new_descendents.into_iter());
320            i += 1;
321        }
322        Ok(result)
323    }
324
325    // todo: move to TreeLike
326    /// Returns ids of files for which the argument is a descendent—the files' parent, recursively. Does not include the argument.
327    /// This function tolerates cycles.
328    pub fn ancestors(&self, id: &Uuid) -> LbResult<HashSet<Uuid>> {
329        let mut result = HashSet::new();
330        let mut current_file = self.find(id)?;
331        while !current_file.is_root()
332            && self.maybe_find(current_file.parent()).is_some()
333            && !result.contains(current_file.parent())
334        {
335            result.insert(*current_file.parent());
336            current_file = self.find_parent(current_file)?;
337        }
338        Ok(result)
339    }
340
341    pub fn pending_roots(&mut self, keychain: &Keychain) -> LbResult<Vec<Uuid>> {
342        let mut result = Vec::new();
343        let owner = Owner(keychain.get_pk()?);
344        for id in self.ids() {
345            // file must be owned by another user
346            if self.find(&id)?.owner() == owner {
347                continue;
348            }
349
350            // file must be shared with this user
351            if self.find(&id)?.access_mode(&owner).is_none() {
352                continue;
353            }
354
355            // file must not be deleted
356            if self.calculate_deleted(&id)? {
357                continue;
358            }
359
360            // file must not have any links pointing to it
361            if self.linked_by(&id)?.is_some() {
362                continue;
363            }
364
365            result.push(id);
366        }
367
368        Ok(result)
369    }
370
371    pub fn non_deleted_pending_files(&mut self, keychain: &Keychain) -> LbResult<Vec<Uuid>> {
372        let roots = self.pending_roots(keychain)?;
373        let mut result = vec![];
374
375        for id in roots {
376            result.push(id);
377            let descendants = self.descendants(&id)?;
378
379            for id in descendants {
380                if !self.calculate_deleted(&id)? {
381                    result.push(id);
382                }
383            }
384        }
385
386        Ok(result)
387    }
388
389    pub fn stage<T2: TreeLikeMut<F = T::F>>(self, staged: T2) -> LazyTree<StagedTree<T, T2>> {
390        // todo: optimize by performing minimal updates on self caches
391        LazyTree::<StagedTree<T, T2>> {
392            tree: StagedTree::new(self.tree, staged),
393            name: uuid_map(),
394            implicit_deleted: uuid_map(),
395            children: uuid_map(),
396            linked_by: uuid_map(),
397        }
398    }
399
400    // todo: this is dead code
401    // pub fn stage_removals(self, removed: HashSet<Uuid>) -> LazyTree<StagedTree<T, Option<T::F>>> {
402    //     // todo: optimize by performing minimal updates on self caches
403    //     LazyTree::<StagedTree<T, Option<T::F>>> {
404    //         tree: StagedTree::removal(self.tree, removed),
405    //         name: uuid_map(),
406    //         implicit_deleted: uuid_map(),
407    //         children: uuid_map(),
408    //         linked_by: uuid_map(),
409    //     }
410    // }
411
412    // todo: optimize
413    pub fn assert_names_decryptable(&mut self, keychain: &Keychain) -> LbResult<()> {
414        for id in self.ids() {
415            if self.name(&id, keychain).is_err() {
416                return Err(LbErrKind::Validation(ValidationFailure::NonDecryptableFileName(id)))?;
417            }
418        }
419        Ok(())
420    }
421}
422
423pub type Stage1<Base, Local> = StagedTree<Base, Local>;
424pub type LazyStaged1<Base, Local> = LazyTree<Stage1<Base, Local>>;
425pub type Stage2<Base, Local, Staged> = StagedTree<StagedTree<Base, Local>, Staged>;
426pub type LazyStage2<Base, Local, Staged> = LazyTree<Stage2<Base, Local, Staged>>;
427
428#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq)]
429pub enum ValidationFailure {
430    Orphan(Uuid),
431
432    /// A folder was moved into itself
433    Cycle(HashSet<Uuid>),
434
435    /// A filename is not available
436    PathConflict(HashSet<Uuid>),
437
438    /// This filename is too long
439    FileNameTooLong(Uuid),
440
441    /// A link or document was treated as a folder
442    NonFolderWithChildren(Uuid),
443
444    /// You cannot have a link to a file you own
445    OwnedLink(Uuid),
446
447    /// You cannot have a link that points to a nonexistent file
448    BrokenLink(Uuid),
449
450    /// You cannot have multiple links to the same file
451    DuplicateLink {
452        target: Uuid,
453    },
454
455    /// You cannot have a link inside a shared folder
456    SharedLink {
457        link: Uuid,
458        shared_ancestor: Uuid,
459    },
460
461    FileWithDifferentOwnerParent(Uuid),
462    NonDecryptableFileName(Uuid),
463    DeletedFileUpdated(Uuid),
464}
465
466impl<T> LazyTree<T>
467where
468    T: TreeLikeMut,
469{
470    pub fn stage_and_promote<S: TreeLikeMut<F = T::F>>(&mut self, mut staged: S) -> LbResult<()> {
471        for id in staged.ids() {
472            if let Some(removed) = staged.remove(id)? {
473                self.tree.insert(removed)?;
474            }
475        }
476        // todo: incremental cache update
477        self.name = uuid_map();
478        self.implicit_deleted = uuid_map();
479        self.children = uuid_map();
480        self.linked_by = uuid_map();
481        Ok(())
482    }
483
484    pub fn stage_validate_and_promote<S: TreeLikeMut<F = T::F>>(
485        &mut self, mut staged: S, owner: Owner,
486    ) -> LbResult<()> {
487        StagedTree::new(&self.tree, &mut staged)
488            .to_lazy()
489            .validate(owner)?;
490        self.stage_and_promote(staged)?;
491        Ok(())
492    }
493
494    // todo: this is dead code
495    pub fn stage_removals_and_promote(&mut self, removed: HashSet<Uuid>) -> LbResult<()> {
496        for id in removed {
497            self.tree.remove(id)?;
498        }
499        // todo: incremental cache update
500        self.name = uuid_map();
501        self.implicit_deleted = uuid_map();
502        self.children = uuid_map();
503        self.linked_by = uuid_map();
504        Ok(())
505    }
506}
507
508impl<Base, Staged> LazyStaged1<Base, Staged>
509where
510    Base: TreeLikeMut,
511    Staged: TreeLikeMut<F = Base::F>,
512{
513    // todo: incrementalism
514    pub fn promote(self) -> LbResult<LazyTree<Base>> {
515        let mut staged = self.tree.staged;
516        let mut base = self.tree.base;
517        for id in staged.ids() {
518            if let Some(removed) = staged.remove(id)? {
519                base.insert(removed)?;
520            }
521        }
522        for id in self.tree.removed {
523            base.remove(id)?;
524        }
525
526        Ok(LazyTree {
527            tree: base,
528            name: uuid_map(),
529            implicit_deleted: uuid_map(),
530            children: uuid_map(),
531            linked_by: uuid_map(),
532        })
533    }
534}
535
536impl<Base, Staged> LazyStaged1<Base, Staged>
537where
538    Base: TreeLike,
539    Staged: TreeLikeMut<F = Base::F>,
540{
541    // todo: incrementalism
542    pub fn unstage(self) -> (LazyTree<Base>, Staged) {
543        (
544            LazyTree {
545                tree: self.tree.base,
546                name: uuid_map(),
547                implicit_deleted: uuid_map(),
548                children: uuid_map(),
549                linked_by: uuid_map(),
550            },
551            self.tree.staged,
552        )
553    }
554}
555
556impl<T: TreeLike> TreeLike for LazyTree<T> {
557    type F = T::F;
558
559    fn ids(&self) -> Vec<Uuid> {
560        self.tree.ids()
561    }
562
563    fn maybe_find(&self, id: &Uuid) -> Option<&Self::F> {
564        self.tree.maybe_find(id)
565    }
566}
567
568fn uuid_map<V>() -> HashMap<Uuid, V, UuidIdentityHasherBuilder> {
569    HashMap::with_hasher(UuidIdentityHasherBuilder)
570}