This commit is contained in:
Antonios Barotsis 2024-03-06 22:33:24 +01:00
parent 5b0e0b6991
commit 3a3f38c93e
6 changed files with 107 additions and 44 deletions

View File

@ -4,7 +4,7 @@ use std::{
env, env,
io::prelude::*, io::prelude::*,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::mpsc::{self, Receiver}, sync::mpsc::{self, Receiver, Sender},
thread, thread,
}; };
@ -15,13 +15,13 @@ use crate::{
error::FinalError, error::FinalError,
info, info,
list::FileInArchive, list::FileInArchive,
utils::{self, Bytes, EscapedPathDisplay, FileVisibilityPolicy}, utils::{self, io::PrintMessage, Bytes, EscapedPathDisplay, FileVisibilityPolicy},
warning, warning,
}; };
/// Unpacks the archive given by `archive` into the folder given by `into`. /// Unpacks the archive given by `archive` into the folder given by `into`.
/// Assumes that output_folder is empty /// Assumes that output_folder is empty
pub fn unpack_archive(reader: Box<dyn Read>, output_folder: &Path, quiet: bool) -> crate::Result<usize> { pub fn unpack_archive(reader: Box<dyn Read>, output_folder: &Path, quiet: bool, log_sender: Sender<PrintMessage>) -> crate::Result<usize> {
assert!(output_folder.read_dir().expect("dir exists").count() == 0); assert!(output_folder.read_dir().expect("dir exists").count() == 0);
let mut archive = tar::Archive::new(reader); let mut archive = tar::Archive::new(reader);
@ -36,12 +36,14 @@ pub fn unpack_archive(reader: Box<dyn Read>, output_folder: &Path, quiet: bool)
// spoken text for users using screen readers, braille displays // spoken text for users using screen readers, braille displays
// and so on // and so on
if !quiet { if !quiet {
info!( log_sender.send(PrintMessage {
inaccessible, contents: format!(
"{:?} extracted. ({})", "{:?} extracted. ({})",
utils::strip_cur_dir(&output_folder.join(file.path()?)), utils::strip_cur_dir(&output_folder.join(file.path()?)),
Bytes::new(file.size()), Bytes::new(file.size()),
); ),
accessible: false
}).unwrap();
files_unpacked += 1; files_unpacked += 1;
} }

View File

@ -1,7 +1,7 @@
use std::{ use std::{
io::{self, BufReader, Read}, io::{self, BufReader, Read},
ops::ControlFlow, ops::ControlFlow,
path::{Path, PathBuf}, path::{Path, PathBuf}, sync::mpsc::Sender,
}; };
use fs_err as fs; use fs_err as fs;
@ -14,7 +14,7 @@ use crate::{
Extension, Extension,
}, },
info, 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, QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
}; };
@ -31,6 +31,7 @@ pub fn decompress_file(
output_file_path: PathBuf, output_file_path: PathBuf,
question_policy: QuestionPolicy, question_policy: QuestionPolicy,
quiet: bool, quiet: bool,
log_sender: Sender<PrintMessage>,
) -> crate::Result<()> { ) -> crate::Result<()> {
assert!(output_dir.exists()); assert!(output_dir.exists());
let reader = fs::File::open(input_file_path)?; let reader = fs::File::open(input_file_path)?;
@ -53,6 +54,7 @@ pub fn decompress_file(
output_dir, output_dir,
&output_file_path, &output_file_path,
question_policy, question_policy,
log_sender.clone(),
)? { )? {
files files
} else { } else {
@ -63,12 +65,14 @@ pub fn decompress_file(
// having a final status message is important especially in an accessibility context // 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 // as screen readers may not read a commands exit code, making it hard to reason
// about whether the command succeeded without such a message // about whether the command succeeded without such a message
info!( log_sender.send(PrintMessage {
accessible, contents: format!(
"Successfully decompressed archive in {} ({} files).", "Successfully decompressed archive in {} ({} files).",
nice_directory_display(output_dir), nice_directory_display(output_dir),
files_unpacked files_unpacked
); ),
accessible: true
}).unwrap();
return Ok(()); return Ok(());
} }
@ -112,10 +116,11 @@ pub fn decompress_file(
} }
Tar => { Tar => {
if let ControlFlow::Continue(files) = smart_unpack( 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_dir,
&output_file_path, &output_file_path,
question_policy, question_policy,
log_sender.clone(),
)? { )? {
files files
} else { } else {
@ -140,6 +145,7 @@ pub fn decompress_file(
output_dir, output_dir,
&output_file_path, &output_file_path,
question_policy, question_policy,
log_sender.clone(),
)? { )? {
files files
} else { } else {
@ -158,7 +164,7 @@ pub fn decompress_file(
}; };
if let ControlFlow::Continue(files) = 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 files
} else { } else {
@ -186,6 +192,7 @@ pub fn decompress_file(
output_dir, output_dir,
&output_file_path, &output_file_path,
question_policy, question_policy,
log_sender.clone(),
)? { )? {
files files
} else { } else {
@ -198,12 +205,17 @@ pub fn decompress_file(
// having a final status message is important especially in an accessibility context // 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 // as screen readers may not read a commands exit code, making it hard to reason
// about whether the command succeeded without such a message // about whether the command succeeded without such a message
info!( log_sender.send(PrintMessage {
accessible, contents: format!(
"Successfully decompressed archive in {}.", "Successfully decompressed archive in {}.",
nice_directory_display(output_dir) nice_directory_display(output_dir)
); ),
info!(accessible, "Files unpacked: {}", files_unpacked); accessible: true
}).unwrap();
log_sender.send(PrintMessage {
contents: format!("Files unpacked: {}", files_unpacked),
accessible: true
}).unwrap();
Ok(()) Ok(())
} }
@ -218,15 +230,19 @@ fn smart_unpack(
output_dir: &Path, output_dir: &Path,
output_file_path: &Path, output_file_path: &Path,
question_policy: QuestionPolicy, question_policy: QuestionPolicy,
log_sender: Sender<PrintMessage>,
) -> crate::Result<ControlFlow<(), usize>> { ) -> crate::Result<ControlFlow<(), usize>> {
assert!(output_dir.exists()); assert!(output_dir.exists());
let temp_dir = tempfile::tempdir_in(output_dir)?; let temp_dir = tempfile::tempdir_in(output_dir)?;
let temp_dir_path = temp_dir.path(); let temp_dir_path = temp_dir.path();
info!(
accessible, log_sender.send(PrintMessage {
"Created temporary directory {} to hold decompressed elements.", contents: format!(
nice_directory_display(temp_dir_path) "Created temporary directory {} to hold decompressed elements.",
); nice_directory_display(temp_dir_path)
),
accessible: true
}).unwrap();
let files = unpack_fn(temp_dir_path)?; let files = unpack_fn(temp_dir_path)?;
@ -244,12 +260,15 @@ fn smart_unpack(
return Ok(ControlFlow::Break(())); return Ok(ControlFlow::Break(()));
} }
fs::rename(&file_path, &correct_path)?; fs::rename(&file_path, &correct_path)?;
info!(
accessible, log_sender.send(PrintMessage {
"Successfully moved {} to {}.", contents: format!(
nice_directory_display(&file_path), "Successfully moved {} to {}.",
nice_directory_display(&correct_path) nice_directory_display(&file_path),
); nice_directory_display(&correct_path)
),
accessible: true
}).unwrap();
} else { } else {
// Multiple files in the root directory, so: // Multiple files in the root directory, so:
// Rename the temporary directory to the archive name, which is output_file_path // Rename the temporary directory to the archive name, which is output_file_path
@ -258,12 +277,14 @@ fn smart_unpack(
return Ok(ControlFlow::Break(())); return Ok(ControlFlow::Break(()));
} }
fs::rename(temp_dir_path, output_file_path)?; fs::rename(temp_dir_path, output_file_path)?;
info!( log_sender.send(PrintMessage {
accessible, contents: format!(
"Successfully moved {} to {}.", "Successfully moved {} to {}.",
nice_directory_display(temp_dir_path), nice_directory_display(temp_dir_path),
nice_directory_display(output_file_path) nice_directory_display(output_file_path)
); ),
accessible: true
}).unwrap();
} }
Ok(ControlFlow::Continue(files)) Ok(ControlFlow::Continue(files))

View File

@ -4,7 +4,7 @@ mod compress;
mod decompress; mod decompress;
mod list; 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 rayon::prelude::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use utils::colors; use utils::colors;
@ -17,7 +17,7 @@ use crate::{
extension::{self, parse_format}, extension::{self, parse_format},
info, info,
list::ListOptions, list::ListOptions,
utils::{self, to_utf, EscapedPathDisplay, FileVisibilityPolicy}, utils::{self, io::PrintMessage, to_utf, EscapedPathDisplay, FileVisibilityPolicy},
warning, CliArgs, QuestionPolicy, warning, CliArgs, QuestionPolicy,
}; };
@ -169,6 +169,27 @@ pub fn run(
PathBuf::from(".") PathBuf::from(".")
}; };
let (log_sender, log_receiver) = channel::<PrintMessage>();
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 files
.par_iter() .par_iter()
.zip(formats) .zip(formats)
@ -182,8 +203,20 @@ pub fn run(
output_file_path, output_file_path,
question_policy, question_policy,
args.quiet, 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 } => { Subcommand::List { archives: files, tree } => {
let mut formats = vec![]; let mut formats = vec![];

5
src/utils/io.rs Normal file
View File

@ -0,0 +1,5 @@
#[derive(Debug)]
pub struct PrintMessage {
pub contents: String,
pub accessible: bool,
}

View File

@ -7,6 +7,7 @@ pub mod colors;
mod file_visibility; mod file_visibility;
mod formatting; mod formatting;
mod fs; mod fs;
pub mod io;
mod question; mod question;
pub use file_visibility::FileVisibilityPolicy; pub use file_visibility::FileVisibilityPolicy;

View File

@ -142,6 +142,7 @@ impl<'a> Confirmation<'a> {
*colors::RESET *colors::RESET
); );
} }
let _stdout_lock = io::stdout().lock();
io::stdout().flush()?; io::stdout().flush()?;
let mut answer = String::new(); let mut answer = String::new();