feat: preserve files' modified at timestamp while copying (#926)

Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
Rolv Apneseth 2024-04-20 05:23:29 +00:00 committed by GitHub
parent a0b4ee6e6e
commit 80000cfd86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 69 additions and 34 deletions

1
Cargo.lock generated
View File

@ -2910,6 +2910,7 @@ dependencies = [
"bitflags 2.5.0",
"crossterm",
"dirs",
"filetime",
"futures",
"libc",
"parking_lot",

View File

@ -1 +1 @@
{"flagWords":[],"language":"en","version":"0.2","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"]}
{"version":"0.2","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"]}

View File

@ -31,7 +31,7 @@ impl File {
_ => {}
}
let mut it = copy_with_progress(&task.from, &task.to);
let mut it = copy_with_progress(&task.from, &task.to, task.meta.as_ref().unwrap());
while let Some(res) = it.recv().await {
match res {
Ok(0) => {
@ -142,7 +142,11 @@ impl File {
}
}
let meta = Self::metadata(&task.from, task.follow).await?;
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()))?;
@ -150,7 +154,7 @@ impl File {
if meta.is_file() {
self.queue(FileOp::Paste(task), LOW).await?;
} else if meta.is_symlink() {
self.queue(FileOp::Link(task.to_link(meta)), NORMAL).await?;
self.queue(FileOp::Link(task.into()), NORMAL).await?;
}
return self.succ(id);
}
@ -168,9 +172,9 @@ impl File {
};
}
let root = task.to.clone();
let root = &task.to;
let skip = task.from.components().count();
let mut dirs = VecDeque::from([task.from]);
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>());
@ -181,22 +185,21 @@ impl File {
let mut it = continue_unless_ok!(fs::read_dir(&src).await);
while let Ok(Some(entry)) = it.next_entry().await {
let src = Url::from(entry.path());
let meta = continue_unless_ok!(Self::metadata(&src, task.follow).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(src);
dirs.push_back(from);
continue;
}
task.to = dest.join(src.file_name().unwrap());
task.from = src;
let to = dest.join(from.file_name().unwrap());
self.prog.send(TaskProg::New(task.id, meta.len()))?;
if meta.is_file() {
self.queue(FileOp::Paste(task.clone()), LOW).await?;
self.queue(FileOp::Paste(task.spawn(from, to, meta)), LOW).await?;
} else if meta.is_symlink() {
self.queue(FileOp::Link(task.to_link(meta)), NORMAL).await?;
self.queue(FileOp::Link(task.spawn(from, to, meta).into()), NORMAL).await?;
}
}
}
@ -303,17 +306,3 @@ impl File {
self.macro_.send(op.into(), priority).await.map_err(|_| anyhow!("Failed to send task"))
}
}
impl FileOpPaste {
fn to_link(&self, meta: Metadata) -> FileOpLink {
FileOpLink {
id: self.id,
from: self.from.clone(),
to: self.to.clone(),
meta: Some(meta),
resolve: true,
relative: false,
delete: self.cut,
}
}
}

View File

@ -26,11 +26,26 @@ pub struct FileOpPaste {
pub id: usize,
pub from: Url,
pub to: Url,
pub meta: Option<Metadata>,
pub cut: bool,
pub follow: bool,
pub retry: u8,
}
impl FileOpPaste {
pub(super) fn spawn(&self, from: Url, to: Url, meta: Metadata) -> Self {
Self {
id: self.id,
from,
to,
meta: Some(meta),
cut: self.cut,
follow: self.follow,
retry: self.retry,
}
}
}
#[derive(Clone, Debug)]
pub struct FileOpLink {
pub id: usize,
@ -42,6 +57,20 @@ pub struct FileOpLink {
pub delete: bool,
}
impl From<FileOpPaste> for FileOpLink {
fn from(value: FileOpPaste) -> Self {
Self {
id: value.id,
from: value.from,
to: value.to,
meta: value.meta,
resolve: true,
relative: false,
delete: value.cut,
}
}
}
#[derive(Clone, Debug)]
pub struct FileOpDelete {
pub id: usize,

View File

@ -99,7 +99,10 @@ impl Scheduler {
if !force {
to = unique_path(to).await;
}
file.paste(FileOpPaste { id, from, to, cut: true, follow: false, retry: 0 }).await.ok();
file
.paste(FileOpPaste { id, from, to, meta: None, cut: true, follow: false, retry: 0 })
.await
.ok();
}
.boxed(),
LOW,
@ -121,7 +124,10 @@ impl Scheduler {
if !force {
to = unique_path(to).await;
}
file.paste(FileOpPaste { id, from, to, cut: false, follow, retry: 0 }).await.ok();
file
.paste(FileOpPaste { id, from, to, meta: None, cut: false, follow, retry: 0 })
.await
.ok();
}
.boxed(),
LOW,

View File

@ -13,6 +13,7 @@ anyhow = "1.0.82"
bitflags = "2.5.0"
crossterm = "0.27.0"
dirs = "5.0.1"
filetime = "0.2.23"
futures = "0.3.30"
parking_lot = "0.12.1"
percent-encoding = "2.3.1"

View File

@ -1,6 +1,7 @@
use std::{collections::VecDeque, path::{Path, PathBuf}};
use std::{collections::VecDeque, fs::Metadata, path::{Path, PathBuf}};
use anyhow::Result;
use filetime::{set_file_mtime, FileTime};
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
pub async fn accessible(path: &Path) -> bool {
@ -34,16 +35,24 @@ pub async fn calculate_size(path: &Path) -> u64 {
total
}
pub fn copy_with_progress(from: &Path, to: &Path) -> mpsc::Receiver<Result<u64, io::Error>> {
pub fn copy_with_progress(
from: &Path,
to: &Path,
meta: &Metadata,
) -> mpsc::Receiver<Result<u64, io::Error>> {
let (tx, rx) = mpsc::channel(1);
let (tick_tx, mut tick_rx) = oneshot::channel();
tokio::spawn({
let (from, to) = (from.to_path_buf(), to.to_path_buf());
let (from, to) = (from.to_owned(), to.to_owned());
let mtime = FileTime::from_last_modification_time(meta);
async move {
_ = match fs::copy(from, to).await {
Ok(len) => tick_tx.send(Ok(len)),
_ = match fs::copy(&from, &to).await {
Ok(len) => {
set_file_mtime(to, mtime).ok();
tick_tx.send(Ok(len))
}
Err(e) => tick_tx.send(Err(e)),
};
}