mirror of
https://github.com/aclist/dztui.git
synced 2026-11-05 17:45:10 +01:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
903f8fc31d | ||
|
|
6b583f68d6 | ||
|
|
3b3ccd0de6 | ||
|
|
70aacaa866 | ||
|
|
7582ab4d3b | ||
|
|
225a2a148e | ||
|
|
9117655ebd | ||
|
|
fdf92dca40 | ||
|
|
a97766855d | ||
|
|
3097701e98 | ||
|
|
895e08d228 | ||
|
|
08a6f698a5 | ||
|
|
78be8c93b7 | ||
|
|
8a2889d15d | ||
|
|
1a33f71716 | ||
|
|
ae13dc9a89 | ||
|
|
d5706f969c | ||
|
|
790e34b20c | ||
|
|
35230ae03f | ||
|
|
596d3a59ac | ||
|
|
e7cef215c6 | ||
|
|
12daf041ff | ||
|
|
90ef6cbdbb | ||
|
|
2cc26b6573 | ||
|
|
0ec1c157f4 | ||
|
|
0cf6439538 | ||
|
|
e3c1ef59ee | ||
|
|
80680b86b0 | ||
|
|
a4ac357c8f | ||
|
|
728b8b8242 | ||
|
|
3b51392d39 | ||
|
|
159e2da355 | ||
|
|
b83a333cea |
No files matched your search
@@ -53,6 +53,10 @@
|
|||||||
- Expand/collapse changelog versions
|
- Expand/collapse changelog versions
|
||||||
- Support local light/dark mode changes on system theme
|
- Support local light/dark mode changes on system theme
|
||||||
- Server time calculator
|
- Server time calculator
|
||||||
|
- Add "Unmodded" filter to servers
|
||||||
|
- List timestamp of last DayZ client update
|
||||||
|
- Validate mods via Mod page to pull in updates
|
||||||
|
- Support for Nasdara (Badlands) DLC
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Conform to PEP 440 versioning for beta versions
|
- Conform to PEP 440 versioning for beta versions
|
||||||
@@ -74,6 +78,7 @@
|
|||||||
- Enable LAN page Empty/Full filters on first run of app
|
- Enable LAN page Empty/Full filters on first run of app
|
||||||
- Propagate subscribed mods to Steam client
|
- Propagate subscribed mods to Steam client
|
||||||
- Make j/k keys in context menus follow same wraparound logic as down arrow/up arrow
|
- Make j/k keys in context menus follow same wraparound logic as down arrow/up arrow
|
||||||
|
- Use SQLite DB for geolocation records
|
||||||
|
|
||||||
### Dropped
|
### Dropped
|
||||||
- Battlemetrics API support
|
- Battlemetrics API support
|
||||||
@@ -92,6 +97,8 @@
|
|||||||
- Moved dialogs out of threads
|
- Moved dialogs out of threads
|
||||||
- History table not being in lock-step with state file
|
- History table not being in lock-step with state file
|
||||||
- Current keyword filter not being applied when refreshing table
|
- Current keyword filter not being applied when refreshing table
|
||||||
|
- Steam Flatpak cannot run DZGUI as a non-Steam game
|
||||||
|
- Collisions between native Steam and Steam Flatpak clients
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
- Load offline mods
|
- Load offline mods
|
||||||
|
|||||||
+28
-3
@@ -1,15 +1,18 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, TYPE_CHECKING
|
||||||
from warnings import deprecated
|
from warnings import deprecated
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
|
|
||||||
class ACF:
|
class ACF:
|
||||||
def __init__(self, file: Path) -> None:
|
def __init__(self, file: Path) -> None:
|
||||||
@@ -42,7 +45,7 @@ class ACF:
|
|||||||
def dequote(cls, s: str) -> str:
|
def dequote(cls, s: str) -> str:
|
||||||
return s.rstrip('"').lstrip('"')
|
return s.rstrip('"').lstrip('"')
|
||||||
|
|
||||||
def parse(self, lines: Iterator[list[str]]) -> dict[str, str]:
|
def parse(self, lines: "Iterator[list[str]]") -> dict[str, str]:
|
||||||
acf: dict[str, Any] = {}
|
acf: dict[str, Any] = {}
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
@@ -69,6 +72,28 @@ class ACF:
|
|||||||
except StopIteration:
|
except StopIteration:
|
||||||
return acf
|
return acf
|
||||||
|
|
||||||
|
def search_key(self, key: str) -> str:
|
||||||
|
return str(self.dict["AppState"][key])
|
||||||
|
|
||||||
|
def date_to_str(self, epoch: int) -> str | None:
|
||||||
|
if epoch == 0:
|
||||||
|
return None
|
||||||
|
return datetime.fromtimestamp(epoch).isoformat(" ")
|
||||||
|
|
||||||
|
def get_date_by_key(self, key: str) -> str | None:
|
||||||
|
try:
|
||||||
|
stamp = int(self.search_key(key))
|
||||||
|
return self.date_to_str(stamp)
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_last_updated(self) -> str | None:
|
||||||
|
return self.get_date_by_key("LastUpdated")
|
||||||
|
|
||||||
|
def get_last_played(self) -> str | None:
|
||||||
|
return self.get_date_by_key("LastPlayed")
|
||||||
|
|
||||||
def get_allows_downloads(self) -> int | None:
|
def get_allows_downloads(self) -> int | None:
|
||||||
try:
|
try:
|
||||||
# TODO: consider coercing strs to ints on initial import
|
# TODO: consider coercing strs to ints on initial import
|
||||||
|
|||||||
+30
-3
@@ -12,6 +12,7 @@ from typing import Any, Generator, Union
|
|||||||
from warnings import deprecated
|
from warnings import deprecated
|
||||||
|
|
||||||
from dzgui.api.acf import ACF
|
from dzgui.api.acf import ACF
|
||||||
|
from dzgui.const.endpoints import VALIDATE
|
||||||
from dzgui.init.prereqs import has_any_client
|
from dzgui.init.prereqs import has_any_client
|
||||||
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
@@ -316,10 +317,9 @@ def unsubscribe(key: str, mod: int) -> None:
|
|||||||
update_workshop(key, mod, UNSUB_ENDPOINT)
|
update_workshop(key, mod, UNSUB_ENDPOINT)
|
||||||
|
|
||||||
|
|
||||||
@deprecated("Use subscribe()")
|
def validate_mod(client: str, mod: str) -> None:
|
||||||
def enqueue_mod(client: str, mod: str, appid: int) -> None:
|
|
||||||
client_args = concat_bash_args(client)
|
client_args = concat_bash_args(client)
|
||||||
subprocess.Popen([*client_args, "+workshop_download_item", str(appid), mod])
|
subprocess.Popen([*client_args, "+workshop_download_item", str(APPID_DAYZ), mod])
|
||||||
|
|
||||||
|
|
||||||
@deprecated("Cf. https://github.com/ValveSoftware/steam-for-linux/issues/9672")
|
@deprecated("Cf. https://github.com/ValveSoftware/steam-for-linux/issues/9672")
|
||||||
@@ -389,6 +389,26 @@ def get_running_app() -> int | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_updated(path: Path, appid: int) -> str | None:
|
||||||
|
root_path = get_app_path(path, appid)
|
||||||
|
acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf")
|
||||||
|
try:
|
||||||
|
return ACF(acf).get_last_updated()
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_played(path: Path, appid: int) -> str | None:
|
||||||
|
root_path = get_app_path(path, appid)
|
||||||
|
acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf")
|
||||||
|
try:
|
||||||
|
return ACF(acf).get_last_played()
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_app_allows_downloads(path: Path, appid: int) -> bool:
|
def get_app_allows_downloads(path: Path, appid: int) -> bool:
|
||||||
try:
|
try:
|
||||||
root_path = get_app_path(path, appid)
|
root_path = get_app_path(path, appid)
|
||||||
@@ -500,3 +520,10 @@ def get_app_name(appid: int) -> str | None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(e)
|
logger.debug(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("Currently unused")
|
||||||
|
def validate_dayz(client: str) -> None:
|
||||||
|
client_args = concat_bash_args(client)
|
||||||
|
uri = VALIDATE + str(APPID_DAYZ)
|
||||||
|
subprocess.Popen([*client_args, uri])
|
||||||
+3
-3
@@ -121,7 +121,7 @@ def load_steam_icon() -> None:
|
|||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
|
|
||||||
|
|
||||||
def load_gui(version: str, is_debug: bool) -> None:
|
def build_gui(version: str, is_debug: bool) -> App | None:
|
||||||
lock = lock_acquire() # noqa
|
lock = lock_acquire() # noqa
|
||||||
|
|
||||||
set_locale()
|
set_locale()
|
||||||
@@ -151,7 +151,7 @@ def load_gui(version: str, is_debug: bool) -> None:
|
|||||||
wizard = SetupWizard(_is_steam_deck, XDG, xdg_paths)
|
wizard = SetupWizard(_is_steam_deck, XDG, xdg_paths)
|
||||||
|
|
||||||
if not wizard.is_setup_complete():
|
if not wizard.is_setup_complete():
|
||||||
return
|
return None
|
||||||
|
|
||||||
setup_logger(XDG.debug)
|
setup_logger(XDG.debug)
|
||||||
with open(XDG.debug, "w") as f:
|
with open(XDG.debug, "w") as f:
|
||||||
@@ -186,4 +186,4 @@ def load_gui(version: str, is_debug: bool) -> None:
|
|||||||
print(boot.all_ok)
|
print(boot.all_ok)
|
||||||
load_steam_icon()
|
load_steam_icon()
|
||||||
app = App(prefs)
|
app = App(prefs)
|
||||||
app.run(None)
|
return app
|
||||||
@@ -18,6 +18,7 @@ COORDS_API = "http://ip-api.com/json"
|
|||||||
# steam URIs
|
# steam URIs
|
||||||
WORKSHOP = "steam://url/CommunityFilePage/"
|
WORKSHOP = "steam://url/CommunityFilePage/"
|
||||||
OPEN_URL = "steam://openurl/"
|
OPEN_URL = "steam://openurl/"
|
||||||
|
VALIDATE = "steam://validate/"
|
||||||
|
|
||||||
# user facing
|
# user facing
|
||||||
GITHUB_PAGES = "https://aclist.github.io/dzgui/index"
|
GITHUB_PAGES = "https://aclist.github.io/dzgui/index"
|
||||||
|
|||||||
+6
-1
@@ -157,6 +157,7 @@ class ContextMenu(EnumWithAttrs):
|
|||||||
COPY_SERVER_IP = {"label": strings.copy_ip}
|
COPY_SERVER_IP = {"label": strings.copy_ip}
|
||||||
COPY_SERVER_NAME = {"label": strings.copy_name}
|
COPY_SERVER_NAME = {"label": strings.copy_name}
|
||||||
UNSUB_MOD = {"label": strings.unsub_mod}
|
UNSUB_MOD = {"label": strings.unsub_mod}
|
||||||
|
VALIDATE_MOD = {"label": strings.validate_mod}
|
||||||
OPEN_WORKSHOP = {"label": strings.open_workshop}
|
OPEN_WORKSHOP = {"label": strings.open_workshop}
|
||||||
REFRESH_PLAYERS = {"label": strings.refresh_players}
|
REFRESH_PLAYERS = {"label": strings.refresh_players}
|
||||||
REMOVE_HISTORY = {"label": strings.remove_history}
|
REMOVE_HISTORY = {"label": strings.remove_history}
|
||||||
@@ -172,7 +173,7 @@ class ContextMenuGroup(Enum):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
SERVER_MOD = (ContextMenu.OPEN_WORKSHOP,)
|
SERVER_MOD = (ContextMenu.OPEN_WORKSHOP,)
|
||||||
MOD = (ContextMenu.OPEN_WORKSHOP, ContextMenu.UNSUB_MOD)
|
MOD = (ContextMenu.OPEN_WORKSHOP, ContextMenu.UNSUB_MOD, ContextMenu.VALIDATE_MOD)
|
||||||
MOD_OFFLINE = (None,)
|
MOD_OFFLINE = (None,)
|
||||||
LOG = (ContextMenu.COPY_LOG_CLIPBOARD,)
|
LOG = (ContextMenu.COPY_LOG_CLIPBOARD,)
|
||||||
SERVER_BROWSER = (
|
SERVER_BROWSER = (
|
||||||
@@ -246,6 +247,10 @@ class ModButton(EnumWithAttrs):
|
|||||||
"label": strings.mod_panel.select_stale,
|
"label": strings.mod_panel.select_stale,
|
||||||
"tooltip": strings.mod_panel.select_stale_tooltip,
|
"tooltip": strings.mod_panel.select_stale_tooltip,
|
||||||
}
|
}
|
||||||
|
VALIDATE = {
|
||||||
|
"label": strings.mod_panel.validate,
|
||||||
|
"tooltip": strings.mod_panel.validate_tooltip,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ButtonType(EnumWithAttrs):
|
class ButtonType(EnumWithAttrs):
|
||||||
|
|||||||
@@ -102,6 +102,16 @@ class Controller(GObject.GObject):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.critical(f"{attr} is not a valid AppNavigation attribute.")
|
logger.critical(f"{attr} is not a valid AppNavigation attribute.")
|
||||||
|
|
||||||
|
def validate_mods(
|
||||||
|
self, treeview: Union["ModTreeView", "OfflineModTreeView", None] = None
|
||||||
|
) -> None:
|
||||||
|
if treeview is None:
|
||||||
|
view = self.mediator.modtreeview
|
||||||
|
else:
|
||||||
|
view = treeview
|
||||||
|
mod_man = view.get_mod_man()
|
||||||
|
mod_man.validate_mods()
|
||||||
|
|
||||||
def get_prefs(self) -> UserPrefs:
|
def get_prefs(self) -> UserPrefs:
|
||||||
return self.prefs
|
return self.prefs
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 361 KiB |
+1
-1
Submodule dzgui/lib/dayzquery updated: 07483b88ed...fe413df8e8.
+5
-2
@@ -42,6 +42,9 @@ def main() -> None:
|
|||||||
test_libgi_missing()
|
test_libgi_missing()
|
||||||
|
|
||||||
# NOTE: only import ligbirepository modules after the above check
|
# NOTE: only import ligbirepository modules after the above check
|
||||||
from dzgui.app_init import load_gui
|
from dzgui.app_init import build_gui
|
||||||
|
|
||||||
load_gui(version, args.debug)
|
app = build_gui(version, args.debug)
|
||||||
|
if app is None:
|
||||||
|
sys.exit(0)
|
||||||
|
app.run(None)
|
||||||
@@ -75,10 +75,14 @@ class ContextMenuManager:
|
|||||||
|
|
||||||
if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
|
if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
|
||||||
match action:
|
match action:
|
||||||
case ContextMenu.UNSUB_MOD:
|
|
||||||
self.controller.unsub_mods(self.treeview)
|
|
||||||
case ContextMenu.OPEN_WORKSHOP:
|
case ContextMenu.OPEN_WORKSHOP:
|
||||||
self.open_mod_page()
|
self.open_mod_page()
|
||||||
|
case ContextMenu.UNSUB_MOD:
|
||||||
|
self.controller.unsub_mods(self.treeview)
|
||||||
|
case ContextMenu.VALIDATE_MOD:
|
||||||
|
self.controller.validate_mods(self.treeview)
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
if isinstance(self.treeview, LogTreeView):
|
if isinstance(self.treeview, LogTreeView):
|
||||||
match action:
|
match action:
|
||||||
|
|||||||
+26
-2
@@ -4,7 +4,7 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.api.steam import unsubscribe
|
from dzgui.api.steam import unsubscribe, validate_mod
|
||||||
from dzgui.api.mods import (
|
from dzgui.api.mods import (
|
||||||
get_delimited_mods,
|
get_delimited_mods,
|
||||||
find_stale_mods,
|
find_stale_mods,
|
||||||
@@ -85,7 +85,7 @@ class ModManager:
|
|||||||
total_mods = len(self.store)
|
total_mods = len(self.store)
|
||||||
self.emitter.emit("mods_updated", msg, total_mods)
|
self.emitter.emit("mods_updated", msg, total_mods)
|
||||||
|
|
||||||
def unsub_mods(self) -> None:
|
def get_selected_mods(self) -> list[tuple[str, Gtk.TreeIter]]:
|
||||||
sel = self.treeview.get_selection()
|
sel = self.treeview.get_selection()
|
||||||
model, pathlist = sel.get_selected_rows()
|
model, pathlist = sel.get_selected_rows()
|
||||||
# NOTE: reverse when multiple selection
|
# NOTE: reverse when multiple selection
|
||||||
@@ -96,6 +96,15 @@ class ModManager:
|
|||||||
continue
|
continue
|
||||||
mod, _iter = res
|
mod, _iter = res
|
||||||
mods.append((mod, _iter))
|
mods.append((mod, _iter))
|
||||||
|
return mods
|
||||||
|
|
||||||
|
def validate_mods(self) -> None:
|
||||||
|
mods = self.get_selected_mods()
|
||||||
|
self.thread_man.set_job_count(len(mods))
|
||||||
|
self.validate_all_mods(mods)
|
||||||
|
|
||||||
|
def unsub_mods(self) -> None:
|
||||||
|
mods = self.get_selected_mods()
|
||||||
self.thread_man.set_job_count(len(mods))
|
self.thread_man.set_job_count(len(mods))
|
||||||
self.unsub_all_mods(mods)
|
self.unsub_all_mods(mods)
|
||||||
|
|
||||||
@@ -109,6 +118,21 @@ class ModManager:
|
|||||||
mod = model.get_value(tree_iter, 2)
|
mod = model.get_value(tree_iter, 2)
|
||||||
return mod, tree_iter
|
return mod, tree_iter
|
||||||
|
|
||||||
|
def validate_atomic_mod(self, mod: str) -> None:
|
||||||
|
# NOTE: propagation to dayz exp will happen during
|
||||||
|
# subsequent symlink phase on connect
|
||||||
|
client = self.controller.query_config(Preferences.CLIENT)
|
||||||
|
validate_mod(client, mod)
|
||||||
|
|
||||||
|
@call_on_thread(dialogs.validating_mods)
|
||||||
|
def validate_all_mods(self, mods: list[tuple[str, Gtk.TreeIter]]) -> None:
|
||||||
|
for mod, _iter in mods:
|
||||||
|
self.validate_atomic_mod(mod)
|
||||||
|
self.thread_man.increment_dialog()
|
||||||
|
time.sleep(API_RATE_LIMIT)
|
||||||
|
func = StoredFunc(lambda: None)
|
||||||
|
self.thread_man.set_cleanup_func(func)
|
||||||
|
|
||||||
@call_on_thread(dialogs.deleting_mods)
|
@call_on_thread(dialogs.deleting_mods)
|
||||||
def unsub_all_mods(self, mods: list[tuple[str, Gtk.TreeIter]]) -> None:
|
def unsub_all_mods(self, mods: list[tuple[str, Gtk.TreeIter]]) -> None:
|
||||||
for mod, _iter in mods:
|
for mod, _iter in mods:
|
||||||
|
|||||||
+30
-7
@@ -201,19 +201,43 @@ class ServerModelManager:
|
|||||||
|
|
||||||
@call_on_thread(dialog.querying)
|
@call_on_thread(dialog.querying)
|
||||||
def add_by_ip(self, addr: str) -> None:
|
def add_by_ip(self, addr: str) -> None:
|
||||||
res = Servers.query_by_ip(addr)
|
self.preserve_on_fail = True
|
||||||
self._parse_single_record(res)
|
try:
|
||||||
|
res = Servers.query_by_ip(addr)
|
||||||
|
self._parse_single_record(res)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
self.thread_man.set_cleanup_func(
|
||||||
|
StoredFunc(self._cleanup_on_failure), destroy_first=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
@call_on_thread(dialog.querying)
|
@call_on_thread(dialog.querying)
|
||||||
def add_by_record(self, record: Servers.Record) -> None:
|
def add_by_record(self, record: Servers.Record) -> None:
|
||||||
|
self.preserve_on_fail = True
|
||||||
# NOTE: rationale: a raw Record as shown in server browser may resolve to a different IP
|
# NOTE: rationale: a raw Record as shown in server browser may resolve to a different IP
|
||||||
res = Servers.query_by_record(record)
|
try:
|
||||||
self._parse_single_record(res)
|
res = Servers.query_by_record(record)
|
||||||
|
self._parse_single_record(res)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
self.thread_man.set_cleanup_func(
|
||||||
|
StoredFunc(self._cleanup_on_failure), destroy_first=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
@call_on_thread(dialog.querying)
|
@call_on_thread(dialog.querying)
|
||||||
def remove_by_record(self, record: Servers.Record) -> None:
|
def remove_by_record(self, record: Servers.Record) -> None:
|
||||||
res = Servers.query_by_record(record)
|
self.preserve_on_fail = True
|
||||||
self._parse_single_record(res, delete=True)
|
try:
|
||||||
|
res = Servers.query_by_record(record)
|
||||||
|
self._parse_single_record(res, delete=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
self.thread_man.set_cleanup_func(
|
||||||
|
StoredFunc(self._cleanup_on_failure), destroy_first=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
def update_history(
|
def update_history(
|
||||||
self,
|
self,
|
||||||
@@ -280,7 +304,6 @@ class ServerModelManager:
|
|||||||
proxy_man.update_playercount(self.playercount)
|
proxy_man.update_playercount(self.playercount)
|
||||||
|
|
||||||
def _parse_single_record(self, response: "A2SInfo", delete: bool = False) -> None:
|
def _parse_single_record(self, response: "A2SInfo", delete: bool = False) -> None:
|
||||||
self.preserve_on_fail = True
|
|
||||||
try:
|
try:
|
||||||
row = response.as_row()
|
row = response.as_row()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ update_success = "Updated successfully. Please exit and relaunch."
|
|||||||
load_error_lan = "Failed to find any servers on your network.\nCheck the server query port or your firewall settings."
|
load_error_lan = "Failed to find any servers on your network.\nCheck the server query port or your firewall settings."
|
||||||
fetching_mods = "Fetching mod metadata"
|
fetching_mods = "Fetching mod metadata"
|
||||||
deleting_mods = "Unsubscribing mods"
|
deleting_mods = "Unsubscribing mods"
|
||||||
|
validating_mods = "Validating mods"
|
||||||
scanning_mods = "Scanning mods"
|
scanning_mods = "Scanning mods"
|
||||||
parsing_mods = "Parsing mods"
|
parsing_mods = "Parsing mods"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import cast, TYPE_CHECKING
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, Gdk, GLib # noqa E402
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
from dzgui.views.base import OuterWindow
|
||||||
|
from dzgui.views.mixins.context_mixin import ContextMixin
|
||||||
|
from gi.repository import GdkPixbuf
|
||||||
|
|
||||||
|
# TODO: randomize/mock PII distance values
|
||||||
|
|
||||||
|
|
||||||
|
class IdleScreenshot:
|
||||||
|
def __init__(
|
||||||
|
self, controller: "Controller", window: "OuterWindow", scrot_path: Path
|
||||||
|
) -> None:
|
||||||
|
self.window = window
|
||||||
|
self.controller = controller
|
||||||
|
self.scrot_path = scrot_path
|
||||||
|
|
||||||
|
clock = window.get_frame_clock()
|
||||||
|
if clock is None:
|
||||||
|
raise TypeError("Widget not realized, FrameClock not initialized")
|
||||||
|
clock.connect("after-paint", self.callback)
|
||||||
|
|
||||||
|
def callback(self, clock: Gdk.FrameClock) -> None:
|
||||||
|
GLib.timeout_add(1000, self.check_idle)
|
||||||
|
|
||||||
|
def check_idle(self) -> bool:
|
||||||
|
tv = self.controller.get_active_treeview()
|
||||||
|
loaded = tv.is_loaded()
|
||||||
|
if loaded is True:
|
||||||
|
self.emit_key_press(tv)
|
||||||
|
GLib.timeout_add(1000, self.save_and_quit)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def emit_key_press(self, widget: "ContextMixin") -> None:
|
||||||
|
window = widget.get_parent_window()
|
||||||
|
if window is None:
|
||||||
|
raise TypeError(f"Failed to find a valid parent window for {widget}")
|
||||||
|
event = Gdk.EventKey.new(Gdk.EventType.KEY_PRESS)
|
||||||
|
# NOTE: GTK introspection returns Gdk.Event type
|
||||||
|
event_key = cast(Gdk.EventKey, event)
|
||||||
|
event_key.keyval = Gdk.KEY_l
|
||||||
|
event_key.state = Gdk.ModifierType.CONTROL_MASK
|
||||||
|
event_key.window = window
|
||||||
|
event_key.time = Gdk.CURRENT_TIME
|
||||||
|
event_key.send_event = True
|
||||||
|
# TODO: suppress errors from synthetic key events
|
||||||
|
widget.present_menu(widget, event)
|
||||||
|
|
||||||
|
def save_and_quit(self) -> None:
|
||||||
|
self.save_pixbuf()
|
||||||
|
self.controller.save_res_and_quit()
|
||||||
|
|
||||||
|
def save_pixbuf(self) -> None:
|
||||||
|
pixbuf = self.capture_application()
|
||||||
|
if pixbuf:
|
||||||
|
pixbuf.savev(str(self.scrot_path), "png", [], [])
|
||||||
|
|
||||||
|
def capture_application(self) -> "GdkPixbuf.Pixbuf":
|
||||||
|
# NOTE: composited context menu over X11 window
|
||||||
|
root = Gdk.get_default_root_window()
|
||||||
|
|
||||||
|
xwindow = self.window.get_window()
|
||||||
|
if xwindow is None:
|
||||||
|
raise ValueError("Failed to get a valid X11 window")
|
||||||
|
index, x, y = xwindow.get_origin()
|
||||||
|
width, height = self.window.get_size()
|
||||||
|
|
||||||
|
application = Gdk.pixbuf_get_from_window(root, x, y, width, height)
|
||||||
|
if application is None:
|
||||||
|
raise ValueError("Failed to get a valid GdkPixbuf")
|
||||||
|
return application
|
||||||
@@ -31,6 +31,7 @@ show_details = "Show server details"
|
|||||||
refresh_players = "Refresh player count"
|
refresh_players = "Refresh player count"
|
||||||
open_workshop = "Open in Steam Workshop"
|
open_workshop = "Open in Steam Workshop"
|
||||||
unsub_mod = "Unsubscribe mod"
|
unsub_mod = "Unsubscribe mod"
|
||||||
|
validate_mod = "Validate mod"
|
||||||
copy_name = "Copy name to clipboard"
|
copy_name = "Copy name to clipboard"
|
||||||
copy_ip = "Copy IP to clipboard"
|
copy_ip = "Copy IP to clipboard"
|
||||||
copy_log = "Copy record(s) to clipboard"
|
copy_log = "Copy record(s) to clipboard"
|
||||||
@@ -200,6 +201,8 @@ class ModPanelStrings:
|
|||||||
clear_highlights: str
|
clear_highlights: str
|
||||||
select_stale: str
|
select_stale: str
|
||||||
select_stale_tooltip: str
|
select_stale_tooltip: str
|
||||||
|
validate: str
|
||||||
|
validate_tooltip: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
@@ -319,6 +322,8 @@ mod_panel = ModPanelStrings(
|
|||||||
clear_highlights="Clears highlights and reverts the table to a default state",
|
clear_highlights="Clears highlights and reverts the table to a default state",
|
||||||
select_stale="Select stale",
|
select_stale="Select stale",
|
||||||
select_stale_tooltip="Only selects highlighted stale mods",
|
select_stale_tooltip="Only selects highlighted stale mods",
|
||||||
|
validate="Validate selected",
|
||||||
|
validate_tooltip="Validates selected mods and updates them if necessary",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+20
-2
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Literal, Self
|
from typing import Any, Literal, Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
@@ -47,7 +47,7 @@ logger = logging.getLogger(APP_NAME)
|
|||||||
|
|
||||||
|
|
||||||
class OuterWindow(Gtk.ApplicationWindow):
|
class OuterWindow(Gtk.ApplicationWindow):
|
||||||
def __init__(self, app: Gtk.Application) -> None:
|
def __init__(self, app: "App") -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
application=app, title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER
|
application=app, title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER
|
||||||
)
|
)
|
||||||
@@ -78,6 +78,15 @@ class OuterWindow(Gtk.ApplicationWindow):
|
|||||||
MainController.loaded = True
|
MainController.loaded = True
|
||||||
MainController.populate_model(MainController.get_active_treeview())
|
MainController.populate_model(MainController.get_active_treeview())
|
||||||
|
|
||||||
|
try:
|
||||||
|
from dzgui.util.save_screenshot import IdleScreenshot
|
||||||
|
|
||||||
|
scrot = app.get_automation_param("screenshot_path")
|
||||||
|
self.fullscreen()
|
||||||
|
IdleScreenshot(MainController, self, scrot)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
|
def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
|
||||||
self.halt_proc_and_quit()
|
self.halt_proc_and_quit()
|
||||||
|
|
||||||
@@ -291,6 +300,14 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore
|
|||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self.connect("activate", self._on_activate)
|
self.connect("activate", self._on_activate)
|
||||||
|
|
||||||
|
self.automation_params: dict[str, Any] = {}
|
||||||
|
|
||||||
|
def get_automation_param(self, key: str) -> Any:
|
||||||
|
return self.automation_params[key]
|
||||||
|
|
||||||
|
def set_automation_param(self, key: str, value: Any) -> None:
|
||||||
|
self.automation_params[key] = value
|
||||||
|
|
||||||
def _on_activate(self, app: Self) -> None:
|
def _on_activate(self, app: Self) -> None:
|
||||||
self.win = OuterWindow(app)
|
self.win = OuterWindow(app)
|
||||||
|
|
||||||
@@ -331,4 +348,5 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore
|
|||||||
) -> None:
|
) -> None:
|
||||||
self.win.halt_proc_and_quit()
|
self.win.halt_proc_and_quit()
|
||||||
|
|
||||||
|
|
||||||
MainController = Controller()
|
MainController = Controller()
|
||||||
@@ -313,6 +313,8 @@ class FilterPanel(Gtk.Box):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def toggle_check(self, digit: int) -> None:
|
def toggle_check(self, digit: int) -> None:
|
||||||
|
if not self.button_grid.is_sensitive():
|
||||||
|
return
|
||||||
checks = self.button_grid.get_checkboxes()
|
checks = self.button_grid.get_checkboxes()
|
||||||
check = checks[digit]
|
check = checks[digit]
|
||||||
state = check.get_active()
|
state = check.get_active()
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class ModSelectionPanel(Gtk.Box):
|
|||||||
ModButton.SELECT_ALL,
|
ModButton.SELECT_ALL,
|
||||||
ModButton.UNSELECT_ALL,
|
ModButton.UNSELECT_ALL,
|
||||||
ModButton.UNSUB_SELECTED,
|
ModButton.UNSUB_SELECTED,
|
||||||
|
ModButton.VALIDATE,
|
||||||
)
|
)
|
||||||
for button in buttons:
|
for button in buttons:
|
||||||
b = EnumeratedModButton(button)
|
b = EnumeratedModButton(button)
|
||||||
@@ -114,3 +115,5 @@ class ModSelectionPanel(Gtk.Box):
|
|||||||
self.swap_sensitive(False)
|
self.swap_sensitive(False)
|
||||||
case ModButton.SELECT_STALE:
|
case ModButton.SELECT_STALE:
|
||||||
self.controller.select_colorized()
|
self.controller.select_colorized()
|
||||||
|
case ModButton.VALIDATE:
|
||||||
|
self.controller.validate_mods()
|
||||||
@@ -16,9 +16,7 @@ logger = logging.getLogger(APP_NAME)
|
|||||||
|
|
||||||
class ContextMixin(TreeView):
|
class ContextMixin(TreeView):
|
||||||
def present_menu(
|
def present_menu(
|
||||||
self,
|
self, widget: Gtk.Widget, event: Gdk.Event
|
||||||
widget: Gtk.Widget,
|
|
||||||
event: Gdk.EventButton | Gdk.EventKey,
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
if self.menu is None:
|
if self.menu is None:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from pathlib import Path
|
|||||||
from typing import Callable, TYPE_CHECKING
|
from typing import Callable, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.api import pefile as PeFile
|
from dzgui.api import pefile as PeFile
|
||||||
|
from dzgui.api.steam import get_last_updated
|
||||||
|
|
||||||
from dzgui.config import query
|
from dzgui.config import query
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
@@ -340,8 +341,10 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
[LeftLabel(options.flatpak_label), self.flatpak_toggle],
|
[LeftLabel(options.flatpak_label), self.flatpak_toggle],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.dayz_version_label = Gtk.Label(label=strings.null)
|
self.dayz_version_label = Gtk.Label(label=strings.null, halign=Gtk.Align.START)
|
||||||
self.dayz_exp_version_label = Gtk.Label(label=strings.null)
|
self.dayz_exp_version_label = Gtk.Label(
|
||||||
|
label=strings.null, halign=Gtk.Align.START
|
||||||
|
)
|
||||||
|
|
||||||
version_rows = [
|
version_rows = [
|
||||||
[LeftLabel(APPNAME_DAYZ), self.dayz_version_label],
|
[LeftLabel(APPNAME_DAYZ), self.dayz_version_label],
|
||||||
@@ -467,10 +470,18 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
dayz_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ)
|
dayz_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ)
|
||||||
if dayz_version is None:
|
if dayz_version is None:
|
||||||
dayz_version = strings.null
|
dayz_version = strings.null
|
||||||
|
else:
|
||||||
|
last_updated = get_last_updated(steam_path, APPID_DAYZ)
|
||||||
|
if last_updated is not None:
|
||||||
|
dayz_version += f" (updated at {last_updated})"
|
||||||
|
|
||||||
dayz_exp_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP)
|
dayz_exp_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP)
|
||||||
if dayz_exp_version is None:
|
if dayz_exp_version is None:
|
||||||
dayz_exp_version = strings.null
|
dayz_exp_version = strings.null
|
||||||
|
else:
|
||||||
|
last_updated = get_last_updated(steam_path, APPID_DAYZ_EXP)
|
||||||
|
if last_updated is not None:
|
||||||
|
dayz_exp_version += f" (updated at {last_updated})"
|
||||||
|
|
||||||
self.dayz_version_label.set_text(dayz_version)
|
self.dayz_version_label.set_text(dayz_version)
|
||||||
self.dayz_exp_version_label.set_text(dayz_exp_version)
|
self.dayz_exp_version_label.set_text(dayz_exp_version)
|
||||||
|
|||||||
@@ -108,14 +108,9 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
|||||||
|
|
||||||
def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
|
def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
|
||||||
"""
|
"""
|
||||||
Suppresses spamming on keydown
|
Adds debounce to key press/release events to prevent spamming
|
||||||
TODO: explain this better
|
when rapidly navigating through records
|
||||||
"""
|
"""
|
||||||
# TODO: multisel
|
|
||||||
# if event.keyval is Gdk.KEY_space:
|
|
||||||
# it = self.get_focused_row_iter()
|
|
||||||
# self.get_selection().select_iter(it)
|
|
||||||
# return True
|
|
||||||
|
|
||||||
model = self.get_model()
|
model = self.get_model()
|
||||||
if model is None:
|
if model is None:
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
|||||||
authors = [
|
authors = [
|
||||||
{name = "aclist"}
|
{name = "aclist"}
|
||||||
]
|
]
|
||||||
version = "7.0.0b29"
|
version = "7.0.0b30"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
root = Path(__file__).resolve().parents[1]
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
def get_entrypoint() -> None:
|
def get_entrypoint() -> None:
|
||||||
p = root.joinpath("pyproject.toml")
|
p = root.joinpath("pyproject.toml")
|
||||||
with open(p, "rb") as f:
|
with open(p, "rb") as f:
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"AppState"
|
||||||
|
{
|
||||||
|
"appid" "444"
|
||||||
|
"Universe" "1"
|
||||||
|
"name" "TEST APP"
|
||||||
|
"StateFlags" "4"
|
||||||
|
"installdir" "TESTAPP"
|
||||||
|
"LastUpdated" "1792595373"
|
||||||
|
"LastPlayed" "0"
|
||||||
|
"SizeOnDisk" "0"
|
||||||
|
"StagingSize" "0"
|
||||||
|
"buildid" "0"
|
||||||
|
"LastOwner" "0"
|
||||||
|
"DownloadType" "2"
|
||||||
|
"UpdateResult" "0"
|
||||||
|
"BytesToDownload" "0"
|
||||||
|
"BytesDownloaded" "0"
|
||||||
|
"BytesToStage" "0"
|
||||||
|
"BytesStaged" "0"
|
||||||
|
"TargetBuildID" "0"
|
||||||
|
"AutoUpdateBehavior" "0"
|
||||||
|
"AllowOtherDownloadsWhileRunning" "0"
|
||||||
|
"ScheduledAutoUpdate" "0"
|
||||||
|
"FullValidateAfterNextUpdate" "1"
|
||||||
|
"InstalledDepots"
|
||||||
|
{
|
||||||
|
"3330"
|
||||||
|
{
|
||||||
|
"manifest" "0"
|
||||||
|
"size" "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"SharedDepots"
|
||||||
|
{
|
||||||
|
"228983" "228980"
|
||||||
|
"228985" "228980"
|
||||||
|
"228988" "228980"
|
||||||
|
"228990" "228980"
|
||||||
|
"229004" "228980"
|
||||||
|
}
|
||||||
|
"UserConfig"
|
||||||
|
{
|
||||||
|
"language" "english"
|
||||||
|
"platform_override_dest" "linux"
|
||||||
|
"platform_override_source" "windows"
|
||||||
|
}
|
||||||
|
"MountedConfig"
|
||||||
|
{
|
||||||
|
"language" "english"
|
||||||
|
"platform_override_dest" "linux"
|
||||||
|
"platform_override_source" "windows"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"AppState"
|
||||||
|
{
|
||||||
|
"appid" "555"
|
||||||
|
"Universe" "1"
|
||||||
|
"name" "TEST APP"
|
||||||
|
"StateFlags" "4"
|
||||||
|
"installdir" "TESTAPP"
|
||||||
|
"LastUpdated" "0"
|
||||||
|
"LastPlayed" "0"
|
||||||
|
"SizeOnDisk" "0"
|
||||||
|
"StagingSize" "0"
|
||||||
|
"buildid" "0"
|
||||||
|
"LastOwner" "0"
|
||||||
|
"DownloadType" "2"
|
||||||
|
"UpdateResult" "0"
|
||||||
|
"BytesToDownload" "0"
|
||||||
|
"BytesDownloaded" "0"
|
||||||
|
"BytesToStage" "0"
|
||||||
|
"BytesStaged" "0"
|
||||||
|
"TargetBuildID" "0"
|
||||||
|
"AutoUpdateBehavior" "0"
|
||||||
|
"AllowOtherDownloadsWhileRunning" "0"
|
||||||
|
"ScheduledAutoUpdate" "0"
|
||||||
|
"FullValidateAfterNextUpdate" "1"
|
||||||
|
"InstalledDepots"
|
||||||
|
{
|
||||||
|
"3330"
|
||||||
|
{
|
||||||
|
"manifest" "0"
|
||||||
|
"size" "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"SharedDepots"
|
||||||
|
{
|
||||||
|
"228983" "228980"
|
||||||
|
"228985" "228980"
|
||||||
|
"228988" "228980"
|
||||||
|
"228990" "228980"
|
||||||
|
"229004" "228980"
|
||||||
|
}
|
||||||
|
"UserConfig"
|
||||||
|
{
|
||||||
|
"language" "english"
|
||||||
|
"platform_override_dest" "linux"
|
||||||
|
"platform_override_source" "windows"
|
||||||
|
}
|
||||||
|
"MountedConfig"
|
||||||
|
{
|
||||||
|
"language" "english"
|
||||||
|
"platform_override_dest" "linux"
|
||||||
|
"platform_override_source" "windows"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import pytest
|
|||||||
from _pytest.monkeypatch import MonkeyPatch
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from dzgui.api.steam import get_app_allows_downloads
|
from dzgui.api.steam import get_app_allows_downloads, get_last_updated
|
||||||
from tests.fixtures import fixture_path
|
from tests.fixtures import fixture_path
|
||||||
|
|
||||||
pytestmark = pytest.mark.apitest
|
pytestmark = pytest.mark.apitest
|
||||||
@@ -43,6 +43,14 @@ def patch_api() -> None:
|
|||||||
mp.undo()
|
mp.undo()
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_last_update(steam_path) -> None:
|
||||||
|
assert get_last_updated(steam_path, 555) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_last_update(steam_path) -> None:
|
||||||
|
assert get_last_updated(steam_path, 444) == "2026-10-22 00:09:33"
|
||||||
|
|
||||||
|
|
||||||
def test_app_allows_downloads(steam_path) -> None:
|
def test_app_allows_downloads(steam_path) -> None:
|
||||||
assert get_app_allows_downloads(steam_path, 111) is True
|
assert get_app_allows_downloads(steam_path, 111) is True
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ def keys():
|
|||||||
|
|
||||||
# TODO: use a static fixture instead of system config
|
# TODO: use a static fixture instead of system config
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@pytest.mark.realconfig
|
|
||||||
def config():
|
def config():
|
||||||
paths = get_xdg_paths()
|
paths = get_xdg_paths()
|
||||||
xdg = parse_filepaths(paths)
|
xdg = parse_filepaths(paths)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import configparser
|
import configparser
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from dzgui.const.constants import FLATPAK_APPID, GTK_APP_ID
|
from dzgui.const.constants import FLATPAK_APPID, GTK_APP_ID, TMP_PATH
|
||||||
from dzgui.config.xdg import get_xdg_paths
|
from dzgui.config.xdg import get_xdg_paths
|
||||||
from dzgui.util.flatpak import apply_overrides
|
from dzgui.util.flatpak import apply_overrides
|
||||||
|
|
||||||
@@ -38,6 +38,8 @@ def test_session_bus(override, config) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_paths(override, xdg_paths, config) -> None:
|
def test_paths(override, xdg_paths, config) -> None:
|
||||||
expect = xdg_paths
|
expect: list[str] = []
|
||||||
|
expect.append(TMP_PATH)
|
||||||
|
expect.extend([*xdg_paths.values()])
|
||||||
paths = config[PATHS_KEY][PATHS_SUBKEY].split(";")
|
paths = config[PATHS_KEY][PATHS_SUBKEY].split(";")
|
||||||
assert all(str(path) in paths for path in expect.values())
|
assert all(str(path) in paths for path in expect)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from dzgui.model.model_factory import FastInsertListStore
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def heterogeneous_list() -> None:
|
||||||
|
return [["a", 0, True], ["b", 1, False]]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mismatched_length_list() -> None:
|
||||||
|
return [["a", 0, True], ["b", 1, False, 0]]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def extended_list(heterogeneous_list) -> None:
|
||||||
|
fs = FastInsertListStore(str, int, bool)
|
||||||
|
fs.extend(heterogeneous_list)
|
||||||
|
return fs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def appended_list(heterogeneous_list) -> None:
|
||||||
|
fs = FastInsertListStore(str, int, bool)
|
||||||
|
for row in heterogeneous_list:
|
||||||
|
fs.append(row)
|
||||||
|
return fs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("target", ["extended_list", "appended_list"])
|
||||||
|
def test_list_parity(heterogeneous_list, request, target) -> None:
|
||||||
|
target = request.getfixturevalue(target)
|
||||||
|
extracted = [[el for el in row] for row in target]
|
||||||
|
assert heterogeneous_list == extracted
|
||||||
|
|
||||||
|
|
||||||
|
def test_type_disparity(heterogeneous_list) -> None:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
fs = FastInsertListStore(str, bool, bool)
|
||||||
|
fs.extend(heterogeneous_list)
|
||||||
|
|
||||||
|
|
||||||
|
def test_length_disparity(mismatched_length_list) -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
fs = FastInsertListStore(str, int, bool)
|
||||||
|
fs.extend(mismatched_length_list)
|
||||||
Reference in new issue
Block a user