first commit

This commit is contained in:
feie9456 2025-06-27 09:08:17 +08:00
commit d390f3ce54
9 changed files with 5556 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
.idea

3939
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "compresscsdesign"
version = "0.1.0"
edition = "2021"
[dependencies]
eframe = "0.30.0"
rfd = "0.15.2"
egui-notify = "0.18.0"
crc32fast = "1.3"
sha2 = "0.10"

6
readme.txt Normal file
View File

@ -0,0 +1,6 @@
构建流程:
1. 安装 Rust 和 Cargo 环境
2. 运行 cargo run 进行调试
3. 运行 cargo build -r 构建可执行文件
(已构建 Windows 下可执行文件 "鹅鹅文件压缩器.exe" 于当前目录,可直接运行)

BIN
src/assets/icon.bin Normal file

Binary file not shown.

427
src/huffman.rs Normal file
View File

@ -0,0 +1,427 @@
use std::{
collections::{BinaryHeap, HashMap},
io::{self, Read, Write},
};
use std::cmp::Ordering;
/// Huffman 树节点
#[derive(Eq, PartialEq)]
struct Node {
freq: u32,
byte: Option<u8>,
left: Option<Box<Node>>,
right: Option<Box<Node>>,
}
// -----------------------------------------------------
// 实现 Ord/PartialOrd使得 BinaryHeap 成为最小堆
// -----------------------------------------------------
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
// BinaryHeap 默认是大顶堆,但我们想要小顶堆效果,所以反转 freq
other.freq.cmp(&self.freq)
}
}
/// 构建 Huffman 树
fn build_huffman_tree(freq_map: &HashMap<u8, u32>) -> Option<Box<Node>> {
if freq_map.is_empty() {
return None;
}
// 按字节升序排序,保证同频时顺序稳定
let mut keys: Vec<u8> = freq_map.keys().cloned().collect();
keys.sort_unstable();
let mut heap = BinaryHeap::new();
for b in keys {
heap.push(Node {
freq: freq_map[&b],
byte: Some(b),
left: None,
right: None,
});
}
while heap.len() > 1 {
let n1 = heap.pop().unwrap();
let n2 = heap.pop().unwrap();
let parent = Node {
freq: n1.freq + n2.freq,
byte: None,
left: Some(Box::new(n1)),
right: Some(Box::new(n2)),
};
heap.push(parent);
}
let root = heap.pop().map(Box::new);
root
}
/// 递归遍历 Huffman 树,构建 (字节 -> 比特序列) 的 map
fn build_code_map(node: &Node, prefix: Vec<u8>, code_map: &mut HashMap<u8, Vec<u8>>) {
if let Some(b) = node.byte {
// 如果树只有一个节点prefix 会是空,强行给它 [0]
if prefix.is_empty() {
code_map.insert(b, vec![0]);
} else {
code_map.insert(b, prefix);
}
return;
}
// 否则继续往左右子节点
if let Some(ref left) = node.left {
let mut left_path = prefix.clone();
left_path.push(0);
build_code_map(left, left_path, code_map);
}
if let Some(ref right) = node.right {
let mut right_path = prefix.clone();
right_path.push(1);
build_code_map(right, right_path, code_map);
}
}
// -----------------------------------------------------
// BitWriter: 将比特流写入内存 buffer
// -----------------------------------------------------
struct BitWriter {
buffer: Vec<u8>,
current_byte: u8,
bit_count: u8,
}
impl BitWriter {
fn new() -> Self {
Self {
buffer: Vec::new(),
current_byte: 0,
bit_count: 0,
}
}
fn write_bit(&mut self, bit: u8) {
self.current_byte = (self.current_byte << 1) | (bit & 1);
self.bit_count += 1;
if self.bit_count == 8 {
self.buffer.push(self.current_byte);
self.current_byte = 0;
self.bit_count = 0;
}
}
fn flush(&mut self) {
if self.bit_count > 0 {
self.current_byte <<= 8 - self.bit_count;
self.buffer.push(self.current_byte);
self.current_byte = 0;
self.bit_count = 0;
}
}
fn into_inner(mut self) -> Vec<u8> {
self.flush();
self.buffer
}
}
// -----------------------------------------------------
// BitReader: 从内存切片逐比特读
// -----------------------------------------------------
struct BitReader<'a> {
data: &'a [u8],
byte_pos: usize,
bit_pos: u8,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
byte_pos: 0,
bit_pos: 0,
}
}
fn read_bit(&mut self) -> Option<u8> {
if self.byte_pos >= self.data.len() {
return None;
}
let byte = self.data[self.byte_pos];
let bit = (byte >> (7 - self.bit_pos)) & 1;
self.bit_pos += 1;
if self.bit_pos == 8 {
self.bit_pos = 0;
self.byte_pos += 1;
}
Some(bit)
}
}
pub fn encode_stream<R: Read, W: Write, D: Write>(
mut input: R,
mut output: W,
onprogress: &mut impl FnMut(usize),
mut debug_out: Option<&mut D>,
) -> io::Result<()> {
// 1) 先把所有输入读到内存
let mut buf = [0u8; 8192];
let mut data = Vec::new();
let mut total_read = 0usize;
let mut last_reported = 0usize;
const REPORT_THRESHOLD: usize = 16 * 1024; // 16KB
loop {
let n = input.read(&mut buf)?;
if n == 0 {
break;
}
data.extend_from_slice(&buf[..n]);
total_read += n;
// 进度回调
if total_read.saturating_sub(last_reported) >= REPORT_THRESHOLD {
onprogress(total_read);
last_reported = total_read;
}
}
// 结束读输入时再更新一次
onprogress(total_read);
// 2) 构建 freq_map
let mut freq_map = HashMap::new();
for &b in &data {
*freq_map.entry(b).or_insert(0) += 1;
}
// 3) 构建 Huffman 树 & code_map
let root = build_huffman_tree(&freq_map).unwrap();
let mut code_map = HashMap::new();
build_code_map(&root, Vec::new(), &mut code_map);
// 如果有 debug_out => 输出 frequency table
if let Some(ref mut dbg) = debug_out {
writeln!(dbg, "[Huffman encode] freq_map:")?;
for (&byte, &freq) in &freq_map {
writeln!(dbg, " byte = {:02X}, freq = {}", byte, freq)?;
}
writeln!(dbg, "[Huffman encode] code_map:")?;
for (&byte, code_bits) in &code_map {
// 将 bit 序列拼成字符串
let bits_str = code_bits
.iter()
.map(|b| if *b == 0 { '0' } else { '1' })
.collect::<String>();
writeln!(dbg, " byte = {:02X}, code = {}", byte, bits_str)?;
}
}
// 4) 写 freq_table (256 个 u32小端序)
{
let mut freq_array = vec![0u32; 256];
for (&b, &f) in &freq_map {
freq_array[b as usize] = f;
}
let mut freq_table_bytes = Vec::with_capacity(256 * 4);
for &f in &freq_array {
freq_table_bytes.extend_from_slice(&f.to_le_bytes());
}
output.write_all(&freq_table_bytes)?;
}
// 5) 利用 code_map 把 data 编码为 bit_stream
let mut bit_writer = BitWriter::new();
let mut total_bits: u32 = 0;
for &b in &data {
if let Some(bits) = code_map.get(&b) {
for &bit in bits {
bit_writer.write_bit(bit);
total_bits += 1;
}
}
}
bit_writer.flush();
let bit_stream = bit_writer.into_inner();
// 6) 写 total_bits
output.write_all(&total_bits.to_le_bytes())?;
// 7) 写 bit_stream
output.write_all(&bit_stream)?;
// 结束时 debug
if let Some(ref mut dbg) = debug_out {
writeln!(
dbg,
"[Huffman encode] data_len = {}, total_bits = {}, bit_stream_len(bytes) = {}",
data.len(),
total_bits,
bit_stream.len()
)?;
}
Ok(())
}
pub fn decode_stream<R: Read, W: Write, D: Write>(
mut input: R,
mut output: W,
onprogress: &mut impl FnMut(usize),
mut debug_out: Option<&mut D>,
) -> io::Result<()> {
let mut total_read = 0usize;
let mut last_reported = 0usize;
const REPORT_THRESHOLD: usize = 16 * 1024; // 16KB
// 帮助函数:读取固定大小到 buffer并更新进度
fn read_exact_progress<R: Read>(
reader: &mut R,
buf: &mut [u8],
total_read: &mut usize,
last_reported: &mut usize,
onprogress: &mut impl FnMut(usize),
) -> io::Result<()> {
let mut offset = 0;
while offset < buf.len() {
let n = reader.read(&mut buf[offset..])?;
if n == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Unexpected EOF while reading Huffman stream",
));
}
offset += n;
*total_read += n;
// 回调
if total_read.saturating_sub(*last_reported) >= REPORT_THRESHOLD {
onprogress(*total_read);
*last_reported = *total_read;
}
}
Ok(())
}
// 1) 读取 freq_table
let mut freq_table_bytes = vec![0u8; 256 * 4];
read_exact_progress(
&mut input,
&mut freq_table_bytes,
&mut total_read,
&mut last_reported,
onprogress,
)?;
// 2) 构造 freq_map
let mut freq_map = HashMap::new();
for i in 0..256 {
let freq = u32::from_le_bytes([
freq_table_bytes[i * 4],
freq_table_bytes[i * 4 + 1],
freq_table_bytes[i * 4 + 2],
freq_table_bytes[i * 4 + 3],
]);
if freq > 0 {
freq_map.insert(i as u8, freq);
}
}
// 如果有 debug_out => 输出 frequency table
if let Some(ref mut dbg) = debug_out {
writeln!(dbg, "[Huffman decode] freq_map:")?;
for (&byte, &freq) in &freq_map {
writeln!(dbg, " byte = {:02X}, freq = {}", byte, freq)?;
}
}
// 3) 重建 Huffman 树
let root = build_huffman_tree(&freq_map).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"Invalid Huffman frequency table",
)
})?;
// 4) 读取 total_bits
let mut total_bits_buf = [0u8; 4];
read_exact_progress(
&mut input,
&mut total_bits_buf,
&mut total_read,
&mut last_reported,
onprogress,
)?;
let total_bits = u32::from_le_bytes(total_bits_buf) as usize;
// 5) 读取 bit_stream
let bit_stream_len = (total_bits + 7) / 8;
let mut bit_stream = vec![0u8; bit_stream_len];
if bit_stream_len > 0 {
read_exact_progress(
&mut input,
&mut bit_stream,
&mut total_read,
&mut last_reported,
onprogress,
)?;
}
onprogress(total_read);
// 6) 解码
let mut bit_reader = BitReader::new(&bit_stream);
let mut decoded_buffer = Vec::with_capacity(8192);
let mut current_node = &root;
for _ in 0..total_bits {
let bit = bit_reader
.read_bit()
.ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "Bitstream ended early"))?;
if bit == 0 {
if let Some(ref left) = current_node.left {
current_node = left;
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid Huffman bitstream: 0 leads to no left child",
));
}
} else {
if let Some(ref right) = current_node.right {
current_node = right;
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid Huffman bitstream: 1 leads to no right child",
));
}
}
// 到达叶子节点 => 写入
if let Some(b) = current_node.byte {
decoded_buffer.push(b);
current_node = &root;
if decoded_buffer.len() >= 8192 {
output.write_all(&decoded_buffer)?;
decoded_buffer.clear();
}
}
}
// 写剩余数据
if !decoded_buffer.is_empty() {
output.write_all(&decoded_buffer)?;
}
if let Some(ref mut dbg) = debug_out {
writeln!(dbg, "[Huffman decode] Decoding finished. total_bits = {}", total_bits)?;
}
Ok(())
}

27
src/lib.rs Normal file
View File

@ -0,0 +1,27 @@
use crc32fast::Hasher as Crc32Hasher;
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::Read;
use std::path::Path;
pub fn compute_crc32_and_sha256(path: &Path) -> (Option<u32>, Option<String>) {
let mut file = match File::open(path) {
Ok(f) => f,
Err(_) => return (None, None),
};
let mut buffer = Vec::new();
if file.read_to_end(&mut buffer).is_err() {
return (None, None);
}
let mut crc_hasher = Crc32Hasher::new();
crc_hasher.update(&buffer);
let crc32 = Some(crc_hasher.finalize());
let mut sha_hasher = Sha256::new();
sha_hasher.update(&buffer);
let result = sha_hasher.finalize();
let sha256 = Some(format!("{:x}", result));
(crc32, sha256)
}

378
src/lz77.rs Normal file
View File

@ -0,0 +1,378 @@
use std::{
collections::VecDeque,
io::{self, Read, Write},
};
#[derive(Debug, Clone, PartialEq)]
pub struct Token {
pub offset: u16,
pub length: u8,
pub next_char: u8,
}
pub struct LZ77 {
pub window_size: usize,
pub lookahead_size: usize,
}
impl LZ77 {
pub fn new(window_size: usize, lookahead_size: usize) -> Self {
Self {
window_size,
lookahead_size,
}
}
/// 在 window 中寻找与 lookahead 最长的匹配,返回 (offset, length) (usize, usize)。
fn find_longest_match(&self, window: &[u8], lookahead: &[u8]) -> (usize, usize) {
if lookahead.is_empty() || window.is_empty() {
return (0, 0);
}
let mut best_len = 0usize;
let mut best_off = 0usize;
// 从 window 尾部往前搜索
for start in (0..window.len()).rev() {
let mut length = 0usize;
while length < lookahead.len()
&& length < self.lookahead_size
&& start + length < window.len()
&& window[start + length] == lookahead[length]
{
length += 1;
}
if length > best_len {
best_len = length;
best_off = window.len() - start;
if best_len == self.lookahead_size {
break;
}
}
}
(best_off, best_len)
}
/// 将「匹配 Token」编码成 4 字节 [offset(2bytes,LE), length(1byte), next_char(1byte)]
pub fn encode_token_to_bytes(token: &Token) -> [u8; 4] {
let offset_bytes = token.offset.to_le_bytes();
[
offset_bytes[0],
offset_bytes[1],
token.length,
token.next_char,
]
}
pub fn compress_stream<R: Read, W: Write, D: Write>(
&self,
mut input: R,
mut output: W,
onprogress: &mut impl FnMut(usize),
debug_out: Option<&mut D>,
) -> io::Result<()> {
let mut window: VecDeque<u8> = VecDeque::new();
let mut lookahead = Vec::new();
lookahead.reserve(self.lookahead_size);
let mut buffer = [0u8; 4096];
// 节流相关变量
let mut total_read = 0usize;
let mut last_reported = 0usize;
const REPORT_THRESHOLD: usize = 16 * 1024; // 16KB
// 用于统计调试信息
let mut match_count = 0usize;
let mut total_match_length = 0usize;
// 初次读
let bytes_read = input.read(&mut buffer)?;
if bytes_read > 0 {
total_read += bytes_read;
onprogress(total_read);
}
lookahead.extend_from_slice(&buffer[..bytes_read]);
// 用于暂存无法匹配的字面量
let mut literal_buf: Vec<u8> = Vec::new();
// 函数内联,用来一次性写出字面量块
let flush_literal_buf = |out: &mut W, buf: &mut Vec<u8>| -> io::Result<()> {
let run_len = buf.len();
if run_len == 0 {
return Ok(());
}
if run_len > 255 {
let mut start = 0;
while start < run_len {
let end = (start + 255).min(run_len);
let chunk_size = end - start;
// 写头 [0,0, chunk_size, 0xFF]
let head = [0u8, 0u8, chunk_size as u8, 0xFF];
out.write_all(&head)?;
// 写 chunk_size 个字面量
out.write_all(&buf[start..end])?;
start = end;
}
} else {
// 写头 [0,0, run_len, 0xFF]
let head = [0u8, 0u8, run_len as u8, 0xFF];
out.write_all(&head)?;
out.write_all(buf)?;
}
buf.clear();
Ok(())
};
while !lookahead.is_empty() {
// 若 lookahead 不足,则再读
while lookahead.len() < self.lookahead_size {
match input.read(&mut buffer) {
Ok(0) => break, // EOF
Ok(n) => {
total_read += n;
if total_read - last_reported >= REPORT_THRESHOLD {
onprogress(total_read);
last_reported = total_read;
}
lookahead.extend_from_slice(&buffer[..n]);
}
Err(e) => return Err(e),
}
}
// 在 window 中找最长匹配
let window_slice: Vec<u8> = window.iter().copied().collect();
let (offset, length) = self.find_longest_match(&window_slice, &lookahead);
if length > 0 {
// 统计信息
match_count += 1;
total_match_length += length;
// 在此处先把字面量缓冲写出(如果有的话)
if !literal_buf.is_empty() {
flush_literal_buf(&mut output, &mut literal_buf)?;
}
// 有匹配 => 写出一个匹配 Token
if length < lookahead.len() {
let next_char = lookahead[length];
let token = Token {
offset: offset as u16,
length: length as u8,
next_char,
};
output.write_all(&Self::encode_token_to_bytes(&token))?;
// 更新 window
let advance = length + 1;
for _ in 0..advance {
if !lookahead.is_empty() {
window.push_back(lookahead.remove(0));
if window.len() > self.window_size {
window.pop_front();
}
}
}
} else {
// length == lookahead.len(),把最后的 next_char 逻辑改成逐字节写字面量
for _ in 0..length {
if let Some(&byte) = lookahead.get(0) {
literal_buf.push(byte);
lookahead.remove(0);
window.push_back(byte);
if window.len() > self.window_size {
window.pop_front();
}
}
}
}
} else {
// 无匹配 => 把 lookahead[0] 当字面量放进 literal_buf
let byte = lookahead.remove(0);
literal_buf.push(byte);
window.push_back(byte);
if window.len() > self.window_size {
window.pop_front();
}
}
}
// 文件处理结束后,可能还有剩余的字面量要一次性写出
if !literal_buf.is_empty() {
flush_literal_buf(&mut output, &mut literal_buf)?;
}
// 最后确保所有读的字节都已报告
if total_read > last_reported {
onprogress(total_read);
}
// 如果有 debug_out就输出一下匹配统计等调试信息
if let Some(dbg) = debug_out {
let avg_match_len = if match_count > 0 {
total_match_length as f64 / match_count as f64
} else {
0.0
};
writeln!(
dbg,
"[LZ77 compress] match_count = {}, total_match_length = {}, average_match_len = {:.2}",
match_count, total_match_length, avg_match_len
)?;
}
Ok(())
}
pub fn decompress_stream<R: Read, W: Write, D: Write>(
&self,
mut input: R,
mut output: W,
onprogress: &mut impl FnMut(usize),
debug_out: Option<&mut D>,
) -> io::Result<()> {
let mut ring_buffer = vec![0u8; 65536];
let mut pos = 0usize; // 已经解出的字节数
let mut token_buf = [0u8; 4];
// 节流相关变量
let mut total_read = 0usize;
let mut last_reported = 0usize;
const REPORT_THRESHOLD: usize = 16 * 1024; // 16KB
// 统计信息
let mut match_count = 0usize;
let mut total_match_length = 0usize;
let mut literal_count = 0usize;
loop {
// 读取 4 字节 => 若 EOF 就结束
let mut read_bytes = 0;
while read_bytes < 4 {
let n = input.read(&mut token_buf[read_bytes..])?;
if n == 0 {
return if read_bytes == 0 {
// 正常EOF
// 在结束时输出一下统计信息
if let Some(dbg) = debug_out {
if match_count > 0 {
let avg_match_len = total_match_length as f64 / match_count as f64;
writeln!(
dbg,
"[LZ77 decompress] match_count = {}, total_match_length = {}, average_match_len = {:.2}, literal_count = {}",
match_count, total_match_length, avg_match_len, literal_count
)?;
} else {
writeln!(
dbg,
"[LZ77 decompress] No matches, literal_count = {}",
literal_count
)?;
}
}
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Unexpected EOF while reading token header",
))
};
}
read_bytes += n;
total_read += n;
if total_read - last_reported >= REPORT_THRESHOLD {
onprogress(total_read);
last_reported = total_read;
}
}
let offset = u16::from_le_bytes([token_buf[0], token_buf[1]]);
let length = token_buf[2];
let next_char = token_buf[3];
// 检测是否是 批量字面量 Token 头 => [0,0, run_len, 0xFF]
if offset == 0 && length > 0 && next_char == 0xFF {
// length 表示 run_len
let run_len = length as usize;
literal_count += run_len;
// 继续从输入读取 run_len 个字面量
let mut literal_data = vec![0u8; run_len];
let mut read_now = 0;
while read_now < run_len {
let n = input.read(&mut literal_data[read_now..])?;
if n == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("EOF while reading literal run (need {} bytes)", run_len),
));
}
read_now += n;
total_read += n;
if total_read - last_reported >= REPORT_THRESHOLD {
onprogress(total_read);
last_reported = total_read;
}
}
// 写出并更新 pos
output.write_all(&literal_data)?;
for &byte in &literal_data {
let buffer_pos = pos % ring_buffer.len();
ring_buffer[buffer_pos] = byte;
pos += 1;
}
} else {
// 否则 => 这是一个普通 MATCH Token
// (offset, length, next_char)
let offset_usize = offset as usize;
let length_usize = length as usize;
// 如果有 match统计
if offset_usize > 0 && length_usize > 0 {
match_count += 1;
total_match_length += length_usize;
} else {
// 这意味着 offset=0/length=0算是 1 字节 literal
literal_count += 1;
}
// 先复制 match
if offset_usize > 0 && length_usize > 0 {
if offset_usize > pos {
// 无效 offset
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid offset: {} > {}", offset_usize, pos),
));
}
for _ in 0..length_usize {
let buffer_pos = pos % ring_buffer.len();
let lookup_pos = (pos - offset_usize) % ring_buffer.len();
let byte = ring_buffer[lookup_pos];
ring_buffer[buffer_pos] = byte;
output.write_all(&[byte])?;
pos += 1;
}
}
// next_char 始终写出
{
let buffer_pos = pos % ring_buffer.len();
ring_buffer[buffer_pos] = next_char;
output.write_all(&[next_char])?;
pos += 1;
}
}
}
}
}

765
src/main.rs Normal file
View File

@ -0,0 +1,765 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use eframe::egui::{Color32, IconData, Shadow};
use eframe::{
egui::{self, Align, Layout},
App, Frame, NativeOptions,
};
use egui_notify::Toasts;
use rfd::FileDialog;
use std::borrow::Cow;
use std::io::{BufWriter};
use std::time::{Duration, Instant};
use std::{fs, fs::File, io::{BufReader, Cursor}, path::PathBuf, sync::mpsc::{self, Receiver, Sender}, thread};
use std::path::Path;
mod huffman;
mod lz77;
use compresscsdesign::compute_crc32_and_sha256;
enum CompressMsg {
FileStart(usize),
FileProgress(usize, usize), // (current_read, total_size)
FileDone(usize),
Error(String),
AllDone,
}
#[derive(PartialEq)]
enum ProcessType {
Compress,
Decompress,
}
#[derive(Default)]
struct ChecksumFile {
path: PathBuf,
crc32: Option<u32>,
sha256: Option<String>,
}
#[derive(Default)]
struct ChecksumWindow {
show: bool,
files: Vec<ChecksumFile>,
}
struct MyApp {
selected_files: Vec<PathBuf>,
enable_lz77: bool,
enable_huffman: bool,
enable_debug: bool,
lz77_window_size: usize,
lz77_lookahead_size: usize,
output_path: String,
process_type: ProcessType,
is_compressing: bool,
compress_index: usize,
compress_progress: usize,
compress_progress_total: usize,
receiver: Receiver<CompressMsg>,
sender: Option<Sender<CompressMsg>>,
// egui_notify
toasts: Toasts,
checksum_window: ChecksumWindow,
start_time: Instant,
}
fn read_font_file(paths: &[&str]) -> Option<Vec<u8>> {
for &path in paths {
if let Ok(data) = fs::read(Path::new(path)) {
return Some(data);
}
}
None
}
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
let paths = [
"C:/Users/feie9454/AppData/Local/Microsoft/Windows/Fonts/PingFang Medium.ttf",
"C:/Windows/Fonts/msyh.ttc"
];
match read_font_file(&paths) {
Some(font_data) => {
println!("Font loaded successfully, size: {} bytes", font_data.len()) ;
let mut fonts = egui::FontDefinitions::default();
fonts.font_data.insert(
"my_font".to_owned(),
egui::FontData::from_owned(font_data).into(),
);
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "my_font".to_owned());
cc.egui_ctx.set_fonts(fonts);
},
None => eprintln!("Failed to load font from all specified paths."),
}
let (tx, rx) = mpsc::channel();
Self {
selected_files: Vec::new(),
enable_lz77: true,
enable_huffman: true,
enable_debug: false,
lz77_window_size: 16384,
lz77_lookahead_size: 127,
output_path: String::new(),
process_type: ProcessType::Compress,
compress_index: 0,
compress_progress: 0,
compress_progress_total: 0,
is_compressing: false,
receiver: rx,
sender: Some(tx),
toasts: Toasts::default().with_shadow(Shadow {
offset: Default::default(),
blur: 10.0,
spread: 5.0,
color: Color32::from_black_alpha(15),
}),
checksum_window: ChecksumWindow {
show: false,
files: vec![],
},
start_time: Instant::now(),
}
}
}
impl App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) {
// -- 主窗口:处理文件拖拽事件 --
ctx.input(|i| {
if !i.raw.dropped_files.is_empty() && !self.checksum_window.show {
// 只有在「校验工具」窗口关闭时才往 selected_files 里加
// 避免在校验窗口打开时拖拽却误加到压缩列表
for file in &i.raw.dropped_files {
if let Some(path) = &file.path {
self.selected_files.push(path.clone());
}
}
}
});
// -- 从压缩/解压线程中接收消息 --
while let Ok(msg) = self.receiver.try_recv() {
match msg {
CompressMsg::FileStart(index) => {
// 切换到正在处理的文件
println!("开始处理第 {} 个文件", index + 1);
self.compress_index = index;
self.compress_progress = 0;
}
CompressMsg::FileProgress(current_read, total_size) => {
// 更新单个文件的进度百分比
self.compress_progress = current_read;
self.compress_progress_total = total_size;
}
CompressMsg::FileDone(index) => {
// 某个文件处理结束
if index + 1 >= self.selected_files.len() {
self.compress_index = self.selected_files.len() - 1;
} else {
self.compress_index = index + 1;
self.compress_progress = 0;
}
}
CompressMsg::AllDone => {
self.is_compressing = false;
self.compress_index = self.selected_files.len() - 1;
self.toasts.success(format!(
"所有文件处理完毕!耗时 {}ms",
(Instant::now() - self.start_time).as_millis()
));
}
CompressMsg::Error(msg) => {
self.toasts.error(format!("处理时发生错误:{}", msg));
}
}
}
// ---- 绘制主界面 ----
egui::CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| {
ui.heading("鹅鹅文件压缩 / 解压");
ui.colored_label(Color32::DARK_GRAY, "3230608044 查俊豪");
});
ui.separator();
ui.label("基于 LZ77 和 Huffman 算法实现的压缩工具,支持多文件批量压缩 / 解压。");
if !self.selected_files.is_empty() {
ui.label("已选择文件:");
for path in &self.selected_files {
ui.label(format!("- {}", path.display()));
}
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
if ui.button("清空").clicked() {
self.selected_files.clear();
}
});
} else {
ui.label("请拖拽文件到窗口中以添加文件。");
}
ui.separator();
ui.label("处理类型:");
ui.radio_value(&mut self.process_type, ProcessType::Compress, "压缩");
if self.process_type == ProcessType::Compress {
egui::CollapsingHeader::new("压缩设置")
.default_open(true)
.show(ui, |ui| {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label("预设:");
if ui.button("极快").clicked() {
self.enable_lz77 = false;
self.enable_huffman = true;
}
if ui.button("平衡").clicked() {
self.enable_lz77 = true;
self.enable_huffman = true;
self.lz77_window_size = 16384;
self.lz77_lookahead_size = 127;
}
if ui.button("极限压缩").clicked() {
self.enable_lz77 = true;
self.enable_huffman = true;
self.lz77_window_size = 65535;
self.lz77_lookahead_size = 255;
}
});
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.checkbox(&mut self.enable_lz77, "启用 LZ77");
});
if self.enable_lz77 {
ui.add(
egui::Slider::new(&mut self.lz77_window_size, 128..=65535)
.text("LZ77 Window Size"),
);
ui.add(
egui::Slider::new(&mut self.lz77_lookahead_size, 32..=255)
.text("LZ77 Lookahead Size"),
);
}
ui.horizontal(|ui| {
ui.checkbox(&mut self.enable_huffman, "启用 Huffman");
});
ui.add_space(4.0);
});
}
ui.radio_value(&mut self.process_type, ProcessType::Decompress, "解压");
ui.separator();
ui.horizontal(|ui| {
ui.label("输出路径: ");
ui.text_edit_singleline(&mut self.output_path);
if ui.button("选择...").clicked() {
if let Some(folder) = FileDialog::new().pick_folder() {
self.output_path = folder.display().to_string();
}
}
});
ui.add_space(4.0);
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
ui.checkbox(&mut self.enable_debug, "输出调试信息");
});
ui.separator();
ui.label(format!(
"正在处理文件: {}/{}",
if self.is_compressing {
self.compress_index + 1
} else {
0
},
self.selected_files.len()
));
let current_file_progress = if self.compress_progress_total == 0 {
0.0
} else {
self.compress_progress as f32 / self.compress_progress_total as f32
};
// 总体进度条
if self.selected_files.is_empty() || !self.is_compressing {
ui.add(egui::ProgressBar::new(0.0).show_percentage());
} else {
ui.add(
egui::ProgressBar::new(
(self.compress_index as f32 + current_file_progress)
/ (self.selected_files.len() as f32),
)
.show_percentage(),
);
};
// 当前文件进度条
if self.selected_files.is_empty() || !self.is_compressing {
ui.label("0 KiB / 0 KiB");
ui.add(egui::ProgressBar::new(0.0).show_percentage());
} else {
ui.label(format!(
"{}: {} KiB / {} KiB",
if !self.selected_files.is_empty() {
self.selected_files[self.compress_index]
.file_name()
.unwrap_or("".as_ref())
.to_string_lossy()
} else {
Cow::from("")
},
self.compress_progress / 1024,
self.compress_progress_total / 1024
));
ui.add(egui::ProgressBar::new(current_file_progress).show_percentage());
}
ui.separator();
ui.add_space(ui.available_height() - 24.0);
ui.horizontal(|ui| {
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
if ui.button("开始!").clicked() {
if self.selected_files.is_empty() {
self.toasts
.info("请选择要处理的文件")
.duration(Some(Duration::from_secs(3)));
return;
}
if self.output_path.is_empty() {
self.toasts
.info("请指定输出路径")
.duration(Some(Duration::from_secs(3)));
return;
}
if self.is_compressing {
self.toasts
.info("请等待任务处理完成")
.duration(Some(Duration::from_secs(3)));
return;
}
match self.process_type {
ProcessType::Decompress => {
self.start_time = Instant::now();
self.start_decompress_thread();
}
ProcessType::Compress => {
self.start_time = Instant::now();
self.start_compress_thread();
}
}
}
if ui.button("校验工具").clicked() {
// 打开校验窗口
self.checksum_window.show = true;
}
});
});
});
self.update_checksum_window(ctx);
self.toasts.show(ctx);
ctx.request_repaint();
}
}
impl MyApp {
fn start_compress_thread(&mut self) {
self.is_compressing = true;
self.compress_progress = 0;
self.compress_index = 0;
let (tx, rx) = mpsc::channel();
self.sender = Some(tx.clone());
self.receiver = rx;
let selected_files = self.selected_files.clone();
let output_path = self.output_path.clone();
// 新增: 这几个为了在子线程里捕获
let enable_lz77 = self.enable_lz77;
let enable_huffman = self.enable_huffman;
let enable_debug = self.enable_debug;
let lz77_window_size = self.lz77_window_size;
let lz77_lookahead_size = self.lz77_lookahead_size;
thread::spawn(move || {
// 压缩线程逻辑:依次处理 selected_files
for (i, file_path) in selected_files.iter().enumerate() {
let _ = tx.send(CompressMsg::FileStart(i));
let total_size = file_path.metadata().map(|m| m.len()).unwrap_or(0);
let input_file = match File::open(file_path) {
Ok(f) => f,
Err(e) => {
eprintln!("无法打开输入文件: {}, 错误: {}", file_path.display(), e);
continue;
}
};
let input_reader = BufReader::new(input_file);
// -----------------------------
// 1) 组装输出文件名
// -----------------------------
let filename = file_path.file_name().unwrap_or_default().to_string_lossy();
let mut out_filename = filename.to_string();
if enable_lz77 && enable_huffman {
out_filename.push_str(".lz77.huf");
} else if enable_lz77 {
out_filename.push_str(".lz77");
} else if enable_huffman {
out_filename.push_str(".huf");
} else {
let _ = tx.send(CompressMsg::Error("未启用任何压缩算法!".to_string()));
continue;
}
let output_fullpath = format!("{}/{}", output_path, out_filename);
let output_file = match File::create(&output_fullpath) {
Ok(f) => f,
Err(e) => {
eprintln!("无法创建输出文件: {}, 错误: {}", output_fullpath, e);
continue;
}
};
let output_writer = BufWriter::new(output_file);
// -----------------------------
// 2) 如果 debug创建同名log文件
// -----------------------------
// 用 Option<File> 来存日志句柄
let mut debug_file: Option<File> = None;
if enable_debug {
let log_filename = format!("{}.log", out_filename);
let log_filepath = format!("{}/{}", output_path, log_filename);
match File::create(&log_filepath) {
Ok(f) => {
debug_file = Some(f);
}
Err(e) => {
eprintln!("无法创建log文件: {}, 错误: {}", log_filepath, e);
// 不要return只是没有日志而已
}
}
}
// -----------------------------
// 3) 定义进度回调
// -----------------------------
let mut bytes_read_so_far = 0usize;
let mut onprogress = |read_bytes: usize| {
bytes_read_so_far = read_bytes;
let _ = tx.send(CompressMsg::FileProgress(
bytes_read_so_far,
total_size as usize,
));
};
// -----------------------------
// 4) 执行压缩
// -----------------------------
let result = if enable_lz77 && enable_huffman {
// 先 LZ77 => temp_buf => 再 Huffman => output
let mut temp_buf = Vec::new();
let lz77 = lz77::LZ77::new(lz77_window_size, lz77_lookahead_size);
// 第一次调用: LZ77压缩 => temp_buf
// 注意:把 debug_file.as_mut() 传给 compress_stream
let r1 = {
let debug_out: Option<&mut File> = debug_file.as_mut(); // 取可变引用
lz77.compress_stream(
input_reader,
&mut temp_buf,
&mut onprogress,
debug_out,
)
};
if r1.is_err() {
r1
} else {
// 第二次调用: Huffman => output_writer
let temp_reader = BufReader::new(Cursor::new(temp_buf));
let debug_out: Option<&mut File> = debug_file.as_mut();
huffman::encode_stream(
temp_reader,
output_writer,
&mut onprogress,
debug_out,
)
}
} else if enable_lz77 {
// 仅 LZ77
let lz77 = lz77::LZ77::new(lz77_window_size, lz77_lookahead_size);
let debug_out: Option<&mut File> = debug_file.as_mut();
lz77.compress_stream(input_reader, output_writer, &mut onprogress, debug_out)
} else {
// 仅 Huffman
let debug_out: Option<&mut File> = debug_file.as_mut();
huffman::encode_stream(input_reader, output_writer, &mut onprogress, debug_out)
};
if let Err(e) = result {
eprintln!("压缩过程出错: {}", e);
let _ = tx.send(CompressMsg::Error(format!("压缩过程出错: {}", e)));
}
// 单个文件结束
let _ = tx.send(CompressMsg::FileDone(i));
}
// 全部文件处理结束
let _ = tx.send(CompressMsg::AllDone);
});
}
fn start_decompress_thread(&mut self) {
self.is_compressing = true;
self.compress_progress = 0;
self.compress_index = 0;
let (tx, rx) = mpsc::channel();
self.sender = Some(tx.clone());
self.receiver = rx;
let selected_files = self.selected_files.clone();
let output_path = self.output_path.clone();
let enable_debug = self.enable_debug;
thread::spawn(move || {
for (i, file_path) in selected_files.iter().enumerate() {
let _ = tx.send(CompressMsg::FileStart(i));
let total_size = file_path.metadata().map(|m| m.len()).unwrap_or(0);
let input_file = match File::open(file_path) {
Ok(f) => f,
Err(e) => {
eprintln!("无法打开待解压文件: {}, 错误: {}", file_path.display(), e);
let _ = tx.send(CompressMsg::Error(format!(
"无法打开待解压文件: {}, 错误: {}",
file_path.display(),
e
)));
continue;
}
};
let input_reader = BufReader::new(input_file);
let filename = file_path.file_name().unwrap_or_default().to_string_lossy();
let mut out_filename = filename.to_string();
// -----------------------------
// 2) 如果 debug创建同名log文件
// -----------------------------
let mut debug_file: Option<File> = None;
if enable_debug {
let log_filename = format!("{}.log", out_filename);
let log_filepath = format!("{}/{}", output_path, log_filename);
match File::create(&log_filepath) {
Ok(f) => {
debug_file = Some(f);
}
Err(e) => {
eprintln!("无法创建log文件: {}, 错误: {}", log_filepath, e);
}
}
}
let use_lz77 =
out_filename.ends_with(".lz77") || out_filename.ends_with(".lz77.huf");
let use_huffman = out_filename.ends_with(".huf");
// 去掉后缀,以得到解压后的“原始”文件名
if out_filename.ends_with(".lz77.huf") {
out_filename.truncate(out_filename.len() - ".lz77.huf".len());
} else if out_filename.ends_with(".lz77") {
out_filename.truncate(out_filename.len() - ".lz77".len());
} else if out_filename.ends_with(".huf") {
out_filename.truncate(out_filename.len() - ".huf".len());
} else {
let _ = tx.send(CompressMsg::Error(format!(
"无法识别的压缩格式:{}",
out_filename
)));
continue;
}
let output_fullpath = format!("{}/{}", output_path, out_filename);
let output_file = match File::create(&output_fullpath) {
Ok(f) => f,
Err(e) => {
eprintln!("无法创建解压后文件: {}, 错误: {}", output_fullpath, e);
let _ = tx.send(CompressMsg::Error(format!(
"无法创建解压后文件: {}, 错误: {}",
output_fullpath, e
)));
continue;
}
};
let output_writer = BufWriter::new(output_file);
// 定义进度回调
let mut bytes_read_so_far = 0usize;
let mut onprogress = |read_bytes: usize| {
bytes_read_so_far = read_bytes;
let _ = tx.send(CompressMsg::FileProgress(
bytes_read_so_far,
total_size as usize,
));
};
// -----------------------------
// 3) 执行解压
// -----------------------------
let result = if use_lz77 && use_huffman {
// 后缀是 .lz77.huf => 先 Huffman 解 => temp_buf => 再 LZ77 解 => output
let mut temp_buf = Vec::new();
// 第一次Huffman decode => temp_buf
let r1 = {
let debug_out: Option<&mut File> = debug_file.as_mut();
huffman::decode_stream(
input_reader,
&mut temp_buf,
&mut onprogress,
debug_out,
)
};
if r1.is_err() {
r1
} else {
let temp_reader = BufReader::new(Cursor::new(temp_buf));
let lz = lz77::LZ77::new(0, 0);
let debug_out: Option<&mut File> = debug_file.as_mut();
lz.decompress_stream(temp_reader, output_writer, &mut onprogress, debug_out)
}
} else if use_lz77 {
// .lz77 => 直接 LZ77 解
let lz = lz77::LZ77::new(0, 0);
let debug_out: Option<&mut File> = debug_file.as_mut();
lz.decompress_stream(input_reader, output_writer, &mut onprogress, debug_out)
} else {
// .huf => 直接 Huffman 解
let debug_out: Option<&mut File> = debug_file.as_mut();
huffman::decode_stream(input_reader, output_writer, &mut onprogress, debug_out)
};
if let Err(e) = result {
eprintln!("解压过程出错: {}", e);
let _ = tx.send(CompressMsg::Error(format!("解压过程出错: {}", e)));
}
let _ = tx.send(CompressMsg::FileDone(i));
}
let _ = tx.send(CompressMsg::AllDone);
});
}
fn update_checksum_window(&mut self, ctx: &egui::Context) {
if self.checksum_window.show {
egui::Window::new("校验工具")
.open(&mut self.checksum_window.show)
.collapsible(false)
.resizable(false)
.show(ctx, |ui| {
ui.label("请将文件拖入此窗口,或重复拖拽多个文件。");
if ctx.input(|i| !i.raw.dropped_files.is_empty()) {
ctx.input(|i| {
for file in &i.raw.dropped_files {
if let Some(path) = &file.path {
// 如果文件已经存在,就不重复添加
if !self.checksum_window.files.iter().any(|f| f.path == *path) {
let mut cf = ChecksumFile {
path: path.clone(),
..Default::default()
};
let (crc32, sha256) = compute_crc32_and_sha256(&cf.path);
cf.crc32 = crc32;
cf.sha256 = sha256;
self.checksum_window.files.push(cf);
}
}
}
});
}
ui.separator();
if self.checksum_window.files.is_empty() {
ui.label("尚未添加任何文件。");
} else {
egui::ScrollArea::vertical()
.max_height(400.0)
.show(ui, |ui| {
for file_info in &self.checksum_window.files {
ui.group(|ui| {
ui.label(format!("文件: {}", file_info.path.display()));
if let Some(crc32) = file_info.crc32 {
ui.label(format!("CRC32: {:08X}", crc32));
} else {
ui.label("CRC32: 计算失败或文件不可读");
}
if let Some(ref sha256) = file_info.sha256 {
ui.label(format!("SHA256: {}", sha256));
} else {
ui.label("SHA256: 计算失败或文件不可读");
}
});
ui.add_space(6.0);
}
});
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
if ui.button("清空文件列表").clicked() {
self.checksum_window.files.clear();
}
});
}
});
}
}
}
fn load_icon() -> IconData {
IconData {
rgba: include_bytes!(".\\assets\\icon.bin").to_vec(),
width: 128,
height: 128,
}
}
fn main() -> eframe::Result<()> {
let options = NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_title("鹅鹅文件压缩器")
.with_icon(load_icon())
.with_inner_size([500.0, 600.0]),
..Default::default()
};
eframe::run_native(
"鹅鹅文件压缩器",
options,
Box::new(|cc| Ok(Box::new(MyApp::new(cc)))),
)
}