mirror of
https://github.com/tcsenpai/UWINE.git
synced 2025-06-06 11:35:20 +00:00
Merge pull request #30 from R1kaB3rN/download
Add download functionality to ulwgl_run
This commit is contained in:
commit
b034600a9d
258
ulwgl_dl_util.py
Normal file
258
ulwgl_dl_util.py
Normal file
@ -0,0 +1,258 @@
|
||||
from pathlib import Path
|
||||
from os import environ
|
||||
from tarfile import open as tar_open
|
||||
from typing import Dict, List, Tuple, Any, Union
|
||||
from hashlib import sha512
|
||||
from shutil import rmtree
|
||||
from http.client import HTTPSConnection, HTTPResponse, HTTPException, HTTPConnection
|
||||
from ssl import create_default_context
|
||||
from json import loads as loads_json
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
|
||||
def get_ulwgl_proton(env: Dict[str, str]) -> Union[Dict[str, str]]:
|
||||
"""Attempt to find existing Proton from the system or downloads the latest if PROTONPATH is not set.
|
||||
|
||||
Only fetches the latest if not first found in .local/share/Steam/compatibilitytools.d
|
||||
.cache/ULWGL is referenced for the latest then as fallback
|
||||
"""
|
||||
files: List[Tuple[str, str]] = []
|
||||
|
||||
try:
|
||||
files = _fetch_releases()
|
||||
except HTTPException:
|
||||
print("Offline.\nContinuing ...")
|
||||
|
||||
cache: Path = Path.home().joinpath(".cache/ULWGL")
|
||||
steam_compat: Path = Path.home().joinpath(".local/share/Steam/compatibilitytools.d")
|
||||
|
||||
cache.mkdir(exist_ok=True, parents=True)
|
||||
steam_compat.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
# Prioritize the Steam compat
|
||||
if _get_from_steamcompat(env, steam_compat, cache, files):
|
||||
return env
|
||||
|
||||
# Use the latest Proton in the cache if it exists
|
||||
if _get_from_cache(env, steam_compat, cache, files, True):
|
||||
return env
|
||||
|
||||
# Download the latest if Proton is not in Steam compat
|
||||
# If the digests mismatched, refer to the cache in the next block
|
||||
if _get_latest(env, steam_compat, cache, files):
|
||||
return env
|
||||
|
||||
# Refer to an old version previously downloaded
|
||||
# Reached on digest mismatch, user interrupt or download failure/no internet
|
||||
if _get_from_cache(env, steam_compat, cache, files, False):
|
||||
return env
|
||||
|
||||
# No internet and cache/compat tool is empty, just return and raise an exception from the caller
|
||||
return env
|
||||
|
||||
|
||||
def _fetch_releases() -> List[Tuple[str, str]]:
|
||||
"""Fetch the latest releases from the Github API."""
|
||||
files: List[Tuple[str, str]] = []
|
||||
resp: HTTPResponse = None
|
||||
conn: HTTPConnection = HTTPSConnection(
|
||||
"api.github.com", timeout=30, context=create_default_context()
|
||||
)
|
||||
|
||||
conn.request(
|
||||
"GET",
|
||||
"/repos/Open-Wine-Components/ULWGL-Proton/releases",
|
||||
headers={
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"User-Agent": "",
|
||||
},
|
||||
)
|
||||
|
||||
resp = conn.getresponse()
|
||||
|
||||
if resp and resp.status != 200:
|
||||
return files
|
||||
|
||||
# Attempt to acquire the tarball and checksum from the JSON data
|
||||
releases: List[Dict[str, Any]] = loads_json(resp.read().decode("utf-8"))
|
||||
for release in releases:
|
||||
if "assets" in release:
|
||||
assets: List[Dict[str, Any]] = release["assets"]
|
||||
|
||||
for asset in assets:
|
||||
if (
|
||||
"name" in asset
|
||||
and (
|
||||
asset["name"].endswith("sum")
|
||||
or (
|
||||
asset["name"].endswith("tar.gz")
|
||||
and asset["name"].startswith("ULWGL-Proton")
|
||||
)
|
||||
)
|
||||
and "browser_download_url" in asset
|
||||
):
|
||||
if asset["name"].endswith("sum"):
|
||||
files.append((asset["name"], asset["browser_download_url"]))
|
||||
else:
|
||||
files.append((asset["name"], asset["browser_download_url"]))
|
||||
|
||||
if len(files) == 2:
|
||||
break
|
||||
break
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def _fetch_proton(
|
||||
env: Dict[str, str], steam_compat: Path, cache: Path, files: List[Tuple[str, str]]
|
||||
) -> Dict[str, str]:
|
||||
"""Download the latest ULWGL-Proton and set it as PROTONPATH."""
|
||||
hash, hash_url = files[0]
|
||||
proton, proton_url = files[1]
|
||||
proton_dir: str = proton[: proton.find(".tar.gz")] # Proton dir
|
||||
|
||||
# TODO: Parallelize this
|
||||
print(f"Downloading {hash} ...")
|
||||
urlretrieve(hash_url, cache.joinpath(hash).as_posix())
|
||||
print(f"Downloading {proton} ...")
|
||||
urlretrieve(proton_url, cache.joinpath(proton).as_posix())
|
||||
|
||||
print("Completed.")
|
||||
|
||||
with cache.joinpath(proton).open(mode="rb") as file:
|
||||
if (
|
||||
sha512(file.read()).hexdigest()
|
||||
!= cache.joinpath(hash).read_text().split(" ")[0]
|
||||
):
|
||||
err: str = "Digests mismatched.\nFalling back to cache ..."
|
||||
raise ValueError(err)
|
||||
print(f"{proton}: SHA512 is OK")
|
||||
|
||||
_extract_dir(cache.joinpath(proton), steam_compat)
|
||||
environ["PROTONPATH"] = steam_compat.joinpath(proton_dir).as_posix()
|
||||
env["PROTONPATH"] = environ["PROTONPATH"]
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def _extract_dir(proton: Path, steam_compat: Path) -> None:
|
||||
"""Extract from the cache to another location."""
|
||||
with tar_open(proton.as_posix(), "r:gz") as tar:
|
||||
print(f"Extracting {proton} -> {steam_compat.as_posix()} ...")
|
||||
tar.extractall(path=steam_compat.as_posix())
|
||||
print("Completed.")
|
||||
|
||||
|
||||
def _cleanup(tarball: str, proton: str, cache: Path, steam_compat: Path) -> None:
|
||||
"""Remove files that may have been left in an incomplete state to avoid corruption.
|
||||
|
||||
We want to do this when a download for a new release is interrupted
|
||||
"""
|
||||
print("Keyboard Interrupt.\nCleaning ...")
|
||||
|
||||
if cache.joinpath(tarball).is_file():
|
||||
print(f"Purging {tarball} in {cache} ...")
|
||||
cache.joinpath(tarball).unlink()
|
||||
if steam_compat.joinpath(proton).is_dir():
|
||||
print(f"Purging {proton} in {steam_compat} ...")
|
||||
rmtree(steam_compat.joinpath(proton).as_posix())
|
||||
|
||||
|
||||
def _get_from_steamcompat(
|
||||
env: Dict[str, str], steam_compat: Path, cache: Path, files: List[Tuple[str, str]]
|
||||
) -> Union[Dict[str, str], None]:
|
||||
"""Refer to Steam compat folder for any existing Proton directories."""
|
||||
proton_dir: str = "" # Latest Proton
|
||||
|
||||
if len(files) == 2:
|
||||
proton_dir: str = files[1][0][: files[1][0].find(".tar.gz")]
|
||||
|
||||
for proton in steam_compat.glob("ULWGL-Proton*"):
|
||||
print(f"{proton.name} found in: {steam_compat.as_posix()}")
|
||||
environ["PROTONPATH"] = proton.as_posix()
|
||||
env["PROTONPATH"] = environ["PROTONPATH"]
|
||||
|
||||
# Notify the user that they're not using the latest
|
||||
if proton_dir and proton.name != proton_dir:
|
||||
print(
|
||||
"ULWGL-Proton is outdated.\nFor latest release, please download "
|
||||
+ files[1][1]
|
||||
)
|
||||
|
||||
return env
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_from_cache(
|
||||
env: Dict[str, str],
|
||||
steam_compat: Path,
|
||||
cache: Path,
|
||||
files: List[Tuple[str, str]],
|
||||
use_latest=True,
|
||||
) -> Union[Dict[str, str], None]:
|
||||
"""Refer to ULWGL cache directory.
|
||||
|
||||
Use the latest in the cache when present. When download fails, use an old version
|
||||
Older Proton versions are only referred to when: digests mismatch, user interrupt, or download failure/no internet
|
||||
"""
|
||||
path: Path = None
|
||||
name: str = ""
|
||||
|
||||
for tarball in cache.glob("ULWGL-Proton*.tar.gz"):
|
||||
if files and tarball == cache.joinpath(files[1][0]) and use_latest:
|
||||
path = tarball
|
||||
name = tarball.name
|
||||
break
|
||||
if tarball != cache.joinpath(files[1][0]) and not use_latest:
|
||||
path = tarball
|
||||
name = tarball.name
|
||||
break
|
||||
|
||||
if path:
|
||||
proton_dir: str = name[: name.find(".tar.gz")] # Proton dir
|
||||
|
||||
print(f"{name} found in: {path}")
|
||||
try:
|
||||
_extract_dir(path, steam_compat)
|
||||
environ["PROTONPATH"] = steam_compat.joinpath(proton_dir).as_posix()
|
||||
env["PROTONPATH"] = environ["PROTONPATH"]
|
||||
|
||||
return env
|
||||
except KeyboardInterrupt:
|
||||
if steam_compat.joinpath(proton_dir).is_dir():
|
||||
print(f"Purging {proton_dir} in {steam_compat} ...")
|
||||
rmtree(steam_compat.joinpath(proton_dir).as_posix())
|
||||
raise
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_latest(
|
||||
env: Dict[str, str], steam_compat: Path, cache: Path, files: List[Tuple[str, str]]
|
||||
) -> Union[Dict[str, str], None]:
|
||||
"""Download the latest Proton for new installs -- empty cache and Steam compat.
|
||||
|
||||
When the digests mismatched or when interrupted, refer to cache for an old version
|
||||
"""
|
||||
if files:
|
||||
print("Fetching latest release ...")
|
||||
try:
|
||||
_fetch_proton(env, steam_compat, cache, files)
|
||||
env["PROTONPATH"] = environ["PROTONPATH"]
|
||||
except ValueError:
|
||||
# Digest mismatched or download failed
|
||||
# Refer to the cache for old version next
|
||||
return None
|
||||
except KeyboardInterrupt:
|
||||
tarball: str = files[1][0]
|
||||
proton_dir: str = tarball[: tarball.find(".tar.gz")] # Proton dir
|
||||
|
||||
# Exit cleanly
|
||||
# Clean up extracted data and cache to prevent corruption/errors
|
||||
# Refer to the cache for old version next
|
||||
_cleanup(tarball, proton_dir, cache, steam_compat)
|
||||
return None
|
||||
|
||||
return env
|
14
ulwgl_run.py
14
ulwgl_run.py
@ -10,6 +10,7 @@ from typing import Dict, Any, List, Set, Union, Tuple
|
||||
import ulwgl_plugins
|
||||
from re import match
|
||||
import subprocess
|
||||
from ulwgl_dl_util import get_ulwgl_proton
|
||||
|
||||
|
||||
def parse_args() -> Union[Namespace, Tuple[str, List[str]]]: # noqa: D103
|
||||
@ -118,9 +119,16 @@ def check_env(
|
||||
"PROTONPATH" not in os.environ
|
||||
or not Path(os.environ["PROTONPATH"]).expanduser().is_dir()
|
||||
):
|
||||
err: str = "Environment variable not set or not a directory: PROTONPATH"
|
||||
raise ValueError(err)
|
||||
env["PROTONPATH"] = os.environ["PROTONPATH"]
|
||||
# Attempt to auto set this env var for the user
|
||||
os.environ["PROTONPATH"] = ""
|
||||
get_ulwgl_proton(env)
|
||||
else:
|
||||
env["PROTONPATH"] = os.environ["PROTONPATH"]
|
||||
|
||||
# If download fails/doesn't exist in the system, raise an error
|
||||
if not os.environ["PROTONPATH"]:
|
||||
err: str = "Download failed.\nProton could not be found in cache or compatibilitytools.d\nPlease set $PROTONPATH or visit https://github.com/Open-Wine-Components/ULWGL-Proton/releases"
|
||||
raise FileNotFoundError(err)
|
||||
|
||||
return env
|
||||
|
||||
|
434
ulwgl_test.py
434
ulwgl_test.py
@ -9,6 +9,8 @@ from tomllib import TOMLDecodeError
|
||||
from shutil import rmtree
|
||||
import re
|
||||
import ulwgl_plugins
|
||||
import ulwgl_dl_util
|
||||
import tarfile
|
||||
|
||||
|
||||
class TestGameLauncher(unittest.TestCase):
|
||||
@ -49,6 +51,34 @@ class TestGameLauncher(unittest.TestCase):
|
||||
self.test_file = "./tmp.WMYQiPb9A"
|
||||
# Executable
|
||||
self.test_exe = self.test_file + "/" + "foo"
|
||||
# Cache
|
||||
self.test_cache = Path("./tmp.5HYdpddgvs")
|
||||
# Steam compat dir
|
||||
self.test_compat = Path("./tmp.ZssGZoiNod")
|
||||
# ULWGL-Proton dir
|
||||
self.test_proton_dir = Path("ULWGL-Proton-5HYdpddgvs")
|
||||
# ULWGL-Proton release
|
||||
self.test_archive = Path(self.test_cache).joinpath(
|
||||
f"{self.test_proton_dir}.tar.gz"
|
||||
)
|
||||
|
||||
self.test_cache.mkdir(exist_ok=True)
|
||||
self.test_compat.mkdir(exist_ok=True)
|
||||
self.test_proton_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Mock the proton file in the dir
|
||||
self.test_proton_dir.joinpath("proton").touch(exist_ok=True)
|
||||
|
||||
# Mock the release downloaded in the cache: tmp.5HYdpddgvs/ULWGL-Proton-5HYdpddgvs.tar.gz
|
||||
# Expected directory structure within the archive:
|
||||
#
|
||||
# +-- ULWGL-Proton-5HYdpddgvs (root directory)
|
||||
# | +-- proton (normal file)
|
||||
with tarfile.open(self.test_archive.as_posix(), "w:gz") as tar:
|
||||
tar.add(
|
||||
self.test_proton_dir.as_posix(), arcname=self.test_proton_dir.as_posix()
|
||||
)
|
||||
|
||||
Path(self.test_file).mkdir(exist_ok=True)
|
||||
Path(self.test_exe).touch()
|
||||
|
||||
@ -61,6 +91,365 @@ class TestGameLauncher(unittest.TestCase):
|
||||
if Path(self.test_file).exists():
|
||||
rmtree(self.test_file)
|
||||
|
||||
if self.test_cache.exists():
|
||||
rmtree(self.test_cache.as_posix())
|
||||
|
||||
if self.test_compat.exists():
|
||||
rmtree(self.test_compat.as_posix())
|
||||
|
||||
if self.test_proton_dir.exists():
|
||||
rmtree(self.test_proton_dir.as_posix())
|
||||
|
||||
def test_latest_interrupt(self):
|
||||
"""Test _get_latest in the event the user interrupts the download/extraction process.
|
||||
|
||||
Assumes a file is being downloaded or extracted in this case.
|
||||
A KeyboardInterrupt should be raised, and the cache/compat dir should be cleaned afterwards.
|
||||
"""
|
||||
result = None
|
||||
# In the real usage, should be populated after successful callout for latest Proton releases
|
||||
# In this case, assume the test variable will be downloaded
|
||||
files = [("", ""), (self.test_archive.name, "")]
|
||||
|
||||
# In the event of an interrupt, both the cache/compat dir will be checked for the latest release for removal
|
||||
# We do this since the extraction process can be interrupted as well
|
||||
ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat)
|
||||
|
||||
with patch("ulwgl_dl_util._fetch_proton") as mock_function:
|
||||
# Mock the interrupt
|
||||
# We want the dir we tried to extract to be cleaned
|
||||
mock_function.side_effect = KeyboardInterrupt
|
||||
result = ulwgl_dl_util._get_latest(
|
||||
self.env, self.test_compat, self.test_cache, files
|
||||
)
|
||||
self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to be empty")
|
||||
self.assertFalse(result, "Expected None when a ValueError occurs")
|
||||
|
||||
# Verify the state of the compat dir/cache
|
||||
self.assertFalse(
|
||||
self.test_compat.joinpath(
|
||||
self.test_archive.name[: self.test_archive.name.find(".tar.gz")]
|
||||
).exists(),
|
||||
"Expected Proton dir in compat to be cleaned",
|
||||
)
|
||||
self.assertFalse(
|
||||
self.test_cache.joinpath(self.test_archive.name).exists(),
|
||||
"Expected Proton dir in compat to be cleaned",
|
||||
)
|
||||
|
||||
def test_latest_val_err(self):
|
||||
"""Test _get_latest in the event something goes wrong in the download process for the latest Proton.
|
||||
|
||||
Assumes a file is being downloaded in this case.
|
||||
A ValueError should be raised, and one case it can happen is if the digests mismatched for some reason
|
||||
"""
|
||||
result = None
|
||||
# In the real usage, should be populated after successful callout for latest Proton releases
|
||||
# When empty, it means the callout failed for some reason (e.g. no internet)
|
||||
files = [("", ""), (self.test_archive.name, "")]
|
||||
|
||||
with patch("ulwgl_dl_util._fetch_proton") as mock_function:
|
||||
# Mock the interrupt
|
||||
mock_function.side_effect = ValueError
|
||||
result = ulwgl_dl_util._get_latest(
|
||||
self.env, self.test_compat, self.test_cache, files
|
||||
)
|
||||
self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to be empty")
|
||||
self.assertFalse(result, "Expected None when a ValueError occurs")
|
||||
|
||||
def test_latest_offline(self):
|
||||
"""Test _get_latest when the user doesn't have internet."""
|
||||
result = None
|
||||
# In the real usage, should be populated after successful callout for latest Proton releases
|
||||
# When empty, it means the callout failed for some reason (e.g. no internet)
|
||||
files = []
|
||||
|
||||
os.environ["PROTONPATH"] = ""
|
||||
|
||||
with patch("ulwgl_dl_util._fetch_proton"):
|
||||
result = ulwgl_dl_util._get_latest(
|
||||
self.env, self.test_compat, self.test_cache, files
|
||||
)
|
||||
self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to be empty")
|
||||
self.assertTrue(result is self.env, "Expected the same reference")
|
||||
|
||||
def test_cache_interrupt(self):
|
||||
"""Test _get_from_cache on keyboard interrupt on extraction from the cache to the compat dir."""
|
||||
# In the real usage, should be populated after successful callout for latest Proton releases
|
||||
# Just mock it and assumes its the latest
|
||||
files = [("", ""), (self.test_archive.name, "")]
|
||||
|
||||
ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat)
|
||||
|
||||
self.assertTrue(
|
||||
self.test_compat.joinpath(
|
||||
self.test_archive.name[: self.test_archive.name.find(".tar.gz")]
|
||||
).exists(),
|
||||
"Expected Proton dir to exist in compat",
|
||||
)
|
||||
|
||||
with patch("ulwgl_dl_util._extract_dir") as mock_function:
|
||||
with self.assertRaisesRegex(KeyboardInterrupt, ""):
|
||||
# Mock the interrupt
|
||||
# We want to simulate an interrupt mid-extraction in this case
|
||||
# We want the dir we tried to extract to be cleaned
|
||||
mock_function.side_effect = KeyboardInterrupt
|
||||
ulwgl_dl_util._get_from_cache(
|
||||
self.env, self.test_compat, self.test_cache, files, True
|
||||
)
|
||||
|
||||
# After interrupt, we attempt to clean the compat dir for the file we tried to extract because it could be in an incomplete state
|
||||
# Verify that the dir we tried to extract from cache is removed to avoid corruption on next launch
|
||||
self.assertFalse(
|
||||
self.test_compat.joinpath(
|
||||
self.test_archive.name[: self.test_archive.name.find(".tar.gz")]
|
||||
).exists(),
|
||||
"Expected Proton dir in compat to be cleaned",
|
||||
)
|
||||
|
||||
def test_cache_old(self):
|
||||
"""Test _get_from_cache when the cache is empty.
|
||||
|
||||
In real usage, this only happens as a last resort when: download fails, digests mismatched, etc.
|
||||
"""
|
||||
result = None
|
||||
# In the real usage, should be populated after successful callout for latest Proton releases
|
||||
# Just mock it and assumes its the latest
|
||||
files = [("", ""), (self.test_archive.name, "")]
|
||||
|
||||
# Mock old Proton versions in the cache
|
||||
test_proton_dir = Path("ULWGL-Proton-foo")
|
||||
test_proton_dir.mkdir(exist_ok=True)
|
||||
test_archive = Path(self.test_cache).joinpath(
|
||||
f"{test_proton_dir.as_posix()}.tar.gz"
|
||||
)
|
||||
|
||||
with tarfile.open(test_archive.as_posix(), "w:gz") as tar:
|
||||
tar.add(test_proton_dir.as_posix(), arcname=test_proton_dir.as_posix())
|
||||
|
||||
result = ulwgl_dl_util._get_from_cache(
|
||||
self.env, self.test_compat, self.test_cache, files, False
|
||||
)
|
||||
|
||||
# Verify that the old Proton was assigned
|
||||
self.assertTrue(result is self.env, "Expected the same reference")
|
||||
self.assertEqual(
|
||||
self.env["PROTONPATH"],
|
||||
self.test_compat.joinpath(
|
||||
test_archive.name[: test_archive.name.find(".tar.gz")]
|
||||
).as_posix(),
|
||||
"Expected PROTONPATH to be proton dir in compat",
|
||||
)
|
||||
|
||||
test_archive.unlink()
|
||||
test_proton_dir.rmdir()
|
||||
|
||||
def test_cache_empty(self):
|
||||
"""Test _get_from_cache when the cache is empty."""
|
||||
result = None
|
||||
# In the real usage, should be populated after successful callout for latest Proton releases
|
||||
# Just mock it and assumes its the latest
|
||||
files = [("", ""), (self.test_archive.name, "")]
|
||||
|
||||
self.test_archive.unlink()
|
||||
|
||||
result = ulwgl_dl_util._get_from_cache(
|
||||
self.env, self.test_compat, self.test_cache, files, True
|
||||
)
|
||||
self.assertFalse(result, "Expected None when calling _get_from_cache")
|
||||
self.assertFalse(
|
||||
self.env["PROTONPATH"],
|
||||
"Expected PROTONPATH to be empty when the cache is empty",
|
||||
)
|
||||
|
||||
def test_cache(self):
|
||||
"""Test _get_from_cache.
|
||||
|
||||
Tests the case when the latest Proton already exists in the cache
|
||||
"""
|
||||
result = None
|
||||
# In the real usage, should be populated after successful callout for latest Proton releases
|
||||
# Just mock it and assumes its the latest
|
||||
files = [("", ""), (self.test_archive.name, "")]
|
||||
|
||||
result = ulwgl_dl_util._get_from_cache(
|
||||
self.env, self.test_compat, self.test_cache, files, True
|
||||
)
|
||||
self.assertTrue(result is self.env, "Expected the same reference")
|
||||
self.assertEqual(
|
||||
self.env["PROTONPATH"],
|
||||
self.test_compat.joinpath(
|
||||
self.test_archive.name[: self.test_archive.name.find(".tar.gz")]
|
||||
).as_posix(),
|
||||
"Expected PROTONPATH to be proton dir in compat",
|
||||
)
|
||||
|
||||
def test_steamcompat_nodir(self):
|
||||
"""Test _get_from_steamcompat when a Proton doesn't exist in the Steam compat dir.
|
||||
|
||||
In this case, the None should be returned to signal that we should continue with downloading the latest Proton
|
||||
"""
|
||||
result = None
|
||||
files = [("", ""), (self.test_archive.name, "")]
|
||||
|
||||
result = ulwgl_dl_util._get_from_steamcompat(
|
||||
self.env, self.test_compat, self.test_cache, files
|
||||
)
|
||||
|
||||
self.assertFalse(result, "Expected None after calling _get_from_steamcompat")
|
||||
self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to not be set")
|
||||
|
||||
def test_steamcompat(self):
|
||||
"""Test _get_from_steamcompat.
|
||||
|
||||
When a Proton exist in .local/share/Steam/compatibilitytools.d, use it when PROTONPATH is unset
|
||||
"""
|
||||
result = None
|
||||
files = [("", ""), (self.test_archive.name, "")]
|
||||
|
||||
ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat)
|
||||
|
||||
result = ulwgl_dl_util._get_from_steamcompat(
|
||||
self.env, self.test_compat, self.test_cache, files
|
||||
)
|
||||
|
||||
self.assertTrue(result is self.env, "Expected the same reference")
|
||||
self.assertEqual(
|
||||
self.env["PROTONPATH"],
|
||||
self.test_compat.joinpath(
|
||||
self.test_archive.name[: self.test_archive.name.find(".tar.gz")]
|
||||
).as_posix(),
|
||||
"Expected PROTONPATH to be proton dir in compat",
|
||||
)
|
||||
|
||||
def test_cleanup_no_exists(self):
|
||||
"""Test _cleanup when passed files that do not exist.
|
||||
|
||||
In the event of an interrupt during the download/extract process, we only want to clean the files that exist
|
||||
NOTE: This is **extremely** important, as we do **not** want to delete anything else but the files we downloaded/extracted -- the incomplete tarball/extracted dir
|
||||
"""
|
||||
result = None
|
||||
|
||||
ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat)
|
||||
|
||||
# Create a file in the cache and compat
|
||||
self.test_cache.joinpath("foo").touch()
|
||||
self.test_compat.joinpath("foo").touch()
|
||||
|
||||
# Before cleaning
|
||||
# On setUp, an archive is created and a dir should exist in compat after extraction
|
||||
self.assertTrue(
|
||||
self.test_compat.joinpath("foo").exists(),
|
||||
"Expected test file to exist in compat before cleaning",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.test_cache.joinpath("foo").exists(),
|
||||
"Expected test file to exist in cache before cleaning",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.test_archive.exists(),
|
||||
"Expected archive to exist in cache before cleaning",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.test_compat.joinpath(self.test_proton_dir).joinpath("proton").exists(),
|
||||
"Expected 'proton' to exist before cleaning",
|
||||
)
|
||||
|
||||
# Pass files that do not exist
|
||||
result = ulwgl_dl_util._cleanup(
|
||||
"foo.tar.gz",
|
||||
"foo",
|
||||
self.test_cache,
|
||||
self.test_compat,
|
||||
)
|
||||
|
||||
# Verify state of cache and compat after cleaning
|
||||
self.assertFalse(result, "Expected None after cleaning")
|
||||
self.assertTrue(
|
||||
self.test_compat.joinpath("foo").exists(),
|
||||
"Expected test file to exist in compat after cleaning",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.test_cache.joinpath("foo").exists(),
|
||||
"Expected test file to exist in cache after cleaning",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.test_compat.joinpath(self.test_proton_dir).exists(),
|
||||
"Expected proton dir to still exist after cleaning",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.test_archive.exists(),
|
||||
"Expected archive to still exist after cleaning",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.test_compat.joinpath(self.test_proton_dir).joinpath("proton").exists(),
|
||||
"Expected 'proton' to still exist after cleaning",
|
||||
)
|
||||
|
||||
def test_cleanup(self):
|
||||
"""Test _cleanup.
|
||||
|
||||
In the event of an interrupt during the download/extract process, we want to clean the cache or the extracted dir in Steam compat to avoid incomplete files
|
||||
"""
|
||||
result = None
|
||||
|
||||
ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat)
|
||||
result = ulwgl_dl_util._cleanup(
|
||||
self.test_proton_dir.as_posix() + ".tar.gz",
|
||||
self.test_proton_dir.as_posix(),
|
||||
self.test_cache,
|
||||
self.test_compat,
|
||||
)
|
||||
self.assertFalse(result, "Expected None after cleaning")
|
||||
self.assertFalse(
|
||||
self.test_compat.joinpath(self.test_proton_dir).exists(),
|
||||
"Expected proton dir to be cleaned in compat",
|
||||
)
|
||||
self.assertFalse(
|
||||
self.test_archive.exists(),
|
||||
"Expected archive to be cleaned in cache",
|
||||
)
|
||||
self.assertFalse(
|
||||
self.test_compat.joinpath(self.test_proton_dir).joinpath("proton").exists(),
|
||||
"Expected 'proton' to not exist after cleaned",
|
||||
)
|
||||
|
||||
def test_extract_err(self):
|
||||
"""Test _extract_dir when passed a non-gzip compressed archive.
|
||||
|
||||
An error should be raised as we only expect .tar.gz releases
|
||||
"""
|
||||
test_archive = self.test_cache.joinpath(f"{self.test_proton_dir}.tar")
|
||||
# Do not apply compression
|
||||
with tarfile.open(test_archive.as_posix(), "w") as tar:
|
||||
tar.add(
|
||||
self.test_proton_dir.as_posix(), arcname=self.test_proton_dir.as_posix()
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(tarfile.ReadError, "gzip"):
|
||||
ulwgl_dl_util._extract_dir(test_archive, self.test_compat)
|
||||
|
||||
if test_archive.exists():
|
||||
test_archive.unlink()
|
||||
|
||||
def test_extract(self):
|
||||
"""Test _extract_dir.
|
||||
|
||||
An error should not be raised when the Proton release is extracted to the Steam compat dir
|
||||
"""
|
||||
result = None
|
||||
|
||||
result = ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat)
|
||||
self.assertFalse(result, "Expected None after extracting")
|
||||
self.assertTrue(
|
||||
self.test_compat.joinpath(self.test_proton_dir).exists(),
|
||||
"Expected proton dir to exists in compat",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.test_compat.joinpath(self.test_proton_dir).joinpath("proton").exists(),
|
||||
"Expected 'proton' file to exists in the proton dir",
|
||||
)
|
||||
|
||||
def test_game_drive_empty(self):
|
||||
"""Test enable_steam_game_drive.
|
||||
|
||||
@ -1053,20 +1442,27 @@ class TestGameLauncher(unittest.TestCase):
|
||||
result, Namespace, "Expected a Namespace from parse_arg"
|
||||
)
|
||||
|
||||
def test_env_proton_dir(self):
|
||||
"""Test check_env when $PROTONPATH is not a directory.
|
||||
def test_env_proton_nodir(self):
|
||||
"""Test check_env when $PROTONPATH is not set on failing to setting it.
|
||||
|
||||
An ValueError should occur if the value is not a directory
|
||||
An FileNotFoundError should be raised when we fail to set PROTONPATH
|
||||
"""
|
||||
with self.assertRaisesRegex(ValueError, "PROTONPATH"):
|
||||
os.environ["WINEPREFIX"] = self.test_file
|
||||
os.environ["GAMEID"] = self.test_file
|
||||
os.environ["PROTONPATH"] = "./foo"
|
||||
ulwgl_run.check_env(self.env)
|
||||
self.assertFalse(
|
||||
Path(os.environ["PROTONPATH"]).is_dir(),
|
||||
"Expected PROTONPATH to not be a directory",
|
||||
)
|
||||
result = None
|
||||
|
||||
# Mock getting the Proton
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
with patch.object(
|
||||
ulwgl_run,
|
||||
"get_ulwgl_proton",
|
||||
return_value=self.env,
|
||||
):
|
||||
os.environ["WINEPREFIX"] = self.test_file
|
||||
os.environ["GAMEID"] = self.test_file
|
||||
result = ulwgl_run.check_env(self.env)
|
||||
# Mock setting it on success
|
||||
os.environ["PROTONPATH"] = self.test_file
|
||||
self.assertTrue(result is self.env, "Expected the same reference")
|
||||
self.assertFalse(os.environ["PROTONPATH"])
|
||||
|
||||
def test_env_wine_dir(self):
|
||||
"""Test check_env when $WINEPREFIX is not a directory.
|
||||
@ -1135,10 +1531,20 @@ class TestGameLauncher(unittest.TestCase):
|
||||
|
||||
def test_env_vars_proton(self):
|
||||
"""Test check_env when setting only $WINEPREFIX and $GAMEID."""
|
||||
with self.assertRaisesRegex(ValueError, "PROTONPATH"):
|
||||
with self.assertRaisesRegex(FileNotFoundError, "Proton"):
|
||||
os.environ["WINEPREFIX"] = self.test_file
|
||||
os.environ["GAMEID"] = self.test_file
|
||||
ulwgl_run.check_env(self.env)
|
||||
# Mock getting the Proton
|
||||
with patch.object(
|
||||
ulwgl_run,
|
||||
"get_ulwgl_proton",
|
||||
return_value=self.env,
|
||||
):
|
||||
os.environ["WINEPREFIX"] = self.test_file
|
||||
os.environ["GAMEID"] = self.test_file
|
||||
result = ulwgl_run.check_env(self.env)
|
||||
self.assertTrue(result is self.env, "Expected the same reference")
|
||||
self.assertFalse(os.environ["PROTONPATH"])
|
||||
|
||||
def test_env_vars_wine(self):
|
||||
"""Test check_env when setting only $WINEPREFIX."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user