mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-14 00:11:01 +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(crate) struct BinaryReader {
|
||||||
pub files: Vec<BinaryData>,
|
pub files: Vec<BinaryData>,
|
||||||
pub exe: String,
|
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 {
|
impl Default for BinaryReader {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let (files, exe) = merge(read_embedded(), read_package());
|
let package = read_package();
|
||||||
Self { files, exe }
|
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");
|
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]
|
#[test]
|
||||||
fn matches_paths_across_separator_styles() {
|
fn matches_paths_across_separator_styles() {
|
||||||
// generate.py emits backslashes when it runs on Windows.
|
// 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: &[u8] = &[];
|
||||||
const APP_METADATA_CONFIG: &str = "meta.toml";
|
const APP_METADATA_CONFIG: &str = "meta.toml";
|
||||||
const META_LINE_PREFIX_TIMESTAMP: &str = "timestamp = ";
|
const META_LINE_PREFIX_TIMESTAMP: &str = "timestamp = ";
|
||||||
|
const META_LINE_PREFIX_FILE: &str = "file = ";
|
||||||
const APP_PREFIX: &str = "rustdesk";
|
const APP_PREFIX: &str = "rustdesk";
|
||||||
const APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME";
|
const APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME";
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -62,12 +63,47 @@ fn is_timestamp_matches(dir: &Path, ts: &mut u64) -> bool {
|
|||||||
false
|
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);
|
let meta_file = dir.join(APP_METADATA_CONFIG);
|
||||||
if ts != 0 {
|
let mut content = format!("{}{}\n", META_LINE_PREFIX_TIMESTAMP, ts);
|
||||||
let content = format!("{}{}", META_LINE_PREFIX_TIMESTAMP, ts);
|
for path in package_paths {
|
||||||
// Ignore is ok here
|
content.push_str(&format!("{}{}\n", META_LINE_PREFIX_FILE, path));
|
||||||
let _ = std::fs::write(meta_file, content);
|
}
|
||||||
|
// 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();
|
std::fs::remove_dir_all(&dir).ok();
|
||||||
}
|
}
|
||||||
|
remove_dropped_package_files(&dir, &reader.package_paths);
|
||||||
for file in reader.files.iter() {
|
for file in reader.files.iter() {
|
||||||
file.write_to_file(&dir);
|
file.write_to_file(&dir);
|
||||||
}
|
}
|
||||||
write_meta(&dir, ts);
|
write_meta(&dir, ts, &reader.package_paths);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
win::copy_runtime_broker(&dir);
|
win::copy_runtime_broker(&dir);
|
||||||
#[cfg(linux)]
|
#[cfg(linux)]
|
||||||
|
|||||||
Reference in New Issue
Block a user