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::MultiGzDecoder::new(file);
87 let mut contents = String::new();
88 decoder.read_to_string(&mut contents)?;
89 Ok(FileContents::Owned(contents))
90 }
91 Compression::Zstd => {
92 #[cfg(feature = "zstd")]
93 {
94 let file = std::fs::File::open(path)?;
95 let mut decoder = zstd::stream::read::Decoder::new(file)?;
96 let mut contents = String::new();
97 decoder.read_to_string(&mut contents)?;
98 Ok(FileContents::Owned(contents))
99 }
100 #[cfg(not(feature = "zstd"))]
101 {
102 Err(io::Error::new(
103 io::ErrorKind::Unsupported,
104 "zstd-compressed input detected; rebuild readcon-core with --features zstd",
105 )
106 .into())
107 }
108 }
109 Compression::None => {
110 if metadata.len() < MMAP_THRESHOLD {
111 let contents = std::fs::read_to_string(path)?;
112 Ok(FileContents::Owned(contents))
113 } else {
114 let file = std::fs::File::open(path)?;
115 let mmap = unsafe { memmap2::Mmap::map(&file)? };
116 Ok(FileContents::Mapped(mmap))
117 }
118 }
119 }
120}
121
122pub enum FileContents {
124 Owned(String),
125 Mapped(memmap2::Mmap),
126}
127
128impl FileContents {
129 pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
130 match self {
131 FileContents::Owned(s) => Ok(s.as_str()),
132 FileContents::Mapped(m) => std::str::from_utf8(m),
133 }
134 }
135
136 pub fn as_str_sliced(&self) -> Result<&str, std::str::Utf8Error> {
139 match self {
140 FileContents::Owned(s) => Ok(s.as_str()),
141 FileContents::Mapped(m) => crate::frame_index::validate_utf8_sliced(m),
142 }
143 }
144}
145
146pub fn gzip_writer(path: &Path) -> io::Result<flate2::write::GzEncoder<std::fs::File>> {
148 let file = std::fs::File::create(path)?;
149 Ok(flate2::write::GzEncoder::new(
150 file,
151 flate2::Compression::default(),
152 ))
153}
154
155#[cfg(feature = "zstd")]
161pub fn zstd_writer(
162 path: &Path,
163) -> io::Result<zstd::stream::write::AutoFinishEncoder<'static, std::fs::File>> {
164 let file = std::fs::File::create(path)?;
165 let encoder = zstd::stream::write::Encoder::new(file, 3)?;
167 Ok(encoder.auto_finish())
168}