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(¤t)?.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(¤t)? {
68 self.find(&link)?
69 } else {
70 self.find(¤t)?
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(¤t, 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 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(¤t)? {
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 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 ¤t,
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()) .collect::<Vec<&str>>()
224}