mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: new command hardlink (#1268)
Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
parent
afa5936bf5
commit
54eb0cc663
9 changed files with 206 additions and 57 deletions
|
|
@ -1 +1 @@
|
|||
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","nlink"],"language":"en","version":"0.2","flagWords":[]}
|
||||
{"flagWords":[],"language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking"],"version":"0.2"}
|
||||
|
|
@ -59,30 +59,30 @@ keymap = [
|
|||
{ on = "<C-r>", run = "select_all --state=none", desc = "Inverse selection of all files" },
|
||||
|
||||
# Operation
|
||||
{ on = "o", run = "open", desc = "Open the selected files" },
|
||||
{ on = "O", run = "open --interactive", desc = "Open the selected files interactively" },
|
||||
{ on = "<Enter>", run = "open", desc = "Open the selected files" },
|
||||
{ on = "<S-Enter>", run = "open --interactive", desc = "Open the selected files interactively" },
|
||||
{ on = "y", run = "yank", desc = "Copy the selected files" },
|
||||
{ on = "Y", run = "unyank", desc = "Cancel the yank status of files" },
|
||||
{ on = "o", run = "open", desc = "Open selected files" },
|
||||
{ on = "O", run = "open --interactive", desc = "Open selected files interactively" },
|
||||
{ on = "<Enter>", run = "open", desc = "Open selected files" },
|
||||
{ on = "<S-Enter>", run = "open --interactive", desc = "Open selected files interactively" },
|
||||
{ on = "y", run = "yank", desc = "Copy selected files" },
|
||||
{ on = "x", run = "yank --cut", desc = "Cut the selected files" },
|
||||
{ on = "X", run = "unyank", desc = "Cancel the yank status of files" },
|
||||
{ on = "p", run = "paste", desc = "Paste the files" },
|
||||
{ on = "P", run = "paste --force", desc = "Paste the files (overwrite if the destination exists)" },
|
||||
{ on = "-", run = "link", desc = "Symlink the absolute path of files" },
|
||||
{ on = "_", run = "link --relative", desc = "Symlink the relative path of files" },
|
||||
{ on = "d", run = "remove", desc = "Move the files to the trash" },
|
||||
{ on = "D", run = "remove --permanently", desc = "Permanently delete the files" },
|
||||
{ on = "a", run = "create", desc = "Create a file or directory (ends with / for directories)" },
|
||||
{ on = "r", run = "rename --cursor=before_ext", desc = "Rename a file or directory" },
|
||||
{ on = "Y", run = "unyank", desc = "Cancel the yank status" },
|
||||
{ on = "X", run = "unyank", desc = "Cancel the yank status" },
|
||||
{ on = "p", run = "paste", desc = "Paste yanked files" },
|
||||
{ on = "P", run = "paste --force", desc = "Paste yanked files (overwrite if the destination exists)" },
|
||||
{ on = "-", run = "link", desc = "Symlink the absolute path of yanked files" },
|
||||
{ on = "_", run = "link --relative", desc = "Symlink the relative path of yanked files" },
|
||||
{ on = "d", run = "remove", desc = "Trash selected files" },
|
||||
{ on = "D", run = "remove --permanently", desc = "Permanently delete selected files" },
|
||||
{ on = "a", run = "create", desc = "Create a file (ends with / for directories)" },
|
||||
{ on = "r", run = "rename --cursor=before_ext", desc = "Rename selected file(s)" },
|
||||
{ on = ";", run = "shell --interactive", desc = "Run a shell command" },
|
||||
{ on = ":", run = "shell --block --interactive", desc = "Run a shell command (block the UI until the command finishes)" },
|
||||
{ on = ":", run = "shell --block --interactive", desc = "Run a shell command (block until finishes)" },
|
||||
{ on = ".", run = "hidden toggle", desc = "Toggle the visibility of hidden files" },
|
||||
{ on = "s", run = "search fd", desc = "Search files by name using fd" },
|
||||
{ on = "S", run = "search rg", desc = "Search files by content using ripgrep" },
|
||||
{ on = "<C-s>", run = "search none", desc = "Cancel the ongoing search" },
|
||||
{ on = "z", run = "plugin zoxide", desc = "Jump to a directory using zoxide" },
|
||||
{ on = "Z", run = "plugin fzf", desc = "Jump to a directory, or reveal a file using fzf" },
|
||||
{ on = "Z", run = "plugin fzf", desc = "Jump to a directory or reveal a file using fzf" },
|
||||
|
||||
# Linemode
|
||||
{ on = [ "m", "s" ], run = "linemode size", desc = "Set linemode to size" },
|
||||
|
|
@ -92,19 +92,19 @@ keymap = [
|
|||
{ on = [ "m", "n" ], run = "linemode none", desc = "Set linemode to none" },
|
||||
|
||||
# Copy
|
||||
{ on = [ "c", "c" ], run = "copy path", desc = "Copy the absolute path" },
|
||||
{ on = [ "c", "d" ], run = "copy dirname", desc = "Copy the path of the parent directory" },
|
||||
{ on = [ "c", "f" ], run = "copy filename", desc = "Copy the name of the file" },
|
||||
{ on = [ "c", "n" ], run = "copy name_without_ext", desc = "Copy the name of the file without the extension" },
|
||||
{ on = [ "c", "c" ], run = "copy path", desc = "Copy the file path" },
|
||||
{ on = [ "c", "d" ], run = "copy dirname", desc = "Copy the directory path" },
|
||||
{ on = [ "c", "f" ], run = "copy filename", desc = "Copy the filename" },
|
||||
{ on = [ "c", "n" ], run = "copy name_without_ext", desc = "Copy the filename without extension" },
|
||||
|
||||
# Filter
|
||||
{ on = "f", run = "filter --smart", desc = "Filter the files" },
|
||||
{ on = "f", run = "filter --smart", desc = "Filter files" },
|
||||
|
||||
# Find
|
||||
{ on = "/", run = "find --smart", desc = "Find next file" },
|
||||
{ on = "?", run = "find --previous --smart", desc = "Find previous file" },
|
||||
{ on = "n", run = "find_arrow", desc = "Go to next found file" },
|
||||
{ on = "N", run = "find_arrow --previous", desc = "Go to previous found file" },
|
||||
{ on = "n", run = "find_arrow", desc = "Go to the next found" },
|
||||
{ on = "N", run = "find_arrow --previous", desc = "Go to the previous found" },
|
||||
|
||||
# Sorting
|
||||
{ on = [ ",", "m" ], run = "sort modified --reverse=no", desc = "Sort by modified time" },
|
||||
|
|
@ -121,7 +121,7 @@ keymap = [
|
|||
{ on = [ ",", "S" ], run = "sort size --reverse", desc = "Sort by size (reverse)" },
|
||||
|
||||
# Tabs
|
||||
{ on = "t", run = "tab_create --current", desc = "Create a new tab using the current path" },
|
||||
{ on = "t", run = "tab_create --current", desc = "Create a new tab with CWD" },
|
||||
|
||||
{ on = "1", run = "tab_switch 0", desc = "Switch to the first tab" },
|
||||
{ on = "2", run = "tab_switch 1", desc = "Switch to the second tab" },
|
||||
|
|
@ -136,11 +136,11 @@ keymap = [
|
|||
{ on = "[", run = "tab_switch -1 --relative", desc = "Switch to the previous tab" },
|
||||
{ on = "]", run = "tab_switch 1 --relative", desc = "Switch to the next tab" },
|
||||
|
||||
{ on = "{", run = "tab_swap -1", desc = "Swap the current tab with the previous tab" },
|
||||
{ on = "}", run = "tab_swap 1", desc = "Swap the current tab with the next tab" },
|
||||
{ on = "{", run = "tab_swap -1", desc = "Swap current tab with previous tab" },
|
||||
{ on = "}", run = "tab_swap 1", desc = "Swap current tab with next tab" },
|
||||
|
||||
# Tasks
|
||||
{ on = "w", run = "tasks_show", desc = "Show the tasks manager" },
|
||||
{ on = "w", run = "tasks_show", desc = "Show task manager" },
|
||||
|
||||
# Goto
|
||||
{ on = [ "g", "h" ], run = "cd ~", desc = "Go to the home directory" },
|
||||
|
|
@ -155,10 +155,10 @@ keymap = [
|
|||
[tasks]
|
||||
|
||||
keymap = [
|
||||
{ on = "<Esc>", run = "close", desc = "Hide the task manager" },
|
||||
{ on = "<C-[>", run = "close", desc = "Hide the task manager" },
|
||||
{ on = "<C-c>", run = "close", desc = "Hide the task manager" },
|
||||
{ on = "w", run = "close", desc = "Hide the task manager" },
|
||||
{ on = "<Esc>", run = "close", desc = "Close task manager" },
|
||||
{ on = "<C-[>", run = "close", desc = "Close task manager" },
|
||||
{ on = "<C-c>", run = "close", desc = "Close task manager" },
|
||||
{ on = "w", run = "close", desc = "Close task manager" },
|
||||
|
||||
{ on = "k", run = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = "j", run = "arrow 1", desc = "Move cursor down" },
|
||||
|
|
|
|||
23
yazi-core/src/manager/commands/hardlink.rs
Normal file
23
yazi-core/src/manager/commands/hardlink.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use yazi_shared::event::Cmd;
|
||||
|
||||
use crate::{manager::Manager, tasks::Tasks};
|
||||
|
||||
pub struct Opt {
|
||||
force: bool,
|
||||
follow: bool,
|
||||
}
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(c: Cmd) -> Self { Self { force: c.bool("force"), follow: c.bool("follow") } }
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn hardlink(&mut self, opt: impl Into<Opt>, tasks: &Tasks) {
|
||||
if self.yanked.cut {
|
||||
return;
|
||||
}
|
||||
|
||||
let opt = opt.into() as Opt;
|
||||
tasks.file_hardlink(&self.yanked, self.cwd(), opt.force, opt.follow);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
mod bulk_rename;
|
||||
mod close;
|
||||
mod create;
|
||||
mod hardlink;
|
||||
mod hover;
|
||||
mod link;
|
||||
mod open;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,17 @@ impl Tasks {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn file_hardlink(&self, src: &HashSet<Url>, dest: &Url, force: bool, follow: bool) {
|
||||
for u in src {
|
||||
let to = dest.join(u.file_name().unwrap());
|
||||
if force && *u == to {
|
||||
debug!("file_hardlink: same file, skipping {:?}", to);
|
||||
} else {
|
||||
self.scheduler.file_hardlink(u.clone(), to, force, follow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_remove(&self, targets: Vec<Url>, permanently: bool) {
|
||||
for u in targets {
|
||||
if permanently {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ impl<'a> Executor<'a> {
|
|||
on!(MANAGER, unyank);
|
||||
on!(MANAGER, paste, &self.app.cx.tasks);
|
||||
on!(MANAGER, link, &self.app.cx.tasks);
|
||||
on!(MANAGER, hardlink, &self.app.cx.tasks);
|
||||
on!(MANAGER, remove, &self.app.cx.tasks);
|
||||
on!(MANAGER, remove_do, &self.app.cx.tasks);
|
||||
on!(MANAGER, create);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use std::{borrow::Cow, collections::VecDeque, fs::Metadata, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
|
||||
use tracing::warn;
|
||||
use yazi_config::TASKS;
|
||||
use yazi_shared::fs::{calculate_size, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, Url};
|
||||
|
||||
use super::{FileOp, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash};
|
||||
use super::{FileOp, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash};
|
||||
use crate::{TaskOp, TaskProg, LOW, NORMAL};
|
||||
|
||||
pub struct File {
|
||||
|
|
@ -39,7 +38,7 @@ impl File {
|
|||
}
|
||||
Ok(n) => self.prog.send(TaskProg::Adv(task.id, 0, n))?,
|
||||
Err(e) if e.kind() == NotFound => {
|
||||
warn!("Paste task partially done: {:?}", task);
|
||||
warn!("Paste task partially done: {task:?}");
|
||||
break;
|
||||
}
|
||||
// Operation not permitted (os error 1)
|
||||
|
|
@ -65,7 +64,7 @@ impl File {
|
|||
match fs::read_link(&task.from).await {
|
||||
Ok(p) => Cow::Owned(p),
|
||||
Err(e) if e.kind() == NotFound => {
|
||||
self.log(task.id, format!("Link task partially done: {:?}", task))?;
|
||||
warn!("Link task partially done: {task:?}");
|
||||
return Ok(self.prog.send(TaskProg::Adv(task.id, 1, meta.len()))?);
|
||||
}
|
||||
Err(e) => Err(e)?,
|
||||
|
|
@ -83,14 +82,14 @@ impl File {
|
|||
ok_or_not_found(fs::remove_file(&task.to).await)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
fs::symlink(src, &task.to).await?
|
||||
fs::symlink(src, &task.to).await?;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if meta.is_dir() {
|
||||
fs::symlink_dir(src, &task.to).await?
|
||||
fs::symlink_dir(src, &task.to).await?;
|
||||
} else {
|
||||
fs::symlink_file(src, &task.to).await?
|
||||
fs::symlink_file(src, &task.to).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +98,26 @@ impl File {
|
|||
}
|
||||
self.prog.send(TaskProg::Adv(task.id, 1, meta.len()))?;
|
||||
}
|
||||
FileOp::Hardlink(task) => {
|
||||
let meta = task.meta.as_ref().unwrap();
|
||||
let src = if !task.follow {
|
||||
Cow::Borrowed(task.from.as_path())
|
||||
} else if let Ok(p) = fs::canonicalize(&task.from).await {
|
||||
Cow::Owned(p)
|
||||
} else {
|
||||
Cow::Borrowed(task.from.as_path())
|
||||
};
|
||||
|
||||
ok_or_not_found(fs::remove_file(&task.to).await)?;
|
||||
match fs::hard_link(src, &task.to).await {
|
||||
Err(e) if e.kind() == NotFound => {
|
||||
warn!("Hardlink task partially done: {task:?}");
|
||||
}
|
||||
v => v?,
|
||||
}
|
||||
|
||||
self.prog.send(TaskProg::Adv(task.id, 1, meta.len()))?;
|
||||
}
|
||||
FileOp::Delete(task) => {
|
||||
if let Err(e) = fs::remove_file(&task.target).await {
|
||||
if e.kind() != NotFound && maybe_exists(&task.target).await {
|
||||
|
|
@ -206,6 +225,61 @@ impl File {
|
|||
self.succ(id)
|
||||
}
|
||||
|
||||
pub async fn hardlink(&self, mut task: FileOpHardlink) -> Result<()> {
|
||||
if task.meta.is_none() {
|
||||
task.meta = Some(Self::metadata(&task.from, task.follow).await?);
|
||||
}
|
||||
|
||||
let meta = task.meta.as_ref().unwrap();
|
||||
if !meta.is_dir() {
|
||||
let id = task.id;
|
||||
self.prog.send(TaskProg::New(id, meta.len()))?;
|
||||
self.queue(FileOp::Hardlink(task), NORMAL).await?;
|
||||
return self.succ(id);
|
||||
}
|
||||
|
||||
macro_rules! continue_unless_ok {
|
||||
($result:expr) => {
|
||||
match $result {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
self.prog.send(TaskProg::New(task.id, 0))?;
|
||||
self.fail(task.id, format!("An error occurred while hardlinking: {e}"))?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let root = &task.to;
|
||||
let skip = task.from.components().count();
|
||||
let mut dirs = VecDeque::from([task.from.clone()]);
|
||||
|
||||
while let Some(src) = dirs.pop_front() {
|
||||
let dest = root.join(src.components().skip(skip).collect::<PathBuf>());
|
||||
continue_unless_ok!(match fs::create_dir(&dest).await {
|
||||
Err(e) if e.kind() != AlreadyExists => Err(e),
|
||||
_ => Ok(()),
|
||||
});
|
||||
|
||||
let mut it = continue_unless_ok!(fs::read_dir(&src).await);
|
||||
while let Ok(Some(entry)) = it.next_entry().await {
|
||||
let from = Url::from(entry.path());
|
||||
let meta = continue_unless_ok!(Self::metadata(&from, task.follow).await);
|
||||
|
||||
if meta.is_dir() {
|
||||
dirs.push_back(from);
|
||||
continue;
|
||||
}
|
||||
|
||||
let to = dest.join(from.file_name().unwrap());
|
||||
self.prog.send(TaskProg::New(task.id, meta.len()))?;
|
||||
self.queue(FileOp::Hardlink(task.spawn(from, to, meta)), NORMAL).await?;
|
||||
}
|
||||
}
|
||||
self.succ(task.id)
|
||||
}
|
||||
|
||||
pub async fn delete(&self, mut task: FileOpDelete) -> Result<()> {
|
||||
let meta = fs::symlink_metadata(&task.target).await?;
|
||||
if !meta.is_dir() {
|
||||
|
|
@ -246,6 +320,7 @@ impl File {
|
|||
self.succ(id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn metadata(path: &Path, follow: bool) -> io::Result<Metadata> {
|
||||
if !follow {
|
||||
return fs::symlink_metadata(path).await;
|
||||
|
|
@ -255,24 +330,18 @@ impl File {
|
|||
if meta.is_ok() { meta } else { fs::symlink_metadata(path).await }
|
||||
}
|
||||
|
||||
pub(crate) fn remove_empty_dirs(dir: &Path) -> BoxFuture<()> {
|
||||
async move {
|
||||
let mut it = match fs::read_dir(dir).await {
|
||||
Ok(it) => it,
|
||||
Err(_) => return,
|
||||
};
|
||||
pub(crate) async fn remove_empty_dirs(dir: &Path) {
|
||||
let Ok(mut it) = fs::read_dir(dir).await else { return };
|
||||
|
||||
while let Ok(Some(entry)) = it.next_entry().await {
|
||||
if entry.file_type().await.map(|t| t.is_dir()).unwrap_or(false) {
|
||||
let path = entry.path();
|
||||
Self::remove_empty_dirs(&path).await;
|
||||
fs::remove_dir(path).await.ok();
|
||||
}
|
||||
while let Ok(Some(entry)) = it.next_entry().await {
|
||||
if entry.file_type().await.is_ok_and(|t| t.is_dir()) {
|
||||
let path = entry.path();
|
||||
Box::pin(Self::remove_empty_dirs(&path)).await;
|
||||
fs::remove_dir(path).await.ok();
|
||||
}
|
||||
|
||||
fs::remove_dir(dir).await.ok();
|
||||
}
|
||||
.boxed()
|
||||
|
||||
fs::remove_dir(dir).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use yazi_shared::fs::Url;
|
|||
pub enum FileOp {
|
||||
Paste(FileOpPaste),
|
||||
Link(FileOpLink),
|
||||
Hardlink(FileOpHardlink),
|
||||
Delete(FileOpDelete),
|
||||
Trash(FileOpTrash),
|
||||
}
|
||||
|
|
@ -15,12 +16,14 @@ impl FileOp {
|
|||
match self {
|
||||
Self::Paste(op) => op.id,
|
||||
Self::Link(op) => op.id,
|
||||
Self::Hardlink(op) => op.id,
|
||||
Self::Delete(op) => op.id,
|
||||
Self::Trash(op) => op.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Paste
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileOpPaste {
|
||||
pub id: usize,
|
||||
|
|
@ -46,6 +49,7 @@ impl FileOpPaste {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Link
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileOpLink {
|
||||
pub id: usize,
|
||||
|
|
@ -71,6 +75,23 @@ impl From<FileOpPaste> for FileOpLink {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Hardlink
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileOpHardlink {
|
||||
pub id: usize,
|
||||
pub from: Url,
|
||||
pub to: Url,
|
||||
pub meta: Option<Metadata>,
|
||||
pub follow: bool,
|
||||
}
|
||||
|
||||
impl FileOpHardlink {
|
||||
pub(super) fn spawn(&self, from: Url, to: Url, meta: Metadata) -> Self {
|
||||
Self { id: self.id, from, to, meta: Some(meta), follow: self.follow }
|
||||
}
|
||||
}
|
||||
|
||||
// --- Delete
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileOpDelete {
|
||||
pub id: usize,
|
||||
|
|
@ -78,6 +99,7 @@ pub struct FileOpDelete {
|
|||
pub length: u64,
|
||||
}
|
||||
|
||||
// --- Trash
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileOpTrash {
|
||||
pub id: usize,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use yazi_dds::Pump;
|
|||
use yazi_shared::{event::Data, fs::{unique_path, Url}, Throttle};
|
||||
|
||||
use super::{Ongoing, TaskProg, TaskStage};
|
||||
use crate::{file::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}, TaskKind, TaskOp, HIGH, LOW, NORMAL};
|
||||
use crate::{file::{File, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}, TaskKind, TaskOp, HIGH, LOW, NORMAL};
|
||||
|
||||
pub struct Scheduler {
|
||||
pub file: Arc<File>,
|
||||
|
|
@ -154,6 +154,28 @@ impl Scheduler {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn file_hardlink(&self, from: Url, mut to: Url, force: bool, follow: bool) {
|
||||
let name = format!("Hardlink {:?} to {:?}", from, to);
|
||||
let id = self.ongoing.lock().add(TaskKind::User, name);
|
||||
|
||||
if to.starts_with(&from) && to != from {
|
||||
self.new_and_fail(id, "Cannot hardlink directory into itself").ok();
|
||||
return;
|
||||
}
|
||||
|
||||
let file = self.file.clone();
|
||||
_ = self.micro.try_send(
|
||||
async move {
|
||||
if !force {
|
||||
to = unique_path(to).await;
|
||||
}
|
||||
file.hardlink(FileOpHardlink { id, from, to, meta: None, follow }).await.ok();
|
||||
}
|
||||
.boxed(),
|
||||
LOW,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn file_delete(&self, target: Url) {
|
||||
let mut ongoing = self.ongoing.lock();
|
||||
let id = ongoing.add(TaskKind::User, format!("Delete {:?}", target));
|
||||
|
|
@ -368,7 +390,7 @@ impl Scheduler {
|
|||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
prog.send(TaskProg::Fail(id, format!("Failed to work on this task: {:?}", e))).ok();
|
||||
prog.send(TaskProg::Fail(id, format!("Failed to work on this task: {e:?}"))).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue