this repo has no description
1//! Shared I/O plumbing for single-stream compressors.
2//!
3//! Every single-file codec (gzip, xz, bzip2, zstd, lz4, brotli, snappy, lzma)
4//! has the same shape: resolve the input into a `Read`, resolve the output
5//! into a `Write`, reject directory inputs/outputs, and forward the in-memory
6//! `Reader`/`Writer` variants untouched for pipeline stages. These helpers
7//! consolidate that plumbing so each backend only expresses its codec choice.
8
9use crate::progress::{OutputTarget, ProgressArgs, copy_with_progress};
10use crate::utils::{CmprssInput, CmprssOutput, Result, WriteWrapper};
11use anyhow::bail;
12use std::fs::File;
13use std::io::{self, BufReader, BufWriter, Read, Write};
14
15/// Resolve a `CmprssInput` into a single boxed `Read` stream for single-stream
16/// codecs. Returns the stream together with the input file's size when known
17/// (used to drive progress bars).
18///
19/// Bails when multiple input paths are given, or when a path input is a
20/// directory — single-stream codecs operate on exactly one byte stream.
21pub fn open_input(input: CmprssInput, name: &str) -> Result<(Box<dyn Read + Send>, Option<u64>)> {
22 match input {
23 CmprssInput::Path(paths) => {
24 if paths.len() > 1 {
25 bail!("Multiple input files not supported for {name}");
26 }
27 let path = &paths[0];
28 if path.is_dir() {
29 bail!("{name} does not operate on directories; specify a file instead");
30 }
31 let size = std::fs::metadata(path)?.len();
32 let reader: Box<dyn Read + Send> = Box::new(BufReader::new(File::open(path)?));
33 Ok((reader, Some(size)))
34 }
35 CmprssInput::Pipe(stdin) => Ok((Box::new(BufReader::new(stdin)), None)),
36 CmprssInput::Reader(reader) => Ok((reader.0, None)),
37 }
38}
39
40/// Bail if the output path refers to an existing directory. Single-stream
41/// codecs always emit a single byte stream, so they can't write to a
42/// directory.
43pub fn guard_file_output(output: &CmprssOutput, name: &str) -> Result {
44 if let CmprssOutput::Path(path) = output
45 && path.is_dir()
46 {
47 bail!("{name} does not operate on directories; specify an output file instead");
48 }
49 Ok(())
50}
51
52/// Resolve a `CmprssOutput` into an owned boxed writer plus an `OutputTarget`
53/// describing how it should be treated by the copy path (progress bar vs. no
54/// progress, etc.). This consumes the output, so callers that still need to
55/// inspect the `CmprssOutput` variant should capture what they need before
56/// calling.
57pub fn prepare_output(output: CmprssOutput) -> Result<(Box<dyn Write + Send>, OutputTarget)> {
58 match output {
59 CmprssOutput::Writer(WriteWrapper(w)) => Ok((w, OutputTarget::InMemory)),
60 CmprssOutput::Pipe(stdout) => Ok((Box::new(BufWriter::new(stdout)), OutputTarget::Stdout)),
61 CmprssOutput::Path(path) => Ok((
62 Box::new(BufWriter::new(File::create(path)?)),
63 OutputTarget::File,
64 )),
65 }
66}
67
68/// Copy bytes from `reader` through `writer`, branching on `target`:
69/// pipeline-internal stages use a bare `io::copy` (no progress), while
70/// user-facing outputs go through `copy_with_progress` to show a progress bar
71/// when configured.
72pub fn copy_stream<R: Read, W: Write>(
73 mut reader: R,
74 mut writer: W,
75 file_size: Option<u64>,
76 progress_args: &ProgressArgs,
77 target: OutputTarget,
78) -> Result {
79 if target == OutputTarget::InMemory {
80 io::copy(&mut reader, &mut writer)?;
81 } else {
82 copy_with_progress(
83 reader,
84 writer,
85 progress_args.chunk_size.size_in_bytes,
86 file_size,
87 progress_args.progress,
88 target,
89 )?;
90 }
91 Ok(())
92}