Skip to main content

lb_rs/model/
path_ops.rs

1use crate::model::access_info::UserAccessMode;
2use crate::model::errors::{LbErrKind, LbResult};
3use crate::model::file_like::FileLike;
4use crate::model::file_metadata::{FileType, Owner};
5use crate::model::lazy::{LazyStaged1, LazyTree};
6use crate::model::tree_like::{TreeLike, TreeLikeMut};
7use crate::model::{symkey, validate};
8use crate::service::keychain::Keychain;
9use serde::{Deserialize, Serialize};
10use std::collections::HashSet;
11use uuid::Uuid;
12
13use super::ValidationFailure;
14use super::signed_meta::SignedMeta;
15
16impl<T> LazyTree<T>
17where
18    T: TreeLike<F = SignedMeta>,
19{
20    pub fn path_to_id(&mut self, path: &str, root: &Uuid, keychain: &Keychain) -> LbResult<Uuid> {
21        let mut current = *root;
22        'path: for name in split_path(path) {
23            let id = if let FileType::Link { target } = self.find(&current)?.file_type() {
24                target
25            } else {
26                current
27            };
28            'child: for child in self.children(&id)? {
29                if self.calculate_deleted(&child)? {
30                    continue 'child;
31                }
32
33                if self.name_using_links(&child, keychain)? == name {
34                    current = match self.find(&child)?.file_type() {
35                        FileType::Link { target } => target,
36                        _ => child,
37                    };
38
39                    continue 'path;
40                }
41            }
42
43            return Err(LbErrKind::FileNonexistent.into());
44        }
45
46        Ok(current)
47    }
48
49    pub fn id_to_path(&mut self, id: &Uuid, keychain: &Keychain) -> LbResult<String> {
50        let meta = self.find(id)?;
51
52        if meta.is_root() {
53            return Ok("/".to_string());
54        }
55
56        let mut path = match meta.file_type() {
57            FileType::Document => "",
58            FileType::Folder => "/",
59            FileType::Link { target } => match self.find(&target)?.file_type() {
60                FileType::Document | FileType::Link { .. } => "",
61                FileType::Folder => "/",
62            },
63        }
64        .to_string();
65
66        let mut current = *meta.id();
67        loop {
68            let current_meta = if let Some(link) = self.linked_by(&current)? {
69                self.find(&link)?
70            } else {
71                self.find(&current)?
72            };
73            if self.maybe_find(current_meta.parent()).is_none() {
74                return Err(LbErrKind::FileParentNonexistent.into());
75            }
76            if current_meta.is_root() {
77                return Ok(path);
78            }
79            let next = *current_meta.parent();
80            let current_name = self.name_using_links(&current, keychain)?;
81            path = format!("/{current_name}{path}");
82            current = next;
83        }
84    }
85
86    pub fn list_paths(
87        &mut self, filter: Option<Filter>, keychain: &Keychain,
88    ) -> LbResult<Vec<(Uuid, String)>> {
89        // Deal with filter
90        let filtered = match filter {
91            Some(Filter::DocumentsOnly) => {
92                let mut ids = vec![];
93                for id in self.ids() {
94                    if self.find(&id)?.is_document() {
95                        ids.push(id);
96                    }
97                }
98                ids
99            }
100            Some(Filter::FoldersOnly) => {
101                let mut ids = vec![];
102                for id in self.ids() {
103                    if self.find(&id)?.is_folder() {
104                        ids.push(id);
105                    }
106                }
107                ids
108            }
109            Some(Filter::LeafNodesOnly) => {
110                let mut retained: Vec<_> = self.ids().into_iter().collect();
111                for id in self.ids() {
112                    let parent = self.find(&id)?.parent();
113                    retained.retain(|fid| fid != parent);
114                }
115                retained
116            }
117            None => self.ids(),
118        };
119
120        let mut paths = vec![];
121        for id in filtered {
122            if !self.is_invisible_id(id)? {
123                paths.push((id, self.id_to_path(&id, keychain)?));
124            }
125        }
126
127        Ok(paths)
128    }
129}
130
131impl<Base, Local> LazyStaged1<Base, Local>
132where
133    Base: TreeLike<F = SignedMeta>,
134    Local: TreeLikeMut<F = Base::F>,
135{
136    pub fn create_link_at_path(
137        &mut self, path: &str, target_id: Uuid, root: &Uuid, keychain: &Keychain,
138    ) -> LbResult<Uuid> {
139        validate::path(path)?;
140        let file_type = FileType::Link { target: target_id };
141        let path_components = split_path(path);
142        self.create_at_path_helper(file_type, path_components, root, keychain)
143    }
144
145    pub fn create_at_path(
146        &mut self, path: &str, root: &Uuid, keychain: &Keychain,
147    ) -> LbResult<Uuid> {
148        validate::path(path)?;
149        let file_type = if path.ends_with('/') { FileType::Folder } else { FileType::Document };
150        let path_components = split_path(path);
151        self.create_at_path_helper(file_type, path_components, root, keychain)
152    }
153
154    fn create_at_path_helper(
155        &mut self, file_type: FileType, path_components: Vec<&str>, root: &Uuid,
156        keychain: &Keychain,
157    ) -> LbResult<Uuid> {
158        let mut current = *root;
159
160        'path: for index in 0..path_components.len() {
161            'child: for child in self.children(&current)? {
162                if self.calculate_deleted(&child)? {
163                    continue 'child;
164                }
165
166                if self.name_using_links(&child, keychain)? == path_components[index] {
167                    if index == path_components.len() - 1 {
168                        return Err(LbErrKind::Validation(ValidationFailure::PathConflict(
169                            HashSet::from([child]),
170                        )))?;
171                    }
172
173                    current = match self.find(&child)?.file_type() {
174                        FileType::Document => {
175                            return Err(LbErrKind::Validation(
176                                ValidationFailure::NonFolderWithChildren(child),
177                            ))?;
178                        }
179                        FileType::Folder => child,
180                        FileType::Link { target } => {
181                            let current = self.find(&target)?;
182                            if current.access_mode(&Owner(keychain.get_pk()?))
183                                < Some(UserAccessMode::Write)
184                            {
185                                return Err(LbErrKind::InsufficientPermission.into());
186                            }
187                            *current.id()
188                        }
189                    };
190                    continue 'path;
191                }
192            }
193
194            // Child does not exist, create it
195            let this_file_type =
196                if index != path_components.len() - 1 { FileType::Folder } else { file_type };
197
198            current = self.create(
199                Uuid::new_v4(),
200                symkey::generate_key(),
201                &current,
202                path_components[index],
203                this_file_type,
204                keychain,
205            )?;
206        }
207
208        Ok(current)
209    }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub enum Filter {
214    DocumentsOnly,
215    FoldersOnly,
216    LeafNodesOnly,
217}
218
219fn split_path(path: &str) -> Vec<&str> {
220    path.split('/')
221        .collect::<Vec<&str>>()
222        .into_iter()
223        .filter(|s| !s.is_empty()) // Remove the trailing empty element in the case this is a folder
224        .collect::<Vec<&str>>()
225}