use std::collections::BTreeSet;
use crate::io::{create_dir_all, has_matching_version, read_file, write_file_if_changed};
use crate::paths::{join_path, parent_directory};
const FINGERPRINT_FILE: &str = "lock-fingerprint";
const LOCK_FINGERPRINT_VERSION: u32 = crate::CACHE_VERSION;
pub fn fingerprint_path(generator_dir: &str) -> String {
join_path(generator_dir, FINGERPRINT_FILE)
}
pub fn load(path: &str) -> Option<Vec<(String, u128)>> {
let content = read_file(path).ok()?;
if !has_matching_version(&content, LOCK_FINGERPRINT_VERSION) {
return None;
}
let mut entries = Vec::new();
for line in content.split('\n') {
if line.is_empty() || line.starts_with('#') {
continue;
}
let pipe = line.find('|')?;
let mtime: u128 = line[pipe + 1..].parse().ok()?;
entries.push((line[..pipe].to_string(), mtime));
}
if entries.is_empty() {
return None;
}
Some(entries)
}
pub fn is_valid(entries: &[(String, u128)]) -> bool {
for (p, recorded) in entries {
let meta = std::fs::metadata(p);
if *recorded == 0 {
if meta.is_ok() {
return false;
}
continue;
}
let Ok(meta) = meta else {
return false;
};
let Some(now) = mtime_nanos(&meta) else {
return false;
};
if now != *recorded {
return false;
}
}
true
}
pub fn build_entries(
project_root: &str,
unity_path: &str,
contributed_paths_relative: &[String],
extra_absolute: &[String],
) -> Vec<(String, u128)> {
let mut paths: BTreeSet<String> = BTreeSet::new();
let mut add_abs = |p: String| {
paths.insert(p);
};
add_abs(unity_path.to_string());
add_abs(join_path(project_root, "ProjectSettings/ProjectVersion.txt"));
add_abs(join_path(project_root, "ProjectSettings/ProjectSettings.asset"));
add_abs(join_path(project_root, "Packages"));
add_abs(join_path(project_root, "Packages/manifest.json"));
add_abs(join_path(project_root, "Assets"));
add_abs(join_path(project_root, "Library/PackageCache"));
for rel in contributed_paths_relative {
let mut dir = parent_directory(rel).to_string();
loop {
if dir.is_empty() {
break;
}
paths.insert(join_path(project_root, &dir));
let parent = parent_directory(&dir).to_string();
if parent == dir {
break;
}
dir = parent;
}
}
for abs in extra_absolute {
paths.insert(abs.clone());
}
paths
.into_iter()
.map(|p| {
let ns = std::fs::metadata(&p)
.ok()
.and_then(|m| mtime_nanos(&m))
.unwrap_or(0);
(p, ns)
})
.collect()
}
pub fn write(
fingerprint_file: &str,
unity_version: &str,
entries: &[(String, u128)],
) -> std::io::Result<()> {
create_dir_all(parent_directory(fingerprint_file));
let mut s = String::from("# lock-fingerprint — auto-generated, do not edit\n");
s.push_str(&format!("# version: {}\n", LOCK_FINGERPRINT_VERSION));
s.push_str(&format!("# unity-version: {}\n", unity_version));
for (path, mtime) in entries {
s.push_str(path);
s.push('|');
s.push_str(&mtime.to_string());
s.push('\n');
}
write_file_if_changed(fingerprint_file, &s).map(|_| ()).map_err(|e| {
std::io::Error::other(format!("{}", e))
})
}
#[cfg(unix)]
fn mtime_nanos(m: &std::fs::Metadata) -> Option<u128> {
use std::os::unix::fs::MetadataExt;
let secs: i64 = m.mtime();
let nanos: i64 = m.mtime_nsec();
if secs < 0 {
return None;
}
Some((secs as u128) * 1_000_000_000 + (nanos as u128))
}
#[cfg(not(unix))]
fn mtime_nanos(m: &std::fs::Metadata) -> Option<u128> {
let mt = m.modified().ok()?;
let d = mt.duration_since(std::time::SystemTime::UNIX_EPOCH).ok()?;
Some(d.as_nanos())
}