Make the app cross-platform and add a Windows build
- paths: per-platform detection behind an injectable Env. Windows finds Player.log in the user profile and the install via Steam (registry), the standalone installer, Epic, or the uninstall registry. App dirs come from platformdirs (unchanged XDG locations on Linux). - logtail: open Player.log only while reading, so Windows can still rotate it when MTGA restarts. - carddb: on Windows read a private copy of the card database so MTGA's updater can replace the original. - App icon (SVG source + generated ICO/PNG), Windows taskbar identity, and a Windows-safe timer glyph. - PyInstaller spec, launcher and PowerShell build script producing a one-folder windowed build and zip. - Tests for path detection, the log follower and the card-DB copy; README covers Windows usage, building and per-OS locations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
171fc64ca9
commit
e82f8d1858
19 files changed
+685
-54
No files matched your search
@@ -0,0 +1,135 @@
|
||||
"""Path detection for Linux (Steam/Proton) and Windows, against fake directory trees."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from mtga_assistant.carddb import local_copy, open_card_db
|
||||
from mtga_assistant.paths import MTGA_APP_ID, Config, Env, find_card_db, find_log_path, find_mtga_dir
|
||||
|
||||
|
||||
def make_install(d: Path) -> Path:
|
||||
raw = d / "MTGA_Data/Downloads/Raw"
|
||||
raw.mkdir(parents=True)
|
||||
(raw / "Raw_CardDatabase_abc.mtga").write_bytes(b"db")
|
||||
return d
|
||||
|
||||
|
||||
def make_log_dir(profile: Path) -> Path:
|
||||
d = profile / "AppData/LocalLow/Wizards Of The Coast/MTGA"
|
||||
d.mkdir(parents=True)
|
||||
return d
|
||||
|
||||
|
||||
def steam_library(lib: Path, vdf_root: Path, *, with_mtga: bool = True) -> None:
|
||||
(vdf_root / "steamapps").mkdir(parents=True, exist_ok=True)
|
||||
escaped = str(lib).replace("\\", "\\\\")
|
||||
(vdf_root / "steamapps/libraryfolders.vdf").write_text(
|
||||
f'"libraryfolders"\n{{\n "0"\n {{\n "path" "{escaped}"\n }}\n}}\n')
|
||||
if with_mtga:
|
||||
(lib / "steamapps").mkdir(parents=True, exist_ok=True)
|
||||
(lib / f"steamapps/appmanifest_{MTGA_APP_ID}.acf").write_text("")
|
||||
make_install(lib / "steamapps/common/MTGA")
|
||||
|
||||
|
||||
# -- Linux ---------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_linux_proton(tmp_path):
|
||||
home, lib = tmp_path / "home", tmp_path / "games"
|
||||
steam_library(lib, home / ".local/share/Steam")
|
||||
make_log_dir(lib / f"steamapps/compatdata/{MTGA_APP_ID}/pfx/drive_c/users/steamuser")
|
||||
env = Env(platform="linux", home=home)
|
||||
log = find_log_path(Config(), env)
|
||||
assert log is not None and log.name == "Player.log" and "compatdata" in log.parts
|
||||
assert find_mtga_dir(Config(), env) == lib / "steamapps/common/MTGA"
|
||||
|
||||
|
||||
def test_linux_nothing_installed(tmp_path):
|
||||
env = Env(platform="linux", home=tmp_path)
|
||||
assert find_log_path(Config(), env) is None
|
||||
assert find_mtga_dir(Config(), env) is None
|
||||
|
||||
|
||||
# -- Windows ---------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def windows_env(tmp_path, registry=None, uninstall=None) -> Env:
|
||||
home = tmp_path / "Users/me"
|
||||
home.mkdir(parents=True, exist_ok=True)
|
||||
return Env(
|
||||
platform="win32", home=home,
|
||||
environ={"USERPROFILE": str(home), "ProgramFiles": str(tmp_path / "Program Files"),
|
||||
"ProgramFiles(x86)": str(tmp_path / "Program Files (x86)")},
|
||||
read_registry=lambda hive, key, value: (registry or {}).get((hive, key, value)),
|
||||
uninstall_entries=lambda: uninstall or [],
|
||||
)
|
||||
|
||||
|
||||
def test_windows_log_is_in_user_profile(tmp_path):
|
||||
env = windows_env(tmp_path)
|
||||
assert find_log_path(Config(), env) is None # MTGA never ran
|
||||
d = make_log_dir(Path(env.environ["USERPROFILE"]))
|
||||
assert find_log_path(Config(), env) == d / "Player.log"
|
||||
|
||||
|
||||
def test_windows_steam_found_via_registry(tmp_path):
|
||||
steam, lib = tmp_path / "Steam", tmp_path / "D/SteamLibrary"
|
||||
steam_library(lib, steam)
|
||||
env = windows_env(tmp_path, registry={("HKCU", r"Software\Valve\Steam", "SteamPath"): str(steam)})
|
||||
assert find_mtga_dir(Config(), env) == lib / "steamapps/common/MTGA"
|
||||
|
||||
|
||||
def test_windows_steam_default_location(tmp_path):
|
||||
steam, lib = tmp_path / "Program Files (x86)/Steam", tmp_path / "E/Games"
|
||||
steam_library(lib, steam)
|
||||
assert find_mtga_dir(Config(), windows_env(tmp_path)) == lib / "steamapps/common/MTGA"
|
||||
|
||||
|
||||
def test_windows_standalone_and_epic(tmp_path):
|
||||
env = windows_env(tmp_path)
|
||||
epic = make_install(tmp_path / "Program Files/Epic Games/MagicTheGathering")
|
||||
assert find_mtga_dir(Config(), env) == epic
|
||||
standalone = make_install(tmp_path / "Program Files/Wizards of the Coast/MTGA")
|
||||
assert find_mtga_dir(Config(), env) == standalone # preferred over Epic
|
||||
|
||||
|
||||
def test_windows_uninstall_registry_fallback(tmp_path):
|
||||
custom = make_install(tmp_path / "Games/MTGA")
|
||||
env = windows_env(tmp_path, uninstall=[("Some Other App", str(tmp_path)),
|
||||
("Magic: The Gathering Arena", str(custom))])
|
||||
assert find_mtga_dir(Config(), env) == custom
|
||||
assert find_card_db(custom).name == "Raw_CardDatabase_abc.mtga"
|
||||
|
||||
|
||||
def test_config_overrides_detection(tmp_path):
|
||||
cfg = Config(log_path=tmp_path / "x.log", mtga_dir=tmp_path / "mtga")
|
||||
env = windows_env(tmp_path)
|
||||
assert find_log_path(cfg, env) == tmp_path / "x.log"
|
||||
assert find_mtga_dir(cfg, env) == tmp_path / "mtga"
|
||||
|
||||
|
||||
# -- card DB copy (Windows) -------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_card_db_copy_reused_and_old_versions_removed(tmp_path):
|
||||
src_dir, copies = tmp_path / "Raw", tmp_path / "cache/carddb"
|
||||
src_dir.mkdir()
|
||||
v1 = src_dir / "Raw_CardDatabase_v1.mtga"
|
||||
v1.write_bytes(b"one")
|
||||
c1 = local_copy(v1, copies)
|
||||
assert c1.read_bytes() == b"one"
|
||||
mtime = c1.stat().st_mtime_ns
|
||||
assert local_copy(v1, copies).stat().st_mtime_ns == mtime # not copied again
|
||||
|
||||
v2 = src_dir / "Raw_CardDatabase_v2.mtga"
|
||||
v2.write_bytes(b"two!")
|
||||
c2 = local_copy(v2, copies)
|
||||
assert c2.read_bytes() == b"two!" and not c1.exists()
|
||||
|
||||
|
||||
def test_open_card_db_copies_only_on_windows(tmp_path):
|
||||
import sqlite3
|
||||
db = tmp_path / "Raw_CardDatabase_x.mtga"
|
||||
sqlite3.connect(db).close()
|
||||
assert open_card_db(db, tmp_path / "copies", platform="linux").path == db
|
||||
win = open_card_db(db, tmp_path / "copies", platform="win32")
|
||||
assert win.path == tmp_path / "copies" / db.name
|
||||
Reference in new issue
Block a user