mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-23 11:13:51 -05:00
[Audio] Refactor internal Lavalink server management (#2495)
* Refactor internal Lavalink server management Killing many birds with one stone here. - Made server manager into class-based API with two public methods: `start()` and `shutdown()`. Must be re-instantiated each time it is restarted. - Using V3 universal Lavalink.jar hosted on Cog-Creators/Lavalink-Jars repository. - Uses output of `java -jar Lavalink.jar --version` to check if a new jar needs to be downloaded. - `ServerManager.start()` won't return until server is ready, i.e. when "Started Launcher in X seconds" message is printed to STDOUT. - `shlex.quote()` is used so spaces in path to Lavalink.jar don't cause issues. - Enabling external Lavalink will cause internal server to be terminated. - Disabling internal Lavalink will no longer reset settings in config - instead, hard-coded values will be used when connecting to an internal server. - Internal server will now run both WS and REST servers on port 2333, meaning one less port will need to be taken up. - Now using `asyncio.subprocess` module so waiting on and reading from subprocesses can be done asynchronously. Signed-off-by: Toby Harradine <tobyharradine@gmail.com> * Don't use shlex.quote on Windows Signed-off-by: Toby <tobyharradine@gmail.com> * Don't use shlex.quote at all I misread a note in the python docs and assumed it was best to use it. Turns out the note only applies to `asyncio.create_subprocess_shell`. Signed-off-by: Toby <tobyharradine@gmail.com> * Missed the port on the rebase * Ignore invalid architectures and inform users when commands are used. * Style fix
This commit is contained in:
@@ -14,6 +14,7 @@ import os
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
from typing import Optional
|
||||
import redbot.core
|
||||
from redbot.core import Config, commands, checks, bank
|
||||
from redbot.core.data_manager import cog_data_path
|
||||
@@ -29,7 +30,7 @@ from redbot.core.utils.menus import (
|
||||
)
|
||||
from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
|
||||
from urllib.parse import urlparse
|
||||
from .manager import shutdown_lavalink_server, start_lavalink_server, maybe_download_lavalink
|
||||
from .manager import ServerManager
|
||||
|
||||
_ = Translator("Audio", __file__)
|
||||
|
||||
@@ -43,41 +44,45 @@ log = logging.getLogger("red.audio")
|
||||
class Audio(commands.Cog):
|
||||
"""Play audio through voice channels."""
|
||||
|
||||
_default_lavalink_settings = {
|
||||
"host": "localhost",
|
||||
"rest_port": 2333,
|
||||
"ws_port": 2333,
|
||||
"password": "youshallnotpass",
|
||||
}
|
||||
|
||||
def __init__(self, bot):
|
||||
super().__init__()
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, 2711759130, force_registration=True)
|
||||
|
||||
default_global = {
|
||||
"host": "localhost",
|
||||
"rest_port": "2333",
|
||||
"ws_port": "2332",
|
||||
"password": "youshallnotpass",
|
||||
"status": False,
|
||||
"current_version": redbot.core.VersionInfo.from_str("3.0.0a0").to_json(),
|
||||
"use_external_lavalink": False,
|
||||
"restrict": True,
|
||||
"localpath": str(cog_data_path(raw_name="Audio")),
|
||||
}
|
||||
default_global = dict(
|
||||
status=False,
|
||||
use_external_lavalink=False,
|
||||
restrict=True,
|
||||
current_version=redbot.core.VersionInfo.from_str("3.0.0a0").to_json(),
|
||||
localpath=str(cog_data_path(raw_name="Audio")),
|
||||
**self._default_lavalink_settings,
|
||||
)
|
||||
|
||||
default_guild = {
|
||||
"disconnect": False,
|
||||
"dj_enabled": False,
|
||||
"dj_role": None,
|
||||
"emptydc_enabled": False,
|
||||
"emptydc_timer": 0,
|
||||
"jukebox": False,
|
||||
"jukebox_price": 0,
|
||||
"maxlength": 0,
|
||||
"playlists": {},
|
||||
"notify": False,
|
||||
"repeat": False,
|
||||
"shuffle": False,
|
||||
"thumbnail": False,
|
||||
"volume": 100,
|
||||
"vote_enabled": False,
|
||||
"vote_percent": 0,
|
||||
}
|
||||
default_guild = dict(
|
||||
disconnect=False,
|
||||
dj_enabled=False,
|
||||
dj_role=None,
|
||||
emptydc_enabled=False,
|
||||
emptydc_timer=0,
|
||||
jukebox=False,
|
||||
jukebox_price=0,
|
||||
maxlength=0,
|
||||
playlists={},
|
||||
notify=False,
|
||||
repeat=False,
|
||||
shuffle=False,
|
||||
thumbnail=False,
|
||||
volume=100,
|
||||
vote_enabled=False,
|
||||
vote_percent=0,
|
||||
)
|
||||
|
||||
self.config.register_guild(**default_guild)
|
||||
self.config.register_global(**default_global)
|
||||
@@ -86,9 +91,24 @@ class Audio(commands.Cog):
|
||||
self._connect_task = None
|
||||
self._disconnect_task = None
|
||||
self._cleaned_up = False
|
||||
|
||||
self.spotify_token = None
|
||||
self.play_lock = {}
|
||||
|
||||
self._manager: Optional[ServerManager] = None
|
||||
|
||||
async def cog_before_invoke(self, ctx):
|
||||
if self.llsetup in [ctx.command, ctx.command.root_parent]:
|
||||
pass
|
||||
elif self._connect_task.cancelled:
|
||||
await ctx.send(
|
||||
"You have attempted to run Audio's Lavalink server on an unsupported"
|
||||
" architecture. Only settings related commands will be available."
|
||||
)
|
||||
raise RuntimeError(
|
||||
"Not running audio command due to invalid machine architecture for Lavalink."
|
||||
)
|
||||
|
||||
async def initialize(self):
|
||||
self._restart_connect()
|
||||
self._disconnect_task = self.bot.loop.create_task(self.disconnect_timer())
|
||||
@@ -103,16 +123,33 @@ class Audio(commands.Cog):
|
||||
async def attempt_connect(self, timeout: int = 30):
|
||||
while True: # run until success
|
||||
external = await self.config.use_external_lavalink()
|
||||
if not external:
|
||||
shutdown_lavalink_server()
|
||||
await maybe_download_lavalink(self.bot.loop, self)
|
||||
await start_lavalink_server(self.bot.loop)
|
||||
try:
|
||||
if external is False:
|
||||
settings = self._default_lavalink_settings
|
||||
host = settings["host"]
|
||||
password = settings["password"]
|
||||
rest_port = settings["rest_port"]
|
||||
ws_port = settings["ws_port"]
|
||||
if self._manager is not None:
|
||||
await self._manager.shutdown()
|
||||
self._manager = ServerManager()
|
||||
try:
|
||||
await self._manager.start()
|
||||
except RuntimeError as exc:
|
||||
log.exception(
|
||||
"Exception whilst starting internal Lavalink server, retrying...",
|
||||
exc_info=exc,
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
except asyncio.CancelledError:
|
||||
log.exception("Invalid machine architecture, cannot run Lavalink.")
|
||||
break
|
||||
else:
|
||||
host = await self.config.host()
|
||||
password = await self.config.password()
|
||||
rest_port = await self.config.rest_port()
|
||||
ws_port = await self.config.ws_port()
|
||||
|
||||
try:
|
||||
await lavalink.initialize(
|
||||
bot=self.bot,
|
||||
host=host,
|
||||
@@ -122,9 +159,10 @@ class Audio(commands.Cog):
|
||||
timeout=timeout,
|
||||
)
|
||||
return # break infinite loop
|
||||
except Exception:
|
||||
if not external:
|
||||
shutdown_lavalink_server()
|
||||
except asyncio.TimeoutError:
|
||||
log.error("Connecting to Lavalink server timed out, retrying...")
|
||||
if external is False and self._manager is not None:
|
||||
await self._manager.shutdown()
|
||||
await asyncio.sleep(1) # prevent busylooping
|
||||
|
||||
async def event_handler(self, player, event_type, extra):
|
||||
@@ -3104,19 +3142,16 @@ class Audio(commands.Cog):
|
||||
await self.config.use_external_lavalink.set(not external)
|
||||
|
||||
if external:
|
||||
await self.config.host.set("localhost")
|
||||
await self.config.password.set("youshallnotpass")
|
||||
await self.config.rest_port.set(2333)
|
||||
await self.config.ws_port.set(2332)
|
||||
embed = discord.Embed(
|
||||
colour=await ctx.embed_colour(),
|
||||
title=_("External lavalink server: {true_or_false}.").format(
|
||||
true_or_false=not external
|
||||
),
|
||||
)
|
||||
embed.set_footer(text=_("Defaults reset."))
|
||||
await ctx.send(embed=embed)
|
||||
else:
|
||||
if self._manager is not None:
|
||||
await self._manager.shutdown()
|
||||
await self._embed_msg(
|
||||
ctx,
|
||||
_("External lavalink server: {true_or_false}.").format(true_or_false=not external),
|
||||
@@ -3229,6 +3264,8 @@ class Audio(commands.Cog):
|
||||
async def _check_external(self):
|
||||
external = await self.config.use_external_lavalink()
|
||||
if not external:
|
||||
if self._manager is not None:
|
||||
await self._manager.shutdown()
|
||||
await self.config.use_external_lavalink.set(True)
|
||||
return True
|
||||
else:
|
||||
@@ -3597,7 +3634,8 @@ class Audio(commands.Cog):
|
||||
|
||||
lavalink.unregister_event_listener(self.event_handler)
|
||||
self.bot.loop.create_task(lavalink.close())
|
||||
shutdown_lavalink_server()
|
||||
if self._manager is not None:
|
||||
self.bot.loop.create_task(self._manager.shutdown())
|
||||
self._cleaned_up = True
|
||||
|
||||
__del__ = cog_unload
|
||||
|
||||
Reference in New Issue
Block a user