def get_latest_mission_from_github(): """ Downloads the latest mission from a Github repository The repository needs to have releases (tagged) The function will download the first MIZ file found in the latest release """ if core.CTX.dcs_auto_mission: LOGGER.debug('getting latest mission from Github') commands.DCS.block_start('loading mission') if DCSConfig.DCS_AUTO_MISSION_GH_OWNER( ) and DCSConfig.DCS_AUTO_MISSION_GH_REPO(): LOGGER.debug('looking for newer mission file') latest_version, asset_name, download_url = utils.get_latest_release( DCSConfig.DCS_AUTO_MISSION_GH_OWNER(), DCSConfig.DCS_AUTO_MISSION_GH_REPO()) LOGGER.debug('latest release: %s', latest_version) local_file = MissionPath(Path(_get_mission_folder(), asset_name)) if not local_file: LOGGER.info('downloading new mission: %s', asset_name) req = requests.get(download_url) if req.ok: local_file.path.write_bytes(req.content) local_file.set_as_active() else: LOGGER.error('failed to download latest mission') else: LOGGER.warning('no config values given for [auto mission]') commands.DCS.unblock_start('loading mission') else: LOGGER.debug('skipping mission update')
def set_priority(self): """ Sets the DCS process CPU priority to the CFG value """ def _command(): if self.app.nice() != self.valid_priorities[ DCSConfig.DCS_CPU_PRIORITY()]: LOGGER.debug('setting DCS process priority to: %s', DCSConfig.DCS_CPU_PRIORITY()) self.app.nice( self.valid_priorities[DCSConfig.DCS_CPU_PRIORITY()]) time.sleep(15) while True: if DCSConfig.DCS_CPU_PRIORITY(): if core.CTX.exit: return if DCSConfig.DCS_CPU_PRIORITY( ) not in self.valid_priorities.keys(): LOGGER.error( f'invalid priority: %s\n' f'Choose one of: %s', DCSConfig.DCS_CPU_PRIORITY(), self.valid_priorities.keys(), ) return self._work_with_dcs_process(_command) else: LOGGER.warning( 'no CPU priority given in config file for dcs.exe') return time.sleep(30)
def reboot(force: bool = False): """Reboots the server computer""" if DCS.there_are_connected_players(): if not force: return 'there are connected players; cannot restart the server now (use "--force" to restart anyway)' LOGGER.warning('forcing restart with connected players') os.system('shutdown /r /t 30 /c "Reboot initialized by ESST"') # nosec return ''
def check_for_connected_players() -> bool: """ Returns: False if there are connected players """ if DCS.there_are_connected_players(): LOGGER.warning( 'there are connected players; cannot kill the server now') return False return True
def _work_with_dcs_process(self, func): if core.CTX.exit: return try: func() self._warned = False except (psutil.NoSuchProcess, AttributeError): if not core.CTX.exit and not self._warned: LOGGER.warning('DCS process does not exist') self._warned = True
def queue_kill(): """Kill DCS application when all players left""" def _queue_kill(queue: Queue): while DCS.there_are_connected_players(): if not queue.empty(): queue.get_nowait() LOGGER.debug('queued DCS kill has been cancelled') return time.sleep(5) LOGGER.info('executing planned DCS restart') DCS.kill() LOGGER.warning('queuing DCS kill for when all players have left') CTX.loop.run_in_executor(None, _queue_kill, CANCEL_QUEUED_KILL)
def download_mission_from_discord(discord_attachment, overwrite: bool = False, load: bool = False, force: bool = False): """ Downloads a mission from a discord message attachment Args: force: force restart even with players connected discord_attachment: url to download the mission from overwrite: whether or not to overwrite an existing file load: whether or not to restart the server with the downloaded mission """ url = discord_attachment['url'] size = discord_attachment['size'] filename = discord_attachment['filename'] local_file = MissionPath(Path(_get_mission_folder(), filename)) overwriting = '' if local_file: if overwrite: overwriting = ' (replacing existing file)' else: LOGGER.warning( 'this mission already exists: %s\nuse "overwrite" to replace it', local_file.path) return LOGGER.info( 'downloading: %s (%s) %s', filename, humanize.naturalsize(size), overwriting, ) with requests.get(url) as response: local_file.path.write_bytes(response.content) if load: if commands.DCS.there_are_connected_players() and not force: LOGGER.error( 'there are connected players; cannot restart the server now (use "force" to kill anyway)' ) return LOGGER.info('restarting the server with this mission') local_file.set_as_active() commands.DCS.restart(force=force) else: LOGGER.info('download successful, mission is now available')
def kill(force: bool = False, queue: bool = False): """Kills DCS application""" CANCEL_QUEUED_KILL.put(1) if DCS.there_are_connected_players(): if not force: if queue: DCS.queue_kill() else: LOGGER.error( 'there are connected players; cannot kill the server now' ' (use "--force" to kill anyway)') return else: LOGGER.warning('forcing kill with connected players') LOGGER.debug('setting context for DCS kill') CTX.dcs_do_kill = True
async def _read_socket(self): await asyncio.sleep(0.1) try: data, _ = self.sock.recvfrom(4096) data = json.loads(data.decode().strip()) if data['type'] == 'ping': self._parse_ping(data) elif data['type'] == 'status': self._parse_status(data) # elif data['type'] == 'mission_load': # self._parse_mission_load(data) else: LOGGER.warning('unknown command received on DCS socket: "%s"', data['type']) except socket.timeout: pass
def set_affinity(self): """ Sets the DCS process CPU affinity to the CFG value """ def _command(): if list(self._app.cpu_affinity()) != list( DCSConfig.DCS_CPU_AFFINITY()): LOGGER.debug('setting DCS process affinity to: %s', DCSConfig.DCS_CPU_AFFINITY()) self._app.cpu_affinity(list(DCSConfig.DCS_CPU_AFFINITY())) while True: if DCSConfig.DCS_CPU_AFFINITY(): if core.CTX.exit: return self._work_with_dcs_process(_command) else: LOGGER.warning('no CPU affinity given in config file') return time.sleep(30)
def monitor_cpu_usage(self): """ Gets the CPU usage of "DCS.exe" over 5 seconds, and sends an alert if the given threshold is exceeded Threshold is set via the config value "DCS_HIGH_CPU_USAGE", and it defaults to 80% """ while not core.CTX.exit: try: if self.app and self.app.is_running(): cpu_usage = int( self.app.cpu_percent( DCSConfig.DCS_HIGH_CPU_USAGE_INTERVAL())) mem_usage = int(self.app.memory_percent()) core.Status.dcs_cpu_usage = f'{cpu_usage}%' if core.CTX.dcs_show_cpu_usage or core.CTX.dcs_show_cpu_usage_once: commands.DISCORD.say(f'DCS cpu usage: {cpu_usage}%') core.CTX.dcs_show_cpu_usage_once = False if DCSConfig.DCS_HIGH_CPU_USAGE(): if cpu_usage > DCSConfig.DCS_HIGH_CPU_USAGE( ) and not core.Status.paused: LOGGER.warning( 'DCS cpu usage has been higher than %s%% for %s seconds', DCSConfig.DCS_HIGH_CPU_USAGE(), DCSConfig.DCS_HIGH_CPU_USAGE_INTERVAL(), ) now_ = utils.now() core.CTX.dcs_mem_history.append((now_, mem_usage)) core.CTX.dcs_cpu_history.append((now_, cpu_usage)) except psutil.NoSuchProcess: pass # I didn't think it could, happen, but of course it did ... # See https://github.com/132nd-vWing/ESST/issues/59 except AttributeError: pass
async def monitor_connection(): """ Loop that checks WAN every 5 seconds """ LOGGER.debug('starting connection monitoring loop') while not core.CTX.exit: current_status = await wan_available() if current_status != core.CTX.wan: if current_status: LOGGER.debug('connected to the Internet') commands.DISCORD.say( 'I just lost internet connection, server is scheduled to be restarted' ) else: LOGGER.warning('internet connection lost !') commands.DCS.kill(force=False, queue=True) core.CTX.wan = current_status await asyncio.sleep(10) LOGGER.debug('end of connection monitoring loop')
def _load(name, metar_or_icao, time, max_wind, min_wind, force): # noqa: C901 if max_wind or min_wind: LOGGER.warning( '"min_wind" and "max_wind" have been disabled for the time being') if name is None: mission = missions_manager.get_running_mission() if not mission: LOGGER.error('unable to retrieve current mission') return else: try: LOGGER.debug('trying to cast mission name into an int: %s', name) mission_number = int(name) except ValueError: LOGGER.debug('loading mission name: %s', name) mission = missions_manager.MissionPath(name) if not mission: LOGGER.debug('mission path not found: %s', mission.path) LOGGER.error('mission file not found: %s', mission.name) return else: LOGGER.debug('loading mission number: %s', mission_number) mission = _mission_index_to_mission_name(mission_number) if not mission: LOGGER.error( 'invalid mission index: %s; use "!mission show" to see available indices', mission_number) return LOGGER.info('loading mission file: %s', mission.path) if time: try: mission_time = elib_miz.MissionTime.from_string(time) LOGGER.info('setting mission time: %s', mission_time.iso_format) except elib_miz.exc.InvalidDateTimeString: LOGGER.error('invalid date-time string: %s', time) return except ValueError as err: LOGGER.error(err) return else: mission_time = None if metar_or_icao: LOGGER.info('analyzing METAR string: %s', metar_or_icao) try: weather_ = elib_wx.Weather(metar_or_icao) LOGGER.info('setting mission weather: %s', weather_.as_str()) except elib_wx.BadStationError: LOGGER.error('wrong ICAO code: %s', metar_or_icao) return LOGGER.info('METAR: %s', weather_.raw_metar_str) else: LOGGER.info('building METAR from mission file') # noinspection SpellCheckingInspection weather_ = elib_wx.Weather(str(mission.path)) LOGGER.info('METAR: %s', weather_.as_str()) commands.DCS.block_start('loading mission') commands.DCS.kill(force=force) try: LOGGER.debug('waiting on DCS application to close') while core.Status.dcs_application != 'not running': sleep(1) LOGGER.debug('DCS has closed, carrying on') active_mission = mission if time: mission_time.apply_to_miz(str(mission.path), str(mission.auto.path), overwrite=True) active_mission = mission.auto if metar_or_icao: weather_.apply_to_miz(str(mission.path), str(mission.auto.path), overwrite=True) active_mission = mission.auto active_mission.set_as_active(weather_) finally: commands.DCS.unblock_start('loading mission')
def reboot(force: bool = False): """ Restart the server computer (protected) """ LOGGER.warning('rebooting server, ciao a tutti !') commands.SERVER.reboot(force)
def _show_graph(graph): if graph: DISCORD.send_file(graph) else: LOGGER.warning('failed to create the graph')
def clean_old_logs(): """ Removes old logs """ LOGGER.warning('removal of old logs has been temporarily disabled')
async def run(self): # noqa: C901 """ Entry point of the thread """ if not core.CTX.start_dcs_loop: LOGGER.debug('skipping DCS application loop') return if not await self._get_dcs_version_from_executable(): return await core.CTX.loop.run_in_executor(None, install_game_gui_hooks) await core.CTX.loop.run_in_executor( None, server_settings.write_server_settings) await core.CTX.loop.run_in_executor( None, missions_manager.get_latest_mission_from_github) await core.CTX.loop.run_in_executor(None, missions_manager.initial_setup) LOGGER.debug('starting DCS monitoring thread') if DCS.dcs_cannot_start(): blockers = ", ".join(DCS.dcs_cannot_start()) if blockers not in self._blockers_warned: self._blockers_warned.add(blockers) LOGGER.warning('DCS is prevented to start by: %s', ', '.join(DCS.dcs_cannot_start())) else: if self._blockers_warned: self._blockers_warned = set() await self._try_to_connect_to_existing_dcs_application() await self._start_new_dcs_application_if_needed() cpu_monitor_thread = core.CTX.loop.run_in_executor( None, self.monitor_cpu_usage) cpu_affinity_thread = core.CTX.loop.run_in_executor( None, self.set_affinity) cpu_priority_thread = core.CTX.loop.run_in_executor( None, self.set_priority) while True: if core.CTX.exit: LOGGER.debug('interrupted by exit signal') await cpu_monitor_thread await cpu_affinity_thread await cpu_priority_thread break if DCS.dcs_cannot_start(): blockers = ", ".join(DCS.dcs_cannot_start()) if blockers not in self._blockers_warned: self._blockers_warned.add(blockers) LOGGER.warning('DCS is prevented to start by: %s', ', '.join(DCS.dcs_cannot_start())) else: if self._blockers_warned: self._blockers_warned = set() await self._check_if_dcs_is_running() if not self.process_pid: LOGGER.debug('DCS has stopped, re-starting') await self.restart() if core.CTX.dcs_do_kill: await self.kill_running_app() if core.CTX.dcs_do_restart: await self.restart() await asyncio.sleep(0.1) LOGGER.debug('end of DCS loop')