readcon_core/
compression.rs1use std::io::{self, Read};
6use std::path::Path;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Compression {
16 None,
17 Gzip,
18 Zstd,
20}
21
22pub fn detect_compression(bytes: &[u8]) -> Compression {
28 if bytes.len() >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b {
29 return Compression::Gzip;
30 }
31 if bytes.len() >= 4
32 && bytes[0] == 0x28
33 && bytes[1] == 0xb5
34 && bytes[2] == 0x2f
35 && bytes[3] == 0xfd
36 {
37 return Compression::Zstd;
38 }
39 Compression::None
40}
41
42pub fn detect_compression_from_extension(path: &Path) -> Compression {
47 match path.extension().and_then(|e| e.to_str()) {
48 Some("gz") => Compression::Gzip,
49 Some("zst") => Compression::Zstd,
50 _ => Compression::None,
51 }
52}
53
54const MMAP_THRESHOLD: u64 = 64 * 1024;
56
57pub fn read_file_contents(path: &Path) -> Result<FileContents, Box<dyn std::error::Error>> {
65 let file = std::fs::File::open(path)?;
66 let metadata = file.metadata()?;
67
68 let mut magic = [0u8; 4];
70 let bytes_read = {
71 let mut f = &file;
72 f.read(&mut magic)?
73 };
74
75 let compression = if bytes_read > 0 {
76 detect_compression(&magic[..bytes_read])
77 } else {
78 Compression::None
79 };
80
81 match compression {
82 Compression::Gzip => {
83 let file = std::fs::File::open(path)?;
85 let mut decoder = flate2::read::GzDecoder::new(file);
86 let mut contents = String::new();
87 decoder.read_to_string(&mut contents)?;
88 Ok(FileContents::Owned(contents))
89 }
90 Compression::Zstd => {
91 #[cfg(feature = "zstd")]
92 {
93 let file = std::fs::File::open(path)?;
94 let mut decoder = zstd::stream::read::Decoder::new(file)?;
95 let mut contents = String::new();
96 decoder.read_to_string(&mut contents)?;
97 Ok(FileContents::Owned(contents))
98 }
99 #[cfg(not(feature = "zstd"))]
100 {
101 Err(io::Error::new(
102 io::ErrorKind::Unsupported,
103 "zstd-compressed input detected; rebuild readcon-core with --features zstd",
104 )
105 .into())
106 }
107 }
108 Compression::None => {
109 if metadata.len() < MMAP_THRESHOLD {
110 let contents = std::fs::read_to_string(path)?;
111 Ok(FileContents::Owned(contents))
112 } else {
113 let file = std::fs::File::open(path)?;
114 let mmap = unsafe { memmap2::Mmap::map(&file)? };
115 Ok(FileContents::Mapped(mmap))
116 }
117 }
118 }
119}
120
121pub enum FileContents {
123 Owned(String),
124 Mapped(memmap2::Mmap),
125}
126
127impl FileContents {
128 pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
129 match self {
130 FileContents::Owned(s) => Ok(s.as_str()),
131 FileContents::Mapped(m) => std::str::from_utf8(m),
132 }
133 }
134}
135
136pub fn gzip_writer(path: &Path) -> io::Result<flate2::write::GzEncoder<std::fs::File>> {
138 let file = std::fs::File::create(path)?;
139 Ok(flate2::write::GzEncoder::new(
140 file,
141 flate2::Compression::default(),
142 ))
143}
144
145#[cfg(feature = "zstd")]
151pub fn zstd_writer(
152 path: &Path,
153) -> io::Result<zstd::stream::write::AutoFinishEncoder<'static, std::fs::File>> {
154 let file = std::fs::File::create(path)?;
155 let encoder = zstd::stream::write::Encoder::new(file, 3)?;
157 Ok(encoder.auto_finish())
158}