From 8b0f4f3ee4999f9601d515a8c8c6d9cf5b17dd72 Mon Sep 17 00:00:00 2001 From: figsoda Date: Wed, 12 Oct 2022 17:46:11 -0400 Subject: [PATCH 1/5] rewrite progress module --- src/archive/tar.rs | 12 ++-- src/archive/zip.rs | 14 ++-- src/commands/compress.rs | 92 +++++++++---------------- src/commands/decompress.rs | 27 ++++---- src/macros.rs | 18 ++--- src/progress.rs | 134 +++++++++++-------------------------- 6 files changed, 103 insertions(+), 194 deletions(-) diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 85b41b7..101a0e4 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -19,11 +19,7 @@ use crate::{ /// Unpacks the archive given by `archive` into the folder given by `into`. /// Assumes that output_folder is empty -pub fn unpack_archive( - reader: Box, - output_folder: &Path, - mut display_handle: impl Write, -) -> crate::Result> { +pub fn unpack_archive(reader: Box, output_folder: &Path, mut out: impl Write) -> crate::Result> { assert!(output_folder.read_dir().expect("dir exists").count() == 0); let mut archive = tar::Archive::new(reader); @@ -40,7 +36,7 @@ pub fn unpack_archive( // and so on info!( - @display_handle, + @out, inaccessible, "{:?} extracted. ({})", utils::strip_cur_dir(&output_folder.join(file.path()?)), Bytes::new(file.size()) @@ -86,7 +82,7 @@ pub fn build_archive_from_paths( input_filenames: &[PathBuf], writer: W, file_visibility_policy: FileVisibilityPolicy, - mut display_handle: D, + mut out: D, ) -> crate::Result where W: Write, @@ -108,7 +104,7 @@ where // little importance for most users, but would generate lots of // spoken text for users using screen readers, braille displays // and so on - info!(@display_handle, inaccessible, "Compressing '{}'.", utils::to_utf(path)); + info!(@out, inaccessible, "Compressing '{}'.", utils::to_utf(path)); if path.is_dir() { builder.append_dir(path, path)?; diff --git a/src/archive/zip.rs b/src/archive/zip.rs index fd0ecfb..ed27b80 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -27,11 +27,7 @@ use crate::{ /// Unpacks the archive given by `archive` into the folder given by `output_folder`. /// Assumes that output_folder is empty -pub fn unpack_archive( - mut archive: ZipArchive, - output_folder: &Path, - mut display_handle: D, -) -> crate::Result> +pub fn unpack_archive(mut archive: ZipArchive, output_folder: &Path, mut out: D) -> crate::Result> where R: Read + Seek, D: Write, @@ -57,7 +53,7 @@ where // importance for most users, but would generate lots of // spoken text for users using screen readers, braille displays // and so on - info!(@display_handle, inaccessible, "File {} extracted to \"{}\"", idx, file_path.display()); + info!(@out, inaccessible, "File {} extracted to \"{}\"", idx, file_path.display()); fs::create_dir_all(&file_path)?; } _is_file @ false => { @@ -70,7 +66,7 @@ where // same reason is in _is_dir: long, often not needed text info!( - @display_handle, + @out, inaccessible, "{:?} extracted. ({})", file_path.display(), Bytes::new(file.size()) @@ -137,7 +133,7 @@ pub fn build_archive_from_paths( input_filenames: &[PathBuf], writer: W, file_visibility_policy: FileVisibilityPolicy, - mut display_handle: D, + mut out: D, ) -> crate::Result where W: Write + Seek, @@ -177,7 +173,7 @@ where // little importance for most users, but would generate lots of // spoken text for users using screen readers, braille displays // and so on - info!(@display_handle, inaccessible, "Compressing '{}'.", to_utf(path)); + info!(@out, inaccessible, "Compressing '{}'.", to_utf(path)); let metadata = match path.metadata() { Ok(metadata) => metadata, diff --git a/src/commands/compress.rs b/src/commands/compress.rs index ddf24f8..440b9fc 100644 --- a/src/commands/compress.rs +++ b/src/commands/compress.rs @@ -1,11 +1,12 @@ use std::{ - io::{self, BufWriter, Write}, + io::{self, BufWriter, Cursor, Seek, Write}, path::{Path, PathBuf}, }; use fs_err as fs; use crate::{ + accessible::is_running_in_accessible_mode, archive, commands::warn_user_about_loading_zip_in_memory, extension::{ @@ -42,12 +43,6 @@ pub fn compress_files( (total_size + size, and_precise & precise) }); - // NOTE: canonicalize is here to avoid a weird bug: - // > If output_file_path is a nested path and it exists and the user overwrite it - // >> output_file_path.exists() will always return false (somehow) - // - canonicalize seems to fix this - let output_file_path = output_file.path().canonicalize()?; - let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file); let mut writer: Box = Box::new(file_writer); @@ -80,37 +75,28 @@ pub fn compress_files( match first_format { Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => { - let _progress = Progress::new_accessible_aware( - total_input_size, - precise, - Some(Box::new(move || { - output_file_path.metadata().expect("file exists").len() - })), - ); - writer = chain_writer_encoder(&first_format, writer)?; let mut reader = fs::File::open(&files[0]).unwrap(); - io::copy(&mut reader, &mut writer)?; + + if is_running_in_accessible_mode() { + io::copy(&mut reader, &mut writer)?; + } else { + io::copy( + &mut Progress::new(total_input_size, precise, true).wrap_read(reader), + &mut writer, + )?; + } } Tar => { - let mut progress = Progress::new_accessible_aware( - total_input_size, - precise, - Some(Box::new(move || { - output_file_path.metadata().expect("file exists").len() - })), - ); - - archive::tar::build_archive_from_paths( - &files, - &mut writer, - file_visibility_policy, - progress - .as_mut() - .map(Progress::display_handle) - .unwrap_or(&mut io::stdout()), - )?; - writer.flush()?; + if is_running_in_accessible_mode() { + archive::tar::build_archive_from_paths(&files, &mut writer, file_visibility_policy, io::stdout())?; + writer.flush()?; + } else { + let mut progress = Progress::new(total_input_size, precise, true); + let mut writer = progress.wrap_write(writer); + archive::tar::build_archive_from_paths(&files, &mut writer, file_visibility_policy, &mut progress)?; + writer.flush()?; + } } Zip => { if !formats.is_empty() { @@ -122,34 +108,18 @@ pub fn compress_files( } } - let mut vec_buffer = io::Cursor::new(vec![]); + let mut vec_buffer = Cursor::new(vec![]); - let current_position_fn = { - let vec_buffer_ptr = { - struct FlyPtr(*const io::Cursor>); - unsafe impl Send for FlyPtr {} - FlyPtr(&vec_buffer as *const _) - }; - Box::new(move || { - let vec_buffer_ptr = &vec_buffer_ptr; - // Safety: ptr is valid and vec_buffer is still alive - unsafe { &*vec_buffer_ptr.0 }.position() - }) - }; - - let mut progress = Progress::new_accessible_aware(total_input_size, precise, Some(current_position_fn)); - - archive::zip::build_archive_from_paths( - &files, - &mut vec_buffer, - file_visibility_policy, - progress - .as_mut() - .map(Progress::display_handle) - .unwrap_or(&mut io::stdout()), - )?; - let vec_buffer = vec_buffer.into_inner(); - io::copy(&mut vec_buffer.as_slice(), &mut writer)?; + if is_running_in_accessible_mode() { + archive::zip::build_archive_from_paths(&files, &mut vec_buffer, file_visibility_policy, io::stdout())?; + vec_buffer.rewind()?; + io::copy(&mut vec_buffer, &mut writer)?; + } else { + let mut progress = Progress::new(total_input_size, precise, true); + archive::zip::build_archive_from_paths(&files, &mut vec_buffer, file_visibility_policy, &mut progress)?; + vec_buffer.rewind()?; + io::copy(&mut progress.wrap_read(vec_buffer), &mut writer)?; + } } } diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index d918fe9..bddf7eb 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -7,6 +7,7 @@ use std::{ use fs_err as fs; use crate::{ + accessible::is_running_in_accessible_mode, commands::warn_user_about_loading_zip_in_memory, extension::{ split_first_compression_format, @@ -110,13 +111,15 @@ pub fn decompress_file( } let mut writer = writer.unwrap(); - let current_position_fn = Box::new({ - let output_file_path = output_file_path.clone(); - move || output_file_path.clone().metadata().expect("file exists").len() - }); - let _progress = Progress::new_accessible_aware(total_input_size, true, Some(current_position_fn)); + if is_running_in_accessible_mode() { + io::copy(&mut reader, &mut writer)?; + } else { + io::copy( + &mut Progress::new(total_input_size, true, true).wrap_read(reader), + &mut writer, + )?; + } - io::copy(&mut reader, &mut writer)?; vec![output_file_path] } Tar => { @@ -196,13 +199,11 @@ fn smart_unpack( ); // unpack the files - let files = unpack_fn( - temp_dir_path, - Progress::new_accessible_aware(total_input_size, true, None) - .as_mut() - .map(Progress::display_handle) - .unwrap_or(&mut io::stdout()), - )?; + let files = if is_running_in_accessible_mode() { + unpack_fn(temp_dir_path, &mut io::stdout()) + } else { + unpack_fn(temp_dir_path, &mut Progress::new(total_input_size, true, false)) + }?; let root_contains_only_one_element = fs::read_dir(temp_dir_path)?.count() == 1; if root_contains_only_one_element { diff --git a/src/macros.rs b/src/macros.rs index 9068b6d..6150a2b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -18,7 +18,7 @@ use crate::accessible::is_running_in_accessible_mode; /// ability to skip some lines deemed not important like a seeing person would. /// /// By default `info` outputs to Stdout, if you want to specify the output you can use -/// `@display_handle` modifier +/// `@out` modifier #[macro_export] macro_rules! info { @@ -27,24 +27,24 @@ macro_rules! info { (accessible, $($arg:tt)*) => { info!(@::std::io::stdout(), accessible, $($arg)*); }; - (@$display_handle: expr, accessible, $($arg:tt)*) => { - let display_handle = &mut $display_handle; + (@$out: expr, accessible, $($arg:tt)*) => { + let out = &mut $out; // if in ACCESSIBLE mode, suppress the "[INFO]" and just print the message if !$crate::accessible::is_running_in_accessible_mode() { - $crate::macros::_info_helper(display_handle); + $crate::macros::_info_helper(out); } - writeln!(display_handle, $($arg)*).unwrap(); + writeln!(out, $($arg)*).unwrap(); }; // Inccessible (long/no important) info message. // Print info message if ACCESSIBLE is not turned on (inaccessible, $($arg:tt)*) => { info!(@::std::io::stdout(), inaccessible, $($arg)*); }; - (@$display_handle: expr, inaccessible, $($arg:tt)*) => { + (@$out: expr, inaccessible, $($arg:tt)*) => { if !$crate::accessible::is_running_in_accessible_mode() { - let display_handle = &mut $display_handle; - $crate::macros::_info_helper(display_handle); - writeln!(display_handle, $($arg)*).unwrap(); + let out = &mut $out; + $crate::macros::_info_helper(out); + writeln!(out, $($arg)*).unwrap(); } }; } diff --git a/src/progress.rs b/src/progress.rs index 7ec3683..5c2a530 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -1,122 +1,68 @@ //! Module that provides functions to display progress bars for compressing and decompressing files. use std::{ - io, - sync::mpsc::{self, Receiver, Sender}, - thread, - time::Duration, + io::{self, Read, Write}, + mem, }; -use indicatif::{ProgressBar, ProgressStyle}; - -use crate::accessible::is_running_in_accessible_mode; +use indicatif::{ProgressBar, ProgressBarIter, ProgressStyle}; /// Draw a ProgressBar using a function that checks periodically for the progress pub struct Progress { - draw_stop: Sender<()>, - clean_done: Receiver<()>, - display_handle: DisplayHandle, + bar: ProgressBar, + buf: Vec, } -/// Writes to this struct will be displayed on the progress bar or stdout depending on the -/// ProgressBarPolicy -struct DisplayHandle { - buf: Vec, - sender: Sender, -} -impl io::Write for DisplayHandle { +impl Write for Progress { fn write(&mut self, buf: &[u8]) -> io::Result { self.buf.extend(buf); - // Newline is the signal to flush - if matches!(buf.last(), Some(&b'\n')) { + + if self.buf.last() == Some(&b'\n') { self.buf.pop(); self.flush()?; } + Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { - fn io_error(_: X) -> io::Error { - io::Error::new(io::ErrorKind::Other, "failed to flush buffer") - } - self.sender - .send(String::from_utf8(self.buf.drain(..).collect()).map_err(io_error)?) - .map_err(io_error) + self.bar.set_message( + String::from_utf8(mem::take(&mut self.buf)) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to parse buffer content as utf8"))?, + ); + Ok(()) } } impl Progress { - /// Create a ProgressBar using a function that checks periodically for the progress - /// If precise is true, the total_input_size will be displayed as the total_bytes size - /// If ACCESSIBLE is set, this function returns None - pub fn new_accessible_aware( - total_input_size: u64, - precise: bool, - current_position_fn: Option u64 + Send>>, - ) -> Option { - if is_running_in_accessible_mode() { - return None; - } - Some(Self::new(total_input_size, precise, current_position_fn)) - } - - fn new(total_input_size: u64, precise: bool, current_position_fn: Option u64 + Send>>) -> Self { - let (draw_tx, draw_rx) = mpsc::channel(); - let (clean_tx, clean_rx) = mpsc::channel(); - let (msg_tx, msg_rx) = mpsc::channel(); - - thread::spawn(move || { - let template = { - let mut t = String::new(); - t += "{wide_msg} [{elapsed_precise}] "; - if precise && current_position_fn.is_some() { - t += "[{bar:.cyan/blue}] "; - } else { - t += "{spinner:.green} "; - } - if current_position_fn.is_some() { - t += "{bytes}/ "; - } - if precise { - t += "{total_bytes} "; - } - t += "({bytes_per_sec}, {eta}) {path}"; - t - }; - let bar = ProgressBar::new(total_input_size) - .with_style(ProgressStyle::with_template(&template).unwrap().progress_chars("#>-")); - - while draw_rx.try_recv().is_err() { - if let Some(ref pos_fn) = current_position_fn { - bar.set_position(pos_fn()); - } else { - bar.tick(); - } - if let Ok(msg) = msg_rx.try_recv() { - bar.set_message(msg); - } - thread::sleep(Duration::from_millis(100)); + pub(crate) fn new(total_input_size: u64, precise: bool, position_updates: bool) -> Self { + let template = { + let mut t = String::new(); + t += "{wide_msg} [{elapsed_precise}] "; + if precise && position_updates { + t += "[{bar:.cyan/blue}] "; + } else { + t += "{spinner:.green} "; } - bar.finish(); - let _ = clean_tx.send(()); - }); + if position_updates { + t += "{bytes}/ "; + } + if precise { + t += "{total_bytes} "; + } + t += "({bytes_per_sec}, {eta}) {path}"; + t + }; + let bar = ProgressBar::new(total_input_size) + .with_style(ProgressStyle::with_template(&template).unwrap().progress_chars("#>-")); - Progress { - draw_stop: draw_tx, - clean_done: clean_rx, - display_handle: DisplayHandle { - buf: Vec::new(), - sender: msg_tx, - }, - } + Progress { bar, buf: Vec::new() } } - pub(crate) fn display_handle(&mut self) -> &mut dyn io::Write { - &mut self.display_handle - } -} -impl Drop for Progress { - fn drop(&mut self) { - let _ = self.draw_stop.send(()); - let _ = self.clean_done.recv(); + pub(crate) fn wrap_read(&self, read: R) -> ProgressBarIter { + self.bar.wrap_read(read) + } + + pub(crate) fn wrap_write(&self, write: W) -> ProgressBarIter { + self.bar.wrap_write(write) } } From 759f322722aec57ec6f0a987699151b1862a5be5 Mon Sep 17 00:00:00 2001 From: figsoda Date: Wed, 12 Oct 2022 20:53:34 -0400 Subject: [PATCH 2/5] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0f3166..cf79543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ Categories Used: - Clean up the description for the `-d/--dir` argument to `decompress` [\#264](https://github.com/ouch-org/ouch/pull/264) ([hivehand](https://github.com/hivehand)) - Show subcommand aliases on --help [\#275](https://github.com/ouch-org/ouch/pull/275) ([marcospb19](https://github.com/marcospb19)) - Update dependencies [\#276](https://github.com/ouch-org/ouch/pull/276) ([figsoda](https://github.com/figsoda)) +- Rewrite progress module [\#280](https://github.com/ouch-org/ouch/pull/280) ([figsoda](https://github.com/figsoda)) ### New Contributors From dd6ab2aa6e84e308056e2fbc587da0e82484c568 Mon Sep 17 00:00:00 2001 From: figsoda Date: Thu, 13 Oct 2022 10:27:04 -0400 Subject: [PATCH 3/5] rename out to log_out --- src/archive/tar.rs | 12 ++++++++---- src/archive/zip.rs | 14 +++++++++----- src/macros.rs | 18 +++++++++--------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 101a0e4..f183698 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -19,7 +19,11 @@ use crate::{ /// Unpacks the archive given by `archive` into the folder given by `into`. /// Assumes that output_folder is empty -pub fn unpack_archive(reader: Box, output_folder: &Path, mut out: impl Write) -> crate::Result> { +pub fn unpack_archive( + reader: Box, + output_folder: &Path, + mut log_out: impl Write, +) -> crate::Result> { assert!(output_folder.read_dir().expect("dir exists").count() == 0); let mut archive = tar::Archive::new(reader); @@ -36,7 +40,7 @@ pub fn unpack_archive(reader: Box, output_folder: &Path, mut out: impl // and so on info!( - @out, + @log_out, inaccessible, "{:?} extracted. ({})", utils::strip_cur_dir(&output_folder.join(file.path()?)), Bytes::new(file.size()) @@ -82,7 +86,7 @@ pub fn build_archive_from_paths( input_filenames: &[PathBuf], writer: W, file_visibility_policy: FileVisibilityPolicy, - mut out: D, + mut log_out: D, ) -> crate::Result where W: Write, @@ -104,7 +108,7 @@ where // little importance for most users, but would generate lots of // spoken text for users using screen readers, braille displays // and so on - info!(@out, inaccessible, "Compressing '{}'.", utils::to_utf(path)); + info!(@log_out, inaccessible, "Compressing '{}'.", utils::to_utf(path)); if path.is_dir() { builder.append_dir(path, path)?; diff --git a/src/archive/zip.rs b/src/archive/zip.rs index ed27b80..9b18318 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -27,7 +27,11 @@ use crate::{ /// Unpacks the archive given by `archive` into the folder given by `output_folder`. /// Assumes that output_folder is empty -pub fn unpack_archive(mut archive: ZipArchive, output_folder: &Path, mut out: D) -> crate::Result> +pub fn unpack_archive( + mut archive: ZipArchive, + output_folder: &Path, + mut log_out: D, +) -> crate::Result> where R: Read + Seek, D: Write, @@ -53,7 +57,7 @@ where // importance for most users, but would generate lots of // spoken text for users using screen readers, braille displays // and so on - info!(@out, inaccessible, "File {} extracted to \"{}\"", idx, file_path.display()); + info!(@log_out, inaccessible, "File {} extracted to \"{}\"", idx, file_path.display()); fs::create_dir_all(&file_path)?; } _is_file @ false => { @@ -66,7 +70,7 @@ where // same reason is in _is_dir: long, often not needed text info!( - @out, + @log_out, inaccessible, "{:?} extracted. ({})", file_path.display(), Bytes::new(file.size()) @@ -133,7 +137,7 @@ pub fn build_archive_from_paths( input_filenames: &[PathBuf], writer: W, file_visibility_policy: FileVisibilityPolicy, - mut out: D, + mut log_out: D, ) -> crate::Result where W: Write + Seek, @@ -173,7 +177,7 @@ where // little importance for most users, but would generate lots of // spoken text for users using screen readers, braille displays // and so on - info!(@out, inaccessible, "Compressing '{}'.", to_utf(path)); + info!(@log_out, inaccessible, "Compressing '{}'.", to_utf(path)); let metadata = match path.metadata() { Ok(metadata) => metadata, diff --git a/src/macros.rs b/src/macros.rs index 6150a2b..40f89e7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -18,7 +18,7 @@ use crate::accessible::is_running_in_accessible_mode; /// ability to skip some lines deemed not important like a seeing person would. /// /// By default `info` outputs to Stdout, if you want to specify the output you can use -/// `@out` modifier +/// `@log_out` modifier #[macro_export] macro_rules! info { @@ -27,24 +27,24 @@ macro_rules! info { (accessible, $($arg:tt)*) => { info!(@::std::io::stdout(), accessible, $($arg)*); }; - (@$out: expr, accessible, $($arg:tt)*) => { - let out = &mut $out; + (@$log_out: expr, accessible, $($arg:tt)*) => { + let log_out = &mut $log_out; // if in ACCESSIBLE mode, suppress the "[INFO]" and just print the message if !$crate::accessible::is_running_in_accessible_mode() { - $crate::macros::_info_helper(out); + $crate::macros::_info_helper(log_out); } - writeln!(out, $($arg)*).unwrap(); + writeln!(log_out, $($arg)*).unwrap(); }; // Inccessible (long/no important) info message. // Print info message if ACCESSIBLE is not turned on (inaccessible, $($arg:tt)*) => { info!(@::std::io::stdout(), inaccessible, $($arg)*); }; - (@$out: expr, inaccessible, $($arg:tt)*) => { + (@$log_out: expr, inaccessible, $($arg:tt)*) => { if !$crate::accessible::is_running_in_accessible_mode() { - let out = &mut $out; - $crate::macros::_info_helper(out); - writeln!(out, $($arg)*).unwrap(); + let log_out = &mut $log_out; + $crate::macros::_info_helper(log_out); + writeln!(log_out, $($arg)*).unwrap(); } }; } From 51855948f1b4acff6e4b50346fcb0f6f9ab827a5 Mon Sep 17 00:00:00 2001 From: figsoda Date: Thu, 13 Oct 2022 11:31:43 -0400 Subject: [PATCH 4/5] add OutputLine trait for performance improvements --- src/archive/tar.rs | 5 ++-- src/archive/zip.rs | 5 ++-- src/commands/compress.rs | 4 +-- src/commands/decompress.rs | 8 +++--- src/commands/mod.rs | 2 +- src/macros.rs | 29 +++++++------------- src/progress.rs | 55 +++++++++++++++++++++++++------------- src/utils/fs.rs | 4 +-- 8 files changed, 61 insertions(+), 51 deletions(-) diff --git a/src/archive/tar.rs b/src/archive/tar.rs index f183698..9d900f0 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -14,6 +14,7 @@ use crate::{ error::FinalError, info, list::FileInArchive, + progress::OutputLine, utils::{self, Bytes, FileVisibilityPolicy}, }; @@ -22,7 +23,7 @@ use crate::{ pub fn unpack_archive( reader: Box, output_folder: &Path, - mut log_out: impl Write, + mut log_out: impl OutputLine, ) -> crate::Result> { assert!(output_folder.read_dir().expect("dir exists").count() == 0); let mut archive = tar::Archive::new(reader); @@ -90,7 +91,7 @@ pub fn build_archive_from_paths( ) -> crate::Result where W: Write, - D: Write, + D: OutputLine, { let mut builder = tar::Builder::new(writer); diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 9b18318..c804a91 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -19,6 +19,7 @@ use crate::{ error::FinalError, info, list::FileInArchive, + progress::OutputLine, utils::{ self, cd_into_same_dir_as, get_invalid_utf8_paths, pretty_format_list_of_paths, strip_cur_dir, to_utf, Bytes, FileVisibilityPolicy, @@ -34,7 +35,7 @@ pub fn unpack_archive( ) -> crate::Result> where R: Read + Seek, - D: Write, + D: OutputLine, { assert!(output_folder.read_dir().expect("dir exists").count() == 0); @@ -141,7 +142,7 @@ pub fn build_archive_from_paths( ) -> crate::Result where W: Write + Seek, - D: Write, + D: OutputLine, { let mut writer = zip::ZipWriter::new(writer); let options = zip::write::FileOptions::default(); diff --git a/src/commands/compress.rs b/src/commands/compress.rs index 440b9fc..1054b29 100644 --- a/src/commands/compress.rs +++ b/src/commands/compress.rs @@ -89,7 +89,7 @@ pub fn compress_files( } Tar => { if is_running_in_accessible_mode() { - archive::tar::build_archive_from_paths(&files, &mut writer, file_visibility_policy, io::stdout())?; + archive::tar::build_archive_from_paths(&files, &mut writer, file_visibility_policy, io::stderr())?; writer.flush()?; } else { let mut progress = Progress::new(total_input_size, precise, true); @@ -111,7 +111,7 @@ pub fn compress_files( let mut vec_buffer = Cursor::new(vec![]); if is_running_in_accessible_mode() { - archive::zip::build_archive_from_paths(&files, &mut vec_buffer, file_visibility_policy, io::stdout())?; + archive::zip::build_archive_from_paths(&files, &mut vec_buffer, file_visibility_policy, io::stderr())?; vec_buffer.rewind()?; io::copy(&mut vec_buffer, &mut writer)?; } else { diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index bddf7eb..66ec78e 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -1,5 +1,5 @@ use std::{ - io::{self, BufReader, Read, Write}, + io::{self, BufReader, Read}, ops::ControlFlow, path::{Path, PathBuf}, }; @@ -15,7 +15,7 @@ use crate::{ Extension, }, info, - progress::Progress, + progress::{OutputLine, Progress}, utils::{self, nice_directory_display, user_wants_to_continue}, QuestionAction, QuestionPolicy, BUFFER_CAPACITY, }; @@ -183,7 +183,7 @@ pub fn decompress_file( /// output_dir named after the archive (given by `output_file_path`) /// Note: This functions assumes that `output_dir` exists fn smart_unpack( - unpack_fn: impl FnOnce(&Path, &mut dyn Write) -> crate::Result>, + unpack_fn: impl FnOnce(&Path, &mut dyn OutputLine) -> crate::Result>, total_input_size: u64, output_dir: &Path, output_file_path: &Path, @@ -200,7 +200,7 @@ fn smart_unpack( // unpack the files let files = if is_running_in_accessible_mode() { - unpack_fn(temp_dir_path, &mut io::stdout()) + unpack_fn(temp_dir_path, &mut io::stderr()) } else { unpack_fn(temp_dir_path, &mut Progress::new(total_input_size, true, false)) }?; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 9236a25..b1c3fec 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,7 +5,6 @@ mod decompress; mod list; use std::{ - io::Write, ops::ControlFlow, path::{Path, PathBuf}, }; @@ -19,6 +18,7 @@ use crate::{ extension::{self, flatten_compression_formats, Extension, SUPPORTED_EXTENSIONS}, info, list::ListOptions, + progress::OutputLine, utils::{ self, dir_is_empty, pretty_format_list_of_paths, to_utf, try_infer_extension, user_wants_to_continue, FileVisibilityPolicy, diff --git a/src/macros.rs b/src/macros.rs index 40f89e7..8d54d7a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -25,35 +25,26 @@ macro_rules! info { // Accessible (short/important) info message. // Show info message even in ACCESSIBLE mode (accessible, $($arg:tt)*) => { - info!(@::std::io::stdout(), accessible, $($arg)*); + info!(@::std::io::stderr(), accessible, $($arg)*); }; - (@$log_out: expr, accessible, $($arg:tt)*) => { - let log_out = &mut $log_out; + (@$log_out: expr, accessible, $($arg:tt)*) => {{ // if in ACCESSIBLE mode, suppress the "[INFO]" and just print the message if !$crate::accessible::is_running_in_accessible_mode() { - $crate::macros::_info_helper(log_out); + $log_out.output_line_info(format_args!($($arg)*)); + } else { + $log_out.output_line(format_args!($($arg)*)); } - writeln!(log_out, $($arg)*).unwrap(); - }; + }}; // Inccessible (long/no important) info message. // Print info message if ACCESSIBLE is not turned on (inaccessible, $($arg:tt)*) => { - info!(@::std::io::stdout(), inaccessible, $($arg)*); + info!(@::std::io::stderr(), inaccessible, $($arg)*); }; - (@$log_out: expr, inaccessible, $($arg:tt)*) => { + (@$log_out: expr, inaccessible, $($arg:tt)*) => {{ if !$crate::accessible::is_running_in_accessible_mode() { - let log_out = &mut $log_out; - $crate::macros::_info_helper(log_out); - writeln!(log_out, $($arg)*).unwrap(); + $log_out.output_line_info(format_args!($($arg)*)); } - }; -} - -/// Helper to display "\[INFO\]", colored yellow -pub fn _info_helper(handle: &mut impl std::io::Write) { - use crate::utils::colors::{RESET, YELLOW}; - - write!(handle, "{}[INFO]{} ", *YELLOW, *RESET).unwrap(); + }}; } /// Macro that prints \[WARNING\] messages, wraps [`eprintln`]. diff --git a/src/progress.rs b/src/progress.rs index 5c2a530..5e17c12 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -1,35 +1,52 @@ //! Module that provides functions to display progress bars for compressing and decompressing files. use std::{ - io::{self, Read, Write}, - mem, + fmt::Arguments, + io::{Read, Stderr, Write}, }; use indicatif::{ProgressBar, ProgressBarIter, ProgressStyle}; +use crate::utils::colors::{RESET, YELLOW}; + /// Draw a ProgressBar using a function that checks periodically for the progress pub struct Progress { bar: ProgressBar, - buf: Vec, } -impl Write for Progress { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend(buf); +pub trait OutputLine { + fn output_line(&mut self, args: Arguments); + fn output_line_info(&mut self, args: Arguments); +} - if self.buf.last() == Some(&b'\n') { - self.buf.pop(); - self.flush()?; - } - - Ok(buf.len()) +impl OutputLine for Progress { + fn output_line(&mut self, args: Arguments) { + self.bar.set_message(args.to_string()); } - fn flush(&mut self) -> io::Result<()> { - self.bar.set_message( - String::from_utf8(mem::take(&mut self.buf)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to parse buffer content as utf8"))?, - ); - Ok(()) + fn output_line_info(&mut self, args: Arguments) { + self.bar.set_message(format!("{}[INFO]{}{args}", *YELLOW, *RESET)); + } +} + +impl OutputLine for Stderr { + fn output_line(&mut self, args: Arguments) { + self.write_fmt(args).unwrap(); + } + + fn output_line_info(&mut self, args: Arguments) { + write!(self, "{}[INFO]{} {args}", *YELLOW, *RESET).unwrap(); + self.write_fmt(args).unwrap(); + self.write_all(b"\n").unwrap(); + } +} + +impl OutputLine for &mut T { + fn output_line(&mut self, args: Arguments) { + (*self).output_line(args) + } + + fn output_line_info(&mut self, args: Arguments) { + (*self).output_line_info(args); } } @@ -55,7 +72,7 @@ impl Progress { let bar = ProgressBar::new(total_input_size) .with_style(ProgressStyle::with_template(&template).unwrap().progress_chars("#>-")); - Progress { bar, buf: Vec::new() } + Progress { bar } } pub(crate) fn wrap_read(&self, read: R) -> ProgressBarIter { diff --git a/src/utils/fs.rs b/src/utils/fs.rs index ac514f7..241ec97 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -3,14 +3,14 @@ use std::{ env, fs::ReadDir, - io::{Read, Write}, + io::Read, path::{Path, PathBuf}, }; use fs_err as fs; use super::{to_utf, user_wants_to_overwrite}; -use crate::{extension::Extension, info, QuestionPolicy}; +use crate::{extension::Extension, info, progress::OutputLine, QuestionPolicy}; /// Checks if given path points to an empty directory. pub fn dir_is_empty(dir_path: &Path) -> bool { From 889f1d9c3501a6977609dfefad24caa4fb7a19aa Mon Sep 17 00:00:00 2001 From: figsoda Date: Thu, 13 Oct 2022 11:51:35 -0400 Subject: [PATCH 5/5] fix typo --- src/progress.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/progress.rs b/src/progress.rs index 5e17c12..767aa02 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -24,7 +24,7 @@ impl OutputLine for Progress { } fn output_line_info(&mut self, args: Arguments) { - self.bar.set_message(format!("{}[INFO]{}{args}", *YELLOW, *RESET)); + self.bar.set_message(format!("{}[INFO]{} {args}", *YELLOW, *RESET)); } }