mirror of
https://github.com/uutils/coreutils
synced 2024-07-23 19:04:18 +00:00
cp: add progress bar
Adds the `-g` and `--progress` flags to enable a progress bar via indicatif.
This commit is contained in:
parent
1172a7e781
commit
21e691c3b9
|
@ -23,6 +23,7 @@ fsext
|
|||
getopts
|
||||
getrandom
|
||||
globset
|
||||
indicatif
|
||||
itertools
|
||||
lscolors
|
||||
mdbook
|
||||
|
|
48
Cargo.lock
generated
48
Cargo.lock
generated
|
@ -281,7 +281,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"terminal_size",
|
||||
"terminal_size 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -327,6 +327,20 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "120133d4db2ec47efe2e26502ee984747630c67f51974fca0b6c1340cf2368d3"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"terminal_size 0.1.17",
|
||||
"unicode-width",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
|
@ -791,6 +805,12 @@ version = "1.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.8.4"
|
||||
|
@ -1047,6 +1067,17 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc42b206e70d86ec03285b123e65a5458c92027d1fb2ae3555878b8113b3ddf"
|
||||
dependencies = [
|
||||
"console",
|
||||
"number_prefix",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
|
@ -2033,6 +2064,16 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.2"
|
||||
|
@ -2050,7 +2091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"terminal_size",
|
||||
"terminal_size 0.2.2",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
@ -2293,6 +2334,7 @@ dependencies = [
|
|||
"clap 4.0.22",
|
||||
"exacl",
|
||||
"filetime",
|
||||
"indicatif",
|
||||
"libc",
|
||||
"quick-error",
|
||||
"selinux",
|
||||
|
@ -2598,7 +2640,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"selinux",
|
||||
"term_grid",
|
||||
"terminal_size",
|
||||
"terminal_size 0.2.2",
|
||||
"unicode-width",
|
||||
"uucore",
|
||||
]
|
||||
|
|
|
@ -26,6 +26,7 @@ quick-error = "2.0.1"
|
|||
selinux = { version="0.3", optional=true }
|
||||
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] }
|
||||
walkdir = "2.2"
|
||||
indicatif = "0.17"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
xattr="0.2.3"
|
||||
|
|
|
@ -14,6 +14,7 @@ use std::fs;
|
|||
use std::io;
|
||||
use std::path::{Path, PathBuf, StripPrefixError};
|
||||
|
||||
use indicatif::ProgressBar;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::UIoError;
|
||||
use uucore::fs::{canonicalize, FileInformation, MissingHandling, ResolveMode};
|
||||
|
@ -170,6 +171,7 @@ impl Entry {
|
|||
|
||||
/// Copy a single entry during a directory traversal.
|
||||
fn copy_direntry(
|
||||
progress_bar: &Option<ProgressBar>,
|
||||
entry: Entry,
|
||||
options: &Options,
|
||||
symlinked_files: &mut HashSet<FileInformation>,
|
||||
|
@ -213,6 +215,7 @@ fn copy_direntry(
|
|||
preserve_hardlinks(hard_links, &source_absolute, &dest, &mut found_hard_link)?;
|
||||
if !found_hard_link {
|
||||
match copy_file(
|
||||
progress_bar,
|
||||
&source_absolute,
|
||||
local_to_target.as_path(),
|
||||
options,
|
||||
|
@ -240,6 +243,7 @@ fn copy_direntry(
|
|||
// TODO What other kinds of errors, if any, should
|
||||
// cause us to continue walking the directory?
|
||||
match copy_file(
|
||||
progress_bar,
|
||||
&source_absolute,
|
||||
local_to_target.as_path(),
|
||||
options,
|
||||
|
@ -272,6 +276,7 @@ fn copy_direntry(
|
|||
/// Any errors encountered copying files in the tree will be logged but
|
||||
/// will not cause a short-circuit.
|
||||
pub(crate) fn copy_directory(
|
||||
progress_bar: &Option<ProgressBar>,
|
||||
root: &Path,
|
||||
target: &TargetSlice,
|
||||
options: &Options,
|
||||
|
@ -285,6 +290,7 @@ pub(crate) fn copy_directory(
|
|||
// if no-dereference is enabled and this is a symlink, copy it as a file
|
||||
if !options.dereference(source_in_command_line) && root.is_symlink() {
|
||||
return copy_file(
|
||||
progress_bar,
|
||||
root,
|
||||
target,
|
||||
options,
|
||||
|
@ -344,6 +350,7 @@ pub(crate) fn copy_directory(
|
|||
Ok(direntry) => {
|
||||
let entry = Entry::new(&context, &direntry)?;
|
||||
copy_direntry(
|
||||
progress_bar,
|
||||
entry,
|
||||
options,
|
||||
symlinked_files,
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
// For the full copyright and license information, please view the LICENSE file
|
||||
// that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) copydir ficlone ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked fiemap
|
||||
// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv
|
||||
|
||||
#[macro_use]
|
||||
extern crate quick_error;
|
||||
|
@ -33,6 +33,7 @@ use std::string::ToString;
|
|||
|
||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||
use filetime::FileTime;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
#[cfg(unix)]
|
||||
use libc::mkfifo;
|
||||
use quick_error::ResultExt;
|
||||
|
@ -214,6 +215,7 @@ pub struct Options {
|
|||
target_dir: Option<String>,
|
||||
update: bool,
|
||||
verbose: bool,
|
||||
progress_bar: bool,
|
||||
}
|
||||
|
||||
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
|
||||
|
@ -244,6 +246,7 @@ mod options {
|
|||
pub const PARENT: &str = "parent";
|
||||
pub const PARENTS: &str = "parents";
|
||||
pub const PATHS: &str = "paths";
|
||||
pub const PROGRESS_BAR: &str = "progress";
|
||||
pub const PRESERVE: &str = "preserve";
|
||||
pub const PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes";
|
||||
pub const RECURSIVE: &str = "recursive";
|
||||
|
@ -538,6 +541,18 @@ pub fn uu_app() -> Command {
|
|||
),
|
||||
)
|
||||
// END TODO
|
||||
.arg(
|
||||
// The 'g' short flag is modeled after advcpmv
|
||||
// See this repo: https://github.com/jarun/advcpmv
|
||||
Arg::new(options::PROGRESS_BAR)
|
||||
.long(options::PROGRESS_BAR)
|
||||
.short('g')
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
.help(
|
||||
"Display a progress bar. \n\
|
||||
Note: this feature is not supported by GNU coreutils.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::PATHS)
|
||||
.action(ArgAction::Append)
|
||||
|
@ -817,6 +832,7 @@ impl Options {
|
|||
preserve_attributes,
|
||||
recursive,
|
||||
target_dir,
|
||||
progress_bar: matches.get_flag(options::PROGRESS_BAR),
|
||||
};
|
||||
|
||||
Ok(options)
|
||||
|
@ -935,7 +951,7 @@ fn preserve_hardlinks(
|
|||
/// `Err(Error::NotAllFilesCopied)` if at least one non-fatal error was
|
||||
/// encountered.
|
||||
///
|
||||
/// Behavior depends on `options`, see [`Options`] for details.
|
||||
/// Behavior depends on path`options`, see [`Options`] for details.
|
||||
///
|
||||
/// [`Options`]: ./struct.Options.html
|
||||
fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResult<()> {
|
||||
|
@ -949,7 +965,23 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
|
|||
let mut non_fatal_errors = false;
|
||||
let mut seen_sources = HashSet::with_capacity(sources.len());
|
||||
let mut symlinked_files = HashSet::new();
|
||||
for source in sources {
|
||||
|
||||
let progress_bar = if options.progress_bar {
|
||||
Some(
|
||||
ProgressBar::new(disk_usage(sources, options.recursive)?)
|
||||
.with_style(
|
||||
ProgressStyle::with_template(
|
||||
"{msg}: [{elapsed_precise}] {wide_bar} {bytes:>7}/{total_bytes:7}",
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.with_message(uucore::util_name()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for source in sources.iter() {
|
||||
if seen_sources.contains(source) {
|
||||
// FIXME: compare sources by the actual file they point to, not their path. (e.g. dir/file == dir/../dir/file in most cases)
|
||||
show_warning!("source {} specified more than once", source.quote());
|
||||
|
@ -960,9 +992,14 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
|
|||
preserve_hardlinks(&mut hard_links, source, &dest, &mut found_hard_link)?;
|
||||
}
|
||||
if !found_hard_link {
|
||||
if let Err(error) =
|
||||
copy_source(source, target, &target_type, options, &mut symlinked_files)
|
||||
{
|
||||
if let Err(error) = copy_source(
|
||||
&progress_bar,
|
||||
source,
|
||||
target,
|
||||
&target_type,
|
||||
options,
|
||||
&mut symlinked_files,
|
||||
) {
|
||||
match error {
|
||||
// When using --no-clobber, we don't want to show
|
||||
// an error message
|
||||
|
@ -1017,6 +1054,7 @@ fn construct_dest_path(
|
|||
}
|
||||
|
||||
fn copy_source(
|
||||
progress_bar: &Option<ProgressBar>,
|
||||
source: &SourceSlice,
|
||||
target: &TargetSlice,
|
||||
target_type: &TargetType,
|
||||
|
@ -1026,11 +1064,18 @@ fn copy_source(
|
|||
let source_path = Path::new(&source);
|
||||
if source_path.is_dir() {
|
||||
// Copy as directory
|
||||
copy_directory(source, target, options, symlinked_files, true)
|
||||
copy_directory(progress_bar, source, target, options, symlinked_files, true)
|
||||
} else {
|
||||
// Copy as file
|
||||
let dest = construct_dest_path(source_path, target, target_type, options)?;
|
||||
copy_file(source_path, dest.as_path(), options, symlinked_files, true)
|
||||
copy_file(
|
||||
progress_bar,
|
||||
source_path,
|
||||
dest.as_path(),
|
||||
options,
|
||||
symlinked_files,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1277,6 +1322,7 @@ fn file_or_link_exists(path: &Path) -> bool {
|
|||
/// The original permissions of `source` will be copied to `dest`
|
||||
/// after a successful copy.
|
||||
fn copy_file(
|
||||
progress_bar: &Option<ProgressBar>,
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
options: &Options,
|
||||
|
@ -1452,6 +1498,11 @@ fn copy_file(
|
|||
fs::set_permissions(dest, dest_permissions).ok();
|
||||
}
|
||||
copy_attributes(source, dest, &options.preserve_attributes)?;
|
||||
|
||||
if let Some(progress_bar) = progress_bar {
|
||||
progress_bar.inc(fs::metadata(&source)?.len());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1571,6 +1622,42 @@ pub fn localize_to_target(root: &Path, source: &Path, target: &Path) -> CopyResu
|
|||
Ok(target.join(local_to_root))
|
||||
}
|
||||
|
||||
/// Get the total size of a slice of files and directories.
|
||||
///
|
||||
/// This function is much like the `du` utility, by recursively getting the sizes of files in directories.
|
||||
/// Files are not deduplicated when appearing in multiple sources. If `recursive` is set to `false`, the
|
||||
/// directories in `paths` will be ignored.
|
||||
fn disk_usage(paths: &[PathBuf], recursive: bool) -> io::Result<u64> {
|
||||
let mut total = 0;
|
||||
for p in paths {
|
||||
let md = fs::metadata(p)?;
|
||||
if md.file_type().is_dir() {
|
||||
if recursive {
|
||||
total += disk_usage_directory(p)?;
|
||||
}
|
||||
} else {
|
||||
total += md.len();
|
||||
}
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
/// A helper for `disk_usage` specialized for directories.
|
||||
fn disk_usage_directory(p: &Path) -> io::Result<u64> {
|
||||
let mut total = 0;
|
||||
|
||||
for entry in fs::read_dir(p)? {
|
||||
let entry = entry?;
|
||||
if entry.file_type()?.is_dir() {
|
||||
total += disk_usage_directory(&entry.path())?;
|
||||
} else {
|
||||
total += entry.metadata()?.len();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp_localize_to_target() {
|
||||
assert!(
|
||||
|
|
Loading…
Reference in a new issue