Merge V3/feature/audio into V3/develop (a.k.a. audio refactor) (#3459)

This commit is contained in:
Draper
2020-05-20 21:30:06 +01:00
committed by GitHub
parent ef76affd77
commit 8fa47cb789
53 changed files with 12372 additions and 10144 deletions

View File

@@ -0,0 +1,12 @@
import logging
from ..cog_utils import CompositeMetaClass
from .lavalink import LavalinkTasks
from .player import PlayerTasks
from .startup import StartUpTasks
log = logging.getLogger("red.cogs.Audio.cog.Tasks")
class Tasks(LavalinkTasks, PlayerTasks, StartUpTasks, metaclass=CompositeMetaClass):
"""Class joining all task subclasses"""

View File

@@ -0,0 +1,113 @@
import asyncio
import logging
import lavalink
from ...errors import LavalinkDownloadFailed
from ...manager import ServerManager
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Tasks.lavalink")
class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
def lavalink_restart_connect(self) -> None:
if self.lavalink_connect_task:
self.lavalink_connect_task.cancel()
self.lavalink_connect_task = self.bot.loop.create_task(self.lavalink_attempt_connect())
async def lavalink_attempt_connect(self, timeout: int = 50) -> None:
self.lavalink_connection_aborted = False
max_retries = 5
retry_count = 0
while retry_count < max_retries:
external = await self.config.use_external_lavalink()
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.player_manager is not None:
await self.player_manager.shutdown()
self.player_manager = ServerManager()
try:
await self.player_manager.start()
except LavalinkDownloadFailed as exc:
await asyncio.sleep(1)
if exc.should_retry:
log.exception(
"Exception whilst starting internal Lavalink server, retrying...",
exc_info=exc,
)
retry_count += 1
continue
else:
log.exception(
"Fatal exception whilst starting internal Lavalink server, "
"aborting...",
exc_info=exc,
)
self.lavalink_connection_aborted = True
raise
except asyncio.CancelledError:
log.exception("Invalid machine architecture, cannot run Lavalink.")
raise
except Exception as exc:
log.exception(
"Unhandled exception whilst starting internal Lavalink server, "
"aborting...",
exc_info=exc,
)
self.lavalink_connection_aborted = True
raise
else:
break
else:
config_data = await self.config.all()
host = config_data["host"]
password = config_data["password"]
rest_port = config_data["rest_port"]
ws_port = config_data["ws_port"]
break
else:
log.critical(
"Setting up the Lavalink server failed after multiple attempts. "
"See above tracebacks for details."
)
self.lavalink_connection_aborted = True
return
retry_count = 0
while retry_count < max_retries:
try:
await lavalink.initialize(
bot=self.bot,
host=host,
password=password,
rest_port=rest_port,
ws_port=ws_port,
timeout=timeout,
)
except asyncio.TimeoutError:
log.error("Connecting to Lavalink server timed out, retrying...")
if external is False and self.player_manager is not None:
await self.player_manager.shutdown()
retry_count += 1
await asyncio.sleep(1) # prevent busylooping
except Exception as exc:
log.exception(
"Unhandled exception whilst connecting to Lavalink, aborting...", exc_info=exc
)
self.lavalink_connection_aborted = True
raise
else:
break
else:
self.lavalink_connection_aborted = True
log.critical(
"Connecting to the Lavalink server failed after multiple attempts. "
"See above tracebacks for details."
)

View File

@@ -0,0 +1,70 @@
import asyncio
import logging
import time
from typing import Dict
import lavalink
from redbot.core.utils import AsyncIter
from ...audio_logging import debug_exc_log
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Tasks.player")
class PlayerTasks(MixinMeta, metaclass=CompositeMetaClass):
async def player_automated_timer(self) -> None:
stop_times: Dict = {}
pause_times: Dict = {}
while True:
async for p in AsyncIter(lavalink.all_players()):
server = p.channel.guild
if [self.bot.user] == p.channel.members:
stop_times.setdefault(server.id, time.time())
pause_times.setdefault(server.id, time.time())
else:
stop_times.pop(server.id, None)
if p.paused and server.id in pause_times:
try:
await p.pause(False)
except Exception as err:
debug_exc_log(
log,
err,
f"Exception raised in Audio's unpausing player for {server.id}.",
)
pause_times.pop(server.id, None)
servers = stop_times.copy()
servers.update(pause_times)
async for sid in AsyncIter(servers, steps=5):
server_obj = self.bot.get_guild(sid)
if sid in stop_times and await self.config.guild(server_obj).emptydc_enabled():
emptydc_timer = await self.config.guild(server_obj).emptydc_timer()
if (time.time() - stop_times[sid]) >= emptydc_timer:
stop_times.pop(sid)
try:
player = lavalink.get_player(sid)
await player.stop()
await player.disconnect()
except Exception as err:
if "No such player for that guild" in str(err):
stop_times.pop(sid, None)
debug_exc_log(
log, err, f"Exception raised in Audio's emptydc_timer for {sid}."
)
elif (
sid in pause_times and await self.config.guild(server_obj).emptypause_enabled()
):
emptypause_timer = await self.config.guild(server_obj).emptypause_timer()
if (time.time() - pause_times.get(sid, 0)) >= emptypause_timer:
try:
await lavalink.get_player(sid).pause()
except Exception as err:
if "No such player for that guild" in str(err):
pause_times.pop(sid, None)
debug_exc_log(
log, err, f"Exception raised in Audio's pausing for {sid}."
)
await asyncio.sleep(5)

View File

@@ -0,0 +1,49 @@
import logging
import lavalink
from redbot.core.data_manager import cog_data_path
from redbot.core.utils.dbtools import APSWConnectionWrapper
from ...apis.interface import AudioAPIInterface
from ...apis.playlist_wrapper import PlaylistWrapper
from ..abc import MixinMeta
from ..cog_utils import _SCHEMA_VERSION, CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Tasks.startup")
class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
def start_up_task(self):
# There has to be a task since this requires the bot to be ready
# If it waits for ready in startup, we cause a deadlock during initial load
# as initial load happens before the bot can ever be ready.
self.cog_init_task = self.bot.loop.create_task(self.initialize())
async def initialize(self) -> None:
await self.bot.wait_until_red_ready()
# Unlike most cases, we want the cache to exit before migration.
try:
self.db_conn = APSWConnectionWrapper(
str(cog_data_path(self.bot.get_cog("Audio")) / "Audio.db")
)
self.api_interface = AudioAPIInterface(
self.bot, self.config, self.session, self.db_conn, self.bot.get_cog("Audio")
)
self.playlist_api = PlaylistWrapper(self.bot, self.config, self.db_conn)
await self.playlist_api.init()
await self.api_interface.initialize()
await self.data_schema_migration(
from_version=await self.config.schema_version(), to_version=_SCHEMA_VERSION
)
await self.playlist_api.delete_scheduled()
self.lavalink_restart_connect()
self.player_automated_timer_task = self.bot.loop.create_task(
self.player_automated_timer()
)
lavalink.register_event_listener(self.lavalink_event_handler)
except Exception as err:
log.exception("Audio failed to start up, please report this issue.", exc_info=err)
raise err
self.cog_ready_event.set()