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