use super::{BloatDir, EnforcePolicy, PackageManager, dir_size};
use anyhow::{Result, anyhow};
use std::fs;
use std::path::Path;
const MANIFEST: &str = "ProjectSettings/ProjectVersion.txt";
const EDITOR_LOCK: &str = "Temp/UnityLockfile";
const CACHE_DIRS: &[&str] = &["Library", "Temp"];
pub struct Unity;
impl PackageManager for Unity {
fn name(&self) -> &'static str {
"unity"
}
fn detect(&self, path: &Path) -> bool {
path.join(MANIFEST).is_file()
}
fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
CACHE_DIRS
.iter()
.map(|name| path.join(name))
.filter(|dir| dir.is_dir())
.map(|dir| BloatDir {
name: dir
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned(),
size_bytes: dir_size(&dir),
path: dir,
shared_bytes: 0,
})
.collect()
}
fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
let manifest = path.join(MANIFEST);
let content = fs::read_to_string(&manifest).map_err(|e| {
anyhow!("`{MANIFEST}` could not be read ({e}): nothing to re-import the assets from.")
})?;
if !content.contains("m_EditorVersion:") {
return Err(anyhow!(
"`{MANIFEST}` has no `m_EditorVersion:` line: refusing to treat the \
imported-asset cache as regenerable from it."
));
}
if path.join(EDITOR_LOCK).exists() {
return Err(anyhow!(
"`{EDITOR_LOCK}` exists: the Unity editor has this project open, and \
the asset database is not deletable under it. Close the editor first."
));
}
Ok(())
}
fn restore(&self, _path: &Path, _timeout: std::time::Duration) -> Result<()> {
println!(
"Unity's imported-asset cache regenerates when the editor next opens the \
project; expect a full re-import on the first open"
);
Ok(())
}
fn lockfiles(&self) -> &'static [&'static str] {
&[MANIFEST]
}
fn opt_in(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use tempfile::tempdir;
fn project(dir: &Path) -> PathBuf {
fs::create_dir_all(dir.join("ProjectSettings")).unwrap();
fs::write(
dir.join(MANIFEST),
"m_EditorVersion: 2022.3.10f1\nm_EditorVersionWithRevision: 2022.3.10f1 (ff3792e53c62)\n",
)
.unwrap();
dir.to_path_buf()
}
fn claimed(project: &Path) -> Vec<String> {
Unity
.bloat_dirs(project)
.into_iter()
.map(|b| b.name)
.collect()
}
#[test]
fn detects_on_the_project_manifest() {
let dir = tempdir().unwrap();
assert!(!Unity.detect(dir.path()));
project(dir.path());
assert!(Unity.detect(dir.path()));
}
#[test]
fn the_asset_database_and_scratch_space_are_claimed() {
let dir = tempdir().unwrap();
let root = project(dir.path());
fs::create_dir(root.join("Library")).unwrap();
fs::write(root.join("Library").join("ArtifactDB"), "cache").unwrap();
fs::create_dir(root.join("Temp")).unwrap();
assert_eq!(claimed(&root), vec!["Library", "Temp"]);
}
#[test]
fn nothing_else_beside_the_manifest_is_claimed() {
let dir = tempdir().unwrap();
let root = project(dir.path());
fs::create_dir(root.join("Assets")).unwrap();
assert!(claimed(&root).is_empty());
}
#[test]
fn a_missing_or_bogus_manifest_is_refused() {
let dir = tempdir().unwrap();
let policy = EnforcePolicy::default();
assert!(Unity.enforce_lockfile(dir.path(), policy).is_err());
fs::create_dir_all(dir.path().join("ProjectSettings")).unwrap();
fs::write(dir.path().join(MANIFEST), "hello there").unwrap();
assert!(Unity.enforce_lockfile(dir.path(), policy).is_err());
project(dir.path());
assert!(Unity.enforce_lockfile(dir.path(), policy).is_ok());
}
#[test]
fn an_open_editor_is_refused() {
let dir = tempdir().unwrap();
let root = project(dir.path());
let policy = EnforcePolicy::default();
assert!(Unity.enforce_lockfile(&root, policy).is_ok());
fs::create_dir(root.join("Temp")).unwrap();
fs::write(root.join(EDITOR_LOCK), "").unwrap();
assert!(Unity.enforce_lockfile(&root, policy).is_err());
}
#[test]
fn unity_is_opt_in() {
assert!(Unity.opt_in());
}
}