mirror of
https://github.com/aclist/dztui.git
synced 2026-11-11 20:40:38 +01:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
439109d11d | ||
|
|
a5374d1f1a | ||
|
|
dcc4cb494b | ||
|
|
ff1e150eb1 | ||
|
|
c7ef91f02e | ||
|
|
95bc05eab5 | ||
|
|
89dc6aa8a8 | ||
|
|
9e8e3730c7 | ||
|
|
e5c8f2a69c | ||
|
|
63316a01a9 | ||
|
|
0729273503 | ||
|
|
91d164d666 | ||
|
|
0d7f022c43 | ||
|
|
9b948271e1 | ||
|
|
7322970c54 | ||
|
|
504a2a724c | ||
|
|
c66511b005 | ||
|
|
3677b01fdc | ||
|
|
4f6223f90b | ||
|
|
441066a1a0 | ||
|
|
fa1ac8fa31 |
No files matched your search
@@ -1,11 +1,11 @@
|
|||||||
Submitting a ticket
|
Submitting a ticket
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Tickets are tracked at the `DZGUI issue tracker <https://github.com/aclist/dztui/issues>`_.
|
Tickets are tracked at the `DZGUI issue tracker <https://github.com/aclist/dzgui/issues>`_.
|
||||||
|
|
||||||
You will need to `register for a GitHub account <https://github.com/signup>`_ in order to submit one.
|
You will need to `register for a GitHub account <https://github.com/signup>`_ in order to submit one.
|
||||||
|
|
||||||
If have checked the :doc:`kb` and `closed issues <https://github.com/aclist/dztui/issues?q=is%3Aissue+is%3Aclosed>`_ area and did not find a solution, three ticket types are provided for your
|
If have checked the :doc:`kb` and `closed issues <https://github.com/aclist/dzgui/issues?q=is%3Aissue+is%3Aclosed>`_ area and did not find a solution, three ticket types are provided for your
|
||||||
convenience:
|
convenience:
|
||||||
|
|
||||||
- **Bug report**: for unexpected, breaking behavior in the application
|
- **Bug report**: for unexpected, breaking behavior in the application
|
||||||
@@ -20,18 +20,17 @@ Below are some examples of how to format a constructive report that will ensure
|
|||||||
A non-actionable report
|
A non-actionable report
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Subject: add button
|
Subject: BUG: add button
|
||||||
|
|
||||||
hi, you must add a button to show how long I have to wait to join a full server, the app is useless without this...do your job, guys!!! ;-)
|
hi, you must add a button to show how long I have to wait to join a full server, the app is useless without this...do your job, guys!!! ;-)
|
||||||
|
|
||||||
|
|
||||||
This report exhibits a number of problems:
|
This report exhibits a number of problems:
|
||||||
|
|
||||||
- Vague title and summary: in addition to being unclear, this will slow down filing the incoming report.
|
|
||||||
- Problem space is poorly defined: it is not clear where the issue lies, where the feature is supposed to be implemented, nor why.
|
|
||||||
- Lack of context: the feature may already exist, but there is not enough information to go on. Alternatively, the feature may be technically impossible to implement, but there is not enough
|
|
||||||
information to go on.
|
|
||||||
- Incorrect report type: this is a feature request filed as a bug report.
|
- Incorrect report type: this is a feature request filed as a bug report.
|
||||||
|
- Vague title and summary: this will slow down filing the incoming report.
|
||||||
|
- Problem space is poorly defined: it is not clear where the issue lies, where the feature is supposed to be implemented, nor why.
|
||||||
|
- Lack of context: the feature may already exist, but there is not enough information to go on. Alternatively, the feature may be technically impossible to implement.
|
||||||
- Unprofessional tone: the message is disrespectful towards project members and inserts the user's subjective opinion.
|
- Unprofessional tone: the message is disrespectful towards project members and inserts the user's subjective opinion.
|
||||||
|
|
||||||
An actionable report
|
An actionable report
|
||||||
@@ -56,5 +55,5 @@ This report is actionable because it is objective, specific, and detailed:
|
|||||||
- The subject is clearly defined: specific details ensure it will be filed in a timely manner.
|
- The subject is clearly defined: specific details ensure it will be filed in a timely manner.
|
||||||
- Scope is explicit: the specific version, settings, and conditions under which the problem occur were given.
|
- Scope is explicit: the specific version, settings, and conditions under which the problem occur were given.
|
||||||
- Ample context: the user gave steps to reproduce the problem and clarified each one.
|
- Ample context: the user gave steps to reproduce the problem and clarified each one.
|
||||||
- Courteous tone: the user interacted with project members in a professional manner.
|
|
||||||
- Supporting information: the user provided a crash dump to expedite resolution of the issue.
|
- Supporting information: the user provided a crash dump to expedite resolution of the issue.
|
||||||
|
- Courteous tone: the user interacted with project members in a professional manner.
|
||||||
+2
-2
@@ -36,5 +36,5 @@ html_show_copyright = False
|
|||||||
html_show_sourcelink = False
|
html_show_sourcelink = False
|
||||||
|
|
||||||
today_fmt = "%Y-%m-%d"
|
today_fmt = "%Y-%m-%d"
|
||||||
release = "6.x.x - 7.x.x"
|
release = "7.x.x"
|
||||||
version = "6.x.x - 7.x.x"
|
version = "7.x.x"
|
||||||
@@ -1,19 +1,14 @@
|
|||||||
Developers
|
Developers
|
||||||
===========
|
===========
|
||||||
|
|
||||||
This project is open source under the GPLv3 license. You can find the source code on
|
This project is open source under the GPLv3 license. You can find the source code on
|
||||||
`GitHub <https://github.com/aclist/dztui>`_. If you wish to contribute to
|
`GitHub <https://github.com/aclist/dzgui>`_. If you wish to contribute to
|
||||||
development, you will find further help in the
|
development, you will find further help in the
|
||||||
`CONTRIBUTING.md <https://github.com/aclist/dztui/blob/main/CONTRIBUTING.md>`_
|
`CONTRIBUTING.md <https://github.com/aclist/dzgui7/blob/main/CONTRIBUTING.md>`_
|
||||||
file.
|
file.
|
||||||
|
|
||||||
All contributors are expected to act in accordance with the project's
|
All contributors are expected to act in accordance with the project's
|
||||||
`code of conduct <https://github.com/aclist/dztui/blob/main/CODE_OF_CONDUCT.md>`_,
|
`code of conduct <https://github.com/aclist/dzgui7/blob/main/CODE_OF_CONDUCT.md>`_,
|
||||||
and follow the project's standards and guidelines.
|
and follow the project's standards and guidelines.
|
||||||
|
|
||||||
To install DZGUI and related dependencies for linting, testing, and version control,
|
Detailed instructions on setting up a build environment and installing DZGUI from source code
|
||||||
invoke the command ``uv pip install .[dev]``.
|
can be found in the `DEVELOPERS.md <https://github.com/aclist/dzgui7/blob/main/DEVELOPERS.md>`_ file.
|
||||||
|
|
||||||
After this process, install the applicable pre-commit hooks by invoking the command ``pre-commit install``.
|
|
||||||
|
|
||||||
These pre-commit hooks enforce code checks that prevent making commits unless the relevant tests pass.
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
Command line launch flags
|
||||||
|
==========================
|
||||||
|
When run from the terminal, DZGUI provides several flags:
|
||||||
|
|
||||||
|
``-d, --debug``: enable developer debugging features. This does not add hidden or extra functionality; it merely enables developers to track deprecation warnings and see values stored in memory.
|
||||||
|
End-users will gain no benefit from using this flag.
|
||||||
|
|
||||||
|
``-m, --map``: check and update system map count value. Unnecessary on most modern systems. If your system does not meet the threshold, the DZGUI setup wizard will prompt you to use this flag.
|
||||||
|
|
||||||
|
``-u, --uninstall``: uninstall: granular selection of which state/configuration files to remove.
|
||||||
|
|
||||||
|
``-v, --version``: displays the DZGUI version.
|
||||||
@@ -15,8 +15,8 @@ A DayZ server browser/mod manager for Linux. Used to list official and community
|
|||||||
|
|
||||||
Source code:
|
Source code:
|
||||||
|
|
||||||
- `GitHub <https://www.github.com/aclist/dztui>`_
|
- `GitHub <https://www.github.com/aclist/dzgui>`_
|
||||||
- `Codeberg (mirror) <https://codeberg.org/aclist/dztui>`_
|
- `Codeberg (mirror) <https://codeberg.org/aclist/dzgui>`_
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
-------------
|
-------------
|
||||||
@@ -56,8 +56,8 @@ See: :doc:`kb` or :ref:`Submitting a ticket`.
|
|||||||
:maxdepth: 3
|
:maxdepth: 3
|
||||||
:hidden:
|
:hidden:
|
||||||
|
|
||||||
steam
|
|
||||||
locale
|
locale
|
||||||
|
flags
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:caption: Help
|
:caption: Help
|
||||||
|
|||||||
@@ -1,28 +1,37 @@
|
|||||||
Installation
|
Installation
|
||||||
==================
|
==================
|
||||||
|
|
||||||
As of this writing, you have a choice of two DZGUI versions:
|
.. note::
|
||||||
|
Version 6 of DZGUI 6 has been fully replaced with DZGUI 7.
|
||||||
|
Users on existing DZGUI 6 installations should migrate to the DZGUI 7 build.
|
||||||
|
|
||||||
- DZGUI 6: Legacy stable version, more complicated to install
|
In order to make the first-time setup experience easier for end-users, DZGUI 7 no longer requires
|
||||||
- DZGUI 7: Unstable beta version, much easier to install
|
the tool to be installed from source code into a working Python environment. All necessary dependencies and the
|
||||||
|
runtime environment are built-in.
|
||||||
|
|
||||||
DZGUI 6
|
Turnkey installer
|
||||||
--------------------
|
------------------------
|
||||||
This version is no longer receiving feature updates, but will receive security updates until it is replaced by DZGUI 7.
|
|
||||||
|
|
||||||
DZGUI 6 must be installed from the source code and has many more package dependencies and prerequisites for it to function correctly.
|
- Visit the project's `Releases page <https://github.com/aclist/dzgui/releases/latest>`_.
|
||||||
If you are looking for a one-click, self-contained installer, use :ref:`DZGUI 7` instead.
|
- Download the DZGUI archive from the top of the **Assets** list. This file will be labeled ``dzgui-<VERSION>-x86_64-unknown-linux-musl.tar.gz``.
|
||||||
|
- Extract the archive's contents.
|
||||||
|
- From a terminal, run the command ``./dzgui`` or double click on the extracted file in a file explorer.
|
||||||
|
- Once the setup wizard opens, follow the steps at the bottom of this page.
|
||||||
|
|
||||||
To install **DZGUI 6**, visit :doc:`dzgui6`.
|
UI dependencies (Python and PyGObject)
|
||||||
|
---------------------------------------
|
||||||
|
On most major distributions, GObject introspection libraries should be available by default.
|
||||||
|
|
||||||
|
If you receive an error about a missing ``libgirepository-2.0`` dependency, install it via your system's package manager.
|
||||||
|
|
||||||
DZGUI 7
|
On Debian-based distributions (Ubuntu, etc.), this package is not explicitly installed on the system.
|
||||||
--------------------
|
You can install it via the following set of commands:
|
||||||
This version is currently in beta testing and has all of the features found in DZGUI 6, but may be unstable.
|
|
||||||
|
|
||||||
DZGUI 7 has an all-in-one installer and setup wizard, and many new features not found in DZGUI 6.
|
.. code:: console
|
||||||
However, this codebase is newer and is provided as a pre-release beta candidate.
|
|
||||||
|
|
||||||
If you want the simplest installer and don't mind experiencing the occasional bug, use this version.
|
sudo apt update
|
||||||
|
sudo apt install libgirepository-2.0-0
|
||||||
|
|
||||||
To install **DZGUI 7**, visit :doc:`dzgui7`.
|
Next steps
|
||||||
|
---------------
|
||||||
|
Proceed to the :doc:`setup` section for setup instructions.
|
||||||
+15
-2
@@ -98,15 +98,28 @@ tend to have less headroom for simultaneous connections than their wired counter
|
|||||||
|
|
||||||
DZG-009: Floating dialogs appear maximized on tiling window managers
|
DZG-009: Floating dialogs appear maximized on tiling window managers
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
|
|
||||||
The main DZGUI window and its child dialogs are expected to be rendered as floating by your window manager.
|
The main DZGUI window and its child dialogs are expected to be rendered as floating by your window manager.
|
||||||
DZGUI sends window manager hints to this effect, but tiling window managers designed to bisect the screen into quadrants (e.g., i3 window manager)
|
DZGUI sends window manager hints to this effect, but tiling window managers designed to bisect the screen into quadrants (e.g., i3 window manager)
|
||||||
may try to always launch applications in fullscreen.
|
may try to always launch applications in fullscreen.
|
||||||
|
|
||||||
To resolve this, set specific exclusions or window hints in your WM's configuration file.
|
To resolve this, set specific exclusions or window hints in your WM's configuration file.
|
||||||
For example, for i3, add the following to your `XDG_CONFIG_HOME/i3/config` file (defaults to `$HOME/.config/i3/config`):
|
For example, for i3, add the following to your ``XDG_CONFIG_HOME/i3/config`` file (defaults to ``$HOME/.config/i3/config``):
|
||||||
|
|
||||||
.. code:: console
|
.. code:: console
|
||||||
|
|
||||||
for_window [instance="DZGUI"] floating enable, move position center
|
for_window [instance="DZGUI"] floating enable, move position center
|
||||||
for_window [instance="DZGUI - Dialog"] floating enable, move position center
|
for_window [instance="DZGUI - Dialog"] floating enable, move position center
|
||||||
|
|
||||||
|
DZG-010: Maps combobox extends to full height of window when there are many maps to choose from
|
||||||
|
---------------------------------------------------------------------------------------------------
|
||||||
|
This is a limitation of GTK 3. If and when DZGUI is refactored to support the GTK 4 API, this visual glitch is expected to be resolved.
|
||||||
|
|
||||||
|
DZG-011: GTK reports "Failed to load module 'xapp-gtk3-module' when starting DZGUI
|
||||||
|
---------------------------------------------------------------------------------------------------
|
||||||
|
This is a purely cosmetic message that appears when Cinnamon (Xapp) desktop integations are not present.
|
||||||
|
While you can suppress this message by installing the package ``xapp``, if you are not actually using Cinnamon as
|
||||||
|
part of your desktop environment, it will pull in a large number of unnecessary dependencies.
|
||||||
|
|
||||||
|
DZG-012: After adding the DZGUI Steam shortcut via the installer, shortcut icons do not appear in the small library list
|
||||||
|
-------------------------------------------------------------------------------------------------------------------------
|
||||||
|
It can take 1-2 restarts of the Steam client for these icons to synchronize.
|
||||||
+14
-5
@@ -19,14 +19,14 @@ You will need at a minimum a :ref:`Steam Web API key<steam_api>` in order to bro
|
|||||||
|
|
||||||
Steam Web API key
|
Steam Web API key
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
**This step is required**.
|
**This step is required**.
|
||||||
|
|
||||||
Register for a `Steam Web API key <https://steamcommunity.com/dev/apikey>`_ (free) using your Steam account. You will be asked for a unique URL for your app when registering.
|
Register for a `Steam Web API key <https://steamcommunity.com/dev/apikey>`_ (free) using your Steam account.
|
||||||
|
|
||||||
Since this key is for a personal use application and does not actually call back anywhere, set a generic local identifier here like "127.0.0.1" or some other name that is meaningful to you.
|
You will be asked for a unique URL for your app when registering. Typically, an API would send logs back to your URL in the form of callbacks.
|
||||||
|
However, since these API queries will be used for local consumption by a single user only, you can set a generic local identifier here like ``127.0.0.1`` to have the server discard them.
|
||||||
|
|
||||||
Once configured, you can insert this key in the app when launching it for the first time.
|
Once configured, you can insert this API key in the app when launching it for the first time.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
If you are confused about this requirement, please refer to DZGUI Knowledge Base article :ref:`DZG-007<DZG-007>` for additional information.
|
If you are confused about this requirement, please refer to DZGUI Knowledge Base article :ref:`DZG-007<DZG-007>` for additional information.
|
||||||
@@ -36,7 +36,16 @@ Battlemetrics API key
|
|||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
This feature is no longer supported in DZGUI. Battlemetrics has switched to a paid service.
|
This feature is no longer supported in DZGUI. Battlemetrics has switched to a paid service.
|
||||||
Battlemetrics queries are expected to fail in DZGUI 6. In DZGUI 7, this feature has been removed entirely.
|
|
||||||
|
Choosing a Steam client
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
If multiple Steam clients are available (native Steam client/Flatpak Steam client), you will be
|
||||||
|
asked to choose which you want to use. Due to the way Steam's inter-process communication and internal URIs work,
|
||||||
|
if you choose one or the other, you should not run both clients simultaneously. This can lead DZGUI to send requests to the wrong one.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If you decide to switch to using a different Steam client with DZGUI, please uninstall fully (``dzgui -u``) and re-run the first time setup.
|
||||||
|
|
||||||
|
|
||||||
Next steps
|
Next steps
|
||||||
--------------
|
--------------
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
Uninstallation
|
Uninstallation
|
||||||
==================
|
==================
|
||||||
|
To uninstall DZGUI and its associated files, invoke ``./dzgui`` from the terminal with the ``-u`` or ``--uninstall`` flag.
|
||||||
|
|
||||||
To uninstall DZGUI and its associated files, invoke ``./dzgui.sh`` with the ``-u`` or ``--uninstall`` flag.
|
You will be prompted to select atomic files you wish to remove.
|
||||||
|
|
||||||
Follow the prompts to choose from:
|
|
||||||
|
|
||||||
- **Full uninstall** -- removes the application and all user-defined configuration files
|
|
||||||
- **Partial uninstall** -- removes the application, but keeps user-defined configuration files
|
|
||||||
|
|
||||||
.. tip::
|
.. tip::
|
||||||
If you intend to reinstall DZGUI again, keeping your configuration files is recommended.
|
If you intend to reinstall DZGUI again, keeping your configuration files at a minimum is recommended.
|
||||||
+12
-8
@@ -1,25 +1,29 @@
|
|||||||
Usage
|
Usage
|
||||||
=========
|
=========
|
||||||
|
|
||||||
It is always advised to have Steam running in the background. DZGUI is meant to run "on top of" Steam.
|
It is always advised to have Steam running in the background. DZGUI is meant to run "on top of" Steam.
|
||||||
|
|
||||||
DZGUI can be launched in one of three ways.
|
DZGUI can be launched in one of three ways.
|
||||||
|
|
||||||
From a terminal
|
From a terminal
|
||||||
------------------
|
------------------
|
||||||
|
Invoke the command ``./dzgui`` from wherever you downloaded DZGUI to.
|
||||||
Invoke the command ``./dzgui.sh`` from wherever you downloaded the DZGUI script to.
|
|
||||||
|
|
||||||
Launching from a terminal may give more verbose information in the event of a crash, and can be a good way of troubleshooting problems.
|
Launching from a terminal may give more verbose information in the event of a crash, and can be a good way of troubleshooting problems.
|
||||||
|
|
||||||
From the shortcut shipped with the application
|
From the shortcut shipped with the application
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
When completing the first-time setup, you will be asked if you want to create additional system shortcuts.
|
||||||
|
If applicable for yourDesktop Environment, these shortcuts can be created.
|
||||||
|
|
||||||
On most distributions, if you are using a desktop environment based on the Freedesktop specification, shortcuts will be installed for you.
|
- One shortcut will be located under the ``Games`` category of your system’s applications list/start menu.
|
||||||
|
- The other is accessed via the ``DZGUI`` shortcut on the desktop
|
||||||
- One shortcut is located under the ``Games`` category of your system’s applications list/start menu.
|
|
||||||
- The other is accessed via the ``DZGUI`` desktop icon (Steam Deck only)
|
|
||||||
|
|
||||||
From Steam
|
From Steam
|
||||||
--------------
|
--------------
|
||||||
See :doc:`steam`.
|
DZGUI will also ask you if you want to add a "non-Steam game" shortcut to Steam.
|
||||||
|
This shortcut will launch DZGUI directly and comes with integrated grid and cover artwork.
|
||||||
|
|
||||||
|
You must restart Steam for this shortcut to take effect in your library.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
You can also pass DZGUI launch parameter's to the Steam shortcut via Steam's right-click menu. See :doc:`flags`.
|
||||||
+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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+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)
|
||||||
+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()
|
||||||
@@ -457,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:
|
||||||
@@ -477,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)
|
||||||
|
|||||||
+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}
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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