lockbook_shared/
path_ops.rs

1use std::collections::HashSet;
2
3use libsecp256k1::PublicKey;
4use uuid::Uuid;
5
6use crate::access_info::UserAccessMode;
7use crate::account::Account;
8use crate::file_like::FileLike;
9use crate::file_metadata::{FileType, Owner};
10use crate::lazy::LazyStaged1;
11use crate::signed_file::SignedFile;
12use crate::tree_like::{TreeLike, TreeLikeMut};
13use crate::{symkey, validate, SharedErrorKind, SharedResult};
14
15impl<Base, Local> LazyStaged1<Base, Local>
16where
17    Base: TreeLike<F = SignedFile>,
18    Local: TreeLike<F = Base::F>,
19{
20    pub fn path_to_id(&mut self, path: &str, root: &Uuid, account: &Account) -> SharedResult<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, account)? == 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(SharedErrorKind::FileNonexistent.into());
44        }
45
46        Ok(current)
47    }
48
49    pub fn id_to_path(&mut self, id: &Uuid, account: &Account) -> SharedResult<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(SharedErrorKind::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, account)?;
81            path = format!("/{}{}", current_name, path);
82            current = next;
83        }
84    }
85
86    pub fn list_paths(
87        &mut self, filter: Option<Filter>, account: &Account,
88    ) -> SharedResult<Vec<String>> {
89        // Deal with filter
90        let filtered = match filter {
91            Some(Filter::DocumentsOnly) => {
92                let mut ids = HashSet::new();
93                for id in self.ids() {
94                    if self.find(id)?.is_document() {
95                        ids.insert(*id);
96                    }
97                }
98                ids
99            }
100            Some(Filter::FoldersOnly) => {
101                let mut ids = HashSet::new();
102                for id in self.ids() {
103                    if self.find(id)?.is_folder() {
104                        ids.insert(*id);
105                    }
106                }
107                ids
108            }
109            Some(Filter::LeafNodesOnly) => {
110                let mut retained = self.owned_ids();
111                for id in self.ids() {
112                    retained.remove(self.find(id)?.parent());
113                }
114                retained
115            }
116            None => self.owned_ids(),
117        };
118
119        // remove deleted; include links not linked files
120        let mut paths = vec![];
121        for id in filtered.clone() {
122            let id = match self.linked_by(&id)? {
123                None => id,
124                Some(link) => {
125                    if filtered.contains(&link) {
126                        continue;
127                    }
128                    link
129                }
130            };
131
132            if !self.calculate_deleted(&id)? && !self.in_pending_share(&id)? {
133                paths.push(self.id_to_path(&id, account)?);
134            }
135        }
136
137        Ok(paths)
138    }
139}
140
141impl<Base, Local> LazyStaged1<Base, Local>
142where
143    Base: TreeLike<F = SignedFile>,
144    Local: TreeLikeMut<F = Base::F>,
145{
146    pub fn create_link_at_path(
147        &mut self, path: &str, target_id: Uuid, root: &Uuid, account: &Account, pub_key: &PublicKey,
148    ) -> SharedResult<Uuid> {
149        validate::path(path)?;
150        let file_type = FileType::Link { target: target_id };
151        let path_components = split_path(path);
152        self.create_at_path_helper(file_type, path_components, root, account, pub_key)
153    }
154
155    pub fn create_at_path(
156        &mut self, path: &str, root: &Uuid, account: &Account, pub_key: &PublicKey,
157    ) -> SharedResult<Uuid> {
158        validate::path(path)?;
159        let file_type = if path.ends_with('/') { FileType::Folder } else { FileType::Document };
160        let path_components = split_path(path);
161        self.create_at_path_helper(file_type, path_components, root, account, pub_key)
162    }
163
164    fn create_at_path_helper(
165        &mut self, file_type: FileType, path_components: Vec<&str>, root: &Uuid, account: &Account,
166        pub_key: &PublicKey,
167    ) -> SharedResult<Uuid> {
168        let mut current = *root;
169
170        'path: for index in 0..path_components.len() {
171            'child: for child in self.children(&current)? {
172                if self.calculate_deleted(&child)? {
173                    continue 'child;
174                }
175
176                if self.name_using_links(&child, account)? == path_components[index] {
177                    if index == path_components.len() - 1 {
178                        return Err(SharedErrorKind::PathTaken.into());
179                    }
180
181                    current = match self.find(&child)?.file_type() {
182                        FileType::Document => {
183                            return Err(SharedErrorKind::FileNotFolder.into());
184                        }
185                        FileType::Folder => child,
186                        FileType::Link { target } => {
187                            let current = self.find(&target)?;
188                            if current.access_mode(&Owner(*pub_key)) < Some(UserAccessMode::Write) {
189                                return Err(SharedErrorKind::InsufficientPermission.into());
190                            }
191                            *current.id()
192                        }
193                    };
194                    continue 'path;
195                }
196            }
197
198            // Child does not exist, create it
199            let this_file_type =
200                if index != path_components.len() - 1 { FileType::Folder } else { file_type };
201
202            current = self.create(
203                Uuid::new_v4(),
204                symkey::generate_key(),
205                &current,
206                path_components[index],
207                this_file_type,
208                account,
209            )?;
210        }
211
212        Ok(current)
213    }
214}
215
216#[derive(Debug)]
217pub enum Filter {
218    DocumentsOnly,
219    FoldersOnly,
220    LeafNodesOnly,
221}
222
223fn split_path(path: &str) -> Vec<&str> {
224    path.split('/')
225        .collect::<Vec<&str>>()
226        .into_iter()
227        .filter(|s| !s.is_empty()) // Remove the trailing empty element in the case this is a folder
228        .collect::<Vec<&str>>()
229}