lockbook_shared/
path_ops.rs1use 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(¤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, 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(¤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(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(¤t, 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 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 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(¤t)? {
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 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 ¤t,
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()) .collect::<Vec<&str>>()
229}