Skip to content

Commit 98e9b2c

Browse files
author
karel26
committed
Enhancements:
- /dcs update can now take a specific version also BUGFIX: - mission list replication between nodes did not work - DCS version was not update on remote nodes properly - DCS port and WebGUI port were not published properly by remote nodes
1 parent c74bc21 commit 98e9b2c

File tree

25 files changed

+161
-118
lines changed

25 files changed

+161
-118
lines changed

core/data/impl/nodeimpl.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from core.translations import get_translation
2525
from discord.ext import tasks
2626
from migrate import migrate
27-
from packaging import version
27+
from packaging.version import parse
2828
from pathlib import Path
2929
from psycopg.errors import UndefinedTable, InFailedSqlTransaction, NotNullViolation, OperationalError
3030
from psycopg.rows import dict_row
@@ -90,7 +90,6 @@ def __init__(self, name: str, config_dir: Optional[str] = 'config'):
9090
self.is_shutdown = asyncio.Event()
9191
self.rc = 0
9292
self.dcs_branch = None
93-
self.dcs_version = None
9493
self.all_nodes: dict[str, Optional[Node]] = {self.name: self}
9594
self.suspect: dict[str, Node] = {}
9695
self.instances: list[Instance] = []
@@ -129,6 +128,7 @@ async def __aexit__(self, type, value, traceback):
129128
await self.close_db()
130129

131130
async def post_init(self):
131+
await self.get_dcs_branch_and_version()
132132
self.pool, self.apool = await self.init_db()
133133
try:
134134
self._master = await self.heartbeat()
@@ -388,7 +388,7 @@ async def _upgrade_pending_non_git(self) -> bool:
388388
current_version = re.sub('^v', '', __version__)
389389
latest_version = re.sub('^v', '', result[0]["tag_name"])
390390

391-
if version.parse(latest_version) > version.parse(current_version):
391+
if parse(latest_version) > parse(current_version):
392392
return True
393393
except aiohttp.ClientResponseError as ex:
394394
# ignore rate limits
@@ -435,9 +435,9 @@ async def get_dcs_branch_and_version(self) -> tuple[str, str]:
435435
"Use /dcs update if you want to switch to the release branch.")
436436
return self.dcs_branch, self.dcs_version
437437

438-
async def update(self, warn_times: list[int], branch: Optional[str] = None) -> int:
438+
async def update(self, warn_times: list[int], branch: Optional[str] = None, version: Optional[str] = None) -> int:
439439

440-
async def do_update(branch: Optional[str] = None) -> int:
440+
async def do_update(branch: str, version: Optional[str] = None) -> int:
441441
# disable any popup on the remote machine
442442
if sys.platform == 'win32':
443443
startupinfo = subprocess.STARTUPINFO()
@@ -450,7 +450,9 @@ async def do_update(branch: Optional[str] = None) -> int:
450450
def run_subprocess() -> int:
451451
try:
452452
cmd = [os.path.join(self.installation, 'bin', 'dcs_updater.exe'), '--quiet', 'update']
453-
if branch:
453+
if version:
454+
cmd.append(f"{version}@{branch}")
455+
else:
454456
cmd.append(f"@{branch}")
455457

456458
process = subprocess.run(
@@ -481,15 +483,16 @@ def run_subprocess() -> int:
481483
for callback in self.before_update.values():
482484
await callback()
483485
old_branch, old_version = await self.get_dcs_branch_and_version()
484-
rc = await do_update(branch)
486+
rc = await do_update(branch, version)
485487
if rc in [0, 350]:
486488
self.dcs_branch = self.dcs_version = None
487489
dcs_branch, dcs_version = await self.get_dcs_branch_and_version()
488490
# if only the updater updated itself, run the update again
489491
if old_branch == dcs_branch and old_version == dcs_version:
490492
self.log.info("dcs_updater.exe updated to the latest version, now updating DCS World ...")
491-
rc = await do_update(branch)
493+
rc = await do_update(branch, version)
492494
self.dcs_branch = self.dcs_version = None
495+
await self.get_dcs_branch_and_version()
493496
if rc not in [0, 350]:
494497
return rc
495498
if self.locals['DCS'].get('desanitize', True):
@@ -564,15 +567,15 @@ async def get_available_modules(self) -> list[str]:
564567
pass
565568
return list(licenses)
566569

567-
async def get_latest_version(self, branch: str) -> Optional[str]:
568-
async def _get_latest_version_no_auth():
570+
async def get_available_dcs_versions(self, branch: str) -> Optional[list[str]]:
571+
async def _get_latest_versions_no_auth() -> Optional[list[str]]:
569572
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(
570573
ssl=ssl.create_default_context(cafile=certifi.where()))) as session:
571574
async with session.get(UPDATER_URL.format(branch)) as response:
572575
if response.status == 200:
573-
return json.loads(gzip.decompress(await response.read()))['versions2'][-1]['version']
576+
return [x['version'] for x in json.loads(gzip.decompress(await response.read()))['versions2']]
574577

575-
async def _get_latest_version_auth():
578+
async def _get_latest_versions_auth() -> Optional[list[str]]:
576579
user = self.locals['DCS'].get('user')
577580
password = utils.get_password('DCS', self.config_dir)
578581
headers = {
@@ -584,17 +587,22 @@ async def _get_latest_version_auth():
584587
if r1.status == 200:
585588
async with await session.get(UPDATER_URL.format(branch)) as r2:
586589
if r2.status == 200:
587-
data = json.loads(gzip.decompress(await r2.read()))['versions2'][-1]['version']
590+
data = [x['version'] for x in json.loads(gzip.decompress(await r2.read()))['versions2']]
588591
else:
589592
data = None
590593
async with await session.get(LOGOUT_URL):
591594
pass
592595
return data
593596

594597
if not self.locals['DCS'].get('user'):
595-
return await _get_latest_version_no_auth()
598+
return await _get_latest_versions_no_auth()
596599
else:
597-
return await _get_latest_version_auth()
600+
return await _get_latest_versions_auth()
601+
602+
603+
async def get_latest_version(self, branch: str) -> Optional[str]:
604+
versions = await self.get_available_dcs_versions(branch)
605+
return versions[-1] if versions else None
598606

599607
async def register(self):
600608
self._public_ip = self.locals.get('public_ip')
@@ -679,8 +687,8 @@ def has_timeout(row: dict, timeout: int):
679687
# noinspection PyAsyncCall
680688
asyncio.create_task(self.upgrade())
681689
return True
682-
elif version.parse(cluster['version']) != version.parse(__version__):
683-
if version.parse(cluster['version']) > version.parse(__version__):
690+
elif parse(cluster['version']) != parse(__version__):
691+
if parse(cluster['version']) > parse(__version__):
684692
self.log.warning(
685693
f"Bot version downgraded from {cluster['version']} to {__version__}. "
686694
f"This could lead to unexpected behavior if there have been database "
@@ -723,7 +731,7 @@ def has_timeout(row: dict, timeout: int):
723731
(self.name, self.guild_id))
724732
return True
725733
# we have a version mismatch on the agent, a cloud sync might still be pending
726-
if version.parse(__version__) < version.parse(cluster['version']):
734+
if parse(__version__) < parse(cluster['version']):
727735
self.log.error(f"We are running version {__version__} where the master is on version "
728736
f"{cluster['version']} already. Trying to upgrade ...")
729737
# TODO: we might not have bus access here yet, so be our own bus (dirty)
@@ -736,7 +744,7 @@ def has_timeout(row: dict, timeout: int):
736744
INSERT INTO intercom (guild_id, node, data) VALUES (%s, %s, %s)
737745
""", (self.guild_id, self.name, Json(data)))
738746
return False
739-
elif version.parse(__version__) > version.parse(cluster['version']):
747+
elif parse(__version__) > parse(cluster['version']):
740748
self.log.warning(
741749
f"This node is running on version {__version__} where the master still runs on "
742750
f"{cluster['version']}. You need to upgrade your master node!")
@@ -906,7 +914,7 @@ async def autoupdate(self):
906914
except aiohttp.ClientError:
907915
self.log.warning("Update check failed, possible server outage at ED.")
908916
return
909-
if new_version and old_version != new_version:
917+
if new_version and parse(old_version) < parse(new_version):
910918
self.log.info('A new version of DCS World is available. Auto-updating ...')
911919
rc = await self.update([300, 120, 60])
912920
if rc == 0:

core/data/impl/serverimpl.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,9 @@ async def loadNextMission(self, modify_mission: Optional[bool] = True) -> bool:
890890
return True
891891
return False
892892

893+
async def getMissionList(self) -> list[str]:
894+
return self.settings.get('missionList', [])
895+
893896
async def run_on_extension(self, extension: str, method: str, **kwargs) -> Any:
894897
ext = self.extensions.get(extension)
895898
if not ext:

core/data/node.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__(self, name: str, config_dir: Optional[str] = 'config'):
5959
self.locals = None
6060
self.config = self.read_config(os.path.join(config_dir, 'main.yaml'))
6161
self.guild_id: int = int(self.config['guild_id'])
62+
self.dcs_version = None
6263
self.slow_system: bool = False
6364

6465
def __repr__(self):
@@ -133,7 +134,7 @@ async def upgrade_pending(self) -> bool:
133134
async def upgrade(self):
134135
raise NotImplemented()
135136

136-
async def update(self, warn_times: list[int], branch: Optional[str] = None) -> int:
137+
async def update(self, warn_times: list[int], branch: Optional[str] = None, version: Optional[str] = None) -> int:
137138
raise NotImplemented()
138139

139140
async def get_dcs_branch_and_version(self) -> tuple[str, str]:
@@ -148,6 +149,9 @@ async def get_installed_modules(self) -> list[str]:
148149
async def get_available_modules(self) -> list[str]:
149150
raise NotImplemented()
150151

152+
async def get_available_dcs_versions(self, branch: str) -> Optional[list[str]]:
153+
raise NotImplemented()
154+
151155
async def get_latest_version(self, branch: str) -> Optional[str]:
152156
raise NotImplemented()
153157

core/data/proxy/nodeproxy.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121

2222
class NodeProxy(Node):
23-
def __init__(self, local_node: "NodeImpl", name: str, public_ip: str):
23+
def __init__(self, local_node: "NodeImpl", name: str, public_ip: str, dcs_version: str):
2424
from services.servicebus import ServiceBus
2525

2626
super().__init__(name, local_node.config_dir)
@@ -32,6 +32,7 @@ def __init__(self, local_node: "NodeImpl", name: str, public_ip: str):
3232
self.locals = self.read_locals()
3333
self.bus = ServiceRegistry.get(ServiceBus)
3434
self.slow_system = self.locals.get('slow_system', False)
35+
self.dcs_version = dcs_version
3536

3637
@property
3738
def master(self) -> bool:
@@ -102,14 +103,15 @@ async def upgrade(self):
102103
"method": "upgrade"
103104
}, node=self.name)
104105

105-
async def update(self, warn_times: list[int], branch: Optional[str] = None) -> int:
106+
async def update(self, warn_times: list[int], branch: Optional[str] = None, version: Optional[str] = None) -> int:
106107
data = await self.bus.send_to_node_sync({
107108
"command": "rpc",
108109
"object": "Node",
109110
"method": "update",
110111
"params": {
111112
"warn_times": warn_times,
112-
"branch": branch or ""
113+
"branch": branch or "",
114+
"version": version or ""
113115
}
114116
}, node=self.name, timeout=600)
115117
return data['return']
@@ -155,6 +157,20 @@ async def get_available_modules(self) -> list[str]:
155157
}, node=self.name, timeout=timeout)
156158
return data['return']
157159

160+
@cache_with_expiration(expiration=60)
161+
async def get_available_dcs_versions(self, branch: str) -> Optional[list[str]]:
162+
timeout = 60 if not self.slow_system else 120
163+
data = await self.bus.send_to_node_sync({
164+
"command": "rpc",
165+
"object": "Node",
166+
"method": "get_available_dcs_versions",
167+
"params": {
168+
"branch": branch
169+
}
170+
}, node=self.name, timeout=timeout)
171+
return data['return']
172+
173+
158174
@cache_with_expiration(expiration=60)
159175
async def get_latest_version(self, branch: str) -> Optional[str]:
160176
timeout = 60 if not self.slow_system else 120

core/data/proxy/serverproxy.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44

55
from core import Server, Status, utils, Coalition
6-
from core.utils.helper import async_cache
6+
from core.utils.helper import async_cache, cache_with_expiration
77
from core.data.node import UploadStatus
88
from dataclasses import dataclass, field
99
from typing import Optional, Union, Any
@@ -279,14 +279,10 @@ async def addMission(self, path: str, *, autostart: Optional[bool] = False) -> l
279279
"autostart": autostart
280280
}
281281
}, timeout=timeout, node=self.node.name)
282-
# wait for the missionList sync
283-
while path not in self.settings['missionList']:
284-
await asyncio.sleep(1)
285282
return data['return']
286283

287284
async def deleteMission(self, mission_id: int) -> list[str]:
288285
timeout = 60 if not self.node.slow_system else 120
289-
path = self.settings['missionList'][mission_id - 1]
290286
data = await self.bus.send_to_node_sync({
291287
"command": "rpc",
292288
"object": "Server",
@@ -296,9 +292,6 @@ async def deleteMission(self, mission_id: int) -> list[str]:
296292
"mission_id": mission_id
297293
}
298294
}, timeout=timeout, node=self.node.name)
299-
# wait for the missionList sync
300-
while path in self.settings['missionList']:
301-
await asyncio.sleep(1)
302295
return data['return']
303296

304297
async def replaceMission(self, mission_id: int, path: str) -> list[str]:
@@ -342,6 +335,17 @@ async def loadNextMission(self, modify_mission: Optional[bool] = True) -> bool:
342335
}, timeout=timeout, node=self.node.name)
343336
return data['return']
344337

338+
@cache_with_expiration(expiration=10)
339+
async def getMissionList(self) -> list[str]:
340+
timeout = 180 if not self.node.slow_system else 300
341+
data = await self.bus.send_to_node_sync({
342+
"command": "rpc",
343+
"object": "Server",
344+
"method": "getMissionList",
345+
"server_name": self.name
346+
}, timeout=timeout, node=self.node.name)
347+
return data['return']
348+
345349
async def run_on_extension(self, extension: str, method: str, **kwargs) -> Any:
346350
timeout = 180 if not self.node.slow_system else 300
347351
params = {

core/data/server.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ class Server(DataObject):
5252
restart_pending: bool = field(default=False, compare=False)
5353
on_mission_end: dict = field(default_factory=dict, compare=False)
5454
on_empty: dict = field(default_factory=dict, compare=False)
55-
dcs_version: str = field(default=None, compare=False)
5655
extensions: dict[str, Extension] = field(default_factory=dict, compare=False)
5756
afk: dict[str, datetime] = field(default_factory=dict, compare=False)
5857
listeners: dict[str, asyncio.Future] = field(default_factory=dict, compare=False)
@@ -361,7 +360,7 @@ async def loadNextMission(self, modify_mission: Optional[bool] = True) -> bool:
361360
raise NotImplemented()
362361

363362
async def getMissionList(self) -> list[str]:
364-
return self.settings.get('missionList', [])
363+
raise NotImplemented()
365364

366365
async def modifyMission(self, filename: str, preset: Union[list, dict]) -> str:
367366
raise NotImplemented()

core/utils/discord.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,7 @@ def get_name(base_dir: str, path: str):
10371037
base_dir = await server.get_missions_dir()
10381038
choices: list[app_commands.Choice[int]] = [
10391039
app_commands.Choice(name=get_name(base_dir, x), value=idx)
1040-
for idx, x in enumerate(server.settings['missionList'])
1040+
for idx, x in enumerate(await server.getMissionList())
10411041
if not current or current.casefold() in get_name(base_dir, x).casefold()
10421042
]
10431043
return sorted(choices, key=lambda choice: choice.name)[:25]

install.cmd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ if not exist "%VENV%" (
1818
echo Creating the Python Virtual Environment. This may take some time...
1919
python -m pip install --upgrade pip
2020
python -m venv "%VENV%"
21+
"%VENV%\Scripts\python.exe" -m pip install --upgrade pip
2122
"%VENV%\Scripts\python.exe" -m pip install -r requirements.txt
2223
)
2324
"%VENV%\Scripts\python" install.py %*

0 commit comments

Comments
 (0)