import shlex import shutil import asyncio import asyncio.subprocess import os import logging import re from subprocess import Popen, DEVNULL from typing import Optional, Tuple _JavaVersion = Tuple[int, int] log = logging.getLogger("red.audio.manager") proc = None SHUTDOWN = asyncio.Event() def has_java_error(pid): from . import LAVALINK_DOWNLOAD_DIR poss_error_file = LAVALINK_DOWNLOAD_DIR / "hs_err_pid{}.log".format(pid) return poss_error_file.exists() async def monitor_lavalink_server(loop): while not SHUTDOWN.is_set(): if proc.poll() is not None: break await asyncio.sleep(0.5) if not SHUTDOWN.is_set(): log.info("Lavalink jar shutdown.") if not has_java_error(proc.pid): log.info("Restarting Lavalink jar.") await start_lavalink_server(loop) else: log.error( "Your Java is borked. Please find the hs_err_pid{}.log file" " in the Audio data folder and report this issue.".format(proc.pid) ) async def has_java(loop) -> Tuple[bool, Optional[_JavaVersion]]: java_available = shutil.which("java") is not None if not java_available: return False, None version = await get_java_version(loop) return (2, 0) > version >= (1, 8) or version >= (8, 0), version async def get_java_version(loop) -> _JavaVersion: """ This assumes we've already checked that java exists. """ _proc: asyncio.subprocess.Process = await asyncio.create_subprocess_exec( "java", "-version", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, loop=loop, ) # java -version outputs to stderr _, err = await _proc.communicate() version_info: str = err.decode("utf-8") # We expect the output to look something like: # $ java -version # ... # ... version "MAJOR.MINOR.PATCH[_BUILD]" ... # ... # We only care about the major and minor parts though. version_line_re = re.compile( r'version "(?P\d+).(?P\d+).\d+(?:_\d+)?(?:-[A-Za-z0-9]+)?"' ) short_version_re = re.compile(r'version "(?P\d+)"') lines = version_info.splitlines() for line in lines: match = version_line_re.search(line) short_match = short_version_re.search(line) if match: return int(match["major"]), int(match["minor"]) elif short_match: return int(short_match["major"]), 0 raise RuntimeError( "The output of `java -version` was unexpected. Please report this issue on Red's " "issue tracker." ) async def start_lavalink_server(loop): java_available, java_version = await has_java(loop) if not java_available: raise RuntimeError("You must install Java 1.8+ for Lavalink to run.") extra_flags = "" if java_version == (1, 8): extra_flags = "-Dsun.zip.disableMemoryMapping=true" from . import LAVALINK_DOWNLOAD_DIR, LAVALINK_JAR_FILE start_cmd = "java {} -jar {}".format(extra_flags, LAVALINK_JAR_FILE.resolve()) global proc proc = Popen( shlex.split(start_cmd, posix=os.name == "posix"), cwd=str(LAVALINK_DOWNLOAD_DIR), stdout=DEVNULL, stderr=DEVNULL, ) log.info("Lavalink jar started. PID: {}".format(proc.pid)) loop.create_task(monitor_lavalink_server(loop)) def shutdown_lavalink_server(): log.info("Shutting down lavalink server.") SHUTDOWN.set() global proc if proc is not None: proc.terminate() proc.wait() proc = None