mirror of
https://github.com/aclist/dztui.git
synced 2026-11-11 12:30:37 +01:00
Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcc4cb494b | ||
|
|
ff1e150eb1 | ||
|
|
c7ef91f02e | ||
|
|
95bc05eab5 | ||
|
|
89dc6aa8a8 | ||
|
|
9e8e3730c7 | ||
|
|
52c126ff8c | ||
|
|
e5c8f2a69c | ||
|
|
63316a01a9 | ||
|
|
0729273503 | ||
|
|
91d164d666 | ||
|
|
0d7f022c43 | ||
|
|
f76c400697 | ||
|
|
7ae5880890 | ||
|
|
afbced228d | ||
|
|
f5d7bf713d | ||
|
|
9b948271e1 | ||
|
|
7322970c54 | ||
|
|
504a2a724c | ||
|
|
c66511b005 | ||
|
|
c94e4e73db | ||
|
|
8da264686a | ||
|
|
598973104c | ||
|
|
3677b01fdc | ||
|
|
4f6223f90b | ||
|
|
441066a1a0 | ||
|
|
fa1ac8fa31 |
No files matched your search
+6
-5
@@ -2,10 +2,10 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from concurrent.futures import wait
|
from concurrent.futures import ThreadPoolExecutor, wait
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from dzgui.api.steam import get_app_path
|
from dzgui.api.steam import get_app_path
|
||||||
from dzgui.api.servers import get_rules, fqip_to_record
|
from dzgui.api.servers import get_rules, fqip_to_record
|
||||||
@@ -18,8 +18,7 @@ from dzgui.const.constants import (
|
|||||||
|
|
||||||
from dzgui.config.query import lookup
|
from dzgui.config.query import lookup
|
||||||
from dzgui.const.enum import Preferences
|
from dzgui.const.enum import Preferences
|
||||||
|
from dzgui.init.migrate import clean_legacy_versions_file
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
@@ -117,10 +116,12 @@ def remove_stale_signatures(
|
|||||||
config: Path, versions: Path, ids: list[int] | None = None
|
config: Path, versions: Path, ids: list[int] | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
if versions.is_file() is False:
|
if versions.is_file() is False:
|
||||||
logger.warning("Creating new version signatures file")
|
logger.debug("Creating new version signatures file")
|
||||||
versions.touch()
|
versions.touch()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
clean_legacy_versions_file(versions)
|
||||||
|
|
||||||
path = lookup(config, Preferences.DEFAULT)
|
path = lookup(config, Preferences.DEFAULT)
|
||||||
steam_path = Path(path)
|
steam_path = Path(path)
|
||||||
if ids is None:
|
if ids is None:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from typing import Any, Optional, TYPE_CHECKING, Union
|
|||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, REQUEST_TIMEOUT
|
from dzgui.const.constants import APP_NAME, REQUEST_TIMEOUT
|
||||||
from dzgui.const.endpoints import STEAM_SERVERS
|
from dzgui.const.endpoints import STEAM_SERVERS
|
||||||
|
from dzgui.model.proxy_model import ServerRow
|
||||||
from dzgui.util.format import format_exception
|
from dzgui.util.format import format_exception
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ def sanitize(name: str) -> str:
|
|||||||
|
|
||||||
def parse_json(
|
def parse_json(
|
||||||
json: list[dict[str, Any]],
|
json: list[dict[str, Any]],
|
||||||
) -> list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]]:
|
) -> list[ServerRow]:
|
||||||
"""
|
"""
|
||||||
Server metadata is underspecified and server operators
|
Server metadata is underspecified and server operators
|
||||||
tend to insert random garbage in the headers. In case
|
tend to insert random garbage in the headers. In case
|
||||||
@@ -226,7 +227,7 @@ def parse_json(
|
|||||||
mapname = row["map"].lower()
|
mapname = row["map"].lower()
|
||||||
players = row["players"]
|
players = row["players"]
|
||||||
max_players = row["max_players"]
|
max_players = row["max_players"]
|
||||||
raw = (
|
raw = ServerRow(
|
||||||
name,
|
name,
|
||||||
mapname,
|
mapname,
|
||||||
view,
|
view,
|
||||||
|
|||||||
+1
-1
@@ -18,8 +18,8 @@ from dzgui.config.query import lookup
|
|||||||
from dzgui.config.userprefs import UserPrefs
|
from dzgui.config.userprefs import UserPrefs
|
||||||
from dzgui.config.xdg import get_xdg_paths, parse_filepaths
|
from dzgui.config.xdg import get_xdg_paths, parse_filepaths
|
||||||
from dzgui.init.migrate import (
|
from dzgui.init.migrate import (
|
||||||
has_new_config,
|
|
||||||
copy_state_files,
|
copy_state_files,
|
||||||
|
has_new_config,
|
||||||
migrate_cols_file,
|
migrate_cols_file,
|
||||||
)
|
)
|
||||||
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from dzgui.util._json import read_json
|
|||||||
|
|
||||||
from dzgui.const.enum import Preferences
|
from dzgui.const.enum import Preferences
|
||||||
|
|
||||||
|
|
||||||
class ConfigFileError(Exception):
|
class ConfigFileError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -39,4 +40,4 @@ def get_favorites(path: Path) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def enum_to_key(enum: Preferences) -> str:
|
def enum_to_key(enum: Preferences) -> str:
|
||||||
return str(enum.dict["key"])
|
return str(enum.key)
|
||||||
@@ -6,7 +6,8 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.util.ip import Coords
|
from dzgui.util.ip import Coords
|
||||||
|
|
||||||
|
|
||||||
# NOTE: mutable dataclass, 'use_miles' key changes on demand
|
# NOTE: mutable dataclass: 'use_miles' key changes on demand
|
||||||
|
# 'frozen' cannot be True
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class UserPrefs:
|
class UserPrefs:
|
||||||
is_steam_deck: bool
|
is_steam_deck: bool
|
||||||
|
|||||||
+224
-206
@@ -1,7 +1,9 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Self
|
|
||||||
|
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
|
from dzgui.strings import rows
|
||||||
|
|
||||||
|
|
||||||
class Port(Enum):
|
class Port(Enum):
|
||||||
@@ -46,125 +48,61 @@ class FilterMode(Enum):
|
|||||||
TOGGLE_ON = 3
|
TOGGLE_ON = 3
|
||||||
|
|
||||||
|
|
||||||
class EnumWithAttrs(Enum):
|
@dataclass(slots=True, frozen=True)
|
||||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self:
|
class ModButtonMeta:
|
||||||
value = len(cls.__members__) + 1
|
label: str
|
||||||
obj = object.__new__(cls)
|
tooltip: str
|
||||||
obj._value_ = value
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __init__(self, d: dict):
|
|
||||||
self.dict = d
|
|
||||||
|
|
||||||
|
|
||||||
class Preferences(EnumWithAttrs):
|
@dataclass(slots=True, frozen=True)
|
||||||
STEAM = {
|
class ButtonTypeMeta:
|
||||||
"key": "steam_api",
|
label: str
|
||||||
}
|
tooltip: str
|
||||||
CLIENT = {
|
opens: NotebookPage | None
|
||||||
"key": "client",
|
|
||||||
}
|
|
||||||
WINDOW = {
|
|
||||||
"key": "fullscreen",
|
|
||||||
}
|
|
||||||
DIST = {
|
|
||||||
"key": "use_miles",
|
|
||||||
}
|
|
||||||
NAME = {
|
|
||||||
"key": "name",
|
|
||||||
}
|
|
||||||
DEFAULT = {
|
|
||||||
"key": "default_steam_path",
|
|
||||||
}
|
|
||||||
FAV_LBL = {
|
|
||||||
"key": "fav_label",
|
|
||||||
}
|
|
||||||
FAV_SRV = {
|
|
||||||
"key": "fav_server",
|
|
||||||
}
|
|
||||||
IP_LIST = {
|
|
||||||
"key": "ip_list",
|
|
||||||
}
|
|
||||||
START_TAB = {
|
|
||||||
"key": "start_tab",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NotebookPage(EnumWithAttrs):
|
@dataclass(slots=True, frozen=True)
|
||||||
OFFLINE = {"crumbs": strings.crumbs.offline, "statusbar": True}
|
class ConfigKey:
|
||||||
CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": True}
|
key: str
|
||||||
DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False}
|
|
||||||
HELP = {"crumbs": strings.crumbs._help, "statusbar": True}
|
|
||||||
KEYS = {"crumbs": strings.crumbs.keys, "statusbar": True}
|
|
||||||
LOG = {"crumbs": strings.crumbs.log, "statusbar": True}
|
|
||||||
MODS = {"crumbs": strings.crumbs.mods, "statusbar": True}
|
|
||||||
OPTIONS = {"crumbs": strings.crumbs.options, "statusbar": False}
|
|
||||||
SERVERS = {"crumbs": strings.crumbs.servers, "statusbar": True}
|
|
||||||
THANKS = {"crumbs": strings.crumbs.thanks, "statusbar": True}
|
|
||||||
CONNECTION = {"crumbs": "Connect", "statusbar": True}
|
|
||||||
|
|
||||||
|
|
||||||
class RowType(EnumWithAttrs):
|
@dataclass(slots=True, frozen=True)
|
||||||
# TODO: strings
|
class NotebookMeta:
|
||||||
CHANGELOG = {
|
crumbs: str
|
||||||
"label": "View changelog",
|
has_statusbar: bool
|
||||||
"tooltip": "Opens the DZGUI changelog",
|
|
||||||
}
|
|
||||||
SHOW_LOG = {
|
|
||||||
"label": "Show debug log",
|
|
||||||
"tooltip": "Read the DZGUI log generated since startup",
|
|
||||||
}
|
|
||||||
SEPARATOR = {
|
|
||||||
"label": "SEPARATOR",
|
|
||||||
"tooltip": "",
|
|
||||||
}
|
|
||||||
DUMP_LOG = {
|
|
||||||
"label": "Output system info to log file",
|
|
||||||
"tooltip": "Dump diagnostic data for troubleshooting",
|
|
||||||
}
|
|
||||||
DOCS = {
|
|
||||||
"label": "Documentation/help files (GitHub) ⧉",
|
|
||||||
"tooltip": "Open the DZGUI documentation in a browser",
|
|
||||||
}
|
|
||||||
DOCS_FALLBACK = {
|
|
||||||
"label": "Documentation/help files (Codeberg mirror) ⧉",
|
|
||||||
"tooltip": "Open the DZGUI documentation in a browser",
|
|
||||||
}
|
|
||||||
BUGS = {
|
|
||||||
"label": "Report a bug (GitHub) ⧉",
|
|
||||||
"tooltip": "Open the DZGUI issue tracker in a browser",
|
|
||||||
}
|
|
||||||
FORUM = {
|
|
||||||
"label": "DZGUI Subreddit ⧉",
|
|
||||||
"tooltip": "Open the DZGUI discussion forum in a browser",
|
|
||||||
}
|
|
||||||
SPONSOR = {
|
|
||||||
"label": "Sponsor (GitHub) ⧉",
|
|
||||||
"tooltip": "Sponsor development of the DZGUI project",
|
|
||||||
}
|
|
||||||
THANKS = {
|
|
||||||
"label": "Special thanks",
|
|
||||||
"tooltip": "A list of contributors, testers, and sponsors",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: rename to ContextMenuItem
|
@dataclass(slots=True, frozen=True)
|
||||||
class ContextMenu(EnumWithAttrs):
|
class RowMeta:
|
||||||
ADD_NOTE = {"label": strings.add_note}
|
label: str
|
||||||
ADD_SERVER = {"label": strings.add}
|
tooltip: str
|
||||||
CONNECT = {"label": strings.connect}
|
|
||||||
COPY_LOG_CLIPBOARD = {"label": strings.copy_log}
|
|
||||||
COPY_SERVER_IP = {"label": strings.copy_ip}
|
@dataclass(slots=True, frozen=True)
|
||||||
COPY_SERVER_NAME = {"label": strings.copy_name}
|
class ContextMenuMeta:
|
||||||
UNSUB_MOD = {"label": strings.unsub_mod}
|
label: str
|
||||||
VALIDATE_MOD = {"label": strings.validate_mod}
|
|
||||||
OPEN_WORKSHOP = {"label": strings.open_workshop}
|
|
||||||
REFRESH_PLAYERS = {"label": strings.refresh_players}
|
class ContextMenuItem(Enum):
|
||||||
REMOVE_HISTORY = {"label": strings.remove_history}
|
ADD_NOTE = ContextMenuMeta(strings.add_note)
|
||||||
REMOVE_SERVER = {"label": strings.remove}
|
ADD_SERVER = ContextMenuMeta(strings.add)
|
||||||
SET_FAV = {"label": strings.add_fav}
|
CONNECT = ContextMenuMeta(strings.connect)
|
||||||
SHOW_DETAILS = {"label": strings.show_details}
|
COPY_LOG_CLIPBOARD = ContextMenuMeta(strings.copy_log)
|
||||||
SHOW_MODS = {"label": strings.show_mods}
|
COPY_SERVER_IP = ContextMenuMeta(strings.copy_ip)
|
||||||
|
COPY_SERVER_NAME = ContextMenuMeta(strings.copy_name)
|
||||||
|
UNSUB_MOD = ContextMenuMeta(strings.unsub_mod)
|
||||||
|
VALIDATE_MOD = ContextMenuMeta(strings.validate_mod)
|
||||||
|
OPEN_WORKSHOP = ContextMenuMeta(strings.open_workshop)
|
||||||
|
REFRESH_PLAYERS = ContextMenuMeta(strings.refresh_players)
|
||||||
|
REMOVE_HISTORY = ContextMenuMeta(strings.remove_history)
|
||||||
|
REMOVE_SERVER = ContextMenuMeta(strings.remove)
|
||||||
|
SET_FAV = ContextMenuMeta(strings.add_fav)
|
||||||
|
SHOW_DETAILS = ContextMenuMeta(strings.show_details)
|
||||||
|
SHOW_MODS = ContextMenuMeta(strings.show_mods)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
return self.value.label
|
||||||
|
|
||||||
|
|
||||||
class ContextMenuGroup(Enum):
|
class ContextMenuGroup(Enum):
|
||||||
@@ -172,113 +110,193 @@ class ContextMenuGroup(Enum):
|
|||||||
Groupings of context menu items
|
Groupings of context menu items
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SERVER_MOD = (ContextMenu.OPEN_WORKSHOP,)
|
SERVER_MOD = (ContextMenuItem.OPEN_WORKSHOP,)
|
||||||
MOD = (ContextMenu.OPEN_WORKSHOP, ContextMenu.UNSUB_MOD, ContextMenu.VALIDATE_MOD)
|
MOD = (
|
||||||
|
ContextMenuItem.OPEN_WORKSHOP,
|
||||||
|
ContextMenuItem.UNSUB_MOD,
|
||||||
|
ContextMenuItem.VALIDATE_MOD,
|
||||||
|
)
|
||||||
MOD_OFFLINE = (None,)
|
MOD_OFFLINE = (None,)
|
||||||
LOG = (ContextMenu.COPY_LOG_CLIPBOARD,)
|
LOG = (ContextMenuItem.COPY_LOG_CLIPBOARD,)
|
||||||
SERVER_BROWSER = (
|
SERVER_BROWSER = (
|
||||||
ContextMenu.CONNECT,
|
ContextMenuItem.CONNECT,
|
||||||
ContextMenu.ADD_SERVER,
|
ContextMenuItem.ADD_SERVER,
|
||||||
ContextMenu.SET_FAV,
|
ContextMenuItem.SET_FAV,
|
||||||
ContextMenu.COPY_SERVER_NAME,
|
ContextMenuItem.COPY_SERVER_NAME,
|
||||||
ContextMenu.COPY_SERVER_IP,
|
ContextMenuItem.COPY_SERVER_IP,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenuItem.ADD_NOTE,
|
||||||
ContextMenu.SHOW_MODS,
|
ContextMenuItem.SHOW_MODS,
|
||||||
ContextMenu.SHOW_DETAILS,
|
ContextMenuItem.SHOW_DETAILS,
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenuItem.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
SCAN_LAN = (
|
SCAN_LAN = (
|
||||||
ContextMenu.CONNECT,
|
ContextMenuItem.CONNECT,
|
||||||
ContextMenu.SET_FAV,
|
ContextMenuItem.SET_FAV,
|
||||||
ContextMenu.COPY_SERVER_NAME,
|
ContextMenuItem.COPY_SERVER_NAME,
|
||||||
ContextMenu.COPY_SERVER_IP,
|
ContextMenuItem.COPY_SERVER_IP,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenuItem.ADD_NOTE,
|
||||||
ContextMenu.SHOW_MODS,
|
ContextMenuItem.SHOW_MODS,
|
||||||
ContextMenu.SHOW_DETAILS,
|
ContextMenuItem.SHOW_DETAILS,
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenuItem.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
SAVED = (
|
SAVED = (
|
||||||
ContextMenu.CONNECT,
|
ContextMenuItem.CONNECT,
|
||||||
ContextMenu.SET_FAV,
|
ContextMenuItem.SET_FAV,
|
||||||
ContextMenu.REMOVE_SERVER,
|
ContextMenuItem.REMOVE_SERVER,
|
||||||
ContextMenu.COPY_SERVER_NAME,
|
ContextMenuItem.COPY_SERVER_NAME,
|
||||||
ContextMenu.COPY_SERVER_IP,
|
ContextMenuItem.COPY_SERVER_IP,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenuItem.ADD_NOTE,
|
||||||
ContextMenu.SHOW_MODS,
|
ContextMenuItem.SHOW_MODS,
|
||||||
ContextMenu.SHOW_DETAILS,
|
ContextMenuItem.SHOW_DETAILS,
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenuItem.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
RECENT = (
|
RECENT = (
|
||||||
ContextMenu.CONNECT,
|
ContextMenuItem.CONNECT,
|
||||||
ContextMenu.ADD_SERVER,
|
ContextMenuItem.ADD_SERVER,
|
||||||
ContextMenu.SET_FAV,
|
ContextMenuItem.SET_FAV,
|
||||||
ContextMenu.REMOVE_HISTORY,
|
ContextMenuItem.REMOVE_HISTORY,
|
||||||
ContextMenu.COPY_SERVER_NAME,
|
ContextMenuItem.COPY_SERVER_NAME,
|
||||||
ContextMenu.COPY_SERVER_IP,
|
ContextMenuItem.COPY_SERVER_IP,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenuItem.ADD_NOTE,
|
||||||
ContextMenu.SHOW_MODS,
|
ContextMenuItem.SHOW_MODS,
|
||||||
ContextMenu.SHOW_DETAILS,
|
ContextMenuItem.SHOW_DETAILS,
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenuItem.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModButton(EnumWithAttrs):
|
class Preferences(Enum):
|
||||||
SELECT_ALL = {
|
STEAM = ConfigKey("steam_api")
|
||||||
"label": strings.mod_panel.select_all,
|
CLIENT = ConfigKey("client")
|
||||||
"tooltip": strings.mod_panel.select_all_tooltip,
|
WINDOW = ConfigKey("fullscreen")
|
||||||
}
|
DIST = ConfigKey("use_miles")
|
||||||
UNSELECT_ALL = {
|
NAME = ConfigKey("name")
|
||||||
"label": strings.mod_panel.unselect_all,
|
DEFAULT = ConfigKey("default_steam_path")
|
||||||
"tooltip": strings.mod_panel.unselect_all_tooltip,
|
FAV_LBL = ConfigKey("fav_label")
|
||||||
}
|
FAV_SRV = ConfigKey("fav_server")
|
||||||
HIGHLIGHT_STALE = {
|
IP_LIST = ConfigKey("ip_list")
|
||||||
"label": strings.mod_panel.highlight_stale,
|
START_TAB = ConfigKey("start_tab")
|
||||||
"tooltip": strings.mod_panel.highlight_stale_tooltip,
|
|
||||||
}
|
@property
|
||||||
UNHIGHLIGHT_STALE = {
|
def key(self) -> str:
|
||||||
"label": strings.mod_panel.unhighlight_stale,
|
return self.value.key
|
||||||
"tooltip": strings.mod_panel.unhighlight_stale_tooltip,
|
|
||||||
}
|
|
||||||
UNSUB_SELECTED = {
|
|
||||||
"label": strings.mod_panel.unsub_selected,
|
|
||||||
"tooltip": strings.mod_panel.unsub_selected_tooltip,
|
|
||||||
}
|
|
||||||
SELECT_STALE = {
|
|
||||||
"label": strings.mod_panel.select_stale,
|
|
||||||
"tooltip": strings.mod_panel.select_stale_tooltip,
|
|
||||||
}
|
|
||||||
VALIDATE = {
|
|
||||||
"label": strings.mod_panel.validate,
|
|
||||||
"tooltip": strings.mod_panel.validate_tooltip,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ButtonType(EnumWithAttrs):
|
class NotebookPage(Enum):
|
||||||
SERVERS = {
|
OFFLINE = NotebookMeta(strings.crumbs.offline, True)
|
||||||
"label": strings.buttons.servers_label,
|
CHANGELOG = NotebookMeta(strings.crumbs.changelog, True)
|
||||||
"tooltip": strings.buttons.servers_tooltip,
|
DEVELOPERS = NotebookMeta(strings.crumbs.developers, False)
|
||||||
"opens": NotebookPage.SERVERS,
|
HELP = NotebookMeta(strings.crumbs._help, True)
|
||||||
}
|
KEYS = NotebookMeta(strings.crumbs.keys, True)
|
||||||
MODS = {
|
LOG = NotebookMeta(strings.crumbs.log, True)
|
||||||
"label": strings.buttons.mods_label,
|
MODS = NotebookMeta(strings.crumbs.mods, True)
|
||||||
"tooltip": strings.buttons.mods_tooltip,
|
OPTIONS = NotebookMeta(strings.crumbs.options, False)
|
||||||
"opens": NotebookPage.MODS,
|
SERVERS = NotebookMeta(strings.crumbs.servers, True)
|
||||||
}
|
THANKS = NotebookMeta(strings.crumbs.thanks, True)
|
||||||
OPTIONS = {
|
CONNECTION = NotebookMeta(strings.crumbs.connect, True)
|
||||||
"label": strings.buttons.options_label,
|
|
||||||
"tooltip": strings.buttons.options_tooltip,
|
@property
|
||||||
"opens": NotebookPage.OPTIONS,
|
def crumbs(self) -> str:
|
||||||
}
|
return self.value.crumbs
|
||||||
HELP = {
|
|
||||||
"label": strings.buttons.help_label,
|
@property
|
||||||
"tooltip": strings.buttons.help_tooltip,
|
def has_statusbar(self) -> bool:
|
||||||
"opens": NotebookPage.HELP,
|
return self.value.has_statusbar
|
||||||
}
|
|
||||||
EXIT = {
|
|
||||||
"label": strings.buttons.exit_label,
|
class RowType(Enum):
|
||||||
"tooltip": strings.buttons.exit_tooltip,
|
CHANGELOG = RowMeta(rows.changelog_label, rows.changelog_status)
|
||||||
"opens": None,
|
SHOW_LOG = RowMeta(rows.log_label, rows.log_status)
|
||||||
}
|
SEPARATOR = RowMeta(rows.separator_label, rows.separator_status)
|
||||||
|
DUMP_LOG = RowMeta(rows.system_log_label, rows.system_log_status)
|
||||||
|
DOCS = RowMeta(rows.docs_label, rows.docs_status)
|
||||||
|
DOCS_FALLBACK = RowMeta(rows.docs_fallback_label, rows.docs_fallback_status)
|
||||||
|
BUGS = RowMeta(rows.bugs_label, rows.bugs_status)
|
||||||
|
FORUM = RowMeta(rows.forum_label, rows.forum_status)
|
||||||
|
SPONSOR = RowMeta(rows.sponsor_label, rows.sponsor_status)
|
||||||
|
THANKS = RowMeta(rows.thanks_label, rows.thanks_status)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
return self.value.label
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tooltip(self) -> str:
|
||||||
|
return self.value.tooltip
|
||||||
|
|
||||||
|
|
||||||
|
class ModButton(Enum):
|
||||||
|
SELECT_ALL = ModButtonMeta(
|
||||||
|
strings.mod_panel.select_all, strings.mod_panel.select_all_tooltip
|
||||||
|
)
|
||||||
|
UNSELECT_ALL = ModButtonMeta(
|
||||||
|
strings.mod_panel.unselect_all, strings.mod_panel.unselect_all_tooltip
|
||||||
|
)
|
||||||
|
HIGHLIGHT_STALE = ModButtonMeta(
|
||||||
|
strings.mod_panel.highlight_stale, strings.mod_panel.highlight_stale_tooltip
|
||||||
|
)
|
||||||
|
UNHIGHLIGHT_STALE = ModButtonMeta(
|
||||||
|
strings.mod_panel.unhighlight_stale, strings.mod_panel.unhighlight_stale_tooltip
|
||||||
|
)
|
||||||
|
UNSUB_SELECTED = ModButtonMeta(
|
||||||
|
strings.mod_panel.unsub_selected,
|
||||||
|
strings.mod_panel.unsub_selected_tooltip,
|
||||||
|
)
|
||||||
|
SELECT_STALE = ModButtonMeta(
|
||||||
|
strings.mod_panel.select_stale,
|
||||||
|
strings.mod_panel.select_stale_tooltip,
|
||||||
|
)
|
||||||
|
VALIDATE = ModButtonMeta(
|
||||||
|
strings.mod_panel.validate,
|
||||||
|
strings.mod_panel.validate_tooltip,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
return self.value.label
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tooltip(self) -> str:
|
||||||
|
return self.value.tooltip
|
||||||
|
|
||||||
|
|
||||||
|
class ButtonType(Enum):
|
||||||
|
SERVERS = ButtonTypeMeta(
|
||||||
|
strings.buttons.servers_label,
|
||||||
|
strings.buttons.servers_tooltip,
|
||||||
|
NotebookPage.SERVERS,
|
||||||
|
)
|
||||||
|
MODS = ButtonTypeMeta(
|
||||||
|
strings.buttons.mods_label,
|
||||||
|
strings.buttons.mods_tooltip,
|
||||||
|
NotebookPage.MODS,
|
||||||
|
)
|
||||||
|
OPTIONS = ButtonTypeMeta(
|
||||||
|
strings.buttons.options_label,
|
||||||
|
strings.buttons.options_tooltip,
|
||||||
|
NotebookPage.OPTIONS,
|
||||||
|
)
|
||||||
|
HELP = ButtonTypeMeta(
|
||||||
|
strings.buttons.help_label,
|
||||||
|
strings.buttons.help_tooltip,
|
||||||
|
NotebookPage.HELP,
|
||||||
|
)
|
||||||
|
EXIT = ButtonTypeMeta(
|
||||||
|
strings.buttons.exit_label,
|
||||||
|
strings.buttons.exit_tooltip,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
return self.value.label
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tooltip(self) -> str:
|
||||||
|
return self.value.tooltip
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opens(self) -> NotebookPage | None:
|
||||||
|
return self.value.opens
|
||||||
|
|
||||||
|
|
||||||
HELP_MENU_ROWS = (
|
HELP_MENU_ROWS = (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import dzgui.util._json as JSON # noqa
|
|||||||
|
|
||||||
from dzgui.const.enum import (
|
from dzgui.const.enum import (
|
||||||
ButtonType,
|
ButtonType,
|
||||||
ContextMenu,
|
ContextMenuItem,
|
||||||
FilterMode,
|
FilterMode,
|
||||||
NotebookPage,
|
NotebookPage,
|
||||||
Preferences,
|
Preferences,
|
||||||
@@ -196,7 +196,9 @@ class Controller(GObject.GObject):
|
|||||||
def show_developers_page(self) -> None:
|
def show_developers_page(self) -> None:
|
||||||
self.open_page(NotebookPage.DEVELOPERS)
|
self.open_page(NotebookPage.DEVELOPERS)
|
||||||
|
|
||||||
def open_page(self, page: NotebookPage) -> None:
|
def open_page(self, page: NotebookPage | None) -> None:
|
||||||
|
if page is None:
|
||||||
|
return
|
||||||
self.mediator.grid.notebook.set_page_by_enum(page)
|
self.mediator.grid.notebook.set_page_by_enum(page)
|
||||||
|
|
||||||
def open_page_by_button(self, button: "ContextualButton") -> None:
|
def open_page_by_button(self, button: "ContextualButton") -> None:
|
||||||
@@ -262,7 +264,7 @@ class Controller(GObject.GObject):
|
|||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
return
|
return
|
||||||
|
|
||||||
def menu_action(self, action: ContextMenu, tree: "ServerTreeView") -> None:
|
def menu_action(self, action: ContextMenuItem, tree: "ServerTreeView") -> None:
|
||||||
context_man = ContextMenuManager(tree, self)
|
context_man = ContextMenuManager(tree, self)
|
||||||
context_man.process(action)
|
context_man.process(action)
|
||||||
|
|
||||||
@@ -370,7 +372,6 @@ class Controller(GObject.GObject):
|
|||||||
return filter_man.get_filters()
|
return filter_man.get_filters()
|
||||||
|
|
||||||
# FIXME: wipe maps store when changing tabs if model is none
|
# FIXME: wipe maps store when changing tabs if model is none
|
||||||
|
|
||||||
def has_server_model(self) -> bool:
|
def has_server_model(self) -> bool:
|
||||||
treeview = self.get_active_treeview()
|
treeview = self.get_active_treeview()
|
||||||
proxy_man = treeview.get_proxy_man()
|
proxy_man = treeview.get_proxy_man()
|
||||||
|
|||||||
+22
-1
@@ -1,12 +1,21 @@
|
|||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from dzgui.const.constants import LEGACY_CONFIG_PATH, LEGACY_COLS_PATH, LEGACY_IPS_PATH
|
from dzgui.const.constants import (
|
||||||
|
APP_NAME,
|
||||||
|
LEGACY_CONFIG_PATH,
|
||||||
|
LEGACY_COLS_PATH,
|
||||||
|
LEGACY_IPS_PATH,
|
||||||
|
)
|
||||||
from dzgui.config.convert import rc2json
|
from dzgui.config.convert import rc2json
|
||||||
from dzgui.util._json import read_json, write_json
|
from dzgui.util._json import read_json, write_json
|
||||||
|
|
||||||
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
def migrate_legacy_conf(config: Path) -> Any:
|
def migrate_legacy_conf(config: Path) -> Any:
|
||||||
old_conf = Path.home() / LEGACY_CONFIG_PATH
|
old_conf = Path.home() / LEGACY_CONFIG_PATH
|
||||||
@@ -70,3 +79,15 @@ def copy_ipdb(ips_path: Path) -> None:
|
|||||||
return
|
return
|
||||||
# TODO: ensure that month file is copied
|
# TODO: ensure that month file is copied
|
||||||
shutil.copy(legacy, ips_path)
|
shutil.copy(legacy, ips_path)
|
||||||
|
|
||||||
|
|
||||||
|
def clean_legacy_versions_file(versions: Path) -> None:
|
||||||
|
# NOTE: handling for #523
|
||||||
|
try:
|
||||||
|
pat = r"^(\d+),(\d+)$"
|
||||||
|
lines = versions.read_text().splitlines()
|
||||||
|
clean = [line for line in lines if re.match(pat, line)]
|
||||||
|
concat = "\n".join(clean)
|
||||||
|
versions.write_text(concat)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
@@ -46,6 +46,15 @@ class ConfigManager:
|
|||||||
ips.append(record)
|
ips.append(record)
|
||||||
self.update_config(Preferences.IP_LIST, ips)
|
self.update_config(Preferences.IP_LIST, ips)
|
||||||
|
|
||||||
|
def remove_from_history_file(self, fqip: str) -> None:
|
||||||
|
try:
|
||||||
|
history = self.prefs.paths.history
|
||||||
|
ips = [line.rstrip() for line in history.read_text().splitlines()]
|
||||||
|
ips.remove(fqip)
|
||||||
|
history.write_text("\n".join(ips))
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
|
||||||
def update_history_file(self, fqip: str) -> None:
|
def update_history_file(self, fqip: str) -> None:
|
||||||
try:
|
try:
|
||||||
with open(self.prefs.paths.history, "r") as f:
|
with open(self.prefs.paths.history, "r") as f:
|
||||||
@@ -124,7 +133,7 @@ class ConfigManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def enum_to_key(self, enum: Preferences) -> str:
|
def enum_to_key(self, enum: Preferences) -> str:
|
||||||
return str(enum.dict["key"])
|
return str(enum.key)
|
||||||
|
|
||||||
def toggle_config(self, key: Preferences) -> None:
|
def toggle_config(self, key: Preferences) -> None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import logging
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.const.enum import ContextMenu, Preferences
|
from dzgui.const.enum import ContextMenuItem, Preferences
|
||||||
from dzgui.util.clip import copy_clipboard
|
from dzgui.util.clip import copy_clipboard
|
||||||
from dzgui.util.open_links import open_workshop_page
|
from dzgui.util.open_links import open_workshop_page
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class ContextMenuManager:
|
|||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.treeview = treeview
|
self.treeview = treeview
|
||||||
|
|
||||||
def process(self, action: ContextMenu) -> None:
|
def process(self, action: ContextMenuItem) -> None:
|
||||||
if isinstance(self.treeview, ServerTreeView):
|
if isinstance(self.treeview, ServerTreeView):
|
||||||
record = self.treeview.get_record()
|
record = self.treeview.get_record()
|
||||||
if record is None:
|
if record is None:
|
||||||
@@ -42,14 +42,14 @@ class ContextMenuManager:
|
|||||||
|
|
||||||
match action:
|
match action:
|
||||||
# UNTHREADED
|
# UNTHREADED
|
||||||
case ContextMenu.ADD_NOTE:
|
case ContextMenuItem.ADD_NOTE:
|
||||||
dialog = NoteDialog(self.controller)
|
dialog = NoteDialog(self.controller)
|
||||||
dialog.run() # type: ignore[no-untyped-call]
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
case ContextMenu.COPY_SERVER_IP:
|
case ContextMenuItem.COPY_SERVER_IP:
|
||||||
self.copy_server_ip()
|
self.copy_server_ip()
|
||||||
case ContextMenu.COPY_SERVER_NAME:
|
case ContextMenuItem.COPY_SERVER_NAME:
|
||||||
self.copy_server_name()
|
self.copy_server_name()
|
||||||
case ContextMenu.SET_FAV:
|
case ContextMenuItem.SET_FAV:
|
||||||
name = self.treeview.get_value_at_index(0)
|
name = self.treeview.get_value_at_index(0)
|
||||||
# NOTE: fully qualified ip
|
# NOTE: fully qualified ip
|
||||||
fqip = self.treeview.get_record_string()
|
fqip = self.treeview.get_record_string()
|
||||||
@@ -58,40 +58,40 @@ class ContextMenuManager:
|
|||||||
self.controller.set_fav(name, fqip, simple)
|
self.controller.set_fav(name, fqip, simple)
|
||||||
|
|
||||||
# THREADED
|
# THREADED
|
||||||
case ContextMenu.ADD_SERVER:
|
case ContextMenuItem.ADD_SERVER:
|
||||||
self.controller.add_by_record(record)
|
self.controller.add_by_record(record)
|
||||||
case ContextMenu.CONNECT:
|
case ContextMenuItem.CONNECT:
|
||||||
self.controller.connect_by_record(record)
|
self.controller.connect_by_record(record)
|
||||||
case ContextMenu.REFRESH_PLAYERS:
|
case ContextMenuItem.REFRESH_PLAYERS:
|
||||||
self.controller.refresh_players(record)
|
self.controller.refresh_players(record)
|
||||||
case ContextMenu.REMOVE_HISTORY:
|
case ContextMenuItem.REMOVE_HISTORY:
|
||||||
self.controller.remove_from_history(record)
|
self.controller.remove_from_history(record)
|
||||||
case ContextMenu.REMOVE_SERVER:
|
case ContextMenuItem.REMOVE_SERVER:
|
||||||
self.controller.remove_by_record(record)
|
self.controller.remove_by_record(record)
|
||||||
case ContextMenu.SHOW_DETAILS:
|
case ContextMenuItem.SHOW_DETAILS:
|
||||||
self.controller.get_details(record)
|
self.controller.get_details(record)
|
||||||
case ContextMenu.SHOW_MODS:
|
case ContextMenuItem.SHOW_MODS:
|
||||||
self.controller.get_modlist(record)
|
self.controller.get_modlist(record)
|
||||||
|
|
||||||
if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
|
if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
|
||||||
match action:
|
match action:
|
||||||
case ContextMenu.OPEN_WORKSHOP:
|
case ContextMenuItem.OPEN_WORKSHOP:
|
||||||
self.open_mod_page()
|
self.open_mod_page()
|
||||||
case ContextMenu.UNSUB_MOD:
|
case ContextMenuItem.UNSUB_MOD:
|
||||||
self.controller.unsub_mods(self.treeview)
|
self.controller.unsub_mods(self.treeview)
|
||||||
case ContextMenu.VALIDATE_MOD:
|
case ContextMenuItem.VALIDATE_MOD:
|
||||||
self.controller.validate_mods(self.treeview)
|
self.controller.validate_mods(self.treeview)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if isinstance(self.treeview, LogTreeView):
|
if isinstance(self.treeview, LogTreeView):
|
||||||
match action:
|
match action:
|
||||||
case ContextMenu.COPY_LOG_CLIPBOARD:
|
case ContextMenuItem.COPY_LOG_CLIPBOARD:
|
||||||
self.copy_log()
|
self.copy_log()
|
||||||
|
|
||||||
if isinstance(self.treeview, ServerModTreeView):
|
if isinstance(self.treeview, ServerModTreeView):
|
||||||
match action:
|
match action:
|
||||||
case ContextMenu.OPEN_WORKSHOP:
|
case ContextMenuItem.OPEN_WORKSHOP:
|
||||||
self.open_mod_page()
|
self.open_mod_page()
|
||||||
|
|
||||||
def copy_server_ip(self) -> None:
|
def copy_server_ip(self) -> None:
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class ModelFactory:
|
|||||||
|
|
||||||
def make_help_store(self) -> FastInsertListStore:
|
def make_help_store(self) -> FastInsertListStore:
|
||||||
store = self.new_model_from_class(MenuCols)
|
store = self.new_model_from_class(MenuCols)
|
||||||
rows = [[row.dict["label"], row] for row in HELP_MENU_ROWS]
|
rows = [[row.label, row] for row in HELP_MENU_ROWS]
|
||||||
store.extend(rows)
|
store.extend(rows)
|
||||||
return store
|
return store
|
||||||
|
|
||||||
|
|||||||
+148
-75
@@ -1,7 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, fields
|
||||||
from typing import Any, TYPE_CHECKING, Union
|
from operator import attrgetter
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.enum import FilterMode
|
from dzgui.const.enum import FilterMode
|
||||||
from dzgui.model.model_factory import ModelFactory
|
from dzgui.model.model_factory import ModelFactory
|
||||||
@@ -33,6 +34,100 @@ class ServerRow:
|
|||||||
provider: str
|
provider: str
|
||||||
modded: bool
|
modded: bool
|
||||||
|
|
||||||
|
# NOTE: construct getter once for fast lookup in lieu of field introspection
|
||||||
|
_to_tuple = attrgetter(
|
||||||
|
"name",
|
||||||
|
"mapname",
|
||||||
|
"view",
|
||||||
|
"time",
|
||||||
|
"players",
|
||||||
|
"max_players",
|
||||||
|
"queue",
|
||||||
|
"ip",
|
||||||
|
"qport",
|
||||||
|
"ping",
|
||||||
|
"provider",
|
||||||
|
"modded",
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def marshal_rows(
|
||||||
|
cls, rows: list["ServerRow"]
|
||||||
|
) -> list[tuple[str, str, str, str, int, int, int, str, int, str, str, bool]]:
|
||||||
|
"""Marshals data before passing into FastInsertListStore's extend() method"""
|
||||||
|
getter = cls._to_tuple
|
||||||
|
return [getter(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
class FiltrationManager:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
|
||||||
|
self.filter_map = {
|
||||||
|
strings.filter_3pp: self.filter_3pp,
|
||||||
|
strings.filter_1pp: self.filter_1pp,
|
||||||
|
strings.filter_official: self.filter_official,
|
||||||
|
strings.filter_unofficial: self.filter_unofficial,
|
||||||
|
strings.filter_empty: self.filter_empty,
|
||||||
|
strings.filter_full: self.filter_full,
|
||||||
|
strings.filter_duplicate: self.filter_duplicate,
|
||||||
|
strings.filter_day: self.filter_day,
|
||||||
|
strings.filter_night: self.filter_night,
|
||||||
|
strings.filter_nonascii: self.filter_nonascii,
|
||||||
|
strings.filter_lowpop: self.filter_lowpop,
|
||||||
|
strings.filter_modded: self.filter_modded,
|
||||||
|
strings.filter_unmodded: self.filter_unmodded,
|
||||||
|
}
|
||||||
|
|
||||||
|
def filter_3pp(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.view != strings.filter_3pp]
|
||||||
|
|
||||||
|
def filter_1pp(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.view != strings.filter_1pp]
|
||||||
|
|
||||||
|
def filter_official(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.provider != strings.filter_official]
|
||||||
|
|
||||||
|
def filter_unofficial(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.provider != strings.filter_unofficial]
|
||||||
|
|
||||||
|
def filter_empty(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.players != 0]
|
||||||
|
|
||||||
|
def filter_full(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.players != row.max_players]
|
||||||
|
|
||||||
|
def filter_duplicate(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
seen = set()
|
||||||
|
final = []
|
||||||
|
for row in rows:
|
||||||
|
if row.name not in seen:
|
||||||
|
seen.add(row.name)
|
||||||
|
final.append(row)
|
||||||
|
rows = final
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def filter_day(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if not re.match(DAY_REG, row.time)]
|
||||||
|
|
||||||
|
def filter_night(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if not re.match(NIGHT_REG, row.time)]
|
||||||
|
|
||||||
|
def filter_nonascii(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.name.isascii()]
|
||||||
|
|
||||||
|
def filter_lowpop(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if (row.players / row.max_players * 100) > 30]
|
||||||
|
|
||||||
|
def filter_modded(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if not row.modded]
|
||||||
|
|
||||||
|
def filter_unmodded(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.modded]
|
||||||
|
|
||||||
|
def filter(self, filter_type: str, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
func = self.filter_map[filter_type]
|
||||||
|
return func(rows)
|
||||||
|
|
||||||
|
|
||||||
class ProxyModelManager:
|
class ProxyModelManager:
|
||||||
"""
|
"""
|
||||||
@@ -49,19 +144,15 @@ class ProxyModelManager:
|
|||||||
def __init__(self, filter_man: "FilterManager") -> None:
|
def __init__(self, filter_man: "FilterManager") -> None:
|
||||||
self.filter_cache: dict[
|
self.filter_cache: dict[
|
||||||
tuple[str, ...],
|
tuple[str, ...],
|
||||||
list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]],
|
list[ServerRow],
|
||||||
] = {}
|
] = {}
|
||||||
|
|
||||||
self.proxy_model: "FastInsertListStore"
|
self.proxy_model: "FastInsertListStore"
|
||||||
self.filter_man = filter_man
|
self.filter_man = filter_man
|
||||||
|
self.filtration_manager = FiltrationManager()
|
||||||
|
|
||||||
self.control_model: (
|
self.control_model: list[ServerRow] | None = None
|
||||||
list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]]
|
self.filtered: list[ServerRow] = []
|
||||||
| None
|
|
||||||
) = None
|
|
||||||
self.filtered: list[
|
|
||||||
tuple[str, str, str, str, int, int, int, str, int, int, str, bool]
|
|
||||||
] = []
|
|
||||||
self.success = True
|
self.success = True
|
||||||
|
|
||||||
def has_control_model(self) -> bool:
|
def has_control_model(self) -> bool:
|
||||||
@@ -69,9 +160,7 @@ class ProxyModelManager:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def append_row_to_control(
|
def append_row_to_control(self, row: ServerRow) -> None:
|
||||||
self, row: tuple[str, str, str, str, int, int, int, str, int, int, str, bool]
|
|
||||||
) -> None:
|
|
||||||
if self.control_model is None:
|
if self.control_model is None:
|
||||||
raise AttributeError("Trying to add rows to a non-existent model")
|
raise AttributeError("Trying to add rows to a non-existent model")
|
||||||
self.control_model.append(row)
|
self.control_model.append(row)
|
||||||
@@ -79,21 +168,24 @@ class ProxyModelManager:
|
|||||||
|
|
||||||
def update_playercount(self, playercount: "NewPlayerCount") -> None:
|
def update_playercount(self, playercount: "NewPlayerCount") -> None:
|
||||||
treeiter = playercount.treeiter
|
treeiter = playercount.treeiter
|
||||||
self.proxy_model[treeiter][4] = playercount.players
|
field = [f.name for f in fields(ServerRow)]
|
||||||
self.proxy_model[treeiter][6] = playercount.queue
|
index_players = field.index("players")
|
||||||
|
index_queue = field.index("queue")
|
||||||
|
self.proxy_model[treeiter][index_players] = playercount.players
|
||||||
|
self.proxy_model[treeiter][index_queue] = playercount.queue
|
||||||
|
|
||||||
def append_row_to_history(
|
def append_row_to_history(
|
||||||
self,
|
self,
|
||||||
history: tuple[str, str, str, str, int, int, int, str, int, int, str, bool],
|
history: ServerRow,
|
||||||
) -> None:
|
) -> None:
|
||||||
addr = history[7]
|
addr = history.ip
|
||||||
qport = history[8]
|
qport = history.qport
|
||||||
|
|
||||||
if self.control_model is None:
|
if self.control_model is None:
|
||||||
raise AttributeError("Trying to append row to empty model")
|
raise AttributeError("Trying to append row to empty model")
|
||||||
found = False
|
found = False
|
||||||
for i, row in enumerate(self.control_model):
|
for i, row in enumerate(self.control_model):
|
||||||
if addr == row[7] and qport == row[8]:
|
if addr == row.ip and qport == row.qport:
|
||||||
item = self.control_model.pop(i)
|
item = self.control_model.pop(i)
|
||||||
self.control_model.append(item)
|
self.control_model.append(item)
|
||||||
found = True
|
found = True
|
||||||
@@ -102,6 +194,7 @@ class ProxyModelManager:
|
|||||||
if found is False:
|
if found is False:
|
||||||
self.control_model.append(history)
|
self.control_model.append(history)
|
||||||
|
|
||||||
|
# NOTE: truncate history to 10 records max
|
||||||
if len(self.control_model) == 11:
|
if len(self.control_model) == 11:
|
||||||
del self.control_model[0]
|
del self.control_model[0]
|
||||||
|
|
||||||
@@ -113,7 +206,7 @@ class ProxyModelManager:
|
|||||||
addr = f"{record.ip}:{record.gameport}"
|
addr = f"{record.ip}:{record.gameport}"
|
||||||
qport = record.qport
|
qport = record.qport
|
||||||
for row in self.control_model:
|
for row in self.control_model:
|
||||||
if addr == row[7] and qport == row[8]:
|
if addr == row.ip and qport == row.qport:
|
||||||
self.control_model.remove(row)
|
self.control_model.remove(row)
|
||||||
break
|
break
|
||||||
self.wipe_cache()
|
self.wipe_cache()
|
||||||
@@ -122,19 +215,22 @@ class ProxyModelManager:
|
|||||||
def clear_proxy_model(self) -> None:
|
def clear_proxy_model(self) -> None:
|
||||||
self.proxy_model.clear()
|
self.proxy_model.clear()
|
||||||
|
|
||||||
def get_proxy_model(self) -> Union["FastInsertListStore", None]:
|
def get_proxy_model(self) -> "FastInsertListStore | None":
|
||||||
if hasattr(self, "proxy_model"):
|
if hasattr(self, "proxy_model"):
|
||||||
return self.proxy_model
|
return self.proxy_model
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def filter(
|
def filter(
|
||||||
self, mode: FilterMode, skip_cache: bool = False
|
self, mode: FilterMode, skip_cache: bool = False
|
||||||
) -> Union["FastInsertListStore", None]:
|
) -> "FastInsertListStore | None":
|
||||||
"""
|
"""
|
||||||
Native Gtk.TreeView.refilter() method was not performant enough
|
Native Gtk.TreeView.refilter() method was not performant enough
|
||||||
when running in the main loop with 40k+ records
|
when running in the main loop with 40k+ records
|
||||||
|
|
||||||
skip_cache: used when updating a record on Saved Servers/History and forcing a refilter
|
skip_cache: used when updating a record on Saved Servers/History and forcing a refilter
|
||||||
|
|
||||||
|
NOTE: data must be marshalled from ServerRow format before constructing list stores
|
||||||
|
The "clone" is the proxy model (list of tuples) actually fed into Gtk.TreeView
|
||||||
"""
|
"""
|
||||||
# TODO: filter cache: return a dataclass object with clearly enumerated map, keyword, and filter values
|
# TODO: filter cache: return a dataclass object with clearly enumerated map, keyword, and filter values
|
||||||
# instead of just a serial list of strings
|
# instead of just a serial list of strings
|
||||||
@@ -143,8 +239,9 @@ class ProxyModelManager:
|
|||||||
if skip_cache is False:
|
if skip_cache is False:
|
||||||
if filters in self.filter_cache:
|
if filters in self.filter_cache:
|
||||||
cached_rows = self.filter_cache[filters]
|
cached_rows = self.filter_cache[filters]
|
||||||
|
marshalled_rows = ServerRow.marshal_rows(cached_rows)
|
||||||
clone = ModelFactory().make_server_store()
|
clone = ModelFactory().make_server_store()
|
||||||
clone.extend(cached_rows)
|
clone.extend(marshalled_rows)
|
||||||
self.set_proxy_model(clone)
|
self.set_proxy_model(clone)
|
||||||
self.set_filtered(cached_rows)
|
self.set_filtered(cached_rows)
|
||||||
return None
|
return None
|
||||||
@@ -163,17 +260,18 @@ class ProxyModelManager:
|
|||||||
clone = ModelFactory().make_server_store()
|
clone = ModelFactory().make_server_store()
|
||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
rows = self.sort_rows(rows)
|
rows = self.sort_rows(rows)
|
||||||
clone.extend(rows)
|
marshalled_rows = ServerRow.marshal_rows(rows)
|
||||||
|
clone.extend(marshalled_rows)
|
||||||
|
|
||||||
self.set_cache(filters, rows)
|
self.set_cache(filters, rows)
|
||||||
self.set_proxy_model(clone)
|
self.set_proxy_model(clone)
|
||||||
return clone
|
return clone
|
||||||
|
|
||||||
def sort_rows(self, rows: list) -> list:
|
def sort_rows(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
rows.sort(key=lambda x: re.sub(r"[^A-Za-z0-9]+", "", x[0].lower()))
|
rows.sort(key=lambda x: re.sub(r"[^A-Za-z0-9]+", "", x.name.lower()))
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def filter_initial(self, filters: tuple) -> list:
|
def filter_initial(self, filters: tuple[str, ...]) -> list[ServerRow]:
|
||||||
"""
|
"""
|
||||||
Simply culls the control model of any disabled filters
|
Simply culls the control model of any disabled filters
|
||||||
"""
|
"""
|
||||||
@@ -182,7 +280,7 @@ class ProxyModelManager:
|
|||||||
self.set_filtered(self.filter_toggle_off(filters, f))
|
self.set_filtered(self.filter_toggle_off(filters, f))
|
||||||
return self.filtered
|
return self.filtered
|
||||||
|
|
||||||
def filter_map(self, filters: tuple) -> list:
|
def filter_map(self, filters: tuple[str, ...]) -> list[ServerRow]:
|
||||||
"""
|
"""
|
||||||
Multi-filtration for any context starts by narrowing by map
|
Multi-filtration for any context starts by narrowing by map
|
||||||
"""
|
"""
|
||||||
@@ -192,10 +290,10 @@ class ProxyModelManager:
|
|||||||
if sel_map == str_filters.all_maps:
|
if sel_map == str_filters.all_maps:
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
rows = [row for row in rows if row[1] == sel_map]
|
rows = [row for row in rows if row.mapname == sel_map]
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def filter_keyword(self, filters: tuple) -> list:
|
def filter_keyword(self, filters: tuple[str, ...]) -> list[ServerRow]:
|
||||||
keyword = self.filter_man.get_active_keyword()
|
keyword = self.filter_man.get_active_keyword()
|
||||||
rows = self.filtered
|
rows = self.filtered
|
||||||
|
|
||||||
@@ -205,13 +303,15 @@ class ProxyModelManager:
|
|||||||
filtered = [
|
filtered = [
|
||||||
row
|
row
|
||||||
for row in rows
|
for row in rows
|
||||||
if keyword in row[0].lower()
|
if keyword in row.name.lower()
|
||||||
or keyword in row[1].lower()
|
or keyword in row.mapname.lower()
|
||||||
or keyword in row[7].lower()
|
or keyword in row.ip.lower()
|
||||||
]
|
]
|
||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
def filter_toggle_off(self, filters: tuple, filter_type: str) -> list:
|
def filter_toggle_off(
|
||||||
|
self, filters: tuple[str, ...], filter_type: str
|
||||||
|
) -> list[ServerRow]:
|
||||||
"""
|
"""
|
||||||
Sub-filtration of the current model
|
Sub-filtration of the current model
|
||||||
"""
|
"""
|
||||||
@@ -225,43 +325,11 @@ class ProxyModelManager:
|
|||||||
self.set_filtered(None)
|
self.set_filtered(None)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
rows = self.filtered
|
# TODO: pack filter type enum in filter checkbox, rather than stringwise comparison
|
||||||
match filter_type:
|
rows = self.filtration_manager.filter(filter_type, self.filtered)
|
||||||
case strings.filter_3pp:
|
|
||||||
rows = [row for row in rows if row[2] != strings.filter_3pp]
|
|
||||||
case strings.filter_1pp:
|
|
||||||
rows = [row for row in rows if row[2] != strings.filter_1pp]
|
|
||||||
case strings.filter_official:
|
|
||||||
rows = [row for row in rows if row[10] != strings.filter_official]
|
|
||||||
case strings.filter_unofficial:
|
|
||||||
rows = [row for row in rows if row[10] != strings.filter_unofficial]
|
|
||||||
case strings.filter_empty:
|
|
||||||
rows = [row for row in rows if row[4] != 0]
|
|
||||||
case strings.filter_full:
|
|
||||||
rows = [row for row in rows if row[4] != row[5]]
|
|
||||||
case strings.filter_duplicate:
|
|
||||||
seen = set()
|
|
||||||
final = []
|
|
||||||
for row in rows:
|
|
||||||
if row[0] not in seen:
|
|
||||||
seen.add(row[0])
|
|
||||||
final.append(row)
|
|
||||||
rows = final
|
|
||||||
case strings.filter_day:
|
|
||||||
rows = [row for row in rows if not re.match(DAY_REG, row[3])]
|
|
||||||
case strings.filter_night:
|
|
||||||
rows = [row for row in rows if not re.match(NIGHT_REG, row[3])]
|
|
||||||
case strings.filter_nonascii:
|
|
||||||
rows = [row for row in rows if row[0].isascii()]
|
|
||||||
case strings.filter_lowpop:
|
|
||||||
rows = [row for row in rows if (row[4] / row[5] * 100) > 30]
|
|
||||||
case strings.filter_modded:
|
|
||||||
rows = [row for row in rows if not row[11]]
|
|
||||||
case strings.filter_unmodded:
|
|
||||||
rows = [row for row in rows if row[11]]
|
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def filter_toggle_on(self, filters: tuple[str, ...]) -> list:
|
def filter_toggle_on(self, filters: tuple[str, ...]) -> list[ServerRow]:
|
||||||
"""Effectively applies all filters"""
|
"""Effectively applies all filters"""
|
||||||
self.set_filtered(self.control_model)
|
self.set_filtered(self.control_model)
|
||||||
self.set_filtered(self.filter_map(filters))
|
self.set_filtered(self.filter_map(filters))
|
||||||
@@ -272,31 +340,36 @@ class ProxyModelManager:
|
|||||||
self.set_filtered(self.filter_toggle_off(filters, f))
|
self.set_filtered(self.filter_toggle_off(filters, f))
|
||||||
return self.filtered
|
return self.filtered
|
||||||
|
|
||||||
def set_cache(self, filters: tuple[str, ...], filtered_rows: list[tuple]) -> None:
|
def set_cache(
|
||||||
|
self, filters: tuple[str, ...], filtered_rows: list[ServerRow]
|
||||||
|
) -> None:
|
||||||
self.filter_cache[filters] = filtered_rows
|
self.filter_cache[filters] = filtered_rows
|
||||||
|
|
||||||
def set_filtered(self, rows: list | None) -> None:
|
def set_filtered(self, rows: list[ServerRow] | None) -> None:
|
||||||
if rows is None:
|
if rows is None:
|
||||||
self.filtered = []
|
self.filtered = []
|
||||||
else:
|
else:
|
||||||
self.filtered = rows
|
self.filtered = rows
|
||||||
|
|
||||||
def get_filtered(self) -> list:
|
def get_filtered(self) -> list[ServerRow]:
|
||||||
return self.filtered
|
return self.filtered
|
||||||
|
|
||||||
def set_proxy_model(self, model: "FastInsertListStore") -> None:
|
def set_proxy_model(self, model: "FastInsertListStore") -> None:
|
||||||
"""
|
"""
|
||||||
FastInsertListStore representation of the raw model after filtration
|
FastInsertListStore representation of the raw model after filtration
|
||||||
|
|
||||||
|
This attribute is saved from within a thread so that it later be retrieved
|
||||||
|
and safely packed into the TreeView in the main loop, cf. get_proxy_model()
|
||||||
"""
|
"""
|
||||||
self.proxy_model = model
|
self.proxy_model = model
|
||||||
|
|
||||||
def set_control(self, rows: list) -> None:
|
def set_control(self, rows: list[ServerRow]) -> None:
|
||||||
"""
|
"""
|
||||||
Raw data model prior to filtration
|
Raw data model prior to filtration
|
||||||
"""
|
"""
|
||||||
self.control_model = rows
|
self.control_model = rows
|
||||||
|
|
||||||
def get_control(self) -> list[Any]:
|
def get_control(self) -> list[ServerRow]:
|
||||||
if self.control_model is None:
|
if self.control_model is None:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"Expected a populated control model, but it is Nonetype"
|
"Expected a populated control model, but it is Nonetype"
|
||||||
@@ -307,7 +380,7 @@ class ProxyModelManager:
|
|||||||
self.filtered = []
|
self.filtered = []
|
||||||
self.filter_cache = {}
|
self.filter_cache = {}
|
||||||
|
|
||||||
def push(self, data: list[Any]) -> None:
|
def push(self, data: list[ServerRow]) -> None:
|
||||||
self.wipe_cache()
|
self.wipe_cache()
|
||||||
self.set_control(data)
|
self.set_control(data)
|
||||||
cur_keyword = self.filter_man.get_active_keyword()
|
cur_keyword = self.filter_man.get_active_keyword()
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from gi.repository import Gtk, GLib
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.api.servers import A2SInfo, Record
|
from dzgui.api.servers import A2SInfo, Record
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
from dzgui.model.proxy_model import ProxyModelManager
|
from dzgui.model.proxy_model import ProxyModelManager, ServerRow
|
||||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
@@ -241,10 +241,7 @@ class ServerModelManager:
|
|||||||
|
|
||||||
def update_history(
|
def update_history(
|
||||||
self,
|
self,
|
||||||
rows: (
|
rows: list["ServerRow"] | None = None,
|
||||||
list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]]
|
|
||||||
| None
|
|
||||||
) = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
if rows is not None:
|
if rows is not None:
|
||||||
@@ -267,7 +264,6 @@ class ServerModelManager:
|
|||||||
self.first_iteration = False
|
self.first_iteration = False
|
||||||
self.emitter.emit("load_maps", store)
|
self.emitter.emit("load_maps", store)
|
||||||
|
|
||||||
# TODO: dataclass for record rows; check for other dict annotations
|
|
||||||
def add_to_history(self, data: tuple[dict[str, Any], "Record"]) -> None:
|
def add_to_history(self, data: tuple[dict[str, Any], "Record"]) -> None:
|
||||||
row, record = data
|
row, record = data
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
@@ -285,6 +281,10 @@ class ServerModelManager:
|
|||||||
def remove_from_history(self, record: Servers.Record) -> None:
|
def remove_from_history(self, record: Servers.Record) -> None:
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
proxy_man.remove_row_from_control(record)
|
proxy_man.remove_row_from_control(record)
|
||||||
|
|
||||||
|
fqip = Servers.record_to_fqip(record)
|
||||||
|
config_man = self.controller.get_config_man()
|
||||||
|
config_man.remove_from_history_file(fqip)
|
||||||
self.update_history()
|
self.update_history()
|
||||||
|
|
||||||
@call_on_thread(dialog.querying)
|
@call_on_thread(dialog.querying)
|
||||||
@@ -453,13 +453,13 @@ class ServerModelManager:
|
|||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run() # type: ignore[no-untyped-call]
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def _push_data(self, data: list[Any]) -> None:
|
def _push_data(self, data: list["ServerRow"]) -> None:
|
||||||
self._get_proxy_man().push(data)
|
self._get_proxy_man().push(data)
|
||||||
self._sort_unique_maps(data)
|
self._sort_unique_maps(data)
|
||||||
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_success))
|
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_success))
|
||||||
|
|
||||||
def _sort_unique_maps(self, data: list[Any]) -> None:
|
def _sort_unique_maps(self, data: list[Any]) -> None:
|
||||||
u_maps = set([row[1] for row in data])
|
u_maps = set([row.mapname for row in data])
|
||||||
self._set_new_maps(sorted(u_maps))
|
self._set_new_maps(sorted(u_maps))
|
||||||
|
|
||||||
def _set_new_maps(self, maps: list[str]) -> None:
|
def _set_new_maps(self, maps: list[str]) -> None:
|
||||||
@@ -473,7 +473,6 @@ class ServerModelManager:
|
|||||||
|
|
||||||
@call_on_thread(dialog.filtering)
|
@call_on_thread(dialog.filtering)
|
||||||
def refilter(self, mode: FilterMode) -> None:
|
def refilter(self, mode: FilterMode) -> None:
|
||||||
# FIXME: causes two wait dialogs when map selection change signal emits after loading servers
|
|
||||||
self.first_iteration = False
|
self.first_iteration = False
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
proxy_man.filter(mode)
|
proxy_man.filter(mode)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
changelog_label = "View changelog"
|
||||||
|
changelog_status = "Opens the DZGUI changelog"
|
||||||
|
|
||||||
|
log_label = "Show debug log"
|
||||||
|
log_status = "Read the DZGUI log generated since startup"
|
||||||
|
|
||||||
|
separator_label = "SEPARATOR"
|
||||||
|
separator_status = ""
|
||||||
|
|
||||||
|
system_log_label = "Output system info to log file"
|
||||||
|
system_log_status = "Dump diagnostic data for troubleshooting"
|
||||||
|
|
||||||
|
docs_label = "Documentation/help files (GitHub) ⧉"
|
||||||
|
docs_status = "Open the DZGUI documentation in a browser"
|
||||||
|
|
||||||
|
docs_fallback_label = "Documentation/help files (Codeberg mirror) ⧉"
|
||||||
|
docs_fallback_status = "Open the DZGUI documentation in a browser"
|
||||||
|
|
||||||
|
bugs_label = "Report a bug (GitHub) ⧉"
|
||||||
|
bugs_status = "Open the DZGUI issue tracker in a browser"
|
||||||
|
|
||||||
|
forum_label = "DZGUI Subreddit ⧉"
|
||||||
|
forum_status = "Open the DZGUI discussion forum in a browser"
|
||||||
|
|
||||||
|
sponsor_label = "Sponsor (GitHub) ⧉"
|
||||||
|
sponsor_status = "Sponsor development of the DZGUI project"
|
||||||
|
|
||||||
|
thanks_label = "Special thanks"
|
||||||
|
thanks_status = "A list of contributors, testers, and sponsors"
|
||||||
+6
-11
@@ -11,6 +11,7 @@ from dzgui.const.enum import Preferences
|
|||||||
from dzgui.config.query import lookup
|
from dzgui.config.query import lookup
|
||||||
from dzgui.init.prefix import get_version
|
from dzgui.init.prefix import get_version
|
||||||
from dzgui.util.redact import redact_home
|
from dzgui.util.redact import redact_home
|
||||||
|
from dzgui.util.strings import unknown
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
@@ -27,28 +28,22 @@ def get_cpu_model() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def print_servers(servers: list[str]) -> str:
|
def print_servers(servers: list[str]) -> str:
|
||||||
s = ""
|
return "\n".join(servers)
|
||||||
for server in servers:
|
|
||||||
s += server + "\n"
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def print_mods(mods: list[int]) -> str:
|
def print_mods(mods: list[int]) -> str:
|
||||||
s = ""
|
return "\n".join(map(str, mods))
|
||||||
for mod in mods:
|
|
||||||
s += str(mod) + "\n"
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def write_diagnostic(config: Path, outfile: Path) -> None:
|
def write_diagnostic(config: Path, outfile: Path) -> None:
|
||||||
date = datetime.now().isoformat()
|
date = datetime.now().isoformat()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# FIXME: ID_LIKE not available on CachyOS
|
# NOTE: ID_LIKE not available on CachyOS
|
||||||
distro = platform.freedesktop_os_release()["ID_LIKE"]
|
distro = platform.freedesktop_os_release()["ID_LIKE"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(e)
|
logger.warn(e)
|
||||||
distro = "Unknown"
|
distro = unknown
|
||||||
|
|
||||||
kernel = os.uname().release
|
kernel = os.uname().release
|
||||||
cpu = get_cpu_model()
|
cpu = get_cpu_model()
|
||||||
@@ -68,7 +63,6 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
|
|||||||
servers = lookup(config, Preferences.IP_LIST)
|
servers = lookup(config, Preferences.IP_LIST)
|
||||||
servers_pretty = print_servers(servers)
|
servers_pretty = print_servers(servers)
|
||||||
|
|
||||||
# FIXME: extraneous newlines in lists of mods
|
|
||||||
template = f"""\
|
template = f"""\
|
||||||
{APP_NAME} version {version}
|
{APP_NAME} version {version}
|
||||||
Date: {date}
|
Date: {date}
|
||||||
@@ -82,6 +76,7 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
|
|||||||
|
|
||||||
Mods:
|
Mods:
|
||||||
{mods_pretty}
|
{mods_pretty}
|
||||||
|
|
||||||
Servers:
|
Servers:
|
||||||
{servers_pretty}
|
{servers_pretty}
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ class Crumbs:
|
|||||||
developers: str
|
developers: str
|
||||||
default: str
|
default: str
|
||||||
offline: str
|
offline: str
|
||||||
|
connect: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
@@ -437,6 +438,7 @@ crumbs = Crumbs(
|
|||||||
developers="Options > Developers",
|
developers="Options > Developers",
|
||||||
default="Servers > ",
|
default="Servers > ",
|
||||||
offline="Mods > Play offline",
|
offline="Mods > Play offline",
|
||||||
|
connect="Connect",
|
||||||
)
|
)
|
||||||
|
|
||||||
no_mods = "No local mods found."
|
no_mods = "No local mods found."
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class ContextualButton(Gtk.Button):
|
class ContextualButton(Gtk.Button):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, label: str, opens: "NotebookPage", tooltip: str, context: ButtonType
|
self,
|
||||||
|
label: str,
|
||||||
|
opens: "NotebookPage | None",
|
||||||
|
tooltip: str,
|
||||||
|
context: ButtonType,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
label=label,
|
label=label,
|
||||||
@@ -51,9 +55,9 @@ class ButtonBox(Gtk.Box):
|
|||||||
|
|
||||||
for side_button in ButtonType:
|
for side_button in ButtonType:
|
||||||
button = ContextualButton(
|
button = ContextualButton(
|
||||||
label=side_button.dict["label"],
|
label=side_button.label,
|
||||||
opens=side_button.dict["opens"],
|
opens=side_button.opens,
|
||||||
tooltip=side_button.dict["tooltip"],
|
tooltip=side_button.tooltip,
|
||||||
context=side_button,
|
context=side_button,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Callable, Literal, Self, TYPE_CHECKING, Union
|
from typing import Callable, Literal, Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.util.clip import copy_clipboard
|
from dzgui.util.clip import copy_clipboard
|
||||||
from dzgui.strings import buttons
|
from dzgui.strings import buttons
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class Breadcrumbs(Gtk.Label):
|
|||||||
label = page.get_current_tab_text()
|
label = page.get_current_tab_text()
|
||||||
self.set_server_crumbs(label)
|
self.set_server_crumbs(label)
|
||||||
else:
|
else:
|
||||||
crumbs = enum.dict["crumbs"]
|
crumbs = enum.crumbs
|
||||||
self.set_crumbs(crumbs)
|
self.set_crumbs(crumbs)
|
||||||
|
|
||||||
def set_server_crumbs(self, suffix: str) -> None:
|
def set_server_crumbs(self, suffix: str) -> None:
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ class EnumeratedModButton(Gtk.Button):
|
|||||||
super().__init__(margin_start=10, margin_end=10, focus_on_click=False)
|
super().__init__(margin_start=10, margin_end=10, focus_on_click=False)
|
||||||
|
|
||||||
self.enum = enum
|
self.enum = enum
|
||||||
self.set_label(enum.dict["label"])
|
self.set_label(enum.label)
|
||||||
self.set_tooltip_text(enum.dict["tooltip"])
|
self.set_tooltip_text(enum.tooltip)
|
||||||
|
|
||||||
|
|
||||||
class ModPanelButton(EnumeratedModButton):
|
class ModPanelButton(EnumeratedModButton):
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class Statusbar(Gtk.Grid):
|
|||||||
return
|
return
|
||||||
|
|
||||||
enum = notebook.get_page_by_enum()
|
enum = notebook.get_page_by_enum()
|
||||||
show_statusbar = enum.dict["statusbar"]
|
show_statusbar = enum.has_statusbar
|
||||||
|
|
||||||
if show_statusbar is False:
|
if show_statusbar is False:
|
||||||
self.set_by_context(enum, "")
|
self.set_by_context(enum, "")
|
||||||
@@ -148,5 +148,5 @@ class Statusbar(Gtk.Grid):
|
|||||||
row = tree.get_value_at_index(1)
|
row = tree.get_value_at_index(1)
|
||||||
if self.controller.loaded is False:
|
if self.controller.loaded is False:
|
||||||
return
|
return
|
||||||
tooltip = row.dict["tooltip"]
|
tooltip = row.tooltip
|
||||||
self.set_by_context(NotebookPage.HELP, tooltip)
|
self.set_by_context(NotebookPage.HELP, tooltip)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.const.enum import ContextMenu
|
from dzgui.const.enum import ContextMenuItem
|
||||||
from dzgui.util.keys import is_ctrl_mask, is_navkey
|
from dzgui.util.keys import is_ctrl_mask, is_navkey
|
||||||
from dzgui.util.strings import edit_note
|
from dzgui.util.strings import edit_note
|
||||||
from dzgui.views.trees.tree_base import TreeView
|
from dzgui.views.trees.tree_base import TreeView
|
||||||
@@ -58,17 +58,17 @@ class ContextMixin(TreeView):
|
|||||||
self.context_menu.select_first(False)
|
self.context_menu.select_first(False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _process_dynamic_row(self, row: ContextMenu) -> Gtk.MenuItem:
|
def _process_dynamic_row(self, row: ContextMenuItem) -> Gtk.MenuItem:
|
||||||
if row == ContextMenu.ADD_SERVER and self.is_in_favs(): # type: ignore
|
if row == ContextMenuItem.ADD_SERVER and self.is_in_favs(): # type: ignore
|
||||||
row = ContextMenu.REMOVE_SERVER
|
row = ContextMenuItem.REMOVE_SERVER
|
||||||
|
|
||||||
item = Gtk.MenuItem(label=row.dict["label"])
|
item = Gtk.MenuItem(label=row.label)
|
||||||
item.connect("activate", self._on_menu_click, row)
|
item.connect("activate", self._on_menu_click, row)
|
||||||
|
|
||||||
if row == ContextMenu.SHOW_MODS:
|
if row == ContextMenuItem.SHOW_MODS:
|
||||||
item.set_sensitive(self.is_modded()) # type: ignore
|
item.set_sensitive(self.is_modded()) # type: ignore
|
||||||
|
|
||||||
if row == ContextMenu.ADD_NOTE:
|
if row == ContextMenuItem.ADD_NOTE:
|
||||||
if self.controller.has_note():
|
if self.controller.has_note():
|
||||||
item.set_label(edit_note)
|
item.set_label(edit_note)
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ class ContextMixin(TreeView):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _on_menu_click(self, widget: Gtk.MenuItem, enum: ContextMenu) -> None:
|
def _on_menu_click(self, widget: Gtk.MenuItem, enum: ContextMenuItem) -> None:
|
||||||
self.controller.menu_action(enum, self) # type: ignore
|
self.controller.menu_action(enum, self) # type: ignore
|
||||||
|
|
||||||
def _on_key(self, menu: Gtk.Menu, event: Gdk.EventKey) -> bool | None:
|
def _on_key(self, menu: Gtk.Menu, event: Gdk.EventKey) -> bool | None:
|
||||||
|
|||||||
@@ -77,4 +77,4 @@ class MenuTreeView(TreeView):
|
|||||||
raise ValueError("Trying to call a method on non-existent model")
|
raise ValueError("Trying to call a method on non-existent model")
|
||||||
_iter = self.get_focused_row_iter()
|
_iter = self.get_focused_row_iter()
|
||||||
rowtype = model.get_value(_iter, 1)
|
rowtype = model.get_value(_iter, 1)
|
||||||
return str(rowtype.dict["tooltip"])
|
return str(rowtype.tooltip)
|
||||||
@@ -7,7 +7,7 @@ from warnings import deprecated
|
|||||||
|
|
||||||
from dzgui.api.servers import ping, Record
|
from dzgui.api.servers import ping, Record
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.const.enum import ContextMenu, ContextMenuGroup, ServerTab
|
from dzgui.const.enum import ContextMenuItem, ContextMenuGroup, ServerTab
|
||||||
from dzgui.managers.filter import FilterManager
|
from dzgui.managers.filter import FilterManager
|
||||||
from dzgui.model.proxy_model import ProxyModelManager
|
from dzgui.model.proxy_model import ProxyModelManager
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
@@ -251,9 +251,9 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
if self.enum is ServerTab.LAN:
|
if self.enum is ServerTab.LAN:
|
||||||
self.emitter.emit("request_custom_port_focus")
|
self.emitter.emit("request_custom_port_focus")
|
||||||
case Gdk.KEY_c:
|
case Gdk.KEY_c:
|
||||||
self.controller.menu_action(ContextMenu.COPY_SERVER_IP, self)
|
self.controller.menu_action(ContextMenuItem.COPY_SERVER_IP, self)
|
||||||
case Gdk.KEY_r:
|
case Gdk.KEY_r:
|
||||||
self.controller.menu_action(ContextMenu.REFRESH_PLAYERS, self)
|
self.controller.menu_action(ContextMenuItem.REFRESH_PLAYERS, self)
|
||||||
else:
|
else:
|
||||||
match event.keyval:
|
match event.keyval:
|
||||||
case Gdk.KEY_l | Gdk.KEY_Right:
|
case Gdk.KEY_l | Gdk.KEY_Right:
|
||||||
@@ -413,5 +413,9 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
if row_index >= start[0] <= end[0]:
|
if row_index >= start[0] <= end[0]:
|
||||||
# NOTE: fetch raw data rows
|
# NOTE: fetch raw data rows
|
||||||
real_model = self.proxy_man.get_control()
|
real_model = self.proxy_man.get_control()
|
||||||
value = real_model[row_index][col_index]
|
from dataclasses import fields
|
||||||
|
from dzgui.model.proxy_model import ServerRow
|
||||||
|
key = fields(ServerRow)[col_index].name
|
||||||
|
|
||||||
|
value = getattr(real_model[row_index], key)
|
||||||
cell.set_property("text", str(value))
|
cell.set_property("text", str(value))
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from dzgui.init.migrate import clean_legacy_versions_file
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_null_signature_stripping(tmp_path: "Path") -> None:
|
||||||
|
tmp = tmp_path.joinpath("dzg.versions")
|
||||||
|
lines = [
|
||||||
|
"1111,2222",
|
||||||
|
"3333,4444",
|
||||||
|
"5555,null",
|
||||||
|
"foo",
|
||||||
|
",4555",
|
||||||
|
"1111",
|
||||||
|
" ",
|
||||||
|
",,,",
|
||||||
|
"-1",
|
||||||
|
]
|
||||||
|
expect = ["1111,2222", "3333,4444"]
|
||||||
|
tmp.write_text("\n".join(lines))
|
||||||
|
clean_legacy_versions_file(tmp)
|
||||||
|
assert expect == tmp.read_text().splitlines()
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
from dzgui.model.model_factory import ModelFactory
|
||||||
|
from dzgui.model.proxy_model import ServerRow
|
||||||
|
|
||||||
|
|
||||||
|
def test_row_conversion() -> None:
|
||||||
|
"""Converted FastInsertListStore is expected to have identical elements"""
|
||||||
|
data = [
|
||||||
|
"Test Server",
|
||||||
|
"chernarusplus",
|
||||||
|
"1PP",
|
||||||
|
"05:00",
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
"127.0.0.0:27016",
|
||||||
|
27017,
|
||||||
|
99,
|
||||||
|
"Unofficial",
|
||||||
|
False,
|
||||||
|
]
|
||||||
|
rows = [ServerRow(*data)]
|
||||||
|
marshalled = ServerRow.marshal_rows(rows)
|
||||||
|
store = ModelFactory().make_server_store()
|
||||||
|
store.extend(marshalled)
|
||||||
|
converted = [[el for el in row] for row in store]
|
||||||
|
assert data == converted[0]
|
||||||
Reference in new issue
Block a user