From 1f3fad2322bc1cf8fa47bd1c6bbea3ea8058fd2e Mon Sep 17 00:00:00 2001 From: Lauri Niskanen Date: Sat, 6 Jul 2024 21:03:12 +0300 Subject: [PATCH] Add '--hard' option to 'link' command Resolves #1258. --- yazi-core/src/manager/commands/link.rs | 7 ++-- yazi-core/src/tasks/file.rs | 4 +-- yazi-scheduler/src/file/file.rs | 45 ++++++++++++++++++++------ yazi-scheduler/src/file/op.rs | 2 ++ yazi-scheduler/src/scheduler.rs | 13 ++++++-- 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/yazi-core/src/manager/commands/link.rs b/yazi-core/src/manager/commands/link.rs index 4a3857b79..7011c5468 100644 --- a/yazi-core/src/manager/commands/link.rs +++ b/yazi-core/src/manager/commands/link.rs @@ -4,11 +4,14 @@ use crate::{manager::Manager, tasks::Tasks}; pub struct Opt { relative: bool, + hard: bool, force: bool, } impl From for Opt { - fn from(c: Cmd) -> Self { Self { relative: c.bool("relative"), force: c.bool("force") } } + fn from(c: Cmd) -> Self { + Self { relative: c.bool("relative"), hard: c.bool("hard"), force: c.bool("force") } + } } impl Manager { @@ -18,6 +21,6 @@ impl Manager { } let opt = opt.into() as Opt; - tasks.file_link(&self.yanked, self.cwd(), opt.relative, opt.force); + tasks.file_link(&self.yanked, self.cwd(), opt.relative, opt.hard, opt.force); } } diff --git a/yazi-core/src/tasks/file.rs b/yazi-core/src/tasks/file.rs index 7e2e2fe38..5be72ad9c 100644 --- a/yazi-core/src/tasks/file.rs +++ b/yazi-core/src/tasks/file.rs @@ -28,13 +28,13 @@ impl Tasks { } } - pub fn file_link(&self, src: &HashSet, dest: &Url, relative: bool, force: bool) { + pub fn file_link(&self, src: &HashSet, dest: &Url, relative: bool, hard: bool, force: bool) { for u in src { let to = dest.join(u.file_name().unwrap()); if force && *u == to { debug!("file_link: same file, skipping {:?}", to); } else { - self.scheduler.file_link(u.clone(), to, relative, force); + self.scheduler.file_link(u.clone(), to, relative, hard, force); } } } diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index a68f0ac06..d816d7302 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -81,16 +81,25 @@ impl File { }; ok_or_not_found(fs::remove_file(&task.to).await)?; - #[cfg(unix)] - { - fs::symlink(src, &task.to).await? - } - #[cfg(windows)] - { - if meta.is_dir() { - fs::symlink_dir(src, &task.to).await? + + if task.hard { + if src.is_dir() { + hard_link_directory(src, &task.to).await?; } else { - fs::symlink_file(src, &task.to).await? + fs::hard_link(src, &task.to).await?; + } + } else { + #[cfg(unix)] + { + fs::symlink(src, &task.to).await? + } + #[cfg(windows)] + { + if meta.is_dir() { + fs::symlink_dir(src, &task.to).await? + } else { + fs::symlink_file(src, &task.to).await? + } } } @@ -276,6 +285,24 @@ impl File { } } +async fn hard_link_directory(src: impl AsRef, dst: impl AsRef) -> Result<()> { + fs::create_dir(&dst).await?; + + let mut directory = fs::read_dir(src).await?; + + while let Some(entry) = directory.next_entry().await? { + let target_path = dst.as_ref().join(entry.file_name()); + + if entry.file_type().await?.is_dir() { + Box::pin(hard_link_directory(entry.path(), target_path)).await?; + } else { + fs::hard_link(entry.path(), target_path).await?; + } + } + + Ok(()) +} + impl File { #[inline] fn succ(&self, id: usize) -> Result<()> { Ok(self.prog.send(TaskProg::Succ(id))?) } diff --git a/yazi-scheduler/src/file/op.rs b/yazi-scheduler/src/file/op.rs index 3eabec69e..b1692c7b9 100644 --- a/yazi-scheduler/src/file/op.rs +++ b/yazi-scheduler/src/file/op.rs @@ -54,6 +54,7 @@ pub struct FileOpLink { pub meta: Option, pub resolve: bool, pub relative: bool, + pub hard: bool, pub delete: bool, } @@ -66,6 +67,7 @@ impl From for FileOpLink { meta: value.meta, resolve: true, relative: false, + hard: false, delete: value.cut, } } diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index 8159be84b..0c2ff4270 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -134,7 +134,7 @@ impl Scheduler { ); } - pub fn file_link(&self, from: Url, mut to: Url, relative: bool, force: bool) { + pub fn file_link(&self, from: Url, mut to: Url, relative: bool, hard: bool, force: bool) { let name = format!("Link {from:?} to {to:?}"); let id = self.ongoing.lock().add(TaskKind::User, name); @@ -145,7 +145,16 @@ impl Scheduler { to = unique_path(to).await; } file - .link(FileOpLink { id, from, to, meta: None, resolve: false, relative, delete: false }) + .link(FileOpLink { + id, + from, + to, + meta: None, + resolve: false, + relative, + hard, + delete: false, + }) .await .ok(); }