实现原理
本质就是将exe所需的所有资源制作为一个自解压文件(SFX)。
打包软件
本体
tauri+rust
做配置界面
- 打包文件夹
- 界面方式(本地文件-单页面应用/网址)
- 起始界面(资源路径)
- pip(可新增)
- install(进度回调)
- complete(选项设置-快捷方式)
打包自解压
使用rust
打包
toml
[
dependencies
]
flate2 = { version = "1.0", features = ["zlib"] }
walkdir = "2.3"
tempfile = "3.3"
std = { version = "1.0", features = ["fs", "path", "process"] }
flate2
:用于文件的压缩和解压缩操作,它提供了对zlib
等压缩算法的支持。walkdir
:方便遍历目录及其子目录中的所有文件。tempfile
:用于创建临时目录和解压文件。
rust
use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::path::Path;
use tempfile::tempdir;
use walkdir::WalkDir;
// 压缩文件或目录
fn compress_files(source_paths: &[&str], output_path: &str) -> io::Result<()> {
let output_file = File::create(output_path)?;
let mut encoder = ZlibEncoder::new(BufWriter::new(output_file), Compression::default());
for source_path in source_paths {
for entry in WalkDir::new(source_path) {
let entry = entry?;
if entry.file_type().is_file() {
let mut file = File::open(entry.path())?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
// 写入文件名长度和文件名
let file_name = entry.path().to_str().unwrap();
encoder.write_all(&(file_name.len() as u32).to_be_bytes())?;
encoder.write_all(file_name.as_bytes())?;
// 写入文件内容长度和文件内容
encoder.write_all(&(buffer.len() as u32).to_be_bytes())?;
encoder.write_all(&buffer)?;
}
}
}
encoder.finish()?;
Ok(())
}
// 解压文件
fn decompress_files(compressed_path: &str, destination_dir: &Path) -> io::Result<()> {
let compressed_file = File::open(compressed_path)?;
let mut decoder = flate2::read::ZlibDecoder::new(BufReader::new(compressed_file));
loop {
let mut name_length_bytes = [0; 4];
if decoder.read_exact(&mut name_length_bytes).is_err() {
break;
}
let name_length = u32::from_be_bytes(name_length_bytes) as usize;
let mut name_bytes = vec![0; name_length];
decoder.read_exact(&mut name_bytes)?;
let file_name = String::from_utf8_lossy(&name_bytes);
let mut content_length_bytes = [0; 4];
decoder.read_exact(&mut content_length_bytes)?;
let content_length = u32::from_be_bytes(content_length_bytes) as usize;
let mut content_bytes = vec![0; content_length];
decoder.read_exact(&mut content_bytes)?;
let output_path = destination_dir.join(&file_name);
fs::create_dir_all(output_path.parent().unwrap())?;
let mut output_file = File::create(output_path)?;
output_file.write_all(&content_bytes)?;
}
Ok(())
}
// 执行程序
fn execute_program(program_path: &Path) -> io::Result<()> {
std::process::Command::new(program_path).spawn()?.wait()?;
Ok(())
}
fn main() -> io::Result<()> {
// 要压缩的文件或目录
let source_paths = ["main.exe", "example.dll", "config.ini"];
let compressed_file_path = "compressed_data.zlib";
// 压缩文件
compress_files(&source_paths, compressed_file_path)?;
// 创建临时目录
let temp_dir = tempdir()?;
let temp_dir_path = temp_dir.path();
// 解压文件
decompress_files(compressed_file_path, temp_dir_path)?;
// 执行主程序
let main_program_path = temp_dir_path.join("main.exe");
execute_program(&main_program_path)?;
// 删除压缩文件
fs::remove_file(compressed_file_path)?;
Ok(())
}
compress_files
函数:- 遍历指定的文件和目录,使用
flate2
库的ZlibEncoder
进行压缩。 - 对于每个文件,先写入文件名的长度和文件名,再写入文件内容的长度和文件内容。
- 遍历指定的文件和目录,使用
decompress_files
函数:- 读取压缩文件,使用
flate2
库的ZlibDecoder
进行解压。 - 根据之前写入的文件名长度和内容长度信息,将文件解压到指定的临时目录。
- 读取压缩文件,使用
execute_program
函数 :使用std::process::Command
执行指定路径的程序。main
函数:- 调用
compress_files
函数将文件压缩到compressed_data.zlib
。 - 创建临时目录,调用
decompress_files
函数将压缩文件解压到临时目录。 - 执行主程序
main.exe
。 - 最后删除压缩文件。
- 调用
编译并运行,生成自解压文件(SFX)
bash
cargo build --release
./target/release/your_project_name
本体打包
在开发环境中使用 cargo
把 Rust 项目编译成可执行文件,并且使用 --release
选项来进行优化,生成高性能的二进制文件。在项目根目录下执行如下命令:
bash
cargo build --release
需要将以上可以生成自解压文件的项目打包,去除cargo流程
安装过程
首先移除 src-tauri/tauri.conf.json
中的 window 这个属性配置
其次修改 src-tauri/main.rs
, 在初始化app时,使用其 setup 这个 api 进行动态化初始一个 window:
rust
tauri::Builder::default()
.setup(|app| {
WindowBuilder::new(app, "main", WindowUrl::App("https://weread.qq.com/".into()))
.title("")
.build();
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
tauri
通过rust
流式下载文件,并计算进度给前端
- 在 Rust 端,使用合适的网络库(如
reqwest
)来发起文件下载请求。 - 在下载过程中,通过读取已接收的数据量来计算下载进度。
- 使用 Tauri 的消息传递机制(例如
invoke
方法)将进度信息传递给前端。
rust
use reqwest::blocking::Response;
use tauri::api::http::send_message;
fn download_file(url: &str) {
let client = reqwest::blocking::Client::new();
let mut response = client.get(url).unwrap();
let total_length = response
.content_length()
.unwrap_or(0);
let mut downloaded_bytes = 0;
let mut buffer = Vec::new();
while let Some(chunk) = response.chunk().unwrap() {
buffer.extend_from_slice(&chunk);
downloaded_bytes += chunk.len();
let progress = (downloaded_bytes as f64 / total_length as f64) * 100.0;
// 将进度发送给前端
send_message("download_progress", progress).unwrap();
}
// 保存文件等后续操作
}

进度
目前已完成前端ui部分
源码在github