Re-work room persistence (#487)
* Initial server-side room persistence implementation (#434) * Added room permanence option to server * Fixed error if roomsDirPath is None * Sanitized filenames * Delete room file on empty playlist * Fixed position not saving when leaving and seeking, and position not loading after a restart * Decoupled permanence check * Added --rooms-timer option that limits the max lifespan of persistent rooms * Assigned filename to variable to deduplicate calculation * Freed up room when loading unwanted room from file Co-authored-by: Assistant <assistant.moetron@gmail.com> * Use sqlite for persistent/permanent rooms (#434) * Add -temp rooms and persistent room notices * Use system loanguage for servers * Make room temp check case-insensitive * Improve temp room check * Fix controlled rooms * Refactor how non-macOS/frozen initialLanguage is fixed * Fix persistent room list * Don't send dummy users to new console clients (#434) * Allow hiding of empty persistent rooms (#434) * List current rooms in join list Co-authored-by: Assistant <assistant.moetron@gmail.com>
This commit is contained in:
parent
148198b212
commit
c0e8652c8c
@ -78,12 +78,13 @@ Random string used to generate managed room passwords.
|
|||||||
Path to a file from which motd (Message Of The Day) will be read.
|
Path to a file from which motd (Message Of The Day) will be read.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-rooms\-dir [directory]
|
.B \-\-rooms\-db-file [directory]
|
||||||
Path to a directory from where room data will be written to and read from. This will enable rooms to persist without watchers and through restarts. Will not work if using \fB\-\-isolate\-rooms\fP.
|
Enables room persistence. Path is to where a database file should be loaded/create where room data will be written to and read from. This will enable rooms to persist without watchers and through restarts. Will not work if using \fB\-\-isolate\-rooms\fP.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-rooms\-timer [directory]
|
.B \-\-permanent\-rooms-file [directory]
|
||||||
Requires \fB\-\-rooms\-timer\fP. Time in seconds that rooms will persist without users. \fB0\fP disables the timer, meaning rooms persist permanently.
|
Specifies a list of rooms that will still be listed even if their playlist is empty. Path is to where a text file with one room per line. This will require persistent rooms to be enabled.
|
||||||
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-max\-chat\-message\-length [maxChatMessageLength]
|
.B \-\-max\-chat\-message\-length [maxChatMessageLength]
|
||||||
|
|||||||
@ -640,6 +640,7 @@ class SyncplayClient(object):
|
|||||||
"chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION),
|
"chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION),
|
||||||
"readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION),
|
"readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION),
|
||||||
"managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION),
|
"managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION),
|
||||||
|
"persistentRooms": False,
|
||||||
"maxChatMessageLength": constants.FALLBACK_MAX_CHAT_MESSAGE_LENGTH,
|
"maxChatMessageLength": constants.FALLBACK_MAX_CHAT_MESSAGE_LENGTH,
|
||||||
"maxUsernameLength": constants.FALLBACK_MAX_USERNAME_LENGTH,
|
"maxUsernameLength": constants.FALLBACK_MAX_USERNAME_LENGTH,
|
||||||
"maxRoomNameLength": constants.FALLBACK_MAX_ROOM_NAME_LENGTH,
|
"maxRoomNameLength": constants.FALLBACK_MAX_ROOM_NAME_LENGTH,
|
||||||
@ -706,11 +707,13 @@ class SyncplayClient(object):
|
|||||||
# Can change during runtime:
|
# Can change during runtime:
|
||||||
features["sharedPlaylists"] = self.sharedPlaylistIsEnabled() # Can change during runtime
|
features["sharedPlaylists"] = self.sharedPlaylistIsEnabled() # Can change during runtime
|
||||||
features["chat"] = self.chatIsEnabled() # Can change during runtime
|
features["chat"] = self.chatIsEnabled() # Can change during runtime
|
||||||
|
features["uiMode"] = self.ui.getUIMode()
|
||||||
|
|
||||||
# Static for this version/release of Syncplay:
|
# Static for this version/release of Syncplay:
|
||||||
features["featureList"] = True
|
features["featureList"] = True
|
||||||
features["readiness"] = True
|
features["readiness"] = True
|
||||||
features["managedRooms"] = True
|
features["managedRooms"] = True
|
||||||
|
features["persistentRooms"] = True
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@ -1593,6 +1596,9 @@ class UiManager(object):
|
|||||||
self.lastAlertOSDEndTime = None
|
self.lastAlertOSDEndTime = None
|
||||||
self.lastError = ""
|
self.lastError = ""
|
||||||
|
|
||||||
|
def getUIMode(self):
|
||||||
|
return self.__ui.uiMode
|
||||||
|
|
||||||
def addFileToPlaylist(self, newPlaylistItem):
|
def addFileToPlaylist(self, newPlaylistItem):
|
||||||
self.__ui.addFileToPlaylist(newPlaylistItem)
|
self.__ui.addFileToPlaylist(newPlaylistItem)
|
||||||
|
|
||||||
|
|||||||
@ -349,3 +349,8 @@ DEFAULT_TRUSTED_DOMAINS = ["youtube.com", "youtu.be"]
|
|||||||
TRUSTABLE_WEB_PROTOCOLS = ["http", "https"]
|
TRUSTABLE_WEB_PROTOCOLS = ["http", "https"]
|
||||||
|
|
||||||
PRIVATE_FILE_FIELDS = ["path"]
|
PRIVATE_FILE_FIELDS = ["path"]
|
||||||
|
|
||||||
|
CONSOLE_UI_MODE = "CLI"
|
||||||
|
GRAPHICAL_UI_MODE = "GUI"
|
||||||
|
UNKNOWN_UI_MODE = "Unknown"
|
||||||
|
FALLBACK_ASSUMED_UI_MODE = GRAPHICAL_UI_MODE
|
||||||
|
|||||||
@ -34,8 +34,8 @@ def main():
|
|||||||
args.port,
|
args.port,
|
||||||
args.password,
|
args.password,
|
||||||
args.motd_file,
|
args.motd_file,
|
||||||
args.rooms_dir,
|
args.rooms_db_file,
|
||||||
args.rooms_timer,
|
args.permanent_rooms_file,
|
||||||
args.isolate_rooms,
|
args.isolate_rooms,
|
||||||
args.salt,
|
args.salt,
|
||||||
args.disable_ready,
|
args.disable_ready,
|
||||||
|
|||||||
@ -64,7 +64,7 @@ def getInitialLanguage():
|
|||||||
try:
|
try:
|
||||||
import sys
|
import sys
|
||||||
frozen = getattr(sys, 'frozen', '')
|
frozen = getattr(sys, 'frozen', '')
|
||||||
if frozen in 'macosx_app':
|
if frozen and frozen in 'macosx_app':
|
||||||
from PySide2.QtCore import QLocale
|
from PySide2.QtCore import QLocale
|
||||||
initialLanguage = QLocale.system().uiLanguages()[0].split('-')[0]
|
initialLanguage = QLocale.system().uiLanguages()[0].split('-')[0]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -288,6 +288,7 @@ de = {
|
|||||||
"autoplay-menu-label": "Auto-Play-Knopf anzeigen",
|
"autoplay-menu-label": "Auto-Play-Knopf anzeigen",
|
||||||
"autoplay-guipushbuttonlabel": "Automatisch abspielen wenn alle bereit sind",
|
"autoplay-guipushbuttonlabel": "Automatisch abspielen wenn alle bereit sind",
|
||||||
"autoplay-minimum-label": "Minimum an Nutzern:",
|
"autoplay-minimum-label": "Minimum an Nutzern:",
|
||||||
|
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||||
|
|
||||||
"sendmessage-label": "Senden",
|
"sendmessage-label": "Senden",
|
||||||
|
|
||||||
@ -458,6 +459,7 @@ de = {
|
|||||||
|
|
||||||
# Server messages to client
|
# Server messages to client
|
||||||
"new-syncplay-available-motd-message": "Du nutzt Syncplay Version {}, aber es gibt eine neuere Version auf https://syncplay.pl", # ClientVersion
|
"new-syncplay-available-motd-message": "Du nutzt Syncplay Version {}, aber es gibt eine neuere Version auf https://syncplay.pl", # ClientVersion
|
||||||
|
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||||
|
|
||||||
# Server notifications
|
# Server notifications
|
||||||
"welcome-server-notification": "Willkommen zum Syncplay-Server, v. {0}", # version
|
"welcome-server-notification": "Willkommen zum Syncplay-Server, v. {0}", # version
|
||||||
@ -474,8 +476,8 @@ de = {
|
|||||||
"server-salt-argument": "zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird",
|
"server-salt-argument": "zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird",
|
||||||
"server-disable-ready-argument": "Bereitschaftsfeature deaktivieren",
|
"server-disable-ready-argument": "Bereitschaftsfeature deaktivieren",
|
||||||
"server-motd-argument": "Pfad zur Datei, von der die Nachricht des Tages geladen wird",
|
"server-motd-argument": "Pfad zur Datei, von der die Nachricht des Tages geladen wird",
|
||||||
"server-rooms-argument": "path to directory to store/fetch room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||||
"server-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # TODO: Translate
|
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||||
"server-chat-argument": "Soll Chat deaktiviert werden?",
|
"server-chat-argument": "Soll Chat deaktiviert werden?",
|
||||||
"server-chat-maxchars-argument": "Maximale Zeichenzahl in einer Chatnachricht (Standard ist {})",
|
"server-chat-maxchars-argument": "Maximale Zeichenzahl in einer Chatnachricht (Standard ist {})",
|
||||||
"server-maxusernamelength-argument": "Maximale Zeichenzahl in einem Benutzernamen (Standard ist {})",
|
"server-maxusernamelength-argument": "Maximale Zeichenzahl in einem Benutzernamen (Standard ist {})",
|
||||||
|
|||||||
@ -292,6 +292,7 @@ en = {
|
|||||||
"autoplay-menu-label": "Show auto-play button",
|
"autoplay-menu-label": "Show auto-play button",
|
||||||
"autoplay-guipushbuttonlabel": "Play when all ready",
|
"autoplay-guipushbuttonlabel": "Play when all ready",
|
||||||
"autoplay-minimum-label": "Min users:",
|
"autoplay-minimum-label": "Min users:",
|
||||||
|
"hideemptyrooms-menu-label": "Hide empty persistent rooms",
|
||||||
|
|
||||||
"sendmessage-label": "Send",
|
"sendmessage-label": "Send",
|
||||||
|
|
||||||
@ -458,6 +459,7 @@ en = {
|
|||||||
|
|
||||||
# Server messages to client
|
# Server messages to client
|
||||||
"new-syncplay-available-motd-message": "You are using Syncplay {} but a newer version is available from https://syncplay.pl", # ClientVersion
|
"new-syncplay-available-motd-message": "You are using Syncplay {} but a newer version is available from https://syncplay.pl", # ClientVersion
|
||||||
|
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # NOTE: Do not translate the word -temp
|
||||||
|
|
||||||
# Server notifications
|
# Server notifications
|
||||||
"welcome-server-notification": "Welcome to Syncplay server, ver. {0}", # version
|
"welcome-server-notification": "Welcome to Syncplay server, ver. {0}", # version
|
||||||
@ -475,8 +477,8 @@ en = {
|
|||||||
"server-salt-argument": "random string used to generate managed room passwords",
|
"server-salt-argument": "random string used to generate managed room passwords",
|
||||||
"server-disable-ready-argument": "disable readiness feature",
|
"server-disable-ready-argument": "disable readiness feature",
|
||||||
"server-motd-argument": "path to file from which motd will be fetched",
|
"server-motd-argument": "path to file from which motd will be fetched",
|
||||||
"server-rooms-argument": "path to directory to store/fetch room data. Enables rooms to persist without watchers and through restarts",
|
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts",
|
||||||
"server-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning",
|
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled",
|
||||||
"server-chat-argument": "Should chat be disabled?",
|
"server-chat-argument": "Should chat be disabled?",
|
||||||
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters
|
"server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters
|
||||||
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})",
|
"server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})",
|
||||||
|
|||||||
@ -291,6 +291,7 @@ es = {
|
|||||||
"autoplay-menu-label": "Mostrar botón de auto-reproducción",
|
"autoplay-menu-label": "Mostrar botón de auto-reproducción",
|
||||||
"autoplay-guipushbuttonlabel": "Reproducir cuando todos estén listos",
|
"autoplay-guipushbuttonlabel": "Reproducir cuando todos estén listos",
|
||||||
"autoplay-minimum-label": "Mín. de usuarios:",
|
"autoplay-minimum-label": "Mín. de usuarios:",
|
||||||
|
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||||
|
|
||||||
"sendmessage-label": "Enviar",
|
"sendmessage-label": "Enviar",
|
||||||
|
|
||||||
@ -457,6 +458,7 @@ es = {
|
|||||||
|
|
||||||
# Server messages to client
|
# Server messages to client
|
||||||
"new-syncplay-available-motd-message": "Estás usando Syncplay {} pero hay una versión más nueva disponible en https://syncplay.pl", # ClientVersion
|
"new-syncplay-available-motd-message": "Estás usando Syncplay {} pero hay una versión más nueva disponible en https://syncplay.pl", # ClientVersion
|
||||||
|
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||||
|
|
||||||
# Server notifications
|
# Server notifications
|
||||||
"welcome-server-notification": "Bienvenido al servidor de Syncplay, ver. {0}", # version
|
"welcome-server-notification": "Bienvenido al servidor de Syncplay, ver. {0}", # version
|
||||||
@ -474,8 +476,8 @@ es = {
|
|||||||
"server-salt-argument": "cadena aleatoria utilizada para generar contraseñas de salas administradas",
|
"server-salt-argument": "cadena aleatoria utilizada para generar contraseñas de salas administradas",
|
||||||
"server-disable-ready-argument": "deshabilitar la función de preparación",
|
"server-disable-ready-argument": "deshabilitar la función de preparación",
|
||||||
"server-motd-argument": "ruta al archivo del cual se obtendrá el texto motd",
|
"server-motd-argument": "ruta al archivo del cual se obtendrá el texto motd",
|
||||||
"server-rooms-argument": "path to directory to store/fetch room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||||
"server-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # TODO: Translate
|
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||||
"server-chat-argument": "¿Debería deshabilitarse el chat?",
|
"server-chat-argument": "¿Debería deshabilitarse el chat?",
|
||||||
"server-chat-maxchars-argument": "Número máximo de caracteres en un mensaje de chat (el valor predeterminado es {})", # Default number of characters
|
"server-chat-maxchars-argument": "Número máximo de caracteres en un mensaje de chat (el valor predeterminado es {})", # Default number of characters
|
||||||
"server-maxusernamelength-argument": "Número máximo de caracteres para el nombre de usuario (el valor predeterminado es {})",
|
"server-maxusernamelength-argument": "Número máximo de caracteres para el nombre de usuario (el valor predeterminado es {})",
|
||||||
|
|||||||
@ -291,6 +291,7 @@ it = {
|
|||||||
"autoplay-menu-label": "Mostra il tasto di riproduzione automatica",
|
"autoplay-menu-label": "Mostra il tasto di riproduzione automatica",
|
||||||
"autoplay-guipushbuttonlabel": "Riproduci quando tutti sono pronti",
|
"autoplay-guipushbuttonlabel": "Riproduci quando tutti sono pronti",
|
||||||
"autoplay-minimum-label": "Minimo utenti pronti:",
|
"autoplay-minimum-label": "Minimo utenti pronti:",
|
||||||
|
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||||
|
|
||||||
"sendmessage-label": "Invia",
|
"sendmessage-label": "Invia",
|
||||||
|
|
||||||
@ -457,6 +458,7 @@ it = {
|
|||||||
|
|
||||||
# Server messages to client
|
# Server messages to client
|
||||||
"new-syncplay-available-motd-message": "Stai usando Syncplay {} ma una nuova versione è disponibile presso https://syncplay.pl", # ClientVersion
|
"new-syncplay-available-motd-message": "Stai usando Syncplay {} ma una nuova versione è disponibile presso https://syncplay.pl", # ClientVersion
|
||||||
|
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate
|
||||||
|
|
||||||
# Server notifications
|
# Server notifications
|
||||||
"welcome-server-notification": "Benvenuto nel server Syncplay, ver. {0}", # version
|
"welcome-server-notification": "Benvenuto nel server Syncplay, ver. {0}", # version
|
||||||
@ -474,8 +476,8 @@ it = {
|
|||||||
"server-salt-argument": "usare stringhe casuali per generare le password delle stanze gestite",
|
"server-salt-argument": "usare stringhe casuali per generare le password delle stanze gestite",
|
||||||
"server-disable-ready-argument": "disabilita la funzionalità \"pronto\"",
|
"server-disable-ready-argument": "disabilita la funzionalità \"pronto\"",
|
||||||
"server-motd-argument": "percorso del file da cui verrà letto il messaggio del giorno",
|
"server-motd-argument": "percorso del file da cui verrà letto il messaggio del giorno",
|
||||||
"server-rooms-argument": "path to directory to store/fetch room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||||
"server-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # TODO: Translate
|
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||||
"server-chat-argument": "abilita o disabilita la chat",
|
"server-chat-argument": "abilita o disabilita la chat",
|
||||||
"server-chat-maxchars-argument": "Numero massimo di caratteri in un messaggio di chat (default è {})", # Default number of characters
|
"server-chat-maxchars-argument": "Numero massimo di caratteri in un messaggio di chat (default è {})", # Default number of characters
|
||||||
"server-maxusernamelength-argument": "Numero massimo di caratteri in un nome utente (default è {})",
|
"server-maxusernamelength-argument": "Numero massimo di caratteri in un nome utente (default è {})",
|
||||||
|
|||||||
@ -292,6 +292,7 @@ pt_BR = {
|
|||||||
"autoplay-menu-label": "Mostrar botão de reprodução automática",
|
"autoplay-menu-label": "Mostrar botão de reprodução automática",
|
||||||
"autoplay-guipushbuttonlabel": "Tocar quando todos estiverem prontos",
|
"autoplay-guipushbuttonlabel": "Tocar quando todos estiverem prontos",
|
||||||
"autoplay-minimum-label": "Mín. de usuários:",
|
"autoplay-minimum-label": "Mín. de usuários:",
|
||||||
|
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||||
|
|
||||||
"sendmessage-label": "Enviar",
|
"sendmessage-label": "Enviar",
|
||||||
|
|
||||||
@ -458,6 +459,7 @@ pt_BR = {
|
|||||||
|
|
||||||
# Server messages to client
|
# Server messages to client
|
||||||
"new-syncplay-available-motd-message": "Você está usando o Syncplay {}, mas uma versão mais nova está disponível em https://syncplay.pl", # ClientVersion
|
"new-syncplay-available-motd-message": "Você está usando o Syncplay {}, mas uma versão mais nova está disponível em https://syncplay.pl", # ClientVersion
|
||||||
|
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||||
|
|
||||||
# Server notifications
|
# Server notifications
|
||||||
"welcome-server-notification": "Seja bem-vindo ao servidor de Syncplay, versão {0}", # version
|
"welcome-server-notification": "Seja bem-vindo ao servidor de Syncplay, versão {0}", # version
|
||||||
@ -475,8 +477,8 @@ pt_BR = {
|
|||||||
"server-salt-argument": "string aleatória utilizada para gerar senhas de salas gerenciadas",
|
"server-salt-argument": "string aleatória utilizada para gerar senhas de salas gerenciadas",
|
||||||
"server-disable-ready-argument": "desativar recurso de prontidão",
|
"server-disable-ready-argument": "desativar recurso de prontidão",
|
||||||
"server-motd-argument": "caminho para o arquivo o qual o motd será obtido",
|
"server-motd-argument": "caminho para o arquivo o qual o motd será obtido",
|
||||||
"server-rooms-argument": "path to directory to store/fetch room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||||
"server-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # TODO: Translate
|
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||||
"server-chat-argument": "O chat deve ser desativado?",
|
"server-chat-argument": "O chat deve ser desativado?",
|
||||||
"server-chat-maxchars-argument": "Número máximo de caracteres numa mensagem do chat (o padrão é {})", # Default number of characters
|
"server-chat-maxchars-argument": "Número máximo de caracteres numa mensagem do chat (o padrão é {})", # Default number of characters
|
||||||
"server-maxusernamelength-argument": "Número máximos de caracteres num nome de usuário (o padrão é {})",
|
"server-maxusernamelength-argument": "Número máximos de caracteres num nome de usuário (o padrão é {})",
|
||||||
|
|||||||
@ -290,6 +290,7 @@ pt_PT = {
|
|||||||
"autoplay-menu-label": "Mostrar botão de reprodução automática",
|
"autoplay-menu-label": "Mostrar botão de reprodução automática",
|
||||||
"autoplay-guipushbuttonlabel": "Tocar quando todos estiverem prontos",
|
"autoplay-guipushbuttonlabel": "Tocar quando todos estiverem prontos",
|
||||||
"autoplay-minimum-label": "Mín. de utilizadores:",
|
"autoplay-minimum-label": "Mín. de utilizadores:",
|
||||||
|
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||||
|
|
||||||
"sendmessage-label": "Enviar",
|
"sendmessage-label": "Enviar",
|
||||||
|
|
||||||
@ -457,6 +458,7 @@ pt_PT = {
|
|||||||
|
|
||||||
# Server messages to client
|
# Server messages to client
|
||||||
"new-syncplay-available-motd-message": "Você está usando o Syncplay {}, mas uma versão mais nova está disponível em https://syncplay.pl", # ClientVersion
|
"new-syncplay-available-motd-message": "Você está usando o Syncplay {}, mas uma versão mais nova está disponível em https://syncplay.pl", # ClientVersion
|
||||||
|
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||||
|
|
||||||
# Server notifications
|
# Server notifications
|
||||||
"welcome-server-notification": "Seja bem-vindo ao servidor de Syncplay, versão {0}", # version
|
"welcome-server-notification": "Seja bem-vindo ao servidor de Syncplay, versão {0}", # version
|
||||||
@ -474,8 +476,8 @@ pt_PT = {
|
|||||||
"server-salt-argument": "string aleatória utilizada para gerar senhas de salas gerenciadas",
|
"server-salt-argument": "string aleatória utilizada para gerar senhas de salas gerenciadas",
|
||||||
"server-disable-ready-argument": "desativar recurso de prontidão",
|
"server-disable-ready-argument": "desativar recurso de prontidão",
|
||||||
"server-motd-argument": "caminho para o arquivo o qual o motd será obtido",
|
"server-motd-argument": "caminho para o arquivo o qual o motd será obtido",
|
||||||
"server-rooms-argument": "path to directory to store/fetch room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||||
"server-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # TODO: Translate
|
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||||
"server-chat-argument": "O chat deve ser desativado?",
|
"server-chat-argument": "O chat deve ser desativado?",
|
||||||
"server-chat-maxchars-argument": "Número máximo de caracteres numa mensagem do chat (o padrão é {})", # Default number of characters
|
"server-chat-maxchars-argument": "Número máximo de caracteres numa mensagem do chat (o padrão é {})", # Default number of characters
|
||||||
"server-maxusernamelength-argument": "Número máximos de caracteres num nome de utilizador (o padrão é {})",
|
"server-maxusernamelength-argument": "Número máximos de caracteres num nome de utilizador (o padrão é {})",
|
||||||
|
|||||||
@ -291,6 +291,7 @@ ru = {
|
|||||||
"autoplay-menu-label": "Показывать кнопку &автовоспроизведения",
|
"autoplay-menu-label": "Показывать кнопку &автовоспроизведения",
|
||||||
"autoplay-guipushbuttonlabel": "Стартовать, когда все будут готовы",
|
"autoplay-guipushbuttonlabel": "Стартовать, когда все будут готовы",
|
||||||
"autoplay-minimum-label": "Минимум зрителей:",
|
"autoplay-minimum-label": "Минимум зрителей:",
|
||||||
|
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||||
"sendmessage-label": "Выслать",
|
"sendmessage-label": "Выслать",
|
||||||
|
|
||||||
"ready-guipushbuttonlabel": "Я готов",
|
"ready-guipushbuttonlabel": "Я готов",
|
||||||
@ -455,6 +456,7 @@ ru = {
|
|||||||
|
|
||||||
# Server messages to client
|
# Server messages to client
|
||||||
"new-syncplay-available-motd-message": "Вы используете Syncplay версии {}. Доступна более новая версия на https://syncplay.pl/", # ClientVersion
|
"new-syncplay-available-motd-message": "Вы используете Syncplay версии {}. Доступна более новая версия на https://syncplay.pl/", # ClientVersion
|
||||||
|
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||||
|
|
||||||
# Server notifications
|
# Server notifications
|
||||||
"welcome-server-notification": "Добро пожаловать на сервер Syncplay версии {0}", # version
|
"welcome-server-notification": "Добро пожаловать на сервер Syncplay версии {0}", # version
|
||||||
@ -471,8 +473,8 @@ ru = {
|
|||||||
"server-salt-argument": "генерировать пароли к управляемым комнатам на основании указанной строки (соли)",
|
"server-salt-argument": "генерировать пароли к управляемым комнатам на основании указанной строки (соли)",
|
||||||
"server-disable-ready-argument": "отключить статусы готов/не готов",
|
"server-disable-ready-argument": "отключить статусы готов/не готов",
|
||||||
"server-motd-argument": "путь к файлу, из которого будет извлекаться MOTD-сообщение",
|
"server-motd-argument": "путь к файлу, из которого будет извлекаться MOTD-сообщение",
|
||||||
"server-rooms-argument": "path to directory to store/fetch room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||||
"server-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # TODO: Translate
|
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||||
"server-chat-argument": "Должен ли чат быть отключён?",
|
"server-chat-argument": "Должен ли чат быть отключён?",
|
||||||
"server-chat-maxchars-argument": "Максимальное число символов в сообщениях в чате (по умолчанию {})",
|
"server-chat-maxchars-argument": "Максимальное число символов в сообщениях в чате (по умолчанию {})",
|
||||||
"server-maxusernamelength-argument": "Максимальное число символов в именах пользователей (по умолчанию {})",
|
"server-maxusernamelength-argument": "Максимальное число символов в именах пользователей (по умолчанию {})",
|
||||||
|
|||||||
@ -292,6 +292,7 @@ tr = {
|
|||||||
"autoplay-menu-label": "Otomatik oynat düğmesini göster",
|
"autoplay-menu-label": "Otomatik oynat düğmesini göster",
|
||||||
"autoplay-guipushbuttonlabel": "Her şey hazır olduğunda oynat",
|
"autoplay-guipushbuttonlabel": "Her şey hazır olduğunda oynat",
|
||||||
"autoplay-minimum-label": "Asgari kullanıcı:",
|
"autoplay-minimum-label": "Asgari kullanıcı:",
|
||||||
|
"hideemptyrooms-menu-label": "Hide empty persistent rooms", # TODO: Translate
|
||||||
|
|
||||||
"sendmessage-label": "Gönder",
|
"sendmessage-label": "Gönder",
|
||||||
|
|
||||||
@ -458,6 +459,7 @@ tr = {
|
|||||||
|
|
||||||
# Server messages to client
|
# Server messages to client
|
||||||
"new-syncplay-available-motd-message": "Syncplay {} kullanıyorsunuz ancak daha yeni bir sürüm https://syncplay.pl adresinde mevcut", # ClientVersion
|
"new-syncplay-available-motd-message": "Syncplay {} kullanıyorsunuz ancak daha yeni bir sürüm https://syncplay.pl adresinde mevcut", # ClientVersion
|
||||||
|
"persistent-rooms-notice": "NOTICE: This server uses persistent rooms, which means that the playlist information is stored between playback sessions. If you want to create a room where information is not saved then put -temp at the end of the room name.", # TO DO: Translate - NOTE: Do not translate the word -temp
|
||||||
|
|
||||||
# Server notifications
|
# Server notifications
|
||||||
"welcome-server-notification": "Syncplay sunucusuna hoş geldiniz, ver. {0}", # version
|
"welcome-server-notification": "Syncplay sunucusuna hoş geldiniz, ver. {0}", # version
|
||||||
@ -475,8 +477,8 @@ tr = {
|
|||||||
"server-salt-argument": "yönetilen oda şifreleri oluşturmak için kullanılan rastgele dize",
|
"server-salt-argument": "yönetilen oda şifreleri oluşturmak için kullanılan rastgele dize",
|
||||||
"server-disable-ready-argument": "hazır olma özelliğini devre dışı bırak",
|
"server-disable-ready-argument": "hazır olma özelliğini devre dışı bırak",
|
||||||
"server-motd-argument": "motd alınacak dosyanın yolu",
|
"server-motd-argument": "motd alınacak dosyanın yolu",
|
||||||
"server-rooms-argument": "path to directory to store/fetch room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
"server-rooms-argument": "path to database file to use and/or create to store persistent room data. Enables rooms to persist without watchers and through restarts", # TODO: Translate
|
||||||
"server-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # TODO: Translate
|
"server-permanent-rooms-argument": "path to file which lists permenant rooms that will be listed even if the room is empty (in the form of a text file which lists one room per line) - requires persistent rooms to be enabled", # TODO: Translate
|
||||||
"server-chat-argument": "Sohbet devre dışı bırakılmalı mı?",
|
"server-chat-argument": "Sohbet devre dışı bırakılmalı mı?",
|
||||||
"server-chat-maxchars-argument": "Bir sohbet mesajındaki maksimum karakter sayısı (varsayılan: {})", # Default number of characters
|
"server-chat-maxchars-argument": "Bir sohbet mesajındaki maksimum karakter sayısı (varsayılan: {})", # Default number of characters
|
||||||
"server-maxusernamelength-argument": "Bir kullanıcı adındaki maksimum karakter sayısı (varsayılan {})",
|
"server-maxusernamelength-argument": "Bir kullanıcı adındaki maksimum karakter sayısı (varsayılan {})",
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from twisted.python.versions import Version
|
|||||||
from zope.interface.declarations import implementer
|
from zope.interface.declarations import implementer
|
||||||
|
|
||||||
import syncplay
|
import syncplay
|
||||||
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION
|
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION, UNKNOWN_UI_MODE
|
||||||
from syncplay.messages import getMessage
|
from syncplay.messages import getMessage
|
||||||
from syncplay.utils import meetsMinVersion
|
from syncplay.utils import meetsMinVersion
|
||||||
|
|
||||||
@ -131,6 +131,10 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
self._client.setUsername(username)
|
self._client.setUsername(username)
|
||||||
self._client.setRoom(roomName)
|
self._client.setRoom(roomName)
|
||||||
self.logged = True
|
self.logged = True
|
||||||
|
if self.persistentRoomWarning(featureList):
|
||||||
|
if len(motd) > 0:
|
||||||
|
motd += "\n\n"
|
||||||
|
motd += getMessage("persistent-rooms-notice")
|
||||||
if motd:
|
if motd:
|
||||||
self._client.ui.showMessage(motd, True, True)
|
self._client.ui.showMessage(motd, True, True)
|
||||||
self._client.ui.showMessage(getMessage("connected-successful-notification"))
|
self._client.ui.showMessage(getMessage("connected-successful-notification"))
|
||||||
@ -138,6 +142,9 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
self._client.sendFile()
|
self._client.sendFile()
|
||||||
self._client.setServerVersion(version, featureList)
|
self._client.setServerVersion(version, featureList)
|
||||||
|
|
||||||
|
def persistentRoomWarning(self, serverFeatures):
|
||||||
|
return serverFeatures["persistentRooms"] if "persistentRooms" in serverFeatures else False
|
||||||
|
|
||||||
def sendHello(self):
|
def sendHello(self):
|
||||||
hello = {}
|
hello = {}
|
||||||
hello["username"] = self._client.getUsername()
|
hello["username"] = self._client.getUsername()
|
||||||
@ -441,6 +448,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
self._features["featureList"] = False
|
self._features["featureList"] = False
|
||||||
self._features["readiness"] = meetsMinVersion(self._version, USER_READY_MIN_VERSION)
|
self._features["readiness"] = meetsMinVersion(self._version, USER_READY_MIN_VERSION)
|
||||||
self._features["managedRooms"] = meetsMinVersion(self._version, CONTROLLED_ROOMS_MIN_VERSION)
|
self._features["managedRooms"] = meetsMinVersion(self._version, CONTROLLED_ROOMS_MIN_VERSION)
|
||||||
|
self._features["persistentRooms"] = False
|
||||||
|
self._features["uiMode"] = UNKNOWN_UI_MODE
|
||||||
return self._features
|
return self._features
|
||||||
|
|
||||||
def isLogged(self):
|
def isLogged(self):
|
||||||
@ -496,6 +505,11 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
self._logged = True
|
self._logged = True
|
||||||
self.sendHello(version)
|
self.sendHello(version)
|
||||||
|
|
||||||
|
def persistentRoomWarning(self, clientFeatures, serverFeatures):
|
||||||
|
serverPersistentRooms = serverFeatures["persistentRooms"]
|
||||||
|
clientPersistentRooms = clientFeatures["persistentRooms"] if "persistentRooms" in clientFeatures else False
|
||||||
|
return serverPersistentRooms and not clientPersistentRooms
|
||||||
|
|
||||||
@requireLogged
|
@requireLogged
|
||||||
def handleChat(self, chatMessage):
|
def handleChat(self, chatMessage):
|
||||||
if not self._factory.disableChat:
|
if not self._factory.disableChat:
|
||||||
@ -520,8 +534,12 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
hello["room"] = {"name": room.getName()}
|
hello["room"] = {"name": room.getName()}
|
||||||
hello["version"] = clientVersion # Used so 1.2.X client works on newer server
|
hello["version"] = clientVersion # Used so 1.2.X client works on newer server
|
||||||
hello["realversion"] = syncplay.version
|
hello["realversion"] = syncplay.version
|
||||||
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
|
|
||||||
hello["features"] = self._factory.getFeatures()
|
hello["features"] = self._factory.getFeatures()
|
||||||
|
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
|
||||||
|
if self.persistentRoomWarning(clientFeatures=self._features, serverFeatures=hello["features"]):
|
||||||
|
if len(hello["motd"]) > 0:
|
||||||
|
hello["motd"] += "\n\n"
|
||||||
|
hello["motd"] += getMessage("persistent-rooms-notice")
|
||||||
self.sendMessage({"Hello": hello})
|
self.sendMessage({"Hello": hello})
|
||||||
|
|
||||||
@requireLogged
|
@requireLogged
|
||||||
@ -617,11 +635,28 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
}
|
}
|
||||||
userlist[room.getName()][watcher.getName()] = userFile
|
userlist[room.getName()][watcher.getName()] = userFile
|
||||||
|
|
||||||
|
def _addDummyUserOnList(self, userlist, dummyRoom,dummyCount):
|
||||||
|
if dummyRoom not in userlist:
|
||||||
|
userlist[dummyRoom] = {}
|
||||||
|
dummyFile = {
|
||||||
|
"position": 0,
|
||||||
|
"file": {},
|
||||||
|
"controller": False,
|
||||||
|
"isReady": True,
|
||||||
|
"features": []
|
||||||
|
}
|
||||||
|
userlist[dummyRoom][" " * dummyCount] = dummyFile
|
||||||
|
|
||||||
def sendList(self):
|
def sendList(self):
|
||||||
userlist = {}
|
userlist = {}
|
||||||
watchers = self._factory.getAllWatchersForUser(self._watcher)
|
watchers = self._factory.getAllWatchersForUser(self._watcher)
|
||||||
|
dummyCount = 0
|
||||||
for watcher in watchers:
|
for watcher in watchers:
|
||||||
self._addUserOnList(userlist, watcher)
|
self._addUserOnList(userlist, watcher)
|
||||||
|
if self._watcher.isGUIUser(self.getFeatures()):
|
||||||
|
for emptyRoom in self._factory.getEmptyPersistentRooms():
|
||||||
|
dummyCount += 1
|
||||||
|
self._addDummyUserOnList(userlist, emptyRoom, dummyCount)
|
||||||
self.sendMessage({"List": userlist})
|
self.sendMessage({"List": userlist})
|
||||||
|
|
||||||
@requireLogged
|
@requireLogged
|
||||||
|
|||||||
@ -22,14 +22,14 @@ import syncplay
|
|||||||
from syncplay import constants
|
from syncplay import constants
|
||||||
from syncplay.messages import getMessage
|
from syncplay.messages import getMessage
|
||||||
from syncplay.protocols import SyncServerProtocol
|
from syncplay.protocols import SyncServerProtocol
|
||||||
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText
|
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText, getListAsMultilineString, convertMultilineStringToList
|
||||||
|
|
||||||
|
|
||||||
class SyncFactory(Factory):
|
class SyncFactory(Factory):
|
||||||
def __init__(self, port='', password='', motdFilePath=None, roomsDirPath=None, roomsTimer=31558149, isolateRooms=False, salt=None,
|
def __init__(self, port='', password='', motdFilePath=None, roomsDbFile=None, permanentRoomsFile=None, isolateRooms=False, salt=None,
|
||||||
disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH,
|
disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH,
|
||||||
maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCertPath=None):
|
maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCertPath=None):
|
||||||
self.isolateRooms = isolateRooms
|
self.isolateRooms = isolateRooms
|
||||||
|
syncplay.messages.setLanguage(syncplay.messages.getInitialLanguage())
|
||||||
print(getMessage("welcome-server-notification").format(syncplay.version))
|
print(getMessage("welcome-server-notification").format(syncplay.version))
|
||||||
self.port = port
|
self.port = port
|
||||||
if password:
|
if password:
|
||||||
@ -41,18 +41,19 @@ class SyncFactory(Factory):
|
|||||||
print(getMessage("no-salt-notification").format(salt))
|
print(getMessage("no-salt-notification").format(salt))
|
||||||
self._salt = salt
|
self._salt = salt
|
||||||
self._motdFilePath = motdFilePath
|
self._motdFilePath = motdFilePath
|
||||||
self._roomsDirPath = roomsDirPath if roomsDirPath is not None and os.path.isdir(roomsDirPath) else None
|
self.roomsDbFile = roomsDbFile
|
||||||
self._roomsTimer = roomsTimer if roomsDirPath is not None and isinstance(roomsTimer, int) and roomsTimer > 0 else 0
|
|
||||||
self.disableReady = disableReady
|
self.disableReady = disableReady
|
||||||
self.disableChat = disableChat
|
self.disableChat = disableChat
|
||||||
self.maxChatMessageLength = maxChatMessageLength if maxChatMessageLength is not None else constants.MAX_CHAT_MESSAGE_LENGTH
|
self.maxChatMessageLength = maxChatMessageLength if maxChatMessageLength is not None else constants.MAX_CHAT_MESSAGE_LENGTH
|
||||||
self.maxUsernameLength = maxUsernameLength if maxUsernameLength is not None else constants.MAX_USERNAME_LENGTH
|
self.maxUsernameLength = maxUsernameLength if maxUsernameLength is not None else constants.MAX_USERNAME_LENGTH
|
||||||
|
self.permanentRoomsFile = permanentRoomsFile if permanentRoomsFile is not None and os.path.isfile(permanentRoomsFile) else None
|
||||||
|
self.permanentRooms = self.loadListFromMultilineTextFile(self.permanentRoomsFile) if self.permanentRoomsFile is not None else []
|
||||||
if not isolateRooms:
|
if not isolateRooms:
|
||||||
self._roomManager = RoomManager(self._roomsDirPath, self._roomsTimer)
|
self._roomManager = RoomManager(self.roomsDbFile, self.permanentRooms)
|
||||||
else:
|
else:
|
||||||
self._roomManager = PublicRoomManager()
|
self._roomManager = PublicRoomManager()
|
||||||
if statsDbFile is not None:
|
if statsDbFile is not None:
|
||||||
self._statsDbHandle = DBManager(statsDbFile)
|
self._statsDbHandle = StatsDBManager(statsDbFile)
|
||||||
self._statsRecorder = StatsRecorder(self._statsDbHandle, self._roomManager)
|
self._statsRecorder = StatsRecorder(self._statsDbHandle, self._roomManager)
|
||||||
statsDelay = 5*(int(self.port)%10 + 1)
|
statsDelay = 5*(int(self.port)%10 + 1)
|
||||||
self._statsRecorder.startRecorder(statsDelay)
|
self._statsRecorder.startRecorder(statsDelay)
|
||||||
@ -67,6 +68,16 @@ class SyncFactory(Factory):
|
|||||||
self.options = None
|
self.options = None
|
||||||
self.serverAcceptsTLS = False
|
self.serverAcceptsTLS = False
|
||||||
|
|
||||||
|
def loadListFromMultilineTextFile(self, path):
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
return []
|
||||||
|
with open(path) as f:
|
||||||
|
multiline = f.read().splitlines()
|
||||||
|
return multiline
|
||||||
|
|
||||||
|
def loadRoom(self):
|
||||||
|
rooms = self._roomsDbHandle.loadRooms()
|
||||||
|
|
||||||
def buildProtocol(self, addr):
|
def buildProtocol(self, addr):
|
||||||
return SyncServerProtocol(self)
|
return SyncServerProtocol(self)
|
||||||
|
|
||||||
@ -82,6 +93,7 @@ class SyncFactory(Factory):
|
|||||||
features["isolateRooms"] = self.isolateRooms
|
features["isolateRooms"] = self.isolateRooms
|
||||||
features["readiness"] = not self.disableReady
|
features["readiness"] = not self.disableReady
|
||||||
features["managedRooms"] = True
|
features["managedRooms"] = True
|
||||||
|
features["persistentRooms"] = self.roomsDbFile is not None
|
||||||
features["chat"] = not self.disableChat
|
features["chat"] = not self.disableChat
|
||||||
features["maxChatMessageLength"] = self.maxChatMessageLength
|
features["maxChatMessageLength"] = self.maxChatMessageLength
|
||||||
features["maxUsernameLength"] = self.maxUsernameLength
|
features["maxUsernameLength"] = self.maxUsernameLength
|
||||||
@ -137,11 +149,17 @@ class SyncFactory(Factory):
|
|||||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None)
|
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None)
|
||||||
self._roomManager.broadcast(watcher, l)
|
self._roomManager.broadcast(watcher, l)
|
||||||
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
||||||
|
if self.roomsDbFile:
|
||||||
|
l = lambda w: w.sendList(toGUIOnly=True)
|
||||||
|
self._roomManager.broadcast(watcher, l)
|
||||||
|
|
||||||
def removeWatcher(self, watcher):
|
def removeWatcher(self, watcher):
|
||||||
if watcher and watcher.getRoom():
|
if watcher and watcher.getRoom():
|
||||||
self.sendLeftMessage(watcher)
|
self.sendLeftMessage(watcher)
|
||||||
self._roomManager.removeWatcher(watcher)
|
self._roomManager.removeWatcher(watcher)
|
||||||
|
if self.roomsDbFile:
|
||||||
|
l = lambda w: w.sendList(toGUIOnly=True)
|
||||||
|
self._roomManager.broadcast(watcher, l)
|
||||||
|
|
||||||
def sendLeftMessage(self, watcher):
|
def sendLeftMessage(self, watcher):
|
||||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"left": True})
|
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"left": True})
|
||||||
@ -151,6 +169,9 @@ class SyncFactory(Factory):
|
|||||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True, "version": watcher.getVersion(), "features": watcher.getFeatures()}) if w != watcher else None
|
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True, "version": watcher.getVersion(), "features": watcher.getFeatures()}) if w != watcher else None
|
||||||
self._roomManager.broadcast(watcher, l)
|
self._roomManager.broadcast(watcher, l)
|
||||||
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
||||||
|
if self.roomsDbFile:
|
||||||
|
l = lambda w: w.sendList(toGUIOnly=True)
|
||||||
|
self._roomManager.broadcast(watcher, l)
|
||||||
|
|
||||||
def sendFileUpdate(self, watcher):
|
def sendFileUpdate(self, watcher):
|
||||||
if watcher.getFile():
|
if watcher.getFile():
|
||||||
@ -172,6 +193,9 @@ class SyncFactory(Factory):
|
|||||||
def getAllWatchersForUser(self, forUser):
|
def getAllWatchersForUser(self, forUser):
|
||||||
return self._roomManager.getAllWatchersForUser(forUser)
|
return self._roomManager.getAllWatchersForUser(forUser)
|
||||||
|
|
||||||
|
def getEmptyPersistentRooms(self):
|
||||||
|
return self._roomManager.getEmptyPersistentRooms()
|
||||||
|
|
||||||
def authRoomController(self, watcher, password, roomBaseName=None):
|
def authRoomController(self, watcher, password, roomBaseName=None):
|
||||||
room = watcher.getRoom()
|
room = watcher.getRoom()
|
||||||
roomName = roomBaseName if roomBaseName else room.getName()
|
roomName = roomBaseName if roomBaseName else room.getName()
|
||||||
@ -290,8 +314,33 @@ class StatsRecorder(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class RoomsRecorder(StatsRecorder):
|
||||||
|
def __init__(self, dbHandle, roomManager):
|
||||||
|
self._dbHandle = dbHandle
|
||||||
|
self._roomManagerHandle = roomManager
|
||||||
|
|
||||||
class DBManager(object):
|
def startRecorder(self, delay):
|
||||||
|
try:
|
||||||
|
self._dbHandle.connect()
|
||||||
|
reactor.callLater(delay, self._scheduleClientSnapshot) # TODO: FIX THIS!
|
||||||
|
except:
|
||||||
|
print("--- Error in initializing the stats database. Server Stats not enabled. ---")
|
||||||
|
|
||||||
|
def _scheduleClientSnapshot(self):
|
||||||
|
self._clientSnapshotTimer = task.LoopingCall(self._runClientSnapshot)
|
||||||
|
self._clientSnapshotTimer.start(constants.SERVER_STATS_SNAPSHOT_INTERVAL)
|
||||||
|
|
||||||
|
def _runClientSnapshot(self):
|
||||||
|
try:
|
||||||
|
snapshotTime = int(time.time())
|
||||||
|
rooms = self._roomManagerHandle.exportRooms()
|
||||||
|
for room in rooms.values():
|
||||||
|
for watcher in room.getWatchers():
|
||||||
|
self._dbHandle.addVersionLog(snapshotTime, watcher.getVersion())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class StatsDBManager(object):
|
||||||
def __init__(self, dbpath):
|
def __init__(self, dbpath):
|
||||||
self._dbPath = dbpath
|
self._dbPath = dbpath
|
||||||
self._connection = None
|
self._connection = None
|
||||||
@ -305,31 +354,74 @@ class DBManager(object):
|
|||||||
self._createSchema()
|
self._createSchema()
|
||||||
|
|
||||||
def _createSchema(self):
|
def _createSchema(self):
|
||||||
initQuery = 'create table if not exists clients_snapshots (snapshot_time integer, version string)'
|
initQuery = 'create table if not exists clients_snapshots (snapshot_time INTEGER, version STRING)'
|
||||||
self._connection.runQuery(initQuery)
|
return self._connection.runQuery(initQuery)
|
||||||
|
|
||||||
def addVersionLog(self, timestamp, version):
|
def addVersionLog(self, timestamp, version):
|
||||||
content = (timestamp, version, )
|
content = (timestamp, version, )
|
||||||
self._connection.runQuery("INSERT INTO clients_snapshots VALUES (?, ?)", content)
|
self._connection.runQuery("INSERT INTO clients_snapshots VALUES (?, ?)", content)
|
||||||
|
|
||||||
|
class RoomDBManager(object):
|
||||||
|
def __init__(self, dbpath, loadroomscallback):
|
||||||
|
self._dbPath = dbpath
|
||||||
|
self._connection = None
|
||||||
|
self._loadRoomsCallback = loadroomscallback
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self._connection is not None:
|
||||||
|
self._connection.close()
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self._connection = adbapi.ConnectionPool("sqlite3", self._dbPath, check_same_thread=False)
|
||||||
|
self._createSchema().addCallback(self.loadRooms)
|
||||||
|
|
||||||
|
def _createSchema(self):
|
||||||
|
initQuery = 'create table if not exists persistent_rooms (name STRING PRIMARY KEY, playlist STRING, playlistIndex INTEGER, position REAL, lastSavedUpdate INTEGER)'
|
||||||
|
return self._connection.runQuery(initQuery)
|
||||||
|
|
||||||
|
def saveRoom(self, name, playlist, playlistIndex, position, lastUpdate):
|
||||||
|
content = (name, playlist, playlistIndex, position, lastUpdate)
|
||||||
|
self._connection.runQuery("INSERT OR REPLACE INTO persistent_rooms VALUES (?, ?, ?, ?, ?)", content)
|
||||||
|
|
||||||
|
def deleteRoom(self, name):
|
||||||
|
self._connection.runQuery("DELETE FROM persistent_rooms where name = ?", [name])
|
||||||
|
|
||||||
|
def loadRooms(self, result=None):
|
||||||
|
roomsQuery = "SELECT * FROM persistent_rooms"
|
||||||
|
rooms = self._connection.runQuery(roomsQuery)
|
||||||
|
rooms.addCallback(self.loadedRooms)
|
||||||
|
|
||||||
|
def loadedRooms(self, rooms):
|
||||||
|
self._loadRoomsCallback(rooms)
|
||||||
|
|
||||||
class RoomManager(object):
|
class RoomManager(object):
|
||||||
def __init__(self, roomsDir=None, timer=0):
|
def __init__(self, roomsdbfile=None, permanentRooms=[]):
|
||||||
self._roomsDir = roomsDir
|
self._roomsDbFile = roomsdbfile
|
||||||
self._timer = timer
|
|
||||||
self._rooms = {}
|
self._rooms = {}
|
||||||
if self._roomsDir is not None:
|
self._permanentRooms = permanentRooms
|
||||||
for root, dirs, files in os.walk(self._roomsDir):
|
if self._roomsDbFile is not None:
|
||||||
for file in files:
|
self._roomsDbHandle = RoomDBManager(self._roomsDbFile, self.loadRooms)
|
||||||
if file.endswith(".room"):
|
self._roomsDbHandle.connect()
|
||||||
room = Room('', self._roomsDir)
|
else:
|
||||||
room.loadFromFile(os.path.join(root, file))
|
self._roomsDbHandle = None
|
||||||
roomName = truncateText(room.getName(), constants.MAX_ROOM_NAME_LENGTH)
|
|
||||||
if len(room.getPlaylist()) == 0 or room.isStale(self._timer):
|
def loadRooms(self, rooms):
|
||||||
os.remove(os.path.join(root, file))
|
roomsLoaded = []
|
||||||
del room
|
for roomDetails in rooms:
|
||||||
else:
|
roomName = truncateText(roomDetails[0], constants.MAX_ROOM_NAME_LENGTH)
|
||||||
self._rooms[roomName] = room
|
room = Room(roomDetails[0], self._roomsDbHandle)
|
||||||
|
room.loadRoom(roomDetails)
|
||||||
|
if roomName in self._permanentRooms:
|
||||||
|
room.setPermanent(True)
|
||||||
|
self._rooms[roomName] = room
|
||||||
|
roomsLoaded.append(roomName)
|
||||||
|
for roomName in self._permanentRooms:
|
||||||
|
if roomName not in roomsLoaded:
|
||||||
|
roomDetails = (roomName, "", 0, 0, 0)
|
||||||
|
room = Room(roomName, self._roomsDbHandle)
|
||||||
|
room.loadRoom(roomDetails)
|
||||||
|
room.setPermanent(True)
|
||||||
|
self._rooms[roomName] = room
|
||||||
|
|
||||||
def broadcastRoom(self, sender, whatLambda):
|
def broadcastRoom(self, sender, whatLambda):
|
||||||
room = sender.getRoom()
|
room = sender.getRoom()
|
||||||
@ -349,6 +441,20 @@ class RoomManager(object):
|
|||||||
watchers.append(watcher)
|
watchers.append(watcher)
|
||||||
return watchers
|
return watchers
|
||||||
|
|
||||||
|
def getPersistentRooms(self, sender):
|
||||||
|
persistentRooms = []
|
||||||
|
for room in self._rooms.values():
|
||||||
|
if room.isPersistent():
|
||||||
|
persistentRooms.append(room.getName())
|
||||||
|
return persistentRooms
|
||||||
|
|
||||||
|
def getEmptyPersistentRooms(self):
|
||||||
|
emptyPersistentRooms = []
|
||||||
|
for room in self._rooms.values():
|
||||||
|
if len(room.getWatchers()) == 0:
|
||||||
|
emptyPersistentRooms.append(room.getName())
|
||||||
|
return emptyPersistentRooms
|
||||||
|
|
||||||
def moveWatcher(self, watcher, roomName):
|
def moveWatcher(self, watcher, roomName):
|
||||||
roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH)
|
roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH)
|
||||||
self.removeWatcher(watcher)
|
self.removeWatcher(watcher)
|
||||||
@ -367,16 +473,20 @@ class RoomManager(object):
|
|||||||
return self._rooms[roomName]
|
return self._rooms[roomName]
|
||||||
else:
|
else:
|
||||||
if RoomPasswordProvider.isControlledRoom(roomName):
|
if RoomPasswordProvider.isControlledRoom(roomName):
|
||||||
room = ControlledRoom(roomName)
|
room = ControlledRoom(roomName, self._roomsDbHandle)
|
||||||
else:
|
else:
|
||||||
if roomName in self._rooms:
|
if roomName in self._rooms:
|
||||||
self._deleteRoomIfEmpty(self._rooms[roomName])
|
self._deleteRoomIfEmpty(self._rooms[roomName])
|
||||||
room = Room(roomName, self._roomsDir)
|
room = Room(roomName, self._roomsDbHandle)
|
||||||
self._rooms[roomName] = room
|
self._rooms[roomName] = room
|
||||||
return room
|
return room
|
||||||
|
|
||||||
def _deleteRoomIfEmpty(self, room):
|
def _deleteRoomIfEmpty(self, room):
|
||||||
if room.isEmpty() and room.getName() in self._rooms:
|
if room.isEmpty() and room.getName():
|
||||||
|
if self._roomsDbHandle and room.isNotPermanent():
|
||||||
|
if room.isPersistent() and not room.isPlaylistEmpty():
|
||||||
|
return
|
||||||
|
self._roomsDbHandle.deleteRoom(room.getName())
|
||||||
del self._rooms[room.getName()]
|
del self._rooms[room.getName()]
|
||||||
|
|
||||||
def findFreeUsername(self, username):
|
def findFreeUsername(self, username):
|
||||||
@ -412,9 +522,9 @@ class Room(object):
|
|||||||
STATE_PAUSED = 0
|
STATE_PAUSED = 0
|
||||||
STATE_PLAYING = 1
|
STATE_PLAYING = 1
|
||||||
|
|
||||||
def __init__(self, name, _roomsDir=None):
|
def __init__(self, name, roomsdbhandle):
|
||||||
self._name = name
|
self._name = name
|
||||||
self._roomsDir = _roomsDir
|
self._roomsDbHandle = roomsdbhandle
|
||||||
self._watchers = {}
|
self._watchers = {}
|
||||||
self._playState = self.STATE_PAUSED
|
self._playState = self.STATE_PAUSED
|
||||||
self._setBy = None
|
self._setBy = None
|
||||||
@ -423,51 +533,46 @@ class Room(object):
|
|||||||
self._lastUpdate = time.time()
|
self._lastUpdate = time.time()
|
||||||
self._lastSavedUpdate = 0
|
self._lastSavedUpdate = 0
|
||||||
self._position = 0
|
self._position = 0
|
||||||
|
self._permanent = False
|
||||||
|
|
||||||
def __str__(self, *args, **kwargs):
|
def __str__(self, *args, **kwargs):
|
||||||
return self.getName()
|
return self.getName()
|
||||||
|
|
||||||
def roomsCanPersist(self):
|
def roomsCanPersist(self):
|
||||||
return self._roomsDir is not None
|
return self._roomsDbHandle is not None
|
||||||
|
|
||||||
|
def isPersistent(self):
|
||||||
|
return self.roomsCanPersist() and not self.isMarkedAsTemporary()
|
||||||
|
|
||||||
|
def isMarkedAsTemporary(self):
|
||||||
|
roomName = self.getName().lower()
|
||||||
|
return roomName.endswith("-temp") or "-temp:" in roomName
|
||||||
|
|
||||||
|
def isPlaylistEmpty(self):
|
||||||
|
return len(self._playlist) == 0
|
||||||
|
|
||||||
def isPermanent(self):
|
def isPermanent(self):
|
||||||
return self.roomsCanPersist()
|
return self._permanent
|
||||||
|
|
||||||
|
def isNotPermanent(self):
|
||||||
|
return not self.isPermanent()
|
||||||
|
|
||||||
def sanitizeFilename(self, filename, blacklist="<>:/\\|?*\"", placeholder="_"):
|
def sanitizeFilename(self, filename, blacklist="<>:/\\|?*\"", placeholder="_"):
|
||||||
return ''.join([c if c not in blacklist and ord(c) >= 32 else placeholder for c in filename])
|
return ''.join([c if c not in blacklist and ord(c) >= 32 else placeholder for c in filename])
|
||||||
|
|
||||||
def writeToFile(self):
|
def writeToDb(self):
|
||||||
if not self.isPermanent():
|
if not self.isPersistent():
|
||||||
return
|
return
|
||||||
filename = os.path.join(self._roomsDir, self.sanitizeFilename(self._name)+'.room')
|
processed_playlist = getListAsMultilineString(self._playlist)
|
||||||
if len(self._playlist) == 0:
|
self._roomsDbHandle.saveRoom(self._name, processed_playlist, self._playlistIndex, self._position, self._lastSavedUpdate)
|
||||||
try:
|
|
||||||
os.remove(filename)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return
|
|
||||||
data = {}
|
|
||||||
data['name'] = self._name
|
|
||||||
data['playlist'] = self._playlist
|
|
||||||
data['playlistIndex'] = self._playlistIndex
|
|
||||||
data['position'] = self._position
|
|
||||||
data['lastSavedUpdate'] = self._lastSavedUpdate
|
|
||||||
with open(filename, "w") as outfile:
|
|
||||||
json.dump(data, outfile)
|
|
||||||
|
|
||||||
def loadFromFile(self, filename):
|
def loadRoom(self, room):
|
||||||
with open(filename) as json_file:
|
name, playlist, playlistindex, position, lastupdate = room
|
||||||
data = json.load(json_file)
|
self._name = name
|
||||||
self._name = truncateText(data['name'], constants.MAX_ROOM_NAME_LENGTH)
|
self._playlist = convertMultilineStringToList(playlist)
|
||||||
self._playlist = data['playlist']
|
self._playlistIndex = playlistindex
|
||||||
self._playlistIndex = data['playlistIndex']
|
self._position = position
|
||||||
self._position = data['position']
|
self._lastSavedUpdate = lastupdate
|
||||||
self._lastSavedUpdate = data['lastSavedUpdate']
|
|
||||||
|
|
||||||
def isStale(self, timer):
|
|
||||||
if timer == 0 or self._lastSavedUpdate == 0:
|
|
||||||
return False
|
|
||||||
return time.time() - self._lastSavedUpdate > timer
|
|
||||||
|
|
||||||
def getName(self):
|
def getName(self):
|
||||||
return self._name
|
return self._name
|
||||||
@ -488,14 +593,17 @@ class Room(object):
|
|||||||
def setPaused(self, paused=STATE_PAUSED, setBy=None):
|
def setPaused(self, paused=STATE_PAUSED, setBy=None):
|
||||||
self._playState = paused
|
self._playState = paused
|
||||||
self._setBy = setBy
|
self._setBy = setBy
|
||||||
self.writeToFile()
|
self.writeToDb()
|
||||||
|
|
||||||
def setPosition(self, position, setBy=None):
|
def setPosition(self, position, setBy=None):
|
||||||
self._position = position
|
self._position = position
|
||||||
for watcher in self._watchers.values():
|
for watcher in self._watchers.values():
|
||||||
watcher.setPosition(position)
|
watcher.setPosition(position)
|
||||||
self._setBy = setBy
|
self._setBy = setBy
|
||||||
self.writeToFile()
|
self.writeToDb()
|
||||||
|
|
||||||
|
def setPermanent(self, newState):
|
||||||
|
self._permanent = newState
|
||||||
|
|
||||||
def isPlaying(self):
|
def isPlaying(self):
|
||||||
return self._playState == self.STATE_PLAYING
|
return self._playState == self.STATE_PLAYING
|
||||||
@ -507,7 +615,7 @@ class Room(object):
|
|||||||
return list(self._watchers.values())
|
return list(self._watchers.values())
|
||||||
|
|
||||||
def addWatcher(self, watcher):
|
def addWatcher(self, watcher):
|
||||||
if self._watchers or self.isPermanent():
|
if self._watchers or self.isPersistent():
|
||||||
watcher.setPosition(self.getPosition())
|
watcher.setPosition(self.getPosition())
|
||||||
self._watchers[watcher.getName()] = watcher
|
self._watchers[watcher.getName()] = watcher
|
||||||
watcher.setRoom(self)
|
watcher.setRoom(self)
|
||||||
@ -517,9 +625,9 @@ class Room(object):
|
|||||||
return
|
return
|
||||||
del self._watchers[watcher.getName()]
|
del self._watchers[watcher.getName()]
|
||||||
watcher.setRoom(None)
|
watcher.setRoom(None)
|
||||||
if not self._watchers and not self.isPermanent():
|
if not self._watchers and not self.isPersistent():
|
||||||
self._position = 0
|
self._position = 0
|
||||||
self.writeToFile()
|
self.writeToDb()
|
||||||
|
|
||||||
def isEmpty(self):
|
def isEmpty(self):
|
||||||
return not bool(self._watchers)
|
return not bool(self._watchers)
|
||||||
@ -532,11 +640,11 @@ class Room(object):
|
|||||||
|
|
||||||
def setPlaylist(self, files, setBy=None):
|
def setPlaylist(self, files, setBy=None):
|
||||||
self._playlist = files
|
self._playlist = files
|
||||||
self.writeToFile()
|
self.writeToDb()
|
||||||
|
|
||||||
def setPlaylistIndex(self, index, setBy=None):
|
def setPlaylistIndex(self, index, setBy=None):
|
||||||
self._playlistIndex = index
|
self._playlistIndex = index
|
||||||
self.writeToFile()
|
self.writeToDb()
|
||||||
|
|
||||||
def getPlaylist(self):
|
def getPlaylist(self):
|
||||||
return self._playlist
|
return self._playlist
|
||||||
@ -544,10 +652,12 @@ class Room(object):
|
|||||||
def getPlaylistIndex(self):
|
def getPlaylistIndex(self):
|
||||||
return self._playlistIndex
|
return self._playlistIndex
|
||||||
|
|
||||||
|
def getControllers(self):
|
||||||
|
return []
|
||||||
|
|
||||||
class ControlledRoom(Room):
|
class ControlledRoom(Room):
|
||||||
def __init__(self, name):
|
def __init__(self, name, roomsdbhandle):
|
||||||
Room.__init__(self, name)
|
Room.__init__(self, name, roomsdbhandle)
|
||||||
self._controllers = {}
|
self._controllers = {}
|
||||||
|
|
||||||
def getPosition(self):
|
def getPosition(self):
|
||||||
@ -570,6 +680,7 @@ class ControlledRoom(Room):
|
|||||||
Room.removeWatcher(self, watcher)
|
Room.removeWatcher(self, watcher)
|
||||||
if watcher.getName() in self._controllers:
|
if watcher.getName() in self._controllers:
|
||||||
del self._controllers[watcher.getName()]
|
del self._controllers[watcher.getName()]
|
||||||
|
self.writeToDb()
|
||||||
|
|
||||||
def setPaused(self, paused=Room.STATE_PAUSED, setBy=None):
|
def setPaused(self, paused=Room.STATE_PAUSED, setBy=None):
|
||||||
if self.canControl(setBy):
|
if self.canControl(setBy):
|
||||||
@ -591,7 +702,7 @@ class ControlledRoom(Room):
|
|||||||
return watcher.getName() in self._controllers
|
return watcher.getName() in self._controllers
|
||||||
|
|
||||||
def getControllers(self):
|
def getControllers(self):
|
||||||
return self._controllers
|
return {}
|
||||||
|
|
||||||
|
|
||||||
class Watcher(object):
|
class Watcher(object):
|
||||||
@ -671,6 +782,23 @@ class Watcher(object):
|
|||||||
if self._connector.meetsMinVersion(constants.CHAT_MIN_VERSION):
|
if self._connector.meetsMinVersion(constants.CHAT_MIN_VERSION):
|
||||||
self._connector.sendMessage({"Chat": message})
|
self._connector.sendMessage({"Chat": message})
|
||||||
|
|
||||||
|
def sendList(self, toGUIOnly=False):
|
||||||
|
if toGUIOnly and self.isGUIUser(self._connector.getFeatures()):
|
||||||
|
clientFeatures = self._connector.getFeatures()
|
||||||
|
if "uiMode" in clientFeatures:
|
||||||
|
if clientFeatures["uiMode"] == constants.CONSOLE_UI_MODE:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
self._connector.sendList()
|
||||||
|
|
||||||
|
def isGUIUser(self, clientFeatures):
|
||||||
|
clientFeatures = self._connector.getFeatures()
|
||||||
|
uiMode = clientFeatures["uiMode"] if "uiMode" in clientFeatures else constants.UNKNOWN_UI_MODE
|
||||||
|
if uiMode == constants.UNKNOWN_UI_MODE:
|
||||||
|
uiMode = constants.FALLBACK_ASSUMED_UI_MODE
|
||||||
|
return uiMode == constants.GRAPHICAL_UI_MODE
|
||||||
|
|
||||||
def sendSetReady(self, username, isReady, manuallyInitiated=True):
|
def sendSetReady(self, username, isReady, manuallyInitiated=True):
|
||||||
self._connector.sendSetReady(username, isReady, manuallyInitiated)
|
self._connector.sendSetReady(username, isReady, manuallyInitiated)
|
||||||
|
|
||||||
@ -756,8 +884,8 @@ class ConfigurationGetter(object):
|
|||||||
self._argparser.add_argument('--disable-chat', action='store_true', help=getMessage("server-chat-argument"))
|
self._argparser.add_argument('--disable-chat', action='store_true', help=getMessage("server-chat-argument"))
|
||||||
self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument"), default=os.environ.get('SYNCPLAY_SALT'))
|
self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument"), default=os.environ.get('SYNCPLAY_SALT'))
|
||||||
self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument"))
|
self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument"))
|
||||||
self._argparser.add_argument('--rooms-dir', metavar='rooms', type=str, nargs='?', help=getMessage("server-rooms-argument"))
|
self._argparser.add_argument('--rooms-db-file', metavar='rooms', type=str, nargs='?', help=getMessage("server-rooms-argument"))
|
||||||
self._argparser.add_argument('--rooms-timer', metavar='timer', type=int, nargs='?',default=31558149, help=getMessage("server-timer-argument"))
|
self._argparser.add_argument('--permanent-rooms-file', metavar='permanentrooms', type=str, nargs='?', help=getMessage("server-permanent-rooms-argument"))
|
||||||
self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH))
|
self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH))
|
||||||
self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH))
|
self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH))
|
||||||
self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument"))
|
self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument"))
|
||||||
|
|||||||
@ -18,6 +18,7 @@ class ConsoleUI(threading.Thread):
|
|||||||
self.PromptResult = ""
|
self.PromptResult = ""
|
||||||
self.promptMode.set()
|
self.promptMode.set()
|
||||||
self._syncplayClient = None
|
self._syncplayClient = None
|
||||||
|
self.uiMode = constants.CONSOLE_UI_MODE
|
||||||
threading.Thread.__init__(self, name="ConsoleUI")
|
threading.Thread.__init__(self, name="ConsoleUI")
|
||||||
|
|
||||||
def addClient(self, client):
|
def addClient(self, client):
|
||||||
|
|||||||
@ -468,6 +468,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.roomsCombobox.clear()
|
self.roomsCombobox.clear()
|
||||||
for roomListValue in self.config['roomList']:
|
for roomListValue in self.config['roomList']:
|
||||||
self.roomsCombobox.addItem(roomListValue)
|
self.roomsCombobox.addItem(roomListValue)
|
||||||
|
for room in self.currentRooms:
|
||||||
|
if room not in self.config['roomList']:
|
||||||
|
self.roomsCombobox.addItem(room)
|
||||||
self.roomsCombobox.setEditText(previousRoomSelection)
|
self.roomsCombobox.setEditText(previousRoomSelection)
|
||||||
|
|
||||||
def addRoomToList(self, newRoom=None):
|
def addRoomToList(self, newRoom=None):
|
||||||
@ -603,7 +606,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
):
|
):
|
||||||
self._syncplayClient.fileSwitch.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]))
|
self._syncplayClient.fileSwitch.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]))
|
||||||
|
|
||||||
|
self.currentRooms = []
|
||||||
for room in rooms:
|
for room in rooms:
|
||||||
|
self.currentRooms.append(room)
|
||||||
|
if self.hideEmptyRooms:
|
||||||
|
foundEmptyRooms = False
|
||||||
|
for user in rooms[room]:
|
||||||
|
if user.username.strip() == "":
|
||||||
|
foundEmptyRooms = True
|
||||||
|
if foundEmptyRooms:
|
||||||
|
continue
|
||||||
self.newWatchlist = []
|
self.newWatchlist = []
|
||||||
roomitem = QtGui.QStandardItem(room)
|
roomitem = QtGui.QStandardItem(room)
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
@ -624,6 +636,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
roomitem.setIcon(QtGui.QPixmap(resourcespath + 'chevrons_right.png'))
|
roomitem.setIcon(QtGui.QPixmap(resourcespath + 'chevrons_right.png'))
|
||||||
|
|
||||||
for user in rooms[room]:
|
for user in rooms[room]:
|
||||||
|
if user.username.strip() == "":
|
||||||
|
continue
|
||||||
useritem = QtGui.QStandardItem(user.username)
|
useritem = QtGui.QStandardItem(user.username)
|
||||||
isController = user.isController()
|
isController = user.isController()
|
||||||
sameRoom = room == currentUser.room
|
sameRoom = room == currentUser.room
|
||||||
@ -695,6 +709,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.listTreeView.expandAll()
|
self.listTreeView.expandAll()
|
||||||
self.updateListGeometry()
|
self.updateListGeometry()
|
||||||
self._syncplayClient.fileSwitch.setFilenameWatchlist(self.newWatchlist)
|
self._syncplayClient.fileSwitch.setFilenameWatchlist(self.newWatchlist)
|
||||||
|
self.fillRoomsCombobox()
|
||||||
|
|
||||||
@needsClient
|
@needsClient
|
||||||
def undoPlaylistChange(self):
|
def undoPlaylistChange(self):
|
||||||
@ -1753,6 +1768,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
window.autoplayAction.setCheckable(True)
|
window.autoplayAction.setCheckable(True)
|
||||||
window.autoplayAction.triggered.connect(self.updateAutoplayVisibility)
|
window.autoplayAction.triggered.connect(self.updateAutoplayVisibility)
|
||||||
|
|
||||||
|
window.hideEmptyRoomsAction = window.windowMenu.addAction(getMessage("hideemptyrooms-menu-label"))
|
||||||
|
window.hideEmptyRoomsAction.setCheckable(True)
|
||||||
|
window.hideEmptyRoomsAction.triggered.connect(self.updateEmptyRoomVisiblity)
|
||||||
|
|
||||||
# Help menu
|
# Help menu
|
||||||
|
|
||||||
@ -1818,6 +1836,11 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
def updateAutoplayVisibility(self):
|
def updateAutoplayVisibility(self):
|
||||||
self.autoplayFrame.setVisible(self.autoplayAction.isChecked())
|
self.autoplayFrame.setVisible(self.autoplayAction.isChecked())
|
||||||
|
|
||||||
|
def updateEmptyRoomVisiblity(self):
|
||||||
|
self.hideEmptyRooms = self.hideEmptyRoomsAction.isChecked()
|
||||||
|
if self._syncplayClient:
|
||||||
|
self._syncplayClient.getUserList()
|
||||||
|
|
||||||
def changeReadyState(self):
|
def changeReadyState(self):
|
||||||
self.updateReadyIcon()
|
self.updateReadyIcon()
|
||||||
if self._syncplayClient:
|
if self._syncplayClient:
|
||||||
@ -2017,6 +2040,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
settings.setValue("pos", self.pos())
|
settings.setValue("pos", self.pos())
|
||||||
settings.setValue("showPlaybackButtons", self.playbackAction.isChecked())
|
settings.setValue("showPlaybackButtons", self.playbackAction.isChecked())
|
||||||
settings.setValue("showAutoPlayButton", self.autoplayAction.isChecked())
|
settings.setValue("showAutoPlayButton", self.autoplayAction.isChecked())
|
||||||
|
settings.setValue("hideEmptyRooms", self.hideEmptyRoomsAction.isChecked())
|
||||||
settings.setValue("autoplayChecked", self.autoplayPushButton.isChecked())
|
settings.setValue("autoplayChecked", self.autoplayPushButton.isChecked())
|
||||||
settings.setValue("autoplayMinUsers", self.autoplayThresholdSpinbox.value())
|
settings.setValue("autoplayMinUsers", self.autoplayThresholdSpinbox.value())
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
@ -2040,6 +2064,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
if settings.value("showAutoPlayButton", "false") == "true":
|
if settings.value("showAutoPlayButton", "false") == "true":
|
||||||
self.autoplayAction.setChecked(True)
|
self.autoplayAction.setChecked(True)
|
||||||
self.updateAutoplayVisibility()
|
self.updateAutoplayVisibility()
|
||||||
|
if settings.value("hideEmptyRooms", "false") == "true":
|
||||||
|
self.hideEmptyRooms = True
|
||||||
|
self.hideEmptyRoomsAction.setChecked(True)
|
||||||
if settings.value("autoplayChecked", "false") == "true":
|
if settings.value("autoplayChecked", "false") == "true":
|
||||||
self.updateAutoPlayState(True)
|
self.updateAutoPlayState(True)
|
||||||
self.autoplayPushButton.setChecked(True)
|
self.autoplayPushButton.setChecked(True)
|
||||||
@ -2063,6 +2090,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.lastCheckedForUpdates = None
|
self.lastCheckedForUpdates = None
|
||||||
self._syncplayClient = None
|
self._syncplayClient = None
|
||||||
self.folderSearchEnabled = True
|
self.folderSearchEnabled = True
|
||||||
|
self.hideEmptyRooms = False
|
||||||
|
self.currentRooms = []
|
||||||
self.QtGui = QtGui
|
self.QtGui = QtGui
|
||||||
if isMacOS():
|
if isMacOS():
|
||||||
self.setWindowFlags(self.windowFlags())
|
self.setWindowFlags(self.windowFlags())
|
||||||
@ -2081,3 +2110,4 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.show()
|
self.show()
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.clearedPlaylistNote = False
|
self.clearedPlaylistNote = False
|
||||||
|
self.uiMode = constants.GRAPHICAL_UI_MODE
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user