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