lzma/
writer.rs

1//! This module implements `LzmaWriter`.
2//!
3//! `LzmaWriter` implements the LZMA (XZ) compression/decompression algorithm as a generic
4//! Writer.  In other words, it behaves similar to BufWriter.  Instead of buffering the `Write`
5//! object passed to it, `LzmaWriter` applies compression or decompression to it as it's write.
6//!
7//!
8//! # Examples
9//!
10//! ```no_run
11//! use lzma::LzmaWriter;
12//! use std::io::prelude::*;
13//! use std::fs::File;
14//!
15//! let f = File::create("foo.xz").unwrap();
16//! let mut f = LzmaWriter::new_compressor(f, 6).unwrap();
17//!
18//! write!(f, "It's a small world!").unwrap();
19//! f.finish().unwrap();
20//! ```
21
22use std::io::{self, Write};
23use lzma_sys::*;
24use std;
25use error::LzmaError;
26use ::Direction;
27use lzma_stream_wrapper::{LzmaStreamWrapper, LzmaCodeResult};
28
29
30const DEFAULT_BUF_SIZE: usize = 4 * 1024;
31
32
33pub struct LzmaWriter<T> {
34	inner: T,
35	stream: LzmaStreamWrapper,
36	buffer: Vec<u8>,
37	direction: Direction,
38}
39
40
41impl<T: Write> LzmaWriter<T> {
42	pub fn new_compressor(inner: T, preset: u32) -> Result<LzmaWriter<T>, LzmaError> {
43		LzmaWriter::with_capacity(DEFAULT_BUF_SIZE, inner, Direction::Compress, preset)
44	}
45
46	pub fn new_decompressor(inner: T) -> Result<LzmaWriter<T>, LzmaError> {
47		LzmaWriter::with_capacity(DEFAULT_BUF_SIZE, inner, Direction::Decompress, 0)
48	}
49
50	pub fn with_capacity(capacity: usize, inner: T, direction: Direction, preset: u32) -> Result<LzmaWriter<T>, LzmaError> {
51		let mut writer = LzmaWriter {
52			inner,
53			stream: LzmaStreamWrapper::new(),
54			buffer: vec![0; capacity],
55			direction,
56		};
57
58		match writer.direction {
59			Direction::Compress => {
60				writer.stream.easy_encoder(preset, lzma_check::LzmaCheckCrc64)?
61			},
62			Direction::Decompress => {
63				writer.stream.stream_decoder(std::u64::MAX, 0)?
64			},
65		}
66
67		Ok(writer)
68	}
69}
70
71impl<W: Write> LzmaWriter<W> {
72	/// Finalizes the LZMA stream so that it finishes compressing or decompressing.
73	///
74	/// This *must* be called after all writing is done to ensure the last pieces of the compressed
75	/// or decompressed stream get written out.
76	pub fn finish(mut self) -> Result<W, LzmaError> {
77		loop {
78			match self.lzma_code_and_write(&[], lzma_action::LzmaFinish) {
79				Ok(LzmaCodeResult {
80					ret: Ok(lzma_ret::LzmaStreamEnd),
81					bytes_read: _,
82					bytes_written: _,
83				}) => break,
84				Ok(_) => continue,
85				Err(err) => return Err(err),
86			}
87		}
88
89		Ok(self.inner)
90	}
91
92	#[allow(clippy::question_mark)]
93	fn lzma_code_and_write(&mut self, input: &[u8], action: lzma_action) -> Result<LzmaCodeResult, LzmaError> {
94		let result = self.stream.code(input, &mut self.buffer, action);
95		if let Err(err) = result.ret {
96			return Err(err);
97		}
98
99		if result.bytes_written > 0 {
100			Write::write_all(&mut self.inner, &self.buffer[..result.bytes_written])?;
101		}
102
103		Ok(result)
104	}
105}
106
107
108impl<W: Write> Write for LzmaWriter<W> {
109	fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
110		// Loop until at least one byte from buf was consumed in order to be
111		// compliant with std::io::Write trait API.
112		loop {
113			match self.lzma_code_and_write(buf, lzma_action::LzmaRun) {
114				Ok(result) => if result.bytes_read == 0 && result.bytes_written > 0 {
115					continue
116				} else {
117					// If result.bytes_read is zero, then neither was something
118					// written nor read. This indicates, something went wrong.
119					return Ok(result.bytes_read)
120				},
121				Err(LzmaError::Io(err)) => return Err(err),
122				Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)),
123			}
124		}
125	}
126
127	fn flush(&mut self) -> io::Result<()> {
128		self.inner.flush()
129	}
130}