Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update process when setting latest UMU-Proton #72

Merged
merged 3 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 73 additions & 3 deletions umu/umu_dl_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from umu_log import log
from umu_consts import STEAM_COMPAT
from tempfile import mkdtemp
from threading import Thread

try:
from tarfile import tar_filter
Expand Down Expand Up @@ -275,7 +276,39 @@ def _get_latest(

_fetch_proton(env, tmp, files)

_extract_dir(tmp.joinpath(tarball), steam_compat)
# Set latest UMU/GE-Proton
if version == "UMU-Proton":
threads: List[Thread] = []
log.debug("Updating UMU-Proton")
old_versions: List[Path] = sorted(
[
file
for file in steam_compat.glob("*")
if file.name.startswith(("UMU-Proton", "ULWGL-Proton"))
]
)
tar_path: Path = tmp.joinpath(tarball)

# Extract the latest archive and update UMU-Proton
# Will extract and remove the previous stable versions
# Though, ideally, an in-place differential update would be
# performed instead for this job but this will do for now
log.debug("Extracting %s -> %s", tar_path, steam_compat)
extract: Thread = Thread(target=_extract_dir, args=[tar_path, steam_compat])
extract.start()
threads.append(extract)
update: Thread = Thread(
target=_update_proton, args=[proton, steam_compat, old_versions]
)
update.start()
threads.append(update)
for thread in threads:
thread.join()
else:
# For GE-Proton, keep the previous build. Since it's a rebase
# of bleeding edge, regressions are more likely to occur
_extract_dir(tmp.joinpath(tarball), steam_compat)

environ["PROTONPATH"] = steam_compat.joinpath(proton).as_posix()
env["PROTONPATH"] = environ["PROTONPATH"]

Expand All @@ -289,7 +322,6 @@ def _get_latest(
tarball: str = files[1][0]

# Digest mismatched
# Refer to the cache for old version next
# Since we do not want the user to use a suspect file, delete it
tmp.joinpath(tarball).unlink(missing_ok=True)
return None
Expand All @@ -299,11 +331,49 @@ def _get_latest(

# Exit cleanly
# Clean up extracted data and cache to prevent corruption/errors
# Refer to the cache for old version next
_cleanup(tarball, proton_dir, tmp, steam_compat)
return None
except HTTPException: # Download failed
log.exception("HTTPException")
return None

return env


def _update_proton(proton: str, steam_compat: Path, old_versions: List[Path]) -> None:
"""Create a symbolic link and remove the previous UMU-Proton.

The symbolic link will be used by clients to reference the PROTONPATH
which can be used for tasks such as killing the running wineserver in
the prefix

Assumes that the directories that are named ULWGL/UMU-Proton is ours
and will be removed.
"""
threads: List[Thread] = []
old: Path = None
log.debug("Old: %s", old_versions)
log.debug("Linking UMU-Latest -> %s", proton)
steam_compat.joinpath("UMU-Latest").unlink(missing_ok=True)
steam_compat.joinpath("UMU-Latest").symlink_to(proton)

if not old_versions:
return

old = old_versions.pop()
if old.is_dir():
log.debug("Removing: %s", old)
oldest: Thread = Thread(target=rmtree, args=[old.as_posix()])
oldest.start()
threads.append(oldest)

for proton in old_versions:
if proton.is_dir():
log.debug("Old stable build found")
log.debug("Removing: %s", proton)
sibling: Thread = Thread(target=rmtree, args=[proton.as_posix()])
sibling.start()
threads.append(sibling)

for thread in threads:
thread.join()
71 changes: 71 additions & 0 deletions umu/umu_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,77 @@ def test_latest_offline(self):
self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to be empty")
self.assertFalse(result, "Expected None to be returned from _get_latest")

def test_latest_umu(self):
"""Test _get_latest when online and when an empty PROTONPATH is set.

Tests that the latest UMU-Proton was set to PROTONPATH and old
stable versions were removed in the process.
"""
result = None
latest = Path("UMU-Proton-9.0-beta16")
latest.mkdir()
Path(f"{latest}.sha512sum").touch()
files = [(f"{latest}.sha512sum", ""), (f"{latest}.tar.gz", "")]

# Mock the latest Proton in /tmp
test_archive = self.test_cache.joinpath(f"{latest}.tar.gz")
with tarfile.open(test_archive.as_posix(), "w:gz") as tar:
tar.add(latest.as_posix(), arcname=latest.as_posix())

# Mock old versions
self.test_compat.joinpath("UMU-Proton-9.0-beta15").mkdir()
self.test_compat.joinpath("UMU-Proton-9.0-beta14").mkdir()
self.test_compat.joinpath("ULWGL-Proton-8.0-5-2").mkdir()

# Create foo files and GE-Proton. We do *not* want unintended
# removals
self.test_compat.joinpath("foo").mkdir()
self.test_compat.joinpath("GE-Proton9-2").mkdir()

os.environ["PROTONPATH"] = ""

with (
patch("umu_dl_util._fetch_proton"),
):
result = umu_dl_util._get_latest(
self.env, self.test_compat, self.test_cache, files
)
self.assertTrue(result is self.env, "Expected the same reference")
# Verify the latest was set
self.assertEqual(
self.env.get("PROTONPATH"),
self.test_compat.joinpath(latest).as_posix(),
"Expected latest to be set",
)
# Verify that the old versions were deleted
self.assertFalse(
self.test_compat.joinpath("UMU-Proton-9.0-beta15").exists(),
"Expected old version to be removed",
)
self.assertFalse(
self.test_compat.joinpath("UMU-Proton-9.0-beta14").exists(),
"Expected old version to be removed",
)
self.assertFalse(
self.test_compat.joinpath("ULWGL-Proton-8.0-5-2").exists(),
"Expected old version to be removed",
)
# Verify foo files survived
self.assertTrue(
self.test_compat.joinpath("foo").exists(), "Expected foo to survive"
)
self.assertTrue(
self.test_compat.joinpath("GE-Proton9-2").exists(),
"Expected GE-Proton9-2 to survive",
)
self.assertTrue(
self.test_compat.joinpath("UMU-Latest").is_symlink(),
"Expected UMU-Latest symlink",
)

latest.rmdir()
Path(f"{latest}.sha512sum").unlink()

def test_steamcompat_nodir(self):
"""Test _get_from_steamcompat when Proton doesn't exist in compat dir.

Expand Down
2 changes: 1 addition & 1 deletion umu/umu_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def _update_umu(
if not local.joinpath("reaper").is_file():
log.warning("Reaper not found")
copy(root.joinpath("reaper"), local.joinpath("reaper"))
log.console(f"Restored {key} to {val} ...")
log.console(f"Restored {key} to {val}")

# Update
if val != reaper:
Expand Down