mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-12 15:31:02 +03:00
fix(portable): delete files a later package no longer carries
The extraction directory is wiped only when the packer's compiled-in timestamp changes. That used to be per customer, because generate.py ran for each build; now the packer is compiled once per release, so every customer and every rebuild within a release share one timestamp and nothing is ever wiped. A customer who removes their logo and rebuilds would therefore keep showing it: the new package simply omits logo.png, and md5 skipping only covers files that are still present. Record the package's paths in the extraction's meta file and delete the ones a later package drops. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7fBdTwziR5BHTkSz7Tzcm
This commit is contained in:
@@ -33,12 +33,23 @@ pub(crate) struct BinaryData {
|
||||
pub(crate) struct BinaryReader {
|
||||
pub files: Vec<BinaryData>,
|
||||
pub exe: String,
|
||||
// Paths supplied by the per-customer package. Recorded so that a file dropped
|
||||
// from a later package -- a logo the customer removed, say -- can be deleted
|
||||
// from an existing extraction, which the timestamp wipe no longer covers now
|
||||
// that the packer is built once per release rather than once per customer.
|
||||
pub package_paths: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for BinaryReader {
|
||||
fn default() -> Self {
|
||||
let (files, exe) = merge(read_embedded(), read_package());
|
||||
Self { files, exe }
|
||||
let package = read_package();
|
||||
let package_paths = package.0.iter().map(|f| f.path.clone()).collect();
|
||||
let (files, exe) = merge(read_embedded(), package);
|
||||
Self {
|
||||
files,
|
||||
exe,
|
||||
package_paths,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +358,23 @@ mod tests {
|
||||
assert_eq!(entry(&files, "./librustdesk.dll").unwrap().raw, b"core");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_paths_are_recorded_for_the_dropped_file_sweep() {
|
||||
let package = parse(blob(
|
||||
&[("./custom.txt", b"cfg"), ("./data/logo.png", b"img")],
|
||||
"./acme.exe",
|
||||
))
|
||||
.unwrap();
|
||||
let mut paths: Vec<String> = package.0.iter().map(|f| f.path.clone()).collect();
|
||||
paths.sort();
|
||||
assert_eq!(paths, vec!["./custom.txt", "./data/logo.png"]);
|
||||
|
||||
// Merging must not disturb them: the generic payload contributes none.
|
||||
let embedded = parse(blob(&[("./librustdesk.dll", b"core")], "./rustdesk.exe")).unwrap();
|
||||
let (files, _) = merge(embedded, package);
|
||||
assert!(entry(&files, "./data/logo.png").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_paths_across_separator_styles() {
|
||||
// generate.py emits backslashes when it runs on Windows.
|
||||
|
||||
@@ -17,6 +17,7 @@ const APP_METADATA: &[u8] = include_bytes!("../app_metadata.toml");
|
||||
const APP_METADATA: &[u8] = &[];
|
||||
const APP_METADATA_CONFIG: &str = "meta.toml";
|
||||
const META_LINE_PREFIX_TIMESTAMP: &str = "timestamp = ";
|
||||
const META_LINE_PREFIX_FILE: &str = "file = ";
|
||||
const APP_PREFIX: &str = "rustdesk";
|
||||
const APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME";
|
||||
#[cfg(windows)]
|
||||
@@ -62,12 +63,47 @@ fn is_timestamp_matches(dir: &Path, ts: &mut u64) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn write_meta(dir: &Path, ts: u64) {
|
||||
fn write_meta(dir: &Path, ts: u64, package_paths: &[String]) {
|
||||
let meta_file = dir.join(APP_METADATA_CONFIG);
|
||||
if ts != 0 {
|
||||
let content = format!("{}{}", META_LINE_PREFIX_TIMESTAMP, ts);
|
||||
// Ignore is ok here
|
||||
let _ = std::fs::write(meta_file, content);
|
||||
let mut content = format!("{}{}\n", META_LINE_PREFIX_TIMESTAMP, ts);
|
||||
for path in package_paths {
|
||||
content.push_str(&format!("{}{}\n", META_LINE_PREFIX_FILE, path));
|
||||
}
|
||||
// Ignore is ok here
|
||||
let _ = std::fs::write(meta_file, content);
|
||||
}
|
||||
|
||||
fn previous_package_files(dir: &Path) -> Vec<String> {
|
||||
let Ok(content) = std::fs::read_to_string(dir.join(APP_METADATA_CONFIG)) else {
|
||||
return Vec::new();
|
||||
};
|
||||
content
|
||||
.lines()
|
||||
.filter_map(|line| line.strip_prefix(META_LINE_PREFIX_FILE))
|
||||
.map(|path| path.trim().to_owned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn normalized(path: &str) -> String {
|
||||
path.replace('\\', "/").trim_start_matches("./").to_lowercase()
|
||||
}
|
||||
|
||||
// A customer who drops a branding asset gets a package without it, and the file
|
||||
// would otherwise linger in an existing extraction and keep being used. The wipe
|
||||
// cannot cover this: it is keyed on the packer's build timestamp, which is now the
|
||||
// same for every customer of a release.
|
||||
fn remove_dropped_package_files(dir: &Path, current: &[String]) {
|
||||
let keep: std::collections::HashSet<String> = current.iter().map(|p| normalized(p)).collect();
|
||||
for previous in previous_package_files(dir) {
|
||||
let normalized_previous = normalized(&previous);
|
||||
if keep.contains(&normalized_previous) || normalized_previous.contains("..") {
|
||||
continue;
|
||||
}
|
||||
let path = dir.join(&previous);
|
||||
if path.is_file() {
|
||||
println!("removing dropped {}", previous);
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +135,11 @@ fn setup(
|
||||
}
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
remove_dropped_package_files(&dir, &reader.package_paths);
|
||||
for file in reader.files.iter() {
|
||||
file.write_to_file(&dir);
|
||||
}
|
||||
write_meta(&dir, ts);
|
||||
write_meta(&dir, ts, &reader.package_paths);
|
||||
#[cfg(windows)]
|
||||
win::copy_runtime_broker(&dir);
|
||||
#[cfg(linux)]
|
||||
|
||||
Reference in New Issue
Block a user