mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Resource packs: a bad pack never blocks import or launch; hard-link fallback for downloads
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
9f2a4433ec
commit
b499251187
4 files changed
+44
-14
No files matched your search
@@ -268,8 +268,25 @@ pub(crate) fn store_download(
|
||||
fs::rename(&part, dest).map_err(|e| e.to_string())
|
||||
} else {
|
||||
// Publish without replacing a file that appeared while the request was in flight.
|
||||
fs::hard_link(&part, dest).map_err(|e| e.to_string())?;
|
||||
fs::remove_file(&part).map_err(|e| e.to_string())
|
||||
match fs::hard_link(&part, dest) {
|
||||
Ok(()) => fs::remove_file(&part).map_err(|e| e.to_string()),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Err(e.to_string()),
|
||||
Err(_) => {
|
||||
// No hard links here (FAT, some network mounts): copy into a new file.
|
||||
let mut out = fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(dest)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let written = out.write_all(&bytes).map_err(|e| e.to_string());
|
||||
drop(out);
|
||||
if written.is_err() {
|
||||
let _ = fs::remove_file(dest);
|
||||
}
|
||||
let _ = fs::remove_file(&part);
|
||||
written
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if result.is_err() {
|
||||
|
||||
@@ -25,7 +25,11 @@ pub fn read_json<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T, Strin
|
||||
pub fn import(source: &Path, work: &Path, log: Log) -> Result<import::Pack, String> {
|
||||
let pack = import::import(source)?;
|
||||
fs::create_dir_all(work).map_err(|e| format!("{}: {e}", work.display()))?;
|
||||
crate::resourcepacks::stage(source, work, &pack)?.save(work)?;
|
||||
// A bad resource-pack entry must not stop the pack's mods from importing.
|
||||
match crate::resourcepacks::stage(source, work, &pack).and_then(|r| r.save(work)) {
|
||||
Ok(()) => {}
|
||||
Err(e) => log(format!("resource packs not staged: {e}")),
|
||||
}
|
||||
write_json(&work.join("pack.json"), &pack)?;
|
||||
let mut kinds = BTreeMap::<&str, usize>::new();
|
||||
for f in &pack.files {
|
||||
|
||||
@@ -145,7 +145,8 @@ fn hostile_archive_paths_and_cross_layer_collisions_are_rejected_before_staging(
|
||||
(&format!("overrides/resourcepacks/{name}"), b"bad"),
|
||||
],
|
||||
);
|
||||
assert!(imported(&src, &t.work()).is_err(), "{name}");
|
||||
// A bad entry leaves the packs out; it no longer fails the import.
|
||||
assert!(imported(&src, &t.work()).is_ok(), "{name}");
|
||||
assert!(available(&t.work()).is_empty());
|
||||
assert!(!t.work().join("escape.zip").exists());
|
||||
}
|
||||
@@ -162,7 +163,7 @@ fn hostile_archive_paths_and_cross_layer_collisions_are_rejected_before_staging(
|
||||
),
|
||||
],
|
||||
);
|
||||
assert!(imported(&src, &t.work()).is_err());
|
||||
assert!(imported(&src, &t.work()).is_ok());
|
||||
assert!(available(&t.work()).is_empty());
|
||||
}
|
||||
|
||||
@@ -209,7 +210,8 @@ fn symlinks_at_source_and_destination_are_rejected() {
|
||||
put(&src.join("resourcepacks/good.zip"), b"good");
|
||||
put(&t.0.join("outside.zip"), b"outside");
|
||||
symlink(t.0.join("outside.zip"), src.join("resourcepacks/link.zip")).unwrap();
|
||||
assert!(imported(&src, &t.work()).is_err());
|
||||
// A bad entry leaves the packs out; it no longer fails the import.
|
||||
assert!(imported(&src, &t.work()).is_ok());
|
||||
fs::remove_file(src.join("resourcepacks/link.zip")).unwrap();
|
||||
imported(&src, &t.work()).unwrap();
|
||||
let home = t.0.join("home");
|
||||
@@ -381,7 +383,8 @@ fn duplicate_archive_entries_are_rejected() {
|
||||
}
|
||||
}
|
||||
fs::write(&src, bytes).unwrap();
|
||||
assert!(imported(&src, &t.work()).is_err());
|
||||
// A bad entry leaves the packs out; it no longer fails the import.
|
||||
assert!(imported(&src, &t.work()).is_ok());
|
||||
assert!(available(&t.work()).is_empty());
|
||||
}
|
||||
|
||||
@@ -400,7 +403,8 @@ fn archive_symlinks_are_refused_and_file_counts_are_bounded() {
|
||||
)
|
||||
.unwrap();
|
||||
zip.finish().unwrap();
|
||||
assert!(imported(&src, &t.work()).is_err());
|
||||
// A bad entry leaves the packs out; it no longer fails the import.
|
||||
assert!(imported(&src, &t.work()).is_ok());
|
||||
assert!(available(&t.work()).is_empty());
|
||||
let mut files: Files = (0..MAX_FILES)
|
||||
.map(|n| (format!("pack/{n}"), Vec::new()))
|
||||
|
||||
@@ -214,7 +214,7 @@ pub fn import(path: &Path, log: Log) -> Result<Imported, String> {
|
||||
pipeline::import(path, &work, log)?;
|
||||
pipeline::inventory(&work, false, log)?;
|
||||
pipeline::report(&work, log)?;
|
||||
prepare_resourcepacks(&work)?;
|
||||
prepare_resourcepacks(&work);
|
||||
load(&work)
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ pub fn port(
|
||||
log: Log,
|
||||
) -> Result<Imported, String> {
|
||||
pipeline::port(work, resources, from, log)?;
|
||||
prepare_resourcepacks(work)?;
|
||||
prepare_resourcepacks(work);
|
||||
load(work)
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ pub fn apply_profile(cmd: &mut Command, work: &Path) -> Result<(), String> {
|
||||
if mods.is_empty() && resourcepacks::available(work).is_empty() {
|
||||
return Err("No content mods or resource packs are available: port the pack first.".into());
|
||||
}
|
||||
prepare_resourcepacks(work)?;
|
||||
prepare_resourcepacks(work);
|
||||
let home = profile_home(work);
|
||||
fs::create_dir_all(&home).map_err(|e| format!("{}: {e}", home.display()))?;
|
||||
let options = home.join("options.txt");
|
||||
@@ -316,9 +316,14 @@ pub fn apply_profile(cmd: &mut Command, work: &Path) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_resourcepacks(work: &Path) -> Result<(), String> {
|
||||
let report = resourcepacks::install(work, &profile_home(work))?;
|
||||
pipeline::write_json(&work.join("profile-resourcepacks.json"), &report)
|
||||
/// Installs the staged resource packs into the profile. A pack that cannot be installed
|
||||
/// is logged and left off; it never blocks importing, porting or launching the mods.
|
||||
fn prepare_resourcepacks(work: &Path) {
|
||||
let result = resourcepacks::install(work, &profile_home(work))
|
||||
.and_then(|report| pipeline::write_json(&work.join("profile-resourcepacks.json"), &report));
|
||||
if let Err(e) = result {
|
||||
eprintln!("resource packs not installed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user