diff --git a/docs/syncplay-server.1 b/docs/syncplay-server.1 index e9b91e8..488206d 100644 --- a/docs/syncplay-server.1 +++ b/docs/syncplay-server.1 @@ -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. .TP -.B \-\-rooms\-dir [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. +.B \-\-rooms\-db-file [directory] +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 -.B \-\-rooms\-timer [directory] -Requires \fB\-\-rooms\-timer\fP. Time in seconds that rooms will persist without users. \fB0\fP disables the timer, meaning rooms persist permanently. +.B \-\-permanent\-rooms-file [directory] +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 .B \-\-max\-chat\-message\-length [maxChatMessageLength] diff --git a/syncplay/ep_server.py b/syncplay/ep_server.py index d2e4aef..548ddd0 100644 --- a/syncplay/ep_server.py +++ b/syncplay/ep_server.py @@ -34,8 +34,8 @@ def main(): args.port, args.password, args.motd_file, - args.rooms_dir, - args.rooms_timer, + args.rooms_db_file, + args.permanent_rooms_file, args.isolate_rooms, args.salt, args.disable_ready, diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 595ea50..a7ed41d 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -474,8 +474,8 @@ de = { "server-salt-argument": "zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird", "server-disable-ready-argument": "Bereitschaftsfeature deaktivieren", "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-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # 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-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-maxchars-argument": "Maximale Zeichenzahl in einer Chatnachricht (Standard ist {})", "server-maxusernamelength-argument": "Maximale Zeichenzahl in einem Benutzernamen (Standard ist {})", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index ac7b25b..d71ed7d 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -475,8 +475,8 @@ en = { "server-salt-argument": "random string used to generate managed room passwords", "server-disable-ready-argument": "disable readiness feature", "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-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", + "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-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-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 {})", diff --git a/syncplay/messages_es.py b/syncplay/messages_es.py index cf4cd57..9f2aeb2 100644 --- a/syncplay/messages_es.py +++ b/syncplay/messages_es.py @@ -474,8 +474,8 @@ es = { "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-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-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # 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-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-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 {})", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 44dd2a4..3ffc168 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -474,8 +474,8 @@ it = { "server-salt-argument": "usare stringhe casuali per generare le password delle stanze gestite", "server-disable-ready-argument": "disabilita la funzionalità \"pronto\"", "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-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # 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-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-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 è {})", diff --git a/syncplay/messages_pt_BR.py b/syncplay/messages_pt_BR.py index 49407d5..a3ea771 100644 --- a/syncplay/messages_pt_BR.py +++ b/syncplay/messages_pt_BR.py @@ -475,8 +475,8 @@ pt_BR = { "server-salt-argument": "string aleatória utilizada para gerar senhas de salas gerenciadas", "server-disable-ready-argument": "desativar recurso de prontidão", "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-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # 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-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-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 é {})", diff --git a/syncplay/messages_pt_PT.py b/syncplay/messages_pt_PT.py index 4cdbedb..05f899e 100644 --- a/syncplay/messages_pt_PT.py +++ b/syncplay/messages_pt_PT.py @@ -474,8 +474,8 @@ pt_PT = { "server-salt-argument": "string aleatória utilizada para gerar senhas de salas gerenciadas", "server-disable-ready-argument": "desativar recurso de prontidão", "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-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # 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-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-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 é {})", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index b9a3e4a..5ba4d6c 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -471,8 +471,8 @@ ru = { "server-salt-argument": "генерировать пароли к управляемым комнатам на основании указанной строки (соли)", "server-disable-ready-argument": "отключить статусы готов/не готов", "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-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # 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-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-maxchars-argument": "Максимальное число символов в сообщениях в чате (по умолчанию {})", "server-maxusernamelength-argument": "Максимальное число символов в именах пользователей (по умолчанию {})", diff --git a/syncplay/messages_tr.py b/syncplay/messages_tr.py index ad602b4..b0767bd 100644 --- a/syncplay/messages_tr.py +++ b/syncplay/messages_tr.py @@ -475,8 +475,8 @@ tr = { "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-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-timer-argument": "time in seconds before a persistent room with no watchers is pruned. 0 disables pruning", # 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-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-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 {})", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 7e780a5..c4245ae 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -617,11 +617,27 @@ class SyncServerProtocol(JSONCommandProtocol): } 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): userlist = {} watchers = self._factory.getAllWatchersForUser(self._watcher) + dummyCount = 0 for watcher in watchers: self._addUserOnList(userlist, watcher) + for emptyRoom in self._factory.getEmptyPersistentRooms(): + dummyCount += 1 + self._addDummyUserOnList(userlist, emptyRoom, dummyCount) self.sendMessage({"List": userlist}) @requireLogged diff --git a/syncplay/server.py b/syncplay/server.py index 58b7c8d..de9615f 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -22,11 +22,11 @@ import syncplay from syncplay import constants from syncplay.messages import getMessage 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): - 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, maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCertPath=None): self.isolateRooms = isolateRooms @@ -41,18 +41,19 @@ class SyncFactory(Factory): print(getMessage("no-salt-notification").format(salt)) self._salt = salt self._motdFilePath = motdFilePath - self._roomsDirPath = roomsDirPath if roomsDirPath is not None and os.path.isdir(roomsDirPath) else None - self._roomsTimer = roomsTimer if roomsDirPath is not None and isinstance(roomsTimer, int) and roomsTimer > 0 else 0 + self.roomsDbFile = roomsDbFile self.disableReady = disableReady self.disableChat = disableChat 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.permanentRoomsFile = permanentRoomsFile if permanentRoomsFile is not None and os.path.isfile(permanentRoomsFile) else None + self.permanentRooms = self.loadListFromMultilineTextFile(self.permanentRoomsFile) if not isolateRooms: - self._roomManager = RoomManager(self._roomsDirPath, self._roomsTimer) + self._roomManager = RoomManager(self.roomsDbFile, self.permanentRooms) else: self._roomManager = PublicRoomManager() if statsDbFile is not None: - self._statsDbHandle = DBManager(statsDbFile) + self._statsDbHandle = StatsDBManager(statsDbFile) self._statsRecorder = StatsRecorder(self._statsDbHandle, self._roomManager) statsDelay = 5*(int(self.port)%10 + 1) self._statsRecorder.startRecorder(statsDelay) @@ -67,6 +68,16 @@ class SyncFactory(Factory): self.options = None 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): return SyncServerProtocol(self) @@ -172,6 +183,9 @@ class SyncFactory(Factory): def getAllWatchersForUser(self, forUser): return self._roomManager.getAllWatchersForUser(forUser) + def getEmptyPersistentRooms(self): + return self._roomManager.getEmptyPersistentRooms() + def authRoomController(self, watcher, password, roomBaseName=None): room = watcher.getRoom() roomName = roomBaseName if roomBaseName else room.getName() @@ -290,8 +304,33 @@ class StatsRecorder(object): except: 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): self._dbPath = dbpath self._connection = None @@ -305,31 +344,74 @@ class DBManager(object): self._createSchema() def _createSchema(self): - initQuery = 'create table if not exists clients_snapshots (snapshot_time integer, version string)' - self._connection.runQuery(initQuery) + initQuery = 'create table if not exists clients_snapshots (snapshot_time INTEGER, version STRING)' + return self._connection.runQuery(initQuery) def addVersionLog(self, timestamp, version): content = (timestamp, version, ) 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): - def __init__(self, roomsDir=None, timer=0): - self._roomsDir = roomsDir - self._timer = timer + def __init__(self, roomsdbfile=None, permanentRooms=[]): + self._roomsDbFile = roomsdbfile self._rooms = {} - if self._roomsDir is not None: - for root, dirs, files in os.walk(self._roomsDir): - for file in files: - if file.endswith(".room"): - room = Room('', self._roomsDir) - room.loadFromFile(os.path.join(root, file)) - roomName = truncateText(room.getName(), constants.MAX_ROOM_NAME_LENGTH) - if len(room.getPlaylist()) == 0 or room.isStale(self._timer): - os.remove(os.path.join(root, file)) - del room - else: - self._rooms[roomName] = room + self._permanentRooms = permanentRooms + if self._roomsDbFile is not None: + self._roomsDbHandle = RoomDBManager(self._roomsDbFile, self.loadRooms) + self._roomsDbHandle.connect() + else: + self._roomsDbHandle = None + + def loadRooms(self, rooms): + roomsLoaded = [] + for roomDetails in rooms: + roomName = truncateText(roomDetails[0], constants.MAX_ROOM_NAME_LENGTH) + 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): room = sender.getRoom() @@ -349,6 +431,20 @@ class RoomManager(object): watchers.append(watcher) 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): roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH) self.removeWatcher(watcher) @@ -359,11 +455,10 @@ class RoomManager(object): oldRoom = watcher.getRoom() if oldRoom: oldRoom.removeWatcher(watcher) - if self._roomsDir is None or oldRoom.isStale(self._timer): - self._deleteRoomIfEmpty(oldRoom) + self._deleteRoomIfEmpty(oldRoom) 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] else: if RoomPasswordProvider.isControlledRoom(roomName): @@ -371,12 +466,17 @@ class RoomManager(object): else: if roomName in self._rooms: self._deleteRoomIfEmpty(self._rooms[roomName]) - room = Room(roomName, self._roomsDir) + room = Room(roomName, self._roomsDbHandle) self._rooms[roomName] = room return 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()] def findFreeUsername(self, username): @@ -412,9 +512,9 @@ class Room(object): STATE_PAUSED = 0 STATE_PLAYING = 1 - def __init__(self, name, _roomsDir=None): + def __init__(self, name, roomsdbhandle): self._name = name - self._roomsDir = _roomsDir + self._roomsDbHandle = roomsdbhandle self._watchers = {} self._playState = self.STATE_PAUSED self._setBy = None @@ -423,51 +523,42 @@ class Room(object): self._lastUpdate = time.time() self._lastSavedUpdate = 0 self._position = 0 + self._permanent = False def __str__(self, *args, **kwargs): return self.getName() 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): - return self.roomsCanPersist() + return self._permanent + + def isNotPermanent(self): + return not self.isPermanent() def sanitizeFilename(self, filename, blacklist="<>:/\\|?*\"", placeholder="_"): return ''.join([c if c not in blacklist and ord(c) >= 32 else placeholder for c in filename]) - def writeToFile(self): - if not self.isPermanent(): + def writeToDb(self): + if not self.isPersistent(): return - filename = os.path.join(self._roomsDir, self.sanitizeFilename(self._name)+'.room') - if len(self._playlist) == 0: - 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) + processed_playlist = getListAsMultilineString(self._playlist) + self._roomsDbHandle.saveRoom(self._name, processed_playlist, self._playlistIndex, self._position, self._lastSavedUpdate) - def loadFromFile(self, filename): - with open(filename) as json_file: - data = json.load(json_file) - self._name = truncateText(data['name'], constants.MAX_ROOM_NAME_LENGTH) - self._playlist = data['playlist'] - self._playlistIndex = data['playlistIndex'] - self._position = data['position'] - self._lastSavedUpdate = data['lastSavedUpdate'] - - def isStale(self, timer): - if timer == 0 or self._lastSavedUpdate == 0: - return False - return time.time() - self._lastSavedUpdate > timer + def loadRoom(self, room): + name, playlist, playlistindex, position, lastupdate = room + self._name = name + self._playlist = convertMultilineStringToList(playlist) + self._playlistIndex = playlistindex + self._position = position + self._lastSavedUpdate = lastupdate def getName(self): return self._name @@ -488,14 +579,17 @@ class Room(object): def setPaused(self, paused=STATE_PAUSED, setBy=None): self._playState = paused self._setBy = setBy - self.writeToFile() + self.writeToDb() def setPosition(self, position, setBy=None): self._position = position for watcher in self._watchers.values(): watcher.setPosition(position) self._setBy = setBy - self.writeToFile() + self.writeToDb() + + def setPermanent(self, newState): + self._permanent = newState def isPlaying(self): return self._playState == self.STATE_PLAYING @@ -507,7 +601,7 @@ class Room(object): return list(self._watchers.values()) def addWatcher(self, watcher): - if self._watchers or self.isPermanent(): + if self._watchers or self.isPersistent(): watcher.setPosition(self.getPosition()) self._watchers[watcher.getName()] = watcher watcher.setRoom(self) @@ -517,9 +611,9 @@ class Room(object): return del self._watchers[watcher.getName()] watcher.setRoom(None) - if not self._watchers and not self.isPermanent(): + if not self._watchers and not self.isPersistent(): self._position = 0 - self.writeToFile() + self.writeToDb() def isEmpty(self): return not bool(self._watchers) @@ -532,11 +626,11 @@ class Room(object): def setPlaylist(self, files, setBy=None): self._playlist = files - self.writeToFile() + self.writeToDb() def setPlaylistIndex(self, index, setBy=None): self._playlistIndex = index - self.writeToFile() + self.writeToDb() def getPlaylist(self): 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('--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('--rooms-dir', 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('--rooms-db-file', metavar='rooms', type=str, nargs='?', help=getMessage("server-rooms-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-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"))