feat: better compression

This commit is contained in:
zhom
2025-08-10 02:01:31 +04:00
parent 61785dc8f5
commit d7b711c0ea
11 changed files with 312 additions and 121 deletions
+9 -9
View File
@@ -7,24 +7,24 @@ fn main() {
let dest_path = Path::new(&out_dir).join("data.rs");
// Check if we have embedded data files
let zip_data_path = Path::new("embedded_data.zip");
let xz_data_path = Path::new("embedded_data.xz");
let build_id_path = Path::new("build_id.txt");
if zip_data_path.exists() && build_id_path.exists() {
if xz_data_path.exists() && build_id_path.exists() {
// Read the build ID
let build_id = fs::read_to_string(build_id_path)
.expect("Failed to read build ID");
// Copy the zip file to the OUT_DIR so include_bytes! can find it
let out_zip_path = Path::new(&out_dir).join("embedded_data.zip");
fs::copy(zip_data_path, &out_zip_path)
// Copy the xz file to the OUT_DIR so include_bytes! can find it
let out_xz_path = Path::new(&out_dir).join("embedded_data.xz");
fs::copy(xz_data_path, &out_xz_path)
.expect("Failed to copy embedded data to OUT_DIR");
// Generate the data.rs file with embedded data
let data_rs_content = format!(
r#"
// Generated at build time - contains embedded application data
const ZIP_DATA: &[u8] = include_bytes!("embedded_data.zip");
// Generated at build time - contains embedded application data (xz-compressed zip)
const XZ_DATA: &[u8] = include_bytes!("embedded_data.xz");
const BUILD_ID: &str = "{}";
"#,
build_id.trim()
@@ -36,7 +36,7 @@ const BUILD_ID: &str = "{}";
// Generate placeholder data for template compilation
let data_rs_content = r#"
// Placeholder data for template compilation
const ZIP_DATA: &[u8] = &[];
const XZ_DATA: &[u8] = &[];
const BUILD_ID: &str = "template";
"#;
@@ -45,6 +45,6 @@ const BUILD_ID: &str = "template";
}
// Tell Cargo to rerun this script if the embedded data changes
println!("cargo:rerun-if-changed=embedded_data.zip");
println!("cargo:rerun-if-changed=embedded_data.xz");
println!("cargo:rerun-if-changed=build_id.txt");
}
+2
View File
@@ -9,6 +9,8 @@ directories = "6"
zip = "4"
serde_json = "1.0"
fs2 = "0.4"
lzma-rs = "0.3"
walkdir = "2.4"
[build-dependencies]
# No build dependencies needed - data is embedded at compile time
+71 -20
View File
@@ -4,6 +4,7 @@ use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::ffi::OsString;
use zip::ZipArchive;
use directories::BaseDirs;
use fs2::FileExt;
@@ -65,29 +66,59 @@ fn get_cache_dir() -> Result<PathBuf> {
}
fn get_node_executable_path(app_dir: &Path) -> PathBuf {
let node_dir = app_dir.join("node");
if cfg!(windows) {
// On Windows, Node.js is extracted to node/{platform}/node.exe
// Try to find the platform-specific subdirectory
let node_dir = app_dir.join("node");
// Look for platform-specific subdirectories
if let Ok(entries) = fs::read_dir(&node_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
let node_exe = path.join("node.exe");
if node_exe.exists() {
return node_exe;
// Prefer common locations first
let candidates = [
node_dir.join("node.exe"),
];
for c in candidates {
if c.exists() {
return c;
}
}
// Recursively search for node.exe under node/
if node_dir.exists() {
for entry in walkdir::WalkDir::new(&node_dir).follow_links(true) {
if let Ok(e) = entry {
let p = e.path();
if p.is_file() {
if let Some(name) = p.file_name().and_then(|n| n.to_str()) {
if name.eq_ignore_ascii_case("node.exe") {
return p.to_path_buf();
}
}
}
}
}
}
// Fallback to direct path (shouldn't happen with new extraction logic)
// Fallback: default where Windows Node is usually at after extraction
node_dir.join("node.exe")
} else {
// On Unix systems, Node.js is in node/bin/node
app_dir.join("node").join("bin").join("node")
let candidate = node_dir.join("bin").join("node");
if candidate.exists() {
candidate
} else {
// As a last resort, search recursively
if node_dir.exists() {
for entry in walkdir::WalkDir::new(&node_dir).follow_links(true) {
if let Ok(e) = entry {
let p = e.path();
if p.is_file() {
if let Some(name) = p.file_name().and_then(|n| n.to_str()) {
if name == "node" {
return p.to_path_buf();
}
}
}
}
}
}
candidate
}
}
}
@@ -136,8 +167,14 @@ fn extract_application(app_dir: &Path) -> Result<()> {
// Create app directory
fs::create_dir_all(app_dir).context("Failed to create app directory")?;
// Extract embedded zip data
let cursor = Cursor::new(ZIP_DATA);
// Decompress embedded XZ data to get inner ZIP, then extract
let mut tar_buf: Vec<u8> = Vec::new();
{
let mut reader = Cursor::new(XZ_DATA);
lzma_rs::xz_decompress(&mut reader, &mut tar_buf)
.context("Failed to decompress embedded xz data")?;
}
let cursor = Cursor::new(tar_buf);
let mut archive = ZipArchive::new(cursor).context("Failed to open embedded zip archive")?;
for i in 0..archive.len() {
@@ -296,12 +333,26 @@ fn run_app(app_dir: &Path, args: &[String]) -> Result<()> {
let mut cmd_args = vec![main_script.clone()];
cmd_args.extend(args.iter().cloned());
// Execute Node.js application with a few retries to tolerate transient Windows issues
let mut status = None;
let mut last_err: Option<anyhow::Error> = None;
let max_attempts: u32 = 8;
let mut status: Option<std::process::ExitStatus> = None;
for attempt in 1..=max_attempts {
match Command::new(&node_executable)
// Prepend Node's directory to PATH and launch via program name to avoid path parsing quirks
let node_bin_dir = node_executable
.parent()
.ok_or_else(|| anyhow::anyhow!("Invalid node executable path: {}", node_executable.display()))?
.to_path_buf();
let program_name = if cfg!(windows) { "node.exe" } else { "node" };
let mut cmd = Command::new(program_name);
// Ensure PATH includes the Node directory first
let mut new_path = std::env::var_os("PATH").unwrap_or_default();
let sep = if cfg!(windows) { ";" } else { ":" };
let mut prefixed: OsString = OsString::new();
prefixed.push(node_bin_dir.as_os_str());
prefixed.push(sep);
prefixed.push(&new_path);
cmd.env("PATH", prefixed);
match cmd
.args(&cmd_args)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())