From e5a38bab67f790803ff98484fc5835adba7bf62a Mon Sep 17 00:00:00 2001 From: diogo464 Date: Fri, 23 Sep 2022 13:45:57 +0100 Subject: rewrite --- src/dotup.rs | 593 ----------------------------------------------------------- 1 file changed, 593 deletions(-) delete mode 100644 src/dotup.rs (limited to 'src/dotup.rs') diff --git a/src/dotup.rs b/src/dotup.rs deleted file mode 100644 index 8de7920..0000000 --- a/src/dotup.rs +++ /dev/null @@ -1,593 +0,0 @@ -use std::{ - cmp::Ordering, - collections::HashSet, - path::{Path, PathBuf}, -}; - -use ansi_term::Color; -use anyhow::Context; - -use crate::{ - depot::{self, Depot, DirNode, LinkID}, - utils, -}; - -#[derive(Debug)] -struct CanonicalPair { - origin: PathBuf, - destination: PathBuf, -} - -#[derive(Debug, Clone)] -enum StatusItem { - Link { - origin: PathBuf, - destination: PathBuf, - is_directory: bool, - }, - Directory { - origin: PathBuf, - items: Vec, - }, - Unlinked { - origin: PathBuf, - is_directory: bool, - }, -} - -impl StatusItem { - fn display_ord_cmp(&self, other: &Self) -> Ordering { - match (self, other) { - ( - StatusItem::Link { - origin: l_origin, .. - }, - StatusItem::Link { - origin: r_origin, .. - }, - ) => l_origin.cmp(r_origin), - (StatusItem::Link { .. }, StatusItem::Directory { .. }) => Ordering::Less, - ( - StatusItem::Link { - is_directory: l_is_dir, - .. - }, - StatusItem::Unlinked { - is_directory: u_is_dir, - .. - }, - ) => { - if *u_is_dir && !*l_is_dir { - Ordering::Less - } else { - Ordering::Greater - } - } - (StatusItem::Directory { .. }, StatusItem::Link { .. }) => Ordering::Greater, - ( - StatusItem::Directory { - origin: l_origin, .. - }, - StatusItem::Directory { - origin: r_origin, .. - }, - ) => l_origin.cmp(r_origin), - (StatusItem::Directory { .. }, StatusItem::Unlinked { .. }) => Ordering::Greater, - ( - StatusItem::Unlinked { - is_directory: u_is_dir, - .. - }, - StatusItem::Link { - is_directory: l_is_dir, - .. - }, - ) => { - if *u_is_dir && !*l_is_dir { - Ordering::Greater - } else { - Ordering::Less - } - } - (StatusItem::Unlinked { .. }, StatusItem::Directory { .. }) => Ordering::Less, - ( - StatusItem::Unlinked { - origin: l_origin, .. - }, - StatusItem::Unlinked { - origin: r_origin, .. - }, - ) => l_origin.cmp(r_origin), - } - } -} - -#[derive(Debug)] -pub struct Dotup { - depot: Depot, - depot_dir: PathBuf, - depot_path: PathBuf, - install_base: PathBuf, -} - -impl Dotup { - fn new(depot: Depot, depot_path: PathBuf, install_base: PathBuf) -> anyhow::Result { - assert!(depot_path.is_absolute()); - assert!(depot_path.is_file()); - assert!(install_base.is_absolute()); - assert!(install_base.is_dir()); - let depot_dir = { - let mut d = depot_path.clone(); - d.pop(); - d - }; - Ok(Self { - depot, - depot_dir, - depot_path, - install_base, - }) - } - - pub fn link(&mut self, origin: impl AsRef, destination: impl AsRef) { - let link_result: anyhow::Result<()> = try { - let origin = self.prepare_relative_origin(origin.as_ref())?; - let destination_ends_with_slash = utils::path_ends_with_slash(destination.as_ref()); - let mut destination = self.prepare_relative_destination(destination.as_ref())?; - if destination_ends_with_slash { - if let Some(filename) = origin.file_name() { - destination.push(filename); - } - } - self.depot.link_create(origin, destination)?; - }; - match link_result { - Ok(_) => {} - Err(e) => println!("Failed to create link : {e}"), - } - } - - pub fn unlink(&mut self, paths: impl Iterator>, uninstall: bool) { - for origin in paths { - let unlink_result: anyhow::Result<()> = try { - let origin = self.prepare_relative_origin(origin.as_ref())?; - let links_under: Vec<_> = self.depot.links_under(&origin)?.collect(); - for link_id in links_under { - if uninstall && self.symlink_is_installed_by_link_id(link_id)? { - self.symlink_uninstall_by_link_id(link_id)?; - } - self.depot.link_remove(link_id); - } - }; - match unlink_result { - Ok(_) => {} - Err(e) => println!("Failed to unlink {} : {e}", origin.as_ref().display()), - } - } - } - - pub fn install(&self, paths: impl Iterator>) { - let install_result: anyhow::Result<()> = try { - let link_ids = self.link_ids_from_paths_iter(paths)?; - self.depot.links_verify_install(link_ids.iter().copied())?; - - for link_id in link_ids { - self.symlink_install_by_link_id(link_id)?; - } - }; - if let Err(e) = install_result { - println!("error while installing : {e}"); - } - } - - pub fn uninstall(&self, paths: impl Iterator>) { - let uninstall_result: anyhow::Result<()> = try { - let link_ids = self.link_ids_from_paths_iter(paths)?; - for link_id in link_ids { - if self.symlink_is_installed_by_link_id(link_id)? { - self.symlink_uninstall_by_link_id(link_id)?; - } - } - }; - if let Err(e) = uninstall_result { - println!("error while uninstalling {e}",); - } - } - - pub fn mv( - &mut self, - origins: impl Iterator>, - destination: impl AsRef, - ) { - let mv_result: anyhow::Result<()> = try { - let origins = { - let mut v = Vec::new(); - for origin in origins { - v.push( - origin - .as_ref() - .canonicalize() - .context("failed to canonicalize origin path")?, - ); - } - v - }; - let destination = utils::weakly_canonical(destination.as_ref()); - log::debug!("mv destination : {}", destination.display()); - - // if we are moving multiple links then the destination must be a directory - if origins.len() > 1 && !destination.is_dir() { - println!("destination must be a directory"); - return; - } - - for origin in origins { - let destination = if destination.is_dir() { - // unwrap: origin must have a filename - destination.join(origin.file_name().unwrap()) - } else { - destination.to_owned() - }; - self.mv_one(&origin, &destination)?; - } - }; - if let Err(e) = mv_result { - println!("error moving : {e}"); - } - } - - fn mv_one(&mut self, origin: &Path, destination: &Path) -> anyhow::Result<()> { - log::debug!("mv_one : {} to {}", origin.display(), destination.display()); - - let relative_origin = self.prepare_relative_origin(origin)?; - let relative_destination = self.prepare_relative_origin(destination)?; - match self.depot.link_find(&relative_origin)? { - Some(link_id) => { - let is_installed = self.symlink_is_installed_by_link_id(link_id)?; - let original_origin = self.depot.link_view(link_id).origin().to_owned(); - log::debug!("is_installed = {is_installed}",); - log::debug!("original_origin = {}", original_origin.display()); - log::debug!("link_destination = {}", relative_destination.display()); - - self.depot.link_move(link_id, relative_destination)?; - if let Err(e) = std::fs::rename(origin, destination).context("Failed to move file") - { - // unwrap: moving the link back to its origin place has to work - self.depot.link_move(link_id, original_origin).unwrap(); - return Err(e); - } - // reinstall because we just moved the origin - if is_installed { - self.symlink_install_by_link_id(link_id) - .context("failed to reinstall link while moving")?; - } - } - None => { - if origin.is_dir() { - let mut links_installed: HashSet<_> = Default::default(); - if self.depot.has_links_under(&relative_origin)? { - let links_under: Vec<_> = - self.depot.links_under(&relative_origin)?.collect(); - for &link_id in links_under.iter() { - let link_view = self.depot.link_view(link_id); - if self.symlink_is_installed_by_link_id(link_id)? { - links_installed.insert(link_id); - } - // unwrap: the link is under `origin` so stripping the prefix should - // not fail - let origin_extra = - link_view.origin().strip_prefix(&relative_origin).unwrap(); - let new_destination = relative_destination.join(origin_extra); - self.depot.link_move(link_id, new_destination)?; - } - } - std::fs::rename(origin, destination)?; - for link_id in links_installed { - self.symlink_install_by_link_id(link_id)?; - } - } else { - std::fs::rename(origin, destination)?; - } - } - } - Ok(()) - } - - pub fn status(&self, paths: impl Iterator>) { - let status_result: anyhow::Result<()> = try { - // canonicalize and remove paths whose parent we already have - let paths = paths.map(utils::weakly_canonical).collect::>(); - let paths = paths - .iter() - .filter(|p| !paths.iter().any(|x| p.starts_with(x) && p != &x)); - - for path in paths { - let item = self.status_path_to_item(path)?; - self.status_print_item(item, 0)?; - } - }; - if let Err(e) = status_result { - println!("error while displaying status : {e}"); - } - } - fn status_path_to_item(&self, canonical_path: &Path) -> anyhow::Result { - debug_assert!(canonical_path.is_absolute()); - debug_assert!(canonical_path.exists()); - let relative_path = self.prepare_relative_origin(canonical_path)?; - - let item = if canonical_path.is_dir() { - if let Some(link_id) = self.depot.link_find(&relative_path)? { - let destination = self.depot.link_view(link_id).destination().to_owned(); - StatusItem::Link { - origin: relative_path, - destination, - is_directory: true, - } - } else if self.depot.has_links_under(&relative_path)? { - let mut items = Vec::new(); - let mut collected_rel_paths = HashSet::::new(); - let directory_paths = utils::collect_paths_in_dir(&canonical_path)?; - for canonical_item_path in directory_paths { - let item = self.status_path_to_item(&canonical_item_path)?; - match &item { - StatusItem::Link { origin, .. } | StatusItem::Directory { origin, .. } => { - collected_rel_paths.insert(origin.to_owned()); - } - _ => {} - } - items.push(item); - } - - for dir_node in self.depot.read_dir(&relative_path)? { - match dir_node { - DirNode::Link(link_id) => { - let link_view = self.depot.link_view(link_id); - let link_rel_path = link_view.origin(); - let link_rel_dest = link_view.destination(); - if !collected_rel_paths.contains(link_rel_path) { - items.push(StatusItem::Link { - origin: link_rel_path.to_owned(), - destination: link_rel_dest.to_owned(), - is_directory: false, - }); - } - } - DirNode::Directory(_) => {} - } - } - - StatusItem::Directory { - origin: relative_path, - items, - } - } else { - StatusItem::Unlinked { - origin: relative_path, - is_directory: true, - } - } - } else if let Some(link_id) = self.depot.link_find(&relative_path)? { - let destination = self.depot.link_view(link_id).destination().to_owned(); - StatusItem::Link { - origin: relative_path, - destination, - is_directory: false, - } - } else { - StatusItem::Unlinked { - origin: relative_path, - is_directory: false, - } - }; - Ok(item) - } - fn status_print_item(&self, item: StatusItem, depth: u32) -> anyhow::Result<()> { - fn print_depth(d: u32) { - for _ in 0..d.saturating_sub(1) { - print!(" "); - } - } - fn origin_color(exists: bool, is_installed: bool) -> Color { - if !exists { - Color::Red - } else if is_installed { - Color::Green - } else { - Color::RGB(255, 127, 0) - } - } - - let destination_color = Color::Blue; - - print_depth(depth); - match item { - StatusItem::Link { - origin, - destination, - is_directory, - } => { - let canonical_origin = self.depot_dir.join(&origin); - let canonical_destination = self.install_base.join(&destination); - let file_name = Self::status_get_filename(&canonical_origin); - let is_installed = - self.symlink_is_installed(&canonical_origin, &canonical_destination)?; - let exists = canonical_origin.exists(); - let origin_color = origin_color(exists, is_installed); - let directory_extra = if is_directory { "/" } else { "" }; - println!( - "{}{} -> {}", - origin_color.paint(file_name), - directory_extra, - destination_color.paint(destination.display().to_string()) - ); - } - StatusItem::Directory { origin, mut items } => { - items.sort_by(|a, b| StatusItem::display_ord_cmp(a, b).reverse()); - let directory_name = Self::status_get_filename(&origin); - if depth != 0 { - println!("{}/", directory_name); - } - for item in items { - self.status_print_item(item, depth + 1)?; - } - } - StatusItem::Unlinked { - origin, - is_directory, - } => { - let file_name = Self::status_get_filename(&origin); - let directory_extra = if is_directory { "/" } else { "" }; - println!("{}{}", file_name, directory_extra); - } - } - Ok(()) - } - fn status_get_filename(path: &Path) -> &str { - path.file_name() - .and_then(|s| s.to_str()) - .unwrap_or_default() - } - - fn prepare_relative_path(path: &Path, base: &Path) -> anyhow::Result { - let canonical = utils::weakly_canonical(path); - let relative = canonical - .strip_prefix(base) - .context("Invalid origin path, not under depot directory")?; - Ok(relative.to_owned()) - } - - fn prepare_relative_origin(&self, path: &Path) -> anyhow::Result { - Self::prepare_relative_path(path, &self.depot_dir) - } - - fn prepare_relative_destination(&self, path: &Path) -> anyhow::Result { - Self::prepare_relative_path(path, &self.install_base) - } - - fn link_ids_from_paths_iter( - &self, - paths: impl Iterator>, - ) -> anyhow::Result> { - let mut link_ids = HashSet::::default(); - for path in paths { - let path = self.prepare_relative_origin(path.as_ref())?; - link_ids.extend(self.depot.links_under(&path)?); - } - Ok(Vec::from_iter(link_ids.into_iter())) - } - - fn symlink_is_installed_by_link_id(&self, link_id: LinkID) -> anyhow::Result { - let canonical_pair = self.canonical_pair_from_link_id(link_id); - self.symlink_is_installed(&canonical_pair.origin, &canonical_pair.destination) - } - - fn symlink_is_installed(&self, origin: &Path, destination: &Path) -> anyhow::Result { - debug_assert!(origin.is_absolute()); - debug_assert!(destination.is_absolute()); - - if destination.is_symlink() { - let symlink_destination = destination.read_link()?; - match symlink_destination.canonicalize() { - Ok(canonicalized) => Ok(origin == canonicalized), - Err(_) => Ok(false), - } - } else { - Ok(false) - } - } - - fn symlink_install_by_link_id(&self, link_id: LinkID) -> anyhow::Result<()> { - let canonical_pair = self.canonical_pair_from_link_id(link_id); - self.symlink_install(&canonical_pair.origin, &canonical_pair.destination) - } - - fn symlink_install(&self, origin: &Path, destination: &Path) -> anyhow::Result<()> { - debug_assert!(origin.is_absolute()); - debug_assert!(destination.is_absolute()); - log::debug!( - "symlink_install : {} -> {}", - origin.display(), - destination.display() - ); - - let destination_parent = destination - .parent() - .ok_or_else(|| anyhow::anyhow!("destination has no parent component"))?; - std::fs::create_dir_all(destination_parent).context("Failed to create directories")?; - // need to do this beacause if the destination path ends in '/' because the symlink - // functions will treat it as a directory but we want a file with that name. - let destination = destination.with_file_name(destination.file_name().unwrap()); - - let destination_exists = destination.exists(); - let destination_is_symlink = destination.is_symlink(); - - if destination_exists && !destination_is_symlink { - return Err(anyhow::anyhow!("destination already exists")); - } - - if destination_is_symlink { - log::debug!("symlink already exists, removing before recreating"); - std::fs::remove_file(&destination)?; - } - - log::debug!( - "creating filesystem symlink {} -> {}", - origin.display(), - destination.display() - ); - std::os::unix::fs::symlink(origin, destination).context("failed to create symlink")?; - - Ok(()) - } - - fn symlink_uninstall(&self, origin: &Path, destination: &Path) -> anyhow::Result<()> { - debug_assert!(origin.is_absolute()); - debug_assert!(destination.is_absolute()); - let destination = destination.with_file_name(destination.file_name().unwrap()); - - if destination.is_symlink() { - let symlink_destination = destination.read_link()?.canonicalize()?; - if symlink_destination == origin { - std::fs::remove_file(&destination)?; - } - } - - Ok(()) - } - - fn symlink_uninstall_by_link_id(&self, link_id: LinkID) -> anyhow::Result<()> { - let canonical_pair = self.canonical_pair_from_link_id(link_id); - self.symlink_uninstall(&canonical_pair.origin, &canonical_pair.destination) - } - - fn canonical_pair_from_link_id(&self, link_id: LinkID) -> CanonicalPair { - let link_view = self.depot.link_view(link_id); - let relative_origin = link_view.origin(); - let relative_destination = link_view.destination(); - let canonical_origin = self.depot_dir.join(relative_origin); - let canonical_destination = self.install_base.join(relative_destination); - CanonicalPair { - origin: canonical_origin, - destination: canonical_destination, - } - } -} - -pub fn read(depot_path: PathBuf, install_base: PathBuf) -> anyhow::Result { - let depot_path = depot_path - .canonicalize() - .context("Failed to canonicalize depot path")?; - let install_base = install_base - .canonicalize() - .context("Failed to canonicalize install base")?; - if !install_base.is_dir() { - return Err(anyhow::anyhow!("Install base must be a directory")); - } - let depot = depot::read(&depot_path)?; - Dotup::new(depot, depot_path, install_base) -} - -pub fn write(dotup: &Dotup) -> anyhow::Result<()> { - depot::write(&dotup.depot_path, &dotup.depot)?; - Ok(()) -} -- cgit