diff --git a/Config/Configs.py b/Config/Configs.py index b7120e4..06efea9 100644 --- a/Config/Configs.py +++ b/Config/Configs.py @@ -18,7 +18,7 @@ def __init__(self) -> None: self.CLEANER_MESSAGES_QUANT = 5 self.ACQUIRE_LOCK_TIMEOUT = 10 self.COMMANDS_PATH = 'DiscordCogs' - self.VC_TIMEOUT = 600 + self.VC_TIMEOUT = 300 self.MAX_PLAYLIST_LENGTH = 50 self.MAX_PLAYLIST_FORCED_LENGTH = 5 diff --git a/Handlers/ClearHandler.py b/Handlers/ClearHandler.py index a151d83..935725f 100644 --- a/Handlers/ClearHandler.py +++ b/Handlers/ClearHandler.py @@ -22,7 +22,6 @@ async def run(self) -> HandlerResponse: if acquired: playlist.clear() processLock.release() - processLock.release() return HandlerResponse(self.ctx) else: processManager.resetProcess(self.guild, self.ctx) diff --git a/Music/MessagesController.py b/Music/MessagesController.py index 5082721..0e8567a 100644 --- a/Music/MessagesController.py +++ b/Music/MessagesController.py @@ -1,5 +1,5 @@ from typing import List -from discord import Embed, Message +from discord import Embed, Message, TextChannel from Music.VulkanBot import VulkanBot from Parallelism.ProcessInfo import ProcessInfo from Config.Configs import VConfigs @@ -19,23 +19,25 @@ def __init__(self, bot: VulkanBot) -> None: async def sendNowPlaying(self, processInfo: ProcessInfo, song: Song) -> None: # Get the lock of the playlist - print('Entrei') - playlistLock = processInfo.getLock() playlist = processInfo.getPlaylist() - with playlistLock: - print('A') - if playlist.isLoopingOne(): - title = self.__messages.ONE_SONG_LOOPING - else: - title = self.__messages.SONG_PLAYING + if playlist.isLoopingOne(): + title = self.__messages.ONE_SONG_LOOPING + else: + title = self.__messages.SONG_PLAYING - embed = self.__embeds.SONG_INFO(song.info, title) - view = PlayerView(self.__bot) - channel = processInfo.getTextChannel() + # Create View and Embed + embed = self.__embeds.SONG_INFO(song.info, title) + view = PlayerView(self.__bot) + channel = processInfo.getTextChannel() + # Delete the previous and send the message + await self.__deletePreviousNPMessages() + await channel.send(embed=embed, view=view) - await self.__deletePreviousNPMessages() - await channel.send(embed=embed, view=view) - self.__previousMessages.append(await self.__getSendedMessage()) + # Get the sended message + sendedMessage = await self.__getSendedMessage(channel) + # Set the message witch contains the view + view.set_message(message=sendedMessage) + self.__previousMessages.append(sendedMessage) async def __deletePreviousNPMessages(self) -> None: for message in self.__previousMessages: @@ -45,9 +47,9 @@ async def __deletePreviousNPMessages(self) -> None: pass self.__previousMessages.clear() - async def __getSendedMessage(self) -> Message: + async def __getSendedMessage(self, channel: TextChannel) -> Message: stringToIdentify = 'Uploader:' - last_messages: List[Message] = await self.__textChannel.history(limit=5).flatten() + last_messages: List[Message] = await channel.history(limit=5).flatten() for message in last_messages: try: diff --git a/Parallelism/PlayerProcess.py b/Parallelism/PlayerProcess.py index d356615..d47aff0 100644 --- a/Parallelism/PlayerProcess.py +++ b/Parallelism/PlayerProcess.py @@ -1,8 +1,8 @@ import asyncio from Music.VulkanInitializer import VulkanInitializer -from discord import User, Member, Message, Embed +from discord import User, Member, Message from asyncio import AbstractEventLoop, Semaphore, Queue -from multiprocessing import Process, RLock, Lock +from multiprocessing import Process, RLock, Lock, Queue from threading import Thread from typing import Callable, List from discord import Guild, FFmpegPCMAudio, VoiceChannel, TextChannel @@ -97,7 +97,8 @@ async def _run(self) -> None: # Start the timeout function self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) # Thread that will receive commands to be executed in this Process - self.__loop.create_task(self.__commandsReceiver()) + self.__commandsReceiver = Thread(target=self.__commandsReceiver, daemon=True) + self.__commandsReceiver.start() # Start a Task to play songs self.__loop.create_task(self.__playPlaylistSongs()) @@ -147,9 +148,7 @@ async def __playSong(self, song: Song) -> None: self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) nowPlayingCommand = VCommands(VCommandsType.NOW_PLAYING, song) - await self.__queueSend.put(nowPlayingCommand) - # await self.__deletePrevNowPlaying() - # await self.__showNowPlaying() + self.__queueSend.put(nowPlayingCommand) except Exception as e: print(f'[ERROR IN PLAY SONG] -> {e}, {type(e)}') self.__playNext(None) @@ -171,6 +170,11 @@ def __playNext(self, error) -> None: self.__playlist.loop_off() self.__playingSong = None self.__playing = False + # Send a command to the main process put this one to sleep + sleepCommand = VCommands(VCommandsType.SLEEPING) + self.__queueSend.put(sleepCommand) + # Release the semaphore to finish the process + self.__semStopPlaying.release() async def __playPrev(self, voiceChannelID: int) -> None: with self.__playlistLock: @@ -192,9 +196,9 @@ async def __playPrev(self, voiceChannelID: int) -> None: self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}') - async def __commandsReceiver(self) -> None: + def __commandsReceiver(self) -> None: while True: - command: VCommands = await self.__queueReceive.get() + command: VCommands = self.__queueReceive.get() type = command.getType() args = command.getArgs() @@ -207,13 +211,13 @@ async def __commandsReceiver(self) -> None: elif type == VCommandsType.SKIP: self.__skip() elif type == VCommandsType.PLAY: - await self.__playPlaylistSongs() + asyncio.run_coroutine_threadsafe(self.__playPlaylistSongs(), self.__loop) elif type == VCommandsType.PREV: - await self.__playPrev(args) + asyncio.run_coroutine_threadsafe(self.__playPrev(args), self.__loop) elif type == VCommandsType.RESET: - await self.__reset() + asyncio.run_coroutine_threadsafe(self.__reset(), self.__loop) elif type == VCommandsType.STOP: - await self.__stop() + asyncio.run_coroutine_threadsafe(self.__stop(), self.__loop) else: print(f'[ERROR] -> Unknown Command Received: {command}') except Exception as e: @@ -242,9 +246,11 @@ async def __stop(self) -> None: if self.__guild.voice_client is not None: if self.__guild.voice_client.is_connected(): with self.__playlistLock: - self.__playlist.clear() self.__playlist.loop_off() + # Send a command to the main process put this to sleep + sleepCommand = VCommands(VCommandsType.SLEEPING) + self.__queueSend.put(sleepCommand) self.__guild.voice_client.stop() self.__playingSong = None await self.__guild.voice_client.disconnect() @@ -291,20 +297,35 @@ async def __timeoutHandler(self) -> None: return if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused(): - self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) + if not self.__isBotAloneInChannel(): # If bot is not alone continue to play + self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) + return - elif self.__guild.voice_client.is_connected(): + # Finish the process + if self.__guild.voice_client.is_connected(): with self.__playerLock: with self.__playlistLock: - self.__playlist.clear() self.__playlist.loop_off() self.__playing = False await self.__guild.voice_client.disconnect() + # Send command to main process to finish this one + sleepCommand = VCommands(VCommandsType.SLEEPING) + self.__queueSend.put(sleepCommand) # Release semaphore to finish process self.__semStopPlaying.release() except Exception as e: print(f'[Error in Timeout] -> {e}') + def __isBotAloneInChannel(self) -> bool: + try: + if len(self.__guild.voice_client.channel.members) <= 1: + return True + else: + return False + except Exception as e: + print(f'[ERROR IN CHECK BOT ALONE] -> {e}') + return False + async def __ensureDiscordConnection(self, bot: VulkanBot) -> None: """Await in this point until connection to discord is established""" guild = None @@ -325,46 +346,3 @@ def __getBotMember(self) -> Member: for member in guild_members: if member.id == self.__bot.user.id: return member - - async def __showNowPlaying(self) -> None: - # Get the lock of the playlist - with self.__playlistLock: - if not self.__playing or self.__playingSong is None: - embed = self.__embeds.NOT_PLAYING() - await self.__textChannel.send(embed=embed) - return - - if self.__playlist.isLoopingOne(): - title = self.__messages.ONE_SONG_LOOPING - else: - title = self.__messages.SONG_PLAYING - - 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 09cb929..9a1fdde 100644 --- a/Parallelism/ProcessManager.py +++ b/Parallelism/ProcessManager.py @@ -1,6 +1,5 @@ -from asyncio import Queue, Task import asyncio -from multiprocessing import Lock +from multiprocessing import Lock, Queue from multiprocessing.managers import BaseManager, NamespaceProxy from queue import Empty from threading import Thread @@ -15,7 +14,6 @@ from Parallelism.ProcessInfo import ProcessInfo from Parallelism.Commands import VCommands, VCommandsType from Music.VulkanBot import VulkanBot -from Tests.LoopRunner import LoopRunner class ProcessManager(Singleton): @@ -31,8 +29,7 @@ def __init__(self, bot: VulkanBot = None) -> None: self.__manager = VManager() self.__manager.start() self.__playersProcess: Dict[Guild, ProcessInfo] = {} - # self.__playersListeners: Dict[Guild, Tuple[Thread, bool]] = {} - self.__playersListeners: Dict[Guild, Task] = {} + self.__playersListeners: Dict[Guild, Tuple[Thread, bool]] = {} self.__playersMessages: Dict[Guild, MessagesController] = {} def setPlayerInfo(self, guild: Guild, info: ProcessInfo): @@ -94,11 +91,11 @@ def __createProcessInfo(self, guild: Guild, context: Context) -> ProcessInfo: processInfo = ProcessInfo(process, queueToSend, queueToListen, playlist, lock, context.channel) - task = asyncio.create_task(self.__listenToCommands(queueToListen, guild)) - # Create a Thread to listen for the queue coming from the Player Process - # thread = Thread(target=self.__listenToCommands, args=(queueToListen, guild), daemon=True) - self.__playersListeners[guildID] = task - # thread.start() + # Create a Thread to listen for the queue coming from the Player Process, this will redirect the Queue to a async + thread = Thread(target=self.__listenToCommands, + args=(queueToListen, guild), daemon=True) + self.__playersListeners[guildID] = (thread, False) + thread.start() # Create a Message Controller for this player self.__playersMessages[guildID] = MessagesController(self.__bot) @@ -118,48 +115,46 @@ def __recreateProcess(self, guild: Guild, context: Context) -> ProcessInfo: queueToSend = Queue() process = PlayerProcess(context.guild.name, playlist, lock, queueToSend, queueToListen, guildID, textID, voiceID, authorID) - processInfo = ProcessInfo(process, queueToSend, queueToListen, playlist, lock) - - task = asyncio.create_task(self.__listenToCommands(queueToListen, guild)) - # Create a Thread to listen for the queue coming from the Player Process - # thread = Thread(target=self.__listenToCommands, args=(queueToListen, guild), daemon=True) - self.__playersListeners[guildID] = task - # thread.start() + processInfo = ProcessInfo(process, queueToSend, queueToListen, + playlist, lock, context.channel) - # Create a Message Controller for this player - self.__playersMessages[guildID] = MessagesController(self.__bot) + # Create a Thread to listen for the queue coming from the Player Process, this will redirect the Queue to a async + thread = Thread(target=self.__listenToCommands, + args=(queueToListen, guild), daemon=True) + self.__playersListeners[guildID] = (thread, False) + thread.start() return processInfo - async def __listenToCommands(self, queue: Queue, guild: Guild) -> None: - shouldEnd = False + def __listenToCommands(self, queue: Queue, guild: Guild) -> None: guildID = guild.id - while not shouldEnd: + while True: shouldEnd = self.__playersListeners[guildID][1] + if shouldEnd: + break + try: - print('Esperando') - command: VCommands = await queue.get() + command: VCommands = queue.get(timeout=5) commandType = command.getType() args = command.getArgs() print(f'Process {guild.name} sended command {commandType}') if commandType == VCommandsType.NOW_PLAYING: - print('Aqui dentro') - await self.__showNowPlaying(args, guildID) + asyncio.run_coroutine_threadsafe(self.showNowPlaying( + guild.id, args), self.__bot.loop) elif commandType == VCommandsType.TERMINATE: # Delete the process elements and return, to finish task - self.__terminateProcess() + self.__terminateProcess(guildID) return elif commandType == VCommandsType.SLEEPING: # The process might be used again - self.__sleepingProcess() + self.__sleepingProcess(guildID) return else: print(f'[ERROR] -> Unknown Command Received from Process: {commandType}') except Empty: continue except Exception as e: - print(e) print(f'[ERROR IN LISTENING PROCESS] -> {guild.name} - {e}') def __terminateProcess(self, guildID: int) -> None: @@ -179,12 +174,10 @@ def __sleepingProcess(self, guildID: int) -> None: queue2.close() queue2.join_thread() - async def __showNowPlaying(self, guildID: int, song: Song) -> None: + async def showNowPlaying(self, guildID: int, song: Song) -> None: messagesController = self.__playersMessages[guildID] processInfo = self.__playersProcess[guildID] - print('Aq1') await messagesController.sendNowPlaying(processInfo, song) - print('Aq2') class VManager(BaseManager): diff --git a/UI/Views/PlayerView.py b/UI/Views/PlayerView.py index aca753c..cb6050e 100644 --- a/UI/Views/PlayerView.py +++ b/UI/Views/PlayerView.py @@ -1,3 +1,4 @@ +from discord import Message from discord.ui import View from Config.Emojis import VEmojis from UI.Buttons.PauseButton import PauseButton @@ -15,9 +16,10 @@ class PlayerView(View): - def __init__(self, bot: VulkanBot, timeout: float = 180): + def __init__(self, bot: VulkanBot, timeout: float = 6000): super().__init__(timeout=timeout) self.__bot = bot + self.__message: Message = None self.add_item(BackButton(self.__bot)) self.add_item(PauseButton(self.__bot)) self.add_item(PlayButton(self.__bot)) @@ -27,3 +29,12 @@ def __init__(self, bot: VulkanBot, timeout: float = 180): self.add_item(LoopOneButton(self.__bot)) self.add_item(LoopOffButton(self.__bot)) self.add_item(LoopAllButton(self.__bot)) + + async def on_timeout(self) -> None: + # Disable all itens and, if has the message, edit it + self.disable_all_items() + if self.__message is not None and isinstance(self.__message, Message): + await self.__message.edit(view=self) + + def set_message(self, message: Message) -> None: + self.__message = message