Move Pink Herbs to patch-V safely
* Move Pink Herbs to patch-V safely * Update Pink Herbs patch description * Add Pink Herbs migration tests
This commit was merged in pull request #3.
This commit is contained in:
+85
-6
@@ -672,7 +672,7 @@ VISUAL_MOD_REVISIONS = {
|
|||||||
"visual_pretty_night_sky": "1",
|
"visual_pretty_night_sky": "1",
|
||||||
"visual_epoch_water": "1",
|
"visual_epoch_water": "1",
|
||||||
"visual_fog_pushback": "1",
|
"visual_fog_pushback": "1",
|
||||||
"visual_pink_herbs": "1",
|
"visual_pink_herbs": "2",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -943,6 +943,64 @@ def _verify_mpq(path):
|
|||||||
raise RemotePackageError("Downloaded file is not a valid MPQ archive.")
|
raise RemotePackageError("Downloaded file is not a valid MPQ archive.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _files_match_exactly(path_a, path_b):
|
||||||
|
"""Compare two files byte-for-byte without loading them fully into memory."""
|
||||||
|
try:
|
||||||
|
if os.path.getsize(path_a) != os.path.getsize(path_b):
|
||||||
|
return False
|
||||||
|
with open(path_a, "rb") as first, open(path_b, "rb") as second:
|
||||||
|
while True:
|
||||||
|
first_chunk = first.read(1024 * 1024)
|
||||||
|
second_chunk = second.read(1024 * 1024)
|
||||||
|
if first_chunk != second_chunk:
|
||||||
|
return False
|
||||||
|
if not first_chunk:
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_legacy_pink_herbs_patch(target_dir, downloaded_path, progress=None):
|
||||||
|
"""Release legacy patch-H ownership only when doing so is demonstrably safe."""
|
||||||
|
mod_id = "visual_pink_herbs"
|
||||||
|
legacy_rel = _safe_relative_path(os.path.join("Data", "patch-H.mpq"))
|
||||||
|
legacy_key = os.path.normcase(legacy_rel)
|
||||||
|
files = _load_managed_manifest(target_dir, mod_id)
|
||||||
|
|
||||||
|
if not any(os.path.normcase(rel) == legacy_key for rel in files):
|
||||||
|
return
|
||||||
|
|
||||||
|
legacy_target = os.path.join(target_dir, legacy_rel)
|
||||||
|
safe_to_release = not os.path.lexists(legacy_target)
|
||||||
|
if os.path.isfile(legacy_target):
|
||||||
|
safe_to_release = _files_match_exactly(legacy_target, downloaded_path)
|
||||||
|
|
||||||
|
if safe_to_release:
|
||||||
|
_restore_or_remove_managed_file(target_dir, mod_id, legacy_rel)
|
||||||
|
_emit_progress(
|
||||||
|
progress,
|
||||||
|
"Released legacy Pink Herbs patch-H.mpq for migration to patch-V.mpq.",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_emit_progress(
|
||||||
|
progress,
|
||||||
|
"Keeping existing patch-H.mpq because it no longer matches Pink Herbs.",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = _load_managed_manifest_data(target_dir, mod_id)
|
||||||
|
remaining = [rel for rel in files if os.path.normcase(rel) != legacy_key]
|
||||||
|
_write_managed_manifest(
|
||||||
|
target_dir,
|
||||||
|
mod_id,
|
||||||
|
remaining,
|
||||||
|
revision=data.get("revision"),
|
||||||
|
)
|
||||||
|
|
||||||
def _install_remote_mpq(
|
def _install_remote_mpq(
|
||||||
target_dir,
|
target_dir,
|
||||||
mod_id,
|
mod_id,
|
||||||
@@ -1038,15 +1096,36 @@ def install_fog_pushback(target_dir, progress=None):
|
|||||||
|
|
||||||
|
|
||||||
def install_pink_herbs(target_dir, progress=None):
|
def install_pink_herbs(target_dir, progress=None):
|
||||||
_install_remote_mpq(
|
mod_id = "visual_pink_herbs"
|
||||||
target_dir,
|
destination = os.path.join("Data", "patch-V.mpq")
|
||||||
"visual_pink_herbs",
|
temp_path = _download(
|
||||||
"https://raw.githubusercontent.com/seacrabsam/patch-herb/main/patch-H.mpq",
|
"https://raw.githubusercontent.com/seacrabsam/patch-herb/main/patch-H.mpq",
|
||||||
os.path.join("Data", "patch-H.mpq"),
|
suffix=".mpq",
|
||||||
progress=progress,
|
progress=progress,
|
||||||
label="Downloading Pink Herbs",
|
label="Downloading Pink Herbs",
|
||||||
revision=VISUAL_MOD_REVISIONS["visual_pink_herbs"],
|
timeout=300,
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
_verify_mpq(temp_path)
|
||||||
|
_migrate_legacy_pink_herbs_patch(target_dir, temp_path, progress=progress)
|
||||||
|
_emit_progress(
|
||||||
|
progress,
|
||||||
|
f"Installing {os.path.basename(destination)}...",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
_install_managed_files(
|
||||||
|
target_dir,
|
||||||
|
mod_id,
|
||||||
|
[(temp_path, destination)],
|
||||||
|
revision=VISUAL_MOD_REVISIONS[mod_id],
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.remove(temp_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
return "seacrabsam/patch-herb main"
|
return "seacrabsam/patch-herb main"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -129,7 +129,7 @@ class WowSetupTool:
|
|||||||
"pretty_night_sky": "Replaces the Vanilla night sky with a more detailed starry sky. Installed file: Data\\patch-Z.mpq. The original hosted patch-9 name is deliberately changed so the official numeric patch-9.mpq is never overwritten.",
|
"pretty_night_sky": "Replaces the Vanilla night sky with a more detailed starry sky. Installed file: Data\\patch-Z.mpq. The original hosted patch-9 name is deliberately changed so the official numeric patch-9.mpq is never overwritten.",
|
||||||
"epoch_water": "Replaces Vanilla water textures with the Epoch Water visual pack. Installed file: Data\\patch-W.mpq.",
|
"epoch_water": "Replaces Vanilla water textures with the Epoch Water visual pack. Installed file: Data\\patch-W.mpq.",
|
||||||
"fog_pushback": "Pushes environmental fog farther back for a clearer long-distance view. Installed file: Data\\patch-Y.mpq. Works best together with an increased Farclip value.",
|
"fog_pushback": "Pushes environmental fog farther back for a clearer long-distance view. Installed file: Data\\patch-Y.mpq. Works best together with an increased Farclip value.",
|
||||||
"pink_herbs": "Turns most herb-node textures bright pink/purple to make gathering nodes easier to spot. Installed file: Data\\patch-H.mpq.",
|
"pink_herbs": "Turns most herb-node textures bright pink/purple to make gathering nodes easier to spot. Installed file: Data\\patch-V.mpq. The patch-V name avoids conflicts with other visual packs that use patch-H.mpq.",
|
||||||
"no_error_sounds": "Installs the complete NoErrorSounds pack: muted spell fizzle sounds plus its included muted interface sounds. Installed as loose WAV files under Sound\\Spells\\Fizzle and Sound\\interface (no MPQ).",
|
"no_error_sounds": "Installs the complete NoErrorSounds pack: muted spell fizzle sounds plus its included muted interface sounds. Installed as loose WAV files under Sound\\Spells\\Fizzle and Sound\\interface (no MPQ).",
|
||||||
"fish_ping": "Replaces the fishing bite sound with a much more noticeable ping. Installed file: Sound\\Spells\\Tradeskills\\FishBite.wav (no MPQ). Designed specifically for WoW Vanilla 1.12.1.",
|
"fish_ping": "Replaces the fishing bite sound with a much more noticeable ping. Installed file: Sound\\Spells\\Tradeskills\\FishBite.wav (no MPQ). Designed specifically for WoW Vanilla 1.12.1.",
|
||||||
"warlock_muted_demons": "Mutes the repeated voice lines from Warlock demons using Vanilla-compatible loose sound replacements. Installed as loose WAV files under Data\\Sound\\Creature (no MPQ)."
|
"warlock_muted_demons": "Mutes the repeated voice lines from Warlock demons using Vanilla-compatible loose sound replacements. Installed as loose WAV files under Data\\Sound\\Creature (no MPQ)."
|
||||||
|
|||||||
@@ -279,6 +279,72 @@ class ManagedPackageTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_pink_herbs_v2_migration_restores_original_patch_h(self):
|
||||||
|
with tempfile.TemporaryDirectory() as root, tempfile.TemporaryDirectory() as src:
|
||||||
|
mod_id = "visual_pink_herbs"
|
||||||
|
legacy_rel = os.path.join("Data", "patch-H.mpq")
|
||||||
|
new_rel = os.path.join("Data", "patch-V.mpq")
|
||||||
|
legacy_target = os.path.join(root, legacy_rel)
|
||||||
|
source = os.path.join(src, "pink-herbs.mpq")
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(legacy_target))
|
||||||
|
with open(source, "wb") as handle:
|
||||||
|
handle.write(b"MPQ pink-herbs")
|
||||||
|
with open(legacy_target, "wb") as handle:
|
||||||
|
handle.write(b"MPQ pink-herbs")
|
||||||
|
|
||||||
|
_, _, backup_root = remote_packages._managed_locations(root, mod_id)
|
||||||
|
backup_h = os.path.join(backup_root, legacy_rel)
|
||||||
|
os.makedirs(os.path.dirname(backup_h))
|
||||||
|
with open(backup_h, "wb") as handle:
|
||||||
|
handle.write(b"MPQ faithful-upscale")
|
||||||
|
|
||||||
|
remote_packages._write_managed_manifest(
|
||||||
|
root, mod_id, [legacy_rel], revision="1"
|
||||||
|
)
|
||||||
|
remote_packages._migrate_legacy_pink_herbs_patch(root, source)
|
||||||
|
remote_packages._install_managed_files(
|
||||||
|
root, mod_id, [(source, new_rel)], revision="2"
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(legacy_target, "rb") as handle:
|
||||||
|
self.assertEqual(handle.read(), b"MPQ faithful-upscale")
|
||||||
|
with open(os.path.join(root, new_rel), "rb") as handle:
|
||||||
|
self.assertEqual(handle.read(), b"MPQ pink-herbs")
|
||||||
|
|
||||||
|
def test_pink_herbs_v2_migration_preserves_replaced_patch_h(self):
|
||||||
|
with tempfile.TemporaryDirectory() as root, tempfile.TemporaryDirectory() as src:
|
||||||
|
mod_id = "visual_pink_herbs"
|
||||||
|
legacy_rel = os.path.join("Data", "patch-H.mpq")
|
||||||
|
new_rel = os.path.join("Data", "patch-V.mpq")
|
||||||
|
legacy_target = os.path.join(root, legacy_rel)
|
||||||
|
source = os.path.join(src, "pink-herbs.mpq")
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(legacy_target))
|
||||||
|
with open(source, "wb") as handle:
|
||||||
|
handle.write(b"MPQ pink-herbs")
|
||||||
|
with open(legacy_target, "wb") as handle:
|
||||||
|
handle.write(b"MPQ replacement-patch-h")
|
||||||
|
|
||||||
|
_, _, backup_root = remote_packages._managed_locations(root, mod_id)
|
||||||
|
backup_h = os.path.join(backup_root, legacy_rel)
|
||||||
|
os.makedirs(os.path.dirname(backup_h))
|
||||||
|
with open(backup_h, "wb") as handle:
|
||||||
|
handle.write(b"MPQ older-original-patch-h")
|
||||||
|
|
||||||
|
remote_packages._write_managed_manifest(
|
||||||
|
root, mod_id, [legacy_rel], revision="1"
|
||||||
|
)
|
||||||
|
remote_packages._migrate_legacy_pink_herbs_patch(root, source)
|
||||||
|
remote_packages._install_managed_files(
|
||||||
|
root, mod_id, [(source, new_rel)], revision="2"
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(legacy_target, "rb") as handle:
|
||||||
|
self.assertEqual(handle.read(), b"MPQ replacement-patch-h")
|
||||||
|
with open(os.path.join(root, new_rel), "rb") as handle:
|
||||||
|
self.assertEqual(handle.read(), b"MPQ pink-herbs")
|
||||||
|
|
||||||
def test_transactional_sound_pack_rolls_back_all_files_and_manifest(self):
|
def test_transactional_sound_pack_rolls_back_all_files_and_manifest(self):
|
||||||
with tempfile.TemporaryDirectory() as root, tempfile.TemporaryDirectory() as src:
|
with tempfile.TemporaryDirectory() as root, tempfile.TemporaryDirectory() as src:
|
||||||
mod_id = "audio_test"
|
mod_id = "audio_test"
|
||||||
|
|||||||
Reference in New Issue
Block a user