diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 8222122..b8f01f9 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -4,7 +4,7 @@ use std::{ env, io::prelude::*, path::{Path, PathBuf}, - sync::mpsc::{self, Receiver}, + sync::mpsc::{self, Receiver, Sender}, thread, }; @@ -15,13 +15,13 @@ use crate::{ error::FinalError, info, list::FileInArchive, - utils::{self, Bytes, EscapedPathDisplay, FileVisibilityPolicy}, + utils::{self, io::PrintMessage, Bytes, EscapedPathDisplay, FileVisibilityPolicy}, warning, }; /// 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, quiet: bool) -> crate::Result { +pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool, log_sender: Sender) -> crate::Result { assert!(output_folder.read_dir().expect("dir exists").count() == 0); let mut archive = tar::Archive::new(reader); @@ -36,12 +36,14 @@ pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool) // spoken text for users using screen readers, braille displays // and so on if !quiet { - info!( - inaccessible, - "{:?} extracted. ({})", - utils::strip_cur_dir(&output_folder.join(file.path()?)), - Bytes::new(file.size()), - ); + log_sender.send(PrintMessage { + contents: format!( + "{:?} extracted. ({})", + utils::strip_cur_dir(&output_folder.join(file.path()?)), + Bytes::new(file.size()), + ), + accessible: false + }).unwrap(); files_unpacked += 1; } diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 5e611f9..ed17f43 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -1,7 +1,7 @@ use std::{ io::{self, BufReader, Read}, ops::ControlFlow, - path::{Path, PathBuf}, + path::{Path, PathBuf}, sync::mpsc::Sender, }; use fs_err as fs; @@ -14,7 +14,7 @@ use crate::{ Extension, }, info, - utils::{self, nice_directory_display, user_wants_to_continue}, + utils::{self, io::PrintMessage, nice_directory_display, user_wants_to_continue}, QuestionAction, QuestionPolicy, BUFFER_CAPACITY, }; @@ -31,6 +31,7 @@ pub fn decompress_file( output_file_path: PathBuf, question_policy: QuestionPolicy, quiet: bool, + log_sender: Sender, ) -> crate::Result<()> { assert!(output_dir.exists()); let reader = fs::File::open(input_file_path)?; @@ -53,6 +54,7 @@ pub fn decompress_file( output_dir, &output_file_path, question_policy, + log_sender.clone(), )? { files } else { @@ -63,12 +65,14 @@ pub fn decompress_file( // having a final status message is important especially in an accessibility context // as screen readers may not read a commands exit code, making it hard to reason // about whether the command succeeded without such a message - info!( - accessible, - "Successfully decompressed archive in {} ({} files).", - nice_directory_display(output_dir), - files_unpacked - ); + log_sender.send(PrintMessage { + contents: format!( + "Successfully decompressed archive in {} ({} files).", + nice_directory_display(output_dir), + files_unpacked + ), + accessible: true + }).unwrap(); return Ok(()); } @@ -112,10 +116,11 @@ pub fn decompress_file( } Tar => { if let ControlFlow::Continue(files) = smart_unpack( - |output_dir| crate::archive::tar::unpack_archive(reader, output_dir, quiet), + |output_dir| crate::archive::tar::unpack_archive(reader, output_dir, quiet, log_sender.clone()), output_dir, &output_file_path, question_policy, + log_sender.clone(), )? { files } else { @@ -140,6 +145,7 @@ pub fn decompress_file( output_dir, &output_file_path, question_policy, + log_sender.clone(), )? { files } else { @@ -158,7 +164,7 @@ pub fn decompress_file( }; if let ControlFlow::Continue(files) = - smart_unpack(unpack_fn, output_dir, &output_file_path, question_policy)? + smart_unpack(unpack_fn, output_dir, &output_file_path, question_policy, log_sender.clone())? { files } else { @@ -186,6 +192,7 @@ pub fn decompress_file( output_dir, &output_file_path, question_policy, + log_sender.clone(), )? { files } else { @@ -198,12 +205,17 @@ pub fn decompress_file( // having a final status message is important especially in an accessibility context // as screen readers may not read a commands exit code, making it hard to reason // about whether the command succeeded without such a message - info!( - accessible, - "Successfully decompressed archive in {}.", - nice_directory_display(output_dir) - ); - info!(accessible, "Files unpacked: {}", files_unpacked); + log_sender.send(PrintMessage { + contents: format!( + "Successfully decompressed archive in {}.", + nice_directory_display(output_dir) + ), + accessible: true + }).unwrap(); + log_sender.send(PrintMessage { + contents: format!("Files unpacked: {}", files_unpacked), + accessible: true + }).unwrap(); Ok(()) } @@ -218,15 +230,19 @@ fn smart_unpack( output_dir: &Path, output_file_path: &Path, question_policy: QuestionPolicy, + log_sender: Sender, ) -> crate::Result> { assert!(output_dir.exists()); let temp_dir = tempfile::tempdir_in(output_dir)?; let temp_dir_path = temp_dir.path(); - info!( - accessible, - "Created temporary directory {} to hold decompressed elements.", - nice_directory_display(temp_dir_path) - ); + + log_sender.send(PrintMessage { + contents: format!( + "Created temporary directory {} to hold decompressed elements.", + nice_directory_display(temp_dir_path) + ), + accessible: true + }).unwrap(); let files = unpack_fn(temp_dir_path)?; @@ -244,12 +260,15 @@ fn smart_unpack( return Ok(ControlFlow::Break(())); } fs::rename(&file_path, &correct_path)?; - info!( - accessible, - "Successfully moved {} to {}.", - nice_directory_display(&file_path), - nice_directory_display(&correct_path) - ); + + log_sender.send(PrintMessage { + contents: format!( + "Successfully moved {} to {}.", + nice_directory_display(&file_path), + nice_directory_display(&correct_path) + ), + accessible: true + }).unwrap(); } else { // Multiple files in the root directory, so: // Rename the temporary directory to the archive name, which is output_file_path @@ -258,12 +277,14 @@ fn smart_unpack( return Ok(ControlFlow::Break(())); } fs::rename(temp_dir_path, output_file_path)?; - info!( - accessible, - "Successfully moved {} to {}.", - nice_directory_display(temp_dir_path), - nice_directory_display(output_file_path) - ); + log_sender.send(PrintMessage { + contents: format!( + "Successfully moved {} to {}.", + nice_directory_display(temp_dir_path), + nice_directory_display(output_file_path) + ), + accessible: true + }).unwrap(); } Ok(ControlFlow::Continue(files)) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b9763d1..c0f751d 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,7 +4,7 @@ mod compress; mod decompress; mod list; -use std::{ops::ControlFlow, path::PathBuf}; +use std::{ops::ControlFlow, path::PathBuf, sync::{mpsc::channel, Arc, Condvar, Mutex}}; use rayon::prelude::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use utils::colors; @@ -17,7 +17,7 @@ use crate::{ extension::{self, parse_format}, info, list::ListOptions, - utils::{self, to_utf, EscapedPathDisplay, FileVisibilityPolicy}, + utils::{self, io::PrintMessage, to_utf, EscapedPathDisplay, FileVisibilityPolicy}, warning, CliArgs, QuestionPolicy, }; @@ -169,6 +169,27 @@ pub fn run( PathBuf::from(".") }; + let (log_sender, log_receiver) = channel::(); + + let pair = Arc::new((Mutex::new(false), Condvar::new())); + let pair2 = Arc::clone(&pair); + + // Log received messages until all senders are dropped + rayon::spawn(move || { + loop { + let msg = log_receiver.recv(); + if let Ok(msg) = msg { + println!("{}", msg.contents); + } else { + let (lock, cvar) = &*pair2; + let mut flushed = lock.lock().unwrap(); + *flushed = true; + cvar.notify_one(); + break; + } + } + }); + files .par_iter() .zip(formats) @@ -182,8 +203,20 @@ pub fn run( output_file_path, question_policy, args.quiet, + log_sender.clone(), ) })?; + + // Drop our sender clones so when all threads are done, no clones are left + drop(log_sender); + + // Prevent the main thread from exiting until the background thread handling the + // logging has set `flushed` to true. + let (lock, cvar) = &*pair; + let mut flushed = lock.lock().unwrap(); + while !*flushed { + flushed = cvar.wait(flushed).unwrap(); + } } Subcommand::List { archives: files, tree } => { let mut formats = vec![]; diff --git a/src/utils/io.rs b/src/utils/io.rs new file mode 100644 index 0000000..618077f --- /dev/null +++ b/src/utils/io.rs @@ -0,0 +1,5 @@ +#[derive(Debug)] +pub struct PrintMessage { + pub contents: String, + pub accessible: bool, +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b6ea08e..57b545a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,6 +7,7 @@ pub mod colors; mod file_visibility; mod formatting; mod fs; +pub mod io; mod question; pub use file_visibility::FileVisibilityPolicy; diff --git a/src/utils/question.rs b/src/utils/question.rs index 713931e..c548258 100644 --- a/src/utils/question.rs +++ b/src/utils/question.rs @@ -142,6 +142,7 @@ impl<'a> Confirmation<'a> { *colors::RESET ); } + let _stdout_lock = io::stdout().lock(); io::stdout().flush()?; let mut answer = String::new();