From f27dc1de93cd7d92439ae9eb55b17ca35674fada Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Sun, 24 Jul 2022 23:49:35 -0300 Subject: [PATCH] Upgrading stability in errors occurrences --- Config/Configs.py | 1 + Config/Messages.py | 1 + DiscordCogs/MusicCog.py | 227 ++++++++++++++++++++-------------- Handlers/ClearHandler.py | 11 +- Handlers/HistoryHandler.py | 10 +- Handlers/LoopHandler.py | 16 ++- Handlers/MoveHandler.py | 13 +- Handlers/PlayHandler.py | 29 +++-- Handlers/QueueHandler.py | 14 ++- Handlers/ShuffleHandler.py | 10 +- Parallelism/PlayerProcess.py | 112 +++++++++++------ Parallelism/ProcessManager.py | 14 +++ Views/Embeds.py | 7 ++ 13 files changed, 310 insertions(+), 155 deletions(-) diff --git a/Config/Configs.py b/Config/Configs.py index 29139cb..09395bb 100644 --- a/Config/Configs.py +++ b/Config/Configs.py @@ -16,6 +16,7 @@ def __init__(self) -> None: '[ERROR] -> You must create and .env file with all required fields, see documentation for help') self.CLEANER_MESSAGES_QUANT = 5 + self.ACQUIRE_LOCK_TIMEOUT = 10 self.COMMANDS_PATH = 'DiscordCogs' self.VC_TIMEOUT = 600 diff --git a/Config/Messages.py b/Config/Messages.py index 7e24672..c1c5693 100644 --- a/Config/Messages.py +++ b/Config/Messages.py @@ -64,6 +64,7 @@ def __init__(self) -> None: self.DOWNLOADING_ERROR = "❌ It's impossible to download and play this video" self.EXTRACTING_ERROR = '❌ An error ocurred while searching for the songs' + self.ERROR_IN_PROCESS = "❌ Due to a internal error your player was restarted, skipping the song." self.MY_ERROR_BAD_COMMAND = 'This string serves to verify if some error was raised by myself on purpose' self.BAD_COMMAND_TITLE = 'Misuse of command' self.BAD_COMMAND = f'❌ Bad usage of this command, type {configs.BOT_PREFIX}help "command" to understand the command better' diff --git a/DiscordCogs/MusicCog.py b/DiscordCogs/MusicCog.py index 2158f84..7a6e7dd 100644 --- a/DiscordCogs/MusicCog.py +++ b/DiscordCogs/MusicCog.py @@ -35,155 +35,200 @@ def __init__(self, bot) -> None: @commands.command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar']) async def play(self, ctx: Context, *args) -> None: - controller = PlayHandler(ctx, self.__bot) - - response = await controller.run(args) - if response is not None: - view1 = EmbedView(response) - view2 = EmoteView(response) - await view1.run() - await view2.run() + try: + controller = PlayHandler(ctx, self.__bot) + + response = await controller.run(args) + if response is not None: + view1 = EmbedView(response) + view2 = EmoteView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name="queue", help=helper.HELP_QUEUE, description=helper.HELP_QUEUE_LONG, aliases=['q', 'fila', 'musicas']) async def queue(self, ctx: Context) -> None: - controller = QueueHandler(ctx, self.__bot) + try: + controller = QueueHandler(ctx, self.__bot) - response = await controller.run() - view2 = EmbedView(response) - await view2.run() + response = await controller.run() + view2 = EmbedView(response) + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name="skip", help=helper.HELP_SKIP, description=helper.HELP_SKIP_LONG, aliases=['s', 'pular', 'next']) async def skip(self, ctx: Context) -> None: - controller = SkipHandler(ctx, self.__bot) + try: + controller = SkipHandler(ctx, self.__bot) - response = await controller.run() - if response.success: - view = EmoteView(response) - else: - view = EmbedView(response) + response = await controller.run() + if response.success: + view = EmoteView(response) + else: + view = EmbedView(response) - await view.run() + await view.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='stop', help=helper.HELP_STOP, description=helper.HELP_STOP_LONG, aliases=['parar']) async def stop(self, ctx: Context) -> None: - controller = StopHandler(ctx, self.__bot) + try: + controller = StopHandler(ctx, self.__bot) - response = await controller.run() - if response.success: - view = EmoteView(response) - else: - view = EmbedView(response) + response = await controller.run() + if response.success: + view = EmoteView(response) + else: + view = EmbedView(response) - await view.run() + await view.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='pause', help=helper.HELP_PAUSE, description=helper.HELP_PAUSE_LONG, aliases=['pausar', 'pare']) async def pause(self, ctx: Context) -> None: - controller = PauseHandler(ctx, self.__bot) + try: + controller = PauseHandler(ctx, self.__bot) - response = await controller.run() - view1 = EmoteView(response) - view2 = EmbedView(response) - await view1.run() - await view2.run() + response = await controller.run() + view1 = EmoteView(response) + view2 = EmbedView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='resume', help=helper.HELP_RESUME, description=helper.HELP_RESUME_LONG, aliases=['soltar', 'despausar']) async def resume(self, ctx: Context) -> None: - controller = ResumeHandler(ctx, self.__bot) + try: + controller = ResumeHandler(ctx, self.__bot) - response = await controller.run() - view1 = EmoteView(response) - view2 = EmbedView(response) - await view1.run() - await view2.run() + response = await controller.run() + view1 = EmoteView(response) + view2 = EmbedView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='prev', help=helper.HELP_PREV, description=helper.HELP_PREV_LONG, aliases=['anterior', 'return', 'previous']) async def prev(self, ctx: Context) -> None: - controller = PrevHandler(ctx, self.__bot) + try: + controller = PrevHandler(ctx, self.__bot) + + response = await controller.run() + if response is not None: + view1 = EmbedView(response) + view2 = EmoteView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') - response = await controller.run() - if response is not None: + @commands.command(name='history', help=helper.HELP_HISTORY, description=helper.HELP_HISTORY_LONG, aliases=['historico', 'anteriores', 'hist']) + async def history(self, ctx: Context) -> None: + try: + controller = HistoryHandler(ctx, self.__bot) + + response = await controller.run() view1 = EmbedView(response) view2 = EmoteView(response) await view1.run() await view2.run() - - @commands.command(name='history', help=helper.HELP_HISTORY, description=helper.HELP_HISTORY_LONG, aliases=['historico', 'anteriores', 'hist']) - async def history(self, ctx: Context) -> None: - controller = HistoryHandler(ctx, self.__bot) - - response = await controller.run() - view1 = EmbedView(response) - view2 = EmoteView(response) - await view1.run() - await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='loop', help=helper.HELP_LOOP, description=helper.HELP_LOOP_LONG, aliases=['l', 'repeat']) async def loop(self, ctx: Context, args='') -> None: - controller = LoopHandler(ctx, self.__bot) + try: + controller = LoopHandler(ctx, self.__bot) - response = await controller.run(args) - view1 = EmoteView(response) - view2 = EmbedView(response) - await view1.run() - await view2.run() + response = await controller.run(args) + view1 = EmoteView(response) + view2 = EmbedView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='clear', help=helper.HELP_CLEAR, description=helper.HELP_CLEAR_LONG, aliases=['c', 'limpar']) async def clear(self, ctx: Context) -> None: - controller = ClearHandler(ctx, self.__bot) + try: + controller = ClearHandler(ctx, self.__bot) - response = await controller.run() - view = EmoteView(response) - await view.run() + response = await controller.run() + view = EmoteView(response) + await view.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='np', help=helper.HELP_NP, description=helper.HELP_NP_LONG, aliases=['playing', 'now', 'this']) async def now_playing(self, ctx: Context) -> None: - controller = NowPlayingHandler(ctx, self.__bot) + try: + controller = NowPlayingHandler(ctx, self.__bot) - response = await controller.run() - view1 = EmbedView(response) - view2 = EmoteView(response) - await view1.run() - await view2.run() + response = await controller.run() + view1 = EmbedView(response) + view2 = EmoteView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='shuffle', help=helper.HELP_SHUFFLE, description=helper.HELP_SHUFFLE_LONG, aliases=['aleatorio', 'misturar']) async def shuffle(self, ctx: Context) -> None: - controller = ShuffleHandler(ctx, self.__bot) + try: + controller = ShuffleHandler(ctx, self.__bot) - response = await controller.run() - view1 = EmbedView(response) - view2 = EmoteView(response) - await view1.run() - await view2.run() + response = await controller.run() + view1 = EmbedView(response) + view2 = EmoteView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='move', help=helper.HELP_MOVE, description=helper.HELP_MOVE_LONG, aliases=['m', 'mover']) async def move(self, ctx: Context, pos1, pos2='1') -> None: - controller = MoveHandler(ctx, self.__bot) + try: + controller = MoveHandler(ctx, self.__bot) - response = await controller.run(pos1, pos2) - view1 = EmbedView(response) - view2 = EmoteView(response) - await view1.run() - await view2.run() + response = await controller.run(pos1, pos2) + view1 = EmbedView(response) + view2 = EmoteView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='remove', help=helper.HELP_REMOVE, description=helper.HELP_REMOVE_LONG, aliases=['remover']) async def remove(self, ctx: Context, position) -> None: - controller = RemoveHandler(ctx, self.__bot) + try: + controller = RemoveHandler(ctx, self.__bot) - response = await controller.run(position) - view1 = EmbedView(response) - view2 = EmoteView(response) - await view1.run() - await view2.run() + response = await controller.run(position) + view1 = EmbedView(response) + view2 = EmoteView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') @commands.command(name='reset', help=helper.HELP_RESET, description=helper.HELP_RESET_LONG, aliases=['resetar']) async def reset(self, ctx: Context) -> None: - controller = ResetHandler(ctx, self.__bot) + try: + controller = ResetHandler(ctx, self.__bot) - response = await controller.run() - view1 = EmbedView(response) - view2 = EmoteView(response) - await view1.run() - await view2.run() + response = await controller.run() + view1 = EmbedView(response) + view2 = EmoteView(response) + await view1.run() + await view2.run() + except Exception as e: + print(f'[ERROR IN COG] -> {e}') def setup(bot): diff --git a/Handlers/ClearHandler.py b/Handlers/ClearHandler.py index 89572d3..765d0c3 100644 --- a/Handlers/ClearHandler.py +++ b/Handlers/ClearHandler.py @@ -16,7 +16,14 @@ async def run(self) -> HandlerResponse: if processInfo: # Clear the playlist playlist = processInfo.getPlaylist() - with processInfo.getLock(): + processLock = processInfo.getLock() + acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) + if acquired: playlist.clear() - + processLock.release() + processLock.release() + else: + processManager.resetProcess(self.guild, self.ctx) + embed = self.embeds.PLAYER_RESTARTED() + return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx) diff --git a/Handlers/HistoryHandler.py b/Handlers/HistoryHandler.py index c1989a9..b33c263 100644 --- a/Handlers/HistoryHandler.py +++ b/Handlers/HistoryHandler.py @@ -15,9 +15,17 @@ async def run(self) -> HandlerResponse: processManager = ProcessManager() processInfo = processManager.getRunningPlayerInfo(self.guild) if processInfo: - with processInfo.getLock(): + processLock = processInfo.getLock() + acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) + if acquired: playlist = processInfo.getPlaylist() history = playlist.getSongsHistory() + processLock.release() + else: + # If the player doesn't respond in time we restart it + processManager.resetProcess(self.guild, self.ctx) + embed = self.embeds.PLAYER_RESTARTED() + return HandlerResponse(self.ctx, embed) else: history = [] diff --git a/Handlers/LoopHandler.py b/Handlers/LoopHandler.py index 6654841..976b1b3 100644 --- a/Handlers/LoopHandler.py +++ b/Handlers/LoopHandler.py @@ -21,13 +21,16 @@ async def run(self, args: str) -> HandlerResponse: playlist = processInfo.getPlaylist() - with processInfo.getLock(): + processLock = processInfo.getLock() + acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) + if acquired: if args == '' or args is None: playlist.loop_all() embed = self.embeds.LOOP_ALL_ACTIVATED() return HandlerResponse(self.ctx, embed) args = args.lower() + error = None if playlist.getCurrentSong() is None: embed = self.embeds.NOT_PLAYING() error = BadCommandUsage() @@ -36,16 +39,19 @@ async def run(self, args: str) -> HandlerResponse: if args == 'one': playlist.loop_one() embed = self.embeds.LOOP_ONE_ACTIVATED() - return HandlerResponse(self.ctx, embed) elif args == 'all': playlist.loop_all() embed = self.embeds.LOOP_ALL_ACTIVATED() - return HandlerResponse(self.ctx, embed) elif args == 'off': playlist.loop_off() embed = self.embeds.LOOP_DISABLE() - return HandlerResponse(self.ctx, embed) else: error = BadCommandUsage() embed = self.embeds.BAD_LOOP_USE() - return HandlerResponse(self.ctx, embed, error) + + processLock.release() + return HandlerResponse(self.ctx, embed) + else: + processManager.resetProcess(self.guild, self.ctx) + embed = self.embeds.PLAYER_RESTARTED() + return HandlerResponse(self.ctx, embed) diff --git a/Handlers/MoveHandler.py b/Handlers/MoveHandler.py index fc04b50..8ce3ce9 100644 --- a/Handlers/MoveHandler.py +++ b/Handlers/MoveHandler.py @@ -20,10 +20,13 @@ async def run(self, pos1: str, pos2: str) -> HandlerResponse: error = BadCommandUsage() return HandlerResponse(self.ctx, embed, error) - with processInfo.getLock(): + processLock = processInfo.getLock() + acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) + if acquired: error = self.__validateInput(pos1, pos2) if error: embed = self.embeds.ERROR_EMBED(error.message) + processLock.release() return HandlerResponse(self.ctx, embed, error) playlist = processInfo.getPlaylist() @@ -32,17 +35,25 @@ async def run(self, pos1: str, pos2: str) -> HandlerResponse: if not playlist.validate_position(pos1) or not playlist.validate_position(pos2): error = InvalidInput() embed = self.embeds.PLAYLIST_RANGE_ERROR() + processLock.release() return HandlerResponse(self.ctx, embed, error) try: song = playlist.move_songs(pos1, pos2) song_name = song.title if song.title else song.identifier embed = self.embeds.SONG_MOVED(song_name, pos1, pos2) + processLock.release() return HandlerResponse(self.ctx, embed) except: + # Release the acquired Lock + processLock.release() embed = self.embeds.ERROR_MOVING() error = UnknownError() return HandlerResponse(self.ctx, embed, error) + else: + processManager.resetProcess(self.guild, self.ctx) + embed = self.embeds.PLAYER_RESTARTED() + return HandlerResponse(self.ctx, embed) def __validateInput(self, pos1: str, pos2: str) -> Union[VulkanError, None]: try: diff --git a/Handlers/PlayHandler.py b/Handlers/PlayHandler.py index 26e652e..032321c 100644 --- a/Handlers/PlayHandler.py +++ b/Handlers/PlayHandler.py @@ -36,8 +36,8 @@ async def run(self, args: str) -> HandlerResponse: raise InvalidInput(self.messages.INVALID_INPUT, self.messages.ERROR_TITLE) # Get the process context for the current guild - manager = ProcessManager() - processInfo = manager.getPlayerInfo(self.guild, self.ctx) + processManager = ProcessManager() + processInfo = processManager.getPlayerInfo(self.guild, self.ctx) playlist = processInfo.getPlaylist() process = processInfo.getProcess() if not process.is_alive(): # If process has not yet started, start @@ -56,22 +56,31 @@ async def run(self, args: str) -> HandlerResponse: error = DownloadingError() return HandlerResponse(self.ctx, embed, error) - # Add the unique song to the playlist and send a command to player process - with processInfo.getLock(): - playlist.add_song(song) - queue = processInfo.getQueue() - playCommand = VCommands(VCommandsType.PLAY, None) - queue.put(playCommand) - # If not playing if not playlist.getCurrentSong(): embed = self.embeds.SONG_ADDED(song.title) - return HandlerResponse(self.ctx, embed) + response = HandlerResponse(self.ctx, embed) else: # If already playing pos = len(playlist.getSongs()) embed = self.embeds.SONG_ADDED_TWO(song.info, pos) + response = HandlerResponse(self.ctx, embed) + + # Add the unique song to the playlist and send a command to player process + processLock = processInfo.getLock() + acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) + if acquired: + playlist.add_song(song) + # Release the acquired Lock + processLock.release() + queue = processInfo.getQueue() + playCommand = VCommands(VCommandsType.PLAY, None) + queue.put(playCommand) + else: + processManager.resetProcess(self.guild, self.ctx) + embed = self.embeds.PLAYER_RESTARTED() return HandlerResponse(self.ctx, embed) + return response else: # If multiple songs added # Trigger a task to download all songs and then store them in the process playlist asyncio.create_task(self.__downloadSongsAndStore(songs, processInfo)) diff --git a/Handlers/QueueHandler.py b/Handlers/QueueHandler.py index d25a1e3..f780d9b 100644 --- a/Handlers/QueueHandler.py +++ b/Handlers/QueueHandler.py @@ -15,14 +15,16 @@ def __init__(self, ctx: Context, bot: Client) -> None: async def run(self) -> HandlerResponse: # Retrieve the process of the guild - process = ProcessManager() - processInfo = process.getRunningPlayerInfo(self.guild) + processManager = ProcessManager() + processInfo = processManager.getRunningPlayerInfo(self.guild) if not processInfo: # If no process return empty list embed = self.embeds.EMPTY_QUEUE() return HandlerResponse(self.ctx, embed) # Acquire the Lock to manipulate the playlist - with processInfo.getLock(): + processLock = processInfo.getLock() + acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) + if acquired: playlist = processInfo.getPlaylist() if playlist.isLoopingOne(): @@ -54,4 +56,10 @@ async def run(self) -> HandlerResponse: text += f"**`{pos}` - ** {song_name} - `{Utils.format_time(song.duration)}`\n" embed = self.embeds.QUEUE(title, text) + # Release the acquired Lock + processLock.release() + return HandlerResponse(self.ctx, embed) + else: + processManager.resetProcess(self.guild, self.ctx) + embed = self.embeds.PLAYER_RESTARTED() return HandlerResponse(self.ctx, embed) diff --git a/Handlers/ShuffleHandler.py b/Handlers/ShuffleHandler.py index 99bc730..94999c8 100644 --- a/Handlers/ShuffleHandler.py +++ b/Handlers/ShuffleHandler.py @@ -15,9 +15,17 @@ async def run(self) -> HandlerResponse: processInfo = processManager.getRunningPlayerInfo(self.guild) if processInfo: try: - with processInfo.getLock(): + processLock = processInfo.getLock() + acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) + if acquired: playlist = processInfo.getPlaylist() playlist.shuffle() + # Release the acquired Lock + processLock.release() + else: + processManager.resetProcess(self.guild, self.ctx) + embed = self.embeds.PLAYER_RESTARTED() + return HandlerResponse(self.ctx, embed) embed = self.embeds.SONGS_SHUFFLED() return HandlerResponse(self.ctx, embed) diff --git a/Parallelism/PlayerProcess.py b/Parallelism/PlayerProcess.py index a62a8f9..5205a1f 100644 --- a/Parallelism/PlayerProcess.py +++ b/Parallelism/PlayerProcess.py @@ -1,6 +1,6 @@ import asyncio from os import listdir -from discord import Intents, User, Member +from discord import Intents, User, Member, Message, Embed from asyncio import AbstractEventLoop, Semaphore from multiprocessing import Process, Queue, RLock from threading import Lock, Thread @@ -40,7 +40,6 @@ def __init__(self, name: str, playlist: Playlist, lock: Lock, queue: Queue, guil # Synchronization objects self.__playlist: Playlist = playlist self.__playlistLock: Lock = lock - self.__playerLock: RLock = None self.__queue: Queue = queue self.__semStopPlaying: Semaphore = None self.__loop: AbstractEventLoop = None @@ -60,6 +59,7 @@ def __init__(self, name: str, playlist: Playlist, lock: Lock, queue: Queue, guil self.__configs: Configs = None self.__embeds: Embeds = None self.__messages: Messages = None + self.__messagesToDelete: List[Message] = [] self.__playing = False self.__forceStop = False self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', @@ -117,6 +117,7 @@ async def __playPlaylistSongs(self) -> None: async def __playSong(self, song: Song) -> None: """Function that will trigger the player to play the song""" try: + self.__playerLock.acquire() if song is None: return @@ -125,13 +126,14 @@ async def __playSong(self, song: Song) -> None: # If not connected, connect to bind channel if self.__guild.voice_client is None: - self.__connectToVoiceChannel() + await self.__connectToVoiceChannel() # If the player is already playing return if self.__guild.voice_client.is_playing(): return self.__playing = True + self.__playingSong = song player = FFmpegPCMAudio(song.source, **self.FFMPEG_OPTIONS) self.__guild.voice_client.play(player, after=lambda e: self.__playNext(e)) @@ -139,10 +141,13 @@ async def __playSong(self, song: Song) -> None: self.__timer.cancel() self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) + await self.__deletePrevNowPlaying() await self.__showNowPlaying() except Exception as e: print(f'[ERROR IN PLAY SONG] -> {e}, {type(e)}') self.__playNext(None) + finally: + self.__playerLock.release() def __playNext(self, error) -> None: with self.__playerLock: @@ -158,7 +163,7 @@ def __playNext(self, error) -> None: else: with self.__playlistLock: self.__playlist.loop_off() - + self.__playingSong = None self.__playing = False async def __playPrev(self, voiceChannelID: int) -> None: @@ -169,7 +174,7 @@ async def __playPrev(self, voiceChannelID: int) -> None: if self.__guild.voice_client is None: # If not connect, connect to the user voice channel self.__voiceChannelID = voiceChannelID self.__voiceChannel = self.__guild.get_channel(self.__voiceChannelID) - self.__connectToVoiceChannel() + await self.__connectToVoiceChannel() # If already playing, stop the current play if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused(): @@ -232,27 +237,37 @@ async def __stop(self) -> None: with self.__playlistLock: self.__playlist.clear() self.__playlist.loop_off() - self.__guild.voice_client.stop() - await self.__guild.voice_client.disconnect() + + self.__guild.voice_client.stop() + self.__playingSong = None + await self.__guild.voice_client.disconnect() + self.__semStopPlaying.release() def __resume(self) -> None: - if self.__guild.voice_client is not None: - if self.__guild.voice_client.is_paused(): - self.__guild.voice_client.resume() + # Lock to work with Player + with self.__playerLock: + if self.__guild.voice_client is not None: + if self.__guild.voice_client.is_paused(): + self.__guild.voice_client.resume() def __skip(self) -> None: - if self.__guild.voice_client is not None: - self.__guild.voice_client.stop() + # Lock to work with Player + with self.__playerLock: + if self.__guild.voice_client is not None and self.__playing: + self.__playing = None + self.__guild.voice_client.stop() async def __forceStop(self) -> None: - if self.__guild.voice_client is None: - return + # Lock to work with Player + with self.__playerLock: + if self.__guild.voice_client is None: + return - self.__guild.voice_client.stop() - await self.__guild.voice_client.disconnect() - with self.__playlistLock: - self.__playlist.clear() - self.__playlist.loop_off() + self.__guild.voice_client.stop() + await self.__guild.voice_client.disconnect() + with self.__playlistLock: + self.__playlist.clear() + self.__playlist.loop_off() async def __createBotInstance(self) -> Client: """Load a new bot instance that should not be directly called. @@ -282,7 +297,6 @@ async def __createBotInstance(self) -> Client: async def __timeoutHandler(self) -> None: try: - print('TimeoutHandler') if self.__guild.voice_client is None: return @@ -290,27 +304,17 @@ async def __timeoutHandler(self) -> None: self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) elif self.__guild.voice_client.is_connected(): - self.__playerLock.acquire() - with self.__playlistLock: - self.__playlist.clear() - self.__playlist.loop_off() - self.__playing = False - await self.__guild.voice_client.disconnect() - # Release semaphore to finish process - self.__playerLock.release() - self.__semStopPlaying.release() + with self.__playerLock: + with self.__playlistLock: + self.__playlist.clear() + self.__playlist.loop_off() + self.__playing = False + await self.__guild.voice_client.disconnect() + # Release semaphore to finish process + self.__semStopPlaying.release() except Exception as e: print(f'[Error in Timeout] -> {e}') - def __is_connected(self) -> bool: - try: - if not self.__voiceChannel.is_connected(): - return False - else: - return True - except: - return False - async def __ensureDiscordConnection(self, bot: Client) -> None: """Await in this point until connection to discord is established""" guild = None @@ -333,9 +337,9 @@ def __getBotMember(self) -> Member: return member async def __showNowPlaying(self) -> None: - # Get the current process of the guild + # Get the lock of the playlist with self.__playlistLock: - if not self.__playing or self.__playlist.getCurrentSong() is None: + if not self.__playing or self.__playingSong is None: embed = self.__embeds.NOT_PLAYING() await self.__textChannel.send(embed=embed) return @@ -345,6 +349,32 @@ async def __showNowPlaying(self) -> None: else: title = self.__messages.SONG_PLAYING - info = self.__playlist.getCurrentSong().info + info = self.__playingSong.info embed = self.__embeds.SONG_INFO(info, title) await self.__textChannel.send(embed=embed) + self.__messagesToDelete.append(await self.__getSendedMessage()) + + async def __deletePrevNowPlaying(self) -> None: + for message in self.__messagesToDelete: + try: + await message.delete() + except: + pass + self.__messagesToDelete.clear() + + async def __getSendedMessage(self) -> Message: + stringToIdentify = 'Uploader:' + last_messages: List[Message] = await self.__textChannel.history(limit=5).flatten() + + for message in last_messages: + try: + if message.author == self.__bot.user: + if len(message.embeds) > 0: + embed: Embed = message.embeds[0] + if len(embed.fields) > 0: + if embed.fields[0].name == stringToIdentify: + return message + + except Exception as e: + print(f'DEVELOPER NOTE -> Error cleaning messages {e}') + continue diff --git a/Parallelism/ProcessManager.py b/Parallelism/ProcessManager.py index 53829f1..6df1b73 100644 --- a/Parallelism/ProcessManager.py +++ b/Parallelism/ProcessManager.py @@ -7,6 +7,7 @@ from Parallelism.PlayerProcess import PlayerProcess from Music.Playlist import Playlist from Parallelism.ProcessInfo import ProcessInfo +from Parallelism.Commands import VCommands, VCommandsType class ProcessManager(Singleton): @@ -39,6 +40,19 @@ def getPlayerInfo(self, guild: Guild, context: Context) -> ProcessInfo: except Exception as e: print(f'[Error In GetPlayerContext] -> {e}') + def resetProcess(self, guild: Guild, context: Context) -> None: + """Restart a running process, already start it to return to play""" + if guild.id not in self.__playersProcess.keys(): + return None + + # Recreate the process keeping the playlist + newProcessInfo = self.__recreateProcess(context) + newProcessInfo.getProcess().start() # Start the process + # Send a command to start the play again + playCommand = VCommands(VCommandsType.PLAY) + newProcessInfo.getQueue().put(playCommand) + self.__playersProcess[guild.id] = newProcessInfo + def getRunningPlayerInfo(self, guild: Guild) -> ProcessInfo: """Return the process info for the guild, if not, return None""" if guild.id not in self.__playersProcess.keys(): diff --git a/Views/Embeds.py b/Views/Embeds.py index 447866a..e9c9329 100644 --- a/Views/Embeds.py +++ b/Views/Embeds.py @@ -231,6 +231,13 @@ def SONG_PROBLEMATIC(self) -> Embed: colour=self.__colors.BLACK) return embed + def PLAYER_RESTARTED(self) -> Embed: + embed = Embed( + title=self.__messages.ERROR_TITLE, + description=self.__messages.ERROR_IN_PROCESS, + colour=self.__colors.BLACK) + return embed + def NO_CHANNEL(self) -> Embed: embed = Embed( title=self.__messages.IMPOSSIBLE_MOVE,