diff options
| author | diogo464 <[email protected]> | 2022-02-08 08:47:26 +0000 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2022-02-08 08:47:26 +0000 |
| commit | 37d767fbc1b0ec5f9afc41e741c56bf2407e837d (patch) | |
| tree | 317e46f56a3dcc9240286cfd4a6c75a75c910243 | |
| parent | 406a3e662b3fafd73ab72a6f7bd4e22131654dfb (diff) | |
snapshot
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/main.rs | 914 |
3 files changed, 902 insertions, 14 deletions
| @@ -96,6 +96,7 @@ dependencies = [ | |||
| 96 | "log", | 96 | "log", |
| 97 | "serde", | 97 | "serde", |
| 98 | "slotmap", | 98 | "slotmap", |
| 99 | "thiserror", | ||
| 99 | "toml", | 100 | "toml", |
| 100 | ] | 101 | ] |
| 101 | 102 | ||
| @@ -13,4 +13,5 @@ flexi_logger = "0.22.3" | |||
| 13 | log = "0.4.14" | 13 | log = "0.4.14" |
| 14 | serde = { version = "1.0.136", features = ["derive"] } | 14 | serde = { version = "1.0.136", features = ["derive"] } |
| 15 | slotmap = "1.0.6" | 15 | slotmap = "1.0.6" |
| 16 | thiserror = "1.0.30" | ||
| 16 | toml = "0.5.8" | 17 | toml = "0.5.8" |
diff --git a/src/main.rs b/src/main.rs index f623450..9b17787 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -2,7 +2,847 @@ | |||
| 2 | 2 | ||
| 3 | // TODO: rewrite all errors so they start with lower case | 3 | // TODO: rewrite all errors so they start with lower case |
| 4 | 4 | ||
| 5 | pub mod depot { | 5 | mod depot { |
| 6 | use std::{ | ||
| 7 | collections::HashSet, | ||
| 8 | ffi::{OsStr, OsString}, | ||
| 9 | ops::Index, | ||
| 10 | path::{Path, PathBuf}, | ||
| 11 | }; | ||
| 12 | use thiserror::Error; | ||
| 13 | |||
| 14 | use slotmap::{Key, SlotMap}; | ||
| 15 | |||
| 16 | //pub type Result<T, E = DepotError> = std::result::Result<T, E>; | ||
| 17 | pub use anyhow::Result; | ||
| 18 | pub use disk::{read, write}; | ||
| 19 | |||
| 20 | slotmap::new_key_type! {pub struct LinkID;} | ||
| 21 | slotmap::new_key_type! {struct NodeID;} | ||
| 22 | |||
| 23 | #[derive(Debug, Error)] | ||
| 24 | enum DepotError { | ||
| 25 | #[error("path must be relative")] | ||
| 26 | InvalidPath, | ||
| 27 | #[error("path must be relative and not empty")] | ||
| 28 | InvalidLinkPath, | ||
| 29 | #[error("{0}")] | ||
| 30 | LinkCreateError(#[from] LinkCreateError), | ||
| 31 | } | ||
| 32 | |||
| 33 | #[derive(Debug, Error)] | ||
| 34 | enum LinkCreateError { | ||
| 35 | #[error("an ancestor of this path is already linked")] | ||
| 36 | AncestorAlreadyLinked, | ||
| 37 | } | ||
| 38 | |||
| 39 | #[derive(Debug, Clone)] | ||
| 40 | struct Node { | ||
| 41 | comp: OsString, | ||
| 42 | parent: NodeID, | ||
| 43 | kind: NodeKind, | ||
| 44 | } | ||
| 45 | |||
| 46 | #[derive(Debug, Clone)] | ||
| 47 | enum NodeKind { | ||
| 48 | Link(LinkID), | ||
| 49 | Directory(HashSet<NodeID>), | ||
| 50 | } | ||
| 51 | |||
| 52 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
| 53 | pub enum NodeSearchResult { | ||
| 54 | Found(NodeID), | ||
| 55 | /// the closest NodeID up the the search point. | ||
| 56 | NotFound(NodeID), | ||
| 57 | } | ||
| 58 | |||
| 59 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
| 60 | pub enum DirNode { | ||
| 61 | Link(LinkID), | ||
| 62 | Directory(PathBuf), | ||
| 63 | } | ||
| 64 | |||
| 65 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
| 66 | pub enum SearchResult { | ||
| 67 | Found(LinkID), | ||
| 68 | Ancestor(LinkID), | ||
| 69 | NotFound, | ||
| 70 | } | ||
| 71 | |||
| 72 | #[derive(Debug, Clone)] | ||
| 73 | struct Link { | ||
| 74 | origin: PathBuf, | ||
| 75 | destination: PathBuf, | ||
| 76 | origin_id: NodeID, | ||
| 77 | } | ||
| 78 | |||
| 79 | #[derive(Debug)] | ||
| 80 | pub struct LinkView<'a> { | ||
| 81 | link_id: LinkID, | ||
| 82 | depot: &'a Depot, | ||
| 83 | } | ||
| 84 | |||
| 85 | impl<'a> LinkView<'a> { | ||
| 86 | pub fn origin(&self) -> &Path { | ||
| 87 | &self.depot.links[self.link_id].origin | ||
| 88 | } | ||
| 89 | |||
| 90 | pub fn destination(&self) -> &Path { | ||
| 91 | &self.depot.links[self.link_id].destination | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | #[derive(Debug, Clone)] | ||
| 96 | struct DepotTree { | ||
| 97 | root: NodeID, | ||
| 98 | nodes: SlotMap<NodeID, Node>, | ||
| 99 | } | ||
| 100 | |||
| 101 | impl Default for DepotTree { | ||
| 102 | fn default() -> Self { | ||
| 103 | let mut nodes = SlotMap::<NodeID, Node>::default(); | ||
| 104 | let root = nodes.insert(Node { | ||
| 105 | comp: Default::default(), | ||
| 106 | parent: Default::default(), | ||
| 107 | kind: NodeKind::Directory(Default::default()), | ||
| 108 | }); | ||
| 109 | Self { root, nodes } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | impl Index<NodeID> for DepotTree { | ||
| 114 | type Output = Node; | ||
| 115 | |||
| 116 | fn index(&self, index: NodeID) -> &Self::Output { | ||
| 117 | self.nodes.index(index) | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | impl DepotTree { | ||
| 122 | /// create a node of kind [`NodeKind::Link`]. | ||
| 123 | pub fn link_create(&mut self, path: &Path, link_id: LinkID) -> Result<NodeID> { | ||
| 124 | debug_assert!(path_verify_link(path).is_ok()); | ||
| 125 | |||
| 126 | let path_search_result = self.search(path); | ||
| 127 | |||
| 128 | // handle the error cases | ||
| 129 | match path_search_result { | ||
| 130 | NodeSearchResult::Found(node_id) => { | ||
| 131 | let node = &self.nodes[node_id]; | ||
| 132 | match &node.kind { | ||
| 133 | NodeKind::Link(_) => Err(anyhow::anyhow!("link already exists")), | ||
| 134 | NodeKind::Directory(_) => { | ||
| 135 | Err(anyhow::anyhow!("path already has links under it")) | ||
| 136 | } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | NodeSearchResult::NotFound(ancestor_node_id) => { | ||
| 140 | let ancestor_node = &self.nodes[ancestor_node_id]; | ||
| 141 | match &ancestor_node.kind { | ||
| 142 | NodeKind::Link(_) => Err(anyhow::anyhow!( | ||
| 143 | "an ancestor of this path is already linked" | ||
| 144 | )), | ||
| 145 | NodeKind::Directory(_) => Ok(()), | ||
| 146 | } | ||
| 147 | } | ||
| 148 | }?; | ||
| 149 | |||
| 150 | // create the node | ||
| 151 | // unwrap: this is a verfied link path, it must have atleast one component | ||
| 152 | let filename = path.file_name().unwrap(); | ||
| 153 | let parent_path = path_parent_or_empty(path); | ||
| 154 | let node_id = self.nodes.insert(Node { | ||
| 155 | comp: filename.to_owned(), | ||
| 156 | parent: Default::default(), | ||
| 157 | kind: NodeKind::Link(link_id), | ||
| 158 | }); | ||
| 159 | let parent_id = self.directory_get_or_create(parent_path, node_id); | ||
| 160 | self.nodes[node_id].parent = parent_id; | ||
| 161 | Ok(node_id) | ||
| 162 | } | ||
| 163 | |||
| 164 | pub fn link_update_id(&mut self, node_id: NodeID, link_id: LinkID) { | ||
| 165 | let node = &mut self.nodes[node_id]; | ||
| 166 | match &mut node.kind { | ||
| 167 | NodeKind::Link(lid) => *lid = link_id, | ||
| 168 | NodeKind::Directory(_) => unreachable!(), | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | /// attempts to moves a node of kind [`NodeKind::Link`] to `destination`. | ||
| 173 | pub fn link_move(&mut self, node_id: NodeID, destination: &Path) -> Result<()> { | ||
| 174 | let parent_id = self.nodes[node_id].parent; | ||
| 175 | let parent = &mut self.nodes[parent_id]; | ||
| 176 | |||
| 177 | // remove the node from its parent temporarily so that the search never returns this | ||
| 178 | // link and that way any link will find means an error. | ||
| 179 | // if an error does happen then we re-add this node to its parent to keep the data | ||
| 180 | // consistent. | ||
| 181 | match &mut parent.kind { | ||
| 182 | NodeKind::Link(_) => unreachable!(), | ||
| 183 | NodeKind::Directory(children) => children.remove(&node_id), | ||
| 184 | }; | ||
| 185 | |||
| 186 | let search_result = self.search(destination); | ||
| 187 | // handle the error cases | ||
| 188 | match search_result { | ||
| 189 | NodeSearchResult::Found(found_id) => { | ||
| 190 | assert!(found_id != node_id); | ||
| 191 | self.directory_add_child(parent_id, node_id); | ||
| 192 | return Err(anyhow::anyhow!("link already exists at that path")); | ||
| 193 | } | ||
| 194 | NodeSearchResult::NotFound(ancestor_id) => { | ||
| 195 | let ancestor = &self.nodes[ancestor_id]; | ||
| 196 | match &ancestor.kind { | ||
| 197 | NodeKind::Link(_) => { | ||
| 198 | self.directory_add_child(parent_id, node_id); | ||
| 199 | return Err(anyhow::anyhow!("ancestor path is already linked")); | ||
| 200 | } | ||
| 201 | NodeKind::Directory(_) => {} | ||
| 202 | } | ||
| 203 | } | ||
| 204 | }; | ||
| 205 | |||
| 206 | let destination_parent = path_parent_or_empty(destination); | ||
| 207 | let new_parent_id = self.directory_get_or_create(destination_parent, node_id); | ||
| 208 | if new_parent_id != parent_id { | ||
| 209 | self.nodes[node_id].parent = new_parent_id; | ||
| 210 | |||
| 211 | // we have to re-add and call the remove function because it could lead to the removal | ||
| 212 | // of several directories if they become empty after this remove. | ||
| 213 | self.directory_add_child(parent_id, node_id); | ||
| 214 | self.directory_remove_child(parent_id, node_id); | ||
| 215 | } | ||
| 216 | |||
| 217 | // unwrap: destination is a verified link path so it has atleast 1 component | ||
| 218 | let comp = destination.file_name().unwrap(); | ||
| 219 | let node = &mut self.nodes[node_id]; | ||
| 220 | if node.comp != comp { | ||
| 221 | node.comp = comp.to_owned(); | ||
| 222 | } | ||
| 223 | |||
| 224 | Ok(()) | ||
| 225 | } | ||
| 226 | |||
| 227 | pub fn link_search(&self, path: &Path) -> SearchResult { | ||
| 228 | match self.search(path) { | ||
| 229 | NodeSearchResult::Found(node_id) => match &self.nodes[node_id].kind { | ||
| 230 | NodeKind::Link(link_id) => SearchResult::Found(*link_id), | ||
| 231 | NodeKind::Directory(_) => SearchResult::NotFound, | ||
| 232 | }, | ||
| 233 | NodeSearchResult::NotFound(node_id) => match &self.nodes[node_id].kind { | ||
| 234 | NodeKind::Link(link_id) => SearchResult::Ancestor(*link_id), | ||
| 235 | NodeKind::Directory(_) => SearchResult::NotFound, | ||
| 236 | }, | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | /// remove a node of kind [`NodeKind::Link`]. | ||
| 241 | pub fn link_remove(&mut self, node_id: NodeID) { | ||
| 242 | let node = &self.nodes[node_id]; | ||
| 243 | assert!(std::matches!(node.kind, NodeKind::Link(_))); | ||
| 244 | let parent_id = node.parent; | ||
| 245 | self.nodes.remove(node_id); | ||
| 246 | self.directory_remove_child(parent_id, node_id); | ||
| 247 | } | ||
| 248 | |||
| 249 | pub fn links_under(&self, path: &Path) -> impl Iterator<Item = LinkID> + '_ { | ||
| 250 | let links = match self.search(path) { | ||
| 251 | NodeSearchResult::Found(node_id) => { | ||
| 252 | let node = &self.nodes[node_id]; | ||
| 253 | match &node.kind { | ||
| 254 | NodeKind::Link(link_id) => vec![*link_id], | ||
| 255 | NodeKind::Directory(children) => { | ||
| 256 | let mut links = Vec::new(); | ||
| 257 | let mut node_ids = Vec::from_iter(children.iter().copied()); | ||
| 258 | while let Some(child_id) = node_ids.pop() { | ||
| 259 | let child = &self.nodes[child_id]; | ||
| 260 | match &child.kind { | ||
| 261 | NodeKind::Link(link_id) => links.push(*link_id), | ||
| 262 | NodeKind::Directory(extra_children) => { | ||
| 263 | node_ids.extend(extra_children.iter().copied()) | ||
| 264 | } | ||
| 265 | } | ||
| 266 | } | ||
| 267 | links | ||
| 268 | } | ||
| 269 | } | ||
| 270 | } | ||
| 271 | NodeSearchResult::NotFound(_) => vec![], | ||
| 272 | }; | ||
| 273 | links.into_iter() | ||
| 274 | } | ||
| 275 | |||
| 276 | pub fn has_links_under(&self, path: &Path) -> bool { | ||
| 277 | // it does not matter what type of node is found. if a directory exists then there | ||
| 278 | // must be atleast one link under it. | ||
| 279 | match self.search(path) { | ||
| 280 | NodeSearchResult::Found(_) => true, | ||
| 281 | NodeSearchResult::NotFound(_) => false, | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | pub fn read_dir(&self, path: &Path) -> Result<impl Iterator<Item = DirNode> + '_> { | ||
| 286 | match self.search(path) { | ||
| 287 | NodeSearchResult::Found(node_id) => match &self.nodes[node_id].kind { | ||
| 288 | NodeKind::Link(_) => Err(anyhow::anyhow!("read dir called on a link")), | ||
| 289 | NodeKind::Directory(children) => Ok(children.iter().map(|child_id| { | ||
| 290 | let child = &self.nodes[*child_id]; | ||
| 291 | match &child.kind { | ||
| 292 | NodeKind::Link(link_id) => DirNode::Link(*link_id), | ||
| 293 | NodeKind::Directory(_) => { | ||
| 294 | DirNode::Directory(self.build_path(*child_id)) | ||
| 295 | } | ||
| 296 | } | ||
| 297 | })), | ||
| 298 | }, | ||
| 299 | NodeSearchResult::NotFound(_) => Err(anyhow::anyhow!("directory not found")), | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | pub fn build_path(&self, node_id: NodeID) -> PathBuf { | ||
| 304 | fn recursive_helper(nodes: &SlotMap<NodeID, Node>, nid: NodeID, pbuf: &mut PathBuf) { | ||
| 305 | if nid.is_null() { | ||
| 306 | return; | ||
| 307 | } | ||
| 308 | let parent_id = nodes[nid].parent; | ||
| 309 | recursive_helper(nodes, parent_id, pbuf); | ||
| 310 | pbuf.push(&nodes[nid].comp); | ||
| 311 | } | ||
| 312 | |||
| 313 | let mut node_path = PathBuf::default(); | ||
| 314 | recursive_helper(&self.nodes, node_id, &mut node_path); | ||
| 315 | node_path | ||
| 316 | } | ||
| 317 | |||
| 318 | fn search(&self, path: &Path) -> NodeSearchResult { | ||
| 319 | debug_assert!(path_verify(path).is_ok()); | ||
| 320 | |||
| 321 | let mut curr_node_id = self.root; | ||
| 322 | let mut comp_iter = path_iter_comps(path).peekable(); | ||
| 323 | while let Some(comp) = comp_iter.next() { | ||
| 324 | if let Some(child_id) = self.directory_search_children(curr_node_id, comp) { | ||
| 325 | let child = &self.nodes[child_id]; | ||
| 326 | match &child.kind { | ||
| 327 | NodeKind::Link(_) => { | ||
| 328 | if comp_iter.peek().is_some() { | ||
| 329 | return NodeSearchResult::NotFound(child_id); | ||
| 330 | } else { | ||
| 331 | return NodeSearchResult::Found(child_id); | ||
| 332 | } | ||
| 333 | } | ||
| 334 | NodeKind::Directory(_) => curr_node_id = child_id, | ||
| 335 | } | ||
| 336 | } else { | ||
| 337 | return NodeSearchResult::NotFound(curr_node_id); | ||
| 338 | } | ||
| 339 | } | ||
| 340 | NodeSearchResult::Found(curr_node_id) | ||
| 341 | } | ||
| 342 | |||
| 343 | // creates directories all the way up to and including path. | ||
| 344 | // there cannot be any links up to `path`. | ||
| 345 | fn directory_get_or_create(&mut self, path: &Path, initial_child: NodeID) -> NodeID { | ||
| 346 | // TODO: this could be replaced if the search function also returned the depth of the | ||
| 347 | // node and we skip those components and just start creating directories up to the | ||
| 348 | // path. | ||
| 349 | let mut curr_node_id = self.root; | ||
| 350 | for comp in path_iter_comps(path) { | ||
| 351 | if let Some(child_id) = self.directory_search_children(curr_node_id, comp) { | ||
| 352 | debug_assert!(std::matches!( | ||
| 353 | self.nodes[child_id].kind, | ||
| 354 | NodeKind::Directory(_) | ||
| 355 | )); | ||
| 356 | curr_node_id = child_id; | ||
| 357 | } else { | ||
| 358 | let new_node_id = self.nodes.insert(Node { | ||
| 359 | comp: comp.to_owned(), | ||
| 360 | parent: curr_node_id, | ||
| 361 | kind: NodeKind::Directory(Default::default()), | ||
| 362 | }); | ||
| 363 | self.directory_add_child(curr_node_id, new_node_id); | ||
| 364 | curr_node_id = new_node_id; | ||
| 365 | } | ||
| 366 | } | ||
| 367 | self.directory_add_child(curr_node_id, initial_child); | ||
| 368 | curr_node_id | ||
| 369 | } | ||
| 370 | |||
| 371 | fn directory_search_children(&self, node_id: NodeID, comp: &OsStr) -> Option<NodeID> { | ||
| 372 | let node = &self.nodes[node_id]; | ||
| 373 | match &node.kind { | ||
| 374 | NodeKind::Link(_) => unreachable!(), | ||
| 375 | NodeKind::Directory(children) => { | ||
| 376 | for &child_id in children { | ||
| 377 | let child = &self.nodes[child_id]; | ||
| 378 | if child.comp == comp { | ||
| 379 | return Some(child_id); | ||
| 380 | } | ||
| 381 | } | ||
| 382 | } | ||
| 383 | } | ||
| 384 | None | ||
| 385 | } | ||
| 386 | |||
| 387 | fn directory_add_child(&mut self, node_id: NodeID, child_id: NodeID) { | ||
| 388 | let node = &mut self.nodes[node_id]; | ||
| 389 | match &mut node.kind { | ||
| 390 | NodeKind::Link(_) => unreachable!(), | ||
| 391 | NodeKind::Directory(children) => children.insert(child_id), | ||
| 392 | }; | ||
| 393 | } | ||
| 394 | |||
| 395 | fn directory_remove_child(&mut self, node_id: NodeID, child_id: NodeID) { | ||
| 396 | let node = &mut self.nodes[node_id]; | ||
| 397 | match &mut node.kind { | ||
| 398 | NodeKind::Link(_) => unreachable!(), | ||
| 399 | NodeKind::Directory(children) => { | ||
| 400 | children.remove(&child_id); | ||
| 401 | if children.is_empty() && !node.parent.is_null() { | ||
| 402 | let parent_id = node.parent; | ||
| 403 | self.directory_remove_child(parent_id, node_id); | ||
| 404 | } | ||
| 405 | } | ||
| 406 | } | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | #[derive(Debug, Default, Clone)] | ||
| 411 | pub struct Depot { | ||
| 412 | links: SlotMap<LinkID, Link>, | ||
| 413 | origin: DepotTree, | ||
| 414 | } | ||
| 415 | |||
| 416 | impl Depot { | ||
| 417 | pub fn link_create( | ||
| 418 | &mut self, | ||
| 419 | origin: impl AsRef<Path>, | ||
| 420 | destination: impl AsRef<Path>, | ||
| 421 | ) -> Result<LinkID> { | ||
| 422 | let origin = origin.as_ref(); | ||
| 423 | let destination = destination.as_ref(); | ||
| 424 | path_verify_link(origin)?; | ||
| 425 | path_verify_link(destination)?; | ||
| 426 | self.link_create_unchecked(origin, destination) | ||
| 427 | } | ||
| 428 | |||
| 429 | pub fn link_remove(&mut self, link_id: LinkID) { | ||
| 430 | let node_id = self.links[link_id].origin_id; | ||
| 431 | self.links.remove(link_id); | ||
| 432 | self.origin.link_remove(node_id); | ||
| 433 | } | ||
| 434 | |||
| 435 | /// moves the link specified by `link_id` to the path at `destination`. | ||
| 436 | /// if the link is already at the destination nothing is done. | ||
| 437 | /// if the destination is another link that that link is removed. | ||
| 438 | /// if the destination is under another link then an error is returned. | ||
| 439 | /// `destination` will be the link's new origin. | ||
| 440 | pub fn link_move(&mut self, link_id: LinkID, destination: impl AsRef<Path>) -> Result<()> { | ||
| 441 | let destination = destination.as_ref(); | ||
| 442 | path_verify_link(destination)?; | ||
| 443 | self.link_move_unchecked(link_id, destination) | ||
| 444 | } | ||
| 445 | |||
| 446 | pub fn link_search(&self, path: impl AsRef<Path>) -> Result<SearchResult> { | ||
| 447 | let path = path.as_ref(); | ||
| 448 | path_verify(path)?; | ||
| 449 | Ok(self.link_search_unchecked(path)) | ||
| 450 | } | ||
| 451 | |||
| 452 | pub fn link_find(&self, path: impl AsRef<Path>) -> Result<Option<LinkID>> { | ||
| 453 | let path = path.as_ref(); | ||
| 454 | path_verify(path)?; | ||
| 455 | Ok(self.link_find_unchecked(path)) | ||
| 456 | } | ||
| 457 | |||
| 458 | pub fn links_under( | ||
| 459 | &self, | ||
| 460 | path: impl AsRef<Path>, | ||
| 461 | ) -> Result<impl Iterator<Item = LinkID> + '_> { | ||
| 462 | let path = path.as_ref(); | ||
| 463 | path_verify(path)?; | ||
| 464 | Ok(self.links_under_unchecked(path)) | ||
| 465 | } | ||
| 466 | |||
| 467 | pub fn has_links_under(&self, path: impl AsRef<Path>) -> Result<bool> { | ||
| 468 | let path = path.as_ref(); | ||
| 469 | path_verify(path)?; | ||
| 470 | Ok(self.has_links_under_unchecked(path)) | ||
| 471 | } | ||
| 472 | |||
| 473 | pub fn link_view(&self, link_id: LinkID) -> LinkView { | ||
| 474 | LinkView { | ||
| 475 | link_id, | ||
| 476 | depot: self, | ||
| 477 | } | ||
| 478 | } | ||
| 479 | |||
| 480 | pub fn read_dir( | ||
| 481 | &self, | ||
| 482 | path: impl AsRef<Path>, | ||
| 483 | ) -> Result<impl Iterator<Item = DirNode> + '_> { | ||
| 484 | let path = path.as_ref(); | ||
| 485 | path_verify(path)?; | ||
| 486 | self.read_dir_unchecked(path) | ||
| 487 | } | ||
| 488 | |||
| 489 | fn link_create_unchecked(&mut self, origin: &Path, destination: &Path) -> Result<LinkID> { | ||
| 490 | let node_id = self.origin.link_create(origin, Default::default())?; | ||
| 491 | let link_id = self.links.insert(Link { | ||
| 492 | origin: origin.to_owned(), | ||
| 493 | destination: destination.to_owned(), | ||
| 494 | origin_id: node_id, | ||
| 495 | }); | ||
| 496 | self.origin.link_update_id(node_id, link_id); | ||
| 497 | Ok(link_id) | ||
| 498 | } | ||
| 499 | |||
| 500 | fn link_move_unchecked(&mut self, link_id: LinkID, destination: &Path) -> Result<()> { | ||
| 501 | let link = &self.links[link_id]; | ||
| 502 | if link.origin == destination { | ||
| 503 | return Ok(()); | ||
| 504 | } | ||
| 505 | let node_id = link.origin_id; | ||
| 506 | self.origin.link_move(node_id, destination)?; | ||
| 507 | self.links[link_id].origin = destination.to_owned(); | ||
| 508 | Ok(()) | ||
| 509 | } | ||
| 510 | |||
| 511 | fn link_search_unchecked(&self, path: &Path) -> SearchResult { | ||
| 512 | self.origin.link_search(path) | ||
| 513 | } | ||
| 514 | |||
| 515 | fn link_find_unchecked(&self, path: &Path) -> Option<LinkID> { | ||
| 516 | match self.link_search_unchecked(path) { | ||
| 517 | SearchResult::Found(link_id) => Some(link_id), | ||
| 518 | _ => None, | ||
| 519 | } | ||
| 520 | } | ||
| 521 | |||
| 522 | fn links_under_unchecked(&self, path: &Path) -> impl Iterator<Item = LinkID> + '_ { | ||
| 523 | self.origin.links_under(path) | ||
| 524 | } | ||
| 525 | |||
| 526 | fn has_links_under_unchecked(&self, path: &Path) -> bool { | ||
| 527 | self.origin.has_links_under(path) | ||
| 528 | } | ||
| 529 | |||
| 530 | fn read_dir_unchecked(&self, path: &Path) -> Result<impl Iterator<Item = DirNode> + '_> { | ||
| 531 | self.origin.read_dir(path) | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | /// a verified link path is a path that: | ||
| 536 | /// + is not empty | ||
| 537 | /// + is relative | ||
| 538 | /// + does not contain Prefix/RootDir/ParentDir | ||
| 539 | fn path_verify_link(path: &Path) -> Result<()> { | ||
| 540 | // make sure the path is not empty | ||
| 541 | if path.components().next().is_none() { | ||
| 542 | return Err(DepotError::InvalidLinkPath.into()); | ||
| 543 | } | ||
| 544 | path_verify(path).map_err(|_| DepotError::InvalidLinkPath.into()) | ||
| 545 | } | ||
| 546 | |||
| 547 | /// a verified path is a path that: | ||
| 548 | /// + is not empty | ||
| 549 | /// + is relative | ||
| 550 | /// + does not contain Prefix/RootDir/ParentDir | ||
| 551 | fn path_verify(path: &Path) -> Result<()> { | ||
| 552 | // make sure the path is relative | ||
| 553 | // make sure the path does not contain '.' or '..' | ||
| 554 | for component in path.components() { | ||
| 555 | match component { | ||
| 556 | std::path::Component::Prefix(_) | ||
| 557 | | std::path::Component::RootDir | ||
| 558 | | std::path::Component::CurDir | ||
| 559 | | std::path::Component::ParentDir => return Err(DepotError::InvalidPath.into()), | ||
| 560 | std::path::Component::Normal(_) => {} | ||
| 561 | } | ||
| 562 | } | ||
| 563 | Ok(()) | ||
| 564 | } | ||
| 565 | |||
| 566 | fn path_parent_or_empty(path: &Path) -> &Path { | ||
| 567 | path.parent().unwrap_or(Path::new("")) | ||
| 568 | } | ||
| 569 | |||
| 570 | /// Iterate over the components of a path. | ||
| 571 | /// # Pre | ||
| 572 | /// The path can only have "Normal" components. | ||
| 573 | fn path_iter_comps(path: &Path) -> impl Iterator<Item = &OsStr> { | ||
| 574 | debug_assert!(path_verify(path).is_ok()); | ||
| 575 | path.components().map(|component| match component { | ||
| 576 | std::path::Component::Normal(comp) => comp, | ||
| 577 | _ => unreachable!(), | ||
| 578 | }) | ||
| 579 | } | ||
| 580 | |||
| 581 | mod disk { | ||
| 582 | use std::path::{Path, PathBuf}; | ||
| 583 | |||
| 584 | use anyhow::Context; | ||
| 585 | use serde::{Deserialize, Serialize}; | ||
| 586 | |||
| 587 | use super::Depot; | ||
| 588 | |||
| 589 | #[derive(Debug, Serialize, Deserialize)] | ||
| 590 | struct DiskLink { | ||
| 591 | origin: PathBuf, | ||
| 592 | destination: PathBuf, | ||
| 593 | } | ||
| 594 | |||
| 595 | #[derive(Debug, Serialize, Deserialize)] | ||
| 596 | struct DiskLinks { | ||
| 597 | links: Vec<DiskLink>, | ||
| 598 | } | ||
| 599 | |||
| 600 | pub fn read(path: &Path) -> anyhow::Result<Depot> { | ||
| 601 | let contents = std::fs::read_to_string(path).context("Failed to read depot file")?; | ||
| 602 | let disk_links = toml::from_str::<DiskLinks>(&contents) | ||
| 603 | .context("Failed to parse depot file")? | ||
| 604 | .links; | ||
| 605 | let mut depot = Depot::default(); | ||
| 606 | for disk_link in disk_links { | ||
| 607 | depot | ||
| 608 | .link_create(disk_link.origin, disk_link.destination) | ||
| 609 | .context("Failed to build depot from file. File is in an invalid state")?; | ||
| 610 | } | ||
| 611 | Ok(depot) | ||
| 612 | } | ||
| 613 | |||
| 614 | pub fn write(path: &Path, depot: &Depot) -> anyhow::Result<()> { | ||
| 615 | let mut links = Vec::with_capacity(depot.links.len()); | ||
| 616 | for (_, link) in depot.links.iter() { | ||
| 617 | links.push(DiskLink { | ||
| 618 | origin: link.origin.clone(), | ||
| 619 | destination: link.destination.clone(), | ||
| 620 | }); | ||
| 621 | } | ||
| 622 | let contents = toml::to_string_pretty(&DiskLinks { links }) | ||
| 623 | .context("Failed to serialize depot")?; | ||
| 624 | std::fs::write(path, contents).context("Failed to write depot to file")?; | ||
| 625 | Ok(()) | ||
| 626 | } | ||
| 627 | } | ||
| 628 | |||
| 629 | #[cfg(test)] | ||
| 630 | mod tests { | ||
| 631 | use super::*; | ||
| 632 | |||
| 633 | #[test] | ||
| 634 | fn test_depot_link_create() { | ||
| 635 | let mut depot = Depot::default(); | ||
| 636 | let f1 = depot.link_create("f1", "f1").unwrap(); | ||
| 637 | let f2 = depot.link_create("f2", "f2").unwrap(); | ||
| 638 | let f3 = depot.link_create("d1/f3", "d1/f3").unwrap(); | ||
| 639 | let f4 = depot.link_create("d1/d2/f4", "d1/d2/d4").unwrap(); | ||
| 640 | |||
| 641 | assert_eq!(depot.link_find("f1").unwrap(), Some(f1)); | ||
| 642 | assert_eq!(depot.link_find("f2").unwrap(), Some(f2)); | ||
| 643 | assert_eq!(depot.link_find("d1/f3").unwrap(), Some(f3)); | ||
| 644 | assert_eq!(depot.link_find("d1/d2/f4").unwrap(), Some(f4)); | ||
| 645 | |||
| 646 | depot.link_create("f2", "").unwrap_err(); | ||
| 647 | depot.link_create("", "d4").unwrap_err(); | ||
| 648 | depot.link_create("f1/f3", "f3").unwrap_err(); | ||
| 649 | } | ||
| 650 | |||
| 651 | #[test] | ||
| 652 | fn test_depot_link_remove() { | ||
| 653 | let mut depot = Depot::default(); | ||
| 654 | let f1 = depot.link_create("d1/f1", "d1/f1").unwrap(); | ||
| 655 | let f2 = depot.link_create("d1/f2", "d1/f2").unwrap(); | ||
| 656 | let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap(); | ||
| 657 | let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap(); | ||
| 658 | let d3 = depot.link_create("d3", "d3").unwrap(); | ||
| 659 | |||
| 660 | depot.link_remove(f2); | ||
| 661 | assert_eq!(depot.link_find("d1/f1").unwrap(), Some(f1)); | ||
| 662 | assert_eq!(depot.link_find("d1/f2").unwrap(), None); | ||
| 663 | depot.link_remove(f4); | ||
| 664 | assert_eq!(depot.link_find("d1/d2/f4").unwrap(), None); | ||
| 665 | depot.link_remove(d3); | ||
| 666 | assert_eq!(depot.link_find("d3").unwrap(), None); | ||
| 667 | } | ||
| 668 | |||
| 669 | #[test] | ||
| 670 | fn test_depot_link_move() { | ||
| 671 | let mut depot = Depot::default(); | ||
| 672 | let f1 = depot.link_create("d1/f1", "d1/f1").unwrap(); | ||
| 673 | let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap(); | ||
| 674 | |||
| 675 | depot.link_move(f1, "").unwrap_err(); | ||
| 676 | depot.link_move(f1, "d1/f2/f1").unwrap_err(); | ||
| 677 | depot.link_move(f1, "d1/f2").unwrap_err(); | ||
| 678 | |||
| 679 | depot.link_move(f1, "f1").unwrap(); | ||
| 680 | assert_eq!(depot.link_view(f1).origin(), Path::new("f1")); | ||
| 681 | depot.link_move(f1, "f2").unwrap(); | ||
| 682 | assert_eq!(depot.link_view(f1).origin(), Path::new("f2")); | ||
| 683 | assert_eq!(depot.link_find("f2").unwrap(), Some(f1)); | ||
| 684 | } | ||
| 685 | |||
| 686 | #[test] | ||
| 687 | fn test_depot_link_search() { | ||
| 688 | let mut depot = Depot::default(); | ||
| 689 | let f1 = depot.link_create("d1/f1", "d1/f1").unwrap(); | ||
| 690 | let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap(); | ||
| 691 | let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap(); | ||
| 692 | let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap(); | ||
| 693 | let _d3 = depot.link_create("d3", "d3").unwrap(); | ||
| 694 | |||
| 695 | assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::Found(f1)); | ||
| 696 | assert_eq!( | ||
| 697 | depot.link_search("d1/f1/f5").unwrap(), | ||
| 698 | SearchResult::Ancestor(f1) | ||
| 699 | ); | ||
| 700 | assert_eq!(depot.link_search("d1").unwrap(), SearchResult::NotFound); | ||
| 701 | assert_eq!( | ||
| 702 | depot.link_search("d1/d2/f5").unwrap(), | ||
| 703 | SearchResult::NotFound | ||
| 704 | ); | ||
| 705 | } | ||
| 706 | |||
| 707 | #[test] | ||
| 708 | fn test_depot_link_find() { | ||
| 709 | let mut depot = Depot::default(); | ||
| 710 | let f1 = depot.link_create("d1/f1", "d1/f1").unwrap(); | ||
| 711 | let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap(); | ||
| 712 | let f3 = depot.link_create("d1/f3", "d1/f3").unwrap(); | ||
| 713 | let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap(); | ||
| 714 | let d3 = depot.link_create("d3", "d3").unwrap(); | ||
| 715 | |||
| 716 | assert_eq!(depot.link_find("d1/f1").unwrap(), Some(f1)); | ||
| 717 | assert_eq!(depot.link_find("d1/f3").unwrap(), Some(f3)); | ||
| 718 | assert_eq!(depot.link_find("d1/d2/f4").unwrap(), Some(f4)); | ||
| 719 | assert_eq!(depot.link_find("d3").unwrap(), Some(d3)); | ||
| 720 | |||
| 721 | assert_eq!(depot.link_find("d5").unwrap(), None); | ||
| 722 | assert_eq!(depot.link_find("d3/d5").unwrap(), None); | ||
| 723 | assert_eq!(depot.link_find("d1/d2/f5").unwrap(), None); | ||
| 724 | } | ||
| 725 | |||
| 726 | #[test] | ||
| 727 | fn test_depot_links_under() { | ||
| 728 | let mut depot = Depot::default(); | ||
| 729 | let f1 = depot.link_create("d1/f1", "d1/f1").unwrap(); | ||
| 730 | let f2 = depot.link_create("d1/f2", "d1/f2").unwrap(); | ||
| 731 | let f3 = depot.link_create("d1/f3", "d1/f3").unwrap(); | ||
| 732 | let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap(); | ||
| 733 | let d3 = depot.link_create("d3", "d3").unwrap(); | ||
| 734 | |||
| 735 | let under_f1 = depot.links_under("d1/f1").unwrap().collect::<Vec<_>>(); | ||
| 736 | assert_eq!(under_f1, vec![f1]); | ||
| 737 | |||
| 738 | let under_d1 = depot.links_under("d1").unwrap().collect::<Vec<_>>(); | ||
| 739 | let expected_under_d1 = vec![f1, f2, f3, f4]; | ||
| 740 | assert!( | ||
| 741 | under_d1.len() == expected_under_d1.len() | ||
| 742 | && expected_under_d1.iter().all(|x| under_d1.contains(x)) | ||
| 743 | ); | ||
| 744 | |||
| 745 | let under_d2 = depot.links_under("d2").unwrap().collect::<Vec<_>>(); | ||
| 746 | assert_eq!(under_d2, vec![]); | ||
| 747 | |||
| 748 | let under_d3 = depot.links_under("d3").unwrap().collect::<Vec<_>>(); | ||
| 749 | assert_eq!(under_d3, vec![d3]); | ||
| 750 | |||
| 751 | let under_root = depot.links_under("").unwrap().collect::<Vec<_>>(); | ||
| 752 | let expected_under_root = vec![f1, f2, f3, f4, d3]; | ||
| 753 | assert!( | ||
| 754 | under_root.len() == expected_under_root.len() | ||
| 755 | && expected_under_root.iter().all(|x| under_root.contains(x)) | ||
| 756 | ); | ||
| 757 | } | ||
| 758 | |||
| 759 | #[test] | ||
| 760 | fn test_depot_has_links_under() { | ||
| 761 | let mut depot = Depot::default(); | ||
| 762 | let _f1 = depot.link_create("d1/f1", "d1/f1").unwrap(); | ||
| 763 | let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap(); | ||
| 764 | let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap(); | ||
| 765 | let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap(); | ||
| 766 | let _d3 = depot.link_create("d3", "d3").unwrap(); | ||
| 767 | |||
| 768 | assert!(depot.has_links_under("").unwrap()); | ||
| 769 | assert!(depot.has_links_under("d1").unwrap()); | ||
| 770 | assert!(depot.has_links_under("d3").unwrap()); | ||
| 771 | assert!(depot.has_links_under("d1/f1").unwrap()); | ||
| 772 | assert!(depot.has_links_under("d1/d2").unwrap()); | ||
| 773 | assert!(depot.has_links_under("d1/d2/f4").unwrap()); | ||
| 774 | |||
| 775 | assert!(!depot.has_links_under("d2").unwrap()); | ||
| 776 | assert!(!depot.has_links_under("d4").unwrap()); | ||
| 777 | assert!(!depot.has_links_under("d1/d2/f4/f5").unwrap()); | ||
| 778 | } | ||
| 779 | |||
| 780 | #[test] | ||
| 781 | fn test_depot_read_dir() { | ||
| 782 | let mut depot = Depot::default(); | ||
| 783 | let f1 = depot.link_create("d1/f1", "d1/f1").unwrap(); | ||
| 784 | let f2 = depot.link_create("d1/f2", "d1/f2").unwrap(); | ||
| 785 | let f3 = depot.link_create("d1/f3", "d1/f3").unwrap(); | ||
| 786 | let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap(); | ||
| 787 | let _d3 = depot.link_create("d3", "d3").unwrap(); | ||
| 788 | |||
| 789 | let read_dir = depot.read_dir("d1").unwrap().collect::<Vec<_>>(); | ||
| 790 | let expected_read_dir = vec![ | ||
| 791 | DirNode::Link(f1), | ||
| 792 | DirNode::Link(f2), | ||
| 793 | DirNode::Link(f3), | ||
| 794 | DirNode::Directory(PathBuf::from("d1/d2")), | ||
| 795 | ]; | ||
| 796 | assert!( | ||
| 797 | read_dir.len() == expected_read_dir.len() | ||
| 798 | && expected_read_dir.iter().all(|x| read_dir.contains(x)) | ||
| 799 | ); | ||
| 800 | } | ||
| 801 | |||
| 802 | #[test] | ||
| 803 | fn test_path_verify() { | ||
| 804 | path_verify(Path::new("")).unwrap(); | ||
| 805 | path_verify(Path::new("f1")).unwrap(); | ||
| 806 | path_verify(Path::new("d1/f1")).unwrap(); | ||
| 807 | path_verify(Path::new("d1/f1.txt")).unwrap(); | ||
| 808 | path_verify(Path::new("d1/./f1.txt")).unwrap(); | ||
| 809 | |||
| 810 | path_verify(Path::new("/")).unwrap_err(); | ||
| 811 | path_verify(Path::new("./f1")).unwrap_err(); | ||
| 812 | path_verify(Path::new("/d1/f1")).unwrap_err(); | ||
| 813 | path_verify(Path::new("d1/../f1.txt")).unwrap_err(); | ||
| 814 | path_verify(Path::new("/d1/../f1.txt")).unwrap_err(); | ||
| 815 | } | ||
| 816 | |||
| 817 | #[test] | ||
| 818 | fn test_path_verify_link() { | ||
| 819 | path_verify_link(Path::new("f1")).unwrap(); | ||
| 820 | path_verify_link(Path::new("d1/f1")).unwrap(); | ||
| 821 | path_verify_link(Path::new("d1/f1.txt")).unwrap(); | ||
| 822 | path_verify_link(Path::new("d1/./f1.txt")).unwrap(); | ||
| 823 | |||
| 824 | path_verify_link(Path::new("")).unwrap_err(); | ||
| 825 | path_verify_link(Path::new("/")).unwrap_err(); | ||
| 826 | path_verify_link(Path::new("./f1")).unwrap_err(); | ||
| 827 | path_verify_link(Path::new("/d1/f1")).unwrap_err(); | ||
| 828 | path_verify_link(Path::new("d1/../f1.txt")).unwrap_err(); | ||
| 829 | path_verify_link(Path::new("/d1/../f1.txt")).unwrap_err(); | ||
| 830 | } | ||
| 831 | |||
| 832 | #[test] | ||
| 833 | fn test_path_iter_comps() { | ||
| 834 | let path = Path::new("comp1/comp2/./comp3/file.txt"); | ||
| 835 | let mut iter = path_iter_comps(path); | ||
| 836 | assert_eq!(iter.next(), Some(OsStr::new("comp1"))); | ||
| 837 | assert_eq!(iter.next(), Some(OsStr::new("comp2"))); | ||
| 838 | assert_eq!(iter.next(), Some(OsStr::new("comp3"))); | ||
| 839 | assert_eq!(iter.next(), Some(OsStr::new("file.txt"))); | ||
| 840 | assert_eq!(iter.next(), None); | ||
| 841 | } | ||
| 842 | } | ||
| 843 | } | ||
| 844 | |||
| 845 | pub mod depot2 { | ||
| 6 | use std::{ | 846 | use std::{ |
| 7 | collections::HashSet, | 847 | collections::HashSet, |
| 8 | ffi::{OsStr, OsString}, | 848 | ffi::{OsStr, OsString}, |
| @@ -933,19 +1773,16 @@ pub mod dotup { | |||
| 933 | } | 1773 | } |
| 934 | } | 1774 | } |
| 935 | 1775 | ||
| 936 | pub fn unlink(&mut self, paths: impl Iterator<Item = impl AsRef<Path>>) { | 1776 | pub fn unlink(&mut self, paths: impl Iterator<Item = impl AsRef<Path>>, uninstall: bool) { |
| 937 | for origin in paths { | 1777 | for origin in paths { |
| 938 | let unlink_result: anyhow::Result<()> = try { | 1778 | let unlink_result: anyhow::Result<()> = try { |
| 939 | let origin = self.prepare_relative_path(origin.as_ref())?; | 1779 | let origin = self.prepare_relative_path(origin.as_ref())?; |
| 940 | let search_results = self.depot.link_search(&origin)?; | 1780 | let links_under: Vec<_> = self.depot.links_under(&origin)?.collect(); |
| 941 | match search_results { | 1781 | for link_id in links_under { |
| 942 | depot::SearchResult::Found(link_id) => { | 1782 | if uninstall && self.symlink_is_installed_by_link_id(link_id)? { |
| 943 | self.depot.link_remove(link_id); | 1783 | self.symlink_uninstall_by_link_id(link_id)?; |
| 944 | println!("removed link {}", origin.display()); | ||
| 945 | } | ||
| 946 | depot::SearchResult::Ancestor(_) | depot::SearchResult::NotFound => { | ||
| 947 | println!("{} is not linked", origin.display()) | ||
| 948 | } | 1784 | } |
| 1785 | self.depot.link_remove(link_id); | ||
| 949 | } | 1786 | } |
| 950 | }; | 1787 | }; |
| 951 | match unlink_result { | 1788 | match unlink_result { |
| @@ -1149,7 +1986,7 @@ pub mod dotup { | |||
| 1149 | } else if is_installed { | 1986 | } else if is_installed { |
| 1150 | Color::Green | 1987 | Color::Green |
| 1151 | } else { | 1988 | } else { |
| 1152 | Color::Cyan | 1989 | Color::RGB(255, 127, 0) |
| 1153 | } | 1990 | } |
| 1154 | } | 1991 | } |
| 1155 | 1992 | ||
| @@ -1301,6 +2138,11 @@ pub mod dotup { | |||
| 1301 | Ok(()) | 2138 | Ok(()) |
| 1302 | } | 2139 | } |
| 1303 | 2140 | ||
| 2141 | fn symlink_uninstall_by_link_id(&self, link_id: LinkID) -> anyhow::Result<()> { | ||
| 2142 | let canonical_pair = self.canonical_pair_from_link_id(link_id); | ||
| 2143 | self.symlink_uninstall(&canonical_pair.origin, &canonical_pair.destination) | ||
| 2144 | } | ||
| 2145 | |||
| 1304 | fn canonical_pair_from_link_id(&self, link_id: LinkID) -> CanonicalPair { | 2146 | fn canonical_pair_from_link_id(&self, link_id: LinkID) -> CanonicalPair { |
| 1305 | let link_view = self.depot.link_view(link_id); | 2147 | let link_view = self.depot.link_view(link_id); |
| 1306 | let relative_origin = link_view.origin(); | 2148 | let relative_origin = link_view.origin(); |
| @@ -1336,7 +2178,10 @@ pub mod dotup { | |||
| 1336 | } | 2178 | } |
| 1337 | 2179 | ||
| 1338 | mod utils { | 2180 | mod utils { |
| 1339 | use std::path::{Component, Path, PathBuf}; | 2181 | use std::{ |
| 2182 | collections::VecDeque, | ||
| 2183 | path::{Component, Path, PathBuf}, | ||
| 2184 | }; | ||
| 1340 | 2185 | ||
| 1341 | use crate::{ | 2186 | use crate::{ |
| 1342 | dotup::{self, Dotup}, | 2187 | dotup::{self, Dotup}, |
| @@ -1345,6 +2190,29 @@ mod utils { | |||
| 1345 | 2190 | ||
| 1346 | pub const DEFAULT_DEPOT_FILE_NAME: &str = ".depot"; | 2191 | pub const DEFAULT_DEPOT_FILE_NAME: &str = ".depot"; |
| 1347 | 2192 | ||
| 2193 | /// Returns a list of canonical paths to all the files in `dir`. This includes files in | ||
| 2194 | /// subdirectories. | ||
| 2195 | /// Fails if dir isnt a directory or if there is some other io error. | ||
| 2196 | pub fn collect_files_in_dir_recursive(dir: impl Into<PathBuf>) -> anyhow::Result<Vec<PathBuf>> { | ||
| 2197 | let mut paths = Vec::new(); | ||
| 2198 | let mut dirs = VecDeque::new(); | ||
| 2199 | dirs.push_back(dir.into()); | ||
| 2200 | |||
| 2201 | while let Some(dir) = dirs.pop_front() { | ||
| 2202 | for entry in std::fs::read_dir(dir)? { | ||
| 2203 | let entry = entry?; | ||
| 2204 | let filetype = entry.file_type()?; | ||
| 2205 | if filetype.is_dir() { | ||
| 2206 | dirs.push_back(entry.path()); | ||
| 2207 | } else { | ||
| 2208 | paths.push(entry.path()); | ||
| 2209 | } | ||
| 2210 | } | ||
| 2211 | } | ||
| 2212 | |||
| 2213 | Ok(paths) | ||
| 2214 | } | ||
| 2215 | |||
| 1348 | pub fn collect_paths_in_dir(dir: impl AsRef<Path>) -> anyhow::Result<Vec<PathBuf>> { | 2216 | pub fn collect_paths_in_dir(dir: impl AsRef<Path>) -> anyhow::Result<Vec<PathBuf>> { |
| 1349 | Ok(std::fs::read_dir(dir)? | 2217 | Ok(std::fs::read_dir(dir)? |
| 1350 | .filter_map(|e| e.ok()) | 2218 | .filter_map(|e| e.ok()) |
| @@ -1522,6 +2390,7 @@ fn main() -> anyhow::Result<()> { | |||
| 1522 | 2 => "debug", | 2390 | 2 => "debug", |
| 1523 | _ => "trace", | 2391 | _ => "trace", |
| 1524 | }; | 2392 | }; |
| 2393 | let log_level = "trace"; | ||
| 1525 | 2394 | ||
| 1526 | Logger::try_with_env_or_str(log_level)? | 2395 | Logger::try_with_env_or_str(log_level)? |
| 1527 | .format(flexi_logger::colored_default_format) | 2396 | .format(flexi_logger::colored_default_format) |
| @@ -1565,31 +2434,48 @@ fn command_init(_global_flags: Flags, args: InitArgs) -> anyhow::Result<()> { | |||
| 1565 | 2434 | ||
| 1566 | #[derive(Parser, Debug)] | 2435 | #[derive(Parser, Debug)] |
| 1567 | struct LinkArgs { | 2436 | struct LinkArgs { |
| 2437 | #[clap(long)] | ||
| 2438 | directory: bool, | ||
| 2439 | |||
| 1568 | origin: PathBuf, | 2440 | origin: PathBuf, |
| 2441 | |||
| 1569 | destination: PathBuf, | 2442 | destination: PathBuf, |
| 1570 | } | 2443 | } |
| 1571 | 2444 | ||
| 1572 | fn command_link(global_flags: Flags, args: LinkArgs) -> anyhow::Result<()> { | 2445 | fn command_link(global_flags: Flags, args: LinkArgs) -> anyhow::Result<()> { |
| 1573 | let mut dotup = utils::read_dotup(&global_flags)?; | 2446 | let mut dotup = utils::read_dotup(&global_flags)?; |
| 1574 | dotup.link(args.origin, args.destination); | 2447 | let origins = if args.directory { |
| 2448 | vec![args.origin] | ||
| 2449 | } else { | ||
| 2450 | utils::collect_files_in_dir_recursive(args.origin)? | ||
| 2451 | }; | ||
| 2452 | for origin in origins { | ||
| 2453 | dotup.link(origin, &args.destination); | ||
| 2454 | } | ||
| 1575 | utils::write_dotup(&dotup)?; | 2455 | utils::write_dotup(&dotup)?; |
| 1576 | Ok(()) | 2456 | Ok(()) |
| 1577 | } | 2457 | } |
| 1578 | 2458 | ||
| 1579 | #[derive(Parser, Debug)] | 2459 | #[derive(Parser, Debug)] |
| 1580 | struct UnlinkArgs { | 2460 | struct UnlinkArgs { |
| 2461 | #[clap(long)] | ||
| 2462 | uninstall: bool, | ||
| 2463 | |||
| 1581 | paths: Vec<PathBuf>, | 2464 | paths: Vec<PathBuf>, |
| 1582 | } | 2465 | } |
| 1583 | 2466 | ||
| 1584 | fn command_unlink(global_flags: Flags, args: UnlinkArgs) -> anyhow::Result<()> { | 2467 | fn command_unlink(global_flags: Flags, args: UnlinkArgs) -> anyhow::Result<()> { |
| 1585 | let mut dotup = utils::read_dotup(&global_flags)?; | 2468 | let mut dotup = utils::read_dotup(&global_flags)?; |
| 1586 | dotup.unlink(args.paths.into_iter()); | 2469 | dotup.unlink(args.paths.into_iter(), args.uninstall); |
| 1587 | utils::write_dotup(&dotup)?; | 2470 | utils::write_dotup(&dotup)?; |
| 1588 | Ok(()) | 2471 | Ok(()) |
| 1589 | } | 2472 | } |
| 1590 | 2473 | ||
| 1591 | #[derive(Parser, Debug)] | 2474 | #[derive(Parser, Debug)] |
| 1592 | struct InstallArgs { | 2475 | struct InstallArgs { |
| 2476 | #[clap(long)] | ||
| 2477 | directory: bool, | ||
| 2478 | |||
| 1593 | paths: Vec<PathBuf>, | 2479 | paths: Vec<PathBuf>, |
| 1594 | } | 2480 | } |
| 1595 | 2481 | ||
