From a90cb1c8e7e8a7a7fcd1cb4e932c6078ec9268ce 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. --- Cargo.lock | 1 + yazi-core/src/manager/commands/link.rs | 7 +++-- yazi-core/src/tasks/file.rs | 4 +-- yazi-scheduler/Cargo.toml | 1 + yazi-scheduler/src/file/file.rs | 37 +++++++++++++++++++------- yazi-scheduler/src/file/op.rs | 2 ++ yazi-scheduler/src/scheduler.rs | 13 +++++++-- 7 files changed, 49 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e12338114..2ea5db4ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2950,6 +2950,7 @@ dependencies = [ "tokio", "tracing", "trash", + "walkdir", "yazi-config", "yazi-dds", "yazi-plugin", 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/Cargo.toml b/yazi-scheduler/Cargo.toml index d42b40208..6ab0e7982 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -22,6 +22,7 @@ futures = "0.3.30" parking_lot = "0.12.3" scopeguard = "1.2.0" tokio = { version = "1.38.0", features = [ "full" ] } +walkdir = "2.5.0" # Logging tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index a68f0ac06..56c5ed15c 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -4,6 +4,7 @@ use anyhow::{anyhow, Result}; use futures::{future::BoxFuture, FutureExt}; use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tracing::warn; +use walkdir::WalkDir; use yazi_config::TASKS; use yazi_shared::fs::{calculate_size, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, Url}; @@ -81,16 +82,32 @@ 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? - } else { - fs::symlink_file(src, &task.to).await? + + if task.hard { + for entry in WalkDir::new(&src) { + let entry = entry?; + let path = entry.path(); + let relative_path = path.strip_prefix(&src).unwrap(); + let target_path = task.to.join(relative_path); + + if path.is_dir() { + fs::create_dir(&target_path).await?; + } else { + fs::hard_link(path, &target_path).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? + } } } 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(); }