Continuing the refactoring

This commit is contained in:
Rafael Vargas 2023-02-19 11:28:33 -03:00
parent 7a5d76ffd3
commit 72043c4475
26 changed files with 369 additions and 502 deletions

View File

@ -23,7 +23,7 @@ from Messages.Responses.EmbedCogResponse import EmbedCommandResponse
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from Config.Configs import VConfigs from Config.Configs import VConfigs
from Config.Embeds import VEmbeds from Config.Embeds import VEmbeds
from Parallelism.ProcessManager import ProcessManager from Parallelism.ProcessManager import ProcessPlayerManager
helper = Helper() helper = Helper()
@ -38,7 +38,7 @@ class MusicCog(Cog):
def __init__(self, bot: VulkanBot) -> None: def __init__(self, bot: VulkanBot) -> None:
self.__bot: VulkanBot = bot self.__bot: VulkanBot = bot
self.__embeds = VEmbeds() self.__embeds = VEmbeds()
VConfigs().setPlayersManager(ProcessManager(bot)) VConfigs().setPlayersManager(ProcessPlayerManager(bot))
@command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar']) @command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar'])
async def play(self, ctx: Context, *args) -> None: async def play(self, ctx: Context, *args) -> None:

View File

@ -1,6 +1,4 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from Parallelism.Commands import VCommands
from multiprocessing import Queue
from typing import List, Union from typing import List, Union
from discord.ext.commands import Context from discord.ext.commands import Context
from discord import Client, Guild, ClientUser, Interaction, Member, User from discord import Client, Guild, ClientUser, Interaction, Member, User
@ -29,12 +27,6 @@ class AbstractHandler(ABC):
else: else:
self.__author = ctx.user self.__author = ctx.user
def putCommandInQueue(self, queue: Queue, command: VCommands) -> None:
try:
queue.put(command)
except Exception as e:
print(f'[ERROR PUTTING COMMAND IN QUEUE] -> {e}')
@abstractmethod @abstractmethod
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
pass pass

View File

@ -5,7 +5,6 @@ from Music.VulkanBot import VulkanBot
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessInfo import PlayerInfo
class ClearHandler(AbstractHandler): class ClearHandler(AbstractHandler):
@ -14,19 +13,18 @@ class ClearHandler(AbstractHandler):
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
# Get the current process of the guild # Get the current process of the guild
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) if playersManager.verifyIfPlayerExists(self.guild):
if processInfo:
# Clear the playlist # Clear the playlist
playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
processLock = processInfo.getLock() playerLock = playersManager.getPlayerLock(self.guild)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
playlist.clear() playlist.clear()
processLock.release() playerLock.release()
embed = self.embeds.PLAYLIST_CLEAR() embed = self.embeds.PLAYLIST_CLEAR()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
else: else:
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED() embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -14,18 +14,16 @@ class HistoryHandler(AbstractHandler):
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
# Get the current process of the guild # Get the current process of the guild
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) if playersManager.verifyIfPlayerExists(self.guild):
if processInfo: playerLock = playersManager.getPlayerLock(self.guild)
processLock = processInfo.getLock() acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
playlist = processInfo.getPlaylist() history = playersManager.getPlayerPlaylist(self.guild).getSongsHistory()
history = playlist.getSongsHistory() playerLock.release()
processLock.release()
else: else:
# If the player doesn't respond in time we restart it # If the player doesn't respond in time we restart it
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED() embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
else: else:

View File

@ -17,32 +17,31 @@ class JumpMusicHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self, musicPos: str) -> HandlerResponse: async def run(self, musicPos: str) -> HandlerResponse:
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) if not playersManager.verifyIfPlayerExists(self.guild):
if not processInfo:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
processLock = processInfo.getLock() playerLock = playersManager.getPlayerLock(self.guild)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
# Try to convert input to int # Try to convert input to int
error = self.__validateInput(musicPos) error = self.__validateInput(musicPos)
if error: if error:
embed = self.embeds.ERROR_EMBED(error.message) embed = self.embeds.ERROR_EMBED(error.message)
processLock.release() playerLock.release()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
# Sanitize the input # Sanitize the input
playlist: Playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
musicPos = self.__sanitizeInput(playlist, musicPos) musicPos = self.__sanitizeInput(playlist, musicPos)
# Validate the position # Validate the position
if not playlist.validate_position(musicPos): if not playlist.validate_position(musicPos):
error = InvalidInput() error = InvalidInput()
embed = self.embeds.PLAYLIST_RANGE_ERROR() embed = self.embeds.PLAYLIST_RANGE_ERROR()
processLock.release() playerLock.release()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
try: try:
# Move the selected song # Move the selected song
@ -50,19 +49,17 @@ class JumpMusicHandler(AbstractHandler):
# Send a command to the player to skip the music # Send a command to the player to skip the music
command = VCommands(VCommandsType.SKIP, None) command = VCommands(VCommandsType.SKIP, None)
queue = processInfo.getQueueToPlayer() playersManager.sendCommandToPlayer(command, self.guild)
self.putCommandInQueue(queue, command)
processLock.release()
return HandlerResponse(self.ctx) return HandlerResponse(self.ctx)
except: except:
# Release the acquired Lock
processLock.release()
embed = self.embeds.ERROR_MOVING() embed = self.embeds.ERROR_MOVING()
error = UnknownError() error = UnknownError()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
finally:
playerLock.release()
else: else:
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED() embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -5,7 +5,6 @@ from Handlers.HandlerResponse import HandlerResponse
from Config.Exceptions import BadCommandUsage from Config.Exceptions import BadCommandUsage
from typing import Union from typing import Union
from discord import Interaction from discord import Interaction
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
@ -14,23 +13,20 @@ class LoopHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self, args: str) -> HandlerResponse: async def run(self, args: str) -> HandlerResponse:
# Get the current process of the guild playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processManager: AbstractPlayersManager = self.config.getPlayersManager() if not playersManager.verifyIfPlayerExists(self.guild):
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
playerLock = playersManager.getPlayerLock(self.guild)
processLock = processInfo.getLock() acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
if args == '' or args is None: if args == '' or args is None:
playlist.loop_all() playlist.loop_all()
embed = self.embeds.LOOP_ALL_ACTIVATED() embed = self.embeds.LOOP_ALL_ACTIVATED()
processLock.release() playerLock.release()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
args = args.lower() args = args.lower()
@ -53,9 +49,9 @@ class LoopHandler(AbstractHandler):
error = BadCommandUsage() error = BadCommandUsage()
embed = self.embeds.BAD_LOOP_USE() embed = self.embeds.BAD_LOOP_USE()
processLock.release() playerLock.release()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
else: else:
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED() embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -15,45 +15,44 @@ class MoveHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self, pos1: str, pos2: str) -> HandlerResponse: async def run(self, pos1: str, pos2: str) -> HandlerResponse:
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) if not playersManager.verifyIfPlayerExists(self.guild):
if not processInfo:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
processLock = processInfo.getLock() playerLock = playersManager.getPlayerLock(self.guild)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
error = self.__validateInput(pos1, pos2) error = self.__validateInput(pos1, pos2)
if error: if error:
embed = self.embeds.ERROR_EMBED(error.message) embed = self.embeds.ERROR_EMBED(error.message)
processLock.release() playerLock.release()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
pos1, pos2 = self.__sanitizeInput(playlist, pos1, pos2) pos1, pos2 = self.__sanitizeInput(playlist, pos1, pos2)
if not playlist.validate_position(pos1) or not playlist.validate_position(pos2): if not playlist.validate_position(pos1) or not playlist.validate_position(pos2):
error = InvalidInput() error = InvalidInput()
embed = self.embeds.PLAYLIST_RANGE_ERROR() embed = self.embeds.PLAYLIST_RANGE_ERROR()
processLock.release() playerLock.release()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
try: try:
song = playlist.move_songs(pos1, pos2) song = playlist.move_songs(pos1, pos2)
song_name = song.title if song.title else song.identifier song_name = song.title if song.title else song.identifier
embed = self.embeds.SONG_MOVED(song_name, pos1, pos2) embed = self.embeds.SONG_MOVED(song_name, pos1, pos2)
processLock.release() playerLock.release()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
except: except:
# Release the acquired Lock # Release the acquired Lock
processLock.release() playerLock.release()
embed = self.embeds.ERROR_MOVING() embed = self.embeds.ERROR_MOVING()
error = UnknownError() error = UnknownError()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
else: else:
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED() embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -14,14 +14,12 @@ class NowPlayingHandler(AbstractHandler):
self.__cleaner = Cleaner() self.__cleaner = Cleaner()
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
# Get the current process of the guild playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processManager: AbstractPlayersManager = self.config.getPlayersManager() if not playersManager.verifyIfPlayerExists(self.guild):
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
if playlist.getCurrentSong() is None: if playlist.getCurrentSong() is None:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -3,7 +3,6 @@ from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.Commands import VCommands, VCommandsType from Parallelism.Commands import VCommands, VCommandsType
from Parallelism.ProcessInfo import PlayerInfo, ProcessStatus
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from typing import Union from typing import Union
from discord import Interaction from discord import Interaction
@ -14,17 +13,10 @@ class PauseHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) if playersManager.verifyIfPlayerExists(self.guild):
if processInfo:
if processInfo.getStatus() == ProcessStatus.SLEEPING:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)
# Send Pause command to be execute by player process
command = VCommands(VCommandsType.PAUSE, None) command = VCommands(VCommandsType.PAUSE, None)
queue = processInfo.getQueueToPlayer() playersManager.sendCommandToPlayer(command, self.guild)
self.putCommandInQueue(queue, command)
embed = self.embeds.PLAYER_PAUSED() embed = self.embeds.PLAYER_PAUSED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -1,6 +1,6 @@
import asyncio import asyncio
import traceback import traceback
from typing import List from typing import List, Union
from Config.Exceptions import DownloadingError, InvalidInput, VulkanError from Config.Exceptions import DownloadingError, InvalidInput, VulkanError
from discord.ext.commands import Context from discord.ext.commands import Context
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
@ -10,12 +10,9 @@ from Music.Downloader import Downloader
from Music.Searcher import Searcher from Music.Searcher import Searcher
from Music.Song import Song from Music.Song import Song
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessInfo import PlayerInfo
from Parallelism.Commands import VCommands, VCommandsType from Parallelism.Commands import VCommands, VCommandsType
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from typing import Union
from discord import Interaction from discord import Interaction
from Music.Playlist import Playlist
class PlayHandler(AbstractHandler): class PlayHandler(AbstractHandler):
@ -37,13 +34,12 @@ class PlayHandler(AbstractHandler):
if musicsInfo is None or len(musicsInfo) == 0: if musicsInfo is None or len(musicsInfo) == 0:
raise InvalidInput(self.messages.INVALID_INPUT, self.messages.ERROR_TITLE) raise InvalidInput(self.messages.INVALID_INPUT, self.messages.ERROR_TITLE)
# Get the process context for the current guild # If there is no executing player for the guild then we create the player
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getOrCreatePlayerInfo(self.guild, self.ctx) if not playersManager.verifyIfPlayerExists(self.guild):
playlist: Playlist = processInfo.getPlaylist() playersManager.createPlayerForGuild(self.guild, self.ctx)
process = processInfo.getProcess()
if not process.is_alive(): # If process has not yet started, start playlist = playersManager.getPlayerPlaylist(self.guild)
process.start()
# Create the Songs objects # Create the Songs objects
songs: List[Song] = [] songs: List[Song] = []
@ -67,19 +63,17 @@ class PlayHandler(AbstractHandler):
embed = self.embeds.SONG_ADDED_TWO(song.info, pos) embed = self.embeds.SONG_ADDED_TWO(song.info, pos)
response = HandlerResponse(self.ctx, embed) response = HandlerResponse(self.ctx, embed)
# Add the unique song to the playlist and send a command to player process # Add the unique song to the playlist and send a command to player
processLock = processInfo.getLock() playerLock = playersManager.getPlayerLock(self.guild)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
playlist.add_song(song) playlist.add_song(song)
# Release the acquired Lock # Release the acquired Lock
processLock.release() playerLock.release()
queue = processInfo.getQueueToPlayer()
playCommand = VCommands(VCommandsType.PLAY, None) playCommand = VCommands(VCommandsType.PLAY, None)
self.putCommandInQueue(queue, playCommand) playersManager.sendCommandToPlayer(playCommand, self.guild)
else: else:
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED() embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
@ -89,10 +83,10 @@ class PlayHandler(AbstractHandler):
if len(songs) > 10: if len(songs) > 10:
fiveFirstSongs = songs[0:5] fiveFirstSongs = songs[0:5]
songs = songs[5:] songs = songs[5:]
await self.__downloadSongsAndStore(fiveFirstSongs, processInfo) await self.__downloadSongsAndStore(fiveFirstSongs, playersManager)
# Trigger a task to download all songs and then store them in the process playlist # Trigger a task to download all songs and then store them in the playlist
asyncio.create_task(self.__downloadSongsAndStore(songs, processInfo)) asyncio.create_task(self.__downloadSongsAndStore(songs, playersManager))
embed = self.embeds.SONGS_ADDED(len(songs)) embed = self.embeds.SONGS_ADDED(len(songs))
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
@ -102,7 +96,7 @@ class PlayHandler(AbstractHandler):
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
except Exception as error: except Exception as error:
print(f'ERROR IN PLAYHANDLER -> {traceback.format_exc()}', {type(error)}) print(f'ERROR IN PLAYHANDLER -> {traceback.format_exc()}', {type(error)})
if isinstance(error, VulkanError): # If error was already processed if isinstance(error, VulkanError):
embed = self.embeds.CUSTOM_ERROR(error) embed = self.embeds.CUSTOM_ERROR(error)
else: else:
error = UnknownError() error = UnknownError()
@ -110,9 +104,8 @@ class PlayHandler(AbstractHandler):
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
async def __downloadSongsAndStore(self, songs: List[Song], processInfo: PlayerInfo) -> None: async def __downloadSongsAndStore(self, songs: List[Song], playersManager: AbstractPlayersManager) -> None:
playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
queue = processInfo.getQueueToPlayer()
playCommand = VCommands(VCommandsType.PLAY, None) playCommand = VCommands(VCommandsType.PLAY, None)
tooManySongs = len(songs) > 100 tooManySongs = len(songs) > 100
@ -126,21 +119,18 @@ class PlayHandler(AbstractHandler):
task = asyncio.create_task(self.__down.download_song(song)) task = asyncio.create_task(self.__down.download_song(song))
tasks.append(task) tasks.append(task)
# In the original order, await for the task and then, if successfully downloaded, add to the playlist
processManager: AbstractPlayersManager = self.config.getPlayersManager()
for index, task in enumerate(tasks): for index, task in enumerate(tasks):
await task await task
song = songs[index] song = songs[index]
if not song.problematic: # If downloaded add to the playlist and send play command if not song.problematic: # If downloaded add to the playlist and send play command
processInfo = processManager.getOrCreatePlayerInfo(self.guild, self.ctx) playerLock = playersManager.getPlayerLock(self.guild)
processLock = processInfo.getLock() acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
playlist.add_song(song) playlist.add_song(song)
self.putCommandInQueue(queue, playCommand) playersManager.sendCommandToPlayer(playCommand, self.guild)
processLock.release() playerLock.release()
else: else:
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
def __isUserConnected(self) -> bool: def __isUserConnected(self) -> bool:
if self.ctx.author.voice: if self.ctx.author.voice:

View File

@ -19,14 +19,13 @@ class PrevHandler(AbstractHandler):
embed = self.embeds.NO_CHANNEL() embed = self.embeds.NO_CHANNEL()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getOrCreatePlayerInfo(self.guild, self.ctx) if not playersManager.verifyIfPlayerExists(self.guild):
if not processInfo:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
if len(playlist.getHistory()) == 0: if len(playlist.getHistory()) == 0:
error = ImpossibleMove() error = ImpossibleMove()
embed = self.embeds.NOT_PREVIOUS_SONG() embed = self.embeds.NOT_PREVIOUS_SONG()
@ -37,15 +36,9 @@ class PrevHandler(AbstractHandler):
embed = self.embeds.FAIL_DUE_TO_LOOP_ON() embed = self.embeds.FAIL_DUE_TO_LOOP_ON()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
# If not started, start the player process
process = processInfo.getProcess()
if not process.is_alive():
process.start()
# Send a prev command, together with the user voice channel # Send a prev command, together with the user voice channel
prevCommand = VCommands(VCommandsType.PREV, self.author.voice.channel.id) prevCommand = VCommands(VCommandsType.PREV, self.author.voice.channel.id)
queue = processInfo.getQueueToPlayer() playersManager.sendCommandToPlayer(prevCommand, self.guild)
self.putCommandInQueue(queue, prevCommand)
embed = self.embeds.RETURNING_SONG() embed = self.embeds.RETURNING_SONG()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -22,29 +22,27 @@ class QueueHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self, pageNumber=0) -> HandlerResponse: async def run(self, pageNumber=0) -> HandlerResponse:
# Retrieve the process of the guild playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processManager: AbstractPlayersManager = self.config.getPlayersManager() if not playersManager.verifyIfPlayerExists(self.guild):
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo: # If no process return empty list
embed = self.embeds.EMPTY_QUEUE() embed = self.embeds.EMPTY_QUEUE()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
# Acquire the Lock to manipulate the playlist # Acquire the Lock to manipulate the playlist
processLock = processInfo.getLock() playerLock = playersManager.getPlayerLock(self.guild)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
playlist: Playlist = processInfo.getPlaylist() playlist: Playlist = playersManager.getPlayerPlaylist(self.guild)
if playlist.isLoopingOne(): if playlist.isLoopingOne():
song = playlist.getCurrentSong() song = playlist.getCurrentSong()
embed = self.embeds.ONE_SONG_LOOPING(song.info) embed = self.embeds.ONE_SONG_LOOPING(song.info)
processLock.release() # Release the Lock playerLock.release() # Release the Lock
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
allSongs = playlist.getSongs() allSongs = playlist.getSongs()
if len(allSongs) == 0: if len(allSongs) == 0:
embed = self.embeds.EMPTY_QUEUE() embed = self.embeds.EMPTY_QUEUE()
processLock.release() # Release the Lock playerLock.release() # Release the Lock
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
songsPages = playlist.getSongsPages() songsPages = playlist.getSongsPages()
@ -93,10 +91,10 @@ class QueueHandler(AbstractHandler):
embed = self.embeds.QUEUE(title, text) embed = self.embeds.QUEUE(title, text)
# Release the acquired Lock # Release the acquired Lock
processLock.release() playerLock.release()
return HandlerResponse(self.ctx, embed, view=queueView) return HandlerResponse(self.ctx, embed, view=queueView)
else: else:
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED() embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -5,7 +5,6 @@ from Config.Exceptions import BadCommandUsage, VulkanError, ErrorRemoving, Inval
from Music.Playlist import Playlist from Music.Playlist import Playlist
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessInfo import PlayerInfo
from typing import Union from typing import Union
from discord import Interaction from discord import Interaction
@ -15,15 +14,13 @@ class RemoveHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self, position: str) -> HandlerResponse: async def run(self, position: str) -> HandlerResponse:
# Get the current process of the guild playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processManager: AbstractPlayersManager = self.config.getPlayersManager() if not playersManager.verifyIfPlayerExists(self.guild):
processInfo = processManager.getRunningPlayerInfo(self.guild)
if not processInfo:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
if playlist is None: if playlist is None:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()

View File

@ -2,7 +2,6 @@ from discord.ext.commands import Context
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessInfo import PlayerInfo, ProcessStatus
from Parallelism.Commands import VCommands, VCommandsType from Parallelism.Commands import VCommands, VCommandsType
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from typing import Union from typing import Union
@ -14,18 +13,10 @@ class ResetHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
# Get the current process of the guild playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processManager: AbstractPlayersManager = self.config.getPlayersManager() if playersManager.verifyIfPlayerExists(self.guild):
processInfo = processManager.getRunningPlayerInfo(self.guild)
if processInfo:
if processInfo.getStatus() == ProcessStatus.SLEEPING:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)
command = VCommands(VCommandsType.RESET, None) command = VCommands(VCommandsType.RESET, None)
queue = processInfo.getQueueToPlayer() playersManager.sendCommandToPlayer(command, self.guild)
self.putCommandInQueue(queue, command)
return HandlerResponse(self.ctx) return HandlerResponse(self.ctx)
else: else:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()

View File

@ -2,7 +2,6 @@ from discord.ext.commands import Context
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessInfo import PlayerInfo, ProcessStatus
from Parallelism.Commands import VCommands, VCommandsType from Parallelism.Commands import VCommands, VCommandsType
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from typing import Union from typing import Union
@ -14,18 +13,10 @@ class ResumeHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) if playersManager.verifyIfPlayerExists(self.guild):
if processInfo:
if processInfo.getStatus() == ProcessStatus.SLEEPING:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)
# Send Resume command to be execute by player process
command = VCommands(VCommandsType.RESUME, None) command = VCommands(VCommandsType.RESUME, None)
queue = processInfo.getQueueToPlayer() playersManager.sendCommandToPlayer(command, self.guild)
self.putCommandInQueue(queue, command)
embed = self.embeds.PLAYER_RESUMED() embed = self.embeds.PLAYER_RESUMED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
else: else:

View File

@ -14,19 +14,18 @@ class ShuffleHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) if playersManager.verifyIfPlayerExists(self.guild):
if processInfo:
try: try:
processLock = processInfo.getLock() playerLock = playersManager.getPlayerLock(self.guild)
acquired = processLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
if acquired: if acquired:
playlist = processInfo.getPlaylist() playlist = playersManager.getPlayerPlaylist(self.guild)
playlist.shuffle() playlist.shuffle()
# Release the acquired Lock # Release the acquired Lock
processLock.release() playerLock.release()
else: else:
processManager.resetProcess(self.guild, self.ctx) playersManager.resetPlayer(self.guild, self.ctx)
embed = self.embeds.PLAYER_RESTARTED() embed = self.embeds.PLAYER_RESTARTED()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)

View File

@ -1,10 +1,8 @@
from discord.ext.commands import Context from discord.ext.commands import Context
from Handlers.AbstractHandler import AbstractHandler from Handlers.AbstractHandler import AbstractHandler
from Config.Exceptions import BadCommandUsage, ImpossibleMove
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessInfo import PlayerInfo, ProcessStatus
from Parallelism.Commands import VCommands, VCommandsType from Parallelism.Commands import VCommands, VCommandsType
from typing import Union from typing import Union
from discord import Interaction from discord import Interaction
@ -15,31 +13,12 @@ class SkipHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
if not self.__user_connected(): playersManager: AbstractPlayersManager = self.config.getPlayersManager()
error = ImpossibleMove() if playersManager.verifyIfPlayerExists(self.guild):
embed = self.embeds.NO_CHANNEL()
return HandlerResponse(self.ctx, embed, error)
processManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild)
if processInfo: # Verify if there is a running process
if processInfo.getStatus() == ProcessStatus.SLEEPING:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)
# Send a command to the player process to skip the music
command = VCommands(VCommandsType.SKIP, None) command = VCommands(VCommandsType.SKIP, None)
queue = processInfo.getQueueToPlayer() playersManager.sendCommandToPlayer(command, self.guild)
self.putCommandInQueue(queue, command)
embed = self.embeds.SKIPPING_SONG() embed = self.embeds.SKIPPING_SONG()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
else: else:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
def __user_connected(self) -> bool:
if self.author.voice:
return True
else:
return False

View File

@ -3,7 +3,6 @@ from Handlers.AbstractHandler import AbstractHandler
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessInfo import PlayerInfo, ProcessStatus
from Parallelism.Commands import VCommands, VCommandsType from Parallelism.Commands import VCommands, VCommandsType
from typing import Union from typing import Union
from discord import Interaction from discord import Interaction
@ -14,18 +13,10 @@ class StopHandler(AbstractHandler):
super().__init__(ctx, bot) super().__init__(ctx, bot)
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
processManager: AbstractPlayersManager = self.config.getPlayersManager() playersManager: AbstractPlayersManager = self.config.getPlayersManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) if playersManager.verifyIfPlayerExists(self.guild):
if processInfo:
if processInfo.getStatus() == ProcessStatus.SLEEPING:
embed = self.embeds.NOT_PLAYING()
return HandlerResponse(self.ctx, embed)
# Send command to player process stop
command = VCommands(VCommandsType.STOP, None) command = VCommands(VCommandsType.STOP, None)
queue = processInfo.getQueueToPlayer() playersManager.sendCommandToPlayer(command, self.guild)
self.putCommandInQueue(queue, command)
embed = self.embeds.STOPPING_PLAYER() embed = self.embeds.STOPPING_PLAYER()
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
else: else:

View File

@ -1,9 +1,11 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from threading import Lock
from typing import Union from typing import Union
from discord.ext.commands import Context from discord.ext.commands import Context
from discord import Guild, Interaction from discord import Guild, Interaction
from Music.Playlist import Playlist
from Music.Song import Song from Music.Song import Song
from Parallelism.ProcessInfo import PlayerInfo from Parallelism.Commands import VCommands
class AbstractPlayersManager(ABC): class AbstractPlayersManager(ABC):
@ -11,19 +13,33 @@ class AbstractPlayersManager(ABC):
pass pass
@abstractmethod @abstractmethod
def setPlayerInfo(self, guild: Guild, info: PlayerInfo): def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None):
"""If the forceCreation boolean is True, then the context must be provided for the Player to be created"""
pass pass
@abstractmethod @abstractmethod
def getOrCreatePlayerInfo(self, guild: Guild, context: Union[Context, Interaction]) -> PlayerInfo: def getPlayerPlaylist(self, guild: Guild) -> Playlist:
"""If there is a player process for the guild, then return the playlist of the guild"""
pass pass
@abstractmethod @abstractmethod
def resetProcess(self, guild: Guild, context: Context) -> None: def getPlayerLock(self, guild: Guild) -> Lock:
"""If there is a player process for the guild, then return the lock of the guild"""
pass pass
@abstractmethod @abstractmethod
def getRunningPlayerInfo(self, guild: Guild) -> PlayerInfo: def verifyIfPlayerExists(self, guild: Guild) -> bool:
"""Returns if a player for the guild exists"""
pass
@abstractmethod
def createPlayerForGuild(self, guild: Guild, context: Union[Context, Interaction]) -> None:
"""With the context information of a guild create a internal player for the guild"""
pass
@abstractmethod
def resetPlayer(self, guild: Guild, context: Context) -> None:
"""Tries to reset the player of the guild"""
pass pass
@abstractmethod @abstractmethod

View File

@ -32,7 +32,7 @@ class TimeoutClock:
class PlayerProcess(Process): class PlayerProcess(Process):
"""Process that will play songs, receive commands from the main process by a Queue""" """Process that will play songs, receive commands from the main process by a Queue"""
def __init__(self, name: str, playlist: Playlist, lock: Lock, queueToReceive: Queue, queueToSend: Queue, guildID: int, textID: int, voiceID: int, authorID: int) -> None: def __init__(self, name: str, playlist: Playlist, lock: Lock, queueToReceive: Queue, queueToSend: Queue, guildID: int, voiceID: int) -> None:
""" """
Start a new process that will have his own bot instance Start a new process that will have his own bot instance
Due to pickle serialization, no objects are stored, the values initialization are being made in the run method Due to pickle serialization, no objects are stored, the values initialization are being made in the run method
@ -46,10 +46,8 @@ class PlayerProcess(Process):
self.__semStopPlaying: Semaphore = None self.__semStopPlaying: Semaphore = None
self.__loop: AbstractEventLoop = None self.__loop: AbstractEventLoop = None
# Discord context ID # Discord context ID
self.__textChannelID = textID
self.__guildID = guildID self.__guildID = guildID
self.__voiceChannelID = voiceID self.__voiceChannelID = voiceID
self.__authorID = authorID
# All information of discord context will be retrieved directly with discord API # All information of discord context will be retrieved directly with discord API
self.__guild: Guild = None self.__guild: Guild = None
self.__bot: VulkanBot = None self.__bot: VulkanBot = None
@ -81,9 +79,6 @@ class PlayerProcess(Process):
self.__bot = await self.__createBotInstance() self.__bot = await self.__createBotInstance()
self.__guild = self.__bot.get_guild(self.__guildID) self.__guild = self.__bot.get_guild(self.__guildID)
self.__voiceChannel = self.__bot.get_channel(self.__voiceChannelID) self.__voiceChannel = self.__bot.get_channel(self.__voiceChannelID)
self.__textChannel = self.__bot.get_channel(self.__textChannelID)
self.__author = self.__bot.get_channel(self.__authorID)
self.__botMember = self.__getBotMember()
# Connect to voice Channel # Connect to voice Channel
await self.__connectToVoiceChannel() await self.__connectToVoiceChannel()

View File

@ -1,12 +1,11 @@
import asyncio import asyncio
from time import sleep, time from time import time
from urllib.parse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
from Music.VulkanInitializer import VulkanInitializer from discord import VoiceClient
from discord import Member, VoiceClient from asyncio import AbstractEventLoop
from asyncio import AbstractEventLoop, Semaphore
from threading import RLock, Thread from threading import RLock, Thread
from multiprocessing import Lock from multiprocessing import Lock
from typing import Callable, List from typing import Callable
from discord import Guild, FFmpegPCMAudio, VoiceChannel from discord import Guild, FFmpegPCMAudio, VoiceChannel
from Music.Playlist import Playlist from Music.Playlist import Playlist
from Music.Song import Song from Music.Song import Song
@ -32,7 +31,7 @@ class TimeoutClock:
class PlayerThread(Thread): class PlayerThread(Thread):
"""Player Thread to control the song playback in the same Process of the Main Process""" """Player Thread to control the song playback in the same Process of the Main Process"""
def __init__(self, bot: VulkanBot, guild: Guild, name: str, voiceChannel: VoiceChannel, playlist: Playlist, lock: Lock, guildID: int, textID: int, voiceID: int, authorID: int) -> None: def __init__(self, bot: VulkanBot, guild: Guild, name: str, voiceChannel: VoiceChannel, playlist: Playlist, lock: Lock, guildID: int, voiceID: int) -> None:
Thread.__init__(self, name=name, group=None, target=None, args=(), kwargs={}) Thread.__init__(self, name=name, group=None, target=None, args=(), kwargs={})
# Synchronization objects # Synchronization objects
self.__playlist: Playlist = playlist self.__playlist: Playlist = playlist

View File

@ -3,8 +3,8 @@ from discord import Button, TextChannel
from discord.ui import View from discord.ui import View
from Config.Emojis import VEmojis from Config.Emojis import VEmojis
from Messages.MessagesCategory import MessagesCategory from Messages.MessagesCategory import MessagesCategory
from Music.Playlist import Playlist
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
from Parallelism.ProcessInfo import PlayerInfo
from Config.Messages import Messages from Config.Messages import Messages
from Music.Song import Song from Music.Song import Song
from Config.Embeds import VEmbeds from Config.Embeds import VEmbeds
@ -29,9 +29,9 @@ class ProcessCommandsExecutor:
self.__embeds = VEmbeds() self.__embeds = VEmbeds()
self.__emojis = VEmojis() self.__emojis = VEmojis()
async def sendNowPlaying(self, processInfo: PlayerInfo, song: Song) -> None: async def sendNowPlaying(self, playlist: Playlist, channel: TextChannel, song: Song) -> None:
print('B')
# Get the lock of the playlist # Get the lock of the playlist
playlist = processInfo.getPlaylist()
if playlist.isLoopingOne(): if playlist.isLoopingOne():
title = self.__messages.ONE_SONG_LOOPING title = self.__messages.ONE_SONG_LOOPING
else: else:
@ -39,7 +39,6 @@ class ProcessCommandsExecutor:
# Create View and Embed # Create View and Embed
embed = self.__embeds.SONG_INFO(song.info, title) embed = self.__embeds.SONG_INFO(song.info, title)
channel = processInfo.getTextChannel()
view = self.__getPlayerView(channel) view = self.__getPlayerView(channel)
# Send Message and add to the MessagesManager # Send Message and add to the MessagesManager
message = await channel.send(embed=embed, view=view) message = await channel.send(embed=embed, view=view)

View File

@ -1,51 +0,0 @@
from enum import Enum
from multiprocessing import Process, Queue, Lock
from discord import TextChannel
from Music.Playlist import Playlist
class ProcessStatus(Enum):
RUNNING = 'Running'
SLEEPING = 'Sleeping'
class PlayerInfo:
"""
Class to store the reference to all structures to maintain a song player
"""
def __init__(self, process: Process, queueToPlayer: Queue, queueToMain: Queue, playlist: Playlist, lock: Lock, textChannel: TextChannel) -> None:
self.__process = process
self.__queueToPlayer = queueToPlayer
self.__queueToMain = queueToMain
self.__playlist = playlist
self.__lock = lock
self.__textChannel = textChannel
self.__status = ProcessStatus.RUNNING
def setProcess(self, newProcess: Process) -> None:
self.__process = newProcess
def getStatus(self) -> ProcessStatus:
return self.__status
def setStatus(self, status: ProcessStatus) -> None:
self.__status = status
def getProcess(self) -> Process:
return self.__process
def getQueueToPlayer(self) -> Queue:
return self.__queueToPlayer
def getQueueToMain(self) -> Queue:
return self.__queueToMain
def getPlaylist(self) -> Playlist:
return self.__playlist
def getLock(self) -> Lock:
return self.__lock
def getTextChannel(self) -> TextChannel:
return self.__textChannel

View File

@ -1,23 +1,70 @@
import asyncio import asyncio
from multiprocessing import Lock, Queue from enum import Enum
from multiprocessing import Lock, Process, Queue
from multiprocessing.managers import BaseManager, NamespaceProxy from multiprocessing.managers import BaseManager, NamespaceProxy
from queue import Empty from queue import Empty
from threading import Thread from threading import Thread
from typing import Dict, Tuple, Union from typing import Dict, Tuple, Union
from Config.Singleton import Singleton from Config.Singleton import Singleton
from discord import Guild, Interaction from discord import Guild, Interaction, TextChannel
from discord.ext.commands import Context from discord.ext.commands import Context
from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessExecutor import ProcessCommandsExecutor from Parallelism.ProcessExecutor import ProcessCommandsExecutor
from Music.Song import Song from Music.Song import Song
from Parallelism.PlayerProcess import PlayerProcess from Parallelism.PlayerProcess import PlayerProcess
from Music.Playlist import Playlist from Music.Playlist import Playlist
from Parallelism.ProcessInfo import PlayerInfo, ProcessStatus
from Parallelism.Commands import VCommands, VCommandsType from Parallelism.Commands import VCommands, VCommandsType
from Music.VulkanBot import VulkanBot from Music.VulkanBot import VulkanBot
class ProcessManager(Singleton, AbstractPlayersManager): class ProcessStatus(Enum):
RUNNING = 'Running'
SLEEPING = 'Sleeping'
class PlayerProcessInfo:
"""
Class to store the reference to all structures to maintain a process player
"""
def __init__(self, process: Process, queueToPlayer: Queue, queueToMain: Queue, playlist: Playlist, lock: Lock, textChannel: TextChannel) -> None:
self.__process = process
self.__queueToPlayer = queueToPlayer
self.__queueToMain = queueToMain
self.__playlist = playlist
self.__lock = lock
self.__textChannel = textChannel
self.__status = ProcessStatus.RUNNING
def setProcess(self, newProcess: Process) -> None:
self.__process = newProcess
def getStatus(self) -> ProcessStatus:
return self.__status
def setStatus(self, status: ProcessStatus) -> None:
self.__status = status
def getProcess(self) -> Process:
return self.__process
def getQueueToPlayer(self) -> Queue:
return self.__queueToPlayer
def getQueueToMain(self) -> Queue:
return self.__queueToMain
def getPlaylist(self) -> Playlist:
return self.__playlist
def getLock(self) -> Lock:
return self.__lock
def getTextChannel(self) -> TextChannel:
return self.__textChannel
class ProcessPlayerManager(Singleton, AbstractPlayersManager):
""" """
Manage all running player process, creating and storing them for future calls Manage all running player process, creating and storing them for future calls
Deals with the creation of shared memory Deals with the creation of shared memory
@ -29,28 +76,51 @@ class ProcessManager(Singleton, AbstractPlayersManager):
VManager.register('Playlist', Playlist) VManager.register('Playlist', Playlist)
self.__manager = VManager() self.__manager = VManager()
self.__manager.start() self.__manager.start()
self.__playersProcess: Dict[int, PlayerInfo] = {} self.__playersProcess: Dict[int, PlayerProcessInfo] = {}
self.__playersListeners: Dict[int, Tuple[Thread, bool]] = {} self.__playersListeners: Dict[int, Tuple[Thread, bool]] = {}
self.__playersCommandsExecutor: Dict[int, ProcessCommandsExecutor] = {} self.__playersCommandsExecutor: Dict[int, ProcessCommandsExecutor] = {}
def setPlayerInfo(self, guild: Guild, info: PlayerInfo): def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None):
self.__playersProcess[guild.id] = info if forceCreation:
processInfo = self.createPlayerForGuild(guild, context)
else:
processInfo = self.__getRunningPlayerInfo(guild)
def getOrCreatePlayerInfo(self, guild: Guild, context: Union[Context, Interaction]) -> PlayerInfo: if processInfo == None:
"""Return the process info for the guild, the user in context must be connected to a voice_channel""" return
queue = processInfo.getQueueToPlayer()
self.__putCommandInQueue(queue, command)
def getPlayerPlaylist(self, guild: Guild) -> Playlist:
playerInfo = self.__getRunningPlayerInfo(guild)
if playerInfo:
return playerInfo.getPlaylist()
def getPlayerLock(self, guild: Guild) -> Lock:
playerInfo = self.__getRunningPlayerInfo(guild)
if playerInfo:
return playerInfo.getLock()
def verifyIfPlayerExists(self, guild: Guild) -> bool:
return guild.id in self.__playersProcess.keys()
def createPlayerForGuild(self, guild: Guild, context: Union[Context, Interaction]) -> None:
try: try:
if guild.id not in self.__playersProcess.keys(): if guild.id not in self.__playersProcess.keys():
self.__playersProcess[guild.id] = self.__createProcessInfo(guild, context) self.__playersProcess[guild.id] = self.__createProcessPlayerInfo(guild, context)
else: else:
# If the process has ended create a new one # If the process has ended create a new one
if not self.__playersProcess[guild.id].getProcess().is_alive(): if not self.__playersProcess[guild.id].getProcess().is_alive():
self.__playersProcess[guild.id] = self.__recreateProcess(guild, context) self.__playersProcess[guild.id] = self.__recreateProcess(guild, context)
# Start the process
self.__playersProcess[guild.id].getProcess().start()
return self.__playersProcess[guild.id] return self.__playersProcess[guild.id]
except Exception as e: except Exception as e:
print(f'[Error In GetPlayerContext] -> {e}') print(f'[Error In GetPlayerContext] -> {e}')
def resetProcess(self, guild: Guild, context: Context) -> None: def resetPlayer(self, guild: Guild, context: Context) -> None:
"""Restart a running process, already start it to return to play""" """Restart a running process, already start it to return to play"""
if guild.id not in self.__playersProcess.keys(): if guild.id not in self.__playersProcess.keys():
return None return None
@ -60,10 +130,10 @@ class ProcessManager(Singleton, AbstractPlayersManager):
newProcessInfo.getProcess().start() # Start the process newProcessInfo.getProcess().start() # Start the process
# Send a command to start the play again # Send a command to start the play again
playCommand = VCommands(VCommandsType.PLAY) playCommand = VCommands(VCommandsType.PLAY)
newProcessInfo.getQueueToPlayer().put(playCommand) self.__putCommandInQueue(newProcessInfo.getQueueToPlayer(), playCommand)
self.__playersProcess[guild.id] = newProcessInfo self.__playersProcess[guild.id] = newProcessInfo
def getRunningPlayerInfo(self, guild: Guild) -> PlayerInfo: def __getRunningPlayerInfo(self, guild: Guild) -> PlayerProcessInfo:
"""Return the process info for the guild, if not, return None""" """Return the process info for the guild, if not, return None"""
if guild.id not in self.__playersProcess.keys(): if guild.id not in self.__playersProcess.keys():
print('Process Info not found') print('Process Info not found')
@ -71,7 +141,7 @@ class ProcessManager(Singleton, AbstractPlayersManager):
return self.__playersProcess[guild.id] return self.__playersProcess[guild.id]
def __createProcessInfo(self, guild: Guild, context: Context) -> PlayerInfo: def __createProcessPlayerInfo(self, guild: Guild, context: Context) -> PlayerProcessInfo:
guildID: int = context.guild.id guildID: int = context.guild.id
textID: int = context.channel.id textID: int = context.channel.id
voiceID: int = context.author.voice.channel.id voiceID: int = context.author.voice.channel.id
@ -82,9 +152,9 @@ class ProcessManager(Singleton, AbstractPlayersManager):
queueToListen = Queue() queueToListen = Queue()
queueToSend = Queue() queueToSend = Queue()
process = PlayerProcess(context.guild.name, playlist, lock, queueToSend, process = PlayerProcess(context.guild.name, playlist, lock, queueToSend,
queueToListen, guildID, textID, voiceID, authorID) queueToListen, guildID, voiceID)
processInfo = PlayerInfo(process, queueToSend, queueToListen, processInfo = PlayerProcessInfo(process, queueToSend, queueToListen,
playlist, lock, context.channel) playlist, lock, context.channel)
# Create a Thread to listen for the queue coming from the Player Process, this will redirect the Queue to a async # 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, thread = Thread(target=self.__listenToCommands,
@ -111,7 +181,7 @@ class ProcessManager(Singleton, AbstractPlayersManager):
except Exception as e: except Exception as e:
print(f'[ERROR STOPPING PROCESS] -> {e}') print(f'[ERROR STOPPING PROCESS] -> {e}')
def __recreateProcess(self, guild: Guild, context: Union[Context, Interaction]) -> PlayerInfo: def __recreateProcess(self, guild: Guild, context: Union[Context, Interaction]) -> PlayerProcessInfo:
"""Create a new process info using previous playlist""" """Create a new process info using previous playlist"""
self.__stopPossiblyRunningProcess(guild) self.__stopPossiblyRunningProcess(guild)
@ -130,8 +200,8 @@ class ProcessManager(Singleton, AbstractPlayersManager):
queueToSend = Queue() queueToSend = Queue()
process = PlayerProcess(context.guild.name, playlist, lock, queueToSend, process = PlayerProcess(context.guild.name, playlist, lock, queueToSend,
queueToListen, guildID, textID, voiceID, authorID) queueToListen, guildID, textID, voiceID, authorID)
processInfo = PlayerInfo(process, queueToSend, queueToListen, processInfo = PlayerProcessInfo(process, queueToSend, queueToListen,
playlist, lock, context.channel) playlist, lock, context.channel)
# Create a Thread to listen for the queue coming from the Player Process, this will redirect the Queue to a async # 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, thread = Thread(target=self.__listenToCommands,
@ -191,10 +261,18 @@ class ProcessManager(Singleton, AbstractPlayersManager):
# Set the status of this process as sleeping, only the playlist object remains # Set the status of this process as sleeping, only the playlist object remains
self.__playersProcess[guildID].setStatus(ProcessStatus.SLEEPING) self.__playersProcess[guildID].setStatus(ProcessStatus.SLEEPING)
def __putCommandInQueue(self, queue: Queue, command: VCommands) -> None:
try:
queue.put(command)
except Exception as e:
print(f'[ERROR PUTTING COMMAND IN QUEUE] -> {e}')
async def showNowPlaying(self, guildID: int, song: Song) -> None: async def showNowPlaying(self, guildID: int, song: Song) -> None:
commandExecutor = self.__playersCommandsExecutor[guildID] commandExecutor = self.__playersCommandsExecutor[guildID]
processInfo = self.__playersProcess[guildID] processInfo = self.__playersProcess[guildID]
print('A')
await commandExecutor.sendNowPlaying(processInfo, song) await commandExecutor.sendNowPlaying(processInfo, song)
print('C')
class VManager(BaseManager): class VManager(BaseManager):

View File

@ -1,199 +0,0 @@
import asyncio
from multiprocessing import Lock, Queue
from multiprocessing.managers import BaseManager, NamespaceProxy
from queue import Empty
from threading import Thread
from typing import Dict, Tuple, Union
from Config.Singleton import Singleton
from discord import Guild, Interaction
from discord.ext.commands import Context
from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Parallelism.ProcessExecutor import ProcessCommandsExecutor
from Music.Song import Song
from Parallelism.PlayerProcess import PlayerProcess
from Music.Playlist import Playlist
from Parallelism.ProcessInfo import PlayerInfo, ProcessStatus
from Parallelism.Commands import VCommands, VCommandsType
from Music.VulkanBot import VulkanBot
class ThreadManager(Singleton, AbstractPlayersManager):
"""
Manage all running player threads, creating and storing them for future calls
"""
def __init__(self, bot: VulkanBot = None) -> None:
if not super().created:
self.__bot = bot
self.__playersProcess: Dict[int, Thread] = {}
def setPlayerInfo(self, guild: Guild, info: PlayerInfo):
self.__playersProcess[guild.id] = info
def getOrCreatePlayerInfo(self, guild: Guild, context: Union[Context, Interaction]) -> PlayerInfo:
"""Return the process info for the guild, the user in context must be connected to a voice_channel"""
try:
if guild.id not in self.__playersProcess.keys():
self.__playersProcess[guild.id] = self.__createProcessInfo(guild, context)
else:
# If the process has ended create a new one
if not self.__playersProcess[guild.id].getProcess().is_alive():
self.__playersProcess[guild.id] = self.__recreateProcess(guild, context)
return self.__playersProcess[guild.id]
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(guild, context)
newProcessInfo.getProcess().start() # Start the process
# Send a command to start the play again
playCommand = VCommands(VCommandsType.PLAY)
newProcessInfo.getQueueToPlayer().put(playCommand)
self.__playersProcess[guild.id] = newProcessInfo
def getRunningPlayerInfo(self, guild: Guild) -> PlayerInfo:
"""Return the process info for the guild, if not, return None"""
if guild.id not in self.__playersProcess.keys():
print('Process Info not found')
return None
return self.__playersProcess[guild.id]
def __createProcessInfo(self, guild: Guild, context: Context) -> PlayerInfo:
guildID: int = context.guild.id
textID: int = context.channel.id
voiceID: int = context.author.voice.channel.id
authorID: int = context.author.id
playlist: Playlist = self.__manager.Playlist()
lock = Lock()
queueToListen = Queue()
queueToSend = Queue()
process = PlayerProcess(context.guild.name, playlist, lock, queueToSend,
queueToListen, guildID, textID, voiceID, authorID)
processInfo = PlayerInfo(process, queueToSend, queueToListen,
playlist, lock, context.channel)
# 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.__playersCommandsExecutor[guildID] = ProcessCommandsExecutor(self.__bot, guildID)
return processInfo
def __stopPossiblyRunningProcess(self, guild: Guild):
try:
if guild.id in self.__playersProcess.keys():
playerProcess = self.__playersProcess[guild.id]
process = playerProcess.getProcess()
process.close()
process.kill()
playerProcess.getQueueToMain().close()
playerProcess.getQueueToMain().join_thread()
playerProcess.getQueueToPlayer().close()
playerProcess.getQueueToPlayer().join_thread()
except Exception as e:
print(f'[ERROR STOPPING PROCESS] -> {e}')
def __recreateProcess(self, guild: Guild, context: Union[Context, Interaction]) -> PlayerInfo:
"""Create a new process info using previous playlist"""
self.__stopPossiblyRunningProcess(guild)
guildID: int = context.guild.id
textID: int = context.channel.id
if isinstance(context, Interaction):
authorID: int = context.user.id
voiceID: int = context.user.voice.channel.id
else:
authorID: int = context.author.id
voiceID: int = context.author.voice.channel.id
playlist: Playlist = self.__playersProcess[guildID].getPlaylist()
lock = Lock()
queueToListen = Queue()
queueToSend = Queue()
process = PlayerProcess(context.guild.name, playlist, lock, queueToSend,
queueToListen, guildID, textID, voiceID, authorID)
processInfo = PlayerInfo(process, queueToSend, queueToListen,
playlist, lock, context.channel)
# 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
def __listenToCommands(self, queue: Queue, guild: Guild) -> None:
guildID = guild.id
while True:
shouldEnd = self.__playersListeners[guildID][1]
if shouldEnd:
break
try:
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:
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(guildID)
return
elif commandType == VCommandsType.SLEEPING:
# The process might be used again
self.__sleepingProcess(guildID)
return
else:
print(f'[ERROR] -> Unknown Command Received from Process: {commandType}')
except Empty:
continue
except Exception as e:
print(f'[ERROR IN LISTENING PROCESS] -> {guild.name} - {e}')
def __terminateProcess(self, guildID: int) -> None:
# Delete all structures associated with the Player
del self.__playersProcess[guildID]
del self.__playersCommandsExecutor[guildID]
threadListening = self.__playersListeners[guildID]
threadListening._stop()
del self.__playersListeners[guildID]
def __sleepingProcess(self, guildID: int) -> None:
# Disable all process structures, except Playlist
queue1 = self.__playersProcess[guildID].getQueueToMain()
queue2 = self.__playersProcess[guildID].getQueueToPlayer()
queue1.close()
queue1.join_thread()
queue2.close()
queue2.join_thread()
# Set the status of this process as sleeping, only the playlist object remains
self.__playersProcess[guildID].setStatus(ProcessStatus.SLEEPING)
async def showNowPlaying(self, guildID: int, song: Song) -> None:
commandExecutor = self.__playersCommandsExecutor[guildID]
processInfo = self.__playersProcess[guildID]
await commandExecutor.sendNowPlaying(processInfo, song)
class VManager(BaseManager):
pass
class VProxy(NamespaceProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__')

View File

@ -0,0 +1,131 @@
from multiprocessing import Lock
from typing import Dict, Union
from Config.Singleton import Singleton
from discord import Guild, Interaction, TextChannel
from discord.ext.commands import Context
from Parallelism.AbstractProcessManager import AbstractPlayersManager
from Music.Song import Song
from Music.Playlist import Playlist
from Parallelism.Commands import VCommands, VCommandsType
from Music.VulkanBot import VulkanBot
from Parallelism.PlayerThread import PlayerThread
class PlayerThreadInfo:
"""
Class to store the reference to all structures to maintain a player thread
"""
def __init__(self, thread: PlayerThread, playlist: Playlist, lock: Lock, textChannel: TextChannel) -> None:
self.__thread = thread
self.__playlist = playlist
self.__lock = lock
self.__textChannel = textChannel
def getThread(self) -> PlayerThread:
return self.__thread
def getPlaylist(self) -> Playlist:
return self.__playlist
def getLock(self) -> Lock:
return self.__lock
def getTextChannel(self) -> TextChannel:
return self.__textChannel
class ThreadPlayerManager(Singleton, AbstractPlayersManager):
"""
Manage all running player threads, creating and storing them for future calls
"""
def __init__(self, bot: VulkanBot = None) -> None:
if not super().created:
self.__bot = bot
self.__playersThreads: Dict[int, PlayerThreadInfo] = {}
def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None):
return super().sendCommandToPlayer(command, guild, forceCreation, context)
def getPlayerPlaylist(self, guild: Guild) -> Playlist:
playerInfo = self.__getRunningPlayerInfo(guild)
if playerInfo:
return playerInfo.getPlaylist()
def getPlayerLock(self, guild: Guild) -> Lock:
playerInfo = self.__getRunningPlayerInfo(guild)
if playerInfo:
return playerInfo.getLock()
def verifyIfPlayerExists(self, guild: Guild) -> bool:
return guild.id in self.__playersThreads.keys()
def createPlayerForGuild(self, guild: Guild, context: Union[Context, Interaction]):
try:
if guild.id not in self.__playersThreads.keys():
self.__playersThreads[guild.id] = self.__createPlayerThreadInfo(context)
else:
# If the thread has ended create a new one
if not self.__playersThreads[guild.id].getThread().is_alive():
self.__playersThreads[guild.id] = self.__recreateThread(guild, context)
return self.__playersThreads[guild.id]
except Exception as e:
print(f'[Error In GetPlayerContext] -> {e}')
def resetPlayer(self, guild: Guild, context: Context) -> None:
if guild.id not in self.__playersThreads.keys():
return None
# Recreate the thread keeping the playlist
newPlayerInfo = self.__recreateThread(guild, context)
newPlayerInfo.getThread().start()
# Send a command to start the play again
playCommand = VCommands(VCommandsType.PLAY)
newPlayerInfo.getQueueToPlayer().put(playCommand)
self.__playersThreads[guild.id] = newPlayerInfo
def __getRunningPlayerInfo(self, guild: Guild) -> PlayerThreadInfo:
if guild.id not in self.__playersThreads.keys():
print('Process Info not found')
return None
return self.__playersThreads[guild.id]
def __createPlayerThreadInfo(self, context: Union[Context, Interaction]) -> PlayerThreadInfo:
guildID: int = context.guild.id
if isinstance(context, Interaction):
voiceID: int = context.user.voice.channel.id
else:
voiceID: int = context.author.voice.channel.id
playlist = Playlist()
lock = Lock()
player = PlayerThread(context.guild.name, playlist, lock, guildID, voiceID)
playerInfo = PlayerThreadInfo(player, playlist, lock, context.channel)
player.start()
return playerInfo
def __recreateThread(self, guild: Guild, context: Union[Context, Interaction]) -> PlayerThreadInfo:
self.__stopPossiblyRunningProcess(guild)
guildID: int = context.guild.id
if isinstance(context, Interaction):
voiceID: int = context.user.voice.channel.id
else:
voiceID: int = context.author.voice.channel.id
playlist = self.__playersThreads[guildID].getPlaylist()
lock = Lock()
player = PlayerThread(context.guild.name, playlist, lock, guildID, voiceID)
playerInfo = PlayerThreadInfo(player, playlist, lock, context.channel)
player.start()
return playerInfo
async def showNowPlaying(self, guildID: int, song: Song) -> None:
commandExecutor = self.__playersCommandsExecutor[guildID]
processInfo = self.__playersThreads[guildID]
await commandExecutor.sendNowPlaying(processInfo, song)