From a3324a80f3c911e6bd8593a9e946e495dae24504 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 3 Jul 2016 16:40:12 +0100 Subject: [PATCH 01/15] Merge in abhsag24's chat code --- syncplay/client.py | 6 +++++- syncplay/constants.py | 1 + syncplay/messages_de.py | 2 ++ syncplay/messages_en.py | 2 ++ syncplay/messages_ru.py | 2 ++ syncplay/protocols.py | 12 +++++++++++- syncplay/server.py | 11 ++++++++++- syncplay/ui/consoleUI.py | 4 ++++ syncplayServer.py | 3 +-- 9 files changed, 38 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 43f2064..a79e465 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -547,7 +547,11 @@ class SyncplayClient(object): self.userlist.currentUser.room = roomName if resetAutoplay: self.resetAutoPlayState() - + + def sendChat(self,message): + if self._protocol and self._protocol.logged: + self._protocol.sendChatMessage(message) + def sendRoom(self): room = self.userlist.currentUser.room if self._protocol and self._protocol.logged and room: diff --git a/syncplay/constants.py b/syncplay/constants.py index 135ebe4..d5cbb7d 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -63,6 +63,7 @@ FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]" CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]" ROOM_NAME_STRIP_REGEX = u"^(\+)(?P.*)(:)(\w{12})$" COMMANDS_UNDO = ["u", "undo", "revert"] +COMMANDS_CHAT = ["ch","chat"] COMMANDS_LIST = ["l", "list", "users"] COMMANDS_PAUSE = ["p", "play", "pause"] COMMANDS_ROOM = ["r", "room"] diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index af15c15..f8f560e 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -85,6 +85,7 @@ de = { "commandlist-notification/toggle" : u"\tt - Bereitschaftsanzeige umschalten", "commandlist-notification/create" : u"\tc [name] - erstelle zentral gesteuerten Raum mit dem aktuellen Raumnamen", "commandlist-notification/auth" : u"\ta [password] - authentifiziere als Raumleiter mit Passwort", + "commandlist-notification/chat" : "\tch [message] - send a chat message in a room", # TODO: Translate "syncplay-version-notification" : u"Syncplay Version: {}", # syncplay.version "more-info-notification" : u"Weitere Informationen auf: {}", # projectURL @@ -374,6 +375,7 @@ de = { "server-salt-argument" : u"zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird", "server-disable-ready-argument" : u"Bereitschaftsfeature deaktivieren", "server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird", + "server-chat-argument" : "Should chat be enabled?", # TODO: Translate "server-messed-up-motd-unescaped-placeholders": u"Die Nachricht des Tages hat unmaskierte Platzhalter. Alle $-Zeichen sollten verdoppelt werden ($$).", "server-messed-up-motd-too-long": u"Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index e3bd6dc..e31ffb7 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -85,6 +85,7 @@ en = { "commandlist-notification/toggle" : u"\tt - toggles whether you are ready to watch or not", "commandlist-notification/create" : "\tc [name] - create managed room using name of current room", "commandlist-notification/auth" : "\ta [password] - authenticate as room operator with operator password", + "commandlist-notification/chat" : "\tch [message] - send a chat message in a room", "syncplay-version-notification" : "Syncplay version: {}", # syncplay.version "more-info-notification" : "More info available at: {}", # projectURL @@ -375,6 +376,7 @@ en = { "server-salt-argument" : "random string used to generate managed room passwords", "server-disable-ready-argument" : u"disable readiness feature", "server-motd-argument": "path to file from which motd will be fetched", + "server-chat-argument" : "Should chat be enabled?", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-too-long": u"Message of the Day is too long - maximum of {} chars, {} given.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 6381543..94804e4 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -85,6 +85,7 @@ ru = { "commandlist-notification/toggle" : u"\tt - переключить статус готов/неготов к просмотру", "commandlist-notification/create" : u"\tc [name] - создать управляемую комнату с таким же именем, как у текущей", "commandlist-notification/auth" : u"\ta [password] - авторизоваться как оператор комнаты с помощью пароля", + "commandlist-notification/chat" : "\tch [message] - send a chat message in a room", # TODO: Translate "syncplay-version-notification" : u"Версия Syncplay: {}", # syncplay.version "more-info-notification" : u"Больше информации на {}", # projectURL @@ -374,6 +375,7 @@ ru = { "server-salt-argument" : u"генерировать пароли к управляемым комнатам на основании указанной строки (соли)", "server-disable-ready-argument" : u"отключить статусы готов/не готов", "server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение", + "server-chat-argument" : "Should chat be enabled?", # TODO: Translate "server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", "server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 2eeafe8..3c6c17e 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -22,6 +22,8 @@ class JSONCommandProtocol(LineReceiver): self.handleState(message[1]) elif command == "Error": self.handleError(message[1]) + elif command == "Chat": + self.handleChat(message[1]) else: self.dropWithError(getMessage("unknown-command-server-error").format(message[1])) # TODO: log, not drop @@ -157,7 +159,8 @@ class SyncClientProtocol(JSONCommandProtocol): def sendFileSetting(self, file_): self.sendSet({"file": file_}) self.sendList() - + def sendChatMessage(self,chatMessage): + self.sendMessage({"Chat": chatMessage}) def handleList(self, userList): self._client.userlist.clearList() for room in userList.iteritems(): @@ -242,6 +245,10 @@ class SyncClientProtocol(JSONCommandProtocol): "password": password } }) + def handleChat(self,message): + messageString = '<'+message['username']+'>'+message['message'] + self._client.ui.showMessage(messageString) + #TODO def setReady(self, isReady, manuallyInitiated=True): self.sendSet({ @@ -345,6 +352,9 @@ class SyncServerProtocol(JSONCommandProtocol): self._factory.addWatcher(self, username, roomName) self._logged = True self.sendHello(version) + def handleChat(self,chatMessage): + if self._factory.chat: + self._factory.sendChat(self._watcher,chatMessage) def setWatcher(self, watcher): self._watcher = watcher diff --git a/syncplay/server.py b/syncplay/server.py index c177338..397dd41 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -14,7 +14,7 @@ import argparse from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion class SyncFactory(Factory): - def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False): + def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False,chat =False): print getMessage("welcome-server-notification").format(syncplay.version) if password: password = hashlib.md5(password).hexdigest() @@ -25,6 +25,7 @@ class SyncFactory(Factory): self._salt = salt self._motdFilePath = motdFilePath self.disableReady = disableReady + self.chat=chat if not isolateRooms: self._roomManager = RoomManager() else: @@ -133,6 +134,10 @@ class SyncFactory(Factory): except ValueError: self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name)) + def sendChat(self,watcher,message): + messageDict={"message":message,"username" : watcher.getName()} + self._roomManager.broadcastRoom(watcher, lambda w: w.sendChatMessage(messageDict)) + def setReady(self, watcher, isReady, manuallyInitiated=True): watcher.setReady(isReady) self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), manuallyInitiated)) @@ -427,6 +432,9 @@ class Watcher(object): def sendControlledRoomAuthStatus(self, success, username, room): self._connector.sendControlledRoomAuthStatus(success, username, room) + def sendChatMessage(self,message): + self._connector.sendMessage({"Chat" : message}) + def sendSetReady(self, username, isReady, manuallyInitiated=True): self._connector.sendSetReady(username, isReady, manuallyInitiated) @@ -507,5 +515,6 @@ class ConfigurationGetter(object): self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument")) self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument")) self._argparser.add_argument('--disable-ready', action='store_true', help=getMessage("server-disable-ready-argument")) + self._argparser.add_argument('--chat', action='store_true', help=getMessage("server-chat-argument")) self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument")) self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument")) diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 242482e..7cf6dbe 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -143,6 +143,9 @@ class ConsoleUI(threading.Thread): self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos elif command.group('command') in constants.COMMANDS_LIST: self._syncplayClient.getUserList() + elif command.group('command') in constants.COMMANDS_CHAT: + message= command.group('parameter') + self._syncplayClient.sendChat(message) elif command.group('command') in constants.COMMANDS_PAUSE: self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused()) elif command.group('command') in constants.COMMANDS_ROOM: @@ -181,6 +184,7 @@ class ConsoleUI(threading.Thread): self.showMessage(getMessage("commandlist-notification/toggle"), True) self.showMessage(getMessage("commandlist-notification/create"), True) self.showMessage(getMessage("commandlist-notification/auth"), True) + self.showMessage(getMessage("commandlist-notification/chat"), True) self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True) self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True) diff --git a/syncplayServer.py b/syncplayServer.py index b4456d7..aa13653 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -19,6 +19,5 @@ from syncplay.server import SyncFactory, ConfigurationGetter if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() - - reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready)) + reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready,args.chat)) reactor.run() From 586c12ec688d3ef94b0884fcabfb06818eaa37d2 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 3 Jul 2016 17:48:42 +0100 Subject: [PATCH 02/15] Add chat to GUI --- buildPy2exe.py | 1 + resources/email_go.png | Bin 0 -> 754 bytes syncplay/__init__.py | 2 +- syncplay/client.py | 1 - syncplay/constants.py | 1 + syncplay/messages_de.py | 3 +++ syncplay/messages_en.py | 3 +++ syncplay/messages_ru.py | 3 +++ syncplay/protocols.py | 3 ++- syncplay/ui/gui.py | 25 +++++++++++++++++++++++++ 10 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 resources/email_go.png diff --git a/buildPy2exe.py b/buildPy2exe.py index 403bd4a..0d72229 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -657,6 +657,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', 'resources/film_folder_edit.png', 'resources/shield_edit.png', + 'resources/email_go.png', 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng' ] resources = ["resources/icon.ico", "resources/syncplay.png"] diff --git a/resources/email_go.png b/resources/email_go.png new file mode 100644 index 0000000000000000000000000000000000000000..4a6c5d396ad024567a769d1f2285c525bef8c88b GIT binary patch literal 754 zcmV~0d>kmp9&fG9>#5EaEn(MD9Xvl47<{Rb2T3D!0$wpy4@P!Tnn zB0fxQ2!aPL_fZ@!=Wv9zmj8jnf5JO`<=MmZs@#@p z5O*{}9L5B6J+L7ORza<+9h)-B~<&cwIhUkd?cp4is-ziNrfY^u-7xdbFt=#%1534Oi8ajBBo}VZvCxd1 zTu*VtX+~P45)Om?dG5aS`PPW(%?lDPBwaz$6C^$8$_Q#plJTjpbgj;_rYi*?oK~m+ zDD`C->dkZKh0j6|Q^-0bJxIFHnHJOo$@r8{VNIUWU>31rp3@BVvYIZG2D0>Yuj2Y- zg?b?AObc>pK`oG+zkPsr3x_BTWXX8H*w}0@e;@>?SdsP&!-HMidhRn*4VkHi+<58} zTg6ae0~aSoNqRq(^;(g}0#Yiz)&q*YIg-f)mmeJA&3%nD3aJ$X-6L%1+fEQRIeTL* zS5K6|3cOaNtTtBowSZzzj!ZfM7L<|f+jtyp4ccf7mf6J$n}&ALkZlfLEYRoNvtG0Q zzJh|U~_Rdjkv}0kGt_?V}5$?sXN`N zTt|nOa)sx)IIf_-7XW**;!X9`USPjuUUsJrREk)6q*M9ZHEb zl2A!T3LBjrrTx74{FsUNcVA1zw%IWoiGNMw=s9P8>+qh!B*saR2}S literal 0 HcmV?d00001 diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 31a5492..8e7ceec 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ -version = '1.4.0' +version = '1.5.0' milestone = 'Yoitsu' release_number = '28' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index a79e465..d363db2 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1703,7 +1703,6 @@ class FileSwitchManager(object): if highPriority and self.folderSearchEnabled: directoryList = self.mediaDirectories # Spin up hard drives to prevent premature timeout - randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt" for directory in directoryList: startTime = time.time() if os.path.isfile(os.path.join(directory, randomFilename)): diff --git a/syncplay/constants.py b/syncplay/constants.py index d5cbb7d..dfbab0c 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -79,6 +79,7 @@ MPV_UNRESPONSIVE_THRESHOLD = 60.0 CONTROLLED_ROOMS_MIN_VERSION = "1.3.0" USER_READY_MIN_VERSION = "1.3.0" SHARED_PLAYLIST_MIN_VERSION = "1.4.0" +CHAT_MIN_VERSION = "1.5.0" MPC_PATHS = [ r"c:\program files (x86)\mpc-hc\mpc-hc.exe", r"c:\program files\mpc-hc\mpc-hc.exe", diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index f8f560e..6529800 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -248,6 +248,8 @@ de = { "autoplay-guipushbuttonlabel" : u"Automatisch abspielen wenn alle bereit sind", "autoplay-minimum-label" : u"Minimum an Nutzern:", + "sendmessage-label" : u"Send", # TODO: Translate + "ready-guipushbuttonlabel" : u"Ich bin bereit den Film anzuschauen!", "roomuser-heading-label" : u"Raum / Benutzer", @@ -350,6 +352,7 @@ de = { "ready-tooltip" : u"Zeigt an, ob du bereit zum anschauen bist", "autoplay-tooltip" : u"Automatisch abspielen, wenn alle Nutzer bereit sind oder die minimale Nutzerzahl erreicht ist.", "switch-to-file-tooltip" : u"Doppelklicken um zu {} zu wechseln", # Filename + "sendmessage-tooltip" : u"Send message to room", # TODO: Translate # In-userlist notes (GUI) "differentsize-note" : u"Verschiedene Größe!", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index e31ffb7..7bb63c6 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -250,6 +250,8 @@ en = { "autoplay-guipushbuttonlabel" : u"Play when all ready", "autoplay-minimum-label" : u"Min users:", + "sendmessage-label" : u"Send", + "ready-guipushbuttonlabel" : u"I'm ready to watch!", "roomuser-heading-label" : "Room / User", @@ -350,6 +352,7 @@ en = { "ready-tooltip" : "Indicates whether you are ready to watch.", "autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.", "switch-to-file-tooltip" : u"Double click to switch to {}", # Filename + "sendmessage-tooltip" : u"Send message to room", # In-userlist notes (GUI) "differentsize-note" : "Different size!", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 94804e4..81ecf7c 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -250,6 +250,8 @@ ru = { "autoplay-guipushbuttonlabel" : u"Воспроизвести автоматически, когда все будут готовы", "autoplay-minimum-label" : u"Минимум пользователей:", + "sendmessage-label" : u"Send", # TODO: Translate + "ready-guipushbuttonlabel" : u"Я готов к просмотру!", "roomuser-heading-label" : u"Комната / Пользователь", @@ -350,6 +352,7 @@ ru = { "ready-tooltip" : u"Показывает, готовы ли Вы к просмотру или нет.", "autoplay-tooltip" : u"Автоматическое воспроизведение, когда все пользователи с индикаторами готовности будут готовы и присутствует достаточное число пользователей.", "switch-to-file-tooltip" : u"Double click to switch to {}", # Filename # TODO: Translate to Russian + "sendmessage-tooltip" : u"Send message to room", # TODO: Translate # In-userlist notes (GUI) "differentsize-note" : u"Размер файла не совпадает!", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 3c6c17e..096f19d 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -246,7 +246,8 @@ class SyncClientProtocol(JSONCommandProtocol): } }) def handleChat(self,message): - messageString = '<'+message['username']+'>'+message['message'] + userMessage = message['message'].replace(u"<",u"<<").replace(u">",u">>") + messageString = u"<{}> {}".format(message['username'], userMessage) self._client.ui.showMessage(messageString) #TODO diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index dc62c85..40eb494 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -328,8 +328,12 @@ class MainWindow(QtGui.QMainWindow): def showMessage(self, message, noTimestamp=False): message = unicode(message) message = message.replace(u"&", u"&").replace(u'"', u""").replace(u"<", u"<").replace(">", u">") + message = message.replace(u"<<", u"") + message = message.replace(u">>", u"") message = message.replace(u"<", u"<".format(constants.STYLE_USERNAME)) message = message.replace(u">", u">") + message = message.replace(u";", u"<") + message = message.replace(u"", u">") message = message.replace(u"\n", u"
") if noTimestamp: self.newMessage(u"{}
".format(message)) @@ -962,6 +966,11 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient.playlist.changePlaylist(newPlaylist) self._syncplayClient.fileSwitch.updateInfo() + def sendChatMessage(self): + if self.chatInput.text() <> "": + self._syncplayClient.sendChat(self.chatInput.text()) + self.chatInput.setText("") + def addTopLayout(self, window): window.topSplit = self.topSplitter(Qt.Horizontal, self) @@ -977,12 +986,28 @@ class MainWindow(QtGui.QMainWindow): window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) window.outputlabel = QtGui.QLabel(getMessage("notifications-heading-label")) + window.chatInput = QtGui.QLineEdit() + window.chatInput.returnPressed.connect(self.sendChatMessage) + window.chatButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'email_go.png'), + getMessage("sendmessage-label")) + window.chatButton.pressed.connect(self.sendChatMessage) + window.chatLayout = QtGui.QHBoxLayout() + window.chatFrame = QtGui.QFrame() + window.chatFrame.setLayout(self.chatLayout) + window.chatFrame.setContentsMargins(0,0,0,0) + window.chatFrame.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) + window.chatLayout.setContentsMargins(0,0,0,0) + self.chatButton.setToolTip(getMessage("sendmessage-tooltip")) + window.chatLayout.addWidget(window.chatInput) + window.chatLayout.addWidget(window.chatButton) + window.chatFrame.setMaximumHeight(window.chatFrame.sizeHint().height()) window.outputFrame = QtGui.QFrame() window.outputFrame.setLineWidth(0) window.outputFrame.setMidLineWidth(0) window.outputLayout.setContentsMargins(0, 0, 0, 0) window.outputLayout.addWidget(window.outputlabel) window.outputLayout.addWidget(window.outputbox) + window.outputLayout.addWidget(window.chatFrame) window.outputFrame.setLayout(window.outputLayout) window.listLayout = QtGui.QVBoxLayout() From 60e357ce75d12675de542c6375d3c31abdeadbcb Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 3 Jul 2016 17:58:37 +0100 Subject: [PATCH 03/15] Revert deletion of randomFilename --- syncplay/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/syncplay/client.py b/syncplay/client.py index d363db2..a79e465 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1703,6 +1703,7 @@ class FileSwitchManager(object): if highPriority and self.folderSearchEnabled: directoryList = self.mediaDirectories # Spin up hard drives to prevent premature timeout + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt" for directory in directoryList: startTime = time.time() if os.path.isfile(os.path.join(directory, randomFilename)): From 23c1496ce4117853f50af725f93d2821b935e281 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 3 Jul 2016 21:12:28 +0100 Subject: [PATCH 04/15] Add feature list to protocol - make server specify supported features so client knows what to disable --- syncplay/client.py | 34 +++++++++++++++++++++++++++------- syncplay/constants.py | 1 + syncplay/messages_de.py | 1 + syncplay/messages_en.py | 1 + syncplay/messages_ru.py | 1 + syncplay/protocols.py | 8 +++++--- syncplay/server.py | 9 +++++++++ syncplay/ui/consoleUI.py | 3 +++ syncplay/ui/gui.py | 8 ++++++++ 9 files changed, 56 insertions(+), 10 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index a79e465..d6effd9 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -71,6 +71,8 @@ class SyncplayClient(object): self.lastControlPasswordAttempt = None self.serverVersion = "0.0.0" + self.serverFeatures = {} + self.lastRewindTime = None self.lastLeftTime = 0 self.lastPausedOnLeaveTime = None @@ -201,7 +203,7 @@ class SyncplayClient(object): if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: self.playlist.advancePlaylistCheck() - elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): + elif pauseChange and self.serverFeatures["readiness"]: if currentLength == 0 or currentLength == -1 or\ not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD): pauseChange = self._toggleReady(pauseChange, paused) @@ -508,13 +510,25 @@ class SyncplayClient(object): size = 0 return filename, size - def setServerVersion(self, version): + def setServerVersion(self, version, featureList): self.serverVersion = version - self.checkForFeatureSupport() + self.checkForFeatureSupport(featureList) - def checkForFeatureSupport(self): + def checkForFeatureSupport(self, featureList): + self.serverFeatures = { + "featureList": utils.meetsMinVersion(self.serverVersion, constants.FEATURE_LIST_MIN_VERSION), + "sharedPlaylists": utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION), + "chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION), + "readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION), + "managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION) + } + if featureList: + self.serverFeatures.update(featureList) if not utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION): self.ui.showErrorMessage(getMessage("shared-playlists-not-supported-by-server-error").format(constants.SHARED_PLAYLIST_MIN_VERSION, self.serverVersion)) + elif not self.serverFeatures["sharedPlaylists"]: + self.ui.showErrorMessage(getMessage("shared-playlists-disabled-by-server-error")) + self.ui.setFeatures(self.serverFeatures) def getSanitizedCurrentUserFile(self): if self.userlist.currentUser.file: @@ -570,7 +584,11 @@ class SyncplayClient(object): return self._protocol and self._protocol.logged and self.userlist.currentUser.room def sharedPlaylistIsEnabled(self): - return self._config['sharedPlaylistEnabled'] + if self.serverFeatures.has_key("sharedPlaylists") and not self.serverFeatures["sharedPlaylists"]: + sharedPlaylistEnabled = False + else: + sharedPlaylistEnabled = self._config['sharedPlaylistEnabled'] + return sharedPlaylistEnabled def connected(self): readyState = self._config['readyAtStart'] if self.userlist.currentUser.isReady() is None else self.userlist.currentUser.isReady() @@ -1004,13 +1022,12 @@ class SyncplayUserlist(object): self._roomUsersChanged = True def isReadinessSupported(self): - # TODO: Return False if server is run with --disable-ready if not utils.meetsMinVersion(self._client.serverVersion,constants.USER_READY_MIN_VERSION): return False elif self.onlyUserInRoomWhoSupportsReadiness(): return False else: - return True + return self._client.serverFeatures["readiness"] def isRoomSame(self, room): if room and self.currentUser.room and self.currentUser.room == room: @@ -1293,6 +1310,9 @@ class UiManager(object): def fileSwitchFoundFiles(self): self.__ui.fileSwitchFoundFiles() + def setFeatures(self, featureList): + self.__ui.setFeatures(featureList) + def showDebugMessage(self, message): if constants.DEBUG_MODE and message.rstrip(): sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) diff --git a/syncplay/constants.py b/syncplay/constants.py index dfbab0c..37f1fd7 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -80,6 +80,7 @@ CONTROLLED_ROOMS_MIN_VERSION = "1.3.0" USER_READY_MIN_VERSION = "1.3.0" SHARED_PLAYLIST_MIN_VERSION = "1.4.0" CHAT_MIN_VERSION = "1.5.0" +FEATURE_LIST_MIN_VERSION = "1.5.0" MPC_PATHS = [ r"c:\program files (x86)\mpc-hc\mpc-hc.exe", r"c:\program files\mpc-hc\mpc-hc.exe", diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 6529800..bd715d8 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -136,6 +136,7 @@ de = { "not-supported-by-server-error" : u"Dieses Feature wird vom Server nicht unterstützt. Es wird ein Server mit Syncplay Version {}+ benötigt, aktuell verwendet wird jedoch Version {}.", #minVersion, serverVersion "shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion # TODO: Translate + "shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.", # TODO: Translate "invalid-seek-value" : u"Ungültige Zeitangabe", "invalid-offset-value" : u"Ungültiger Offset-Wert", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 7bb63c6..7c2a3f9 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -136,6 +136,7 @@ en = { "not-supported-by-server-error" : "This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion "shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion + "shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.", "invalid-seek-value" : u"Invalid seek value", "invalid-offset-value" : u"Invalid offset value", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 81ecf7c..1c54910 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -136,6 +136,7 @@ ru = { "not-supported-by-server-error" : u"Эта возможность не поддерживается сервером. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion #TODO: Translate into Russian "shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion # TODO: Translate + "shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.", # TODO: Translate "invalid-seek-value" : u"Некорректное значение для перемотки", "invalid-offset-value" : u"Некорректное смещение", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 096f19d..1df4f02 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -80,10 +80,11 @@ class SyncClientProtocol(JSONCommandProtocol): version = hello["version"] if hello.has_key("version") else None version = hello["realversion"] if hello.has_key("realversion") else version # Used for 1.2.X compatibility motd = hello["motd"] if hello.has_key("motd") else None - return username, roomName, version, motd + features = hello["features"] if hello.has_key("features") else None + return username, roomName, version, motd, features def handleHello(self, hello): - username, roomName, version, motd = self._extractHelloArguments(hello) + username, roomName, version, motd, featureList = self._extractHelloArguments(hello) if not username or not roomName or not version: self.dropWithError(getMessage("hello-server-error").format(hello)) else: @@ -95,7 +96,7 @@ class SyncClientProtocol(JSONCommandProtocol): self._client.ui.showMessage(getMessage("connected-successful-notification")) self._client.connected() self._client.sendFile() - self._client.setServerVersion(version) + self._client.setServerVersion(version, featureList) def sendHello(self): hello = {} @@ -370,6 +371,7 @@ class SyncServerProtocol(JSONCommandProtocol): hello["version"] = clientVersion # Used so 1.2.X client works on newer server hello["realversion"] = syncplay.version hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion) + hello["features"] = self._factory.getFeatures() self.sendMessage({"Hello": hello}) @requireLogged diff --git a/syncplay/server.py b/syncplay/server.py index 397dd41..90c811a 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -15,6 +15,7 @@ from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomString class SyncFactory(Factory): def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False,chat =False): + self.isolateRooms = isolateRooms print getMessage("welcome-server-notification").format(syncplay.version) if password: password = hashlib.md5(password).hexdigest() @@ -41,6 +42,14 @@ class SyncFactory(Factory): setBy = room.getSetBy() watcher.sendState(position, paused, doSeek, setBy, forcedUpdate) + def getFeatures(self): + features = dict() + features["isolateRooms"] = self.isolateRooms + features["readiness"] = not self.disableReady + features["managedRooms"] = True + features["chat"] = self.chat + return features + def getMotd(self, userIp, username, room, clientVersion): oldClient = False if constants.WARN_OLD_CLIENTS: diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 7cf6dbe..d5af043 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -86,6 +86,9 @@ class ConsoleUI(threading.Thread): def fileSwitchFoundFiles(self): pass + def setFeatures(self, featureList): + pass + def showMessage(self, message, noTimestamp=False): message = message.encode(sys.stdout.encoding, 'replace') if noTimestamp: diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 40eb494..447ab4c 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -325,6 +325,14 @@ class MainWindow(QtGui.QMainWindow): # TODO: Prompt user return None + def setFeatures(self, featureList): + if not featureList["readiness"]: + self.readyPushButton.setEnabled(False) + if not featureList["chat"]: + self.chatFrame.setEnabled(False) + if not featureList["sharedPlaylists"]: + self.playlistGroup.setEnabled(False) + def showMessage(self, message, noTimestamp=False): message = unicode(message) message = message.replace(u"&", u"&").replace(u'"', u""").replace(u"<", u"<").replace(">", u">") From bc07383d139c5bbd32987d19b14112438f945617 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 4 Jul 2016 00:09:36 +0100 Subject: [PATCH 05/15] Enable chat by default --- syncplay/messages_de.py | 2 +- syncplay/messages_en.py | 2 +- syncplay/messages_ru.py | 2 +- syncplay/protocols.py | 2 +- syncplay/server.py | 8 ++++---- syncplayServer.py | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index bd715d8..dc53281 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -379,7 +379,7 @@ de = { "server-salt-argument" : u"zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird", "server-disable-ready-argument" : u"Bereitschaftsfeature deaktivieren", "server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird", - "server-chat-argument" : "Should chat be enabled?", # TODO: Translate + "server-chat-argument" : "Should chat be disabled?", # TODO: Translate "server-messed-up-motd-unescaped-placeholders": u"Die Nachricht des Tages hat unmaskierte Platzhalter. Alle $-Zeichen sollten verdoppelt werden ($$).", "server-messed-up-motd-too-long": u"Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 7c2a3f9..23b4043 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -380,7 +380,7 @@ en = { "server-salt-argument" : "random string used to generate managed room passwords", "server-disable-ready-argument" : u"disable readiness feature", "server-motd-argument": "path to file from which motd will be fetched", - "server-chat-argument" : "Should chat be enabled?", + "server-chat-argument" : "Should chat be disabled?", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-too-long": u"Message of the Day is too long - maximum of {} chars, {} given.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 1c54910..05d1807 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -379,7 +379,7 @@ ru = { "server-salt-argument" : u"генерировать пароли к управляемым комнатам на основании указанной строки (соли)", "server-disable-ready-argument" : u"отключить статусы готов/не готов", "server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение", - "server-chat-argument" : "Should chat be enabled?", # TODO: Translate + "server-chat-argument" : "Should chat be disabled?", # TODO: Translate "server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", "server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 1df4f02..e70df7c 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -355,7 +355,7 @@ class SyncServerProtocol(JSONCommandProtocol): self._logged = True self.sendHello(version) def handleChat(self,chatMessage): - if self._factory.chat: + if not self._factory.disableChat: self._factory.sendChat(self._watcher,chatMessage) def setWatcher(self, watcher): diff --git a/syncplay/server.py b/syncplay/server.py index 90c811a..e3df8e3 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -14,7 +14,7 @@ import argparse from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion class SyncFactory(Factory): - def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False,chat =False): + def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False,disableChat=False): self.isolateRooms = isolateRooms print getMessage("welcome-server-notification").format(syncplay.version) if password: @@ -26,7 +26,7 @@ class SyncFactory(Factory): self._salt = salt self._motdFilePath = motdFilePath self.disableReady = disableReady - self.chat=chat + self.disableChat = disableChat if not isolateRooms: self._roomManager = RoomManager() else: @@ -47,7 +47,7 @@ class SyncFactory(Factory): features["isolateRooms"] = self.isolateRooms features["readiness"] = not self.disableReady features["managedRooms"] = True - features["chat"] = self.chat + features["chat"] = not self.disableChat return features def getMotd(self, userIp, username, room, clientVersion): @@ -524,6 +524,6 @@ class ConfigurationGetter(object): self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument")) self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument")) self._argparser.add_argument('--disable-ready', action='store_true', help=getMessage("server-disable-ready-argument")) - self._argparser.add_argument('--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")) self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument")) diff --git a/syncplayServer.py b/syncplayServer.py index aa13653..347b171 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -19,5 +19,5 @@ from syncplay.server import SyncFactory, ConfigurationGetter if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() - reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready,args.chat)) + reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready,args.disable_chat)) reactor.run() From e28ffdfb7d5f21d887e3c043947ce24be23f5dd8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 8 Jul 2016 17:43:31 +0100 Subject: [PATCH 06/15] Server shouldn't send chat messages to pre-chat client (as it causes them to quit) --- syncplay/protocols.py | 8 ++++++++ syncplay/server.py | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index e70df7c..05425b8 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -160,8 +160,10 @@ class SyncClientProtocol(JSONCommandProtocol): def sendFileSetting(self, file_): self.sendSet({"file": file_}) self.sendList() + def sendChatMessage(self,chatMessage): self.sendMessage({"Chat": chatMessage}) + def handleList(self, userList): self._client.userlist.clearList() for room in userList.iteritems(): @@ -284,6 +286,7 @@ class SyncClientProtocol(JSONCommandProtocol): class SyncServerProtocol(JSONCommandProtocol): def __init__(self, factory): self._factory = factory + self._version = None self._logged = False self.clientIgnoringOnTheFly = 0 self.serverIgnoringOnTheFly = 0 @@ -320,6 +323,9 @@ class SyncServerProtocol(JSONCommandProtocol): def isLogged(self): return self._logged + def meetsMinVersion(self, version): + return self._version >= version + def _extractHelloArguments(self, hello): roomName = None username = hello["username"] if hello.has_key("username") else None @@ -353,7 +359,9 @@ class SyncServerProtocol(JSONCommandProtocol): return self._factory.addWatcher(self, username, roomName) self._logged = True + self._version = version self.sendHello(version) + def handleChat(self,chatMessage): if not self._factory.disableChat: self._factory.sendChat(self._watcher,chatMessage) diff --git a/syncplay/server.py b/syncplay/server.py index e3df8e3..f5ecc5e 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -442,7 +442,8 @@ class Watcher(object): self._connector.sendControlledRoomAuthStatus(success, username, room) def sendChatMessage(self,message): - self._connector.sendMessage({"Chat" : message}) + if self._connector.meetsMinVersion(constants.CHAT_MIN_VERSION): + self._connector.sendMessage({"Chat" : message}) def sendSetReady(self, username, isReady, manuallyInitiated=True): self._connector.sendSetReady(username, isReady, manuallyInitiated) From 466ac588cd213f0cdda14dd82afcc6b6bc7fe08d Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 14 Jul 2016 17:57:33 +0100 Subject: [PATCH 07/15] Send client version when server announces client joined server --- syncplay/protocols.py | 5 ++++- syncplay/server.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 05425b8..ddba3f0 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -326,6 +326,9 @@ class SyncServerProtocol(JSONCommandProtocol): def meetsMinVersion(self, version): return self._version >= version + def getVersion(self): + return self._version + def _extractHelloArguments(self, hello): roomName = None username = hello["username"] if hello.has_key("username") else None @@ -357,9 +360,9 @@ class SyncServerProtocol(JSONCommandProtocol): else: if not self._checkPassword(serverPassword): return + self._version = version self._factory.addWatcher(self, username, roomName) self._logged = True - self._version = version self.sendHello(version) def handleChat(self,chatMessage): diff --git a/syncplay/server.py b/syncplay/server.py index f5ecc5e..4bf0e9d 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -106,7 +106,7 @@ class SyncFactory(Factory): self._roomManager.broadcast(watcher, l) def sendJoinMessage(self, watcher): - l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True}) if w != watcher else None + l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True, "version": watcher.getVersion()}) if w != watcher else None self._roomManager.broadcast(watcher, l) self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False)) @@ -417,6 +417,9 @@ class Watcher(object): def getName(self): return self._name + def getVersion(self): + return self._connector.getVersion() + def getFile(self): return self._file From 0421ef59313d518d7d7923a85b58a9ca1570ba82 Mon Sep 17 00:00:00 2001 From: Etoh Date: Sat, 24 Dec 2016 13:52:02 +0000 Subject: [PATCH 08/15] Remove random newlines from conflict resolution --- syncplay/messages_ru.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 720711c..e7ebda9 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -244,7 +244,6 @@ ru = { "contact-label" : u"Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на dev@syncplay.pl, в IRC канал #Syncplay на irc.freenode.net или задавайте вопросы через GitHub. Кроме того, заходите на www.syncplay.pl за инорфмацией, помощью и обновлениями!", "joinroom-label" : u"Зайти в комнату", - "joinroom-menu-label" : u"Зайти в комнату {}", "seektime-menu-label" : u"Пере&мотать", "undoseek-menu-label" : u"&Отменить перемотку", @@ -254,7 +253,6 @@ ru = { "autoplay-menu-label" : u"Показывать кнопку &автовоспроизведения", "autoplay-guipushbuttonlabel" : u"Стартовать, когда все будут готовы", "autoplay-minimum-label" : u"Минимум зрителей:", - "sendmessage-label" : u"Send", # TODO: Translate "ready-guipushbuttonlabel" : u"Я готов", @@ -420,4 +418,4 @@ ru = { "playlist-instruction-item-message" : u"Перетащите сюда файлы, чтобы добавить их в общий список.", "sharedplaylistenabled-tooltip" : u"Оператор комнаты может добавлять файлы в список общего воспроизведения для удобного совместного просмотра. Папки воспроизведения настраиваются во вкладке 'Файл'.", -} \ No newline at end of file +} From 866a3f5baa40ff202b4459eb701b4e38a884886c Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 24 Dec 2016 13:59:16 +0000 Subject: [PATCH 09/15] Uvper to release 37 --- syncplay/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 1d40d4f..e2de25c 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ version = '1.5.0' milestone = 'Yoitsu' -release_number = '36' +release_number = '37' projectURL = 'http://syncplay.pl/' From 80ef5b13120531b97c364d73e6dbe0567415b7bb Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 24 Dec 2016 16:08:11 +0000 Subject: [PATCH 10/15] Use localised message for "Pausing" and "Initial readiness state" messages (reported by Corwin) --- syncplay/ui/GuiConfiguration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index aa5bdb0..7de7295 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -628,7 +628,7 @@ class ConfigDialog(QtGui.QDialog): # Initial state - self.readyInitialGroup = QtGui.QGroupBox(u"Initial readiness state") + self.readyInitialGroup = QtGui.QGroupBox(getMessage("readiness-title")) self.readyInitialLayout = QtGui.QVBoxLayout() self.readyInitialGroup.setLayout(self.readyInitialLayout) self.readyatstartCheckbox = QCheckBox(getMessage("readyatstart-label")) @@ -637,7 +637,7 @@ class ConfigDialog(QtGui.QDialog): self.readyLayout.addWidget(self.readyInitialGroup) # Automatically pausing - self.readyPauseGroup = QtGui.QGroupBox(u"Pausing") + self.readyPauseGroup = QtGui.QGroupBox(getMessage("pausing-title")) self.readyPauseLayout = QtGui.QVBoxLayout() self.readyPauseGroup.setLayout(self.readyPauseLayout) self.pauseonleaveCheckbox = QCheckBox(getMessage("pauseonleave-label")) From cf4d150ede04cadff8415b8c46ce3b53ee968072 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 24 Dec 2016 21:14:24 +0000 Subject: [PATCH 11/15] Be case insentitive for raw filename comparisons --- syncplay/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/syncplay/utils.py b/syncplay/utils.py index 7733464..9eafe82 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -202,6 +202,11 @@ def hashFilesize(size): return hashlib.sha256(str(size)).hexdigest()[:12] def sameHashed(string1raw, string1hashed, string2raw, string2hashed): + try: + if string1raw.lower() == string2raw.lower(): + return True + except AttributeError: + pass if string1raw == string2raw: return True elif string1raw == string2hashed: From dc5ed86de8ca0938d2846fd5c53d7ac14aaf1763 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 24 Dec 2016 21:20:40 +0000 Subject: [PATCH 12/15] Increase retries and reduce retry delay (#122) --- syncplay/client.py | 2 +- syncplay/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index fc57f6f..932686c 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -39,7 +39,7 @@ class SyncClientFactory(ClientFactory): self._timesTried += 1 self._client.ui.showMessage(getMessage("reconnection-attempt-notification")) self.reconnecting = True - reactor.callLater(0.1 * (2 ** self._timesTried), connector.connect) + reactor.callLater(0.1 * (2 ** min(self._timesTried,5)), connector.connect) else: message = getMessage("disconnection-notification") self._client.ui.showErrorMessage(message) diff --git a/syncplay/constants.py b/syncplay/constants.py index 2ec7783..f941d2e 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -46,7 +46,7 @@ MINIMUM_SLOWDOWN_THRESHOLD = 1.3 SLOWDOWN_RESET_THRESHOLD = 0.1 DIFFERENT_DURATION_THRESHOLD = 2.5 PROTOCOL_TIMEOUT = 12.5 -RECONNECT_RETRIES = 10 +RECONNECT_RETRIES = 999 SERVER_STATE_INTERVAL = 1 WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 AUTOPLAY_DELAY = 3.0 From 665956b84cf349662596a5293fb547adb22bc0f1 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 26 Dec 2016 11:29:12 +0000 Subject: [PATCH 13/15] Add maximum character limits to user-definable elements which appear in GUI/OSD to keep everything on screen --- buildPy2exe.py | 2 +- syncplay/client.py | 1 + syncplay/constants.py | 6 ++++++ syncplay/server.py | 10 +++++++++- syncplay/ui/GuiConfiguration.py | 3 +++ syncplay/ui/gui.py | 2 ++ syncplay/utils.py | 17 +++++++++++++++++ 7 files changed, 39 insertions(+), 2 deletions(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 5682c77..20e671b 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -685,7 +685,7 @@ info = dict( options={'py2exe': { 'dist_dir': OUT_DIR, 'packages': 'PySide.QtUiTools', - 'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl, ast', + 'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl, ast, unicodedata', 'excludes': 'venv, _ssl, doctest, pdb, unittest, win32clipboard, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process, Tkinter', 'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll', 'optimize': 2, diff --git a/syncplay/client.py b/syncplay/client.py index 932686c..28a6b7c 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -588,6 +588,7 @@ class SyncplayClient(object): def sendChat(self,message): if self._protocol and self._protocol.logged: + message = utils.truncateText(message,constants.MAX_CHAT_MESSAGE_LENGTH) self._protocol.sendChatMessage(message) def sendRoom(self): diff --git a/syncplay/constants.py b/syncplay/constants.py index f941d2e..c9191ec 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -56,6 +56,12 @@ PLAYLIST_MAX_CHARACTERS = 10000 PLAYLIST_MAX_ITEMS = 250 VLC_LISTEN_FOR_STDOUT = False # Changing to True this could break VLC 3 on Windows +# Maximum character lengths (for client and server) +MAX_CHAT_MESSAGE_LENGTH = 50 # Number of displayed characters +MAX_USERNAME_LENGTH = 12 # Number of displayed characters +MAX_ROOM_NAME_LENGTH = 35 # Number of displayed characters +MAX_FILENAME_LENGTH = 250 # Number of displayed characters + # Options for the File Switch feature: FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 15.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up) FOLDER_SEARCH_TIMEOUT = 6.0 # Secs - How long to wait until searches in folder to update cache are aborted (after first file is found) diff --git a/syncplay/server.py b/syncplay/server.py index a6f9d70..399325a 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -11,7 +11,7 @@ import codecs import os from string import Template import argparse -from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid +from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText class SyncFactory(Factory): def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False,disableChat=False): @@ -72,11 +72,13 @@ class SyncFactory(Factory): return "" def addWatcher(self, watcherProtocol, username, roomName): + roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH) username = self._roomManager.findFreeUsername(username) watcher = Watcher(self, watcherProtocol, username) self.setWatcherRoom(watcher, roomName, asJoin=True) def setWatcherRoom(self, watcher, roomName, asJoin=False): + roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH) self._roomManager.moveWatcher(watcher, roomName) if asJoin: self.sendJoinMessage(watcher) @@ -145,6 +147,7 @@ class SyncFactory(Factory): self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name)) def sendChat(self,watcher,message): + message = truncateText(message, constants.MAX_CHAT_MESSAGE_LENGTH) messageDict={"message":message,"username" : watcher.getName()} self._roomManager.broadcastRoom(watcher, lambda w: w.sendChatMessage(messageDict)) @@ -192,6 +195,7 @@ class RoomManager(object): return watchers def moveWatcher(self, watcher, roomName): + roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH) self.removeWatcher(watcher) room = self._getRoom(roomName) room.addWatcher(watcher) @@ -218,6 +222,7 @@ class RoomManager(object): del self._rooms[room.getName()] def findFreeUsername(self, username): + username = truncateText(username,constants.MAX_USERNAME_LENGTH) allnames = [] for room in self._rooms.itervalues(): for watcher in room.getWatchers(): @@ -392,6 +397,9 @@ class Watcher(object): reactor.callLater(0.1, self._scheduleSendState) def setFile(self, file_): + print file_ + if file_ and file_.has_key("name"): + file_["name"] = truncateText(file_["name"],constants.MAX_FILENAME_LENGTH) self._file = file_ self._server.sendFileUpdate(self) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 7de7295..5d6c77b 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -542,6 +542,9 @@ class ConfigDialog(QtGui.QDialog): self.defaultroomLabel.setObjectName("room") self.defaultroomTextbox.setObjectName("room") + self.usernameTextbox.setMaxLength(constants.MAX_USERNAME_LENGTH) + self.defaultroomTextbox.setMaxLength(constants.MAX_ROOM_NAME_LENGTH) + self.connectionSettingsLayout = QtGui.QGridLayout() self.connectionSettingsLayout.addWidget(self.hostLabel, 0, 0) self.connectionSettingsLayout.addWidget(self.hostCombobox, 0, 1) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index a7523d3..b5475f2 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1058,6 +1058,7 @@ class MainWindow(QtGui.QMainWindow): window.outputlabel = QtGui.QLabel(getMessage("notifications-heading-label")) window.chatInput = QtGui.QLineEdit() + window.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH) window.chatInput.returnPressed.connect(self.sendChatMessage) window.chatButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'email_go.png'), getMessage("sendmessage-label")) @@ -1112,6 +1113,7 @@ class MainWindow(QtGui.QMainWindow): window.listLayout.addWidget(window.listSplit) window.roomInput = QtGui.QLineEdit() + window.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH) window.roomInput.returnPressed.connect(self.joinRoom) window.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'), getMessage("joinroom-label")) diff --git a/syncplay/utils.py b/syncplay/utils.py index 9eafe82..a7eedac 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -11,6 +11,7 @@ import random import string import urllib import ast +import unicodedata folderSearchEnabled = True @@ -160,6 +161,22 @@ def blackholeStdoutForFrozenWindow(): sys.stdout = Blackhole() del Blackhole +def truncateText(unicodeText, maxLength): + try: + unicodeText = unicodedata.normalize('NFC', unicodeText) + except: + pass + try: + maxSaneLength= maxLength*5 + if len(unicodeText) > maxSaneLength: + unicodeText = unicode(unicodeText.encode("utf-8")[:maxSaneLength], "utf-8", errors="ignore") + while len(unicodeText) > maxLength: + unicodeText = unicode(unicodeText.encode("utf-8")[:-1], "utf-8", errors="ignore") + return unicodeText + except: + pass + return "" + # Relate to file hashing / difference checking: def stripfilename(filename, stripURL): From ddae11469432fd589ce989994c9ec9ee3d5164d6 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 27 Dec 2016 13:50:10 +0000 Subject: [PATCH 14/15] Improve chat/GUI handling of < and > characters --- syncplay/constants.py | 4 +++- syncplay/protocols.py | 3 +-- syncplay/ui/gui.py | 15 ++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index c9191ec..6d8f88d 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -153,7 +153,8 @@ STYLE_READY_PUSHBUTTON = "QPushButton { text-align: left; padding: 10px 5px 10px STYLE_AUTO_PLAY_PUSHBUTTON = "QPushButton { text-align: left; padding: 5px 5px 5px 5px; }" STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }" STYLE_CONTACT_INFO = u"{}

" # Contact info message -STYLE_USERNAME = "color: #367AA9; font-weight:bold;" +STYLE_USER_MESSAGE = u"<{}> {}" +STYLE_USERNAME = u"color: #367AA9; font-weight:bold;" STYLE_ERRORNOTIFICATION = "color: red;" STYLE_DIFFERENTITEM_COLOR = 'red' STYLE_NOFILEITEM_COLOR = 'blue' @@ -180,6 +181,7 @@ UI_COMMAND_REGEX = r"^(?P[^\ ]+)(?:\ (?P.+))?" UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P[/+-])?(?P