Use sqlite for persistent/permanent rooms (#434)
This commit is contained in:
parent
f044e2de10
commit
4329d22334
@ -78,12 +78,12 @@ 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]
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -474,8 +474,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 {})",
|
||||||
|
|||||||
@ -475,8 +475,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 {})",
|
||||||
|
|||||||
@ -474,8 +474,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 {})",
|
||||||
|
|||||||
@ -474,8 +474,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 è {})",
|
||||||
|
|||||||
@ -475,8 +475,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 é {})",
|
||||||
|
|||||||
@ -474,8 +474,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 é {})",
|
||||||
|
|||||||
@ -471,8 +471,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": "Максимальное число символов в именах пользователей (по умолчанию {})",
|
||||||
|
|||||||
@ -475,8 +475,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 {})",
|
||||||
|
|||||||
@ -617,11 +617,27 @@ 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)
|
||||||
|
for emptyRoom in self._factory.getEmptyPersistentRooms():
|
||||||
|
dummyCount += 1
|
||||||
|
self._addDummyUserOnList(userlist, emptyRoom, dummyCount)
|
||||||
self.sendMessage({"List": userlist})
|
self.sendMessage({"List": userlist})
|
||||||
|
|
||||||
@requireLogged
|
@requireLogged
|
||||||
|
|||||||
@ -22,11 +22,11 @@ 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
|
||||||
@ -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 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)
|
||||||
|
|
||||||
@ -172,6 +183,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 +304,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 +344,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 +431,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)
|
||||||
@ -359,11 +455,10 @@ class RoomManager(object):
|
|||||||
oldRoom = watcher.getRoom()
|
oldRoom = watcher.getRoom()
|
||||||
if oldRoom:
|
if oldRoom:
|
||||||
oldRoom.removeWatcher(watcher)
|
oldRoom.removeWatcher(watcher)
|
||||||
if self._roomsDir is None or oldRoom.isStale(self._timer):
|
self._deleteRoomIfEmpty(oldRoom)
|
||||||
self._deleteRoomIfEmpty(oldRoom)
|
|
||||||
|
|
||||||
def _getRoom(self, roomName):
|
def _getRoom(self, roomName):
|
||||||
if roomName in self._rooms and not self._rooms[roomName].isStale(self._timer):
|
if roomName in self._rooms:
|
||||||
return self._rooms[roomName]
|
return self._rooms[roomName]
|
||||||
else:
|
else:
|
||||||
if RoomPasswordProvider.isControlledRoom(roomName):
|
if RoomPasswordProvider.isControlledRoom(roomName):
|
||||||
@ -371,12 +466,17 @@ class RoomManager(object):
|
|||||||
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.isNotPermanent() and room.getName() in self._rooms:
|
||||||
|
if room.isPersistent():
|
||||||
|
if room.isPlaylistEmpty():
|
||||||
|
self._roomsDbHandle.deleteRoom(room.getName())
|
||||||
|
else:
|
||||||
|
return()
|
||||||
del self._rooms[room.getName()]
|
del self._rooms[room.getName()]
|
||||||
|
|
||||||
def findFreeUsername(self, username):
|
def findFreeUsername(self, username):
|
||||||
@ -412,9 +512,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 +523,42 @@ 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()
|
||||||
|
|
||||||
|
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 +579,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 +601,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 +611,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 +626,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
|
||||||
@ -756,8 +850,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"))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user