Skip to content

Commit

Permalink
[Audio] Connect to lavalink in the background (#2460)
Browse files Browse the repository at this point in the history
Also:
- restart and reconnect if connection settings change
  - shutdown and restart if not configured to use external
- show a message in [p]play et al. when the connection hasn't been made
- move the JAR download to manager so audio.py can access it
- only start if no process exists
- bump red-lavalink to 0.2.3

Resolves #2306
  • Loading branch information
calebj authored and Tobotimus committed Feb 16, 2019
1 parent e97240e commit 343132a
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 54 deletions.
26 changes: 20 additions & 6 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 1 addition & 27 deletions redbot/cogs/audio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from pathlib import Path
from aiohttp import ClientSession
import shutil
import logging

from .audio import Audio
from .manager import start_lavalink_server
from .manager import start_lavalink_server, maybe_download_lavalink
from redbot.core import commands
from redbot.core.data_manager import cog_data_path
import redbot.core
Expand All @@ -22,30 +20,6 @@
BUNDLED_APP_YML_FILE = Path(__file__).parent / "data/application.yml"


async def download_lavalink(session):
with LAVALINK_JAR_FILE.open(mode="wb") as f:
async with session.get(LAVALINK_DOWNLOAD_URL) as resp:
while True:
chunk = await resp.content.read(512)
if not chunk:
break
f.write(chunk)


async def maybe_download_lavalink(loop, cog):
jar_exists = LAVALINK_JAR_FILE.exists()
current_build = redbot.core.VersionInfo.from_json(await cog.config.current_version())

if not jar_exists or current_build < redbot.core.version_info:
log.info("Downloading Lavalink.jar")
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
async with ClientSession(loop=loop) as session:
await download_lavalink(session)
await cog.config.current_version.set(redbot.core.version_info.to_json())

shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))


async def setup(bot: commands.Bot):
cog = Audio(bot)
if not await cog.config.use_external_lavalink():
Expand Down
90 changes: 72 additions & 18 deletions redbot/cogs/audio/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
)
from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
from urllib.parse import urlparse
from .manager import shutdown_lavalink_server
from .manager import shutdown_lavalink_server, start_lavalink_server, maybe_download_lavalink

_ = Translator("Audio", __file__)

Expand Down Expand Up @@ -73,26 +73,47 @@ def __init__(self, bot):
self.config.register_global(**default_global)
self.skip_votes = {}
self.session = aiohttp.ClientSession()
self._connect_task = None
self._disconnect_task = None
self._cleaned_up = False

async def initialize(self):
host = await self.config.host()
password = await self.config.password()
rest_port = await self.config.rest_port()
ws_port = await self.config.ws_port()

await lavalink.initialize(
bot=self.bot,
host=host,
password=password,
rest_port=rest_port,
ws_port=ws_port,
timeout=60,
)
self._restart_connect()
self._disconnect_task = self.bot.loop.create_task(self.disconnect_timer())
lavalink.register_event_listener(self.event_handler)

self._disconnect_task = self.bot.loop.create_task(self.disconnect_timer())
def _restart_connect(self):
if self._connect_task:
self._connect_task.cancel()

self._connect_task = self.bot.loop.create_task(self.attempt_connect())

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:
host = await self.config.host()
password = await self.config.password()
rest_port = await self.config.rest_port()
ws_port = await self.config.ws_port()

await lavalink.initialize(
bot=self.bot,
host=host,
password=password,
rest_port=rest_port,
ws_port=ws_port,
timeout=timeout,
)
return # break infinite loop
except Exception:
if not external:
shutdown_lavalink_server()
await asyncio.sleep(1) # prevent busylooping

async def event_handler(self, player, event_type, extra):
notify = await self.config.guild(player.channel.guild).notify()
Expand Down Expand Up @@ -883,6 +904,10 @@ async def play(self, ctx, *, query):
player.store("connect", datetime.datetime.utcnow())
except AttributeError:
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
except IndexError:
return await self._embed_msg(
ctx, _("Connection to Lavalink has not yet been established.")
)
if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author):
return await self._embed_msg(ctx, _("You need the DJ role to queue tracks."))
Expand Down Expand Up @@ -1388,9 +1413,15 @@ async def _playlist_check(self, ctx):
await lavalink.connect(ctx.author.voice.channel)
player = lavalink.get_player(ctx.guild.id)
player.store("connect", datetime.datetime.utcnow())
except IndexError:
await self._embed_msg(
ctx, _("Connection to Lavalink has not yet been established.")
)
return False
except AttributeError:
await self._embed_msg(ctx, _("Connect to a voice channel first."))
return False

player = lavalink.get_player(ctx.guild.id)
player.store("channel", ctx.channel.id)
player.store("guild", ctx.guild.id)
Expand Down Expand Up @@ -1768,6 +1799,10 @@ async def _search_menu(
player.store("connect", datetime.datetime.utcnow())
except AttributeError:
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
except IndexError:
return await self._embed_msg(
ctx, _("Connection to Lavalink has not yet been established.")
)
player = lavalink.get_player(ctx.guild.id)
shuffle = await self.config.guild(ctx.guild).shuffle()
player.store("channel", ctx.channel.id)
Expand Down Expand Up @@ -1852,6 +1887,10 @@ async def _search_button_action(self, ctx, tracks, emoji, page):
player.store("connect", datetime.datetime.utcnow())
except AttributeError:
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
except IndexError:
return await self._embed_msg(
ctx, _("Connection to Lavalink has not yet been established.")
)
player = lavalink.get_player(ctx.guild.id)
jukebox_price = await self.config.guild(ctx.guild).jukebox_price()
shuffle = await self.config.guild(ctx.guild).shuffle()
Expand All @@ -1872,7 +1911,6 @@ async def _search_button_action(self, ctx, tracks, emoji, page):
except IndexError:
search_choice = tracks[-1]
try:
search_check = search_choice.uri
if "localtracks" in search_choice.uri:
if search_choice.title == "Unknown title":
description = "**{} - {}**\n{}".format(
Expand Down Expand Up @@ -2308,6 +2346,7 @@ async def external(self, ctx):
"""Toggle using external lavalink servers."""
external = await self.config.use_external_lavalink()
await self.config.use_external_lavalink.set(not external)

if external:
await self.config.host.set("localhost")
await self.config.password.set("youshallnotpass")
Expand All @@ -2320,13 +2359,15 @@ async def external(self, ctx):
),
)
embed.set_footer(text=_("Defaults reset."))
return await ctx.send(embed=embed)
await ctx.send(embed=embed)
else:
await self._embed_msg(
ctx,
_("External lavalink server: {true_or_false}.").format(true_or_false=not external),
)

self._restart_connect()

@llsetup.command()
async def host(self, ctx, host):
"""Set the lavalink server host."""
Expand All @@ -2340,6 +2381,8 @@ async def host(self, ctx, host):
else:
await self._embed_msg(ctx, _("Host set to {host}.").format(host=host))

self._restart_connect()

@llsetup.command()
async def password(self, ctx, password):
"""Set the lavalink server password."""
Expand All @@ -2356,6 +2399,8 @@ async def password(self, ctx, password):
ctx, _("Server password set to {password}.").format(password=password)
)

self._restart_connect()

@llsetup.command()
async def restport(self, ctx, rest_port: int):
"""Set the lavalink REST server port."""
Expand All @@ -2370,6 +2415,8 @@ async def restport(self, ctx, rest_port: int):
else:
await self._embed_msg(ctx, _("REST port set to {port}.").format(port=rest_port))

self._restart_connect()

@llsetup.command()
async def wsport(self, ctx, ws_port: int):
"""Set the lavalink websocket server port."""
Expand All @@ -2384,6 +2431,8 @@ async def wsport(self, ctx, ws_port: int):
else:
await self._embed_msg(ctx, _("Websocket port set to {port}.").format(port=ws_port))

self._restart_connect()

async def _check_external(self):
external = await self.config.use_external_lavalink()
if not external:
Expand Down Expand Up @@ -2530,7 +2579,7 @@ def _match_url(url):
try:
query_url = urlparse(url)
return all([query_url.scheme, query_url.netloc, query_url.path])
except:
except Exception:
return False

@staticmethod
Expand Down Expand Up @@ -2616,8 +2665,13 @@ async def on_voice_state_update(self, member, before, after):
def __unload(self):
if not self._cleaned_up:
self.session.detach()

if self._disconnect_task:
self._disconnect_task.cancel()

if self._connect_task:
self._connect_task.cancel()

lavalink.unregister_event_listener(self.event_handler)
self.bot.loop.create_task(lavalink.close())
shutdown_lavalink_server()
Expand Down
41 changes: 39 additions & 2 deletions redbot/cogs/audio/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
from subprocess import Popen, DEVNULL
from typing import Optional, Tuple

from aiohttp import ClientSession

import redbot.core

_JavaVersion = Tuple[int, int]

log = logging.getLogger("red.audio.manager")
Expand Down Expand Up @@ -108,6 +112,10 @@ async def start_lavalink_server(loop):
start_cmd = "java {} -jar {}".format(extra_flags, LAVALINK_JAR_FILE.resolve())

global proc

if proc and proc.poll() is None:
return # already running

proc = Popen(
shlex.split(start_cmd, posix=os.name == "posix"),
cwd=str(LAVALINK_DOWNLOAD_DIR),
Expand All @@ -121,10 +129,39 @@ async def start_lavalink_server(loop):


def shutdown_lavalink_server():
log.info("Shutting down lavalink server.")
SHUTDOWN.set()
global shutdown
shutdown = True
global proc
if proc is not None:
log.info("Shutting down lavalink server.")
proc.terminate()
proc.wait()
proc = None


async def download_lavalink(session):
from . import LAVALINK_DOWNLOAD_URL, LAVALINK_JAR_FILE

with LAVALINK_JAR_FILE.open(mode="wb") as f:
async with session.get(LAVALINK_DOWNLOAD_URL) as resp:
while True:
chunk = await resp.content.read(512)
if not chunk:
break
f.write(chunk)


async def maybe_download_lavalink(loop, cog):
from . import LAVALINK_DOWNLOAD_DIR, LAVALINK_JAR_FILE, BUNDLED_APP_YML_FILE, APP_YML_FILE

jar_exists = LAVALINK_JAR_FILE.exists()
current_build = redbot.core.VersionInfo.from_json(await cog.config.current_version())

if not jar_exists or current_build < redbot.core.version_info:
log.info("Downloading Lavalink.jar")
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
async with ClientSession(loop=loop) as session:
await download_lavalink(session)
await cog.config.current_version.set(redbot.core.version_info.to_json())

shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"pyyaml==3.13",
"raven==6.10.0",
"raven-aiohttp==0.7.0",
"red-lavalink==0.2.2",
"red-lavalink==0.2.3",
"schema==0.6.8",
"websockets==7.0",
"yarl==1.3.0",
Expand Down

0 comments on commit 343132a

Please sign in to comment.