mirror of
https://github.com/aclist/dztui.git
synced 2026-10-27 13:12:54 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3027fa830 | ||
|
|
92699ebaa9 | ||
|
|
ef2709561d | ||
|
|
89a6996695 | ||
|
|
41b3ad8eca | ||
|
|
d57277f527 | ||
|
|
c45678ca88 | ||
|
|
c95ad53f6f | ||
|
|
3e144dc7d2 | ||
|
|
afdfa76fdf | ||
|
|
00c27ad020 | ||
|
|
d60204b676 | ||
|
|
3d2e27bfe1 | ||
|
|
8a66bafe4c | ||
|
|
2963c4e01c | ||
|
|
f849cbc183 | ||
|
|
77505db621 | ||
|
|
ba7a2b7ab4 | ||
|
|
6d7ed80996 | ||
|
|
72024c5d43 | ||
|
|
0426d50d3e | ||
|
|
26ee560cb4 | ||
|
|
6ec77f6542 | ||
|
|
59959ae3a1 | ||
|
|
05fd95cd8e | ||
|
|
86ba8d9187 | ||
|
|
e715e6da1c | ||
|
|
18ba851b7e | ||
|
|
d2643d08e9 | ||
|
|
6274f62c93 | ||
|
|
24609a1c71 | ||
|
|
1b344c1260 | ||
|
|
e72e09eec1 | ||
|
|
00e78b1228 | ||
|
|
efddd61ea1 | ||
|
|
d006908bbc | ||
|
|
5ba6b6c040 | ||
|
|
d3b9ba031d | ||
|
|
8bfcb6c8fe | ||
|
|
353b10ebfc | ||
|
|
698150645d | ||
|
|
87f347870f | ||
|
|
74bf4b5cfc | ||
|
|
896799f904 | ||
|
|
0f591e658c | ||
|
|
f9083b33ef | ||
|
|
89fcfcb5d6 | ||
|
|
fc7f1d7815 | ||
|
|
fb36425083 | ||
|
|
c5d8afbc4d | ||
|
|
17f732fafe | ||
|
|
676a7ea9eb | ||
|
|
d3f64c1ba5 | ||
|
|
5da3397a29 | ||
|
|
eca6df90d4 |
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: aclist
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Submit bugs that affect functionality here
|
||||||
|
title: "[BUG] Your title here"
|
||||||
|
labels: ["bug"]
|
||||||
|
assignees:
|
||||||
|
- aclist
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
### WARNING: When posting log/config data, redact any sensitive information like API keys.
|
||||||
|
- type: checkboxes
|
||||||
|
id: kb
|
||||||
|
attributes:
|
||||||
|
label: Did you check if this issue already exists?
|
||||||
|
options:
|
||||||
|
- label: I have checked the [Knowledge Base](https://aclist.github.io/dzgui/kb.html) and searched through [past issues](https://github.com/aclist/dztui/issues?q=is%3Aissue+is%3Aclosed) and did not find my issue there.
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: device
|
||||||
|
attributes:
|
||||||
|
label: Device type
|
||||||
|
options:
|
||||||
|
- Desktop PC
|
||||||
|
- Steam Deck
|
||||||
|
default: 0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: DZGUI version
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
placeholder: "5.6.0"
|
||||||
|
- type: input
|
||||||
|
id: distro
|
||||||
|
attributes:
|
||||||
|
label: Linux distribution
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
placeholder: "Arch Linux"
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: What did you expect to happen?
|
||||||
|
placeholder: "E.g., When doing X, Y occurred. Include as much detail as possible."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Additional log output
|
||||||
|
description: Attach the file `$HOME/.local/state/dzgui/DZGUI_DEBUG.log` by clicking this area to highlight it and then dragging the file in.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
name: Feature suggestion
|
||||||
|
description: Submit requests for new features here
|
||||||
|
title: "[FEAT] Your title here"
|
||||||
|
labels: ["enhancement"]
|
||||||
|
assignees:
|
||||||
|
- aclist
|
||||||
|
body:
|
||||||
|
- type: dropdown
|
||||||
|
id: device
|
||||||
|
attributes:
|
||||||
|
label: Device type
|
||||||
|
options:
|
||||||
|
- Desktop PC
|
||||||
|
- Steam Deck
|
||||||
|
default: 0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: DZGUI version
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
placeholder: "5.6.0"
|
||||||
|
- type: input
|
||||||
|
id: distro
|
||||||
|
attributes:
|
||||||
|
label: Linux distribution
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
placeholder: "Arch Linux"
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: Describe the feature
|
||||||
|
description: A clear and detailed description
|
||||||
|
placeholder: "E.g., Add a dialog to the Options menu that automatically lets the user do Y"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: why
|
||||||
|
attributes:
|
||||||
|
label: Describe the benefit
|
||||||
|
description: How will this feature benefit large numbers of users?
|
||||||
|
placeholder: "E.g., Currently, users have to manually do X to achieve Y. Implementing this feature would achieve A, B, and C."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
name: Troubleshooting
|
||||||
|
description: Submit user error/troubleshooting questions here
|
||||||
|
title: "[HELP] Your title here"
|
||||||
|
labels: ["troubleshooting"]
|
||||||
|
assignees:
|
||||||
|
- aclist
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
### WARNING: When posting log/config data, redact any sensitive information like API keys.
|
||||||
|
- type: dropdown
|
||||||
|
id: device
|
||||||
|
attributes:
|
||||||
|
label: Device type
|
||||||
|
options:
|
||||||
|
- Desktop PC
|
||||||
|
- Steam Deck
|
||||||
|
default: 0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: DZGUI version
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
placeholder: "5.6.0"
|
||||||
|
- type: input
|
||||||
|
id: distro
|
||||||
|
attributes:
|
||||||
|
label: Linux distribution
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
placeholder: "Arch Linux"
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: Describe the issue
|
||||||
|
description: A detailed description. What did you expect to happen?
|
||||||
|
placeholder: "Include as much detail as possible and state the steps leading up to the problem. E.g., First I do this, then I do this, then this happens."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Additional log output
|
||||||
|
description: Attach the file `$HOME/.local/state/dzgui/DZGUI_DEBUG.log` by clicking this area to highlight it and then dragging the file in.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
name: Mirror to Codeberg
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirror-to-codeberg:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: pixta-dev/repository-mirroring-action@v1
|
||||||
|
if: ${{ vars.GIT_REMOTE != '' }}
|
||||||
|
with:
|
||||||
|
target_repo_url: ${{ vars.GIT_REMOTE }}
|
||||||
|
ssh_private_key: ${{ secrets.GIT_SSH_PRIVATE_KEY }}
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
- Early load alerts button
|
- Early load alerts button
|
||||||
- Preconnect dialog
|
- Preconnect dialog
|
||||||
- Preconnect warnings/failsafes like filesize
|
- Preconnect warnings/failsafes like filesize
|
||||||
|
- Save filters per server context between sessions
|
||||||
|
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|||||||
+10
-5
@@ -1,18 +1,23 @@
|
|||||||
# TODO: link to here from sphinx docs
|
# TODO: link to here from sphinx docs
|
||||||
|
|
||||||
|
# Installing dev dependencies
|
||||||
|
uv pip install -e .[dev]
|
||||||
|
|
||||||
# Setting up precommit hooks
|
# Setting up precommit hooks
|
||||||
pre-commit install
|
pre-commit install
|
||||||
|
|
||||||
# Fetching submodules
|
# Fetching submodules
|
||||||
git submodule update --recursive --init
|
git submodule update --recursive --init
|
||||||
|
|
||||||
# Installing dev dependencies
|
|
||||||
uv pip install -e .[dev]
|
|
||||||
|
|
||||||
# Building documentation
|
# Building documentation
|
||||||
sphinx-build -M html source build -a
|
sphinx-build -M html source build -a
|
||||||
|
|
||||||
# Building standalone DZGUI release binaries
|
# Building standalone DZGUI release binaries
|
||||||
1. Run `./scripts/dl_pyapp.sh`
|
1. Run `./scripts/dl_pyapp.sh`
|
||||||
2. Run `python3 scripts/release.py`
|
# TODO: secondary script that packs this archive
|
||||||
3. Generate a release and tag against the bundled `dzgui` executable
|
2. Download cpython 3.13, linux, gnu, v3
|
||||||
|
https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz
|
||||||
|
3. Explicitly install dependencies into site-packages
|
||||||
|
4. Pack back into a tarball
|
||||||
|
5. Run `python3 scripts/release.py`
|
||||||
|
6. Generate a release and tag against the bundled `dzgui` executable
|
||||||
+1
-1
@@ -34,7 +34,7 @@ DZG-004: On Steam Deck, some mods in the Workshop show a black screen when DZGUI
|
|||||||
---------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------------
|
||||||
This is a bug in the Steam client that is being tracked at Valve's Steam for Linux issue tracker here: https://github.com/ValveSoftware/steam-for-linux/issues/9598.
|
This is a bug in the Steam client that is being tracked at Valve's Steam for Linux issue tracker here: https://github.com/ValveSoftware/steam-for-linux/issues/9598.
|
||||||
|
|
||||||
To resolve this issue, manually intervene in the Steam client by selecting a different context (e.g., Store, Library), waiting for it to load, then navigating back to the Workshop context. This should
|
To resolve this issue, manually intervene in the Steam client by selecting a different context (e.g., Store, Library), waiting for it to load, then navigating back to the Workshop context. This should
|
||||||
clear the blockage and allow the contents to render.
|
clear the blockage and allow the contents to render.
|
||||||
|
|
||||||
.. _DZG-005:
|
.. _DZG-005:
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class A2SInfo:
|
|||||||
|
|
||||||
def get_netmask() -> str:
|
def get_netmask() -> str:
|
||||||
hostname = os.uname()[1]
|
hostname = os.uname()[1]
|
||||||
i = socket.gethostbyname(hostname)
|
i = socket.gethostbyname(f"{hostname}.local")
|
||||||
netmask = i.rsplit(".", 1)[0]
|
netmask = i.rsplit(".", 1)[0]
|
||||||
return netmask
|
return netmask
|
||||||
|
|
||||||
@@ -472,6 +472,7 @@ def query_by_ip(addr: str) -> A2SInfo:
|
|||||||
return A2SInfo(Record("0", 0, 0), None)
|
return A2SInfo(Record("0", 0, 0), None)
|
||||||
return query_by_record(record, update_gameport=True)
|
return query_by_record(record, update_gameport=True)
|
||||||
|
|
||||||
|
|
||||||
def query_by_id(server_id: int, key: str) -> A2SInfo:
|
def query_by_id(server_id: int, key: str) -> A2SInfo:
|
||||||
"""
|
"""
|
||||||
Used with numeric Battlemetrics IDs
|
Used with numeric Battlemetrics IDs
|
||||||
|
|||||||
+3
-3
@@ -17,6 +17,7 @@ class Xdg:
|
|||||||
system: Path
|
system: Path
|
||||||
debug: Path
|
debug: Path
|
||||||
ips: Path
|
ips: Path
|
||||||
|
filters: Path
|
||||||
|
|
||||||
|
|
||||||
def is_writeable(path_str: str) -> bool:
|
def is_writeable(path_str: str) -> bool:
|
||||||
@@ -63,9 +64,6 @@ def get_xdg_paths() -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def parse_filepaths(xdg: dict) -> Xdg:
|
def parse_filepaths(xdg: dict) -> Xdg:
|
||||||
# cache = xdg["XDG_CACHE_HOME"]
|
|
||||||
# data = xdg["XDG_DATA_HOME"]
|
|
||||||
|
|
||||||
config = xdg["XDG_CONFIG_HOME"]
|
config = xdg["XDG_CONFIG_HOME"]
|
||||||
state = xdg["XDG_STATE_HOME"]
|
state = xdg["XDG_STATE_HOME"]
|
||||||
|
|
||||||
@@ -80,6 +78,7 @@ def parse_filepaths(xdg: dict) -> Xdg:
|
|||||||
history = state / "dzg.history"
|
history = state / "dzg.history"
|
||||||
versions = state / "dzg.versions"
|
versions = state / "dzg.versions"
|
||||||
ips = state / "ips.csv"
|
ips = state / "ips.csv"
|
||||||
|
filters = state / "dzg.filters.json"
|
||||||
|
|
||||||
return Xdg(
|
return Xdg(
|
||||||
config,
|
config,
|
||||||
@@ -91,4 +90,5 @@ def parse_filepaths(xdg: dict) -> Xdg:
|
|||||||
system,
|
system,
|
||||||
debug,
|
debug,
|
||||||
ips,
|
ips,
|
||||||
|
filters
|
||||||
)
|
)
|
||||||
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
# TODO: if servers fail to load, may leave dangling widgets waiting for a signal
|
# TODO: if servers fail to load, may leave dangling widgets waiting for a signal
|
||||||
|
|
||||||
|
|
||||||
class Emitter(GObject.GObject):
|
class Emitter(GObject.GObject):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -150,3 +151,6 @@ class Emitter(GObject.GObject):
|
|||||||
def keyword_set(self, keyword: str) -> None:
|
def keyword_set(self, keyword: str) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
|
||||||
|
def lan_page_initialized(self) -> None:
|
||||||
|
pass
|
||||||
+30
-11
@@ -84,7 +84,6 @@ class Controller(GObject.GObject):
|
|||||||
self.emitter = Emitter()
|
self.emitter = Emitter()
|
||||||
self.emitter.connect("map_selection_changed", self._on_map_selection_changed)
|
self.emitter.connect("map_selection_changed", self._on_map_selection_changed)
|
||||||
self.emitter.connect("check_toggled", self._on_check_toggled)
|
self.emitter.connect("check_toggled", self._on_check_toggled)
|
||||||
self.emitter.connect("servers_loaded_init", self._on_servers_loaded_init)
|
|
||||||
|
|
||||||
# NOTE: suppress requests until entire UI is loaded
|
# NOTE: suppress requests until entire UI is loaded
|
||||||
self.loaded = False
|
self.loaded = False
|
||||||
@@ -260,6 +259,21 @@ class Controller(GObject.GObject):
|
|||||||
def select_colorized(self) -> None:
|
def select_colorized(self) -> None:
|
||||||
self.mod_man.select_colorized()
|
self.mod_man.select_colorized()
|
||||||
|
|
||||||
|
def get_all_tree_filters(self) -> dict[str, dict[str, bool]]:
|
||||||
|
servers = self.get_servers()
|
||||||
|
all_filters: dict[str, dict[str, bool]] = {}
|
||||||
|
trees = (
|
||||||
|
servers.get_browser(),
|
||||||
|
servers.get_saved(),
|
||||||
|
servers.get_recent(),
|
||||||
|
servers.get_lan(),
|
||||||
|
)
|
||||||
|
for tree in trees:
|
||||||
|
enum = str(tree.get_enum())
|
||||||
|
filters = tree.get_filter_man().get_filters()
|
||||||
|
all_filters[enum] = filters
|
||||||
|
return all_filters
|
||||||
|
|
||||||
def get_filter_man(self) -> "FilterManager":
|
def get_filter_man(self) -> "FilterManager":
|
||||||
"""Each ServerTreeView has an atomic FilterManager"""
|
"""Each ServerTreeView has an atomic FilterManager"""
|
||||||
return self.get_active_treeview().get_filter_man()
|
return self.get_active_treeview().get_filter_man()
|
||||||
@@ -303,8 +317,22 @@ class Controller(GObject.GObject):
|
|||||||
self.emitter.emit("servers_loaded", tv.get_enum())
|
self.emitter.emit("servers_loaded", tv.get_enum())
|
||||||
return
|
return
|
||||||
self.mediator.statusbar.set_by_context(tv.get_enum(), "")
|
self.mediator.statusbar.set_by_context(tv.get_enum(), "")
|
||||||
|
self.mediator.filters.button_grid.block_toggles(True)
|
||||||
|
self.populate_filter_prefs()
|
||||||
|
self.mediator.filters.button_grid.block_toggles(False)
|
||||||
|
self.emitter.emit("lan_page_initialized")
|
||||||
ServerModelManager(self, tv).load()
|
ServerModelManager(self, tv).load()
|
||||||
|
|
||||||
|
def populate_filter_prefs(self) -> None:
|
||||||
|
tv = self.get_active_treeview()
|
||||||
|
filters = self.config_man.get_filters(tv)
|
||||||
|
if filters is None:
|
||||||
|
return
|
||||||
|
filter_man = self.get_filter_man()
|
||||||
|
for filt, state in filters.items():
|
||||||
|
filter_man.set_filter(filt, state)
|
||||||
|
self.mediator.filters.set_filters(filters)
|
||||||
|
|
||||||
def get_dist_cache(self) -> dict[str, "Haversine"]:
|
def get_dist_cache(self) -> dict[str, "Haversine"]:
|
||||||
return self.dist_cache
|
return self.dist_cache
|
||||||
|
|
||||||
@@ -319,16 +347,7 @@ class Controller(GObject.GObject):
|
|||||||
filter_man = self.get_filter_man()
|
filter_man = self.get_filter_man()
|
||||||
return filter_man.get_filters()
|
return filter_man.get_filters()
|
||||||
|
|
||||||
# TODO: rename
|
# FIXME: wipe maps store when changing tabs if model is none
|
||||||
def _on_servers_loaded_init(self, emitter: "Emitter") -> None:
|
|
||||||
"""Triggered after servers load but prior to maps loading"""
|
|
||||||
# FIXME: wipe maps store when changing tabs if model is none
|
|
||||||
# e.g. select recent, toggle map, then select lan -> not wiped
|
|
||||||
tv = self.get_active_treeview()
|
|
||||||
if tv.loaded is False:
|
|
||||||
return
|
|
||||||
store = self.get_map_store()
|
|
||||||
self.emitter.emit("load_maps", store)
|
|
||||||
|
|
||||||
def has_server_model(self) -> bool:
|
def has_server_model(self) -> bool:
|
||||||
treeview = self.get_active_treeview()
|
treeview = self.get_active_treeview()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
- Early load alerts button
|
- Early load alerts button
|
||||||
- Preconnect dialog
|
- Preconnect dialog
|
||||||
- Preconnect warnings/failsafes like filesize
|
- Preconnect warnings/failsafes like filesize
|
||||||
|
- Save filters per server context between sessions
|
||||||
|
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|||||||
+21
-9
@@ -1,6 +1,7 @@
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
from dzgui.const.constants import LEGACY_CONFIG_PATH, LEGACY_COLS_PATH, LEGACY_IPS_PATH
|
from dzgui.const.constants import 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
|
||||||
@@ -17,17 +18,28 @@ def has_new_config(config: Path) -> bool:
|
|||||||
return config.exists()
|
return config.exists()
|
||||||
|
|
||||||
|
|
||||||
def migrate_cols_file(res: Path) -> None:
|
def convert_cols_file(res: Path) -> Any | None:
|
||||||
# NOTE: dzg.columns.json is API 7 spec
|
j = read_json(res)
|
||||||
old_res = Path.home() / LEGACY_COLS_PATH
|
cols = j["cols"]
|
||||||
if old_res.is_file():
|
# NOTE: implies prior conversion
|
||||||
j = read_json(old_res)
|
if "View" in cols:
|
||||||
cols = j["cols"]
|
return None
|
||||||
if "View" in cols:
|
# NOTE: user may not have changed these widths in API 6
|
||||||
return
|
try:
|
||||||
cols["View"] = cols.pop("Perspective")
|
cols["View"] = cols.pop("Perspective")
|
||||||
cols["Max"] = cols.pop("Maximum")
|
cols["Max"] = cols.pop("Maximum")
|
||||||
write_json(j, res)
|
except Exception:
|
||||||
|
pass
|
||||||
|
return j
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_cols_file(res: Path) -> None:
|
||||||
|
# NOTE: filename "dzg.columns.json" is API 7 spec
|
||||||
|
old_res = Path.home() / LEGACY_COLS_PATH
|
||||||
|
if old_res.is_file():
|
||||||
|
j = convert_cols_file(old_res)
|
||||||
|
if j is not None:
|
||||||
|
write_json(j, res)
|
||||||
|
|
||||||
|
|
||||||
def copy_state_files(state_path: Path) -> None:
|
def copy_state_files(state_path: Path) -> None:
|
||||||
|
|||||||
+11
-4
@@ -112,14 +112,15 @@ def main() -> None:
|
|||||||
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
||||||
# TODO: add logging inside wizard
|
# TODO: add logging inside wizard
|
||||||
SetupWizard(_is_steam_deck, XDG.config)
|
SetupWizard(_is_steam_deck, XDG.config)
|
||||||
|
|
||||||
|
# NOTE: implies that setup wizard failed or was closed
|
||||||
|
if has_new_config(XDG.config) is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
setup_logger(XDG.debug)
|
setup_logger(XDG.debug)
|
||||||
with open(XDG.debug, "w") as f:
|
with open(XDG.debug, "w") as f:
|
||||||
f.truncate(0)
|
f.truncate(0)
|
||||||
|
|
||||||
latest_release = check_updates(version)
|
|
||||||
|
|
||||||
if _is_steam_deck is False:
|
if _is_steam_deck is False:
|
||||||
# TODO: sudo escalation dialog
|
# TODO: sudo escalation dialog
|
||||||
# count = get_map_count()
|
# count = get_map_count()
|
||||||
@@ -127,16 +128,22 @@ def main() -> None:
|
|||||||
if has_steam_client() is False:
|
if has_steam_client() is False:
|
||||||
EarlyAlertDialog(init.requires_steam)
|
EarlyAlertDialog(init.requires_steam)
|
||||||
|
|
||||||
# NOTE: clear versions file of unlinked mods
|
is_dayz_installed(XDG.config)
|
||||||
|
|
||||||
|
# TODO: slow running procs here--needs dialog
|
||||||
|
# NOTE: clears versions file of unlinked mods
|
||||||
rebuild_symlinks(XDG.config)
|
rebuild_symlinks(XDG.config)
|
||||||
remove_stale_signatures(XDG.config, XDG.version)
|
remove_stale_signatures(XDG.config, XDG.version)
|
||||||
|
|
||||||
is_dayz_installed(XDG.config)
|
|
||||||
# TODO: handle IP DB failure and use coords fallback
|
# TODO: handle IP DB failure and use coords fallback
|
||||||
|
# TODO: drop this after dialog is complete
|
||||||
|
print("Fetching geolocation data, may take some time...")
|
||||||
get_ipdb(XDG.ips)
|
get_ipdb(XDG.ips)
|
||||||
local_coords = get_local_coords(XDG.ips)
|
local_coords = get_local_coords(XDG.ips)
|
||||||
use_miles = lookup(XDG.config, Preferences.DIST)
|
use_miles = lookup(XDG.config, Preferences.DIST)
|
||||||
|
|
||||||
|
latest_release = check_updates(version)
|
||||||
|
|
||||||
prefs = UserPrefs(
|
prefs = UserPrefs(
|
||||||
is_steam_deck=_is_steam_deck,
|
is_steam_deck=_is_steam_deck,
|
||||||
is_game_mode=_is_game_mode,
|
is_game_mode=_is_game_mode,
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import gi
|
|||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository.Gtk import main_quit # noqa E402
|
from gi.repository.Gtk import main_quit # noqa E402
|
||||||
|
|
||||||
# import dzgui.api.servers as Servers
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.config.userprefs import UserPrefs
|
from dzgui.config.userprefs import UserPrefs
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -160,6 +159,9 @@ class ConfigManager:
|
|||||||
|
|
||||||
def save_res_and_quit(self, tv: "ServerTreeView", window: "OuterWindow") -> None:
|
def save_res_and_quit(self, tv: "ServerTreeView", window: "OuterWindow") -> None:
|
||||||
columns = tv.get_columns()
|
columns = tv.get_columns()
|
||||||
|
filters = self.controller.get_all_tree_filters()
|
||||||
|
filters_file = self.prefs.paths.filters
|
||||||
|
|
||||||
columns_file = self.prefs.paths.columns
|
columns_file = self.prefs.paths.columns
|
||||||
try:
|
try:
|
||||||
data = read_json(columns_file)
|
data = read_json(columns_file)
|
||||||
@@ -174,6 +176,7 @@ class ConfigManager:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
write_json(data, columns_file)
|
write_json(data, columns_file)
|
||||||
|
write_json(filters, filters_file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
|
|
||||||
@@ -217,3 +220,12 @@ class ConfigManager:
|
|||||||
h = WINDOW_DEFAULT_Y
|
h = WINDOW_DEFAULT_Y
|
||||||
logger.info(f"Using default window size {w},{h}")
|
logger.info(f"Using default window size {w},{h}")
|
||||||
window.set_default_size(w, h)
|
window.set_default_size(w, h)
|
||||||
|
|
||||||
|
def get_filters(self, tv: "ServerTreeView") -> Any | None:
|
||||||
|
filters = self.prefs.paths.filters
|
||||||
|
try:
|
||||||
|
enum = tv.get_enum()
|
||||||
|
filters = read_json(filters)
|
||||||
|
return filters[str(enum)]
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
@@ -161,7 +161,7 @@ class ConnectionManager:
|
|||||||
if len(self.missing_mods) > 0:
|
if len(self.missing_mods) > 0:
|
||||||
required_size = sum(row[2] for row in self.missing_mods)
|
required_size = sum(row[2] for row in self.missing_mods)
|
||||||
required_mib = format_mib(required_size)
|
required_mib = format_mib(required_size)
|
||||||
free_mib = format_mib(required_size)
|
free_mib = format_mib(free)
|
||||||
|
|
||||||
dayz_running = is_dayz_running()
|
dayz_running = is_dayz_running()
|
||||||
|
|
||||||
@@ -222,7 +222,8 @@ class ConnectionManager:
|
|||||||
def query_modlist_and_present(self, record: Servers.Record) -> None:
|
def query_modlist_and_present(self, record: Servers.Record) -> None:
|
||||||
try:
|
try:
|
||||||
mods = self._query_modlist(record)
|
mods = self._query_modlist(record)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
print(f"{type(e).__name__}: {e}")
|
||||||
self.thread_man.set_cleanup_func(
|
self.thread_man.set_cleanup_func(
|
||||||
StoredFunc(self._server_timeout), destroy_first=True
|
StoredFunc(self._server_timeout), destroy_first=True
|
||||||
)
|
)
|
||||||
@@ -279,8 +280,13 @@ class ConnectionManager:
|
|||||||
|
|
||||||
for title, mod, stamp, size in self.missing_mods:
|
for title, mod, stamp, size in self.missing_mods:
|
||||||
# TODO: check cancel and exit events
|
# TODO: check cancel and exit events
|
||||||
|
if self.controller.get_exit_event().is_set():
|
||||||
|
return
|
||||||
|
if self.controller.get_cancel_event().is_set():
|
||||||
|
self.controller.clear_cancel_event()
|
||||||
|
return
|
||||||
enqueue_mod(mod, self.appid)
|
enqueue_mod(mod, self.appid)
|
||||||
time.sleep(2)
|
time.sleep(3)
|
||||||
|
|
||||||
if raise_window is True:
|
if raise_window is True:
|
||||||
logger.info("Bringing window to foreground")
|
logger.info("Bringing window to foreground")
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ logger = logging.getLogger(APP_NAME)
|
|||||||
class NoteManager:
|
class NoteManager:
|
||||||
def __init__(self, controller: "Controller", path: "Path") -> None:
|
def __init__(self, controller: "Controller", path: "Path") -> None:
|
||||||
self.notes_path = path
|
self.notes_path = path
|
||||||
self.cache: dict[str, str] = read_json(self.notes_path)
|
try:
|
||||||
|
self.cache: dict[str, str] = read_json(self.notes_path)
|
||||||
|
except Exception:
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
def add_note(self, server: str, note: str) -> None:
|
def add_note(self, server: str, note: str) -> None:
|
||||||
self.cache[server] = note
|
self.cache[server] = note
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from dzgui.const.constants import (
|
||||||
|
APP_NAME,
|
||||||
|
TMP_EXE,
|
||||||
|
TMP_PATH,
|
||||||
|
TMP_TARBALL,
|
||||||
|
)
|
||||||
|
from dzgui.strings import dialogs
|
||||||
|
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
||||||
|
from dzgui.views.dialogs.generic import ExceptionDialog, QuitDialog
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, GLib # noqa E402
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateManager:
|
||||||
|
def __init__(self, controller: "Controller") -> None:
|
||||||
|
|
||||||
|
self.thread_man = ThreadingManager(controller)
|
||||||
|
self.controller = controller
|
||||||
|
|
||||||
|
@call_on_thread(dialogs.fetching_update)
|
||||||
|
def update_version(self, exe_path: str, url: str) -> None:
|
||||||
|
try:
|
||||||
|
res = requests.get(url)
|
||||||
|
if res.status_code == 200:
|
||||||
|
with open(TMP_TARBALL, "wb") as file:
|
||||||
|
file.write(res.content)
|
||||||
|
with tarfile.open(TMP_TARBALL) as tar:
|
||||||
|
tar.extractall(TMP_PATH)
|
||||||
|
|
||||||
|
shutil.move(TMP_EXE, exe_path)
|
||||||
|
|
||||||
|
proc = subprocess.run([exe_path, "self", "restore"])
|
||||||
|
if proc.returncode == 0:
|
||||||
|
func = StoredFunc(self._on_update_success)
|
||||||
|
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
||||||
|
else:
|
||||||
|
msg = dialogs.failed_to_update
|
||||||
|
func = StoredFunc(self._on_update_failure, msg)
|
||||||
|
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
||||||
|
except Exception as e:
|
||||||
|
func = StoredFunc(self._on_update_failure, e)
|
||||||
|
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
||||||
|
logger.warning(e)
|
||||||
|
|
||||||
|
def _on_update_success(self) -> None:
|
||||||
|
msg = dialogs.update_success
|
||||||
|
dialog = QuitDialog(self.controller, msg)
|
||||||
|
dialog.run()
|
||||||
|
|
||||||
|
def _on_update_failure(self, msg: str) -> None:
|
||||||
|
dialog = ExceptionDialog(self.controller, msg)
|
||||||
|
dialog.run()
|
||||||
+21
-13
@@ -73,6 +73,7 @@ class ServerModelManager:
|
|||||||
self._dump_history()
|
self._dump_history()
|
||||||
case ServerTab.LAN:
|
case ServerTab.LAN:
|
||||||
# NOTE: LAN tab is only loaded on demand
|
# NOTE: LAN tab is only loaded on demand
|
||||||
|
self.emitter.emit("lan_page_initialized")
|
||||||
pass
|
pass
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
@@ -123,8 +124,10 @@ class ServerModelManager:
|
|||||||
parsed = Servers.parse_json(servers)
|
parsed = Servers.parse_json(servers)
|
||||||
self._push_data(parsed)
|
self._push_data(parsed)
|
||||||
|
|
||||||
@call_on_thread(dialog.scanning)
|
@call_on_thread(dialog.scanning, show_cancel=True)
|
||||||
def dump_lan(self, port: int, early_abort: bool) -> None:
|
def dump_lan(self, port: int, early_abort: bool) -> None:
|
||||||
|
self.first_iteration = True
|
||||||
|
|
||||||
servers = []
|
servers = []
|
||||||
ports = range(1, 256)
|
ports = range(1, 256)
|
||||||
failure_func = StoredFunc(self._cleanup_on_failure)
|
failure_func = StoredFunc(self._cleanup_on_failure)
|
||||||
@@ -135,19 +138,20 @@ class ServerModelManager:
|
|||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
if self.controller.get_exit_event().is_set():
|
if self.controller.get_exit_event().is_set():
|
||||||
|
event.set()
|
||||||
|
return
|
||||||
|
if self.controller.get_cancel_event().is_set():
|
||||||
|
event.set()
|
||||||
|
self.controller.clear_cancel_event()
|
||||||
return
|
return
|
||||||
res = future.result(timeout=LAN_TIMEOUT)
|
res = future.result(timeout=LAN_TIMEOUT)
|
||||||
if res is None:
|
if res is None:
|
||||||
continue
|
continue
|
||||||
|
servers.append(res)
|
||||||
if early_abort is True:
|
if early_abort is True:
|
||||||
# NOTE: on first non-empty hit, flag pending threads to close
|
# NOTE: on first non-empty hit, flag pending threads to close
|
||||||
event.set()
|
event.set()
|
||||||
servers.append(res)
|
break
|
||||||
self.thread_man.set_cleanup_func(
|
|
||||||
StoredFunc(self._cleanup_on_success)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
servers.append(res)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
self.thread_man.set_cleanup_func(failure_func, destroy_first=True)
|
self.thread_man.set_cleanup_func(failure_func, destroy_first=True)
|
||||||
@@ -239,9 +243,11 @@ class ServerModelManager:
|
|||||||
filter_man = self.tv.get_filter_man()
|
filter_man = self.tv.get_filter_man()
|
||||||
maps = self._get_new_maps()
|
maps = self._get_new_maps()
|
||||||
filter_man.set_unique_maps(maps)
|
filter_man.set_unique_maps(maps)
|
||||||
|
store = filter_man.get_map_store()
|
||||||
|
|
||||||
self.first_iteration = False
|
self.first_iteration = False
|
||||||
self.emitter.emit("servers_loaded_init")
|
self.emitter.emit("load_maps", store)
|
||||||
|
# self.emitter.emit("servers_loaded_init")
|
||||||
|
|
||||||
def add_to_history(self, record: dict[str, Any]) -> None:
|
def add_to_history(self, record: dict[str, Any]) -> None:
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
@@ -318,6 +324,7 @@ class ServerModelManager:
|
|||||||
self.thread_man.set_cleanup_func(func)
|
self.thread_man.set_cleanup_func(func)
|
||||||
return
|
return
|
||||||
config_man.add_saved_server(fqip)
|
config_man.add_saved_server(fqip)
|
||||||
|
|
||||||
# FIXME: this is valid if saved servers tab is already open,
|
# FIXME: this is valid if saved servers tab is already open,
|
||||||
# but not if app was just booted; causes single row to appear prematurely
|
# but not if app was just booted; causes single row to appear prematurely
|
||||||
if proxy_man.has_control_model() is False:
|
if proxy_man.has_control_model() is False:
|
||||||
@@ -368,30 +375,31 @@ class ServerModelManager:
|
|||||||
proxy = self._get_proxy_man().get_proxy_model()
|
proxy = self._get_proxy_man().get_proxy_model()
|
||||||
self.tv.set_model(proxy)
|
self.tv.set_model(proxy)
|
||||||
|
|
||||||
self.emitter.emit("servers_loaded", self.enum)
|
if self.controller.get_active_treeview().get_enum == ServerTab.SAVED:
|
||||||
|
self.emitter.emit("servers_loaded", self.enum)
|
||||||
|
|
||||||
filter_man = self.tv.get_filter_man()
|
filter_man = self.tv.get_filter_man()
|
||||||
# NOTE: maps are set outside of thread because it triggers map changed signals
|
# NOTE: maps are set outside of thread because it triggers map changed signals
|
||||||
maps = self._get_new_maps()
|
maps = self._get_new_maps()
|
||||||
filter_man.set_unique_maps(maps)
|
filter_man.set_unique_maps(maps)
|
||||||
|
store = filter_man.get_map_store()
|
||||||
|
self.emitter.emit("load_maps", store)
|
||||||
|
|
||||||
self.first_iteration = False
|
self.first_iteration = False
|
||||||
self.emitter.emit("servers_loaded_init")
|
|
||||||
self.emitter.emit("saved_servers_changed")
|
self.emitter.emit("saved_servers_changed")
|
||||||
|
|
||||||
def _update_maps(self) -> None:
|
def _update_maps(self) -> None:
|
||||||
filter_man = self.tv.get_filter_man()
|
filter_man = self.tv.get_filter_man()
|
||||||
filter_man.set_unique_maps(self._get_new_maps())
|
filter_man.set_unique_maps(self._get_new_maps())
|
||||||
self.emitter.emit("servers_loaded_init")
|
store = filter_man.get_map_store()
|
||||||
|
self.emitter.emit("load_maps", store)
|
||||||
self.first_iteration = False
|
self.first_iteration = False
|
||||||
|
|
||||||
def _cleanup_on_success(self) -> None:
|
def _cleanup_on_success(self) -> None:
|
||||||
proxy = self._get_proxy_man().get_proxy_model()
|
proxy = self._get_proxy_man().get_proxy_model()
|
||||||
self.tv.set_model(proxy)
|
self.tv.set_model(proxy)
|
||||||
|
|
||||||
# TODO: servers_loaded vs servers_reloaded
|
|
||||||
self.emitter.emit("servers_loaded", self.enum)
|
self.emitter.emit("servers_loaded", self.enum)
|
||||||
|
|
||||||
if self.first_iteration:
|
if self.first_iteration:
|
||||||
self._update_maps()
|
self._update_maps()
|
||||||
|
|
||||||
|
|||||||
@@ -67,4 +67,4 @@ label_client = "Steam client"
|
|||||||
|
|
||||||
### Completion page
|
### Completion page
|
||||||
heading_completion = "Setup complete"
|
heading_completion = "Setup complete"
|
||||||
blurb_completion = "Configuration completed successfully. Please exit and restart DZGUI to apply changes."
|
blurb_completion = "Configuration completed successfully. Click 'Close' to close this dialog and launch DZGUI."
|
||||||
+5
-7
@@ -2,19 +2,17 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def read_json(path: Path) -> Any:
|
def read_json(path: Path) -> Any:
|
||||||
try:
|
try:
|
||||||
with open(path, "r") as infile:
|
with open(path, "r") as infile:
|
||||||
try:
|
data = json.load(infile)
|
||||||
data = json.load(infile)
|
return data
|
||||||
return data
|
except Exception as e:
|
||||||
except json.decoder.JSONDecodeError as e:
|
|
||||||
raise e
|
|
||||||
except OSError as e:
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def write_json(data: dict, path: Path) -> None:
|
def write_json(data: dict[str, Any], path: Path) -> None:
|
||||||
try:
|
try:
|
||||||
j = json.dumps(data, indent=2)
|
j = json.dumps(data, indent=2)
|
||||||
path.write_text(j)
|
path.write_text(j)
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ build_corrupted = (
|
|||||||
"Steam settings or DayZ installation may be corrupted. Try restarting Steam."
|
"Steam settings or DayZ installation may be corrupted. Try restarting Steam."
|
||||||
)
|
)
|
||||||
api_warn_msg = """No servers returned. Please wait and try again.
|
api_warn_msg = """No servers returned. Please wait and try again.
|
||||||
If this issue persists, your API key may be defunct or your network is blocking requests.
|
This usually indicates a transitory network timeout. If this issue persists, your API key or network may be malfunctioning.
|
||||||
"""
|
"""
|
||||||
server_timeout = "Timed out when querying server, check IP or try again later."
|
server_timeout = "Timed out when querying server, check IP or try again later."
|
||||||
server_error = (
|
server_error = (
|
||||||
|
|||||||
+10
-7
@@ -43,11 +43,14 @@ def clone_symlinks(steam_path: Path) -> None:
|
|||||||
|
|
||||||
# TODO: test these two operations
|
# TODO: test these two operations
|
||||||
# use less time intensive logic like the above
|
# use less time intensive logic like the above
|
||||||
for file in exp_path.iterdir():
|
try:
|
||||||
if file.is_symlink():
|
for file in exp_path.iterdir():
|
||||||
file.unlink()
|
if file.is_symlink():
|
||||||
|
file.unlink()
|
||||||
|
|
||||||
for file in dayz_path.iterdir():
|
for file in dayz_path.iterdir():
|
||||||
if file.is_symlink():
|
if file.is_symlink():
|
||||||
uid = file.name
|
uid = file.name
|
||||||
Path(exp_path / uid).symlink_to(dayz_path / uid)
|
Path(exp_path / uid).symlink_to(dayz_path / uid)
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
+1
-1
@@ -262,8 +262,8 @@ class Grid(Gtk.Grid):
|
|||||||
|
|
||||||
self.emitter.connect("server_page_toggled", self.toggle_filter_panels)
|
self.emitter.connect("server_page_toggled", self.toggle_filter_panels)
|
||||||
|
|
||||||
|
# TODO: drop, use map/signal
|
||||||
def hide_widgets_on_init(self) -> None:
|
def hide_widgets_on_init(self) -> None:
|
||||||
self.conpan.set_lan_visible(False)
|
|
||||||
self.right_panel.sel_panel.hide()
|
self.right_panel.sel_panel.hide()
|
||||||
|
|
||||||
def toggle_filter_panels(self, emitter: "Emitter", state: bool) -> None:
|
def toggle_filter_panels(self, emitter: "Emitter", state: bool) -> None:
|
||||||
|
|||||||
@@ -94,9 +94,11 @@ class RefreshButton(IconTextButton):
|
|||||||
icon=REFRESH_ICON,
|
icon=REFRESH_ICON,
|
||||||
label=atomic_buttons.refresh,
|
label=atomic_buttons.refresh,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
emitter = self.controller.get_emitter()
|
emitter = self.controller.get_emitter()
|
||||||
self.loading = False
|
self.loading = False
|
||||||
|
self.is_clicked = False
|
||||||
|
|
||||||
self.time = 30
|
self.time = 30
|
||||||
|
|
||||||
@@ -110,17 +112,21 @@ class RefreshButton(IconTextButton):
|
|||||||
|
|
||||||
def _on_refresh_clicked(self, button: Self) -> None:
|
def _on_refresh_clicked(self, button: Self) -> None:
|
||||||
"""Spawned in a thread"""
|
"""Spawned in a thread"""
|
||||||
self.loading = True
|
self.is_clicked = True
|
||||||
self.controller.refresh_tree()
|
self.controller.refresh_tree()
|
||||||
# TODO: get server tab enum
|
# TODO: get server tab enum
|
||||||
# if LAN tab, reload existing entries in place
|
# if LAN tab, reload existing entries in place
|
||||||
|
|
||||||
def start_decrement(self, emitter: "Emitter", tab: "ServerTab") -> None:
|
def start_decrement(self, emitter: "Emitter", tab: "ServerTab") -> None:
|
||||||
|
if self.is_clicked is False:
|
||||||
|
return
|
||||||
if self.loading:
|
if self.loading:
|
||||||
self.set_sensitive(False)
|
return
|
||||||
self.loading = False
|
self.set_sensitive(False)
|
||||||
self.show_time(True)
|
self.loading = True
|
||||||
GLib.timeout_add_seconds(1, self.decrement)
|
self.is_clicked = False
|
||||||
|
self.show_time(True)
|
||||||
|
GLib.timeout_add_seconds(1, self.decrement)
|
||||||
|
|
||||||
def decrement(self) -> bool:
|
def decrement(self) -> bool:
|
||||||
self.time -= 1
|
self.time -= 1
|
||||||
@@ -128,6 +134,7 @@ class RefreshButton(IconTextButton):
|
|||||||
self.time = 30
|
self.time = 30
|
||||||
self.show_time(False)
|
self.show_time(False)
|
||||||
self.set_sensitive(True)
|
self.set_sensitive(True)
|
||||||
|
self.loading = False
|
||||||
return False
|
return False
|
||||||
self.show_time(True)
|
self.show_time(True)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -278,9 +278,7 @@ class ConnectPanel(Gtk.Box):
|
|||||||
|
|
||||||
for el in self.lan, self.fav, self.add_panel:
|
for el in self.lan, self.fav, self.add_panel:
|
||||||
self.add(el)
|
self.add(el)
|
||||||
|
self.lan.set_visible(False)
|
||||||
def set_lan_visible(self, state: bool) -> None:
|
|
||||||
self.lan.set_visible(state)
|
|
||||||
|
|
||||||
def _on_lan_tab_toggled(self, emitter: "Emitter", state: bool) -> None:
|
def _on_lan_tab_toggled(self, emitter: "Emitter", state: bool) -> None:
|
||||||
self.set_lan_visible(state)
|
self.lan.set_visible(state)
|
||||||
@@ -68,6 +68,7 @@ class ButtonGrid(Gtk.Grid):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def reload_filters(self) -> None:
|
def reload_filters(self) -> None:
|
||||||
|
# TODO: may be superfluous
|
||||||
checkboxes = self.checks
|
checkboxes = self.checks
|
||||||
self.block_toggles(True)
|
self.block_toggles(True)
|
||||||
filters = self.controller.get_enabled_filters()
|
filters = self.controller.get_enabled_filters()
|
||||||
@@ -330,3 +331,8 @@ class FilterPanel(Gtk.Box):
|
|||||||
self.block_map_change_propagation = False
|
self.block_map_change_propagation = False
|
||||||
return
|
return
|
||||||
self.emitter.emit("map_selection_changed", name)
|
self.emitter.emit("map_selection_changed", name)
|
||||||
|
|
||||||
|
def set_filters(self, filters: dict[str, bool]) -> None:
|
||||||
|
for check in self.button_grid.checks:
|
||||||
|
label = check.get_label()
|
||||||
|
check.set_active(filters[label])
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import tarfile
|
|
||||||
|
|
||||||
from typing import Literal, TYPE_CHECKING
|
from typing import Literal, TYPE_CHECKING
|
||||||
|
|
||||||
@@ -13,20 +9,14 @@ from dzgui.const.constants import (
|
|||||||
NO_FILL,
|
NO_FILL,
|
||||||
FILL,
|
FILL,
|
||||||
NO_PADDING,
|
NO_PADDING,
|
||||||
TMP_EXE,
|
|
||||||
TMP_PATH,
|
|
||||||
TMP_TARBALL,
|
|
||||||
)
|
)
|
||||||
from dzgui.const.update import ALLOW_UPDATES
|
|
||||||
from dzgui.const.enum import ServerTab
|
from dzgui.const.enum import ServerTab
|
||||||
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
from dzgui.managers.update import UpdateManager
|
||||||
from dzgui.strings import dialogs
|
|
||||||
from dzgui.util.clip import copy_clipboard
|
from dzgui.util.clip import copy_clipboard
|
||||||
from dzgui.views.components.buttonbox import ButtonBox
|
from dzgui.views.components.buttonbox import ButtonBox
|
||||||
from dzgui.views.components.filter_panel import FilterPanel
|
from dzgui.views.components.filter_panel import FilterPanel
|
||||||
from dzgui.views.components.mod_panel import ModSelectionPanel
|
from dzgui.views.components.mod_panel import ModSelectionPanel
|
||||||
from dzgui.views.components.buttons import IconTextButton, RefreshButton, KeysButton
|
from dzgui.views.components.buttons import IconTextButton, RefreshButton, KeysButton
|
||||||
from dzgui.views.dialogs.generic import ExceptionDialog, QuitDialog
|
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
@@ -45,13 +35,13 @@ class RightPanel(Gtk.Box):
|
|||||||
def __init__(self, controller: "Controller"):
|
def __init__(self, controller: "Controller"):
|
||||||
super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL)
|
super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
self.thread_man = ThreadingManager(controller)
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.controller.register_widget("right_panel", self)
|
self.controller.register_widget("right_panel", self)
|
||||||
|
|
||||||
emitter = controller.get_emitter()
|
emitter = controller.get_emitter()
|
||||||
emitter.connect("servers_loaded", self._on_servers_loaded)
|
emitter.connect("servers_loaded", self._on_servers_loaded)
|
||||||
emitter.connect("server_page_changed", self._on_server_page_changed)
|
emitter.connect("server_page_changed", self._on_server_page_changed)
|
||||||
|
emitter.connect("lan_page_initialized", self._on_lan_page_init)
|
||||||
|
|
||||||
self.button_vbox = ButtonBox(controller)
|
self.button_vbox = ButtonBox(controller)
|
||||||
self.filters_vbox = FilterPanel(controller)
|
self.filters_vbox = FilterPanel(controller)
|
||||||
@@ -95,54 +85,20 @@ class RightPanel(Gtk.Box):
|
|||||||
valign=Gtk.Align.END,
|
valign=Gtk.Align.END,
|
||||||
spacing=10,
|
spacing=10,
|
||||||
)
|
)
|
||||||
if ALLOW_UPDATES and update is not None:
|
exe_path = os.getenv("PYAPP")
|
||||||
|
if exe_path and update is not None:
|
||||||
self.update_button.set_halign(Gtk.Align.END)
|
self.update_button.set_halign(Gtk.Align.END)
|
||||||
self.gutter_box.add(self.update_button)
|
self.gutter_box.add(self.update_button)
|
||||||
self.update_button.connect(
|
self.update_button.connect(
|
||||||
"clicked", self._on_update_button_clicked, update
|
"clicked", self._on_update_button_clicked, exe_path, update
|
||||||
)
|
)
|
||||||
self.gutter_box.add(eb)
|
self.gutter_box.add(eb)
|
||||||
self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING)
|
self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING)
|
||||||
|
|
||||||
def _on_update_success(self) -> None:
|
def _on_update_button_clicked(
|
||||||
msg = dialogs.update_success
|
self, button: Gtk.Button, exe_path: str, url: str
|
||||||
dialog = QuitDialog(self.controller, msg)
|
) -> None:
|
||||||
dialog.run()
|
UpdateManager(self.controller).update_version(exe_path, url)
|
||||||
|
|
||||||
def _on_update_failure(self, msg: str) -> None:
|
|
||||||
dialog = ExceptionDialog(self.controller, msg)
|
|
||||||
dialog.run()
|
|
||||||
|
|
||||||
@call_on_thread(dialogs.fetching_update)
|
|
||||||
def _on_update_button_clicked(self, button: Gtk.Button, url: str) -> None:
|
|
||||||
try:
|
|
||||||
res = requests.get(url)
|
|
||||||
if res.status_code == 200:
|
|
||||||
with open(TMP_TARBALL, "wb") as file:
|
|
||||||
file.write(res.content)
|
|
||||||
with tarfile.open(TMP_TARBALL) as tar:
|
|
||||||
tar.extractall(TMP_PATH)
|
|
||||||
|
|
||||||
exe_path = os.getenv("PYAPP")
|
|
||||||
if exe_path is None:
|
|
||||||
msg = dialogs.failed_to_update
|
|
||||||
func = StoredFunc(self._on_update_failure, msg)
|
|
||||||
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
|
||||||
return
|
|
||||||
shutil.move(TMP_EXE, exe_path)
|
|
||||||
|
|
||||||
proc = subprocess.run([exe_path, "self", "restore"])
|
|
||||||
if proc.returncode == 0:
|
|
||||||
func = StoredFunc(self._on_update_success)
|
|
||||||
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
|
||||||
else:
|
|
||||||
msg = dialogs.failed_to_update
|
|
||||||
func = StoredFunc(self._on_update_failure, msg)
|
|
||||||
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
|
||||||
except Exception as e:
|
|
||||||
func = StoredFunc(self._on_update_failure, e)
|
|
||||||
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
|
||||||
logger.warning(e)
|
|
||||||
|
|
||||||
def _on_server_page_changed(
|
def _on_server_page_changed(
|
||||||
self, emitter: "Emitter", page: "ServerTreeView"
|
self, emitter: "Emitter", page: "ServerTreeView"
|
||||||
@@ -151,6 +107,11 @@ class RightPanel(Gtk.Box):
|
|||||||
if page.loaded is True:
|
if page.loaded is True:
|
||||||
return
|
return
|
||||||
self.filters_vbox.set_sensitive(False)
|
self.filters_vbox.set_sensitive(False)
|
||||||
|
# TODO: unless it is lan page
|
||||||
|
#self.refresh_button.set_sensitive(False)
|
||||||
|
|
||||||
|
def _on_lan_page_init(self, emitter: "Emitter") -> None:
|
||||||
|
self.filters_vbox.set_sensitive(False)
|
||||||
|
|
||||||
def _on_servers_loaded(self, emitter: "Emitter", context: "ServerTab") -> None:
|
def _on_servers_loaded(self, emitter: "Emitter", context: "ServerTab") -> None:
|
||||||
# TODO: similar logic on notebook page change
|
# TODO: similar logic on notebook page change
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class NoOverlayScrolledWindow(Gtk.ScrolledWindow):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__(overlay_scrolling=False)
|
||||||
@@ -111,9 +111,9 @@ class Statusbar(Gtk.Grid):
|
|||||||
context: Union["ServerTab", NotebookPage],
|
context: Union["ServerTab", NotebookPage],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.spinner.stop()
|
self.spinner.stop()
|
||||||
# TODO: CalcDist is being called when table is not loaded
|
# FIXME: CalcDist is being called when table is not loaded
|
||||||
if dist is None:
|
if dist is None:
|
||||||
self.set_by_context(context, "")
|
return
|
||||||
else:
|
else:
|
||||||
pretty = self.append_distance(dist)
|
pretty = self.append_distance(dist)
|
||||||
self.set_by_context(context, pretty)
|
self.set_by_context(context, pretty)
|
||||||
|
|||||||
@@ -53,3 +53,5 @@ class EarlyIgnoreDialog(EarlyAlertDialog):
|
|||||||
|
|
||||||
# TODO: reverse order
|
# TODO: reverse order
|
||||||
self.add_button("Ignore", Gtk.ResponseType.CANCEL)
|
self.add_button("Ignore", Gtk.ResponseType.CANCEL)
|
||||||
|
|
||||||
|
# TODO: if exit, sys.exit(1), else pass
|
||||||
@@ -11,5 +11,5 @@ class ModsMixin:
|
|||||||
self, tree: TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn
|
self, tree: TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
path = self.get_focused_row_path() # type: ignore
|
mod = self.get_value_at_index(2) # type: ignore
|
||||||
self.controller.open_mod_page(path) # type: ignore
|
self.controller.open_workshop_page(mod) # type: ignore
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from dzgui.const.constants import LOG_FILTERS
|
from dzgui.const.constants import LOG_FILTERS
|
||||||
|
from dzgui.views.components.scrollable import NoOverlayScrolledWindow
|
||||||
from dzgui.views.mixins.cursor_mixin import CursorMixin
|
from dzgui.views.mixins.cursor_mixin import CursorMixin
|
||||||
from dzgui.views.mixins.help_menu_mixin import HelpMenuMixin
|
from dzgui.views.mixins.help_menu_mixin import HelpMenuMixin
|
||||||
from dzgui.views.trees.tree_log import LogTreeView
|
from dzgui.views.trees.tree_log import LogTreeView
|
||||||
@@ -21,7 +22,7 @@ class Log(CursorMixin, HelpMenuMixin, Gtk.Box): # type: ignore
|
|||||||
NOTE: LogTreeView is kept in a separate ScrolledWindow so that
|
NOTE: LogTreeView is kept in a separate ScrolledWindow so that
|
||||||
checkboxes will be flush on bottom
|
checkboxes will be flush on bottom
|
||||||
"""
|
"""
|
||||||
self.scrolled = Gtk.ScrolledWindow()
|
self.scrolled = NoOverlayScrolledWindow()
|
||||||
self.treeview = LogTreeView(controller)
|
self.treeview = LogTreeView(controller)
|
||||||
self.scrolled.add(self.treeview)
|
self.scrolled.add(self.treeview)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from typing import Self, TYPE_CHECKING
|
from typing import Self, TYPE_CHECKING
|
||||||
|
from dzgui.views.components.scrollable import NoOverlayScrolledWindow
|
||||||
from dzgui.views.trees.tree_mods import ModTreeView
|
from dzgui.views.trees.tree_mods import ModTreeView
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -10,7 +11,8 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
|
||||||
class Mods(Gtk.ScrolledWindow):
|
|
||||||
|
class Mods(NoOverlayScrolledWindow):
|
||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from dzgui.views.trees.tree_server_mods import ServerModTreeView
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gdk, Gtk # type: ignore # noqa E402
|
from gi.repository import Gdk, Gtk # noqa E402
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|||||||
@@ -162,19 +162,6 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
|||||||
return tv
|
return tv
|
||||||
raise ValueError("No treeview set")
|
raise ValueError("No treeview set")
|
||||||
|
|
||||||
def add_notification(self) -> None:
|
|
||||||
saved = self.notebook.get_nth_page(1)
|
|
||||||
if saved is None:
|
|
||||||
return
|
|
||||||
text = self.notebook.get_tab_label_text(saved)
|
|
||||||
if text is None:
|
|
||||||
return
|
|
||||||
# TODO: strings
|
|
||||||
if "*" in text:
|
|
||||||
return
|
|
||||||
text += "*"
|
|
||||||
self.notebook.set_tab_label_text(saved, text)
|
|
||||||
|
|
||||||
def update_tab_widths(self, col: Gtk.TreeViewColumn) -> None:
|
def update_tab_widths(self, col: Gtk.TreeViewColumn) -> None:
|
||||||
# TODO: may cause pixel offsets when application is maximized
|
# TODO: may cause pixel offsets when application is maximized
|
||||||
width = col.get_width()
|
width = col.get_width()
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
valid_json = False
|
valid_json = False
|
||||||
|
|
||||||
|
# NOTE: reasonable defaults for long columns
|
||||||
width_map = {
|
width_map = {
|
||||||
"Name": 800,
|
"Name": 800,
|
||||||
"Map": 300,
|
"Map": 300,
|
||||||
@@ -96,8 +97,9 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
column.set_fixed_width(saved_size)
|
column.set_fixed_width(saved_size)
|
||||||
column.set_expand(True)
|
column.set_expand(True)
|
||||||
else:
|
else:
|
||||||
w = width_map[column_title]
|
if column_title in width_map:
|
||||||
column.set_fixed_width(w)
|
w = width_map[column_title]
|
||||||
|
column.set_fixed_width(w)
|
||||||
if column_title == "Ping":
|
if column_title == "Ping":
|
||||||
column.set_cell_data_func(renderer, self._get_ping)
|
column.set_cell_data_func(renderer, self._get_ping)
|
||||||
|
|
||||||
@@ -162,8 +164,8 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
|
|
||||||
def _on_map(self, widget: Self) -> None:
|
def _on_map(self, widget: Self) -> None:
|
||||||
# TODO: disable filter panel if current model is None
|
# TODO: disable filter panel if current model is None
|
||||||
if self.get_enum() is ServerTab.LAN:
|
state = self.get_enum() is ServerTab.LAN
|
||||||
self.emitter.emit("lan_tab_toggled", True)
|
self.emitter.emit("lan_tab_toggled", state)
|
||||||
|
|
||||||
store = self.filter_man.get_map_store()
|
store = self.filter_man.get_map_store()
|
||||||
|
|
||||||
@@ -178,8 +180,6 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
# NOTE: removes queue checker for this tab
|
# NOTE: removes queue checker for this tab
|
||||||
GLib.Source.remove(self.queue_id)
|
GLib.Source.remove(self.queue_id)
|
||||||
self.emitter.disconnect(self.handler_id)
|
self.emitter.disconnect(self.handler_id)
|
||||||
if self.get_enum() is ServerTab.LAN:
|
|
||||||
self.emitter.emit("lan_tab_toggled", False)
|
|
||||||
|
|
||||||
def _on_col_width_changed(
|
def _on_col_width_changed(
|
||||||
self, col: Gtk.TreeViewColumn, width: GObject.ParamSpecInt
|
self, col: Gtk.TreeViewColumn, width: GObject.ParamSpecInt
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
|||||||
authors = [
|
authors = [
|
||||||
{name = "aclist"}
|
{name = "aclist"}
|
||||||
]
|
]
|
||||||
version = "7.0.0b2"
|
version = "7.0.0b6"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
BUILD = "3.13.9"
|
||||||
|
DATE = "20251014"
|
||||||
|
CPYTHON_BUILD = f"cpython-{BUILD}"
|
||||||
|
|
||||||
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
build_dir = root.joinpath("build")
|
||||||
|
cpython_dir = build_dir.joinpath(CPYTHON_BUILD)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch(url: str, destination: str) -> None:
|
||||||
|
print(f"Fetching URL '{url}'")
|
||||||
|
res = requests.get(url)
|
||||||
|
code = res.status_code
|
||||||
|
if code != 200:
|
||||||
|
raise Exception(f"URL '{url}' returned status code {code}")
|
||||||
|
with open(build_dir.joinpath(destination), "wb") as f:
|
||||||
|
f.write(res.content)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cpython() -> None:
|
||||||
|
source = f"https://github.com/astral-sh/python-build-standalone/releases/download/{DATE}/"
|
||||||
|
build = f"{CPYTHON_BUILD}%2B{DATE}-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
|
url = f"{source}{build}"
|
||||||
|
outfile = unquote(build)
|
||||||
|
fetch(url, outfile)
|
||||||
|
version = build.split("%")[0]
|
||||||
|
untar(outfile, "python", version)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pyapp() -> None:
|
||||||
|
url = "https://github.com/ofek/pyapp/releases/latest/download/source.tar.gz"
|
||||||
|
outfile = "pyapp-latest.tar.gz"
|
||||||
|
fetch(url, outfile)
|
||||||
|
untar(outfile, "pyapp-*", "pyapp-latest")
|
||||||
|
|
||||||
|
|
||||||
|
def untar(file: str, glob: str, renamed: str) -> None:
|
||||||
|
full_path = build_dir.joinpath(file)
|
||||||
|
with tarfile.open(full_path, "r:gz") as tar:
|
||||||
|
tar.extractall(path=build_dir)
|
||||||
|
full_path.unlink()
|
||||||
|
for _dir in build_dir.glob(glob):
|
||||||
|
os.rename(_dir, build_dir.joinpath(renamed))
|
||||||
|
|
||||||
|
|
||||||
|
def install_deps() -> None:
|
||||||
|
pip = build_dir.joinpath(f"{CPYTHON_BUILD}/bin/pip")
|
||||||
|
subprocess.run([pip, "install", root])
|
||||||
|
|
||||||
|
|
||||||
|
def repackage() -> None:
|
||||||
|
print("Preparing tar archive...")
|
||||||
|
outfile = build_dir.joinpath("airgapped.tar.gz")
|
||||||
|
with tarfile.open(outfile, "w:gz") as tar:
|
||||||
|
tar.add(build_dir.joinpath(CPYTHON_BUILD), arcname="python")
|
||||||
|
|
||||||
|
|
||||||
|
def rebuild_cpython() -> None:
|
||||||
|
if cpython_dir.is_dir() is False:
|
||||||
|
get_cpython()
|
||||||
|
install_deps()
|
||||||
|
repackage()
|
||||||
|
return get_packaged_version()
|
||||||
|
|
||||||
|
|
||||||
|
def get_packaged_version() -> str:
|
||||||
|
proc = subprocess.run(
|
||||||
|
[cpython_dir.joinpath("bin/dzgui"), "-v"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
return proc.stdout.rstrip()
|
||||||
+26
-6
@@ -5,10 +5,12 @@ import tarfile
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from prebuild import get_pyapp, rebuild_cpython
|
||||||
|
|
||||||
|
|
||||||
root = Path(__file__).resolve().parents[1]
|
root = Path(__file__).resolve().parents[1]
|
||||||
output = root.joinpath("dist")
|
output = root.joinpath("dist")
|
||||||
|
build_dir = root.joinpath("build")
|
||||||
pyapp_dir = root.joinpath("pyapp-latest")
|
|
||||||
|
|
||||||
builder = build.ProjectBuilder(root)
|
builder = build.ProjectBuilder(root)
|
||||||
wheel = builder.build("wheel", output_directory=output)
|
wheel = builder.build("wheel", output_directory=output)
|
||||||
@@ -19,23 +21,41 @@ metadata = stem.split("-")
|
|||||||
appname = metadata[0]
|
appname = metadata[0]
|
||||||
version = metadata[1]
|
version = metadata[1]
|
||||||
|
|
||||||
entrypoint = "dzgui.main:main"
|
pyapp_dir = build_dir.joinpath("pyapp-latest")
|
||||||
|
if pyapp_dir.is_dir() is False:
|
||||||
|
get_pyapp()
|
||||||
|
|
||||||
|
packaged_version = rebuild_cpython()
|
||||||
|
assert packaged_version == version
|
||||||
|
cpython = build_dir.joinpath("airgapped.tar.gz")
|
||||||
|
|
||||||
|
entrypoint = "dzgui.main:main"
|
||||||
env = os.environ
|
env = os.environ
|
||||||
env["PYAPP_PROJECT_VERSION"] = version
|
env["PYAPP_PROJECT_VERSION"] = version
|
||||||
env["PYAPP_PROJECT_NAME"] = appname
|
env["PYAPP_PROJECT_NAME"] = appname
|
||||||
env["PYAPP_EXEC_SPEC"] = entrypoint
|
env["PYAPP_EXEC_SPEC"] = entrypoint
|
||||||
env["PYAPP_PROJECT_PATH"] = wheel
|
env["PYAPP_PROJECT_PATH"] = wheel
|
||||||
env["PYAPP_DISTRIBUTION_EMBED"] = "true"
|
|
||||||
env["PYAPP_PYTHON_VERSION"] = "3.13"
|
|
||||||
env["PYAPP_PASS_LOCATION"] = "true"
|
env["PYAPP_PASS_LOCATION"] = "true"
|
||||||
|
|
||||||
|
# NOTE: explicitly install all dependencies into distribution
|
||||||
|
env["PYAPP_SKIP_INSTALL"] = "true"
|
||||||
|
env["PYAPP_DISTRIBUTION_EMBED"] = "true"
|
||||||
|
env["PYAPP_FULL_ISOLATION"] = "true"
|
||||||
|
env["PYAPP_DISTRIBUTION_PATH"] = str(cpython)
|
||||||
|
env["PYAPP_DISTRIBUTION_PYTHON_PATH"] = "python/bin/python3"
|
||||||
|
|
||||||
proc = subprocess.run(["cargo", "build", "--release"], env=env, cwd=pyapp_dir)
|
proc = subprocess.run(["cargo", "build", "--release"], env=env, cwd=pyapp_dir)
|
||||||
if proc.returncode == 0:
|
if proc.returncode == 0:
|
||||||
output_exe = root.joinpath("pyapp-latest/target/release/pyapp")
|
output_exe = build_dir.joinpath("pyapp-latest/target/release/pyapp")
|
||||||
release_exe = output.joinpath(appname)
|
release_exe = output.joinpath(appname)
|
||||||
output_exe.rename(release_exe)
|
output_exe.rename(release_exe)
|
||||||
tarpath = output.joinpath(tarname)
|
tarpath = output.joinpath(tarname)
|
||||||
with tarfile.open(tarpath, "w:gz") as tar:
|
with tarfile.open(tarpath, "w:gz") as tar:
|
||||||
tar.add(release_exe, arcname=appname)
|
tar.add(release_exe, arcname=appname)
|
||||||
print(f"Wrote tarfile to '{tarpath}'")
|
print(f"Wrote tarfile to '{tarpath}'")
|
||||||
|
|
||||||
|
proc = subprocess.run([release_exe, "-v"], capture_output=True, text=True)
|
||||||
|
assert proc.stdout.rstrip() == version
|
||||||
|
|
||||||
|
release_exe.unlink()
|
||||||
|
Path(wheel).unlink()
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"cols": {
|
||||||
|
"Name": 1,
|
||||||
|
"Map": 1,
|
||||||
|
"Gametime": 1,
|
||||||
|
"Players": 1,
|
||||||
|
"Queue": 1,
|
||||||
|
"IP": 1,
|
||||||
|
"Qport": 1,
|
||||||
|
"Ping": 1,
|
||||||
|
"Maximum": 1,
|
||||||
|
"Perspective": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"cols": {
|
||||||
|
"Name": 398,
|
||||||
|
"Map": 181,
|
||||||
|
"Gametime": 151,
|
||||||
|
"Players": 119,
|
||||||
|
"Queue": 96,
|
||||||
|
"IP": 247,
|
||||||
|
"Qport": 94,
|
||||||
|
"Ping": 253
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"cols": {
|
||||||
|
"Name": 1,
|
||||||
|
"Map": 1,
|
||||||
|
"Gametime": 1,
|
||||||
|
"Players": 1,
|
||||||
|
"Queue": 1,
|
||||||
|
"IP": 1,
|
||||||
|
"Qport": 1,
|
||||||
|
"Ping": 1,
|
||||||
|
"View": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from dzgui.init.migrate import convert_cols_file
|
||||||
|
from tests.fixtures import fixture_path
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def columns_with_perspective():
|
||||||
|
return fixture_path("columns_1")
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def columns_without_perspective():
|
||||||
|
return fixture_path("columns_2")
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def columns_with_view():
|
||||||
|
return fixture_path("columns_3")
|
||||||
|
|
||||||
|
@pytest.mark.config
|
||||||
|
def test_columns_with_perspective(columns_with_perspective):
|
||||||
|
j = convert_cols_file(columns_with_perspective)
|
||||||
|
assert "View" in j["cols"]
|
||||||
|
assert "Max" in j["cols"]
|
||||||
|
|
||||||
|
@pytest.mark.config
|
||||||
|
def test_columns_without_perspective(columns_without_perspective):
|
||||||
|
j = convert_cols_file(columns_without_perspective)
|
||||||
|
assert "View" not in j["cols"]
|
||||||
|
assert "Max" not in j["cols"]
|
||||||
|
|
||||||
|
@pytest.mark.config
|
||||||
|
def test_columns_with_view(columns_with_view):
|
||||||
|
j = convert_cols_file(columns_with_view)
|
||||||
|
assert j is None
|
||||||
@@ -2,14 +2,14 @@ from dzgui.util import localize
|
|||||||
|
|
||||||
|
|
||||||
def test_de(monkeypatch):
|
def test_de(monkeypatch):
|
||||||
monkeypatch.setenv("LC_CTYPE", "de_DE.UTF-8")
|
monkeypatch.setenv("LC_ALL", "de_DE.UTF-8")
|
||||||
localize.set_locale()
|
localize.set_locale()
|
||||||
assert localize.number(9900) == "9.900"
|
assert localize.number(9900) == "9.900"
|
||||||
assert localize.number(99.88) == "99,88"
|
assert localize.number(99.88) == "99,88"
|
||||||
|
|
||||||
|
|
||||||
def test_en(monkeypatch):
|
def test_en(monkeypatch):
|
||||||
monkeypatch.setenv("LC_CTYPE", "en_US.UTF-8")
|
monkeypatch.setenv("LC_ALL", "en_US.UTF-8")
|
||||||
localize.set_locale()
|
localize.set_locale()
|
||||||
assert localize.number(9900) == "9,900"
|
assert localize.number(9900) == "9,900"
|
||||||
assert localize.number(99.88) == "99.88"
|
assert localize.number(99.88) == "99.88"
|
||||||
@@ -22,7 +22,7 @@ def test_none(monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
def test_empty(monkeypatch):
|
def test_empty(monkeypatch):
|
||||||
monkeypatch.setenv("LC_CTYPE", "")
|
monkeypatch.setenv("LC_ALL", "")
|
||||||
localize.set_locale()
|
localize.set_locale()
|
||||||
assert localize.number(9900) == "9,900"
|
assert localize.number(9900) == "9,900"
|
||||||
assert localize.number(99.88) == "99.88"
|
assert localize.number(99.88) == "99.88"
|
||||||
Reference in new issue
Block a user