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