Merge pull request #415 from aclist/feat/wizard-mixins

feat: wizard mixins
This commit is contained in:
aclist 2026-07-27 20:06:06 +09:00 committed by GitHub
commit cef9e1984f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 69 additions and 30 deletions

View File

@ -21,7 +21,7 @@ def write_desktop_file(exe_path: Path) -> Path:
Terminal=false
Exec={exe_path}
Name=DZGUI
Comment=dzgui
Comment=DayZ server browser and mod manager
Icon={icon}
Categories=Game"""

View File

@ -4,8 +4,9 @@ from typing import Literal
api_filter = r"(.*&key=)([^&]*)(.*)"
home_filter = r"(/home/)([^\s'\/]*)(.*)"
user_filter = r"(.*Steam/userdata/)([^/]*)(.*)"
REDACTED = r"\1REDACTED\3"
REDACTION_PATTERNS = [api_filter, home_filter]
REDACTION_PATTERNS = [api_filter, home_filter, user_filter]
def redact_home(text: str) -> str:

View File

@ -162,7 +162,8 @@ class APIEntry(Gtk.Box):
self.entry.connect("icon-release", self._on_icon_release)
self.entry.connect("activate", self._on_field_activated)
self.submit = Gtk.Button(label="Submit")
# TODO: strings
self.submit = Gtk.Button(label="Validate")
self.submit.set_sensitive(False)
self.submit.connect("clicked", self._on_submit)

View File

@ -50,6 +50,13 @@ class PageNum(Enum):
FINAL = 8
class OptionalPageMixin:
"""Marks optional pages as advanceable"""
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
class DescriptionArea(Gtk.Box):
def __init__(self, text: str):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
@ -91,6 +98,7 @@ class ScrolledWizardPage(Gtk.ScrolledWindow):
margin_top=50,
spacing=20,
)
self.add(self.box)
self.prog = Progress()
self.box.pack_end(self.prog, expand=False, fill=False, padding=0)
@ -215,7 +223,7 @@ class APIValidationPage(ScrolledWizardPage):
self.spinner.stop()
class BMValidationPage(APIValidationPage):
class BMValidationPage(OptionalPageMixin, APIValidationPage): # type: ignore
def __init__(self) -> None:
super().__init__(
enum=PageNum.BM_API,
@ -224,6 +232,7 @@ class BMValidationPage(APIValidationPage):
link=BM_API_SETUP,
func=self._validate,
)
self.connect("map", self._on_map)
@call_on_thread("", show_dialog=False)
def _validate(self, key: str) -> None:
@ -445,6 +454,7 @@ class Assistant(Gtk.Assistant):
else:
self.set_default_size(1500, 900)
self.is_binary = False if os.getenv("PYAPP") is None else True
self.config_path = XDG.config
self.config_values: dict[str, Any] = config_boilerplate
@ -485,7 +495,7 @@ class Assistant(Gtk.Assistant):
):
continue
# NOTE: disabled for now on system-provided packages
if isinstance(page, ShortcutCreationPage) and os.getenv("PYAPP") is None:
if isinstance(page, ShortcutCreationPage) and not self.is_binary:
continue
self._add_page(page, page.get_page_type())
@ -500,32 +510,33 @@ class Assistant(Gtk.Assistant):
def _advance_page(self, index: int) -> int:
page = self.get_nth_page(index)
# TODO: use enums/isinstance
match page:
case self.page1:
case IntroductionPage():
pass
case self.page2:
if self.page2.is_migrated():
case ConfigMigrationPage():
if page.is_migrated():
steam_path = lookup(self.config_path, Preferences.DEFAULT)
self.page7.set_steam_path(steam_path)
return self.get_n_pages() - 2
case self.page3:
offset = 1 if not self.is_binary else 2
self.setup_complete = True
return self.get_n_pages() - offset
case SteamPathPage():
self.config_values["default_steam_path"] = page.get_path_from_radio()
case self.page4:
case SteamValidationPage():
self.config_values["steam_api"] = page.get_api_key()
case self.page5:
case BMValidationPage():
self.config_values["bm_api"] = page.get_api_key()
# NOTE: collects config values before advancing to last page
case self.page6:
case PreferencesPage():
# NOTE: collects config values before advancing to last page
name, use_miles, client = self.page6.get_prefs()
self.config_values["name"] = name
self.config_values["use_miles"] = use_miles
self.config_values["client"] = client
self.write_config()
self.page7.set_steam_path(self.config_values["default_steam_path"])
case self.page7:
self.page7.create_shortcuts()
self.setup_complete = True
case ShortcutCreationPage():
page.create_shortcuts()
case _:
raise AttributeError("Trying to advance a non-canonical page")
return index + 1
@ -569,11 +580,7 @@ class Assistant(Gtk.Assistant):
bar.set_fraction(fraction)
bar.set_text(f"{page_num}/{total}")
# NOTE: disable forward action
# TODO: use page enums
if page == self.page5:
return
if page != self.page1:
if not isinstance(page, IntroductionPage):
EMITTER.emit("step_pending")
@ -600,7 +607,7 @@ class CheckboxWithLabel(Gtk.Box):
self.button.set_active(state)
class ShortcutCreationPage(ScrolledWizardPage):
class ShortcutCreationPage(OptionalPageMixin, ScrolledWizardPage): # type: ignore
def __init__(self, shortcut: Path) -> None:
super().__init__(
enum=PageNum.SHORTCUTS,
@ -643,9 +650,6 @@ class ShortcutCreationPage(ScrolledWizardPage):
self.desktop_checkbox.set_active(state)
self.desktop_checkbox.set_sensitive(state)
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
def set_steam_path(self, path: Path) -> None:
self.steam_path = path

View File

@ -107,11 +107,12 @@ config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3,Soup2" } }
[tool.pytest.ini_options]
markers = [
"apitest: checks remote endpoints",
"config: config file keys/values",
"mods: tests mod metadata/link creation",
"pefile: validate PE files",
"post_install: requires a completed installation",
"redact: log redaction mechanisms",
"slow: long-running tests",
"webtest: checks remote endpoints"
"webtest: depends on remote endpoint",
]

View File

@ -37,6 +37,14 @@ class RecordsListHandler(logging.Handler):
"Error in directory: '/home/SENSITIVE_USERNAME'",
"Error in directory: '/home/REDACTED'",
),
(
"User directory: /home/user/.local/share/Steam/userdata/999999/grid",
"User directory: /home/REDACTED/.local/share/Steam/userdata/REDACTED/grid",
),
(
"User directory: /drive/.local/share/Steam/userdata/999999",
"User directory: /drive/.local/share/Steam/userdata/REDACTED",
),
],
)
def test_log_redaction(log_error: str, expect: str) -> None:

View File

@ -38,14 +38,14 @@ def dummy_app() -> None:
return d
def test_wrap_exe(dummy_app) -> None:
def test_wrap_exe(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
assert s.shortcuts["shortcuts"]["0"]["Exe"][0] == '"'
assert s.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"'
def test_add_shortcut(dummy_app) -> None:
def test_add_shortcut(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
new = s.shortcuts["shortcuts"]
@ -56,7 +56,7 @@ def test_add_shortcut(dummy_app) -> None:
assert new[ind][k] == v
def test_save_shortcut(dummy_app) -> None:
def test_save_shortcut(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
with tempfile.NamedTemporaryFile() as f:
@ -65,3 +65,27 @@ def test_save_shortcut(dummy_app) -> None:
s.save_shortcuts()
s._load_shortcuts(tmp)
assert len(s.shortcuts["shortcuts"]) == 1
def test_shortcut_crc(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
for key in s.shortcuts["shortcuts"].keys():
entry = s.shortcuts["shortcuts"][key]
name = entry["AppName"]
exe = entry["Exe"]
uid = name + exe
bpid = s.gen_bpid(uid)
assert entry["appid"] & 0xFFFFFFFF == bpid
def test_reverse_crc(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
for key in s.shortcuts["shortcuts"].keys():
entry = s.shortcuts["shortcuts"][key]
name = entry["AppName"]
exe = entry["Exe"]
uid = name + exe
bpid = s.gen_bpid(uid)
assert s.find_appname_by_unsigned_id(bpid) == name