From aa05ed473d4a8c41c18168921daff7131ed63127 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 22 Aug 2014 15:45:57 +0100 Subject: [PATCH 001/142] Stopgap error for mpv 0.5.0+ issue --- syncplay/players/mplayer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 9c9fa08..8dd89a5 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -23,6 +23,7 @@ class MplayerPlayer(BasePlayer): self._duration = None self._filename = None self._filepath = None + self.quitReason = None try: self._listener = self.__Listener(self, playerPath, filePath, args) except ValueError: @@ -125,6 +126,9 @@ class MplayerPlayer(BasePlayer): return u'"{}"'.format(arg) def lineReceived(self, line): + if line == "Error parsing option slave-broken (option not found)": + self.quitReason = "This version of Syncplay is not compatible with mpv 0.5.0+. Use mpv 0.4.0 or check for an update to Syncplay." + match = self.RE_ANSWER.match(line) if not match: return @@ -149,7 +153,9 @@ class MplayerPlayer(BasePlayer): self._filenameAsk.set() elif name == "exiting": if value != 'Quit': - self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("media-player-error").format(value), True) + if self.quitReason == None: + self.quitReason = getMessage("media-player-error").format(value) + self.reactor.callFromThread(self._client.ui.showErrorMessage, self.quitReason, True) self.drop() @staticmethod From b63f0340fecf06237e74fbba672d07d0d5dc59b3 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 25 Aug 2014 19:20:47 +0100 Subject: [PATCH 002/142] Fixed mpv 0.5.0+ on linux --- syncplay/constants.py | 4 +++- syncplay/players/mplayer.py | 3 ++- syncplay/players/mpv.py | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 31613bf..49f8a1b 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -105,7 +105,9 @@ STYLE_NOFILEITEM_COLOR = 'blue' MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4'] # --quiet works with both mpv 0.2 and 0.3 -MPV_SLAVE_ARGS = ['--slave-broken', '--hr-seek=always', '--quiet', '--keep-open'] +MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open'] +MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken'] +MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','input-file=/dev/stdin'] VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', '--play-and-pause'] VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file'] diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 8dd89a5..4f2fbe3 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -127,7 +127,7 @@ class MplayerPlayer(BasePlayer): def lineReceived(self, line): if line == "Error parsing option slave-broken (option not found)": - self.quitReason = "This version of Syncplay is not compatible with mpv 0.5.0+. Use mpv 0.4.0 or check for an update to Syncplay." + self.quitReason = "This version of Syncplay is not compatible with mpv 0.5.0+ on Windows. Use mpv 0.4.3 or check for an update to Syncplay." match = self.RE_ANSWER.match(line) if not match: @@ -266,6 +266,7 @@ class MplayerPlayer(BasePlayer): def sendLine(self, line): try: line = (line.decode('utf8') + u"\n").encode('utf8') + print line self.__process.stdin.write(line) except IOError: pass diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 3799ec7..11c54c3 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -1,9 +1,13 @@ from syncplay.players.mplayer import MplayerPlayer from syncplay import constants -import os +import os, sys class MpvPlayer(MplayerPlayer): SLAVE_ARGS = constants.MPV_SLAVE_ARGS + if sys.platform.startswith('win'): + SLAVE_ARGS.extend(constants.MPV_SLAVE_ARGS_WINDOWS) + else: + SLAVE_ARGS.extend(constants.MPV_SLAVE_ARGS_NONWINDOWS) POSITION_QUERY = 'time-pos' OSD_QUERY = 'show_text' From a05b06625d3a938af13afffc64617297657e16a6 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 25 Aug 2014 19:24:29 +0100 Subject: [PATCH 003/142] Remove rnadom print --- syncplay/players/mplayer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 4f2fbe3..16d6266 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -266,7 +266,6 @@ class MplayerPlayer(BasePlayer): def sendLine(self, line): try: line = (line.decode('utf8') + u"\n").encode('utf8') - print line self.__process.stdin.write(line) except IOError: pass From cbb0b748c65329c6f8ff3055b4a09e91acc52a7f Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 25 Aug 2014 19:27:28 +0100 Subject: [PATCH 004/142] Fix missing -- in slave arg --- syncplay/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 49f8a1b..8a15430 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -107,7 +107,7 @@ MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', # --quiet works with both mpv 0.2 and 0.3 MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open'] MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken'] -MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','input-file=/dev/stdin'] +MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin'] VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', '--play-and-pause'] VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file'] From 18e424d0fa4d556dcdba5686f24de0daeedb5da0 Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 27 Aug 2014 14:39:26 +0100 Subject: [PATCH 005/142] Finalise mpv incompatible error message --- syncplay/messages.py | 1 + syncplay/players/mplayer.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index b1c6631..9fb9e03 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -71,6 +71,7 @@ en = { "server-timeout-error" : "Connection with server timed out", "mpc-slave-error" : "Unable to start MPC in slave mode!", "mpc-version-insufficient-error" : "MPC version not sufficient, please use `mpc-hc` >= `{}`", + "mpv-version-error" : "Syncplay is not compatible with this version of mpv. Please use a different version of mpv (e.g. Git HEAD).", "player-file-open-error" : "Player failed opening file", "player-path-error" : "Player path is not set properly", "hostname-empty-error" : "Hostname can't be empty", diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 16d6266..d995d99 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -127,7 +127,7 @@ class MplayerPlayer(BasePlayer): def lineReceived(self, line): if line == "Error parsing option slave-broken (option not found)": - self.quitReason = "This version of Syncplay is not compatible with mpv 0.5.0+ on Windows. Use mpv 0.4.3 or check for an update to Syncplay." + self.quitReason = getMessage("mpv-version-error") match = self.RE_ANSWER.match(line) if not match: From 36301381670b9bcc58bdb5ef21e7257081c1c11f Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 27 Aug 2014 19:27:15 +0100 Subject: [PATCH 006/142] Correct pause label text --- syncplay/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 9fb9e03..5632461 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -167,7 +167,7 @@ en = { "undoseek-guibuttonlabel" : "Undo seek", "togglepause-guibuttonlabel" : "Toggle pause", "play-guibuttonlabel" : "Play", - "pause-guibuttonlabel" : "Play", + "pause-guibuttonlabel" : "Pause", "roomuser-heading-label" : "Room / User", "fileplayed-heading-label" : "File being played", From c026be11147aa23070c1c5f374848974df78ca60 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 31 Aug 2014 20:33:11 +0100 Subject: [PATCH 007/142] Make mpv error check cover all parsing fails --- syncplay/players/mplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index d995d99..e376f71 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -126,7 +126,7 @@ class MplayerPlayer(BasePlayer): return u'"{}"'.format(arg) def lineReceived(self, line): - if line == "Error parsing option slave-broken (option not found)": + if "Error parsing option" in line: self.quitReason = getMessage("mpv-version-error") match = self.RE_ANSWER.match(line) From 2597dee602ac22aebe08d1a2c7a82406db10b3cc Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 2 Sep 2014 23:12:21 +0100 Subject: [PATCH 008/142] Use --start-time=0 to avoid VLC playback resumption --- syncplay/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 8a15430..c49e03e 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -109,7 +109,7 @@ MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open'] MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken'] MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin'] VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', - '--play-and-pause'] + '--play-and-pause', '--start-time=0'] VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file'] MPLAYER_ANSWER_REGEX = "^ANS_([a-zA-Z_-]+)=(.+)$|^(Exiting)\.\.\. \((.+)\)$" VLC_ANSWER_REGEX = r"(?:^(?P[a-zA-Z_]+)(?:\: )?(?P.*))" From 1c37e30c7d957fe932831efccdc39b62f32da996 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sat, 6 Sep 2014 21:11:23 +0200 Subject: [PATCH 009/142] Fixed crashing with mpv < 0.4 --- syncplay/players/mplayer.py | 9 ++++++--- syncplay/players/mpv.py | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index e376f71..36b7271 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -10,7 +10,6 @@ import os class MplayerPlayer(BasePlayer): speedSupported = True RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX) - SLAVE_ARGS = constants.MPLAYER_SLAVE_ARGS POSITION_QUERY = 'time_pos' OSD_QUERY = 'osd_show_text' @@ -153,7 +152,7 @@ class MplayerPlayer(BasePlayer): self._filenameAsk.set() elif name == "exiting": if value != 'Quit': - if self.quitReason == None: + if self.quitReason is None: self.quitReason = getMessage("media-player-error").format(value) self.reactor.callFromThread(self._client.ui.showErrorMessage, self.quitReason, True) self.drop() @@ -176,6 +175,10 @@ class MplayerPlayer(BasePlayer): def getIconPath(path): return constants.MPLAYER_ICONPATH + @staticmethod + def getStartupArgs(path): + return constants.MPLAYER_SLAVE_ARGS + @staticmethod def isValidPlayerPath(path): if "mplayer" in path and MplayerPlayer.getExpandedPath(path): @@ -225,7 +228,7 @@ class MplayerPlayer(BasePlayer): filePath = os.path.realpath(filePath) call = [playerPath, filePath] - call.extend(playerController.SLAVE_ARGS) + call.extend(playerController.getStartupArgs(playerPath)) if args: call.extend(args) # At least mpv may output escape sequences which result in syncplay diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 11c54c3..31979b9 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -1,15 +1,13 @@ +import re +import subprocess from syncplay.players.mplayer import MplayerPlayer from syncplay import constants import os, sys class MpvPlayer(MplayerPlayer): - SLAVE_ARGS = constants.MPV_SLAVE_ARGS - if sys.platform.startswith('win'): - SLAVE_ARGS.extend(constants.MPV_SLAVE_ARGS_WINDOWS) - else: - SLAVE_ARGS.extend(constants.MPV_SLAVE_ARGS_NONWINDOWS) POSITION_QUERY = 'time-pos' OSD_QUERY = 'show_text' + RE_VERSION = re.compile('.*mpv (\d)\.(\d)\.\d.*') def _setProperty(self, property_, value): self._listener.sendLine("no-osd set {} {}".format(property_, value)) @@ -23,6 +21,17 @@ class MpvPlayer(MplayerPlayer): def run(client, playerPath, filePath, args): return MpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) + @staticmethod + def getStartupArgs(path): + ver = MpvPlayer.RE_VERSION.search(subprocess.check_output([path, '--version'])) + new_mpv = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 5 + args = constants.MPV_SLAVE_ARGS + if sys.platform.startswith('win') or not new_mpv: + args.extend(constants.MPV_SLAVE_ARGS_WINDOWS) + else: + args.extend(constants.MPV_SLAVE_ARGS_NONWINDOWS) + return args + @staticmethod def getDefaultPlayerPathsList(): l = [] From fb121f7ba77f6048106dd2354e7e6fcae2770c25 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sat, 6 Sep 2014 21:50:04 +0200 Subject: [PATCH 010/142] Fixed issue with locale breaking vlc --- syncplay/players/vlc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index ba3145f..8f90a71 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -10,7 +10,6 @@ import socket import asynchat, asyncore import urllib from syncplay.messages import getMessage -import time class VlcPlayer(BasePlayer): speedSupported = True @@ -92,7 +91,7 @@ class VlcPlayer(BasePlayer): def setPosition(self, value): self._position = value - self._listener.sendLine("set-position: {}".format(value)) + self._listener.sendLine("set-position: {:n}".format(value)) def setPaused(self, value): self._paused = value From 11c53368cbfdf688dfcc6b377d5f8e2f0a99b377 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 7 Sep 2014 11:07:18 +0200 Subject: [PATCH 011/142] Updated GUI for controlled rooms --- buildPy2exe.py | 3 ++- resources/controller.png | Bin 0 -> 666 bytes resources/key_go.png | Bin 0 -> 744 bytes resources/page_white_key.png | Bin 0 -> 616 bytes syncplay/ui/gui.py | 18 ++++++++++++++++-- 5 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 resources/controller.png create mode 100644 resources/key_go.png create mode 100644 resources/page_white_key.png diff --git a/buildPy2exe.py b/buildPy2exe.py index 7f91acc..164146b 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -582,7 +582,8 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/timeline_marker.png','resources/control_play_blue.png', 'resources/mpc-hc.png','resources/mpc-hc64.png','resources/mplayer.png', 'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png', - 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/bullet_black.png' + 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/bullet_black.png', + 'resources/controller.png', 'resources/key_go.png', 'resources/page_white_key.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) diff --git a/resources/controller.png b/resources/controller.png new file mode 100644 index 0000000000000000000000000000000000000000..5cf76ed029a4468cfcd05c1a50089a98e718418f GIT binary patch literal 666 zcmV;L0%iS)P) zlg*1#VHC#iD%uAHty;DTg0}q?ty;8bDFzCDreJ80{BB+R< zJENR4PZMQi-K$n$6{M+e{`y zCzHvi{{^55g~BV5NOa+FIKC8%#WFEpk_S+;0uA4flMsu=wvk9g@eiQYYGoUZ#;;nf zwk?;-FdB`3VHmiWEa5y`gl|tCa~huHAQ%j?r0MVf0GiFFt=sK#C%yTk;Ec=V5(eW1 zy!^<*iw^+Bx5qH>dSE^ra{Kc6JlJfuBMv}5)@Yi~=Nw?K*=zV6|Ft zLOAX<8jU;B^k)=l4vT=l&3kn^9UxVL)9GYU)9I9}vfXZjQmOn(B@&4pQul^EU^1CN zHk*ZXIt^qGkTG9cEEbJkuV?WZo6QC!5(!Z8c%13?`&?OQ#B#^uF;uHnNTpKPc_2n0 zqyCI{R;g4puH&IXp%AEOGKB zFaZe_Htgq(0OW7HPl9o7Hk+@=e6O@xZTY%@-!Rz$1Rlkwh5!Hn07*qoM6N<$f($t; AN&o-= literal 0 HcmV?d00001 diff --git a/resources/key_go.png b/resources/key_go.png new file mode 100644 index 0000000000000000000000000000000000000000..30b0dc316e52dba388d88112d4c1cc32672fffbb GIT binary patch literal 744 zcmVP)jGlUfiHCke$)P}4*HwX@?mb` zzgiE#M5fLDxtj=lZ6q=+Lkt2$!wyVqxC~@X03De&Gn$t$kdWJig()R`(|L#ldNHNi zi?tK@-;xA1zab1r3XkO&T;m5wdrx2~XU8>fpl4v~7ZF1h+!NXGy*rQ6jw}?mrNKGI zL&zzIrIL-VTRiLE`+jy5wt-SCISiy4pO}x6>V>$`&V{7&3{GoOF)87oTao_ek0H|L zXxL5q?8f4p7$RLZL=Q4>?LH3$EqhS@^b{VAG@wL(0y*{DquI)BECvw!(ty8jr^s8DqzY3Mx|xw6AM%RhNVG>V(j48H=@2*+n87;k63kFsH&hc^Czx zU)p@TON5%2#gM-!LRIG_NS|MUrcZ`*_YPuLB^9Iro+W2D85SRofmAHM&xik`9B1#a z@o-oLow*L$!CJHqC_n){?E(&Zwhf|^V!qqZ#X+&c)jMMwsg3*W31W<{FoWOGV1 zuOTTStWS(&DYr&0v}HowTZPN*IY_RcCU%rj3Cs*;4T3Shy&sFSmGFOV!%!{P*`>;C zSiN43jAg&56(U(ojS}-tZUVHjYHp;RjQ0M0pRlXN=mLv{hk9Ebp9&~+Wj-T9IkpzWPWd#fZ)d=zV^~S`;LE*! Date: Sun, 7 Sep 2014 11:08:00 +0200 Subject: [PATCH 012/142] Reformated GUI code --- syncplay/ui/gui.py | 210 +++++++++++++++++++++++++-------------------- 1 file changed, 116 insertions(+), 94 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index a041a3c..9dab263 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1,5 +1,5 @@ -from PySide import QtGui #@UnresolvedImport -from PySide.QtCore import Qt, QSettings, QSize, QPoint #@UnresolvedImport +from PySide import QtGui # @UnresolvedImport +from PySide.QtCore import Qt, QSettings, QSize, QPoint # @UnresolvedImport from syncplay import utils, constants, version from syncplay.messages import getMessage import sys @@ -8,6 +8,7 @@ import re import os from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration + class MainWindow(QtGui.QMainWindow): def addClient(self, client): self._syncplayClient = client @@ -36,9 +37,9 @@ class MainWindow(QtGui.QMainWindow): except (): pass - + def promptFor(self, prompt=">", message=""): - #TODO: Prompt user + # TODO: Prompt user return None def showMessage(self, message, noTimestamp=False): @@ -51,13 +52,14 @@ class MainWindow(QtGui.QMainWindow): self.newMessage(message + "
") else: self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
") - + def showUserList(self, currentUser, rooms): self._usertreebuffer = QtGui.QStandardItemModel() self._usertreebuffer.setColumnCount(2) - self._usertreebuffer.setHorizontalHeaderLabels((getMessage("roomuser-heading-label"),getMessage("fileplayed-heading-label"))) + self._usertreebuffer.setHorizontalHeaderLabels( + (getMessage("roomuser-heading-label"), getMessage("fileplayed-heading-label"))) usertreeRoot = self._usertreebuffer.invisibleRootItem() - + for room in rooms: roomitem = QtGui.QStandardItem(room) if room == currentUser.room: @@ -65,14 +67,15 @@ class MainWindow(QtGui.QMainWindow): font.setWeight(QtGui.QFont.Bold) roomitem.setFont(font) blankitem = QtGui.QStandardItem("") - roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable) + roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable) blankitem.setFlags(blankitem.flags() & ~Qt.ItemIsEditable) usertreeRoot.appendRow((roomitem, blankitem)) for user in rooms[room]: useritem = QtGui.QStandardItem(user.username) fileitem = QtGui.QStandardItem("") if user.file: - fileitem = QtGui.QStandardItem(u"{} ({})".format(user.file['name'], formatTime(user.file['duration']))) + fileitem = QtGui.QStandardItem( + u"{} ({})".format(user.file['name'], formatTime(user.file['duration']))) if currentUser.file: sameName = sameFilename(user.file['name'], currentUser.file['name']) sameSize = sameFilesize(user.file['size'], currentUser.file['size']) @@ -83,11 +86,17 @@ class MainWindow(QtGui.QMainWindow): differentDuration = not sameDuration if sameName or sameRoom: if differentSize and sameDuration: - fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentsize-note"))) + fileitem = QtGui.QStandardItem( + u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), + getMessage("differentsize-note"))) elif differentSize and differentDuration: - fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentsizeandduration-note"))) + fileitem = QtGui.QStandardItem( + u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), + getMessage("differentsizeandduration-note"))) elif differentDuration: - fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentduration-note"))) + fileitem = QtGui.QStandardItem( + u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), + getMessage("differentduration-note"))) if sameRoom and (differentName or differentSize or differentDuration): fileitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) else: @@ -98,42 +107,42 @@ class MainWindow(QtGui.QMainWindow): font = QtGui.QFont() font.setWeight(QtGui.QFont.Bold) useritem.setFont(font) - useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) - fileitem.setFlags(fileitem.flags() & ~Qt.ItemIsEditable) + useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) + fileitem.setFlags(fileitem.flags() & ~Qt.ItemIsEditable) roomitem.appendRow((useritem, fileitem)) - + self.listTreeModel = self._usertreebuffer self.listTreeView.setModel(self.listTreeModel) self.listTreeView.setItemsExpandable(False) self.listTreeView.expandAll() self.listTreeView.resizeColumnToContents(0) self.listTreeView.resizeColumnToContents(1) - + def roomClicked(self, item): while item.parent().row() != -1: item = item.parent() self.joinRoom(item.sibling(item.row(), 0).data()) - + def userListChange(self): self._syncplayClient.showUserList() - + def showDebugMessage(self, message): print(message) - - def showErrorMessage(self, message, criticalerror = False): + + def showErrorMessage(self, message, criticalerror=False): message = unicode(message) if criticalerror: - QtGui.QMessageBox.critical(self,"Syncplay", message) + QtGui.QMessageBox.critical(self, "Syncplay", message) message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") message = message.replace("\n", "
") message = "".format(constants.STYLE_ERRORNOTIFICATION) + message + "" self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
") - def joinRoom(self, room = None): + def joinRoom(self, room=None): if room == None: room = self.roomInput.text() if room == "": - if self._syncplayClient.userlist.currentUser.file: + if self._syncplayClient.userlist.currentUser.file: room = self._syncplayClient.userlist.currentUser.file["name"] else: room = self._syncplayClient.defaultRoom @@ -150,45 +159,45 @@ class MainWindow(QtGui.QMainWindow): if t is None: return if sign: - t = self._syncplayClient.getGlobalPosition() + sign * t + t = self._syncplayClient.getGlobalPosition() + sign * t self._syncplayClient.setPosition(t) else: self.showErrorMessage("Invalid seek value") - + def undoSeek(self): tmp_pos = self._syncplayClient.getPlayerPosition() self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek) self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos - + def togglePause(self): self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused()) - + def play(self): self._syncplayClient.setPaused(False) - + def pause(self): self._syncplayClient.setPaused(True) - + def exitSyncplay(self): self._syncplayClient.stop() - + def closeEvent(self, event): self.exitSyncplay() self.saveSettings() - + def loadMediaBrowseSettings(self): settings = QSettings("Syncplay", "MediaBrowseDialog") settings.beginGroup("MediaBrowseDialog") self.mediadirectory = settings.value("mediadir", "") settings.endGroup() - + def saveMediaBrowseSettings(self): settings = QSettings("Syncplay", "MediaBrowseDialog") settings.beginGroup("MediaBrowseDialog") settings.setValue("mediadir", self.mediadirectory) settings.endGroup() - + def browseMediapath(self): self.loadMediaBrowseSettings() options = QtGui.QFileDialog.Options() @@ -200,12 +209,12 @@ class MainWindow(QtGui.QMainWindow): defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation) else: defaultdirectory = "" - browserfilter = "All files (*)" - fileName, filtr = QtGui.QFileDialog.getOpenFileName(self,getMessage("browseformedia-label"),defaultdirectory, - browserfilter, "", options) + browserfilter = "All files (*)" + fileName, filtr = QtGui.QFileDialog.getOpenFileName(self, getMessage("browseformedia-label"), defaultdirectory, + browserfilter, "", options) if fileName: if sys.platform.startswith('win'): - fileName = fileName.replace("/","\\") + fileName = fileName.replace("/", "\\") self.mediadirectory = os.path.dirname(fileName) self.saveMediaBrowseSettings() self._syncplayClient._player.openFile(fileName) @@ -215,7 +224,7 @@ class MainWindow(QtGui.QMainWindow): def identifyAsController(self): tooltip = "Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):" - name = "Identify as Room Controller" + name = "Identify as Room Controller" controlpassword, ok = QtGui.QInputDialog.getText(self, name, tooltip, QtGui.QLineEdit.Normal, "") if ok and controlpassword != '': self._syncplayClient.identifyAsController(controlpassword) @@ -228,11 +237,11 @@ class MainWindow(QtGui.QMainWindow): return 1 else: return None - + def setOffset(self): - newoffset, ok = QtGui.QInputDialog.getText(self,getMessage("setoffset-msgbox-label"), - getMessage("offsetinfo-msgbox-label"), QtGui.QLineEdit.Normal, - "") + newoffset, ok = QtGui.QInputDialog.getText(self, getMessage("setoffset-msgbox-label"), + getMessage("offsetinfo-msgbox-label"), QtGui.QLineEdit.Normal, + "") if ok and newoffset != '': o = re.match(constants.UI_OFFSET_REGEX, "o " + newoffset) if o: @@ -241,13 +250,13 @@ class MainWindow(QtGui.QMainWindow): if t is None: return if o.group('sign') == "/": - t = self._syncplayClient.getPlayerPosition() - t + t = self._syncplayClient.getPlayerPosition() - t elif sign: - t = self._syncplayClient.getUserOffset() + sign * t + t = self._syncplayClient.getUserOffset() + sign * t self._syncplayClient.setUserOffset(t) else: self.showErrorMessage("Invalid offset value") - + def openUserGuide(self): if sys.platform.startswith('linux'): self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/linux/") @@ -258,8 +267,8 @@ class MainWindow(QtGui.QMainWindow): def drop(self): self.close() - - def addTopLayout(self, window): + + def addTopLayout(self, window): window.topSplit = QtGui.QSplitter(Qt.Horizontal) window.outputLayout = QtGui.QVBoxLayout() @@ -269,11 +278,11 @@ class MainWindow(QtGui.QMainWindow): window.outputFrame = QtGui.QFrame() window.outputFrame.setLineWidth(0) window.outputFrame.setMidLineWidth(0) - window.outputLayout.setContentsMargins(0,0,0,0) + window.outputLayout.setContentsMargins(0, 0, 0, 0) window.outputLayout.addWidget(window.outputlabel) window.outputLayout.addWidget(window.outputbox) window.outputFrame.setLayout(window.outputLayout) - + window.listLayout = QtGui.QVBoxLayout() window.listTreeModel = QtGui.QStandardItemModel() window.listTreeView = QtGui.QTreeView() @@ -283,7 +292,7 @@ class MainWindow(QtGui.QMainWindow): window.listFrame = QtGui.QFrame() window.listFrame.setLineWidth(0) window.listFrame.setMidLineWidth(0) - window.listLayout.setContentsMargins(0,0,0,0) + window.listLayout.setContentsMargins(0, 0, 0, 0) window.listLayout.addWidget(window.listlabel) window.listLayout.addWidget(window.listTreeView) window.contactLabel = QtGui.QLabel() @@ -297,13 +306,13 @@ class MainWindow(QtGui.QMainWindow): window.contactLabel.setOpenExternalLinks(True) window.listLayout.addWidget(window.contactLabel) window.listFrame.setLayout(window.listLayout) - + window.topSplit.addWidget(window.outputFrame) window.topSplit.addWidget(window.listFrame) - window.topSplit.setStretchFactor(0,4) - window.topSplit.setStretchFactor(1,5) + window.topSplit.setStretchFactor(0, 4) + window.topSplit.setStretchFactor(1, 5) window.mainLayout.addWidget(window.topSplit) - window.topSplit.setSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Expanding) + window.topSplit.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) def addBottomLayout(self, window): window.bottomLayout = QtGui.QHBoxLayout() @@ -320,137 +329,149 @@ class MainWindow(QtGui.QMainWindow): def addRoomBox(self, window): window.roomGroup = QtGui.QGroupBox(getMessage("room-heading-label")) - + window.roomInput = QtGui.QLineEdit() window.roomInput.returnPressed.connect(self.joinRoom) - window.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'), getMessage("joinroom-guibuttonlabel")) + window.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'), + getMessage("joinroom-guibuttonlabel")) window.roomButton.pressed.connect(self.joinRoom) window.roomLayout = QtGui.QHBoxLayout() window.roomInput.setFixedWidth(150) self.roomButton.setToolTip(getMessage("joinroom-tooltip")) - + window.roomLayout.addWidget(window.roomInput) window.roomLayout.addWidget(window.roomButton) - + window.roomGroup.setLayout(window.roomLayout) window.roomGroup.setFixedSize(window.roomGroup.sizeHint()) - + def addSeekBox(self, window): window.seekGroup = QtGui.QGroupBox(getMessage("seek-heading-label")) - + window.seekInput = QtGui.QLineEdit() window.seekInput.returnPressed.connect(self.seekPosition) - window.seekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'clock_go.png'),getMessage("seektime-guibuttonlabel")) + window.seekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'clock_go.png'), + getMessage("seektime-guibuttonlabel")) window.seekButton.pressed.connect(self.seekPosition) self.seekButton.setToolTip(getMessage("seektime-tooltip")) - + window.seekLayout = QtGui.QHBoxLayout() window.seekInput.setText("0:00") window.seekInput.setFixedWidth(60) - + window.seekLayout.addWidget(window.seekInput) window.seekLayout.addWidget(window.seekButton) - + window.seekGroup.setLayout(window.seekLayout) window.seekGroup.setFixedSize(window.seekGroup.sizeHint()) - + def addMiscBox(self, window): window.miscGroup = QtGui.QGroupBox(getMessage("othercommands-heading-label")) - - window.unseekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'arrow_undo.png'),getMessage("undoseek-guibuttonlabel")) + + window.unseekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'arrow_undo.png'), + getMessage("undoseek-guibuttonlabel")) window.unseekButton.pressed.connect(self.undoSeek) self.unseekButton.setToolTip(getMessage("undoseek-tooltip")) window.miscLayout = QtGui.QHBoxLayout() window.miscLayout.addWidget(window.unseekButton) if constants.MERGE_PLAYPAUSE_BUTTONS == True: - window.playpauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'),getMessage("togglepause-guibuttonlabel")) + window.playpauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'), + getMessage("togglepause-guibuttonlabel")) window.playpauseButton.pressed.connect(self.togglePause) window.miscLayout.addWidget(window.playpauseButton) self.playpauseButton.setToolTip(getMessage("togglepause-tooltip")) else: - window.playButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_play_blue.png'),getMessage("play-guibuttonlabel")) + window.playButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_play_blue.png'), + getMessage("play-guibuttonlabel")) window.playButton.pressed.connect(self.play) window.playButton.setMaximumWidth(60) window.miscLayout.addWidget(window.playButton) - window.pauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'),getMessage("pause-guibuttonlabel")) + window.pauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'), + getMessage("pause-guibuttonlabel")) window.pauseButton.pressed.connect(self.pause) window.pauseButton.setMaximumWidth(60) window.miscLayout.addWidget(window.pauseButton) self.playButton.setToolTip(getMessage("play-tooltip")) self.pauseButton.setToolTip(getMessage("pause-tooltip")) - + window.miscGroup.setLayout(window.miscLayout) window.miscGroup.setFixedSize(window.miscGroup.sizeHint()) - + def addMenubar(self, window): window.menuBar = QtGui.QMenuBar() window.fileMenu = QtGui.QMenu(getMessage("file-menu-label"), self) - window.openAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'folder_explore.png'), getMessage("openmedia-menu-label")) + window.openAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'folder_explore.png'), + getMessage("openmedia-menu-label")) window.openAction.triggered.connect(self.browseMediapath) - window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), getMessage("file-menu-label")) + window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), + getMessage("file-menu-label")) window.exitAction.triggered.connect(self.exitSyncplay) window.menuBar.addMenu(window.fileMenu) - + window.advancedMenu = QtGui.QMenu(getMessage("advanced-menu-label"), self) - window.setoffsetAction = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'timeline_marker.png'),getMessage("setoffset-menu-label")) + window.setoffsetAction = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'timeline_marker.png'), + getMessage("setoffset-menu-label")) window.setoffsetAction.triggered.connect(self.setOffset) - window.createcontrolledroomAction = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'page_white_key.png'), "&Create controlled room suffix") + window.createcontrolledroomAction = window.advancedMenu.addAction( + QtGui.QIcon(self.resourcespath + 'page_white_key.png'), "&Create controlled room suffix") window.createcontrolledroomAction.triggered.connect(self.createControlledRoom) - window.identifyascontroller = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'key_go.png'), "&Identify as room controller") + window.identifyascontroller = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'key_go.png'), + "&Identify as room controller") window.identifyascontroller.triggered.connect(self.identifyAsController) window.menuBar.addMenu(window.advancedMenu) - + window.helpMenu = QtGui.QMenu(getMessage("help-menu-label"), self) - window.userguideAction = window.helpMenu.addAction(QtGui.QIcon(self.resourcespath + 'help.png'), getMessage("userguide-menu-label")) + window.userguideAction = window.helpMenu.addAction(QtGui.QIcon(self.resourcespath + 'help.png'), + getMessage("userguide-menu-label")) window.userguideAction.triggered.connect(self.openUserGuide) - + window.menuBar.addMenu(window.helpMenu) window.mainLayout.setMenuBar(window.menuBar) - + def addMainFrame(self, window): window.mainFrame = QtGui.QFrame() window.mainFrame.setLineWidth(0) window.mainFrame.setMidLineWidth(0) - window.mainFrame.setContentsMargins(0,0,0,0) + window.mainFrame.setContentsMargins(0, 0, 0, 0) window.mainFrame.setLayout(window.mainLayout) - + window.setCentralWidget(window.mainFrame) - + def newMessage(self, message): self.outputbox.moveCursor(QtGui.QTextCursor.End) self.outputbox.insertHtml(message) self.outputbox.moveCursor(QtGui.QTextCursor.End) - + def resetList(self): self.listbox.setText("") - + def newListItem(self, item): self.listbox.moveCursor(QtGui.QTextCursor.End) self.listbox.insertHtml(item) self.listbox.moveCursor(QtGui.QTextCursor.End) - + def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': event.acceptProposedAction() - + def dropEvent(self, event): rewindFile = False if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: - QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted + QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted rewindFile = True data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': if sys.platform.startswith('win'): - dropfilepath = unicode(urls[0].path().replace("/", "\\"))[1:] # Removes starting slash + dropfilepath = unicode(urls[0].path().replace("/", "\\"))[1:] # Removes starting slash else: dropfilepath = unicode(urls[0].path()) if rewindFile == False: @@ -459,14 +480,14 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient.setPosition(0) self._syncplayClient._player.openFile(dropfilepath) self._syncplayClient.setPosition(0) - + def saveSettings(self): settings = QSettings("Syncplay", "MainWindow") settings.beginGroup("MainWindow") settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.endGroup() - + def loadSettings(self): settings = QSettings("Syncplay", "MainWindow") settings.beginGroup("MainWindow") @@ -489,6 +510,7 @@ class MainWindow(QtGui.QMainWindow): self.addMainFrame(self) self.loadSettings() self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png")) - self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) + self.setWindowFlags( + self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.show() self.setAcceptDrops(True) \ No newline at end of file From 358109ad21e189081e70d6fd825e7fa388f728e8 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 7 Sep 2014 16:01:06 +0200 Subject: [PATCH 013/142] Made most of client-side changes regarding controlled rooms --- syncplay/client.py | 71 ++++++++++++++++++++++++++++++++++++---- syncplay/ui/consoleUI.py | 5 ++- syncplay/ui/gui.py | 3 ++ 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 026c4b7..26c7c49 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1,15 +1,15 @@ import hashlib import os.path +import random +import string import time -import re from twisted.internet.protocol import ClientFactory from twisted.internet import reactor, task from syncplay.protocols import SyncClientProtocol from syncplay import utils, constants from syncplay.messages import getMessage -import threading from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \ - PRIVACY_HIDDENFILENAME, FILENAME_STRIP_REGEX + PRIVACY_HIDDENFILENAME import collections class SyncClientFactory(ClientFactory): @@ -67,8 +67,9 @@ class SyncplayClient(object): self.ui = UiManager(self, ui) self.userlist = SyncplayUserlist(self.ui, self) self._protocol = None + """:type : SyncClientProtocol|None""" self._player = None - if config['room'] == None or config['room'] == '': + if config['room'] is None or config['room'] == '': config['room'] = config['name'] # ticket #58 self.defaultRoom = config['room'] self.playerPositionBeforeLastSeek = 0.0 @@ -99,7 +100,8 @@ class SyncplayClient(object): self._warnings = self._WarningManager(self._player, self.userlist, self.ui) if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']: - self.ui.showMessage(getMessage("relative-config-notification").format("; ".join(self._config['loadedRelativePaths'])), noPlayer=True, noTimestamp=True) + paths = "; ".join(self._config['loadedRelativePaths']) + self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True) def initProtocol(self, protocol): self._protocol = protocol @@ -234,7 +236,7 @@ class SyncplayClient(object): madeChangeOnPlayer = self._serverSeeked(position, setBy) if diff > self._config['rewindThreshold'] and not doSeek and not self._config['rewindOnDesync'] == False: madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy) - if (self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False): + if self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False: madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy) if paused == False and pauseChanged: madeChangeOnPlayer = self._serverUnpaused(setBy) @@ -317,7 +319,6 @@ class SyncplayClient(object): size = os.path.getsize(path) except OSError: # file not accessible (stream?) size = 0 - rawfilename = filename filename, size = self.__executePrivacySettings(filename, size) self.userlist.currentUser.setFile(filename, duration, size) self.sendFile() @@ -414,6 +415,41 @@ class SyncplayClient(object): if promptForAction: self.ui.promptFor(getMessage("enter-to-exit-prompt")) + def createControlledRoom(self): + controlPassword = RoomPasswordGenerator.generate_password() + # TODO (Client): Send request to server; handle success and failure + # TODO (Server): Process request, send response + self.ui.showMessage("Attempting to create controlled room suffix with password '{}'...".format(controlPassword)) + self._protocol.requestControlledRoom(controlPassword) + + def controlledRoomCreated(self, controlPassword, roomName): + # NOTE (Client): Triggered by protocol to handle createControlledRoom when room is created + self.ui.showMessage("Created controlled room suffix '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword)) + self.setRoom(roomName) + self.sendRoom() + self.ui.updateRoomName(roomName) + + def controlledRoomCreationError(self, errormsg): + # NOTE (Client): Triggered by protocol to handle createControlledRoom if controlled rooms are not supported by server or if password is malformed + # NOTE (Server): Triggered by protocol to handle createControlledRoom if password is malformed + self.ui.showErrorMessage("Failed to create the controlled room suffix for the following reason: {}.".format(errormsg)) + + def identifyAsController(self, controlPassword): + # TODO (Client): Send identification to server; handle success and failure + # TODO (Server): Process request, send response + self.ui.showMessage("Identifying as room controller with password '{}'...".format(controlPassword)) + self._protocol.requestControlledRoom(controlPassword) + + def controllerIdentificationError(self, errormsg): + # NOTE (Client): Triggered by protocol handling identiedAsController, e.g. on server response or not supported error + # NOTE (Server): Relevant error given in response to identifyAsController if password is wrong + self.ui.showErrorMessage("Failed to identify as a room controller for the following reason: {}.".format(errormsg)) + + def notControllerError(self, errormsg): + # NOTE (Client): Trigger when client gets a "not controller" error from server (e.g. due to illegal pauses, unpauses and seeks) + # NOTE (Server): Give "not controller" error when users try to perform illegal pause, unpause or seek + self.ui.showErrorMessage("There are currently people with 'room controller' status in this room. As such, only they can pause, unpause and seek. If you want to perform these actions then you must either identify as a controller or join a different room. See http://syncplay.pl/guide/ for more details.") + class _WarningManager(object): def __init__(self, player, userlist, ui): self._player = player @@ -662,5 +698,26 @@ class UiManager(object): def markEndOfUserlist(self): self.__ui.markEndOfUserlist() + def updateRoomName(self, room=""): + self.__ui.updateRoomName(room) + def drop(self): self.__ui.drop() + +class RoomPasswordGenerator(object): + @staticmethod + def generate_password(): + parts = ( + RoomPasswordGenerator._get_random_letters(2), + RoomPasswordGenerator._get_random_numbers(3), + RoomPasswordGenerator._get_random_numbers(3) + ) + return "{}-{}-{}".format(*parts) + + @staticmethod + def _get_random_letters(quantity): + return ''.join(random.choice(string.ascii_uppercase) for _ in xrange(quantity)) + + @staticmethod + def _get_random_numbers(quantity): + return ''.join(random.choice(string.digits) for _ in xrange(quantity)) diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index dd2bd84..57a5981 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -35,7 +35,10 @@ class ConsoleUI(threading.Thread): self._executeCommand(data) except EOFError: pass - + + def updateRoomName(self, room=""): + pass + def promptFor(self, prompt=">", message=""): if message <> "": print(message) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 9dab263..f0c7aa5 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -126,6 +126,9 @@ class MainWindow(QtGui.QMainWindow): def userListChange(self): self._syncplayClient.showUserList() + def updateRoomName(self, room=""): + self.roomInput.setText(room) + def showDebugMessage(self, message): print(message) From 525d186d83556d328d6b8c77ebf3f2e06dadba10 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 9 Sep 2014 21:58:18 +0100 Subject: [PATCH 014/142] Hack to hopefully fix VLC locale issue with radix without causing issue with thousand separator --- syncplay/players/vlc.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index 8f90a71..d8089fd 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -29,6 +29,14 @@ class VlcPlayer(BasePlayer): self._filename = None self._filepath = None self._filechanged = False + try: # Hack to fix locale issue without importing locale library + self.radixChar = "{:n}".format(1.5)[1:2] + if self.radixChar == "" or self.radixChar == "1" or self.radixChar == "5": + raise ValueError + except: + self._client.ui.showErrorMessage("Failed to determine locale. As a fallback Syncplay is using the following radix character: \".\".") + self.radixChar = "." + print self.radixChar self._durationAsk = threading.Event() self._filenameAsk = threading.Event() @@ -90,8 +98,7 @@ class VlcPlayer(BasePlayer): self._listener.sendLine("set-rate: {:.2n}".format(value)) def setPosition(self, value): - self._position = value - self._listener.sendLine("set-position: {:n}".format(value)) + self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar)) def setPaused(self, value): self._paused = value From 2742d22edef46e3c90eaec8027f9b164ba6e9e2c Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 9 Sep 2014 22:00:28 +0100 Subject: [PATCH 015/142] Remove print (oops) --- syncplay/players/vlc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index d8089fd..37d3e10 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -36,7 +36,6 @@ class VlcPlayer(BasePlayer): except: self._client.ui.showErrorMessage("Failed to determine locale. As a fallback Syncplay is using the following radix character: \".\".") self.radixChar = "." - print self.radixChar self._durationAsk = threading.Event() self._filenameAsk = threading.Event() From 91f4f0ba71506f1350f0d13d4f6bc8d3d512ab08 Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 10 Sep 2014 08:11:09 +0100 Subject: [PATCH 016/142] Use radixchar-agnostic tonumber in VLC intf (0.2.3) --- resources/lua/intf/syncplay.lua | 56 ++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/resources/lua/intf/syncplay.lua b/resources/lua/intf/syncplay.lua index 79eaa48..e104101 100644 --- a/resources/lua/intf/syncplay.lua +++ b/resources/lua/intf/syncplay.lua @@ -5,7 +5,7 @@ Principal author: Etoh Other contributors: DerGenaue, jb Project: http://syncplay.pl/ - Version: 0.2.2 + Version: 0.2.3 Note: * This interface module is intended to be used in conjunction with Syncplay. @@ -77,16 +77,7 @@ You may also need to re-copy the syncplay.lua file when you update VLC. --]==========================================================================] -local modulepath = config["modulepath"] -if(modulepath ~= nil) and (modulepath ~= "") then - -- Workaround for when the script is not being run from the usual VLC intf folder. - package.path = modulepath - pcall(require,"common") -else - require "common" -end - -local connectorversion = "0.2.2" +local connectorversion = "0.2.3" local durationdelay = 500000 -- Pause for get_duration command etc for increased reliability (uses microseconds) local loopsleepduration = 5000 -- Pause for every event loop (uses microseconds) local quitcheckfrequency = 20 -- Check whether VLC has closed every X loops @@ -118,9 +109,32 @@ local newtitle = 0 local running = true +function radixsafe_tonumber(str) + -- Version of tonumber that works with any radix character (but not thousand seperators) + -- Based on the public domain VLC common.lua us_tonumber() function + str = string.gsub(tostring(str), "[^0-9]", ".") + local s, i, d = string.match(str, "^([+-]?)(%d*)%.?(%d*)$") + if not s or not i or not d then + return nil + end + + if s == "-" then + s = -1 + else + s = 1 + end + if i == "" then + i = "0" + end + if d == nil or d == "" then + d = "0" + end + return s * (tonumber(i) + tonumber(d)/(10^string.len(d))) +end + -- Start hosting Syncplay interface. -port = tonumber(config["port"]) +port = radixsafe_tonumber(config["port"]) if (port == nil or port < 1) then port = 4123 end function quit_vlc() @@ -255,12 +269,12 @@ function set_time ( timetoset) if input then local response, errormsg, realtime, titletrack realtime = timetoset % titlemultiplier - oldtitle = tonumber(get_var("title", 0)) + oldtitle = radixsafe_tonumber(get_var("title", 0)) newtitle = (timetoset - realtime) / titlemultiplier if oldtitle ~= newtitle and newtitle > -1 then - set_var("title", tonumber(newtitle)) + set_var("title", radixsafe_tonumber(newtitle)) end - errormsg = set_var("time", tonumber(realtime)) + errormsg = set_var("time", radixsafe_tonumber(realtime)) return errormsg else return noinput @@ -392,7 +406,7 @@ function display_osd ( argument ) if input then osdarray = get_args(argument,3) --position, duration, message -> message, , position, duration (converted from seconds to microseconds) - local osdduration = tonumber(osdarray[2]) * 1000 * 1000 + local osdduration = radixsafe_tonumber(osdarray[2]) * 1000 * 1000 vlc.osd.message(osdarray[3],channel1,osdarray[1],osdduration) else errormsg = noinput @@ -424,11 +438,11 @@ function do_command ( command, argument) elseif command == "get-filepath" then response = "filepath"..msgseperator..errormerge(get_filepath())..msgterminator elseif command == "get-filename" then response = "filename"..msgseperator..errormerge(get_filename())..msgterminator elseif command == "get-title" then response = "title"..msgseperator..errormerge(get_var("title", 0))..msgterminator - elseif command == "set-position" then errormsg = set_time(tonumber(argument)) - elseif command == "seek-within-title" then errormsg = set_var("time", tonumber(argument)) + elseif command == "set-position" then errormsg = set_time(radixsafe_tonumber(argument)) + elseif command == "seek-within-title" then errormsg = set_var("time", radixsafe_tonumber(argument)) elseif command == "set-playstate" then errormsg = set_playstate(argument) - elseif command == "set-rate" then errormsg = set_var("rate", tonumber(argument)) - elseif command == "set-title" then errormsg = set_var("title", tonumber(argument)) + elseif command == "set-rate" then errormsg = set_var("rate", radixsafe_tonumber(argument)) + elseif command == "set-title" then errormsg = set_var("title", radixsafe_tonumber(argument)) elseif command == "display-osd" then errormsg = display_osd(argument) elseif command == "load-file" then response = load_file(argument) elseif command == "close-vlc" then quit_vlc() @@ -470,7 +484,7 @@ function set_playstate(argument) end if string.sub(vlc.misc.version(),1,2) == "1." then - vlc.msg.err("This version of VLC is not known to support version " .. connectorversion .. " of the Syncplay interface module on Windows. Please use VLC 2+.") + vlc.msg.err("This version of VLC is not known to support the Syncplay interface module. Please use VLC 2+.") quit_vlc() else l = vlc.net.listen_tcp(host, port) From c9d69c3990b625b89af8f666e73f5a3a46fbeb89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wr=C3=B3bel?= Date: Fri, 12 Sep 2014 23:50:31 +0200 Subject: [PATCH 017/142] Little cleaning up in Makefile --- GNUmakefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index c7c57c3..7353c3a 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -18,8 +18,6 @@ endif SHARE_PATH = ${PREFIX}$(BASE_PATH)/share common: - -mkdir -p $(LIB_PATH)/syncplay/resources/ - -mkdir -p $(LIB_PATH)/syncplay/resources/lua -mkdir -p $(LIB_PATH)/syncplay/resources/lua/intf -mkdir -p $(APP_SHORTCUT_PATH) -mkdir -p $(SHARE_PATH)/app-install/icons From 27bd1b38d326af9732b7567cd6144c0ebb8555a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wr=C3=B3bel?= Date: Fri, 12 Sep 2014 23:51:52 +0200 Subject: [PATCH 018/142] BSD support in Makefile --- GNUmakefile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 7353c3a..c52c09a 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -4,9 +4,27 @@ ifndef VLC_SUPPORT VLC_SUPPORT = true endif +ifeq ($(shell uname)),FreeBSD) + BSD = true +endif +ifeq ($(shell uname)),NetBSD) + BSD = true +endif +ifeq ($(shell uname)),OpenBSD) + BSD = true +endif +ifeq ($(shell uname)),DragonFly) + BSD = true +endif + ifeq ($(SINGLE_USER),false) +ifneq ($(BSD),true) BASE_PATH = /usr VLC_LIB_PATH = ${PREFIX}/usr/lib +else + BASE_PATH = /usr/local + VLC_LIB_PATH = ${PREFIX}/usr/local/lib +endif else BASE_PATH = ${HOME}/.local VLC_LIB_PATH = ${HOME}/.local/share From 8ba51ba1116aa5523c04273ea707d0dcb3c9c78f Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 13 Sep 2014 01:19:44 +0100 Subject: [PATCH 019/142] Fix new mpv bug by re-requesting info that was requested prematurely --- syncplay/players/mplayer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 36b7271..343a201 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -128,6 +128,15 @@ class MplayerPlayer(BasePlayer): if "Error parsing option" in line: self.quitReason = getMessage("mpv-version-error") + if "Failed to get value of property" in line: + if "filename" in line: + self._getFilename() + elif "length" in line: + self._getLength() + elif "path" in line: + self._getFilepath() + return + match = self.RE_ANSWER.match(line) if not match: return From db6bb28afbb5093b7d750a90894e8299e56932de Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 13 Sep 2014 01:22:47 +0100 Subject: [PATCH 020/142] Fix new mpv bug by re-requesting info that was requested prematurely --- syncplay/players/mplayer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 36b7271..343a201 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -128,6 +128,15 @@ class MplayerPlayer(BasePlayer): if "Error parsing option" in line: self.quitReason = getMessage("mpv-version-error") + if "Failed to get value of property" in line: + if "filename" in line: + self._getFilename() + elif "length" in line: + self._getLength() + elif "path" in line: + self._getFilepath() + return + match = self.RE_ANSWER.match(line) if not match: return From 655b638097ff5fa47b0f5b7ae737ac9b8af6d96b Mon Sep 17 00:00:00 2001 From: Etoh Date: Sat, 13 Sep 2014 01:25:08 +0100 Subject: [PATCH 021/142] Fix new mpv bug (premature info requests) --- syncplay/players/mplayer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 36b7271..d836f4a 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -127,6 +127,15 @@ class MplayerPlayer(BasePlayer): def lineReceived(self, line): if "Error parsing option" in line: self.quitReason = getMessage("mpv-version-error") + + if "Failed to get value of property" in line: + if "filename" in line: + self._getFilename() + elif "length" in line: + self._getLength() + elif "path" in line: + self._getFilepath() + return match = self.RE_ANSWER.match(line) if not match: From 1c660244ef8ab2333df45a95ff787885b2358854 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 14 Sep 2014 12:52:59 +0100 Subject: [PATCH 022/142] Hopefully fix new mpv file change issues --- syncplay/constants.py | 3 ++- syncplay/players/mplayer.py | 37 ++++++++++++++++++---------- syncplay/players/mpv.py | 49 ++++++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index c49e03e..641bae8 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -87,6 +87,7 @@ MPC_LOCK_WAIT_TIME = 0.2 MPC_RETRY_WAIT_TIME = 0.01 MPC_MAX_RETRIES = 30 MPC_PAUSE_TOGGLE_DELAY = 0.05 +MPV_NEWFILE_IGNORE_TIME = 1 VLC_OPEN_MAX_WAIT_TIME = 15 VLC_MIN_PORT = 10000 VLC_MAX_PORT = 55000 @@ -105,7 +106,7 @@ STYLE_NOFILEITEM_COLOR = 'blue' MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4'] # --quiet works with both mpv 0.2 and 0.3 -MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open'] +MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open', '--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n'] MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken'] MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin'] VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 343a201..98d72df 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -23,6 +23,8 @@ class MplayerPlayer(BasePlayer): self._filename = None self._filepath = None self.quitReason = None + self.lastLoadedTime = None + self.fileLoaded = False try: self._listener = self.__Listener(self, playerPath, filePath, args) except ValueError: @@ -65,6 +67,9 @@ class MplayerPlayer(BasePlayer): self.reactor.callLater(0, self._client.initPlayer, self) self._onFileUpdate() + def _onMPVFileUpdate(self): + pass + def askForStatus(self): self._positionAsk.clear() self._pausedAsk.clear() @@ -87,6 +92,7 @@ class MplayerPlayer(BasePlayer): self._setProperty('speed', "{:.2f}".format(value)) def openFile(self, filePath): + self._clearFileLoaded() self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) self._onFileUpdate() if self._paused != self._client.getGlobalPaused(): @@ -124,18 +130,17 @@ class MplayerPlayer(BasePlayer): arg = arg.replace('"', '\\"') return u'"{}"'.format(arg) - def lineReceived(self, line): - if "Error parsing option" in line: - self.quitReason = getMessage("mpv-version-error") + def _fileIsLoaded(self): + return True - if "Failed to get value of property" in line: - if "filename" in line: - self._getFilename() - elif "length" in line: - self._getLength() - elif "path" in line: - self._getFilepath() - return + def _clearFileLoaded(self): + pass + + def _handleMPVLines(self, line): + pass + + def lineReceived(self, line): + self._handleMPVLines(line) match = self.RE_ANSWER.match(line) if not match: @@ -145,10 +150,16 @@ class MplayerPlayer(BasePlayer): name = name.lower() if name == self.POSITION_QUERY: - self._position = float(value) + if self._fileIsLoaded(): + self._position = float(value) + else: + self._position = self._client.getGlobalPosition() self._positionAsk.set() elif name == "pause": - self._paused = bool(value == 'yes') + if self._fileIsLoaded(): + self._paused = bool(value == 'yes') + else: + self._paused = self._client.getGlobalPaused() self._pausedAsk.set() elif name == "length": self._duration = float(value) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 31979b9..4ba7bcc 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -1,8 +1,9 @@ import re import subprocess from syncplay.players.mplayer import MplayerPlayer +from syncplay.messages import getMessage from syncplay import constants -import os, sys +import os, sys, time class MpvPlayer(MplayerPlayer): POSITION_QUERY = 'time-pos' @@ -17,6 +18,52 @@ class MpvPlayer(MplayerPlayer): self._paused = not self._paused self._listener.sendLine('cycle pause') + def _onFileUpdate(self): + pass + + def _clearFileLoaded(self): + self.fileLoaded = False + self.lastLoadedTime = None + + def _handleMPVLines(self, line): + if "Error parsing option" in line: + self.quitReason = getMessage("mpv-version-error") + + elif line == "": + self._clearFileLoaded() + + elif line == "": + self._onMPVFileUpdate() + + elif "Failed to get value of property" in line: + if "filename" in line: + self._getFilename() + elif "length" in line: + self._getLength() + elif "path" in line: + self._getFilepath() + elif "time-pos" in line: + self.setPosition(self._client.getGlobalPosition()) + self._positionAsk.set() + raise ValueError + + elif "Playing:" in line: + self._clearFileLoaded() + + def _onMPVFileUpdate(self): + self.fileLoaded = True + self.lastLoadedTime = time.time() + self.reactor.callFromThread(self._client.updateFile, self._filename, self._duration, self._filepath) + self.reactor.callFromThread(self.setPosition, self._client.getGlobalPosition()) + if self._paused != self._client.getGlobalPaused(): + self.reactor.callFromThread(self._client.getGlobalPaused) + + def _fileIsLoaded(self): + if self.fileLoaded == True and self.lastLoadedTime != None and time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME): + return True + else: + return False + @staticmethod def run(client, playerPath, filePath, args): return MpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) From a99a70159ab1905ee1ec7ebcf5b6dda80ab8b89c Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 14 Sep 2014 13:40:45 +0100 Subject: [PATCH 023/142] mpv don't raise ValueError --- syncplay/players/mpv.py | 1 - 1 file changed, 1 deletion(-) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 4ba7bcc..1a88a7f 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -45,7 +45,6 @@ class MpvPlayer(MplayerPlayer): elif "time-pos" in line: self.setPosition(self._client.getGlobalPosition()) self._positionAsk.set() - raise ValueError elif "Playing:" in line: self._clearFileLoaded() From 89d0a262cc48320d6271a27e81f65739e8fcce46 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 14 Sep 2014 19:16:16 +0100 Subject: [PATCH 024/142] Refactor Mplayer/mpv --- syncplay/players/mplayer.py | 30 ++++++++++++++---------------- syncplay/players/mpv.py | 18 +++++++++++++++++- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 98d72df..f4a8d1f 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -67,9 +67,6 @@ class MplayerPlayer(BasePlayer): self.reactor.callLater(0, self._client.initPlayer, self) self._onFileUpdate() - def _onMPVFileUpdate(self): - pass - def askForStatus(self): self._positionAsk.clear() self._pausedAsk.clear() @@ -91,9 +88,11 @@ class MplayerPlayer(BasePlayer): def setSpeed(self, value): self._setProperty('speed', "{:.2f}".format(value)) - def openFile(self, filePath): - self._clearFileLoaded() + def _loadFile(self, filePath): self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) + + def openFile(self, filePath): + self._loadFile(filePath) self._onFileUpdate() if self._paused != self._client.getGlobalPaused(): self.setPaused(self._client.getGlobalPaused()) @@ -136,30 +135,29 @@ class MplayerPlayer(BasePlayer): def _clearFileLoaded(self): pass - def _handleMPVLines(self, line): + def _handleUnknownLine(self, line): pass - def lineReceived(self, line): - self._handleMPVLines(line) + def _storePosition(self, value): + self._position = value + def _storePauseState(self, value): + self._paused = value + + def lineReceived(self, line): match = self.RE_ANSWER.match(line) if not match: + self._handleUnknownLine(line) return name, value =[m for m in match.groups() if m] name = name.lower() if name == self.POSITION_QUERY: - if self._fileIsLoaded(): - self._position = float(value) - else: - self._position = self._client.getGlobalPosition() + self._storePosition(float(value)) self._positionAsk.set() elif name == "pause": - if self._fileIsLoaded(): - self._paused = bool(value == 'yes') - else: - self._paused = self._client.getGlobalPaused() + self._storePauseState(bool(value == 'yes')) self._pausedAsk.set() elif name == "length": self._duration = float(value) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 1a88a7f..9b5717d 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -18,6 +18,18 @@ class MpvPlayer(MplayerPlayer): self._paused = not self._paused self._listener.sendLine('cycle pause') + def _storePosition(self, value): + if self._fileIsLoaded(): + self._position = value + else: + self._position = self._client.getGlobalPosition() + + def _storePauseState(self, value): + if self._fileIsLoaded(): + self._paused = bool(value == 'yes') + else: + self._paused = self._client.getGlobalPaused() + def _onFileUpdate(self): pass @@ -25,7 +37,11 @@ class MpvPlayer(MplayerPlayer): self.fileLoaded = False self.lastLoadedTime = None - def _handleMPVLines(self, line): + def _loadFile(self, filePath): + self._clearFileLoaded() + self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) + + def _handleUnknownLine(self, line): if "Error parsing option" in line: self.quitReason = getMessage("mpv-version-error") From d59e183662986c54ec1cb150087248352707d133 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 14 Sep 2014 19:34:34 +0100 Subject: [PATCH 025/142] Use proper pause value --- syncplay/players/mpv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 9b5717d..9280fde 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -26,7 +26,7 @@ class MpvPlayer(MplayerPlayer): def _storePauseState(self, value): if self._fileIsLoaded(): - self._paused = bool(value == 'yes') + self._paused = value else: self._paused = self._client.getGlobalPaused() From 9ca2c54a69b18588e254ec176f7302b4db98a159 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 14 Sep 2014 22:23:42 +0100 Subject: [PATCH 026/142] Unbreak shift+drag mpv reset --- syncplay/players/mplayer.py | 1 + syncplay/players/mpv.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index f4a8d1f..06624a5 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -25,6 +25,7 @@ class MplayerPlayer(BasePlayer): self.quitReason = None self.lastLoadedTime = None self.fileLoaded = False + self._resetPosition = False try: self._listener = self.__Listener(self, playerPath, filePath, args) except ValueError: diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 9280fde..6de5fc0 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -19,7 +19,9 @@ class MpvPlayer(MplayerPlayer): self._listener.sendLine('cycle pause') def _storePosition(self, value): - if self._fileIsLoaded(): + if self._resetPosition: + self._position = 0 + elif self._fileIsLoaded(): self._position = value else: self._position = self._client.getGlobalPosition() @@ -63,13 +65,17 @@ class MpvPlayer(MplayerPlayer): self._positionAsk.set() elif "Playing:" in line: + if self._position < 1: + self._resetPosition = True self._clearFileLoaded() def _onMPVFileUpdate(self): self.fileLoaded = True self.lastLoadedTime = time.time() self.reactor.callFromThread(self._client.updateFile, self._filename, self._duration, self._filepath) - self.reactor.callFromThread(self.setPosition, self._client.getGlobalPosition()) + if not self._resetPosition: + self.reactor.callFromThread(self.setPosition, self._client.getGlobalPosition()) + self._resetPosition = False if self._paused != self._client.getGlobalPaused(): self.reactor.callFromThread(self._client.getGlobalPaused) From b9f20dfb0762686fd879e2f463c7b7fe97914221 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 26 Sep 2014 13:01:40 +0100 Subject: [PATCH 027/142] mpv: Handle old vers / exceptions better --- syncplay/players/mpv.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 6de5fc0..61309a8 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -44,7 +44,7 @@ class MpvPlayer(MplayerPlayer): self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) def _handleUnknownLine(self, line): - if "Error parsing option" in line: + if "Error parsing option" in line or "Error parsing commandline option" in line: self.quitReason = getMessage("mpv-version-error") elif line == "": @@ -91,7 +91,10 @@ class MpvPlayer(MplayerPlayer): @staticmethod def getStartupArgs(path): - ver = MpvPlayer.RE_VERSION.search(subprocess.check_output([path, '--version'])) + try: + ver = MpvPlayer.RE_VERSION.search(subprocess.check_output([path, '--version'])) + except: + ver = None new_mpv = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 5 args = constants.MPV_SLAVE_ARGS if sys.platform.startswith('win') or not new_mpv: From 634124b4e5382a393dc5e48718e9d6156ca04a33 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 26 Sep 2014 13:40:20 +0100 Subject: [PATCH 028/142] mpv: Hopefully fix reset on regular file change bug --- syncplay/players/basePlayer.py | 2 +- syncplay/players/mpc.py | 2 +- syncplay/players/mplayer.py | 6 ++++-- syncplay/players/mpv.py | 15 ++++++++++----- syncplay/players/vlc.py | 2 +- syncplay/ui/gui.py | 2 +- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/syncplay/players/basePlayer.py b/syncplay/players/basePlayer.py index 6fffa3d..97fc2a2 100644 --- a/syncplay/players/basePlayer.py +++ b/syncplay/players/basePlayer.py @@ -49,7 +49,7 @@ class BasePlayer(object): ''' @type filePath: string ''' - def openFile(self, filePath): + def openFile(self, filePath, resetPosition=False): raise NotImplementedError() diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index 0dfef45..d23d857 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -385,7 +385,7 @@ class MPCHCAPIPlayer(BasePlayer): if filePath: self.openFile(filePath) - def openFile(self, filePath): + def openFile(self, filePath, resetPosition=False): self._mpcApi.openFile(filePath) def displayMessage(self, message, duration = (constants.OSD_DURATION*1000)): diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 06624a5..d79c048 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -25,7 +25,7 @@ class MplayerPlayer(BasePlayer): self.quitReason = None self.lastLoadedTime = None self.fileLoaded = False - self._resetPosition = False + self.lastResetTime = None try: self._listener = self.__Listener(self, playerPath, filePath, args) except ValueError: @@ -92,7 +92,9 @@ class MplayerPlayer(BasePlayer): def _loadFile(self, filePath): self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) - def openFile(self, filePath): + def openFile(self, filePath, resetPosition=False): + if resetPosition: + self.lastResetTime = time.time() self._loadFile(filePath) self._onFileUpdate() if self._paused != self._client.getGlobalPaused(): diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 61309a8..6260ada 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -19,7 +19,7 @@ class MpvPlayer(MplayerPlayer): self._listener.sendLine('cycle pause') def _storePosition(self, value): - if self._resetPosition: + if self._recentlyReset(): self._position = 0 elif self._fileIsLoaded(): self._position = value @@ -65,17 +65,22 @@ class MpvPlayer(MplayerPlayer): self._positionAsk.set() elif "Playing:" in line: - if self._position < 1: - self._resetPosition = True self._clearFileLoaded() + def _recentlyReset(self): + if not self.lastResetTime: + return False + elif time.time() < self.lastResetTime + constants.MPV_NEWFILE_IGNORE_TIME: + return True + else: + return False + def _onMPVFileUpdate(self): self.fileLoaded = True self.lastLoadedTime = time.time() self.reactor.callFromThread(self._client.updateFile, self._filename, self._duration, self._filepath) - if not self._resetPosition: + if not (self._recentlyReset()): self.reactor.callFromThread(self.setPosition, self._client.getGlobalPosition()) - self._resetPosition = False if self._paused != self._client.getGlobalPaused(): self.reactor.callFromThread(self._client.getGlobalPaused) diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index 4ca0d91..fe6f79a 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -117,7 +117,7 @@ class VlcPlayer(BasePlayer): def _isASCII (self, s): return all(ord(c) < 128 for c in s) - def openFile(self, filePath): + def openFile(self, filePath, resetPosition=False): if self._isASCII(filePath): self._listener.sendLine('load-file: {}'.format(filePath.encode('ascii', 'ignore'))) else: diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 476c700..4f8c32b 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -443,7 +443,7 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient._player.openFile(dropfilepath) else: self._syncplayClient.setPosition(0) - self._syncplayClient._player.openFile(dropfilepath) + self._syncplayClient._player.openFile(dropfilepath, resetPosition=True) self._syncplayClient.setPosition(0) def saveSettings(self): From 442e753a115faeaf408dfd1e7e8397b537da0973 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 27 Sep 2014 00:27:39 +0100 Subject: [PATCH 029/142] mpv: Hopefully fix mpv compatability issues --- syncplay/constants.py | 4 +++- syncplay/players/mpv.py | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index fe5a453..91218a6 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -108,9 +108,11 @@ STYLE_NOFILEITEM_COLOR = 'blue' MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4'] # --quiet works with both mpv 0.2 and 0.3 -MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open', '--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n'] +MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open'] MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken'] MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin'] +MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n'] +MPV_NEW_VERSION = False VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', '--play-and-pause', '--start-time=0'] VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file'] diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 6260ada..d6a07b5 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -33,16 +33,35 @@ class MpvPlayer(MplayerPlayer): self._paused = self._client.getGlobalPaused() def _onFileUpdate(self): - pass + if not constants.MPV_NEW_VERSION: + oldFilename = self._filename + oldLength = self._duration + oldFilepath = self._filepath + self._fileUpdateClearEvents() + self._getFilename() + self._getLength() + self._getFilepath() + self._fileUpdateWaitEvents() + if (self._filename != oldFilename) or (self._duration != oldLength) or (self._filepath != oldFilepath): + self._client.updateFile(self._filename, self._duration, self._filepath) def _clearFileLoaded(self): self.fileLoaded = False self.lastLoadedTime = None def _loadFile(self, filePath): - self._clearFileLoaded() + if constants.MPV_NEW_VERSION: + self._clearFileLoaded() self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) + def openFile(self, filePath, resetPosition=False): + if resetPosition: + self.lastResetTime = time.time() + self._loadFile(filePath) + if self._paused != self._client.getGlobalPaused(): + self.setPaused(self._client.getGlobalPaused()) + self.setPosition(self._client.getGlobalPosition()) + def _handleUnknownLine(self, line): if "Error parsing option" in line or "Error parsing commandline option" in line: self.quitReason = getMessage("mpv-version-error") @@ -65,7 +84,13 @@ class MpvPlayer(MplayerPlayer): self._positionAsk.set() elif "Playing:" in line: - self._clearFileLoaded() + if constants.MPV_NEW_VERSION: + self._clearFileLoaded() + else: + self.fileLoaded = True + if not self._recentlyReset(): + self._onMPVFileUpdate() + self.reactor.callFromThread(self._onFileUpdate) def _recentlyReset(self): if not self.lastResetTime: @@ -100,9 +125,11 @@ class MpvPlayer(MplayerPlayer): ver = MpvPlayer.RE_VERSION.search(subprocess.check_output([path, '--version'])) except: ver = None - new_mpv = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 5 + constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 5 args = constants.MPV_SLAVE_ARGS - if sys.platform.startswith('win') or not new_mpv: + if constants.MPV_NEW_VERSION or sys.platform.startswith('win'): + args.extend(constants.MPV_SLAVE_ARGS_NEW) + if sys.platform.startswith('win') or not constants.MPV_NEW_VERSION: args.extend(constants.MPV_SLAVE_ARGS_WINDOWS) else: args.extend(constants.MPV_SLAVE_ARGS_NONWINDOWS) From 0b87e087ba297c21f82c777d009266731aabadd8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 4 Oct 2014 00:43:48 +0100 Subject: [PATCH 030/142] Initial controller + ready icon code --- resources/tick.png | Bin 0 -> 537 bytes syncplay/constants.py | 4 +++- syncplay/ui/gui.py | 50 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 resources/tick.png diff --git a/resources/tick.png b/resources/tick.png new file mode 100644 index 0000000000000000000000000000000000000000..a9925a06ab02db30c1e7ead9c701c15bc63145cb GIT binary patch literal 537 zcmV+!0_OdRP)Hs{AQG2a)rMyf zFQK~pm1x3+7!nu%-M`k}``c>^00{o_1pjWJUTfl8mg=3qGEl8H@}^@w`VUx0_$uy4 z2FhRqKX}xI*?Tv1DJd8z#F#0c%*~rM30HE1@2o5m~}ZyoWhqv>ql{V z1ZGE0lgcoK^lx+eqc*rAX1Ky;Xx3U%u#zG!m-;eD1Qsn@kf3|F9qz~|95=&g3(7!X zB}JAT>RU;a%vaNOGnJ%e1=K6eAh43c(QN8RQ6~GP%O}Jju$~Ld*%`mO1p Date: Sat, 4 Oct 2014 01:04:34 +0100 Subject: [PATCH 031/142] Add tick graphic to installer --- buildPy2exe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 164146b..1692e95 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -583,7 +583,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/mpc-hc.png','resources/mpc-hc64.png','resources/mplayer.png', 'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png', 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/bullet_black.png', - 'resources/controller.png', 'resources/key_go.png', 'resources/page_white_key.png' + 'resources/controller.png', 'resources/key_go.png', 'resources/page_white_key.png', 'resources/tick.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) From cdce17b10b343fe2c1723bc85eb8fa39fd8e8eb3 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 5 Oct 2014 11:08:28 +0200 Subject: [PATCH 032/142] Created protocol exchanges for creating controlled rooms --- syncplay/client.py | 28 ++++++++------------- syncplay/protocols.py | 43 ++++++++++++++++++++++++++++---- syncplay/server.py | 57 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 26c7c49..bcb504b 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -417,38 +417,30 @@ class SyncplayClient(object): def createControlledRoom(self): controlPassword = RoomPasswordGenerator.generate_password() - # TODO (Client): Send request to server; handle success and failure - # TODO (Server): Process request, send response self.ui.showMessage("Attempting to create controlled room suffix with password '{}'...".format(controlPassword)) self._protocol.requestControlledRoom(controlPassword) def controlledRoomCreated(self, controlPassword, roomName): - # NOTE (Client): Triggered by protocol to handle createControlledRoom when room is created self.ui.showMessage("Created controlled room suffix '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword)) self.setRoom(roomName) self.sendRoom() + self._protocol.requestControlledRoom(controlPassword) self.ui.updateRoomName(roomName) - def controlledRoomCreationError(self, errormsg): - # NOTE (Client): Triggered by protocol to handle createControlledRoom if controlled rooms are not supported by server or if password is malformed - # NOTE (Server): Triggered by protocol to handle createControlledRoom if password is malformed - self.ui.showErrorMessage("Failed to create the controlled room suffix for the following reason: {}.".format(errormsg)) - def identifyAsController(self, controlPassword): - # TODO (Client): Send identification to server; handle success and failure - # TODO (Server): Process request, send response self.ui.showMessage("Identifying as room controller with password '{}'...".format(controlPassword)) self._protocol.requestControlledRoom(controlPassword) - def controllerIdentificationError(self, errormsg): - # NOTE (Client): Triggered by protocol handling identiedAsController, e.g. on server response or not supported error - # NOTE (Server): Relevant error given in response to identifyAsController if password is wrong - self.ui.showErrorMessage("Failed to identify as a room controller for the following reason: {}.".format(errormsg)) + def controllerIdentificationError(self): + self.ui.showErrorMessage("Failed to identify as a room controller.") - def notControllerError(self, errormsg): - # NOTE (Client): Trigger when client gets a "not controller" error from server (e.g. due to illegal pauses, unpauses and seeks) - # NOTE (Server): Give "not controller" error when users try to perform illegal pause, unpause or seek - self.ui.showErrorMessage("There are currently people with 'room controller' status in this room. As such, only they can pause, unpause and seek. If you want to perform these actions then you must either identify as a controller or join a different room. See http://syncplay.pl/guide/ for more details.") + def controllerIdentificationSuccess(self): + # TODO: More UI stuff + self.ui.showErrorMessage("Authenticated as a room controller") + + # TODO: A person authenticated as a room controller + # TODO: Disable UI's "Create new Controlled Room when in Controlled Room" + # TODO: Disable authenticate when authenticated class _WarningManager(object): def __init__(self, player, userlist, ui): diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 8e4e14a..a45b4be 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -114,13 +114,21 @@ class SyncClientProtocol(JSONCommandProtocol): self._client.userlist.modUser(username, room, file_) def handleSet(self, settings): - for set_ in settings.iteritems(): - command = set_[0] + for (command, values) in settings.iteritems(): if command == "room": - roomName = set_[1]["name"] if set_[1].has_key("name") else None + roomName = values["name"] if values.has_key("name") else None self._client.setRoom(roomName) elif command == "user": - self._SetUser(set_[1]) + self._SetUser(values) + elif command == "controllerAuth": + if values['success']: + self._client.controllerIdentificationSuccess() + else: + self._client.controllerIdentificationError() + elif command == "newControlledRoom": + controlPassword = values['password'] + roomName = values['roomName'] + self._client.controlledRoomCreated(controlPassword, roomName) def sendSet(self, setting): self.sendMessage({"Set": setting}) @@ -210,8 +218,15 @@ class SyncClientProtocol(JSONCommandProtocol): state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly self.sendMessage({"State": state}) + def requestControlledRoom(self, password): + self.sendSet({ + "controllerAuth": { + "password": password + } + }) + def handleError(self, error): - self.dropWithError(error["message"]) # TODO: more processing and fallbacking + self.dropWithError(error["message"]) def sendError(self, message): self.sendMessage({"Error": {"message": message}}) @@ -311,10 +326,28 @@ class SyncServerProtocol(JSONCommandProtocol): self._factory.setWatcherRoom(self._watcher, roomName) elif command == "file": self._watcher.setFile(set_[1]) + elif command == "controllerAuth": + password = set_[1]["password"] if set_[1].has_key("password") else None + self._factory.authRoomController(self._watcher, password) def sendSet(self, setting): self.sendMessage({"Set": setting}) + def sendNewControlledRoom(self, roomName, password): + self.sendSet({ + "newControlledRoom": { + "password": password, + "roomName": roomName + } + }) + + def sendControlledRoomAuthStatus(self, success): + self.sendSet({ + "controllerAuth": { + "success": success + } + }) + def sendUserSetting(self, username, room, file_, event): room = {"name": room.getName()} user = {} diff --git a/syncplay/server.py b/syncplay/server.py index b349003..1776f7c 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -1,4 +1,6 @@ import hashlib +import random +import re from twisted.internet import task, reactor from twisted.internet.protocol import Factory import syncplay @@ -11,12 +13,18 @@ import os from string import Template import argparse +# TODO: Check if room should have status of "Controlled room" +# TODO: Make only controllers able to control a room +# TODO: Send list of controllers +# TODO: Broadcast information about controller auth class SyncFactory(Factory): def __init__(self, password='', motdFilePath=None, isolateRooms=False): print getMessage("welcome-server-notification").format(syncplay.version) if password: password = hashlib.md5(password).hexdigest() self.password = password + # TODO: Make salt come from more reasonable place + self._salt = str(random.random()) self._motdFilePath = motdFilePath if not isolateRooms: self._roomManager = RoomManager() @@ -98,6 +106,19 @@ class SyncFactory(Factory): def getAllWatchersForUser(self, forUser): return self._roomManager.getAllWatchersForUser(forUser) + def authRoomController(self, watcher, password): + room = watcher.getRoom() + try: + success = RoomPasswordProvider.check(room.getName(), password, self._salt) + # TODO: Authenticate watcher to make changes in the room + watcher.sendControlledRoomAuthStatus(success) + except NotControlledRoom: + newName = RoomPasswordProvider.getControlledRoomName(room.getName(), password, self._salt) + watcher.sendNewControlledRoom(newName, password) + except ValueError: + watcher.sendControlledRoomAuthStatus(False) + + class RoomManager(object): def __init__(self): self._rooms = {} @@ -277,6 +298,12 @@ class Watcher(object): def sendSetting(self, user, room, file_, event): self._connector.sendUserSetting(user, room, file_, event) + def sendNewControlledRoom(self, roomName, password): + self._connector.sendNewControlledRoom(roomName, password) + + def sendControlledRoomAuthStatus(self, success): + self._connector.sendControlledRoomAuthStatus(success) + def __lt__(self, b): if self.getPosition() is None or self._file is None: return False @@ -341,3 +368,33 @@ 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('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument")) + + +class RoomPasswordProvider(object): + CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$") + PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}") + + @staticmethod + def check(roomName, password, salt): + if not re.match(RoomPasswordProvider.PASSWORD_REGEX, password): + raise ValueError() + + match = re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName) + if not match: + raise NotControlledRoom() + roomHash = match.group(2) + computedHash = RoomPasswordProvider._computeRoomHash(match.group(1), password, salt) + return roomHash == computedHash + + @staticmethod + def getControlledRoomName(roomName, password, salt): + return "+" + roomName + ":" + RoomPasswordProvider._computeRoomHash(roomName, password, salt) + + @staticmethod + def _computeRoomHash(roomName, password, salt): + salt = hashlib.sha256(salt).hexdigest() + provisionalHash = hashlib.sha256(roomName + salt).hexdigest() + return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper() + +class NotControlledRoom(Exception): + pass \ No newline at end of file From 9506a80b9bb869e0f7209a116884b9eb2b3c460d Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 5 Oct 2014 20:05:06 +0200 Subject: [PATCH 033/142] Updated the controlled room protocol to fit more needs --- syncplay/client.py | 10 +++++----- syncplay/protocols.py | 12 ++++++------ syncplay/server.py | 9 ++++----- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index bcb504b..ba30bba 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -431,14 +431,14 @@ class SyncplayClient(object): self.ui.showMessage("Identifying as room controller with password '{}'...".format(controlPassword)) self._protocol.requestControlledRoom(controlPassword) - def controllerIdentificationError(self): - self.ui.showErrorMessage("Failed to identify as a room controller.") + def controllerIdentificationError(self, username): + self.ui.showErrorMessage("<{}> failed to identify as a room controller.".format(username)) - def controllerIdentificationSuccess(self): + def controllerIdentificationSuccess(self, username): # TODO: More UI stuff - self.ui.showErrorMessage("Authenticated as a room controller") + self.ui.showErrorMessage("<{}> authenticated as a room controller".format(username)) - # TODO: A person authenticated as a room controller + # TODO: Mark person as a room controller # TODO: Disable UI's "Create new Controlled Room when in Controlled Room" # TODO: Disable authenticate when authenticated diff --git a/syncplay/protocols.py b/syncplay/protocols.py index a45b4be..f025c16 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -122,9 +122,9 @@ class SyncClientProtocol(JSONCommandProtocol): self._SetUser(values) elif command == "controllerAuth": if values['success']: - self._client.controllerIdentificationSuccess() + self._client.controllerIdentificationSuccess(values["user"]) else: - self._client.controllerIdentificationError() + self._client.controllerIdentificationError(values["user"]) elif command == "newControlledRoom": controlPassword = values['password'] roomName = values['roomName'] @@ -341,17 +341,17 @@ class SyncServerProtocol(JSONCommandProtocol): } }) - def sendControlledRoomAuthStatus(self, success): + def sendControlledRoomAuthStatus(self, success, username): self.sendSet({ "controllerAuth": { + "user": username, "success": success } }) def sendUserSetting(self, username, room, file_, event): room = {"name": room.getName()} - user = {} - user[username] = {} + user = {username: {}} user[username]["room"] = room if file_: user[username]["file"] = file_ @@ -388,7 +388,7 @@ class SyncServerProtocol(JSONCommandProtocol): "paused": paused, "doSeek": doSeek, "setBy": setBy.getName() - } + } ping = { "latencyCalculation": self._pingService.newTimestamp(), "serverRtt": self._pingService.getRtt() diff --git a/syncplay/server.py b/syncplay/server.py index 1776f7c..173d06f 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -110,13 +110,12 @@ class SyncFactory(Factory): room = watcher.getRoom() try: success = RoomPasswordProvider.check(room.getName(), password, self._salt) - # TODO: Authenticate watcher to make changes in the room - watcher.sendControlledRoomAuthStatus(success) + self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(success, watcher.getName())) except NotControlledRoom: newName = RoomPasswordProvider.getControlledRoomName(room.getName(), password, self._salt) watcher.sendNewControlledRoom(newName, password) except ValueError: - watcher.sendControlledRoomAuthStatus(False) + self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName())) class RoomManager(object): @@ -301,8 +300,8 @@ class Watcher(object): def sendNewControlledRoom(self, roomName, password): self._connector.sendNewControlledRoom(roomName, password) - def sendControlledRoomAuthStatus(self, success): - self._connector.sendControlledRoomAuthStatus(success) + def sendControlledRoomAuthStatus(self, success, username): + self._connector.sendControlledRoomAuthStatus(success, username) def __lt__(self, b): if self.getPosition() is None or self._file is None: From 8072fca28f3e1a05a6db3ccc402448f0a9bb73e8 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 5 Oct 2014 21:01:34 +0200 Subject: [PATCH 034/142] Fixed comparing watchers in server with a rare case --- syncplay/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/server.py b/syncplay/server.py index 173d06f..e2ceea3 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -306,7 +306,7 @@ class Watcher(object): def __lt__(self, b): if self.getPosition() is None or self._file is None: return False - if b.getPosition is None or b._file is None: + if b.getPosition() is None or b.getFile() is None: return True return self.getPosition() < b.getPosition() From 241f5e75bbd33ad319e11c5839bc846a3dec4478 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 5 Oct 2014 21:04:22 +0200 Subject: [PATCH 035/142] Removed unused password argument --- syncplay/protocols.py | 9 ++++----- syncplay/server.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index f025c16..0ccca71 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -268,7 +268,7 @@ class SyncServerProtocol(JSONCommandProtocol): return self._logged def _extractHelloArguments(self, hello): - roomName, roomPassword = None, None + roomName = None username = hello["username"] if hello.has_key("username") else None username = username.strip() serverPassword = hello["password"] if hello.has_key("password") else None @@ -276,9 +276,8 @@ class SyncServerProtocol(JSONCommandProtocol): if room: roomName = room["name"] if room.has_key("name") else None roomName = roomName.strip() - roomPassword = room["password"] if room.has_key("password") else None version = hello["version"] if hello.has_key("version") else None - return username, serverPassword, roomName, roomPassword, version + return username, serverPassword, roomName, version def _checkPassword(self, serverPassword): if self._factory.password: @@ -291,7 +290,7 @@ class SyncServerProtocol(JSONCommandProtocol): return True def handleHello(self, hello): - username, serverPassword, roomName, roomPassword, version = self._extractHelloArguments(hello) + username, serverPassword, roomName, version = self._extractHelloArguments(hello) if not username or not roomName or not version: self.dropWithError(getMessage("hello-server-error")) elif version.split(".")[0:2] != syncplay.version.split(".")[0:2]: @@ -299,7 +298,7 @@ class SyncServerProtocol(JSONCommandProtocol): else: if not self._checkPassword(serverPassword): return - self._factory.addWatcher(self, username, roomName, roomPassword) + self._factory.addWatcher(self, username, roomName) self._logged = True self.sendHello(version) diff --git a/syncplay/server.py b/syncplay/server.py index e2ceea3..91a066c 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -62,7 +62,7 @@ class SyncFactory(Factory): else: return "" - def addWatcher(self, watcherProtocol, username, roomName, roomPassword): + def addWatcher(self, watcherProtocol, username, roomName): username = self._roomManager.findFreeUsername(username) watcher = Watcher(self, watcherProtocol, username) self.setWatcherRoom(watcher, roomName, asJoin=True) From ec74daf9633f1b0a943da99b0fc286e945bb21ab Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 5 Oct 2014 21:05:53 +0200 Subject: [PATCH 036/142] Fixed more unused arguments --- syncplay/server.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 91a066c..c88c9b9 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -91,11 +91,11 @@ class SyncFactory(Factory): l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True}) if w != watcher else None self._roomManager.broadcast(watcher, l) - def sendFileUpdate(self, watcher, file_): + def sendFileUpdate(self, watcher): l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), watcher.getFile(), None) self._roomManager.broadcast(watcher, l) - def forcePositionUpdate(self, room, watcher, doSeek): + def forcePositionUpdate(self, watcher, doSeek): room = watcher.getRoom() paused, position = room.isPaused(), watcher.getPosition() setBy = watcher @@ -263,7 +263,7 @@ class Watcher(object): def setFile(self, file_): self._file = file_ - self._server.sendFileUpdate(self, file_) + self._server.sendFileUpdate(self) def setRoom(self, room): self._room = room @@ -312,7 +312,7 @@ class Watcher(object): def _scheduleSendState(self): self._sendStateTimer = task.LoopingCall(self._askForStateUpdate) - self._sendStateTimer.start(constants.SERVER_STATE_INTERVAL, True) + self._sendStateTimer.start(constants.SERVER_STATE_INTERVAL) def _askForStateUpdate(self, doSeek=False, forcedUpdate=False): self._server.sendState(self, doSeek, forcedUpdate) @@ -349,7 +349,7 @@ class Watcher(object): position += messageAge self.setPosition(position) if doSeek or pauseChanged: - self._server.forcePositionUpdate(self._room, self, doSeek) + self._server.forcePositionUpdate(self, doSeek) class ConfigurationGetter(object): From 7fb2709629c483644c851a7417cc16daeef89f3c Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 9 Oct 2014 19:22:16 +0100 Subject: [PATCH 037/142] Use exit menu label --- syncplay/ui/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 4f8c32b..134841a 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -383,7 +383,7 @@ class MainWindow(QtGui.QMainWindow): window.fileMenu = QtGui.QMenu(getMessage("file-menu-label"), self) window.openAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'folder_explore.png'), getMessage("openmedia-menu-label")) window.openAction.triggered.connect(self.browseMediapath) - window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), getMessage("file-menu-label")) + window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), getMessage("exit-menu-label")) window.exitAction.triggered.connect(self.exitSyncplay) window.menuBar.addMenu(window.fileMenu) From 1b3e370d6d8e3c2b8d0c9cd827eebb00d034de88 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 10 Oct 2014 19:39:43 +0100 Subject: [PATCH 038/142] Add debug messages for VLC/mpv/Client-Server comms --- syncplay/client.py | 5 +++++ syncplay/constants.py | 1 + syncplay/players/mplayer.py | 3 +++ syncplay/players/vlc.py | 4 ++-- syncplay/protocols.py | 2 ++ syncplay/ui/gui.py | 3 --- 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 026c4b7..bd84a42 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -61,6 +61,7 @@ class SyncplayClient(object): constants.SHOW_DIFFERENT_ROOM_OSD = config['showDifferentRoomOSD'] constants.SHOW_SAME_ROOM_OSD = config['showSameRoomOSD'] constants.SHOW_DURATION_NOTIFICATION = config['showDurationNotification'] + constants.DEBUG_MODE = config['debug'] self.lastLeftTime = 0 self.lastLeftUser = u"" self.protocolFactory = SyncClientFactory(self) @@ -637,6 +638,10 @@ class UiManager(object): self.__ui = ui self.lastError = "" + def showDebugMessage(self, message): + if constants.DEBUG_MODE and message.rstrip(): + print "{}{}".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip()) + def showMessage(self, message, noPlayer=False, noTimestamp=False): if not noPlayer: self.showOSDMessage(message) self.__ui.showMessage(message, noTimestamp) diff --git a/syncplay/constants.py b/syncplay/constants.py index 91218a6..98c2743 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -22,6 +22,7 @@ SHOW_SLOWDOWN_OSD = True # Show notifications of slowing down / reverting on ti SHOW_SAME_ROOM_OSD = True # Show OSD notifications for events relating to room user is in SHOW_DIFFERENT_ROOM_OSD = False # Show OSD notifications for events relating to room user is not in SHOW_DURATION_NOTIFICATION = True +DEBUG_MODE = False #Changing these might be ok DEFAULT_REWIND_THRESHOLD = 4 diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index d79c048..8a89e9b 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -148,6 +148,8 @@ class MplayerPlayer(BasePlayer): self._paused = value def lineReceived(self, line): + if line: + self._client.ui.showDebugMessage("player << {}".format(line)) match = self.RE_ANSWER.match(line) if not match: self._handleUnknownLine(line) @@ -290,6 +292,7 @@ class MplayerPlayer(BasePlayer): def sendLine(self, line): try: line = (line.decode('utf8') + u"\n").encode('utf8') + self.__playerController._client.ui.showDebugMessage("player >> {}".format(line)) self.__process.stdin.write(line) except IOError: pass diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index fe6f79a..18a77a6 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -130,6 +130,7 @@ class VlcPlayer(BasePlayer): self._listener.sendLine("get-filename") def lineReceived(self, line): + self._client.ui.showDebugMessage("player >> {}".format(line)) match, name, value = self.RE_ANSWER.match(line), "", "" if match: name, value = match.group('command'), match.group('argument') @@ -327,15 +328,14 @@ class VlcPlayer(BasePlayer): self.__playerController.drop() def found_terminator(self): -# print "received: {}".format("".join(self._ibuffer)) self.__playerController.lineReceived("".join(self._ibuffer)) self._ibuffer = [] def sendLine(self, line): if self.connected: -# print "send: {}".format(line) try: self.push(line + "\n") + self._client.ui.showDebugMessage("player >> {}".format(line)) except: pass if line == "close-vlc": diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 8e4e14a..750d212 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -30,6 +30,7 @@ class JSONCommandProtocol(LineReceiver): if not line: return try: + self._client.ui.showDebugMessage("client/server << {}".format(line)) messages = json.loads(line) except: self.dropWithError(getMessage("not-json-server-error").format(line)) @@ -39,6 +40,7 @@ class JSONCommandProtocol(LineReceiver): def sendMessage(self, dict_): line = json.dumps(dict_) self.sendLine(line) + self._client.ui.showDebugMessage("client/server >> {}".format(line)) def drop(self): self.transport.loseConnection() diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 134841a..402a0c2 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -117,9 +117,6 @@ class MainWindow(QtGui.QMainWindow): def userListChange(self): self._syncplayClient.showUserList() - - def showDebugMessage(self, message): - print(message) def showErrorMessage(self, message, criticalerror = False): message = unicode(message) From 51e2e422935af34e085c4516d6d5d1680c6a6117 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sat, 11 Oct 2014 22:06:54 +0200 Subject: [PATCH 039/142] Completed "backend" for controlled rooms --- syncplay/client.py | 2 +- syncplay/protocols.py | 2 +- syncplay/server.py | 78 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index ba30bba..b5ecd9b 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -225,7 +225,7 @@ class SyncplayClient(object): def _changePlayerStateAccordingToGlobalState(self, position, paused, doSeek, setBy): madeChangeOnPlayer = False - pauseChanged = paused != self.getGlobalPaused() + pauseChanged = paused != self.getGlobalPaused() or paused != self.getPlayerPaused() diff = self.getPlayerPosition() - position if self._lastGlobalUpdate is None: madeChangeOnPlayer = self._initPlayerState(position, paused) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 0ccca71..ce18661 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -386,7 +386,7 @@ class SyncServerProtocol(JSONCommandProtocol): "position": position if position else 0, "paused": paused, "doSeek": doSeek, - "setBy": setBy.getName() + "setBy": setBy.getName() if setBy else None } ping = { "latencyCalculation": self._pingService.newTimestamp(), diff --git a/syncplay/server.py b/syncplay/server.py index c88c9b9..d87a48e 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -13,10 +13,6 @@ import os from string import Template import argparse -# TODO: Check if room should have status of "Controlled room" -# TODO: Make only controllers able to control a room -# TODO: Send list of controllers -# TODO: Broadcast information about controller auth class SyncFactory(Factory): def __init__(self, password='', motdFilePath=None, isolateRooms=False): print getMessage("welcome-server-notification").format(syncplay.version) @@ -73,6 +69,9 @@ class SyncFactory(Factory): self.sendJoinMessage(watcher) else: self.sendRoomSwitchMessage(watcher) + if RoomPasswordProvider.isControlledRoom(roomName): + for controller in watcher.getRoom().getControllers(): + watcher.sendControlledRoomAuthStatus(True, controller) def sendRoomSwitchMessage(self, watcher): l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None) @@ -97,11 +96,14 @@ class SyncFactory(Factory): def forcePositionUpdate(self, watcher, doSeek): room = watcher.getRoom() - paused, position = room.isPaused(), watcher.getPosition() - setBy = watcher - room.setPosition(watcher.getPosition(), setBy) - l = lambda w: w.sendState(position, paused, doSeek, setBy, True) - self._roomManager.broadcastRoom(watcher, l) + if room.canControl(watcher): + paused, position = room.isPaused(), watcher.getPosition() + setBy = watcher + l = lambda w: w.sendState(position, paused, doSeek, setBy, True) + room.setPosition(watcher.getPosition(), setBy) + self._roomManager.broadcastRoom(watcher, l) + else: + watcher.sendState(room.getPosition(), room.isPaused(), doSeek, room.getSetBy(), True) def getAllWatchersForUser(self, forUser): return self._roomManager.getAllWatchersForUser(forUser) @@ -110,6 +112,8 @@ class SyncFactory(Factory): room = watcher.getRoom() try: success = RoomPasswordProvider.check(room.getName(), password, self._salt) + if success: + watcher.getRoom().addController(watcher) self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(success, watcher.getName())) except NotControlledRoom: newName = RoomPasswordProvider.getControlledRoomName(room.getName(), password, self._salt) @@ -155,7 +159,10 @@ class RoomManager(object): if roomName in self._rooms: return self._rooms[roomName] else: - room = Room(roomName) + if RoomPasswordProvider.isControlledRoom(roomName): + room = ControlledRoom(roomName) + else: + room = Room(roomName) self._rooms[roomName] = room return room @@ -248,6 +255,44 @@ class Room(object): def getSetBy(self): return self._setBy + def canControl(self, watcher): + return True + +class ControlledRoom(Room): + def __init__(self, name): + Room.__init__(self, name) + self._controllers = {} + + def getPosition(self): + if self._controllers: + watcher = min(self._controllers.values()) + self._setBy = watcher + return watcher.getPosition() + else: + return 0 + + def addController(self, watcher): + self._controllers[watcher.getName()] = watcher + + def removeWatcher(self, watcher): + Room.removeWatcher(self, watcher) + if watcher.getName() in self._controllers: + del self._controllers[watcher.getName()] + + def setPaused(self, paused=Room.STATE_PAUSED, setBy=None): + if self.canControl(setBy): + Room.setPaused(self, paused, setBy) + + def setPosition(self, position, setBy=None): + if self.canControl(setBy): + Room.setPosition(self, position, setBy) + + def canControl(self, watcher): + return watcher.getName() in self._controllers + + def getControllers(self): + return self._controllers + class Watcher(object): def __init__(self, server, connector, name): self._server = server @@ -339,14 +384,18 @@ class Watcher(object): return False return self._room.isPaused() and not paused or not self._room.isPaused() and paused + def _updatePositionByAge(self, messageAge, paused, position): + if not paused: + position += messageAge + return position + def updateState(self, position, paused, doSeek, messageAge): pauseChanged = self.__hasPauseChanged(paused) self._lastUpdatedOn = time.time() if pauseChanged: self.getRoom().setPaused(Room.STATE_PAUSED if paused else Room.STATE_PLAYING, self) if position is not None: - if not paused: - position += messageAge + position = self._updatePositionByAge(messageAge, paused, position) self.setPosition(position) if doSeek or pauseChanged: self._server.forcePositionUpdate(self, doSeek) @@ -368,11 +417,14 @@ class ConfigurationGetter(object): self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument")) self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument")) - class RoomPasswordProvider(object): CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$") PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}") + @staticmethod + def isControlledRoom(roomName): + return bool(re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName)) + @staticmethod def check(roomName, password, salt): if not re.match(RoomPasswordProvider.PASSWORD_REGEX, password): From feef564d40298d0c14be7e4393c116e890a068f2 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sat, 11 Oct 2014 22:45:52 +0200 Subject: [PATCH 040/142] Changed the version and version mismatch disconnect behavior --- syncplay/__init__.py | 4 ++-- syncplay/messages.py | 2 +- syncplay/protocols.py | 6 ++++-- syncplay/server.py | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index ef963d5..a1fa3eb 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,3 +1,3 @@ -version = '1.2.9' -milestone = 'Pineapple, Pulverize and Destroy!' +version = '1.3.0' +milestone = 'Akki' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/messages.py b/syncplay/messages.py index 5632461..8efbdb3 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -270,7 +270,7 @@ en = { "password-required-server-error" : "Password required", "wrong-password-server-error" : "Wrong password supplied", "hello-server-error" : "Not enough Hello arguments", - "version-mismatch-server-error" : "Mismatch between versions of client and server" + "version-mismatch-server-error" : "Mismatch between versions of client and server, please update your client or server software. Current client version is {}, while the server version: {}" } diff --git a/syncplay/protocols.py b/syncplay/protocols.py index ce18661..2c0b34d 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -293,14 +293,16 @@ class SyncServerProtocol(JSONCommandProtocol): username, serverPassword, roomName, version = self._extractHelloArguments(hello) if not username or not roomName or not version: self.dropWithError(getMessage("hello-server-error")) + return elif version.split(".")[0:2] != syncplay.version.split(".")[0:2]: - self.dropWithError(getMessage("version-mismatch-server-error")) + self.dropWithError(getMessage("version-mismatch-server-error").format(version, syncplay.version)) + return else: if not self._checkPassword(serverPassword): return self._factory.addWatcher(self, username, roomName) self._logged = True - self.sendHello(version) + self.sendHello(syncplay.version) def setWatcher(self, watcher): self._watcher = watcher diff --git a/syncplay/server.py b/syncplay/server.py index d87a48e..8c0d2e3 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -78,7 +78,7 @@ class SyncFactory(Factory): self._roomManager.broadcast(watcher, l) def removeWatcher(self, watcher): - if watcher.getRoom(): + if watcher and watcher.getRoom(): self.sendLeftMessage(watcher) self._roomManager.removeWatcher(watcher) From ea119fca79bd03e08b449cd1ce0de29db569a91c Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 12 Oct 2014 20:26:04 +0200 Subject: [PATCH 041/142] Fix the BC with 1.2.9 --- syncplay/protocols.py | 7 ++----- syncplay/server.py | 7 ++++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 2c0b34d..68e4095 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -294,15 +294,12 @@ class SyncServerProtocol(JSONCommandProtocol): if not username or not roomName or not version: self.dropWithError(getMessage("hello-server-error")) return - elif version.split(".")[0:2] != syncplay.version.split(".")[0:2]: - self.dropWithError(getMessage("version-mismatch-server-error").format(version, syncplay.version)) - return else: if not self._checkPassword(serverPassword): return self._factory.addWatcher(self, username, roomName) self._logged = True - self.sendHello(syncplay.version) + self.sendHello(version) def setWatcher(self, watcher): self._watcher = watcher @@ -314,7 +311,7 @@ class SyncServerProtocol(JSONCommandProtocol): userIp = self.transport.getPeer().host room = self._watcher.getRoom() if room: hello["room"] = {"name": room.getName()} - hello["version"] = syncplay.version + hello["version"] = clientVersion # syncplay.version - Don't BC with 1.2.x hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion) self.sendMessage({"Hello": hello}) diff --git a/syncplay/server.py b/syncplay/server.py index 8c0d2e3..7697397 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -94,7 +94,7 @@ class SyncFactory(Factory): l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), watcher.getFile(), None) self._roomManager.broadcast(watcher, l) - def forcePositionUpdate(self, watcher, doSeek): + def forcePositionUpdate(self, watcher, doSeek, watcherPauseState): room = watcher.getRoom() if room.canControl(watcher): paused, position = room.isPaused(), watcher.getPosition() @@ -103,7 +103,8 @@ class SyncFactory(Factory): room.setPosition(watcher.getPosition(), setBy) self._roomManager.broadcastRoom(watcher, l) else: - watcher.sendState(room.getPosition(), room.isPaused(), doSeek, room.getSetBy(), True) + watcher.sendState(room.getPosition(), watcherPauseState, False, watcher, True) # Fixes BC break with 1.2.x + watcher.sendState(room.getPosition(), room.isPaused(), True, room.getSetBy(), True) def getAllWatchersForUser(self, forUser): return self._roomManager.getAllWatchersForUser(forUser) @@ -398,7 +399,7 @@ class Watcher(object): position = self._updatePositionByAge(messageAge, paused, position) self.setPosition(position) if doSeek or pauseChanged: - self._server.forcePositionUpdate(self, doSeek) + self._server.forcePositionUpdate(self, doSeek, paused) class ConfigurationGetter(object): From a28431340ec64e69c039af3346e8da4b05965843 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 12 Oct 2014 20:39:22 +0200 Subject: [PATCH 042/142] Add method "isController" to the user --- syncplay/client.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index b5ecd9b..6110ffd 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -435,6 +435,7 @@ class SyncplayClient(object): self.ui.showErrorMessage("<{}> failed to identify as a room controller.".format(username)) def controllerIdentificationSuccess(self, username): + self.userlist.setUserAsController(username) # TODO: More UI stuff self.ui.showErrorMessage("<{}> authenticated as a room controller".format(username)) @@ -494,6 +495,7 @@ class SyncplayUser(object): self.username = username self.room = room self.file = file_ + self._controller = False def setFile(self, filename, duration, size): file_ = { @@ -520,6 +522,12 @@ class SyncplayUser(object): else: return "{}".format(self.username) + def setAsController(self): + self._controller = True + + def isController(self): + return self._controller + class SyncplayUserlist(object): def __init__(self, ui, client): self.currentUser = SyncplayUser() @@ -610,6 +618,11 @@ class SyncplayUserlist(object): self.addUser(username, room, file_) self.userListChange() + def setUserAsController(self, username): + if self._users.has_key(username): + user = self._users[username] + user.setAsController() + def areAllFilesInRoomSame(self): for user in self._users.itervalues(): if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file): From 778fb21d0b7815753c88094f7301a1a0bddf5669 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 12 Oct 2014 20:45:53 +0100 Subject: [PATCH 043/142] Move RoomPasswordProvider to utils --- syncplay/server.py | 34 +--------------------------------- syncplay/utils.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 7697397..e576eb7 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -12,6 +12,7 @@ import codecs import os from string import Template import argparse +from syncplay.utils import RoomPasswordProvider, NotControlledRoom class SyncFactory(Factory): def __init__(self, password='', motdFilePath=None, isolateRooms=False): @@ -417,36 +418,3 @@ 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('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument")) - -class RoomPasswordProvider(object): - CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$") - PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}") - - @staticmethod - def isControlledRoom(roomName): - return bool(re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName)) - - @staticmethod - def check(roomName, password, salt): - if not re.match(RoomPasswordProvider.PASSWORD_REGEX, password): - raise ValueError() - - match = re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName) - if not match: - raise NotControlledRoom() - roomHash = match.group(2) - computedHash = RoomPasswordProvider._computeRoomHash(match.group(1), password, salt) - return roomHash == computedHash - - @staticmethod - def getControlledRoomName(roomName, password, salt): - return "+" + roomName + ":" + RoomPasswordProvider._computeRoomHash(roomName, password, salt) - - @staticmethod - def _computeRoomHash(roomName, password, salt): - salt = hashlib.sha256(salt).hexdigest() - provisionalHash = hashlib.sha256(roomName + salt).hexdigest() - return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper() - -class NotControlledRoom(Exception): - pass \ No newline at end of file diff --git a/syncplay/utils.py b/syncplay/utils.py index 40bf3d2..cfd6eca 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -182,3 +182,36 @@ def sameFileduration (duration1, duration2): return True else: return False + +class RoomPasswordProvider(object): + CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$") + PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}") + + @staticmethod + def isControlledRoom(roomName): + return bool(re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName)) + + @staticmethod + def check(roomName, password, salt): + if not re.match(RoomPasswordProvider.PASSWORD_REGEX, password): + raise ValueError() + + match = re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName) + if not match: + raise NotControlledRoom() + roomHash = match.group(2) + computedHash = RoomPasswordProvider._computeRoomHash(match.group(1), password, salt) + return roomHash == computedHash + + @staticmethod + def getControlledRoomName(roomName, password, salt): + return "+" + roomName + ":" + RoomPasswordProvider._computeRoomHash(roomName, password, salt) + + @staticmethod + def _computeRoomHash(roomName, password, salt): + salt = hashlib.sha256(salt).hexdigest() + provisionalHash = hashlib.sha256(roomName + salt).hexdigest() + return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper() + +class NotControlledRoom(Exception): + pass \ No newline at end of file From 7032a9634c72346af990204d42d2e49aa921becc Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 12 Oct 2014 20:51:00 +0200 Subject: [PATCH 044/142] Remove droping on client side due to version mismatch --- syncplay/messages.py | 3 --- syncplay/protocols.py | 2 -- 2 files changed, 5 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 8efbdb3..35ae499 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -270,9 +270,6 @@ en = { "password-required-server-error" : "Password required", "wrong-password-server-error" : "Wrong password supplied", "hello-server-error" : "Not enough Hello arguments", - "version-mismatch-server-error" : "Mismatch between versions of client and server, please update your client or server software. Current client version is {}, while the server version: {}" - - } pl = { diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 68e4095..e56d457 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -78,8 +78,6 @@ class SyncClientProtocol(JSONCommandProtocol): username, roomName, version, motd = self._extractHelloArguments(hello) if not username or not roomName or not version: self.dropWithError(getMessage("hello-server-error").format(hello)) - elif version.split(".")[0:2] != syncplay.version.split(".")[0:2]: - self.dropWithError(getMessage("version-mismatch-server-error".format(hello))) else: self._client.setUsername(username) self._client.setRoom(roomName) From e88863e2ac2ec5d6aa1285be2776b6e47141961b Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 12 Oct 2014 22:07:01 +0200 Subject: [PATCH 045/142] Added sending the "controller" status with a list --- syncplay/client.py | 4 +++- syncplay/protocols.py | 9 +++++++-- syncplay/server.py | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 6110ffd..2833fe0 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -574,10 +574,12 @@ class SyncplayUserlist(object): message = getMessage("file-differences-notification") + ", ".join(differences) self.ui.showMessage(message, not constants.SHOW_OSD_WARNINGS) - def addUser(self, username, room, file_, noMessage=False): + def addUser(self, username, room, file_, noMessage=False, isController=False): if username == self.currentUser.username: return user = SyncplayUser(username, room, file_) + if isController: + user.setAsController() self._users[username] = user if not noMessage: self.__showUserChangeMessage(username, room, file_) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index e56d457..36d15b8 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -148,7 +148,8 @@ class SyncClientProtocol(JSONCommandProtocol): for user in room[1].iteritems(): userName = user[0] file_ = user[1]['file'] if user[1]['file'] <> {} else None - self._client.userlist.addUser(userName, roomName, file_, noMessage=True) + isController = user[1]['controller'] if 'controller' in user[1] else False + self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController) self._client.userlist.showUserList() def sendList(self): @@ -360,7 +361,11 @@ class SyncServerProtocol(JSONCommandProtocol): if room: if room.getName() not in userlist: userlist[room.getName()] = {} - userFile = { "position": 0, "file": watcher.getFile() if watcher.getFile() else {} } + userFile = { + "position": 0, + "file": watcher.getFile() if watcher.getFile() else {}, + "controller": watcher.isController() + } userlist[room.getName()][watcher.getName()] = userFile def sendList(self): diff --git a/syncplay/server.py b/syncplay/server.py index e576eb7..2d79067 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -1,6 +1,5 @@ import hashlib import random -import re from twisted.internet import task, reactor from twisted.internet.protocol import Factory import syncplay @@ -402,6 +401,9 @@ class Watcher(object): if doSeek or pauseChanged: self._server.forcePositionUpdate(self, doSeek, paused) + def isController(self): + return RoomPasswordProvider.isControlledRoom(self._room.getName()) \ + and self._room.canControl(self) class ConfigurationGetter(object): def getConfiguration(self): From d9d5eb86f71937aa88ae85d9f3ddf08971eca8c5 Mon Sep 17 00:00:00 2001 From: BrokeRU Date: Tue, 14 Oct 2014 17:37:46 +0600 Subject: [PATCH 046/142] Russian version! --- syncplay/messages.py | 270 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 269 insertions(+), 1 deletion(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 5632461..059e3dd 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -106,7 +106,7 @@ en = { "file-argument" : 'file to play', "args-argument" : 'player options, if you need to pass options starting with - prepend them with single \'--\' argument', "clear-gui-data-argument" : 'resets path and window state GUI data stored as QSettings', - "language-argument" :'language for Syncplay messages (en/pl)', + "language-argument" :'language for Syncplay messages (en/pl/ru)', "version-argument" : 'prints your version', "version-message" : "You're using Syncplay version {} ({})", @@ -312,9 +312,277 @@ pl = { "server-timeout-error" : u"Przekroczono czas oczekiwania na odpowiedź serwera" } +ru = { + + # Client notifications + "config-cleared-notification" : u"Настройки сброшены. Изменения вступят в силу при сохранении корректной конфигурации.", + + "relative-config-notification" : u"Загружен(ы) файл(ы) относительной конфигурации: {}", + + "connection-attempt-notification" : u"Подключение к {}:{}", # Port, IP + "reconnection-attempt-notification" : u"Соединение с сервером потеряно, переподключение", + "disconnection-notification" : u"Отключились от сервера", + "connection-failed-notification" : u"Не удалось подключиться к серверу", + "connected-successful-notification" : u"Соединение с сервером установлено", + "retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds + + "rewind-notification" : u"Перемотано из-за разницы во времени с <{}>", # User + "slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с <{}>", # User + "revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения", + + "pause-notification" : u"<{}> приостановил(а) воспроизведение", # User + "unpause-notification" : u"<{}> возобновил(а) воспроизведение", # User + "seek-notification" : u"<{}> перемотал с {} на {}", # User, from time, to time + + "current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset + + "room-join-notification" : u"<{}> зашел(зашла) в комнату: '{}'", # User + "left-notification" : u"<{}> покинул(а) комнату", # User + "left-paused-notification" : u"<{}> покинул(а) комнату, <{}> приостановил(а) воспроизведение", # User who left, User who paused + "playing-notification" : u"<{}> включил '{}' ({})", # User, file, duration + "playing-notification/room-addendum" : u" в комнате: '{}'", # Room + + "file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит <{}>.", # User + "file-differences-notification" : u"Ваш файл отличается: ", + "room-files-not-same" : u"Не все пользователи в этой комнате смотрят один и тот же файл.", + "alone-in-the-room" : u"В этой комнате кроме Вас никого нет.", + + "different-filesize-notification" : u" (размер Вашего файла не совпадает с размером их файла!)", + "file-played-by-notification" : u"Файл: {} просматривают:", # File + "notplaying-notification" : u"Люди, которые не смотрят ничего:", + "userlist-room-notification" : u"В комнате '{}':", # Room + + "mplayer-file-required-notification" : u"Для использования Syncplay с mplayer необходимо передать файл в качестве параметра", + "mplayer-file-required-notification/example" : u"Пример использования: syncplay [options] [url|path/]filename", + "mplayer2-required" : u"Syncplay не совместим с MPlayer 1.x, пожалуйста, используйте mplayer2 или mpv", + + "unrecognized-command-notification" : u"Неизвестная команда.", + "commandlist-notification" : u"Доступные команды:", + "commandlist-notification/room" : u"\tr [name] - сменить комнату", + "commandlist-notification/list" : u"\tl - показать список пользователей", + "commandlist-notification/undo" : u"\tu - отменить последнюю перемотку", + "commandlist-notification/pause" : u"\tp - вкл./выкл. паузу", + "commandlist-notification/seek" : u"\t[s][+-]time - перемотать к заданному моменту времени, если не указан + или -, то время считается абсолютным (от начала файла) в секундах или мин:сек", + "commandlist-notification/help" : u"\th - помощь", + "syncplay-version-notification" : u"Версия Syncplay: {}", # syncplay.version + "more-info-notification" : u"Больше информации на {}", # projectURL + + "gui-data-cleared-notification" : u"Syncplay очистил путь и информацию о состоянии окна, использованного GUI.", + + "vlc-version-mismatch" : u"Внимание: Вы используете VLC устаревшей версии {}. К сожалению, Syncplay способен работать с VLC {} и выше.", # VLC version, VLC min version + "vlc-interface-version-mismatch" : u"Внимание: В используете модуль интерфейса Syncplay устаревшей версии {} для VLC. К сожалению, Syncplay способен работать с версией {} и выше.", # VLC interface version, VLC interface min version + "vlc-interface-oldversion-ignored" : u"Внимание: Syncplay обнаружил, что старая версия модуля интерфейса Syncplay для VLC уже установлена в директорию VLC. По существу, если Вы используете VLC 2.0, то предпочтение будет отдано файлу syncplay.lua, содержащемуся в директории Syncplay, но в таком случае другие пользовательские скрипты и расширения интерфейса не будут работать. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.", + "vlc-interface-not-installed" : u"Внимание: Модуль интерфейса Syncplay для VLC не обнаружен в директории VLC. По существу, если Вы используете VLC 2.0, то VLC будет использовать модуль syncplay.lua из директории Syncplay, но в таком случае другие пользовательские скрипты и расширения интерфейса не будут работать. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.", + + # Client prompts + "enter-to-exit-prompt" : u"Для выхода нажмите Enter\n", + + # Client errors + "missing-arguments-error" : u"Некоторые необходимые аргументы отсутствуют, обратитесь к --help", + "server-timeout-error" : u"Подключение к серверу превысило лимит времени", + "mpc-slave-error" : u"Невозможно запустить MPC в slave режиме!", + "mpc-version-insufficient-error" : u"Версия MPC слишком старая, пожалуйста, используйте `mpc-hc` >= `{}`", + "mpv-version-error" : u"Syncplay не совместим с данной версией mpv. Пожалуйста, используйте другую версию mpv (лучше свежайшую).", + "player-file-open-error" : u"Проигрыватель не может открыть файл.", + "player-path-error" : u"Путь к проигрывателю задан неверно.", + "hostname-empty-error" : u"Имя пользователя не может быть пустым.", + "empty-error" : u"{} не может быть пустым.", # Configuration + "media-player-error" : u"Ошибка Media player: \"{}\"", # Error line + "unable-import-gui-error" : u"Невозможно импортировать библиотеки GUI (графического интерфейса). Необходимо установить PySide, иначе графический интерфейс не будет работать.", + + "arguments-missing-error" : u"Некоторые необходимые аргументы отсутствуют, обратитесь к --help", + + "unable-to-start-client-error" : u"Невозможно запустить клиент", + + "not-json-error" : u"Не является закодированной json-строкой\n", + "hello-arguments-error" : u"Не хватает аргументов Hello\n", + "version-mismatch-error" : u"Конфликт версий между клиентом и сервером\n", + "vlc-failed-connection" : u"Ошибка подключения к VLC. Если у Вас не установлен syncplay.lua, то обратитесь к http://syncplay.pl/LUA/ за инструкциями.", + "vlc-failed-noscript" : u"VLC сообщает, что скрипт интерфейса syncplay.lua не установлен. Пожалуйста, обратитесь к http://syncplay.pl/LUA/ за инструкциями.", + "vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.", + "vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error + + # Client arguments + "argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет.', + "argument-epilog" : u'Если параметр не будет передан, то будет использоваться значение, указанное в _config.', + "nogui-argument" : u'не использовать GUI', + "host-argument" : u'адрес сервера', + "name-argument" : u'желательное имя пользователя', + "debug-argument" : u'режим отладки', + "force-gui-prompt-argument" : u'показать окно настройки', + "no-store-argument" : u'не сохранять данные в .syncplay', + "room-argument" : u'начальная комната', + "password-argument" : u'пароль для доступа к серверу', + "player-path-argument" : u'путь к исполняемому файлу Вашего проигрывателя', + "file-argument" : u'воспроизводимый файл', + "args-argument" : u'параметры проигрывателя; если нужно передать параметры, начинающиеся с - , то сначала пишите \'--\'', + "clear-gui-data-argument" : u'сбрасывает путь и данные о состоянии окна GUI, хранимые как QSettings', + "language-argument" : u'язык сообщений Syncplay (en/pl/ru)', + + "version-argument" : u'выводит номер версии', + "version-message" : u"Вы используете Syncplay версии {} ({})", + + # Client labels + "config-window-title" : u"Настройка Syncplay", + + "connection-group-title" : u"Параметры подключения", + "host-label" : u"Адрес сервера: ", + "name-label" : u"Имя пользователя (не обязательно):", + "password-label" : u"Пароль к серверу (если требуется):", + "room-label" : u"Начальная комната: ", + + "media-setting-title" : u"Параметры проигрывателя", + "executable-path-label" : u"Путь к проигрывателю:", + "media-path-label" : u"Путь к видеофайлу:", + "browse-label" : u"Выбрать", + + "more-title" : u"Больше настроек", + "slowdown-threshold-label" : u"Предел для замедления:", + "rewind-threshold-label" : u"Предел для перемотки:", + "never-rewind-value" : u"Никогда", + "seconds-suffix" : u" секунд(ы)", + "privacy-sendraw-option" : u"отпр. как есть", + "privacy-sendhashed-option" : u"отпр. хэш", + "privacy-dontsend-option" : u"не отпр.", + "filename-privacy-label" : u"Имя файла:", + "filesize-privacy-label" : u"Размер файла:", + "slowondesync-label" : u"Замедлять при небольших рассинхронизациях (не поддерживаетя в MPC-HC)", + "rewindondesync-label" : u"Перемотка при больших рассинхронизациях (настоятельно рекомендуется)", + "dontslowdownwithme-label" : u"Никогда не замедлять или перематывать видео другим", + "pauseonleave-label" : u"Приостанавливать, когда кто-то уходит (например, отключился)", + "forceguiprompt-label" : u"Не показывать больше этот диалог", # (Inverted) + "nostore-label" : u"Не сохранять текущую конфигурацию", # (Inverted) + "showosd-label" : u"Включить экранные сообщения (поверх видео)", + + "showosdwarnings-label" : u"Показывать предупреждения (напр., когда файлы не совпадают)", + "showsameroomosd-label" : u"Показывать события Вашей комнаты", + "showdifferentroomosd-label" : u"Показывать события других комнат", + "showslowdownosd-label" : u"Показывать уведомления о замедлении/перемотке", + "showcontactinfo-label" : u"Отображать контактную информацию разработчиков", + "showbuttonlabels-label" : u"Отображать надписи на кнопках", + "showtooltips-label" : u"Отображать всплывающие подсказки", + "showdurationnotification-label" : u"Предупреждать о несовпадении продолжительности видео", + "basics-label" : u"Основное", + "sync-label" : u"Синхронизация", + "messages-label" : u"Сообщения", + "privacy-label" : u"Приватность", + + "help-label" : u"Помощь", + "reset-label" : u"Сброс настроек", + "run-label" : u"Запустить Syncplay", + "storeandrun-label" : u"Сохранить настройки и зап. Syncplay", + + "contact-label" : u"Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на dev@syncplay.pl, в IRC канал #Syncplay на irc.freenode.net или задавайте вопросы через GitHub. Кроме того, заходите на http://syncplay.pl/ за инорфмацией, помощью и обновлениями!", + + "joinroom-guibuttonlabel" : u"Зайти в комнату", + "seektime-guibuttonlabel" : u"Перемотать", + "undoseek-guibuttonlabel" : u"Отменить перемотку", + "togglepause-guibuttonlabel" : u"Вкл./выкл. паузу", + "play-guibuttonlabel" : u"Play", + "pause-guibuttonlabel" : u"Пауза", + + "roomuser-heading-label" : u"Комната / Пользователь", + "fileplayed-heading-label" : u"Воспроизводимый файл", + "notifications-heading-label" : u"Уведомления", + "userlist-heading-label" : u"Кто что смотрит", + "othercommands-heading-label" : u"Другие команды", + "room-heading-label" : u"Комната", + "seek-heading-label" : u"Перемотка", + + "browseformedia-label" : u"Выбрать видеофайл", + + "file-menu-label" : u"&Файл", # & precedes shortcut key + "openmedia-menu-label" : u"&Открыть видеофайл", + "exit-menu-label" : u"&Выход", + "advanced-menu-label" : u"&Дополнительно", + "setoffset-menu-label" : u"Установить &смещение", + "help-menu-label" : u"&Помощь", + "userguide-menu-label" : u"&Руководство Пользователя", + + "setoffset-msgbox-label" : u"Установить смещение", + "offsetinfo-msgbox-label" : u"Смещение (см. как использовать на http://syncplay.pl/guide/):", + + # Tooltips + + "host-tooltip" : u"Имя или IP-адрес, к которому будет произведено подключение, может содержать номер порта (напр., syncplay.pl:8999). Синхронизация возможна только в рамках одного сервера/порта.", + "name-tooltip" : u"Имя, под которым Вы будете известны. Регистриция не требуется, так что имя пользователя можно легко сменить в любой момент. Будет сгенерировано случайным образом, если не указать.", + "password-tooltip" : u"Пароли нужны для подключения к приватным серверам.", + "room-tooltip" : u"Комната, в которую Вы попадете сразу после подключения. Можно не указывать. Синхронизация возможна только между людьми в одной и той же комнате.", + + "executable-path-tooltip" : u"Расположение Вашего видеопроигрывателя (MPC-HC, VLC, mplayer2 или mpv).", + "media-path-tooltip" : u"Расположение видеофайла или потока для просмотра. Обязательно для mpv и mplayer2.", + + "more-tooltip" : u"Показать дополнительные настройки.", + "slowdown-threshold-tooltip" : u"Отставание самого медленного клиента, необходимое для временного уменьшения скорости видео (по умолчанию: {} сек.).".format(constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD), + "rewind-threshold-tooltip" : u"Отставание самого медленного клиента, необходимое для перемотки назад в целях синхронизации (по умолчанию: {} сек.).".format(constants.DEFAULT_REWIND_THRESHOLD), + "filename-privacy-tooltip" : u"Режим приватности для передачи имени воспроизводимого файла на сервер.", + "filesize-privacy-tooltip" : u"Режим приватности для передачи размера воспроизводимого файла на сервер.", + "privacy-sendraw-tooltip" : u"Отправляет эту информацию без шифрования. Рекомендуемая опция с наибольшей функциональностью.", + "privacy-sendhashed-tooltip" : u"Отправляет хэш-сумму этой информации, делая ее невидимой для других пользователей.", + "privacy-dontsend-tooltip" : u"Не отправлять эту информацию на сервер. Предоставляет наибольшую приватность.", + "slowondesync-tooltip" : u"Временно уменьшить скорость воспроизведения в целях синхронизации с другими зрителями. Не поддерживается в MPC-HC.", + "dontslowdownwithme-tooltip" : u"Ваши лаги не будут влиять на других зрителей.", + "pauseonleave-tooltip" : u"Приостановить воспроизведение, если Вы покинули комнату или кто-то из зрителей отключился от сервера.", + "forceguiprompt-tooltip" : u"Окно настройки не будет отображаться при открытии файла в Syncplay.", # (Inverted) + "nostore-tooltip" : u"Запустить Syncplay с данной конфигурацией, но не сохранять изменения навсегда.", # (Inverted) + "rewindondesync-tooltip" : u"Перематывать назад, когда это необходимо для синхронизации. Отключение этой опции может привести к большим рассинхронизациям!", + "showosd-tooltip" : u"Отправлять сообщения Syncplay в видеопроигрыватель и отображать их поверх видео (OSD - On Screen Display).", + "showosdwarnings-tooltip" : u"Показывать OSC-предупреждения, если проигрываются разные файлы или если Вы в комнате больше никого нет.", + "showsameroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к комнате, в которой Вы находитесь.", + "showdifferentroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к любым другим комнатам.", + "showslowdownosd-tooltip" : u"Показывать уведомления о замедлении или перемотке в целях синхронизации.", + "showcontactinfo-tooltip" : u"Показывать информационных блок с контактной информацией разработчиков Syncplay на главном окне Syncplay.", + "showbuttonlabels-tooltip" : u"Показывать текст рядом с иконками на кнопках в основном пользовательском интерфейсе.", + "showtooltips-tooltip" : u"Показывать всплывающие подсказки при наведении мыши на некоторые элементы в окне Syncplay.", + "showdurationnotification-tooltip" : u"Полезно, когда сегмент составного файла отсутствует. Возможны ложные срабатывания.", + + "help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.", + "reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.", + + "togglepause-tooltip" : u"Приостановить/продолжить просмотр.", + "play-tooltip" : u"Продолжить просмотр.", + "pause-tooltip" : u"Приостановить просмотр.", + "undoseek-tooltip" : u"Перейти к тому месту, которое Вы просматривали до перемотки.", + "joinroom-tooltip" : u"Покинуть комнату и зайти в другую, указанную комнату.", + "seektime-tooltip" : u"Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.", + + # In-userlist notes (GUI) + "differentsize-note" : u"Размер файла не совпадает!", + "differentsizeandduration-note" : u"Размер и продолжительность файла не совпадают!", + "differentduration-note" : u"Продолжительность файла не совпадает!", + "nofile-note" : u"(Ничего не воспроизводим)", + + # Server messages to client + "new-syncplay-available-motd-message" : u" Вы используете Syncplay версии {}. Доступна более новая версия на http://syncplay.pl/ . ", # ClientVersion + + # Server arguments + "server-argument-description" : u'Решение для синхронного воспроизведения в VLC, MPlayer или MPC-HC через Интернет. Серверная часть', + "server-argument-epilog" : u'Если параметр не будет передан, то будет использоваться значение, указанное в _config.', + "server-port-argument" : u'номер TCP порта сервера', + "server-password-argument" : u'пароль к серверу', + "server-isolate-room-argument" : u'должны ли комнаты быть изолированными?', + "server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение", + "server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", + "server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", + "server-irc-verbose" : u"Должен ли сервер активно сообщать о всех событиях в комнатах?", + "server-irc-config" : u"Путь к конфигурационным файлам IRC-бота.", + + # Server errors + "unknown-command-server-error" : u"Неизвестная команда: {}", # message + "not-json-server-error" : u"Не является закодированной json-строкой: {}", # message + "not-known-server-error" : u"Данную команду могут выполнять только авторизованные пользователи.", + "client-drop-server-error" : u"Клиент отключен с ошибкой: {} -- {}", # host, error + "password-required-server-error" : u"Необходимо указать пароль.", + "wrong-password-server-error" : u"Указан неверный пароль.", + "hello-server-error" : u"Не хватает аргументов Hello.", + "version-mismatch-server-error" : u"Конфликт версий между клиентом и сервером." + + } + messages = { "en": en, "pl": pl, + "ru": ru, "current": None } From debdad362589ee4221241ce416330adea17c1115 Mon Sep 17 00:00:00 2001 From: BrokeRU Date: Tue, 14 Oct 2014 17:56:08 +0600 Subject: [PATCH 047/142] Update buildPy2exe.py --- buildPy2exe.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/buildPy2exe.py b/buildPy2exe.py index 7f91acc..463f99d 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -27,6 +27,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LoadLanguageFile "$${NSISDIR}\Contrib\Language files\English.nlf" LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Polish.nlf" + LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Russian.nlf" Name "Syncplay $version" OutFile "Syncplay $version Setup.exe" @@ -47,6 +48,11 @@ NSIS_SCRIPT_TEMPLATE = r""" VIAddVersionKey /LANG=$${LANG_POLISH} "LegalCopyright" "Syncplay" VIAddVersionKey /LANG=$${LANG_POLISH} "FileDescription" "Syncplay" + VIAddVersionKey /LANG=$${LANG_RUSSIAN} "ProductName" "Syncplay" + VIAddVersionKey /LANG=$${LANG_RUSSIAN} "FileVersion" "$version.0" + VIAddVersionKey /LANG=$${LANG_RUSSIAN} "LegalCopyright" "Syncplay" + VIAddVersionKey /LANG=$${LANG_RUSSIAN} "FileDescription" "Syncplay" + LangString ^SyncplayLanguage $${LANG_ENGLISH} "en" LangString ^Associate $${LANG_ENGLISH} "Associate Syncplay with multimedia files." LangString ^VLC $${LANG_ENGLISH} "Install Syncplay interface for VLC 2 and above" @@ -67,6 +73,16 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^QuickLaunchBar $${LANG_POLISH} "Pasek szybkiego uruchamiania" LangString ^UninstConfig $${LANG_POLISH} "Usun plik konfiguracyjny." + LangString ^SyncplayLanguage $${LANG_RUSSIAN} "ru" + LangString ^Associate $${LANG_RUSSIAN} "Ассоциировать Syncplay с видеофайлами" + LangString ^VLC $${LANG_RUSSIAN} "Установить интерфейс Syncplay для VLC 2+" + LangString ^BrowseVLCBtn $${LANG_RUSSIAN} "Укажите папку VLC" + LangString ^Shortcut $${LANG_RUSSIAN} "Создать ярлыки:" + LangString ^StartMenu $${LANG_RUSSIAN} "в меню Пуск" + LangString ^Desktop $${LANG_RUSSIAN} "на рабочем столе" + LangString ^QuickLaunchBar $${LANG_RUSSIAN} "в меню быстрого запуска" + LangString ^UninstConfig $${LANG_RUSSIAN} "Удалить файл настроек." + PageEx license LicenseData resources\license.txt PageExEnd @@ -164,6 +180,8 @@ NSIS_SCRIPT_TEMPLATE = r""" Push English Push $${LANG_POLISH} Push Polski + Push $${LANG_RUSSIAN} + Push Russian Push A ; A means auto count languages LangDLL::LangDialog "Language Selection" "Please select the language of Syncplay and the installer" Pop $$LANGUAGE From 64d78637a5bc52fd0aadcace13f9e4a29c022217 Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 15 Oct 2014 00:53:12 +0100 Subject: [PATCH 048/142] Improve controller UI code --- syncplay/client.py | 41 +++++++++++++++++++++++++--------------- syncplay/protocols.py | 7 ++++--- syncplay/server.py | 10 +++++----- syncplay/ui/consoleUI.py | 5 ++++- syncplay/ui/gui.py | 29 ++++++++++++++++------------ syncplay/utils.py | 1 + 6 files changed, 57 insertions(+), 36 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 2833fe0..8854850 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -417,27 +417,29 @@ class SyncplayClient(object): def createControlledRoom(self): controlPassword = RoomPasswordGenerator.generate_password() - self.ui.showMessage("Attempting to create controlled room suffix with password '{}'...".format(controlPassword)) + self.ui.showMessage(u"Attempting to create controlled room suffix with password '{}'...".format(controlPassword)) self._protocol.requestControlledRoom(controlPassword) def controlledRoomCreated(self, controlPassword, roomName): - self.ui.showMessage("Created controlled room suffix '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword)) + self.ui.showMessage(u"Created controlled room suffix '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword)) self.setRoom(roomName) self.sendRoom() self._protocol.requestControlledRoom(controlPassword) self.ui.updateRoomName(roomName) def identifyAsController(self, controlPassword): - self.ui.showMessage("Identifying as room controller with password '{}'...".format(controlPassword)) + self.ui.showMessage(u"Identifying as room controller with password '{}'...".format(controlPassword)) self._protocol.requestControlledRoom(controlPassword) - def controllerIdentificationError(self, username): - self.ui.showErrorMessage("<{}> failed to identify as a room controller.".format(username)) + def controllerIdentificationError(self, username, room): + self.ui.showErrorMessage(u"<{}> failed to identify as a room controller.".format(username)) - def controllerIdentificationSuccess(self, username): + def controllerIdentificationSuccess(self, username, roomname): self.userlist.setUserAsController(username) - # TODO: More UI stuff - self.ui.showErrorMessage("<{}> authenticated as a room controller".format(username)) + self.ui.setControllerStatus(username, isController=True) + if self.userlist.isRoomSame(roomname): + hideFromOSD = not constants.SHOW_SAME_ROOM_OSD + self.ui.showMessage(u"<{}> authenticated as a room controller".format(username), hideFromOSD) # TODO: Mark person as a room controller # TODO: Disable UI's "Create new Controlled Room when in Controlled Room" @@ -522,8 +524,8 @@ class SyncplayUser(object): else: return "{}".format(self.username) - def setAsController(self): - self._controller = True + def setControllerStatus(self, isController): + self._controller = isController def isController(self): return self._controller @@ -574,12 +576,14 @@ class SyncplayUserlist(object): message = getMessage("file-differences-notification") + ", ".join(differences) self.ui.showMessage(message, not constants.SHOW_OSD_WARNINGS) - def addUser(self, username, room, file_, noMessage=False, isController=False): + def addUser(self, username, room, file_, noMessage=False, isController=None): if username == self.currentUser.username: + if isController is not None: + self.currentUser.setControllerStatus(isController) return user = SyncplayUser(username, room, file_) - if isController: - user.setAsController() + if isController is not None: + user.setControllerStatus(isController) self._users[username] = user if not noMessage: self.__showUserChangeMessage(username, room, file_) @@ -610,6 +614,8 @@ class SyncplayUserlist(object): if self._users.has_key(username): user = self._users[username] oldRoom = user.room if user.room else None + if user.room != room: + user.setControllerStatus(isController=False) self.__displayModUserMessage(username, room, file_, user, oldRoom) user.room = room if file_: @@ -621,9 +627,11 @@ class SyncplayUserlist(object): self.userListChange() def setUserAsController(self, username): - if self._users.has_key(username): + if self.currentUser.username == username: + self.currentUser.setControllerStatus(True) + elif self._users.has_key(username): user = self._users[username] - user.setAsController() + user.setControllerStatus(True) def areAllFilesInRoomSame(self): for user in self._users.itervalues(): @@ -691,6 +699,9 @@ class UiManager(object): if constants.SHOW_OSD and self._client._player: self._client._player.displayMessage(message, duration * 1000) + def setControllerStatus(self, username, isController): + self.__ui.setControllerStatus(username, isController) + def showErrorMessage(self, message, criticalerror=False): if message <> self.lastError: # Avoid double call bug self.lastError = message diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 36d15b8..2fa8cdf 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -120,9 +120,9 @@ class SyncClientProtocol(JSONCommandProtocol): self._SetUser(values) elif command == "controllerAuth": if values['success']: - self._client.controllerIdentificationSuccess(values["user"]) + self._client.controllerIdentificationSuccess(values["user"], values["room"]) else: - self._client.controllerIdentificationError(values["user"]) + self._client.controllerIdentificationError(values["user"], values["room"]) elif command == "newControlledRoom": controlPassword = values['password'] roomName = values['roomName'] @@ -338,10 +338,11 @@ class SyncServerProtocol(JSONCommandProtocol): } }) - def sendControlledRoomAuthStatus(self, success, username): + def sendControlledRoomAuthStatus(self, success, username, roomname): self.sendSet({ "controllerAuth": { "user": username, + "room": roomname, "success": success } }) diff --git a/syncplay/server.py b/syncplay/server.py index 2d79067..4b1699a 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -71,7 +71,7 @@ class SyncFactory(Factory): self.sendRoomSwitchMessage(watcher) if RoomPasswordProvider.isControlledRoom(roomName): for controller in watcher.getRoom().getControllers(): - watcher.sendControlledRoomAuthStatus(True, controller) + watcher.sendControlledRoomAuthStatus(True, controller, roomName) def sendRoomSwitchMessage(self, watcher): l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None) @@ -115,12 +115,12 @@ class SyncFactory(Factory): success = RoomPasswordProvider.check(room.getName(), password, self._salt) if success: watcher.getRoom().addController(watcher) - self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(success, watcher.getName())) + self._roomManager.broadcast(watcher, lambda w: w.sendControlledRoomAuthStatus(success, watcher.getName(), room._name)) except NotControlledRoom: newName = RoomPasswordProvider.getControlledRoomName(room.getName(), password, self._salt) watcher.sendNewControlledRoom(newName, password) except ValueError: - self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName())) + self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name)) class RoomManager(object): @@ -346,8 +346,8 @@ class Watcher(object): def sendNewControlledRoom(self, roomName, password): self._connector.sendNewControlledRoom(roomName, password) - def sendControlledRoomAuthStatus(self, success, username): - self._connector.sendControlledRoomAuthStatus(success, username) + def sendControlledRoomAuthStatus(self, success, username, room): + self._connector.sendControlledRoomAuthStatus(success, username, room) def __lt__(self, b): if self.getPosition() is None or self._file is None: diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 57a5981..c7d10e3 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -39,6 +39,9 @@ class ConsoleUI(threading.Thread): def updateRoomName(self, room=""): pass + def setControllerStatus(self, username, isController): + pass + def promptFor(self, prompt=">", message=""): if message <> "": print(message) @@ -77,7 +80,7 @@ class ConsoleUI(threading.Thread): def showDebugMessage(self, message): print(message) - + def showErrorMessage(self, message, criticalerror = False): print("ERROR:\t" + message) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 41bee36..2c1f718 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -6,7 +6,7 @@ import sys import time import re import os -from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration +from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider class UserlistItemDelegate(QtGui.QStyledItemDelegate): def __init__(self): @@ -79,6 +79,19 @@ class MainWindow(QtGui.QMainWindow): # TODO: Prompt user return None + def setControllerStatus(self, username, isController): + roomtocheck = 0 + while self.listTreeModel.item(roomtocheck): + itemtocheck = 0 + while self.listTreeModel.item(roomtocheck).child(itemtocheck): + item = self.listTreeModel.item(roomtocheck).child(itemtocheck) + if item.data(0) == username: + item.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) + return + itemtocheck = itemtocheck + 1 + roomtocheck += 1 + self.listTreeView.resizeColumnToContents(True) + def showMessage(self, message, noTimestamp=False): message = unicode(message) message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") @@ -107,21 +120,13 @@ class MainWindow(QtGui.QMainWindow): roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable) blankitem.setFlags(blankitem.flags() & ~Qt.ItemIsEditable) usertreeRoot.appendRow((roomitem, blankitem)) - #usercounter = 0 # UNCOMMENT FOR DEMONSTRATION for user in rooms[room]: useritem = QtGui.QStandardItem(user.username) - '''# UNCOMMENT FOR DEMONSTRATION - usercounter = usercounter + 1 - - if usercounter == 1: - useritem.setData(True, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) - useritem.setData(True, Qt.UserRole + constants.USERITEM_READY_ROLE) - if usercounter == 2: - useritem.setData(False, Qt.UserRole + constants.USERITEM_READY_ROLE)l''' + isController = user.isController() + useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) if user.file: - fileitem = QtGui.QStandardItem( - u"{} ({})".format(user.file['name'], formatTime(user.file['duration']))) + fileitem = QtGui.QStandardItem(u"{} ({})".format(user.file['name'], formatTime(user.file['duration']))) if currentUser.file: sameName = sameFilename(user.file['name'], currentUser.file['name']) sameSize = sameFilesize(user.file['size'], currentUser.file['size']) diff --git a/syncplay/utils.py b/syncplay/utils.py index cfd6eca..16debcf 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -209,6 +209,7 @@ class RoomPasswordProvider(object): @staticmethod def _computeRoomHash(roomName, password, salt): + roomName = roomName.encode('utf8') salt = hashlib.sha256(salt).hexdigest() provisionalHash = hashlib.sha256(roomName + salt).hexdigest() return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper() From 94ce0221b82a675210ef6c4f121652c8077e7cb8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 15 Oct 2014 01:55:44 +0100 Subject: [PATCH 049/142] Don't die on empty identify password --- syncplay/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/utils.py b/syncplay/utils.py index 16debcf..4a611ea 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -193,7 +193,7 @@ class RoomPasswordProvider(object): @staticmethod def check(roomName, password, salt): - if not re.match(RoomPasswordProvider.PASSWORD_REGEX, password): + if not password or not re.match(RoomPasswordProvider.PASSWORD_REGEX, password): raise ValueError() match = re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName) From 68d163e476e176cc183e8ee2eb11c1760c76bf36 Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 15 Oct 2014 02:07:33 +0100 Subject: [PATCH 050/142] Add console support for controlled rooms --- syncplay/constants.py | 2 ++ syncplay/messages.py | 2 ++ syncplay/ui/consoleUI.py | 14 ++++++++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index facce07..12545e5 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -45,6 +45,8 @@ COMMANDS_LIST = ["l", "list", "users"] COMMANDS_PAUSE = ["p", "play", "pause"] COMMANDS_ROOM = ["r", "room"] COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?'] +COMMANDS_CREATE = ['c','create'] +COMMANDS_IDENTIFY = ['i','identify'] MPC_MIN_VER = "1.6.4" VLC_MIN_VERSION = "2.0.0" VLC_INTERFACE_MIN_VERSION = "0.2.1" diff --git a/syncplay/messages.py b/syncplay/messages.py index 35ae499..23ed522 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -53,6 +53,8 @@ en = { "commandlist-notification/pause" : "\tp - toggle pause", "commandlist-notification/seek" : "\t[s][+-]time - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec", "commandlist-notification/help" : "\th - this help", + "commandlist-notification/create" : "\tc - create controlled room using name of current room", + "commandlist-notification/identify" : "\ti [password] - identify as room controller with controller password", "syncplay-version-notification" : "Syncplay version: {}", # syncplay.version "more-info-notification" : "More info available at: {}", # projectURL diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index c7d10e3..8a60c68 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -55,14 +55,17 @@ class ConsoleUI(threading.Thread): message = u"In room '{}':".format(room) self.showMessage(message, True) for user in rooms[room]: - username = "*<{}>*".format(user.username) if user == currentUser else "<{}>".format(user.username) + userflags = u"" + if user.isController(): + userflags = userflags + u"(Controller) " + username = userflags + u"*<{}>*".format(user.username) if user == currentUser else userflags + u"<{}>".format(user.username) if user.file: message = u"{} is playing:".format(username) self.showMessage(message, True) message = u" File: '{}' ({})".format(user.file['name'], formatTime(user.file['duration'])) if currentUser.file: if user.file['name'] == currentUser.file['name'] and user.file['size'] != currentUser.file['size']: - message += " (their file size is different from yours!)" + message += u" (their file size is different from yours!)" self.showMessage(message, True) else: message = u"{} is not playing a file".format(username) @@ -140,6 +143,11 @@ class ConsoleUI(threading.Thread): self._syncplayClient.setRoom(room) self._syncplayClient.sendRoom() + elif command.group('command') in constants.COMMANDS_CREATE: + self._syncplayClient.createControlledRoom() + elif command.group('command') in constants.COMMANDS_IDENTIFY: + controlpassword = command.group('parameter') + self._syncplayClient.identifyAsController(controlpassword) else: if self._tryAdvancedCommands(data): return @@ -152,6 +160,8 @@ class ConsoleUI(threading.Thread): self.showMessage(getMessage("commandlist-notification/pause"), True) self.showMessage(getMessage("commandlist-notification/seek"), True) self.showMessage(getMessage("commandlist-notification/help"), True) + self.showMessage(getMessage("commandlist-notification/create"), True) + self.showMessage(getMessage("commandlist-notification/identify"), True) self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True) self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True) From a06b691f297325ac35b18fff0b4b89232ecd8bd6 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 17 Oct 2014 15:39:22 +0100 Subject: [PATCH 051/142] Rename 'i'/'identify' to 'auth' to avoid confusion with 'l'/'list' --- syncplay/constants.py | 2 +- syncplay/messages.py | 2 +- syncplay/ui/consoleUI.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 12545e5..0e6b66d 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -46,7 +46,7 @@ COMMANDS_PAUSE = ["p", "play", "pause"] COMMANDS_ROOM = ["r", "room"] COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?'] COMMANDS_CREATE = ['c','create'] -COMMANDS_IDENTIFY = ['i','identify'] +COMMANDS_AUTH = ['a','auth'] MPC_MIN_VER = "1.6.4" VLC_MIN_VERSION = "2.0.0" VLC_INTERFACE_MIN_VERSION = "0.2.1" diff --git a/syncplay/messages.py b/syncplay/messages.py index 23ed522..77a7d07 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -54,7 +54,7 @@ en = { "commandlist-notification/seek" : "\t[s][+-]time - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec", "commandlist-notification/help" : "\th - this help", "commandlist-notification/create" : "\tc - create controlled room using name of current room", - "commandlist-notification/identify" : "\ti [password] - identify as room controller with controller password", + "commandlist-notification/auth" : "\ta [password] - authenticate as room controller with controller password", "syncplay-version-notification" : "Syncplay version: {}", # syncplay.version "more-info-notification" : "More info available at: {}", # projectURL diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 8a60c68..6af11b6 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -145,7 +145,7 @@ class ConsoleUI(threading.Thread): self._syncplayClient.sendRoom() elif command.group('command') in constants.COMMANDS_CREATE: self._syncplayClient.createControlledRoom() - elif command.group('command') in constants.COMMANDS_IDENTIFY: + elif command.group('command') in constants.COMMANDS_AUTH: controlpassword = command.group('parameter') self._syncplayClient.identifyAsController(controlpassword) else: @@ -161,7 +161,7 @@ class ConsoleUI(threading.Thread): self.showMessage(getMessage("commandlist-notification/seek"), True) self.showMessage(getMessage("commandlist-notification/help"), True) self.showMessage(getMessage("commandlist-notification/create"), True) - self.showMessage(getMessage("commandlist-notification/identify"), True) + self.showMessage(getMessage("commandlist-notification/auth"), True) self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True) self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True) From cbf509b057970ff5e9b8cbd2a9cb67e52619bfa9 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 17 Oct 2014 16:45:50 +0100 Subject: [PATCH 052/142] Strip improper chars from control pass auth --- syncplay/client.py | 8 ++++++++ syncplay/constants.py | 1 + 2 files changed, 9 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index 8854850..821d08b 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -3,6 +3,7 @@ import os.path import random import string import time +import re from twisted.internet.protocol import ClientFactory from twisted.internet import reactor, task from syncplay.protocols import SyncClientProtocol @@ -427,7 +428,14 @@ class SyncplayClient(object): self._protocol.requestControlledRoom(controlPassword) self.ui.updateRoomName(roomName) + def stripControlPassword(self, controlPassword): + if controlPassword: + return re.sub(constants.CONTROL_PASSWORD_STRIP_REGEX, "", controlPassword).upper() + else: + return "" + def identifyAsController(self, controlPassword): + controlPassword = self.stripControlPassword(controlPassword) self.ui.showMessage(u"Identifying as room controller with password '{}'...".format(controlPassword)) self._protocol.requestControlledRoom(controlPassword) diff --git a/syncplay/constants.py b/syncplay/constants.py index 0e6b66d..c2473ad 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -40,6 +40,7 @@ MERGE_PLAYPAUSE_BUTTONS = False SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players #Usually there's no need to adjust these FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]" +CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]" COMMANDS_UNDO = ["u", "undo", "revert"] COMMANDS_LIST = ["l", "list", "users"] COMMANDS_PAUSE = ["p", "play", "pause"] From b47c8a00b36e49fa0d08c27b5dee1d1ef8b7cd21 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 20 Oct 2014 23:44:47 +0100 Subject: [PATCH 053/142] Allow user to specify name of controlled room --- syncplay/client.py | 14 +++++++------- syncplay/constants.py | 1 + syncplay/messages.py | 2 +- syncplay/protocols.py | 8 +++++--- syncplay/server.py | 11 ++++++----- syncplay/ui/consoleUI.py | 6 +++++- syncplay/ui/gui.py | 6 +++++- syncplay/utils.py | 16 +++++++++++++++- 8 files changed, 45 insertions(+), 19 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 821d08b..0e2c50f 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -416,16 +416,16 @@ class SyncplayClient(object): if promptForAction: self.ui.promptFor(getMessage("enter-to-exit-prompt")) - def createControlledRoom(self): + def createControlledRoom(self, roomName): controlPassword = RoomPasswordGenerator.generate_password() - self.ui.showMessage(u"Attempting to create controlled room suffix with password '{}'...".format(controlPassword)) - self._protocol.requestControlledRoom(controlPassword) + self.ui.showMessage(u"Attempting to create controlled room '{}' with password '{}'...".format(roomName, controlPassword)) + self._protocol.requestControlledRoom(roomName, controlPassword) - def controlledRoomCreated(self, controlPassword, roomName): - self.ui.showMessage(u"Created controlled room suffix '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword)) + def controlledRoomCreated(self, roomName, controlPassword): + self.ui.showMessage(u"Created controlled room '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword)) self.setRoom(roomName) self.sendRoom() - self._protocol.requestControlledRoom(controlPassword) + self._protocol.requestControlledRoom(roomName, controlPassword) self.ui.updateRoomName(roomName) def stripControlPassword(self, controlPassword): @@ -437,7 +437,7 @@ class SyncplayClient(object): def identifyAsController(self, controlPassword): controlPassword = self.stripControlPassword(controlPassword) self.ui.showMessage(u"Identifying as room controller with password '{}'...".format(controlPassword)) - self._protocol.requestControlledRoom(controlPassword) + self._protocol.requestControlledRoom(self.getRoom(), controlPassword) def controllerIdentificationError(self, username, room): self.ui.showErrorMessage(u"<{}> failed to identify as a room controller.".format(username)) diff --git a/syncplay/constants.py b/syncplay/constants.py index c2473ad..9bd0065 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -41,6 +41,7 @@ SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear #Usually there's no need to adjust these 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_LIST = ["l", "list", "users"] COMMANDS_PAUSE = ["p", "play", "pause"] diff --git a/syncplay/messages.py b/syncplay/messages.py index 77a7d07..187ae16 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -53,7 +53,7 @@ en = { "commandlist-notification/pause" : "\tp - toggle pause", "commandlist-notification/seek" : "\t[s][+-]time - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec", "commandlist-notification/help" : "\th - this help", - "commandlist-notification/create" : "\tc - create controlled room using name of current room", + "commandlist-notification/create" : "\tc [name] - create controlled room using name of current room", "commandlist-notification/auth" : "\ta [password] - authenticate as room controller with controller password", "syncplay-version-notification" : "Syncplay version: {}", # syncplay.version "more-info-notification" : "More info available at: {}", # projectURL diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 2fa8cdf..67b6026 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -126,7 +126,7 @@ class SyncClientProtocol(JSONCommandProtocol): elif command == "newControlledRoom": controlPassword = values['password'] roomName = values['roomName'] - self._client.controlledRoomCreated(controlPassword, roomName) + self._client.controlledRoomCreated(roomName, controlPassword) def sendSet(self, setting): self.sendMessage({"Set": setting}) @@ -217,9 +217,10 @@ class SyncClientProtocol(JSONCommandProtocol): state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly self.sendMessage({"State": state}) - def requestControlledRoom(self, password): + def requestControlledRoom(self, room, password): self.sendSet({ "controllerAuth": { + "room": room, "password": password } }) @@ -325,7 +326,8 @@ class SyncServerProtocol(JSONCommandProtocol): self._watcher.setFile(set_[1]) elif command == "controllerAuth": password = set_[1]["password"] if set_[1].has_key("password") else None - self._factory.authRoomController(self._watcher, password) + room = set_[1]["room"] if set_[1].has_key("room") else None + self._factory.authRoomController(self._watcher, password, room) def sendSet(self, setting): self.sendMessage({"Set": setting}) diff --git a/syncplay/server.py b/syncplay/server.py index 4b1699a..f586be3 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -109,15 +109,16 @@ class SyncFactory(Factory): def getAllWatchersForUser(self, forUser): return self._roomManager.getAllWatchersForUser(forUser) - def authRoomController(self, watcher, password): + def authRoomController(self, watcher, password, roomBaseName=None): room = watcher.getRoom() + roomName = roomBaseName if roomBaseName else room.getName() try: - success = RoomPasswordProvider.check(room.getName(), password, self._salt) + success = RoomPasswordProvider.check(roomName, password, self._salt) if success: watcher.getRoom().addController(watcher) self._roomManager.broadcast(watcher, lambda w: w.sendControlledRoomAuthStatus(success, watcher.getName(), room._name)) except NotControlledRoom: - newName = RoomPasswordProvider.getControlledRoomName(room.getName(), password, self._salt) + newName = RoomPasswordProvider.getControlledRoomName(roomName, password, self._salt) watcher.sendNewControlledRoom(newName, password) except ValueError: self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name)) @@ -343,8 +344,8 @@ class Watcher(object): def sendSetting(self, user, room, file_, event): self._connector.sendUserSetting(user, room, file_, event) - def sendNewControlledRoom(self, roomName, password): - self._connector.sendNewControlledRoom(roomName, password) + def sendNewControlledRoom(self, roomBaseName, password): + self._connector.sendNewControlledRoom(roomBaseName, password) def sendControlledRoomAuthStatus(self, success, username, room): self._connector.sendControlledRoomAuthStatus(success, username, room) diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 6af11b6..70acede 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -144,7 +144,11 @@ class ConsoleUI(threading.Thread): self._syncplayClient.setRoom(room) self._syncplayClient.sendRoom() elif command.group('command') in constants.COMMANDS_CREATE: - self._syncplayClient.createControlledRoom() + roombasename = command.group('parameter') + if roombasename == None: + roombasename = self._syncplayClient.getRoom() + roombasename = utils.stripRoomName(roombasename) + self._syncplayClient.createControlledRoom(roombasename) elif command.group('command') in constants.COMMANDS_AUTH: controlpassword = command.group('parameter') self._syncplayClient.identifyAsController(controlpassword) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 2c1f718..db015a4 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -275,7 +275,11 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient._player.openFile(fileName) def createControlledRoom(self): - self._syncplayClient.createControlledRoom() + controlroom, ok = QtGui.QInputDialog.getText(self, "Create controlled room", + "Enter name of controlled room\r\n(see http://syncplay.pl/guide/ for usage instructions):", QtGui.QLineEdit.Normal, + utils.stripRoomName(self._syncplayClient.getRoom())) + if ok and controlroom != '': + self._syncplayClient.createControlledRoom(controlroom) def identifyAsController(self): tooltip = "Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):" diff --git a/syncplay/utils.py b/syncplay/utils.py index 4a611ea..5d6728f 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -141,7 +141,19 @@ def blackholeStdoutForFrozenWindow(): # Relate to file hashing / difference checking: def stripfilename(filename): - return re.sub(constants.FILENAME_STRIP_REGEX, "", filename) + if filename: + return re.sub(constants.FILENAME_STRIP_REGEX, "", filename) + else: + return "" + +def stripRoomName(RoomName): + if RoomName: + try: + return re.sub(constants.ROOM_NAME_STRIP_REGEX, "\g", RoomName) + except IndexError: + return RoomName + else: + return "" def hashFilename(filename): return hashlib.sha256(stripfilename(filename).encode('utf-8')).hexdigest()[:12] @@ -196,6 +208,8 @@ class RoomPasswordProvider(object): if not password or not re.match(RoomPasswordProvider.PASSWORD_REGEX, password): raise ValueError() + if not roomName: + raise NotControlledRoom() match = re.match(RoomPasswordProvider.CONTROLLED_ROOM_REGEX, roomName) if not match: raise NotControlledRoom() From aaac02f6fceaee53c8727d73b5aa34b14bb1bc9d Mon Sep 17 00:00:00 2001 From: Uriziel Date: Tue, 21 Oct 2014 23:21:30 +0200 Subject: [PATCH 054/142] Fixed mpv problem with unicode filenames (hack?) --- syncplay/players/mplayer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 343a201..aca166e 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -277,7 +277,9 @@ class MplayerPlayer(BasePlayer): def sendLine(self, line): try: - line = (line.decode('utf8') + u"\n").encode('utf8') + if not isinstance(line, unicode): + line = line.decode('utf8') + line = (line + u"\n").encode('utf8') self.__process.stdin.write(line) except IOError: pass From fc5f1e62c93ff391d21e86cacf53b5e2b89577f5 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 21 Oct 2014 23:29:20 +0100 Subject: [PATCH 055/142] Properly update userlist on auth --- syncplay/client.py | 2 +- syncplay/ui/consoleUI.py | 3 --- syncplay/ui/gui.py | 13 ------------- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 0e2c50f..6b786e5 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -444,10 +444,10 @@ class SyncplayClient(object): def controllerIdentificationSuccess(self, username, roomname): self.userlist.setUserAsController(username) - self.ui.setControllerStatus(username, isController=True) if self.userlist.isRoomSame(roomname): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD self.ui.showMessage(u"<{}> authenticated as a room controller".format(username), hideFromOSD) + self.ui.userListChange() # TODO: Mark person as a room controller # TODO: Disable UI's "Create new Controlled Room when in Controlled Room" diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 70acede..1b060ef 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -39,9 +39,6 @@ class ConsoleUI(threading.Thread): def updateRoomName(self, room=""): pass - def setControllerStatus(self, username, isController): - pass - def promptFor(self, prompt=">", message=""): if message <> "": print(message) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index db015a4..70c3804 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -79,19 +79,6 @@ class MainWindow(QtGui.QMainWindow): # TODO: Prompt user return None - def setControllerStatus(self, username, isController): - roomtocheck = 0 - while self.listTreeModel.item(roomtocheck): - itemtocheck = 0 - while self.listTreeModel.item(roomtocheck).child(itemtocheck): - item = self.listTreeModel.item(roomtocheck).child(itemtocheck) - if item.data(0) == username: - item.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) - return - itemtocheck = itemtocheck + 1 - roomtocheck += 1 - self.listTreeView.resizeColumnToContents(True) - def showMessage(self, message, noTimestamp=False): message = unicode(message) message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") From 1db7c73c42393ae68d77b7fce69d69c92e38b7d2 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 21 Oct 2014 23:29:59 +0100 Subject: [PATCH 056/142] Controllers listed first --- syncplay/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 6b786e5..e31c44e 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -524,7 +524,10 @@ class SyncplayUser(object): return sameName and sameSize and sameDuration def __lt__(self, other): - return self.username.lower() < other.username.lower() + if self.isController() == other.isController(): + return self.username.lower() < other.username.lower() + else: + return self.isController() > other.isController() def __repr__(self, *args, **kwargs): if self.file: From 76e1d977cfd6d4e0ba7ffaabef87bf8f8b6aee17 Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 22 Oct 2014 00:43:00 +0100 Subject: [PATCH 057/142] Lie w/ "version" and use "realversion" for true version (1.2.X compatibility) --- syncplay/protocols.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 67b6026..18da515 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -71,6 +71,7 @@ class SyncClientProtocol(JSONCommandProtocol): username = hello["username"] if hello.has_key("username") else None roomName = hello["room"]["name"] if hello.has_key("room") else None 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 @@ -94,7 +95,8 @@ class SyncClientProtocol(JSONCommandProtocol): if password: hello["password"] = password room = self._client.getRoom() if room: hello["room"] = {"name" :room} - hello["version"] = syncplay.version + hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server + hello["realversion"] = syncplay.version self.sendMessage({"Hello": hello}) def _SetUser(self, users): @@ -277,6 +279,7 @@ class SyncServerProtocol(JSONCommandProtocol): roomName = room["name"] if room.has_key("name") else None roomName = roomName.strip() version = hello["version"] if hello.has_key("version") else None + version = hello["realversion"] if hello.has_key("realversion") else version return username, serverPassword, roomName, version def _checkPassword(self, serverPassword): @@ -311,7 +314,8 @@ class SyncServerProtocol(JSONCommandProtocol): userIp = self.transport.getPeer().host room = self._watcher.getRoom() if room: hello["room"] = {"name": room.getName()} - hello["version"] = clientVersion # syncplay.version - Don't BC with 1.2.x + 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) self.sendMessage({"Hello": hello}) From 1e715cc11c0b8f0c926cfc4439ed9a0fe7c7226d Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 19:58:56 +0100 Subject: [PATCH 058/142] Make individual columns for filename, size & duration --- syncplay/messages.py | 6 +++- syncplay/ui/gui.py | 66 +++++++++++++++++++++++--------------------- syncplay/utils.py | 13 +++++++++ 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 187ae16..965cf6d 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -172,7 +172,9 @@ en = { "pause-guibuttonlabel" : "Pause", "roomuser-heading-label" : "Room / User", - "fileplayed-heading-label" : "File being played", + "size-heading-label" : "Size", + "duration-heading-label" : "Length", + "filename-heading-label" : "Filename", "notifications-heading-label" : "Notifications", "userlist-heading-label" : "List of who is playing what", "othercommands-heading-label" : "Other commands", @@ -192,6 +194,8 @@ en = { "setoffset-msgbox-label" : "Set offset", "offsetinfo-msgbox-label" : "Offset (see http://syncplay.pl/guide/ for usage instructions):", + "mebibyte-suffix" : " MiB", + # Tooltips "host-tooltip" : "Hostname or IP to connect to, optionally including port (e.g. syncplay.pl:8999). Only synchronised with people on same server/port.", diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 70c3804..59fd0c4 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -6,7 +6,7 @@ import sys import time import re import os -from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider +from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize class UserlistItemDelegate(QtGui.QStyledItemDelegate): def __init__(self): @@ -92,9 +92,8 @@ class MainWindow(QtGui.QMainWindow): def showUserList(self, currentUser, rooms): self._usertreebuffer = QtGui.QStandardItemModel() - self._usertreebuffer.setColumnCount(2) self._usertreebuffer.setHorizontalHeaderLabels( - (getMessage("roomuser-heading-label"), getMessage("fileplayed-heading-label"))) + (getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") )) usertreeRoot = self._usertreebuffer.invisibleRootItem() for room in rooms: @@ -113,49 +112,54 @@ class MainWindow(QtGui.QMainWindow): isController = user.isController() useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) if user.file: - fileitem = QtGui.QStandardItem(u"{} ({})".format(user.file['name'], formatTime(user.file['duration']))) + filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'])) + filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration']))) + filenameitem = QtGui.QStandardItem((user.file['name'])) if currentUser.file: sameName = sameFilename(user.file['name'], currentUser.file['name']) sameSize = sameFilesize(user.file['size'], currentUser.file['size']) sameDuration = sameFileduration(user.file['duration'], currentUser.file['duration']) sameRoom = room == currentUser.room - differentName = not sameName - differentSize = not sameSize - differentDuration = not sameDuration - if sameName or sameRoom: - if differentSize and sameDuration: - fileitem = QtGui.QStandardItem( - u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), - getMessage("differentsize-note"))) - elif differentSize and differentDuration: - fileitem = QtGui.QStandardItem( - u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), - getMessage("differentsizeandduration-note"))) - elif differentDuration: - fileitem = QtGui.QStandardItem( - u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), - getMessage("differentduration-note"))) - if sameRoom and (differentName or differentSize or differentDuration): - fileitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) + if sameRoom: + if not sameName: + filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) + if not sameSize: + if currentUser.file is not None and formatSize(user.file['size']) == formatSize(currentUser.file['size']): + filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'],precise=True)) + filesizeitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) + if not sameDuration: + filedurationitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) else: - fileitem = QtGui.QStandardItem(getMessage("nofile-note")) + filenameitem = QtGui.QStandardItem(getMessage("nofile-note")) + filedurationitem = QtGui.QStandardItem("") + filesizeitem = QtGui.QStandardItem("") if room == currentUser.room: - fileitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR))) + filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR))) if currentUser.username == user.username: font = QtGui.QFont() font.setWeight(QtGui.QFont.Bold) useritem.setFont(font) useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) - fileitem.setFlags(fileitem.flags() & ~Qt.ItemIsEditable) - roomitem.appendRow((useritem, fileitem)) - + filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable) + filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable) + filedurationitem.setFlags(filedurationitem.flags() & ~Qt.ItemIsEditable) + roomitem.appendRow((useritem, filesizeitem, filedurationitem, filenameitem)) self.listTreeModel = self._usertreebuffer self.listTreeView.setModel(self.listTreeModel) self.listTreeView.setItemDelegate(UserlistItemDelegate()) self.listTreeView.setItemsExpandable(False) self.listTreeView.expandAll() - self.listTreeView.resizeColumnToContents(0) - self.listTreeView.resizeColumnToContents(1) + self.listTreeView.header().setStretchLastSection(False) + self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) + self.listTreeView.header().setResizeMode(1, QtGui.QHeaderView.ResizeToContents) + self.listTreeView.header().setResizeMode(2, QtGui.QHeaderView.ResizeToContents) + self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.ResizeToContents) + NarrowTabsWidth = self.listTreeView.header().sectionSize(0)+self.listTreeView.header().sectionSize(1)+self.listTreeView.header().sectionSize(2) + if self.listTreeView.header().width() < (NarrowTabsWidth+self.listTreeView.header().sectionSize(3)): + self.listTreeView.header().resizeSection(3,self.listTreeView.header().width()-NarrowTabsWidth) + else: + self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.Stretch) + self.listTreeView.expandAll() def roomClicked(self, item): while item.parent().row() != -1: @@ -356,8 +360,8 @@ class MainWindow(QtGui.QMainWindow): window.topSplit.addWidget(window.outputFrame) window.topSplit.addWidget(window.listFrame) - window.topSplit.setStretchFactor(0, 4) - window.topSplit.setStretchFactor(1, 5) + window.topSplit.setStretchFactor(0,4) + window.topSplit.setStretchFactor(1,5) window.mainLayout.addWidget(window.topSplit) window.topSplit.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) diff --git a/syncplay/utils.py b/syncplay/utils.py index 5d6728f..cf04c2d 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -93,6 +93,19 @@ def formatTime(timeInSeconds, weeksAsTitles=True): formattedTime = "{0:} (Title {1:.0f})".format(formattedTime, title) return formattedTime +def formatSize (bytes, precise=False): + if bytes == 0: # E.g. when file size privacy is enabled + return "???" + try: + mebibytes = int(bytes) / 1048576.0 + if precise: + mebibytes = round(mebibytes, 1) + else: + mebibytes = int(mebibytes) + return str(mebibytes) + getMessage("mebibyte-suffix") + except: # E.g. when filesize is hashed + return "???" + def findWorkingDir(): frozen = getattr(sys, 'frozen', '') if not frozen: From c78ce70afaa6356038d98ed96461f5102e65a4dd Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 20:19:21 +0100 Subject: [PATCH 059/142] Make room name column span entire row --- syncplay/ui/gui.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 59fd0c4..fa6fd18 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -104,8 +104,7 @@ class MainWindow(QtGui.QMainWindow): roomitem.setFont(font) blankitem = QtGui.QStandardItem("") roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable) - blankitem.setFlags(blankitem.flags() & ~Qt.ItemIsEditable) - usertreeRoot.appendRow((roomitem, blankitem)) + usertreeRoot.appendRow(roomitem) for user in rooms[room]: useritem = QtGui.QStandardItem(user.username) @@ -149,6 +148,10 @@ class MainWindow(QtGui.QMainWindow): self.listTreeView.setItemDelegate(UserlistItemDelegate()) self.listTreeView.setItemsExpandable(False) self.listTreeView.expandAll() + roomtocheck = 0 + while self.listTreeModel.item(roomtocheck): + self.listTreeView.setFirstColumnSpanned(roomtocheck, self.listTreeView.rootIndex(), True) + roomtocheck += 1 self.listTreeView.header().setStretchLastSection(False) self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) self.listTreeView.header().setResizeMode(1, QtGui.QHeaderView.ResizeToContents) From 7794a94aa7fca924d5e3ab79835d1de60617e170 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 20:32:04 +0100 Subject: [PATCH 060/142] Make rooms italicised --- syncplay/ui/gui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index fa6fd18..c750018 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -98,11 +98,11 @@ class MainWindow(QtGui.QMainWindow): for room in rooms: roomitem = QtGui.QStandardItem(room) + font = QtGui.QFont() + font.setItalic(True) if room == currentUser.room: - font = QtGui.QFont() font.setWeight(QtGui.QFont.Bold) - roomitem.setFont(font) - blankitem = QtGui.QStandardItem("") + roomitem.setFont(font) roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable) usertreeRoot.appendRow(roomitem) From 2aa7b50ac6d3a4864f9ad243968ee0e7e67f7d4e Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 23:03:25 +0100 Subject: [PATCH 061/142] Use different room icon for controlled/uncontroleld rooms --- resources/lock.png | Bin 0 -> 749 bytes syncplay/ui/gui.py | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 resources/lock.png diff --git a/resources/lock.png b/resources/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..2ebc4f6f9663e32cad77d67ef93ab8843dfea3c0 GIT binary patch literal 749 zcmVe|tv9>?g+k#9o0pTxd@;_sq{kwlU;^VvV*?BV8P@}BoaZTQUROpWV6|-M`|^n&)=+8tHo3*<<$NU zU`%V~ZF;?hBSYsjJ6%JzV}E(D{pOLqQklliUf9um_tGl-wty`y*p?eYNW56P>X@1s zZs7KrRZKtmV7Lqj^5Fgr7_`LjhdJK@ltF&O`j7?*NUM$KvmNGz)3WjM?V$vHlPT0AFyF?kLE<#HZabCSW3-oa*6;Z zrXD`Ulwd<^2glP%1Y1Kc1Ij%DU^=ME(jKf6APNlA$Uu;J4bVilQHSWX5uJ$9Zsp4M z0%!@LvyTxz=Z6stxlichODIY+yNGt%RM;m`>H4LOKLFs9Y%b5aUN|2|{0Zw|<_~i} fmXz*V19AKYa Date: Fri, 24 Oct 2014 23:04:14 +0100 Subject: [PATCH 062/142] Make usernames of non-controllers in controlled rooms grey --- syncplay/constants.py | 1 + syncplay/ui/gui.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/syncplay/constants.py b/syncplay/constants.py index 9bd0065..c300163 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -106,6 +106,7 @@ STYLE_USERNAME = "color: #367AA9; font-weight:bold;" STYLE_ERRORNOTIFICATION = "color: red;" STYLE_DIFFERENTITEM_COLOR = 'red' STYLE_NOFILEITEM_COLOR = 'blue' +STYLE_NOTCONTROLLER_COLOR = 'grey' MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4'] # --quiet works with both mpv 0.2 and 0.3 diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 629408f..823490d 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -122,6 +122,8 @@ class MainWindow(QtGui.QMainWindow): useritem = QtGui.QStandardItem(user.username) isController = user.isController() useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) + if isControlledRoom and not isController: + useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) if user.file: filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'])) filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration']))) From 526cb80b86e5c90e6819f5fcd069049350380708 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 23:08:36 +0100 Subject: [PATCH 063/142] Don't make non-controller grey if they are the current user --- syncplay/ui/gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 823490d..2d24c31 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -122,8 +122,6 @@ class MainWindow(QtGui.QMainWindow): useritem = QtGui.QStandardItem(user.username) isController = user.isController() useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) - if isControlledRoom and not isController: - useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) if user.file: filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'])) filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration']))) @@ -152,6 +150,8 @@ class MainWindow(QtGui.QMainWindow): font = QtGui.QFont() font.setWeight(QtGui.QFont.Bold) useritem.setFont(font) + elif isControlledRoom and not isController: + useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable) filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable) From 0c9128cf4481dce0edd61dc2d31359db24889565 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 23:16:17 +0100 Subject: [PATCH 064/142] Use bullet_key icon instead of controller --- resources/bullet_key.png | Bin 0 -> 436 bytes resources/controller.png | Bin 666 -> 0 bytes syncplay/ui/gui.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 resources/bullet_key.png delete mode 100644 resources/controller.png diff --git a/resources/bullet_key.png b/resources/bullet_key.png new file mode 100644 index 0000000000000000000000000000000000000000..3d37f2ea49a445bd104e2afa39601a01b1acb04a GIT binary patch literal 436 zcmV;l0ZaagP))D9~S@raHZk@o8xu=?=BAbx-CyJk|+c2F0@|%^WDn-Kc3b7|M6nh z{|^`I{(pPa@c-rv`wv7JaBHSX+n2kY|Gz!R`v3h=@&9l4pV{WeA)K@>rLPPXC_&Fo|~u=OHx8Q*=y2zb-wG%)4f&?mZqzQ e4THQu3 zlg*1#VHC#iD%uAHty;DTg0}q?ty;8bDFzCDreJ80{BB+R< zJENR4PZMQi-K$n$6{M+e{`y zCzHvi{{^55g~BV5NOa+FIKC8%#WFEpk_S+;0uA4flMsu=wvk9g@eiQYYGoUZ#;;nf zwk?;-FdB`3VHmiWEa5y`gl|tCa~huHAQ%j?r0MVf0GiFFt=sK#C%yTk;Ec=V5(eW1 zy!^<*iw^+Bx5qH>dSE^ra{Kc6JlJfuBMv}5)@Yi~=Nw?K*=zV6|Ft zLOAX<8jU;B^k)=l4vT=l&3kn^9UxVL)9GYU)9I9}vfXZjQmOn(B@&4pQul^EU^1CN zHk*ZXIt^qGkTG9cEEbJkuV?WZo6QC!5(!Z8c%13?`&?OQ#B#^uF;uHnNTpKPc_2n0 zqyCI{R;g4puH&IXp%AEOGKB zFaZe_Htgq(0OW7HPl9o7Hk+@=e6O@xZTY%@-!Rz$1Rlkwh5!Hn07*qoM6N<$f($t; AN&o-= diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 2d24c31..b431c33 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -27,7 +27,7 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): resourcespath = utils.findWorkingDir() + "\\resources\\" else: resourcespath = utils.findWorkingDir() + "/resources/" - controlIconQPixmap = QtGui.QPixmap(resourcespath + "controller.png") + controlIconQPixmap = QtGui.QPixmap(resourcespath + "bullet_key.png") tickIconQPixmap = QtGui.QPixmap(resourcespath + "tick.png") crossIconQPixmap = QtGui.QPixmap(resourcespath + "cross.png") roomController = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) From 984db44d1220d64a9f12999716090d615ebe3b91 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 23:17:57 +0100 Subject: [PATCH 065/142] Move controller icon right until userReady is implemented --- syncplay/ui/gui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index b431c33..4c11b2c 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -35,19 +35,19 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): if roomController and not controlIconQPixmap.isNull(): itemQPainter.drawPixmap ( - optionQStyleOptionViewItem.rect.x()-32, + optionQStyleOptionViewItem.rect.x()-14, optionQStyleOptionViewItem.rect.y(), controlIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) if userReady and not tickIconQPixmap.isNull(): itemQPainter.drawPixmap ( - (optionQStyleOptionViewItem.rect.x()-14), + (optionQStyleOptionViewItem.rect.x()-28), optionQStyleOptionViewItem.rect.y(), tickIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) elif userReady == False and not crossIconQPixmap.isNull(): itemQPainter.drawPixmap ( - (optionQStyleOptionViewItem.rect.x()-14), + (optionQStyleOptionViewItem.rect.x()-28), optionQStyleOptionViewItem.rect.y(), crossIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) From 6160ec1fecbc090e86676281da3f9fe5500f8aa8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 23:45:08 +0100 Subject: [PATCH 066/142] Add user to key icon --- resources/bullet_key.png | Bin 436 -> 0 bytes resources/user_key.png | Bin 0 -> 834 bytes syncplay/ui/gui.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 resources/bullet_key.png create mode 100644 resources/user_key.png diff --git a/resources/bullet_key.png b/resources/bullet_key.png deleted file mode 100644 index 3d37f2ea49a445bd104e2afa39601a01b1acb04a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 436 zcmV;l0ZaagP))D9~S@raHZk@o8xu=?=BAbx-CyJk|+c2F0@|%^WDn-Kc3b7|M6nh z{|^`I{(pPa@c-rv`wv7JaBHSX+n2kY|Gz!R`v3h=@&9l4pV{WeA)K@>rLPPXC_&Fo|~u=OHx8Q*=y2zb-wG%)4f&?mZqzQ e4THQu3gYv3TinXy-2k+}*Gy>bL}L->cqhw$fP*EJb$|SH9*B zJZZj&cz8jvbL6FhjuFt=)&N^Uu4O@CO=euG?YyIIN#V2@=WTCMNs?HihUMzx8`*F+ z7K7k7tw|fes+?r#)3+cv+>fYVg14`e>wfpKEM{Qbc8?N795yHun;udFq6cNzX(hX;quQ*1)P!!FoGESadza0_mQ!A1Bmbxl;}sN zJuH)WVNgr(O@_&#mchHR6zIkIXTEeaN7a#$=)>D@p^PKQ)7i0ZYaXJWCQw2EpPq|o z=*Yo?p{x+^_k>Mr3u>ogv>m)$Z%EHKBwHoRxF^uqUVn4gH|CQl<~tab8IH$1>g;L$ zPbB*p(P~pnX&cS5rg|#I+3L-O%vzm6U>Ep`M=c(+3)RTW6d536Lc#|5acUt^fc4 M07*qoM6N<$f{Dj_?*IS* literal 0 HcmV?d00001 diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 4c11b2c..073b2cc 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -27,7 +27,7 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): resourcespath = utils.findWorkingDir() + "\\resources\\" else: resourcespath = utils.findWorkingDir() + "/resources/" - controlIconQPixmap = QtGui.QPixmap(resourcespath + "bullet_key.png") + controlIconQPixmap = QtGui.QPixmap(resourcespath + "user_key.png") tickIconQPixmap = QtGui.QPixmap(resourcespath + "tick.png") crossIconQPixmap = QtGui.QPixmap(resourcespath + "cross.png") roomController = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) From e220c3c3b39d67e7874b54f3af639be73fdd7bc8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 23:55:58 +0100 Subject: [PATCH 067/142] Re-work username offset to avoid messing up later columns --- syncplay/ui/gui.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 073b2cc..25d405d 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -17,10 +17,6 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): if column == 0: isRoomRow = indexQModelIndex.parent() == indexQModelIndex.parent().parent() itemQPainter.resetTransform() - if not isRoomRow: - transformer = QtGui.QTransform() - transformer.translate(21,0) - itemQPainter.setTransform(transformer) currentQAbstractItemModel = indexQModelIndex.model() itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), 0, indexQModelIndex.parent()) if sys.platform.startswith('win'): @@ -35,21 +31,23 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): if roomController and not controlIconQPixmap.isNull(): itemQPainter.drawPixmap ( - optionQStyleOptionViewItem.rect.x()-14, + optionQStyleOptionViewItem.rect.x()+6, optionQStyleOptionViewItem.rect.y(), controlIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) if userReady and not tickIconQPixmap.isNull(): itemQPainter.drawPixmap ( - (optionQStyleOptionViewItem.rect.x()-28), + (optionQStyleOptionViewItem.rect.x()-10), optionQStyleOptionViewItem.rect.y(), tickIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) elif userReady == False and not crossIconQPixmap.isNull(): itemQPainter.drawPixmap ( - (optionQStyleOptionViewItem.rect.x()-28), + (optionQStyleOptionViewItem.rect.x()-10), optionQStyleOptionViewItem.rect.y(), crossIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) + if not isRoomRow: + optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+21) QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) class MainWindow(QtGui.QMainWindow): From 94c3bf4aa68f165fc409d41579b5b22bf947a380 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 24 Oct 2014 23:58:36 +0100 Subject: [PATCH 068/142] Tidy up user file offset code --- syncplay/ui/gui.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 25d405d..c1ff3e2 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -15,8 +15,6 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): column = indexQModelIndex.column() if column == 0: - isRoomRow = indexQModelIndex.parent() == indexQModelIndex.parent().parent() - itemQPainter.resetTransform() currentQAbstractItemModel = indexQModelIndex.model() itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), 0, indexQModelIndex.parent()) if sys.platform.startswith('win'): @@ -46,7 +44,8 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): (optionQStyleOptionViewItem.rect.x()-10), optionQStyleOptionViewItem.rect.y(), crossIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) - if not isRoomRow: + isUserRow = indexQModelIndex.parent() != indexQModelIndex.parent().parent() + if isUserRow: optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+21) QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) From 106ecc1e943951f6b5793b6853b6f26759965054 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 00:01:38 +0100 Subject: [PATCH 069/142] Update installer to use new icons --- buildPy2exe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 1692e95..e94dbad 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -583,7 +583,8 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/mpc-hc.png','resources/mpc-hc64.png','resources/mplayer.png', 'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png', 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/bullet_black.png', - 'resources/controller.png', 'resources/key_go.png', 'resources/page_white_key.png', 'resources/tick.png' + 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', + 'resources/tick.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) From 261b3e663d6c2bce33d9294e956d105d0bae07c3 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 00:08:03 +0100 Subject: [PATCH 070/142] Use open lock icon if you are controller of room --- buildPy2exe.py | 2 +- resources/lock_open.png | Bin 0 -> 727 bytes syncplay/ui/gui.py | 5 ++++- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 resources/lock_open.png diff --git a/buildPy2exe.py b/buildPy2exe.py index e94dbad..f94fb2f 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -584,7 +584,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png', 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/bullet_black.png', 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', - 'resources/tick.png' + 'resources/tick.png', 'lock_open.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) diff --git a/resources/lock_open.png b/resources/lock_open.png new file mode 100644 index 0000000000000000000000000000000000000000..a471765ff1432092e7113e56b42f2798968b443f GIT binary patch literal 727 zcmV;|0x127P)rLKlJyH=-*k3W|aiVYC~e{STzB+=%NyKm^5w z3m4+bm8cXnzG}0Gw2c^Ol0K$0_ity0#@^}dhsl97GUJu9lkxh`Okt_0|=(|h`!tol*)tahk*i! zgS8GLCq{|Gd7N1T=a4u`0gM4BU}M^~w|L$b3WtyL{rO$Q?PPfL6z$J7;&2In&vryC zqT@6`g9u_Zg@Xt1vO7RIY-D-g8eo{1!oIz%ujUZ52IBq#JOI%e5ertc^kIf}?KOA? zx9*6-2qdQcN5WDRs@&WP6I?Nxm{No*quRTbIBKa{opLb zA%3`w)e+)sGB0CNfH Date: Sat, 25 Oct 2014 00:19:44 +0100 Subject: [PATCH 071/142] Make room names underlined, non-controllers italic --- syncplay/ui/gui.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index d2de151..65abb69 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -102,7 +102,7 @@ class MainWindow(QtGui.QMainWindow): for room in rooms: roomitem = QtGui.QStandardItem(room) font = QtGui.QFont() - font.setItalic(True) + font.setUnderline(True) if room == currentUser.room: font.setWeight(QtGui.QFont.Bold) roomitem.setFont(font) @@ -146,12 +146,14 @@ class MainWindow(QtGui.QMainWindow): filesizeitem = QtGui.QStandardItem("") if room == currentUser.room: filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR))) + font = QtGui.QFont() if currentUser.username == user.username: - font = QtGui.QFont() font.setWeight(QtGui.QFont.Bold) - useritem.setFont(font) - elif isControlledRoom and not isController: - useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) + if isControlledRoom and not isController: + font.setItalic(True) + if currentUser.username == user.username: + useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) + useritem.setFont(font) useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable) filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable) From 2a62f987709d5b37a14c7dad2734ea6e89c36dab Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 00:22:33 +0100 Subject: [PATCH 072/142] Fix installer path for lock_open --- buildPy2exe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index f94fb2f..2bb857a 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -584,7 +584,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png', 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/bullet_black.png', 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', - 'resources/tick.png', 'lock_open.png' + 'resources/tick.png', 'resources/lock_open.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) From 9c6f4e75a29f8abd4c8480ed3cef6bc1f13a0331 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 09:21:16 +0100 Subject: [PATCH 073/142] Hacky fix to give usernames enough room --- syncplay/ui/gui.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 65abb69..1902e90 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -171,9 +171,14 @@ class MainWindow(QtGui.QMainWindow): roomtocheck += 1 self.listTreeView.header().setStretchLastSection(False) self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) + namecolumnwidth = self.listTreeView.header().sectionSize(0) + 40 + if self.listTreeView.header().sectionSize(0) == self.listTreeView.header().sectionSizeHint(0): + namecolumnwidth = namecolumnwidth - 20 self.listTreeView.header().setResizeMode(1, QtGui.QHeaderView.ResizeToContents) self.listTreeView.header().setResizeMode(2, QtGui.QHeaderView.ResizeToContents) self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.ResizeToContents) + self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.Fixed) + self.listTreeView.header().resizeSection(0,namecolumnwidth) NarrowTabsWidth = self.listTreeView.header().sectionSize(0)+self.listTreeView.header().sectionSize(1)+self.listTreeView.header().sectionSize(2) if self.listTreeView.header().width() < (NarrowTabsWidth+self.listTreeView.header().sectionSize(3)): self.listTreeView.header().resizeSection(3,self.listTreeView.header().width()-NarrowTabsWidth) From 110935e5e4bf29d622360d1dbf1aa01687380a82 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 09:45:27 +0100 Subject: [PATCH 074/142] Less hacky fix to give usernames enough room --- syncplay/constants.py | 2 ++ syncplay/ui/gui.py | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index c300163..501a2ba 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -108,6 +108,8 @@ STYLE_DIFFERENTITEM_COLOR = 'red' STYLE_NOFILEITEM_COLOR = 'blue' STYLE_NOTCONTROLLER_COLOR = 'grey' +USERLIST_GUI_USERNAME_OFFSET = 21 # Pixels + MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4'] # --quiet works with both mpv 0.2 and 0.3 MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open'] diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 1902e90..53f1d0d 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -12,6 +12,12 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): def __init__(self): QtGui.QStyledItemDelegate.__init__(self) + def sizeHint(self, option, index): + size = QtGui.QStyledItemDelegate.sizeHint(self, option, index) + if (index.column() == 0): + size.setWidth(size.width() + constants.USERLIST_GUI_USERNAME_OFFSET) + return size + def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): column = indexQModelIndex.column() if column == 0: @@ -46,7 +52,7 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): crossIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) isUserRow = indexQModelIndex.parent() != indexQModelIndex.parent().parent() if isUserRow: - optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+21) + optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+constants.USERLIST_GUI_USERNAME_OFFSET) QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) class MainWindow(QtGui.QMainWindow): @@ -171,14 +177,9 @@ class MainWindow(QtGui.QMainWindow): roomtocheck += 1 self.listTreeView.header().setStretchLastSection(False) self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) - namecolumnwidth = self.listTreeView.header().sectionSize(0) + 40 - if self.listTreeView.header().sectionSize(0) == self.listTreeView.header().sectionSizeHint(0): - namecolumnwidth = namecolumnwidth - 20 self.listTreeView.header().setResizeMode(1, QtGui.QHeaderView.ResizeToContents) self.listTreeView.header().setResizeMode(2, QtGui.QHeaderView.ResizeToContents) self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.ResizeToContents) - self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.Fixed) - self.listTreeView.header().resizeSection(0,namecolumnwidth) NarrowTabsWidth = self.listTreeView.header().sectionSize(0)+self.listTreeView.header().sectionSize(1)+self.listTreeView.header().sectionSize(2) if self.listTreeView.header().width() < (NarrowTabsWidth+self.listTreeView.header().sectionSize(3)): self.listTreeView.header().resizeSection(3,self.listTreeView.header().width()-NarrowTabsWidth) From 5519fd481d8d88e4df95038a4d39abef5630f7e9 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 10:05:51 +0100 Subject: [PATCH 075/142] Change bullet icons to a right double chevron --- buildPy2exe.py | 2 +- resources/bullet_black.png | Bin 211 -> 0 bytes resources/chevrons_right.png | Bin 0 -> 403 bytes syncplay/ui/GuiConfiguration.py | 12 ++++++------ syncplay/ui/gui.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 resources/bullet_black.png create mode 100644 resources/chevrons_right.png diff --git a/buildPy2exe.py b/buildPy2exe.py index 2bb857a..9c8ca4a 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -582,7 +582,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/timeline_marker.png','resources/control_play_blue.png', 'resources/mpc-hc.png','resources/mpc-hc64.png','resources/mplayer.png', 'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png', - 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/bullet_black.png', + 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png', 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', 'resources/tick.png', 'resources/lock_open.png' ] diff --git a/resources/bullet_black.png b/resources/bullet_black.png deleted file mode 100644 index 57619706d10d9736b1849a83f2c5694fbe09c53b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-$h^>lFz(Kw&{<9vg>5sw~gS5O!4 zr|{HuUFIBKiQyL}eBJ-L{`UVT|6_O~L{G%N{Wbre{kQtZ_0LvEh!lvI6;>1s;*b z3=DDyK$uZbZ`*dDAbW|YuPgg?ZZ;l0nRXACzd#|W64!{5;QX|b^2DN4hJeJ(yb?V> z*ARs=V?9$nLj!{^MGJwd{&>1LhFJK&ooL(D;vnGGs}_6XO7fdj9~MCqBLPPiS3yB$ zM;;MZmZS4sTm<F};u@GSgK2AS_hOA(74y;; zWZeJWd3sY$GtcJZ@{3o!62J4~?)SfMC#+WB5cizaF?$*3^wdgI-_M(l_uts&`@FKR zE%CsG4JS99akyA0BYfTDEZfY_oU2wjeYQ!?h$x9wfA2eqrT4Li%$vvGWlLhWA7bf# uz3Y4L;h^g#zWnbpA85zt|9c&9h5z`a*3`(SlKQ}KVDNPHb6Mw<&;$UCMx5XP literal 0 HcmV?d00001 diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index a10f506..c69173a 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -427,7 +427,7 @@ class ConfigDialog(QtGui.QDialog): self.desyncFrame.setMidLineWidth(0) self.slowdownThresholdLabel = QLabel(getMessage("slowdown-threshold-label"), self) - self.slowdownThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "bullet_black.png")) + self.slowdownThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "chevrons_right.png")) self.slowdownThresholdSpinbox = QDoubleSpinBox() try: @@ -444,7 +444,7 @@ class ConfigDialog(QtGui.QDialog): self.slowdownThresholdSpinbox.adjustSize() self.rewindThresholdLabel = QLabel(getMessage("rewind-threshold-label"), self) - self.rewindThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "bullet_black.png")) + self.rewindThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "chevrons_right.png")) self.rewindThresholdSpinbox = QDoubleSpinBox() try: rewindThreshold = float(config['rewindThreshold']) @@ -516,22 +516,22 @@ class ConfigDialog(QtGui.QDialog): self.showSameRoomOSDCheckbox = QCheckBox(getMessage("showsameroomosd-label")) self.showSameRoomOSDCheckbox.setObjectName("showSameRoomOSD") - self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "bullet_black.png")) + self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) self.osdSettingsLayout.addWidget(self.showSameRoomOSDCheckbox) self.showDifferentRoomOSDCheckbox = QCheckBox(getMessage("showdifferentroomosd-label")) self.showDifferentRoomOSDCheckbox.setObjectName("showDifferentRoomOSD") - self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "bullet_black.png")) + self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) self.osdSettingsLayout.addWidget(self.showDifferentRoomOSDCheckbox) self.slowdownOSDCheckbox = QCheckBox(getMessage("showslowdownosd-label")) self.slowdownOSDCheckbox.setObjectName("showSlowdownOSD") - self.slowdownOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "bullet_black.png")) + self.slowdownOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) self.osdSettingsLayout.addWidget(self.slowdownOSDCheckbox) self.showOSDWarningsCheckbox = QCheckBox(getMessage("showosdwarnings-label")) self.showOSDWarningsCheckbox.setObjectName("showOSDWarnings") - self.showOSDWarningsCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "bullet_black.png")) + self.showOSDWarningsCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) self.osdSettingsLayout.addWidget(self.showOSDWarningsCheckbox) self.subitems['showOSD'] = ["showSameRoomOSD", "showDifferentRoomOSD", "showSlowdownOSD", "showOSDWarnings"] diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 53f1d0d..6885f0e 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -122,7 +122,7 @@ class MainWindow(QtGui.QMainWindow): else: roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'lock.png')) else: - roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'bullet_black.png')) + roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'chevrons_right.png')) for user in rooms[room]: useritem = QtGui.QStandardItem(user.username) From 468c7f665d83c0d4c0b2d846f4b43df3e97be678 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 10:23:31 +0100 Subject: [PATCH 076/142] Make room icons closer to room name --- resources/chevrons_right.png | Bin 403 -> 418 bytes resources/lock.png | Bin 749 -> 884 bytes resources/lock_open.png | Bin 727 -> 824 bytes syncplay/constants.py | 4 ++-- 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/chevrons_right.png b/resources/chevrons_right.png index 8d6adcf3f2c62f0798efe3674a21c2331605a26a..356a05a736b4525b0b13af2a55a7c2f74c3554bf 100644 GIT binary patch delta 298 zcmV+_0oDGK1EK?vfqwxDNklApAJh(5AG->j|G>4hv+>s%KVEsD(0=giMFUeh!q?@8B$d+XxbzR?W+rCi+uIsX8S?<#` z5g0gCRpp@AG)<9pU3Wsy^9`D&+1GU?FtC}XiSNy_Y@g>D8QkOh8w|s6zy_P|`(GD_ wqaJylKQIhU@FOhnD<6li{6s*_9}5`f1B%Jz1tGRajQ{`u07*qoM6N<$g7f2w4gdfE delta 283 zcmV+$0p$Lo1Cs-gfqww}Nkl}5;@5i~LoK?Fq+5d%RC z6h#EZpF~6v58*NT3{GUQL2peX1H!m4!vq`bgGwG>byrtY|Gb$n3|GT27M5i{c13xf zw`!VpaHj-8fa+M<2a}&3gSdjB<>VNQD~R~wSR4kqbS;Vo@bzF-}i~j zvTTD4v&SE!s_GZq_}%yY?U1V=2p36`h?}Mfmt}c1P4lwHkJz?N=Xs8XVZhL^$+FBq z(QzCT*EG!rnez#%szUQTV`x}(T_^8Nk_3(8NF40r_Z@Uyx55OA>$>-i!&28YO%LvA h;45Em{_9ZG4;07Jg=Jag87}|;002ovPDHLkV1lCbflL4Z diff --git a/resources/lock.png b/resources/lock.png index 2ebc4f6f9663e32cad77d67ef93ab8843dfea3c0..2d06336d35df082f8c3d48b3fa1c0c1b528671ba 100644 GIT binary patch delta 845 zcmV-T1G4<>1@s1xG=B+jNLh0L01m?d01m?e$8V@)0000QbVXQnQ*UN;cVTj608n9R zZgehAMN}YmGcGkQF)%Rna-jeK0`N&hK~y+TWs+M+lVKRg3z7~<5D`Qdbx{{tU0BfJ zqAn^BqOzg`WkGe(O+*JuQP9(16S33Pro!PGw~!>FMcRsi~=vf9n5l4lgb)Zg)5w z)Ya7?zRAnWLw~2!Ask?_SVGchH0^4&dhRrUXo$61t=Mcf^7HdMh2>@0+1X*4nVDf} zX=%$+Qc`-&W;2C_g(M{<#ZCi=eJ%2Id9s)qF*PSEIXO8(*r&X_oP>k~i+uJ^fx%#? zcRHPj$z)|^&6%2$rBbQph{;q`R1g;zS1+IaO?v82Vt+2WhOmsVOvjTL+*JqZdbp3y zySwSA+(DZ;ium|=xtR>rDLxq1Ds+$i1i|5#Obowba`Zinw!zO25cn+QhLKMMhh8%_ zU}x-mZAh)U)e7LV?U(?(Kyt@2@ii6ta-r7Jw^Yc zUBW>Oe9xs*A4x^>Zt!UEmT$!)fd0B|NWBkur+-?g0OVU9_dVrT^KpXhXT<)O#qL?~ zLocEZ+=&ESM@4fKKI>+rp8LE_n6CiZ&D$C8w=vRqg#J5Q@mr(u+hg!MPvCbP!!KNY zE!&94xC&p@CIUToX^Nez06vv&5l>S?P-xO;6Hn+A&-Vt#nni39PM#C=T9LloC(wN- zq(;UIVG7`5!G_R}(U!A@R_#h!(wB#BxmmM>cUKniHgQ47a#P$q8ZXYK@%${_oSn&U XaIf_6LXssanuR6PuPaD3lbFCZe|tv9>?g+k#9o0pTxZ{ zR;$HSD&^Gv4`57d=52btg(E}fG&@~Gwqt*Ju>Izdy;7OR&R*EjeD~5S{I-BCE!dVD zLP)$=yXu&l7;fP9nN>_b`CzyV2=d_m4H&e<@P|3y0)Lc2e#82Z1_?;3jpwr+=7rO; z@$&7V1bve(w02)15E_MHjIeMB2*;UL!2TX&>l@7OcPMKg;mIs)v+n^QMJRofiRSuy z>O6+{#cQa&US$7iOkAHu^XobR8i=M&kg$X;OfP_NWZ0w{pS}QJaFp&S$iBp<-7qF# zBA%W^uYVI_?Y_fm0O0%so9=28soruwH<;wN~XbGXSj}boShY<|9Pw3-IC_YLvyNGt%RM;m`>H4LOKLFs9Y%b5a rUN|2|{0Zw|<_~i}mXz*V19AKYaq!0;vb79xBj$ zs0R%MDXp*~HL@Oph-je<5~(QBR<}vTkq~VPGc_$U_dRDaaetq2cO1=SQfFM!=IS`V zz8T0nX7upqeD~b*|J`%Gb5{W?q*AH8Qd3iNl}hCgq1l9lgxt8eIIn-zu1E;urR?l% zn?|FduCC6Zva&Lglapb%&u8kS<`B@nv9GL#8(|kNl76uFVB9Sc5!j> zclIm;C=`nEihqg! z!t9Ny=pfSIN1E?!lmXr8hgldeXSOkb@f=UgMSp&ns{%3Cp2u8s7PIJT`1Jt{%3YWW zJg^Mr(iE~@26VkTBvw;`Rmf&47b~n4>uZ4dRuP*-Cu*$5BBbvhu?*xm)Ocg94Cs96 z?)WjEmQZ?2Gx^S&R7lI2nqAMv1ih>nkv>T!Q52UW# zi0eQ=1jU657vjp5s1!85YO{#6jTmT>KBhDGaa@d#A=m?ldw)3G`|YDHIC%v9Yl`y$ZZkoioO;u&}Tc1VQyrb82d;0<=1vjv5>s z%x`D`$XII;5!-udVq#*D4MK?sjaOIX+00RlQ&?*`bL0&f(TFvONm6V3)OFiWf%6JE z?TO*wwRb8}Aj0_NV<c10RmR@v3l_=aDNtH;9xruB%v z+zyn=gX@QZ0*HgP4kIT2k^2xKsjt=dEXjfn3%%8y{xb15VHp2{sKGz(Hap8 zR^(ogj2GXC&Z1ee%O14JyjT~`^{ zok3-D$rpP4;4H);ez=U)5#nqzFJn@GIHLaKb|^9nV-TZ%r}=wgZL(O_Cbdfz_7}jm VaaS&KPgno|002ovPDHLkV1kq3LdgIC diff --git a/syncplay/constants.py b/syncplay/constants.py index 501a2ba..3ad462b 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -97,8 +97,8 @@ VLC_MAX_PORT = 55000 #These are not changes you're looking for STYLE_TABLIST = "QListWidget::item { color: black; border-style: solid; border-width: 1px; border-radius: 2px; } QListWidget::item:selected { background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(242, 248, 255, 255), stop:1 rgba(208, 229, 255, 255)); border-color: #84ACDD; } QListWidget::item:!selected { border-color: transparent; } QListWidget::item:!selected:hover { background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(248, 248, 248, 255), stop:1 rgba(229, 229, 229, 255)); border-color: silver; }" -STYLE_SUBCHECKBOX = "QCheckBox, QLabel {{ margin-left: 8px; padding-left: 18px; background:url('{}') left no-repeat }}" #Graphic path -STYLE_SUBLABEL = "QCheckBox, QLabel {{ margin-left: 8px; padding-left: 14px; background:url('{}') left no-repeat }}" #Graphic path +STYLE_SUBCHECKBOX = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 21px; background:url('{}') left no-repeat }}" #Graphic path +STYLE_SUBLABEL = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 16px; background:url('{}') left no-repeat }}" #Graphic path STYLE_ERRORLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: red; padding: 2px; background: #FFAAAA; }" STYLE_SUCCESSLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: green; padding: 2px; background: #AAFFAA; }" STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }" From b672c21bfb2063cce28329f9297abbcb36279779 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 13:16:21 +0100 Subject: [PATCH 077/142] Use SI megabyte (MB) rather than Mebibyte (MiB) --- syncplay/messages.py | 2 +- syncplay/utils.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 965cf6d..26992d7 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -194,7 +194,7 @@ en = { "setoffset-msgbox-label" : "Set offset", "offsetinfo-msgbox-label" : "Offset (see http://syncplay.pl/guide/ for usage instructions):", - "mebibyte-suffix" : " MiB", + "megabyte-suffix" : " MB", # Tooltips diff --git a/syncplay/utils.py b/syncplay/utils.py index cf04c2d..b1c52e7 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -97,12 +97,12 @@ def formatSize (bytes, precise=False): if bytes == 0: # E.g. when file size privacy is enabled return "???" try: - mebibytes = int(bytes) / 1048576.0 + megabytes = int(bytes) / 1000000.0 if precise: - mebibytes = round(mebibytes, 1) + megabytes = round(megabytes, 1) else: - mebibytes = int(mebibytes) - return str(mebibytes) + getMessage("mebibyte-suffix") + megabytes = int(megabytes) + return str(megabytes) + getMessage("megabyte-suffix") except: # E.g. when filesize is hashed return "???" From cea3be53b38138b1e6869ee28b682472ab6434da Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 23:44:31 +0100 Subject: [PATCH 078/142] Re-format: Underline diff items rather than room; Leave italics for unready users --- syncplay/ui/gui.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 6885f0e..d916868 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -108,7 +108,6 @@ class MainWindow(QtGui.QMainWindow): for room in rooms: roomitem = QtGui.QStandardItem(room) font = QtGui.QFont() - font.setUnderline(True) if room == currentUser.room: font.setWeight(QtGui.QFont.Bold) roomitem.setFont(font) @@ -137,15 +136,20 @@ class MainWindow(QtGui.QMainWindow): sameSize = sameFilesize(user.file['size'], currentUser.file['size']) sameDuration = sameFileduration(user.file['duration'], currentUser.file['duration']) sameRoom = room == currentUser.room + underlinefont = QtGui.QFont() + underlinefont.setUnderline(True) if sameRoom: if not sameName: filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) + filenameitem.setFont(underlinefont) if not sameSize: if currentUser.file is not None and formatSize(user.file['size']) == formatSize(currentUser.file['size']): filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'],precise=True)) + filesizeitem.setFont(underlinefont) filesizeitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) if not sameDuration: filedurationitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) + filedurationitem.setFont(underlinefont) else: filenameitem = QtGui.QStandardItem(getMessage("nofile-note")) filedurationitem = QtGui.QStandardItem("") @@ -156,7 +160,6 @@ class MainWindow(QtGui.QMainWindow): if currentUser.username == user.username: font.setWeight(QtGui.QFont.Bold) if isControlledRoom and not isController: - font.setItalic(True) if currentUser.username == user.username: useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) useritem.setFont(font) From 27af49927ba77d04dce341b35dcd484bc3fccb14 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 25 Oct 2014 23:54:27 +0100 Subject: [PATCH 079/142] Make room item italicised --- syncplay/ui/gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index d916868..204703f 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -108,6 +108,7 @@ class MainWindow(QtGui.QMainWindow): for room in rooms: roomitem = QtGui.QStandardItem(room) font = QtGui.QFont() + font.setItalic(True) if room == currentUser.room: font.setWeight(QtGui.QFont.Bold) roomitem.setFont(font) From 751a7015860ca1d43909fb6a76fca187fb8257ba Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 26 Oct 2014 14:11:32 +0000 Subject: [PATCH 080/142] Add @requireMinServerVersion and use it for controlled rooms --- syncplay/client.py | 18 ++++++++++++++++++ syncplay/protocols.py | 1 + 2 files changed, 19 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index e31c44e..ff86c61 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -6,6 +6,7 @@ import time import re from twisted.internet.protocol import ClientFactory from twisted.internet import reactor, task +from functools import wraps from syncplay.protocols import SyncClientProtocol from syncplay import utils, constants from syncplay.messages import getMessage @@ -62,6 +63,7 @@ class SyncplayClient(object): constants.SHOW_DIFFERENT_ROOM_OSD = config['showDifferentRoomOSD'] constants.SHOW_SAME_ROOM_OSD = config['showSameRoomOSD'] constants.SHOW_DURATION_NOTIFICATION = config['showDurationNotification'] + self.serverVersion = "0.0.0" self.lastLeftTime = 0 self.lastLeftUser = u"" self.protocolFactory = SyncClientFactory(self) @@ -335,6 +337,9 @@ class SyncplayClient(object): size = 0 return filename, size + def setServerVersion(self, version): + self.serverVersion = version + def sendFile(self): file_ = self.userlist.currentUser.file if self._protocol and self._protocol.logged and file_: @@ -416,6 +421,18 @@ class SyncplayClient(object): if promptForAction: self.ui.promptFor(getMessage("enter-to-exit-prompt")) + def requireMinServerVersion(minVersion): + def requireMinVersionDecorator(f): + @wraps(f) + def wrapper(self, *args, **kwds): + if int(self.serverVersion.replace(".", "")) < int(minVersion.replace(".", "")): + self.ui.showErrorMessage(u"This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.".format(minVersion, self.serverVersion)) + return + return f(self, *args, **kwds) + return wrapper + return requireMinVersionDecorator + + @requireMinServerVersion("1.3.0") def createControlledRoom(self, roomName): controlPassword = RoomPasswordGenerator.generate_password() self.ui.showMessage(u"Attempting to create controlled room '{}' with password '{}'...".format(roomName, controlPassword)) @@ -434,6 +451,7 @@ class SyncplayClient(object): else: return "" + @requireMinServerVersion("1.3.0") def identifyAsController(self, controlPassword): controlPassword = self.stripControlPassword(controlPassword) self.ui.showMessage(u"Identifying as room controller with password '{}'...".format(controlPassword)) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 18da515..8603ea1 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -87,6 +87,7 @@ class SyncClientProtocol(JSONCommandProtocol): self._client.ui.showMessage(motd, True, True) self._client.ui.showMessage(getMessage("connected-successful-notification")) self._client.sendFile() + self._client.setServerVersion(version) def sendHello(self): hello = {} From 8cc3eddaf2cbf92da9f70455ce38ebb6c6ad34ac Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 27 Oct 2014 15:07:07 +0000 Subject: [PATCH 081/142] Auto-re-identify as controller on reconnect / changing rooms (not saved on exit) --- syncplay/client.py | 27 ++++++++++++++++++++++++--- syncplay/protocols.py | 1 + 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index ff86c61..4242486 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -63,6 +63,8 @@ class SyncplayClient(object): constants.SHOW_DIFFERENT_ROOM_OSD = config['showDifferentRoomOSD'] constants.SHOW_SAME_ROOM_OSD = config['showSameRoomOSD'] constants.SHOW_DURATION_NOTIFICATION = config['showDurationNotification'] + self.controlpasswords = {} + self.lastControlPasswordAttempt = None self.serverVersion = "0.0.0" self.lastLeftTime = 0 self.lastLeftUser = u"" @@ -365,6 +367,17 @@ class SyncplayClient(object): if self._protocol and self._protocol.logged and room: self._protocol.sendRoomSetting(room) self.getUserList() + self.reIdentifyAsController() + + def reIdentifyAsController(self): + room = self.userlist.currentUser.room + if utils.RoomPasswordProvider.isControlledRoom(room): + storedRoomPassword = self.getControlledRoomPassword(room) + if storedRoomPassword: + self.identifyAsController(storedRoomPassword) + + def connected(self): + self.reIdentifyAsController() def getRoom(self): return self.userlist.currentUser.room @@ -436,6 +449,7 @@ class SyncplayClient(object): def createControlledRoom(self, roomName): controlPassword = RoomPasswordGenerator.generate_password() self.ui.showMessage(u"Attempting to create controlled room '{}' with password '{}'...".format(roomName, controlPassword)) + self.lastControlPasswordAttempt = controlPassword self._protocol.requestControlledRoom(roomName, controlPassword) def controlledRoomCreated(self, roomName, controlPassword): @@ -455,6 +469,7 @@ class SyncplayClient(object): def identifyAsController(self, controlPassword): controlPassword = self.stripControlPassword(controlPassword) self.ui.showMessage(u"Identifying as room controller with password '{}'...".format(controlPassword)) + self.lastControlPasswordAttempt = controlPassword self._protocol.requestControlledRoom(self.getRoom(), controlPassword) def controllerIdentificationError(self, username, room): @@ -465,11 +480,17 @@ class SyncplayClient(object): if self.userlist.isRoomSame(roomname): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD self.ui.showMessage(u"<{}> authenticated as a room controller".format(username), hideFromOSD) + if username == self.userlist.currentUser.username: + self.storeControlPassword(roomname, self.lastControlPasswordAttempt) self.ui.userListChange() - # TODO: Mark person as a room controller - # TODO: Disable UI's "Create new Controlled Room when in Controlled Room" - # TODO: Disable authenticate when authenticated + def storeControlPassword(self, room, password): + if password: + self.controlpasswords[room] = password + + def getControlledRoomPassword(self, room): + if self.controlpasswords.has_key(room): + return self.controlpasswords[room] class _WarningManager(object): def __init__(self, player, userlist, ui): diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 8603ea1..43f4395 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -86,6 +86,7 @@ class SyncClientProtocol(JSONCommandProtocol): if motd: self._client.ui.showMessage(motd, True, True) self._client.ui.showMessage(getMessage("connected-successful-notification")) + self._client.connected() self._client.sendFile() self._client.setServerVersion(version) From 960cef15c9b37dab25f2e34bdb2b25adc7b8ca8c Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 1 Nov 2014 11:56:47 +0000 Subject: [PATCH 082/142] Hide label text if window is too narrow --- syncplay/ui/gui.py | 104 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 204703f..5164751 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1,5 +1,5 @@ from PySide import QtGui # @UnresolvedImport -from PySide.QtCore import Qt, QSettings, QSize, QPoint # @UnresolvedImport +from PySide.QtCore import Qt, QSettings, QSize, QPoint from syncplay import utils, constants, version from syncplay.messages import getMessage import sys @@ -60,26 +60,13 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient = client self.roomInput.setText(self._syncplayClient.getRoom()) self.config = self._syncplayClient.getConfig() + constants.SHOW_BUTTON_LABELS = self.config['showButtonLabels'] try: if self.contactLabel and not self.config['showContactInfo']: self.contactLabel.hide() - if not self.config['showButtonLabels']: - if constants.MERGE_PLAYPAUSE_BUTTONS: - self.playpauseButton.setText("") - else: - self.playButton.setText("") - self.playButton.setFixedWidth(self.playButton.minimumSizeHint().width()) - self.pauseButton.setText("") - self.pauseButton.setFixedWidth(self.pauseButton.minimumSizeHint().width()) - self.roomButton.setText("") - self.roomButton.setFixedWidth(self.roomButton.minimumSizeHint().width()) - self.seekButton.setText("") - self.seekButton.setFixedWidth(self.seekButton.minimumSizeHint().width()) - self.unseekButton.setText("") - self.unseekButton.setFixedWidth(self.unseekButton.minimumSizeHint().width()) - self.roomGroup.setFixedWidth(self.roomGroup.sizeHint().width()) - self.seekGroup.setFixedWidth(self.seekGroup.minimumSizeHint().width()) - self.miscGroup.setFixedWidth(self.miscGroup.minimumSizeHint().width()) + if not constants.SHOW_BUTTON_LABELS: + self.hideRoomSeekLabels() + self.hideMiscLabels() except (): pass @@ -175,6 +162,9 @@ class MainWindow(QtGui.QMainWindow): self.listTreeView.setItemsExpandable(False) self.listTreeView.setRootIsDecorated(False) self.listTreeView.expandAll() + self.updateListGeometry() + + def updateListGeometry(self): roomtocheck = 0 while self.listTreeModel.item(roomtocheck): self.listTreeView.setFirstColumnSpanned(roomtocheck, self.listTreeView.rootIndex(), True) @@ -262,6 +252,69 @@ class MainWindow(QtGui.QMainWindow): self.exitSyncplay() self.saveSettings() + def setupSizes(self): + self.hideRoomSeekLabels() + self.miscThreshold = self.seekGroup.sizeHint().width()+self.roomGroup.sizeHint().width()+self.miscGroup.sizeHint().width()+30 + self.hideMiscLabels() + self.setMinimumWidth(self.seekGroup.sizeHint().width()+self.roomGroup.sizeHint().width()+self.miscGroup.sizeHint().width()+30) + self.seekGroup.setMinimumWidth(self.seekGroup.sizeHint().width()) + self.roomGroup.setMinimumWidth(self.roomGroup.sizeHint().width()) + self.miscGroup.setMinimumWidth(self.miscGroup.sizeHint().width()) + self.showRoomSeekLabels() + self.showMiscLabels() + windowMaximumWidth = self.maximumWidth() + self.seekGroup.setMaximumWidth(self.seekGroup.sizeHint().width()) + self.roomGroup.setMaximumWidth(self.roomGroup.sizeHint().width()) + self.miscGroup.setMaximumWidth(self.miscGroup.sizeHint().width()) + self.setMaximumWidth(windowMaximumWidth) + self.roomSeekThreshold = self.mainLayout.sizeHint().width() + + def hideRoomSeekLabels(self): + self.roomButton.setText("") + self.seekButton.setText("") + + def hideMiscLabels(self): + self.unseekButton.setText("") + if constants.MERGE_PLAYPAUSE_BUTTONS: + self.playpauseButton.setText("") + else: + self.playButton.setText("") + self.pauseButton.setText("") + + def showRoomSeekLabels(self): + if not constants.SHOW_BUTTON_LABELS: + return + self.roomButton.setText(getMessage("joinroom-guibuttonlabel")) + self.seekButton.setText(getMessage("seektime-guibuttonlabel")) + + def showMiscLabels(self): + self.unseekButton.setText(getMessage("undoseek-guibuttonlabel")) + if not constants.SHOW_BUTTON_LABELS: + return + if constants.MERGE_PLAYPAUSE_BUTTONS: + self.playpauseButton.setText(getMessage("togglepause-guibuttonlabel")) + else: + self.playButton.setText(getMessage("play-guibuttonlabel")) + self.pauseButton.setText(getMessage("pause-guibuttonlabel")) + + def resizeEvent(self,resizeEvent): + self.updateListGeometry() + if self.roomGroup and self.miscThreshold: + currentWidth = self.mainFrame.size().width() + if currentWidth < self.miscThreshold: + if self.unseekButton.text() != "": + self.hideMiscLabels() + else: + if self.unseekButton.text() == "": + self.showMiscLabels() + + if currentWidth < self.roomSeekThreshold: + if self.roomButton.text() != "": + self.hideRoomSeekLabels() + else: + if self.roomButton.text() == "": + self.showRoomSeekLabels() + def loadMediaBrowseSettings(self): settings = QSettings("Syncplay", "MediaBrowseDialog") settings.beginGroup("MediaBrowseDialog") @@ -402,9 +455,9 @@ class MainWindow(QtGui.QMainWindow): window.addSeekBox(MainWindow) window.addMiscBox(MainWindow) - window.bottomLayout.addWidget(window.roomGroup, Qt.AlignLeft) - window.bottomLayout.addWidget(window.seekGroup, Qt.AlignLeft) - window.bottomLayout.addWidget(window.miscGroup, Qt.AlignLeft) + window.bottomLayout.addWidget(window.roomGroup) + window.bottomLayout.addWidget(window.seekGroup) + window.bottomLayout.addWidget(window.miscGroup) window.mainLayout.addLayout(window.bottomLayout, Qt.AlignLeft) @@ -425,7 +478,7 @@ class MainWindow(QtGui.QMainWindow): window.roomLayout.addWidget(window.roomButton) window.roomGroup.setLayout(window.roomLayout) - window.roomGroup.setFixedSize(window.roomGroup.sizeHint()) + window.roomGroup.setFixedHeight(window.roomGroup.sizeHint().height()) def addSeekBox(self, window): window.seekGroup = QtGui.QGroupBox(getMessage("seek-heading-label")) @@ -446,7 +499,7 @@ class MainWindow(QtGui.QMainWindow): window.seekLayout.addWidget(window.seekButton) window.seekGroup.setLayout(window.seekLayout) - window.seekGroup.setFixedSize(window.seekGroup.sizeHint()) + window.seekGroup.setFixedHeight(window.seekGroup.sizeHint().height()) def addMiscBox(self, window): window.miscGroup = QtGui.QGroupBox(getMessage("othercommands-heading-label")) @@ -479,7 +532,7 @@ class MainWindow(QtGui.QMainWindow): self.pauseButton.setToolTip(getMessage("pause-tooltip")) window.miscGroup.setLayout(window.miscLayout) - window.miscGroup.setFixedSize(window.miscGroup.sizeHint()) + window.miscGroup.setFixedHeight(window.miscGroup.sizeHint().height()) def addMenubar(self, window): @@ -589,9 +642,10 @@ class MainWindow(QtGui.QMainWindow): self.addBottomLayout(self) self.addMenubar(self) self.addMainFrame(self) + self.setupSizes() self.loadSettings() self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png")) self.setWindowFlags( self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.show() - self.setAcceptDrops(True) \ No newline at end of file + self.setAcceptDrops(True) From e3d204aeb385ab4547ab2d6f0c467bbd90398ec2 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 1 Nov 2014 15:24:13 +0000 Subject: [PATCH 083/142] Update list display on splitter move --- syncplay/ui/gui.py | 51 ++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 5164751..2a9e39e 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1,4 +1,4 @@ -from PySide import QtGui # @UnresolvedImport +from PySide import QtGui from PySide.QtCore import Qt, QSettings, QSize, QPoint from syncplay import utils, constants, version from syncplay.messages import getMessage @@ -56,6 +56,19 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) class MainWindow(QtGui.QMainWindow): + class topSplitter(QtGui.QSplitter): + def createHandle(self): + return self.topSplitterHandle(self.orientation(), self) + + class topSplitterHandle(QtGui.QSplitterHandle): + def mouseReleaseEvent(self, event): + QtGui.QSplitterHandle.mouseReleaseEvent(self, event) + self.parent().parent().parent().updateListGeometry() + + def mouseMoveEvent(self, event): + QtGui.QSplitterHandle.mouseMoveEvent(self, event) + self.parent().parent().parent().updateListGeometry() + def addClient(self, client): self._syncplayClient = client self.roomInput.setText(self._syncplayClient.getRoom()) @@ -70,7 +83,6 @@ class MainWindow(QtGui.QMainWindow): except (): pass - def promptFor(self, prompt=">", message=""): # TODO: Prompt user return None @@ -165,21 +177,24 @@ class MainWindow(QtGui.QMainWindow): self.updateListGeometry() def updateListGeometry(self): - roomtocheck = 0 - while self.listTreeModel.item(roomtocheck): - self.listTreeView.setFirstColumnSpanned(roomtocheck, self.listTreeView.rootIndex(), True) - roomtocheck += 1 - self.listTreeView.header().setStretchLastSection(False) - self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) - self.listTreeView.header().setResizeMode(1, QtGui.QHeaderView.ResizeToContents) - self.listTreeView.header().setResizeMode(2, QtGui.QHeaderView.ResizeToContents) - self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.ResizeToContents) - NarrowTabsWidth = self.listTreeView.header().sectionSize(0)+self.listTreeView.header().sectionSize(1)+self.listTreeView.header().sectionSize(2) - if self.listTreeView.header().width() < (NarrowTabsWidth+self.listTreeView.header().sectionSize(3)): - self.listTreeView.header().resizeSection(3,self.listTreeView.header().width()-NarrowTabsWidth) - else: - self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.Stretch) - self.listTreeView.expandAll() + try: + roomtocheck = 0 + while self.listTreeModel.item(roomtocheck): + self.listTreeView.setFirstColumnSpanned(roomtocheck, self.listTreeView.rootIndex(), True) + roomtocheck += 1 + self.listTreeView.header().setStretchLastSection(False) + self.listTreeView.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) + self.listTreeView.header().setResizeMode(1, QtGui.QHeaderView.ResizeToContents) + self.listTreeView.header().setResizeMode(2, QtGui.QHeaderView.ResizeToContents) + self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.ResizeToContents) + NarrowTabsWidth = self.listTreeView.header().sectionSize(0)+self.listTreeView.header().sectionSize(1)+self.listTreeView.header().sectionSize(2) + if self.listTreeView.header().width() < (NarrowTabsWidth+self.listTreeView.header().sectionSize(3)): + self.listTreeView.header().resizeSection(3,self.listTreeView.header().width()-NarrowTabsWidth) + else: + self.listTreeView.header().setResizeMode(3, QtGui.QHeaderView.Stretch) + self.listTreeView.expandAll() + except: + pass def roomClicked(self, item): while item.parent().row() != -1: @@ -402,7 +417,7 @@ class MainWindow(QtGui.QMainWindow): self.close() def addTopLayout(self, window): - window.topSplit = QtGui.QSplitter(Qt.Horizontal) + window.topSplit = self.topSplitter(Qt.Horizontal, self) window.outputLayout = QtGui.QVBoxLayout() window.outputbox = QtGui.QTextEdit() From 09df5072354af03afb7f0d8aa5530b847d208e6f Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 2 Nov 2014 11:00:44 +0000 Subject: [PATCH 084/142] Add --salt to server. If no room control salt specified, generate random 10-char string and let admin know --- syncplay/client.py | 21 ++------------------- syncplay/messages.py | 4 ++-- syncplay/server.py | 13 ++++++++----- syncplay/utils.py | 27 +++++++++++++++++++++++++++ syncplayServer.py | 2 +- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 4242486..2d42674 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1,7 +1,5 @@ import hashlib import os.path -import random -import string import time import re from twisted.internet.protocol import ClientFactory @@ -447,7 +445,8 @@ class SyncplayClient(object): @requireMinServerVersion("1.3.0") def createControlledRoom(self, roomName): - controlPassword = RoomPasswordGenerator.generate_password() + controlPassword = utils.RandomStringGenerator.generate_room_ + password() self.ui.showMessage(u"Attempting to create controlled room '{}' with password '{}'...".format(roomName, controlPassword)) self.lastControlPasswordAttempt = controlPassword self._protocol.requestControlledRoom(roomName, controlPassword) @@ -772,20 +771,4 @@ class UiManager(object): def drop(self): self.__ui.drop() -class RoomPasswordGenerator(object): - @staticmethod - def generate_password(): - parts = ( - RoomPasswordGenerator._get_random_letters(2), - RoomPasswordGenerator._get_random_numbers(3), - RoomPasswordGenerator._get_random_numbers(3) - ) - return "{}-{}-{}".format(*parts) - @staticmethod - def _get_random_letters(quantity): - return ''.join(random.choice(string.ascii_uppercase) for _ in xrange(quantity)) - - @staticmethod - def _get_random_numbers(quantity): - return ''.join(random.choice(string.digits) for _ in xrange(quantity)) diff --git a/syncplay/messages.py b/syncplay/messages.py index 26992d7..b5ad9a6 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -254,6 +254,7 @@ en = { "welcome-server-notification" : "Welcome to Syncplay server, ver. {0}", # version "client-connected-room-server-notification" : "{0}({2}) connected to room '{1}'", # username, host, room "client-left-server-notification" : "{0} left server", # name + "no-salt-notification" : "PLEASE NOTE: To allow room control passwords generated by this server instance to still work when the server is restarted, please add the following command line argument when running the Syncplay server in the future: --salt {}", #Salt # Server arguments @@ -262,11 +263,10 @@ en = { "server-port-argument" : 'server TCP port', "server-password-argument" : 'server password', "server-isolate-room-argument" : 'should rooms be isolated?', + "server-salt-argument" : "random string used to generate controlled room passwords", "server-motd-argument": "path to file from which motd will be fetched", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.", - "server-irc-verbose": "Should server actively report changes in rooms", - "server-irc-config": "Path to irc bot config files", # Server errors "unknown-command-server-error" : "Unknown command {}", # message diff --git a/syncplay/server.py b/syncplay/server.py index f586be3..f2cad52 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -11,16 +11,18 @@ import codecs import os from string import Template import argparse -from syncplay.utils import RoomPasswordProvider, NotControlledRoom +from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator class SyncFactory(Factory): - def __init__(self, password='', motdFilePath=None, isolateRooms=False): + def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None): print getMessage("welcome-server-notification").format(syncplay.version) if password: password = hashlib.md5(password).hexdigest() self.password = password - # TODO: Make salt come from more reasonable place - self._salt = str(random.random()) + if salt is None: + salt = RandomStringGenerator.generate_server_salt() + print getMessage("no-salt-notification").format(salt) + self._salt = salt self._motdFilePath = motdFilePath if not isolateRooms: self._roomManager = RoomManager() @@ -420,4 +422,5 @@ class ConfigurationGetter(object): self._argparser.add_argument('--port', metavar='port', type=str, nargs='?', help=getMessage("server-port-argument")) 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('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-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")) \ No newline at end of file diff --git a/syncplay/utils.py b/syncplay/utils.py index b1c52e7..9c2715d 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -7,6 +7,8 @@ import sys import os import itertools import hashlib +import random +import string def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None): """Retry calling the decorated function using an exponential backoff. @@ -241,5 +243,30 @@ class RoomPasswordProvider(object): provisionalHash = hashlib.sha256(roomName + salt).hexdigest() return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper() +class RandomStringGenerator(object): + @staticmethod + def generate_room_password(): + parts = ( + RandomStringGenerator._get_random_letters(2), + RandomStringGenerator._get_random_numbers(3), + RandomStringGenerator._get_random_numbers(3) + ) + return "{}-{}-{}".format(*parts) + + @staticmethod + def generate_server_salt(): + parts = ( + RandomStringGenerator._get_random_letters(10), + ) + return "{}".format(*parts) + + @staticmethod + def _get_random_letters(quantity): + return ''.join(random.choice(string.ascii_uppercase) for _ in xrange(quantity)) + + @staticmethod + def _get_random_numbers(quantity): + return ''.join(random.choice(string.digits) for _ in xrange(quantity)) + class NotControlledRoom(Exception): pass \ No newline at end of file diff --git a/syncplayServer.py b/syncplayServer.py index 7b1faa1..c127fbf 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -20,5 +20,5 @@ if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() - reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms)) + reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt)) reactor.run() From f1fbd180688568630237ba2fbeb0302980428aa0 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 2 Nov 2014 11:08:56 +0000 Subject: [PATCH 085/142] Server should ignore showDebugMessage --- syncplay/protocols.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 750d212..fc4db62 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -30,7 +30,7 @@ class JSONCommandProtocol(LineReceiver): if not line: return try: - self._client.ui.showDebugMessage("client/server << {}".format(line)) + self.showDebugMessage("client/server << {}".format(line)) messages = json.loads(line) except: self.dropWithError(getMessage("not-json-server-error").format(line)) @@ -40,7 +40,7 @@ class JSONCommandProtocol(LineReceiver): def sendMessage(self, dict_): line = json.dumps(dict_) self.sendLine(line) - self._client.ui.showDebugMessage("client/server >> {}".format(line)) + self.showDebugMessage("client/server >> {}".format(line)) def drop(self): self.transport.loseConnection() @@ -57,6 +57,9 @@ class SyncClientProtocol(JSONCommandProtocol): self.logged = False self._pingService = PingService() + def showDebugMessage(self, line): + self._client.ui.showDebugMessage(line) + def connectionMade(self): self._client.initProtocol(self) self.sendHello() @@ -243,6 +246,9 @@ class SyncServerProtocol(JSONCommandProtocol): return f(self, *args, **kwds) return wrapper + def showDebugMessage(self, line): + pass + def dropWithError(self, error): print getMessage("client-drop-server-error").format(self.transport.getPeer().host, error) self.sendError(error) From f90b549caaa83abee11af3554fbfc8263415a0fe Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 8 Nov 2014 14:55:39 +0000 Subject: [PATCH 086/142] Pull mpv unicode filename fix from controlledrooms-new --- syncplay/players/mplayer.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 8a89e9b..c7da527 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -68,15 +68,6 @@ class MplayerPlayer(BasePlayer): self.reactor.callLater(0, self._client.initPlayer, self) self._onFileUpdate() - def askForStatus(self): - self._positionAsk.clear() - self._pausedAsk.clear() - self._getPaused() - self._getPosition() - self._positionAsk.wait() - self._pausedAsk.wait() - self._client.updatePlayerStatus(self._paused, self._position) - def _setProperty(self, property_, value): self._listener.sendLine("set_property {} {}".format(property_, value)) @@ -291,7 +282,9 @@ class MplayerPlayer(BasePlayer): def sendLine(self, line): try: - line = (line.decode('utf8') + u"\n").encode('utf8') + if not isinstance(line, unicode): + line = line.decode('utf8') + line = (line + u"\n").encode('utf8') self.__playerController._client.ui.showDebugMessage("player >> {}".format(line)) self.__process.stdin.write(line) except IOError: From 7270c6aae1c81f813c604998d4a588210e049d18 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 8 Nov 2014 15:11:48 +0000 Subject: [PATCH 087/142] Add error handling for mpv OS X stdin failure --- syncplay/players/mpv.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index d6a07b5..fcf36a6 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -66,6 +66,10 @@ class MpvPlayer(MplayerPlayer): if "Error parsing option" in line or "Error parsing commandline option" in line: self.quitReason = getMessage("mpv-version-error") + elif "Could not open pipe at '/dev/stdin'" in line: + self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True) + self.drop() + elif line == "": self._clearFileLoaded() From dfa19aac43bd6a86aea6f1e088088629b8ff3718 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 9 Nov 2014 09:19:05 +0000 Subject: [PATCH 088/142] Undelete mpv askForStatus code --- syncplay/players/mpv.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index fcf36a6..8c277c4 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -96,6 +96,15 @@ class MpvPlayer(MplayerPlayer): self._onMPVFileUpdate() self.reactor.callFromThread(self._onFileUpdate) + def askForStatus(self): + self._positionAsk.clear() + self._pausedAsk.clear() + self._getPaused() + self._getPosition() + self._positionAsk.wait() + self._pausedAsk.wait() + self._client.updatePlayerStatus(self._paused, self._position) + def _recentlyReset(self): if not self.lastResetTime: return False From d63c4e82cfef8ab8b06a164e145ac26753b108f9 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 9 Nov 2014 09:39:10 +0000 Subject: [PATCH 089/142] Move askForStatus back to mplayer --- syncplay/players/mplayer.py | 9 +++++++++ syncplay/players/mpv.py | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index c7da527..efee9fc 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -68,6 +68,15 @@ class MplayerPlayer(BasePlayer): self.reactor.callLater(0, self._client.initPlayer, self) self._onFileUpdate() + def askForStatus(self): + self._positionAsk.clear() + self._pausedAsk.clear() + self._getPaused() + self._getPosition() + self._positionAsk.wait() + self._pausedAsk.wait() + self._client.updatePlayerStatus(self._paused, self._position) + def _setProperty(self, property_, value): self._listener.sendLine("set_property {} {}".format(property_, value)) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 8c277c4..fcf36a6 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -96,15 +96,6 @@ class MpvPlayer(MplayerPlayer): self._onMPVFileUpdate() self.reactor.callFromThread(self._onFileUpdate) - def askForStatus(self): - self._positionAsk.clear() - self._pausedAsk.clear() - self._getPaused() - self._getPosition() - self._positionAsk.wait() - self._pausedAsk.wait() - self._client.updatePlayerStatus(self._paused, self._position) - def _recentlyReset(self): if not self.lastResetTime: return False From 73746d546c1a04b39c01366882d7af5ff5ac59c3 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 9 Nov 2014 10:41:03 +0000 Subject: [PATCH 090/142] Don't use native OSX menu bar --- syncplay/ui/gui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 402a0c2..8e19724 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -473,5 +473,7 @@ class MainWindow(QtGui.QMainWindow): self.loadSettings() self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png")) self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) + if sys.platform.startswith('darwin'): + self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) self.show() self.setAcceptDrops(True) \ No newline at end of file From f323984814da751adf13bbee46a6258048c49d21 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 9 Nov 2014 10:53:53 +0000 Subject: [PATCH 091/142] Hopefully make dropfile path detection work accross all platforms --- syncplay/ui/GuiConfiguration.py | 5 +---- syncplay/ui/gui.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 5dc2960..3d563c6 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -229,10 +229,7 @@ class ConfigDialog(QtGui.QDialog): data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': - if sys.platform.startswith('win'): - dropfilepath = unicode(urls[0].path())[1:] # Removes starting slash - else: - dropfilepath = unicode(urls[0].path()) + dropfilepath = unicode(event.mimeData().urls()[0].toLocalFile()) if dropfilepath[-4:].lower() == ".exe": self.executablepathCombobox.setEditText(dropfilepath) else: diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 8e19724..2df30f0 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -432,10 +432,7 @@ class MainWindow(QtGui.QMainWindow): data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': - if sys.platform.startswith('win'): - dropfilepath = unicode(urls[0].path().replace("/", "\\"))[1:] # Removes starting slash - else: - dropfilepath = unicode(urls[0].path()) + dropfilepath = unicode(event.mimeData().urls()[0].toLocalFile()) if rewindFile == False: self._syncplayClient._player.openFile(dropfilepath) else: From b31f75c5989543472a4c5ab31a8a07960291c6ae Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 9 Nov 2014 11:00:34 +0000 Subject: [PATCH 092/142] Move OSX DontUseNativeMenuBar higher (maybe now it will work?) --- syncplay/ui/gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 2df30f0..4020daa 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -461,6 +461,8 @@ class MainWindow(QtGui.QMainWindow): self.resourcespath = utils.findWorkingDir() + "\\resources\\" else: self.resourcespath = utils.findWorkingDir() + "/resources/" + if sys.platform.startswith('darwin'): + self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) self.setWindowTitle("Syncplay v" + version) self.mainLayout = QtGui.QVBoxLayout() self.addTopLayout(self) @@ -470,7 +472,5 @@ class MainWindow(QtGui.QMainWindow): self.loadSettings() self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png")) self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) - if sys.platform.startswith('darwin'): - self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) self.show() self.setAcceptDrops(True) \ No newline at end of file From d18fd02c96cf3b5bd97457c0e1fcffc607791137 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 9 Nov 2014 13:57:05 +0000 Subject: [PATCH 093/142] Yet another attempt at getting OS X menu bar to show up --- syncplay/ui/gui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 4020daa..416f1b7 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -473,4 +473,6 @@ class MainWindow(QtGui.QMainWindow): self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png")) self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.show() + if sys.platform.startswith('darwin'): + self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) self.setAcceptDrops(True) \ No newline at end of file From c08497701144666aa1e0f60e205013322ff35f53 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 9 Nov 2014 14:06:04 +0000 Subject: [PATCH 094/142] remove one Qt.AA_DontUseNativeMenuBar and all platform checks --- syncplay/ui/gui.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 416f1b7..624bd0f 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -461,8 +461,7 @@ class MainWindow(QtGui.QMainWindow): self.resourcespath = utils.findWorkingDir() + "\\resources\\" else: self.resourcespath = utils.findWorkingDir() + "/resources/" - if sys.platform.startswith('darwin'): - self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) + self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) self.setWindowTitle("Syncplay v" + version) self.mainLayout = QtGui.QVBoxLayout() self.addTopLayout(self) @@ -471,8 +470,6 @@ class MainWindow(QtGui.QMainWindow): self.addMainFrame(self) self.loadSettings() self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png")) - self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) + self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.show() - if sys.platform.startswith('darwin'): - self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) self.setAcceptDrops(True) \ No newline at end of file From cfeb6dbeee83b425af96ad7b2d0d5620aff49613 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 22 Nov 2014 22:43:40 +0000 Subject: [PATCH 095/142] Split old/new mpv into separate classes + add wait timeout for new mpv --- syncplay/constants.py | 1 + syncplay/players/mplayer.py | 7 +- syncplay/players/mpv.py | 233 ++++++++++++++++++------------------ 3 files changed, 119 insertions(+), 122 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 98c2743..a506fe7 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -91,6 +91,7 @@ MPC_RETRY_WAIT_TIME = 0.01 MPC_MAX_RETRIES = 30 MPC_PAUSE_TOGGLE_DELAY = 0.05 MPV_NEWFILE_IGNORE_TIME = 1 +MPV_LOCK_WAIT_TIME = 0.2 VLC_OPEN_MAX_WAIT_TIME = 15 VLC_MIN_PORT = 10000 VLC_MAX_PORT = 55000 diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index efee9fc..03b1ea2 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -25,7 +25,6 @@ class MplayerPlayer(BasePlayer): self.quitReason = None self.lastLoadedTime = None self.fileLoaded = False - self.lastResetTime = None try: self._listener = self.__Listener(self, playerPath, filePath, args) except ValueError: @@ -93,8 +92,7 @@ class MplayerPlayer(BasePlayer): self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) def openFile(self, filePath, resetPosition=False): - if resetPosition: - self.lastResetTime = time.time() + self._filepath = filePath self._loadFile(filePath) self._onFileUpdate() if self._paused != self._client.getGlobalPaused(): @@ -135,9 +133,6 @@ class MplayerPlayer(BasePlayer): def _fileIsLoaded(self): return True - def _clearFileLoaded(self): - pass - def _handleUnknownLine(self, line): pass diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index fcf36a6..6f8d277 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -6,130 +6,22 @@ from syncplay import constants import os, sys, time class MpvPlayer(MplayerPlayer): - POSITION_QUERY = 'time-pos' - OSD_QUERY = 'show_text' RE_VERSION = re.compile('.*mpv (\d)\.(\d)\.\d.*') - def _setProperty(self, property_, value): - self._listener.sendLine("no-osd set {} {}".format(property_, value)) - - def setPaused(self, value): - if self._paused <> value: - self._paused = not self._paused - self._listener.sendLine('cycle pause') - - def _storePosition(self, value): - if self._recentlyReset(): - self._position = 0 - elif self._fileIsLoaded(): - self._position = value - else: - self._position = self._client.getGlobalPosition() - - def _storePauseState(self, value): - if self._fileIsLoaded(): - self._paused = value - else: - self._paused = self._client.getGlobalPaused() - - def _onFileUpdate(self): - if not constants.MPV_NEW_VERSION: - oldFilename = self._filename - oldLength = self._duration - oldFilepath = self._filepath - self._fileUpdateClearEvents() - self._getFilename() - self._getLength() - self._getFilepath() - self._fileUpdateWaitEvents() - if (self._filename != oldFilename) or (self._duration != oldLength) or (self._filepath != oldFilepath): - self._client.updateFile(self._filename, self._duration, self._filepath) - - def _clearFileLoaded(self): - self.fileLoaded = False - self.lastLoadedTime = None - - def _loadFile(self, filePath): - if constants.MPV_NEW_VERSION: - self._clearFileLoaded() - self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) - - def openFile(self, filePath, resetPosition=False): - if resetPosition: - self.lastResetTime = time.time() - self._loadFile(filePath) - if self._paused != self._client.getGlobalPaused(): - self.setPaused(self._client.getGlobalPaused()) - self.setPosition(self._client.getGlobalPosition()) - - def _handleUnknownLine(self, line): - if "Error parsing option" in line or "Error parsing commandline option" in line: - self.quitReason = getMessage("mpv-version-error") - - elif "Could not open pipe at '/dev/stdin'" in line: - self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True) - self.drop() - - elif line == "": - self._clearFileLoaded() - - elif line == "": - self._onMPVFileUpdate() - - elif "Failed to get value of property" in line: - if "filename" in line: - self._getFilename() - elif "length" in line: - self._getLength() - elif "path" in line: - self._getFilepath() - elif "time-pos" in line: - self.setPosition(self._client.getGlobalPosition()) - self._positionAsk.set() - - elif "Playing:" in line: - if constants.MPV_NEW_VERSION: - self._clearFileLoaded() - else: - self.fileLoaded = True - if not self._recentlyReset(): - self._onMPVFileUpdate() - self.reactor.callFromThread(self._onFileUpdate) - - def _recentlyReset(self): - if not self.lastResetTime: - return False - elif time.time() < self.lastResetTime + constants.MPV_NEWFILE_IGNORE_TIME: - return True - else: - return False - - def _onMPVFileUpdate(self): - self.fileLoaded = True - self.lastLoadedTime = time.time() - self.reactor.callFromThread(self._client.updateFile, self._filename, self._duration, self._filepath) - if not (self._recentlyReset()): - self.reactor.callFromThread(self.setPosition, self._client.getGlobalPosition()) - if self._paused != self._client.getGlobalPaused(): - self.reactor.callFromThread(self._client.getGlobalPaused) - - def _fileIsLoaded(self): - if self.fileLoaded == True and self.lastLoadedTime != None and time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME): - return True - else: - return False - @staticmethod def run(client, playerPath, filePath, args): - return MpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) - - @staticmethod - def getStartupArgs(path): try: - ver = MpvPlayer.RE_VERSION.search(subprocess.check_output([path, '--version'])) + ver = MpvPlayer.RE_VERSION.search(subprocess.check_output([playerPath, '--version'])) except: ver = None constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 5 + if constants.MPV_NEW_VERSION: + return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) + else: + return OldMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) + + @staticmethod + def getStartupArgs(path): args = constants.MPV_SLAVE_ARGS if constants.MPV_NEW_VERSION or sys.platform.startswith('win'): args.extend(constants.MPV_SLAVE_ARGS_NEW) @@ -173,3 +65,112 @@ class MpvPlayer(MplayerPlayer): @staticmethod def getIconPath(path): return constants.MPV_ICONPATH + +class OldMpvPlayer(MpvPlayer): + POSITION_QUERY = 'time-pos' + OSD_QUERY = 'show_text' + + def _setProperty(self, property_, value): + self._listener.sendLine("no-osd set {} {}".format(property_, value)) + + def setPaused(self, value): + if self._paused <> value: + self._paused = not self._paused + self._listener.sendLine('cycle pause') + + def mpvVersionErrorCheck(self, line): + if "Error parsing option" in line or "Error parsing commandline option" in line: + self.quitReason = getMessage("mpv-version-error") + + elif "Could not open pipe at '/dev/stdin'" in line: + self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True) + self.drop() + + def _handleUnknownLine(self, line): + self.mpvVersionErrorCheck(line) + if "Playing: " in line: + newpath = line[9:] + oldpath = self._filepath + if newpath != oldpath and oldpath is not None: + self.reactor.callFromThread(self._onFileUpdate) + if self._paused != self._client.getGlobalPaused(): + self.setPaused(self._client.getGlobalPaused()) + self.setPosition(self._client.getGlobalPosition()) + +class NewMpvPlayer(OldMpvPlayer): + lastResetTime = None + + def _storePosition(self, value): + if self._recentlyReset(): + self._position = 0 + elif self._fileIsLoaded(): + self._position = value + else: + self._position = self._client.getGlobalPosition() + + def _storePauseState(self, value): + if self._fileIsLoaded(): + self._paused = value + else: + self._paused = self._client.getGlobalPaused() + + def askForStatus(self): + self._positionAsk.clear() + self._pausedAsk.clear() + self._getPaused() + self._getPosition() + self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME) + self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME) + self._client.updatePlayerStatus(self._paused, self._position) + + def _preparePlayer(self): + self.setPaused(True) + self.reactor.callLater(0, self._client.initPlayer, self) + + def _clearFileLoaded(self): + self.fileLoaded = False + self.lastLoadedTime = None + + def _loadFile(self, filePath): + self._clearFileLoaded() + self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) + + def openFile(self, filePath, resetPosition=False): + if resetPosition: + self.lastResetTime = time.time() + self._loadFile(filePath) + if self._paused != self._client.getGlobalPaused(): + self.setPaused(self._client.getGlobalPaused()) + self.setPosition(self._client.getGlobalPosition()) + + def _handleUnknownLine(self, line): + self.mpvVersionErrorCheck(line) + + if line == "" or "Playing:" in line: + self._clearFileLoaded() + + elif line == "": + self._onFileUpdate() + + def _recentlyReset(self): + if not self.lastResetTime: + return False + elif time.time() < self.lastResetTime + constants.MPV_NEWFILE_IGNORE_TIME: + return True + else: + return False + + def _onFileUpdate(self): + self.fileLoaded = True + self.lastLoadedTime = time.time() + self.reactor.callFromThread(self._client.updateFile, self._filename, self._duration, self._filepath) + if not (self._recentlyReset()): + self.reactor.callFromThread(self.setPosition, self._client.getGlobalPosition()) + if self._paused != self._client.getGlobalPaused(): + self.reactor.callFromThread(self._client.getGlobalPaused) + + def _fileIsLoaded(self): + if self.fileLoaded == True and self.lastLoadedTime != None and time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME): + return True + else: + return False \ No newline at end of file From 4ede1f618c53ad60c8868611667cbe4b16de07fb Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 25 Nov 2014 18:58:13 +0000 Subject: [PATCH 096/142] Correct slashes for file drops on Windows --- syncplay/ui/GuiConfiguration.py | 2 +- syncplay/ui/gui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 3d563c6..054d7c0 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -229,7 +229,7 @@ class ConfigDialog(QtGui.QDialog): data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': - dropfilepath = unicode(event.mimeData().urls()[0].toLocalFile()) + dropfilepath = os.path.abspath(unicode(event.mimeData().urls()[0].toLocalFile())) if dropfilepath[-4:].lower() == ".exe": self.executablepathCombobox.setEditText(dropfilepath) else: diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 624bd0f..ad57eb9 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -432,7 +432,7 @@ class MainWindow(QtGui.QMainWindow): data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': - dropfilepath = unicode(event.mimeData().urls()[0].toLocalFile()) + dropfilepath = os.path.abspath(unicode(event.mimeData().urls()[0].toLocalFile())) if rewindFile == False: self._syncplayClient._player.openFile(dropfilepath) else: From 0c9e25a689a73f72c8a154199eec96e1190bdc9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wr=C3=B3bel?= Date: Tue, 25 Nov 2014 21:20:36 +0100 Subject: [PATCH 097/142] mplayer2 and mpv keep pitch constant upon slowdown. --- syncplay/constants.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index a506fe7..a49a148 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -108,12 +108,12 @@ STYLE_ERRORNOTIFICATION = "color: red;" STYLE_DIFFERENTITEM_COLOR = 'red' STYLE_NOFILEITEM_COLOR = 'blue' -MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4'] +MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af', 'scaletempo'] # --quiet works with both mpv 0.2 and 0.3 -MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open'] -MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken'] -MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin'] -MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n'] +MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open', '-af', 'scaletempo'] +MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken', '-af', 'scaletempo'] +MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin', '-af', 'scaletempo'] +MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n', '-af', 'scaletempo'] MPV_NEW_VERSION = False VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', '--play-and-pause', '--start-time=0'] @@ -133,4 +133,4 @@ INVERTED_STATE_MARKER = "*" ERROR_MESSAGE_MARKER = "*" LOAD_SAVE_MANUALLY_MARKER = "!" CONFIG_NAME_MARKER = ":" -CONFIG_VALUE_MARKER = "=" \ No newline at end of file +CONFIG_VALUE_MARKER = "=" From 4a3094ab85f23515a07d7ede0f1f30279f9dcd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wr=C3=B3bel?= Date: Tue, 25 Nov 2014 21:44:22 +0100 Subject: [PATCH 098/142] Remove redundant slave arguments from mpv. --- syncplay/constants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index a49a148..4cd8f3d 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -111,9 +111,9 @@ STYLE_NOFILEITEM_COLOR = 'blue' MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af', 'scaletempo'] # --quiet works with both mpv 0.2 and 0.3 MPV_SLAVE_ARGS = ['--hr-seek=always', '--quiet', '--keep-open', '-af', 'scaletempo'] -MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken', '-af', 'scaletempo'] -MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin', '-af', 'scaletempo'] -MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n', '-af', 'scaletempo'] +MPV_SLAVE_ARGS_WINDOWS = ['--slave-broken'] +MPV_SLAVE_ARGS_NONWINDOWS = ['--input-terminal=no','--input-file=/dev/stdin'] +MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n'] MPV_NEW_VERSION = False VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', '--play-and-pause', '--start-time=0'] From abd6547c9c6ca7ce9a9aea63c286bc9cf91c6bc3 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 13:56:26 +0000 Subject: [PATCH 099/142] Move to Unicode version of NSIS --- buildPy2exe.py | 52 +++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 463f99d..b0712be 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -16,7 +16,7 @@ import syncplay import os import subprocess -p = "C:\\Program Files (x86)\\NSIS\\makensis.exe" #TODO: how to move that into proper place, huh +p = "C:\\Program Files (x86)\\NSIS\\Unicode\\makensis.exe" #TODO: how to move that into proper place, huh NSIS_COMPILE = p if os.path.isfile(p) else "makensis.exe" OUT_DIR = "syncplay v{}".format(syncplay.version) SETUP_SCRIPT_PATH = "syncplay_setup.nsi" @@ -28,7 +28,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LoadLanguageFile "$${NSISDIR}\Contrib\Language files\English.nlf" LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Polish.nlf" LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Russian.nlf" - + Name "Syncplay $version" OutFile "Syncplay $version Setup.exe" InstallDir $$PROGRAMFILES\Syncplay @@ -36,23 +36,23 @@ NSIS_SCRIPT_TEMPLATE = r""" XPStyle on Icon resources\icon.ico ;Change DIR SetCompressor /SOLID lzma - + VIProductVersion "$version.0" VIAddVersionKey /LANG=$${LANG_ENGLISH} "ProductName" "Syncplay" VIAddVersionKey /LANG=$${LANG_ENGLISH} "FileVersion" "$version.0" VIAddVersionKey /LANG=$${LANG_ENGLISH} "LegalCopyright" "Syncplay" VIAddVersionKey /LANG=$${LANG_ENGLISH} "FileDescription" "Syncplay" - + VIAddVersionKey /LANG=$${LANG_POLISH} "ProductName" "Syncplay" VIAddVersionKey /LANG=$${LANG_POLISH} "FileVersion" "$version.0" VIAddVersionKey /LANG=$${LANG_POLISH} "LegalCopyright" "Syncplay" VIAddVersionKey /LANG=$${LANG_POLISH} "FileDescription" "Syncplay" - + VIAddVersionKey /LANG=$${LANG_RUSSIAN} "ProductName" "Syncplay" VIAddVersionKey /LANG=$${LANG_RUSSIAN} "FileVersion" "$version.0" VIAddVersionKey /LANG=$${LANG_RUSSIAN} "LegalCopyright" "Syncplay" VIAddVersionKey /LANG=$${LANG_RUSSIAN} "FileDescription" "Syncplay" - + LangString ^SyncplayLanguage $${LANG_ENGLISH} "en" LangString ^Associate $${LANG_ENGLISH} "Associate Syncplay with multimedia files." LangString ^VLC $${LANG_ENGLISH} "Install Syncplay interface for VLC 2 and above" @@ -62,7 +62,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^Desktop $${LANG_ENGLISH} "Desktop" LangString ^QuickLaunchBar $${LANG_ENGLISH} "Quick Launch Bar" LangString ^UninstConfig $${LANG_ENGLISH} "Delete configuration file." - + LangString ^SyncplayLanguage $${LANG_POLISH} "pl" LangString ^Associate $${LANG_POLISH} "Skojarz Syncplaya z multimediami" LangString ^VLC $${LANG_POLISH} "Zainstaluj interface Syncplaya dla VLC 2+" @@ -72,7 +72,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^Desktop $${LANG_POLISH} "Pulpit" LangString ^QuickLaunchBar $${LANG_POLISH} "Pasek szybkiego uruchamiania" LangString ^UninstConfig $${LANG_POLISH} "Usun plik konfiguracyjny." - + LangString ^SyncplayLanguage $${LANG_RUSSIAN} "ru" LangString ^Associate $${LANG_RUSSIAN} "Ассоциировать Syncplay с видеофайлами" LangString ^VLC $${LANG_RUSSIAN} "Установить интерфейс Syncplay для VLC 2+" @@ -82,16 +82,16 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^Desktop $${LANG_RUSSIAN} "на рабочем столе" LangString ^QuickLaunchBar $${LANG_RUSSIAN} "в меню быстрого запуска" LangString ^UninstConfig $${LANG_RUSSIAN} "Удалить файл настроек." - + PageEx license LicenseData resources\license.txt PageExEnd Page custom DirectoryCustom DirectoryCustomLeave Page instFiles - + UninstPage custom un.installConfirm un.installConfirmLeave UninstPage instFiles - + Var Dialog Var Icon_Syncplay Var Icon_Syncplay_Handle @@ -114,7 +114,7 @@ NSIS_SCRIPT_TEMPLATE = r""" Var Label_Size Var Label_Space Var Text_Directory - + Var Uninst_Dialog Var Uninst_Icon Var Uninst_Icon_Handle @@ -123,14 +123,14 @@ NSIS_SCRIPT_TEMPLATE = r""" Var Uninst_Text_Directory Var Uninst_CheckBox_Config Var Uninst_CheckBox_Config_State - + Var Size Var SizeHex Var AvailibleSpace Var AvailibleSpaceGiB Var Drive Var VLC_Directory - + !macro APP_ASSOCIATE EXT FileCLASS DESCRIPTION COMMANDTEXT COMMAND WriteRegStr HKCR ".$${EXT}" "" "$${FileCLASS}" WriteRegStr HKCR "$${FileCLASS}" "" `$${DESCRIPTION}` @@ -138,23 +138,23 @@ NSIS_SCRIPT_TEMPLATE = r""" WriteRegStr HKCR "$${FileCLASS}\shell\open" "" `$${COMMANDTEXT}` WriteRegStr HKCR "$${FileCLASS}\shell\open\command" "" `$${COMMAND}` !macroend - + !macro APP_UNASSOCIATE EXT FileCLASS ; Backup the previously associated File class ReadRegStr $$R0 HKCR ".$${EXT}" `$${FileCLASS}_backup` WriteRegStr HKCR ".$${EXT}" "" "$$R0" DeleteRegKey HKCR `$${FileCLASS}` !macroend - + !macro ASSOCIATE EXT !insertmacro APP_ASSOCIATE "$${EXT}" "Syncplay.$${EXT}" "$$INSTDIR\Syncplay.exe,%1%" \ "Open with Syncplay" "$$INSTDIR\Syncplay.exe $$\"%1$$\"" !macroend - + !macro UNASSOCIATE EXT !insertmacro APP_UNASSOCIATE "$${EXT}" "Syncplay.$${EXT}" !macroend - + ;Prevents from running more than one instance of installer and sets default state of checkboxes Function .onInit System::Call 'kernel32::CreateMutexA(i 0, i 0, t "SyncplayMutex") i .r1 ?e' @@ -181,7 +181,7 @@ NSIS_SCRIPT_TEMPLATE = r""" Push $${LANG_POLISH} Push Polski Push $${LANG_RUSSIAN} - Push Russian + Push Русский Push A ; A means auto count languages LangDLL::LangDialog "Language Selection" "Please select the language of Syncplay and the installer" Pop $$LANGUAGE @@ -234,13 +234,13 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_CreateLabel} 8u 85u 187u 10u "$$(^Shortcut)" Pop $$Label_Shortcut - $${NSD_CreateCheckbox} 8u 98u 50u 10u "$$(^StartMenu)" + $${NSD_CreateCheckbox} 8u 98u 60u 10u "$$(^StartMenu)" Pop $$CheckBox_StartMenuShortcut - $${NSD_CreateCheckbox} 68u 98u 50u 10u "$$(^Desktop)" + $${NSD_CreateCheckbox} 78u 98u 70u 10u "$$(^Desktop)" Pop $$CheckBox_DesktopShortcut - $${NSD_CreateCheckbox} 128u 98u 150u 10u "$$(^QuickLaunchBar)" + $${NSD_CreateCheckbox} 158u 98u 130u 10u "$$(^QuickLaunchBar)" Pop $$CheckBox_QuickLaunchShortcut $${If} $$CheckBox_Associate_State == $${BST_CHECKED} @@ -494,18 +494,18 @@ NSIS_SCRIPT_TEMPLATE = r""" IfFileExists "$$VLC_Directory\lua\intf\syncplay.lua" 0 +2 Delete $$VLC_Directory\lua\intf\syncplay.lua FunctionEnd - + Section "Install" SetOverwrite on SetOutPath $$INSTDIR WriteUninstaller uninstall.exe - + $installFiles - + Call InstallOptions Call WriteRegistry SectionEnd - + Section "Uninstall" Call un.AssociateDel Call un.InstallOptions From ef73447753f6655037681fc3141a115eafa0c3b8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 15:01:45 +0000 Subject: [PATCH 100/142] Merge & modify pull request #46 from DerGenaue/master (add German) --- buildPy2exe.py | 16 ++++ syncplay/messages.py | 196 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 211 insertions(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index b0712be..6155944 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -28,6 +28,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LoadLanguageFile "$${NSISDIR}\Contrib\Language files\English.nlf" LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Polish.nlf" LoadLanguageFile "$${NSISDIR}\Contrib\Language files\Russian.nlf" + LoadLanguageFile "$${NSISDIR}\Contrib\Language files\German.nlf" Name "Syncplay $version" OutFile "Syncplay $version Setup.exe" @@ -83,6 +84,19 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^QuickLaunchBar $${LANG_RUSSIAN} "в меню быстрого запуска" LangString ^UninstConfig $${LANG_RUSSIAN} "Удалить файл настроек." + LangString ^SyncplayLanguage $${LANG_GERMAN} "de" + LangString ^Associate $${LANG_GERMAN} "Syncplay mit Multimedia-Dateien assoziieren." + LangString ^VLC $${LANG_GERMAN} "Syncplay-Interface für VLC istallieren (ab VLC 2+)" + LangString ^Shortcut $${LANG_GERMAN} "Erstelle Verknüpfungen an folgenden Orten:" + LangString ^BrowseVLCBtn $${LANG_GERMAN} "Wählen VLC Ordner" + LangString ^StartMenu $${LANG_GERMAN} "Start Menü" + LangString ^Desktop $${LANG_GERMAN} "Desktop" + LangString ^QuickLaunchBar $${LANG_GERMAN} "Schnellstartleiste" + LangString ^UninstConfig $${LANG_GERMAN} "Konfigurationsdatei löschen." + + ; Remove text to save space + LangString ^ClickInstall $${LANG_GERMAN} " " + PageEx license LicenseData resources\license.txt PageExEnd @@ -182,6 +196,8 @@ NSIS_SCRIPT_TEMPLATE = r""" Push Polski Push $${LANG_RUSSIAN} Push Русский + Push $${LANG_GERMAN} + Push Deutsch Push A ; A means auto count languages LangDLL::LangDialog "Language Selection" "Please select the language of Syncplay and the installer" Pop $$LANGUAGE diff --git a/syncplay/messages.py b/syncplay/messages.py index 059e3dd..a190893 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -106,7 +106,7 @@ en = { "file-argument" : 'file to play', "args-argument" : 'player options, if you need to pass options starting with - prepend them with single \'--\' argument', "clear-gui-data-argument" : 'resets path and window state GUI data stored as QSettings', - "language-argument" :'language for Syncplay messages (en/pl/ru)', + "language-argument" :'language for Syncplay messages (de/en/pl/ru)', "version-argument" : 'prints your version', "version-message" : "You're using Syncplay version {} ({})", @@ -578,11 +578,205 @@ ru = { "version-mismatch-server-error" : u"Конфликт версий между клиентом и сервером." } + +de = { + + # Client notifications + "relative-config-notification" : u"Relative Konfigurationsdatei(en) geladen: {}", + + "connection-attempt-notification" : u"Versuche zu verbinden nach {}:{}", # Port, IP + "reconnection-attempt-notification" : u"Verbindung zum Server verloren, versuche erneut", + "disconnection-notification" : u"Verbindung zum Server beendet", + "connection-failed-notification" : u"Verbindung zum Server fehlgeschlagen", + "connected-successful-notification" : u"Erfolgreich mit Server verbunden", + "retrying-notification" : u"%s, versuche erneut in %d Sekunden...", # Seconds + + "rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit <{}>", # User + "slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit <{}>", # User + "revert-notification" : u"Normalgeschwindigkeit", + + "pause-notification" : u"<{}> pausierte", # User + "unpause-notification" : u"<{}> startete", # User + "seek-notification" : u"<{}> sprang von {} nach {}", # User, from time, to time + + "current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset + + "room-join-notification" : u"<{}> hat den Raum '{}' betreten", # User + "left-notification" : u"<{}> ist gegangen", # User + "playing-notification" : u"<{}> spielt '{}' ({})", # User, file, duration + "playing-notification/room-addendum" : u" in Raum: '{}'", # Room + + "file-different-notification" : u"Deine Datei scheint sich von <{}>s zu unterscheiden", # User + "file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: ", + "room-files-not-same" : u"Nicht alle Dateien im Raum sind gleich", + "alone-in-the-room": u"Du bist alleine im Raum", + + "different-filesize-notification" : u" (ihre Dateigröße ist anders als deine!)", + "file-played-by-notification" : u"Datei: {} wird gespielt von:", # File + "notplaying-notification" : u"Personen im Raum, die keine Dateien spielen:", + "userlist-room-notification" : u"In Raum '{}':", # Room + + "mplayer-file-required-notification" : u"Syncplay für mplayer benötigt eine Datei-Angabe beim Start", + "mplayer-file-required-notification/example" : u"Nutzungsbeispiel: syncplay [optionen] [url|pfad/]dateiname", + "mplayer2-required" : u"Syncplay ist inkompatibel zu MPlayer 1.x, bitte nutze MPlayer2 oder mpv", + + "unrecognized-command-notification" : u"Unbekannter Befehl", + "commandlist-notification" : u"Verfügbare Befehle:", + "commandlist-notification/room" : u"\tr [Name] - Raum ändern", + "commandlist-notification/list" : u"\tl - Nutzerliste anzeigen", + "commandlist-notification/undo" : u"\tu - Letzter Zeitsprung rückgängig", + "commandlist-notification/pause" : u"\tp - Pausieren / weiter", + "commandlist-notification/seek" : u"\t[s][+-]Zeit - zu einer bestimmten Zeit spulen, ohne + oder - wird als absolute Zeit gewertet; Angabe in Sekungen oder Minuten:Sekunden", + "commandlist-notification/help" : u"\th - Diese Hilfe", + "syncplay-version-notification" : u"Syncplay Version: {}", # syncplay.version + "more-info-notification" : u"Weitere Informationen auf: {}", # projectURL + + "gui-data-cleared-notification" : u"Syncplay hat die Pfad und Fensterdaten der Syncplay-GUI zurückgesetzt.", + + "vlc-version-mismatch": u"Warnung: Du nutzt VLC Version {}, aber Syncplay wurde für VLC ab Version {} entwickelt.", # VLC version, VLC min version + "vlc-interface-version-mismatch": u"Warnung: Du nutzt Version {} des VLC-Syncplay Interface-Moduls, Syncplay benötigt aber mindestens Version {}.", # VLC interface version, VLC interface min version + "vlc-interface-oldversion-ignored": u"Warnung: Syncplay hat bemerkt, dass eine alte Version des Syncplay Interface-Moduls für VLC im VLC-Verzeichnis installiert ist. Daher wird, wenn du VLC 2.0 nutzt, die syncplay.lua die mit Syncplay mitgeliefert wurde, verwendet. Dies bedeutet allerdings, dass keine anderen Interface-Skripts und Erweiterungen geladen werden. In der Syncplay-Anleitung unter http://syncplay.pl/guide/ [Englisch] findest du Details zur Installation des syncplay.lua-Skripts.", + "vlc-interface-not-installed": u"Warnung: Es wurde kein Syncplay Interface-Modul für VLC im VLC-Verzeichnis gefunden. Daher wird, wenn du VLC 2.0 nutzt, die syncplay.lua die mit Syncplay mitgeliefert wurde, verwendet. Dies bedeutet allerdings, dass keine anderen Interface-Skripts und Erweiterungen geladen werden. In der Syncplay-Anleitung unter http://syncplay.pl/guide/ [Englisch] findest du Details zur Installation des syncplay.lua-Skripts.", + + # Client prompts + "enter-to-exit-prompt" : u"Enter drücken zum Beenden\n", + + # Client errors + "missing-arguments-error" : u"Notwendige Argumente fehlen, siehe --help", + "server-timeout-error" : u"Timeout: Verbindung zum Server fehlgeschlagen", + "mpc-slave-error" : u"Kann MPC nicht im Slave-Modus starten!", + "mpc-version-insufficient-error" : u"MPC-Version nicht ausreichend, bitte nutze `mpc-hc` >= `{}`", + "player-file-open-error" : u"Fehler beim Öffnen der Datei durch den Player", + "player-path-error" : u"Ungültiger Player-Pfad", + "hostname-empty-error" : u"Hostname darf nicht leer sein", + "empty-error" : u"{} darf nicht leer sein", # Configuration + + "arguments-missing-error" : u"Notwendige Argumente fehlen, siehe --help", + + "unable-to-start-client-error" : u"Client kann nicht gestartet werden", + + "not-json-error" : u"Kein JSON-String\n", + "hello-arguments-error" : u"Zu wenige Hello-Argumente\n", + "version-mismatch-error" : u"Verschiedene Versionen auf Client und Server\n", + "vlc-error-echo": u"VLC-Fehler: {}", # VLC error line + "vlc-unicode-loadfile-error" : u"Die Datei kann nicht durch Syncplay geladen werden, da sie nicht-ASCII Zeichen enthält. Bitte öffne die Datei mit VLC.", + "vlc-failed-connection": u"Kann nicht zu VLC verbinden. Wenn du syncplay.lua nicht installiert hast, findest du auf http://syncplay.pl/LUA/ für eine Anleitung.", + "vlc-failed-noscript": u"Laut VLC ist das syncplay.lua Interface-Skript nicht installiert. Auf http://syncplay.pl/LUA/ findest du eine Anleitung.", + "vlc-failed-versioncheck": u"Diese VLC-Version wird von Syncplay nicht unterstützt. Bitte nutze VLC 2.0", + "vlc-failed-other" : u"Beim Laden des syncplay.lua Interface-Skripts durch VLC trat folgender Fehler auf: {}", # Syncplay Error + + # Client arguments + "argument-description" : u'Anwendung, um mehrere MPlayer, MPC-HC und VLC-Instanzen über das Internet zu synchronisieren.', + "argument-epilog" : u'Wenn keine Optionen angegeben sind, werden die _config-Werte verwendet', + "nogui-argument" : u'Keine GUI anzeigen', + "host-argument" : u'Server\'-Addresse', + "name-argument" : u'Gewünschter Nutzername', + "debug-argument" : u'Debug-Modus', + "force-gui-prompt-argument" : u'Einstellungsfenster anzeigen', + "no-store-argument" : u'keine Werte in .syncplay speichern', + "room-argument" : u'Standard-Raum', + "password-argument" : u'Server-Passwort', + "player-path-argument" : u'Pfad zum Player', + "file-argument" : u'Zu spielende Datei', + "args-argument" : u'Player-Einstellungen; Wenn du Einstellungen, die mit - beginnen, nutzen willst, stelle ein einzelnes \'--\'-Argument davor', + "clear-gui-data-argument" : u'Setzt die Pfad- und GUI-Fenster-Daten, die in den QSettings gespeichert sind, zurück', + + # Client labels + "config-window-title" : u"Syncplay Konfiguration", + + "connection-group-title" : u"Verbindungseinstellungen", + "host-label" : u"Server-Adresse: ", + "username-label" : u"Benutzername (optional):", + "password-label" : u"Server-Passwort (falls nötig):", + "room-label" : u"Standard-Raum: ", + + "media-setting-title" : u"Media-Player Einstellungen", + "executable-path-label" : u"Pfad zum Media-Player:", + "media-path-label" : u"Pfad zur Datei:", + "browse-label" : u"Durchsuchen", + + "more-title" : u"Mehr Einstellungen zeigen", + "privacy-sendraw-option" : u"Klartext senden", + "privacy-sendhashed-option" : u"Hash senden", + "privacy-dontsend-option" : u"Nicht senden", + "filename-privacy-label" : u"Dateiname:", + "filesize-privacy-label" : u"Dateigröße:", + "slowdown-label" : u"Verlangsamen wenn nicht synchron", + "dontslowwithme-label" : u"Nie verlangsamen oder andere zurückspulen", + "pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt", + "rewind-label" : u"Zurückspulen bei großer Zeitdifferent (empfohlen)", + "alwayshow-label" : u"Diesen Dialog immer anzeigen", + "donotstore-label" : u"Diese Konfiguration nicht speichern", + + "help-label" : u"Hilfe", + "run-label" : u"Syncplay starten", + "storeandrun-label" : u"Konfiguration speichern und Syncplay starten", + + "roomuser-heading-label" : u"Raum / Benutzer", + "fileplayed-heading-label" : u"Gespielte Datei", + + # Tooltips + + "host-tooltip" : u"Hostname oder IP zu der verbunden werden soll. Ptional mit Port (z.B.. syncplay.pl:8999). Synchronisation findet nur mit Personen auf dem selben Server und Port statt.", + "username-tooltip" : u"Dein Benutzername. Keine Registrierung, kann einfach geändert werden. Bei fehlender Angabe wird ein zufälliger Name generiert.", + "password-tooltip" : u"Passwörter sind nur bei Verbindung zu privaten Servern nötig.", + "room-tooltip" : u"Der Raum, der betreten werden soll, kann ein x-beliebiger sein. Allerdings werden nur Clients im selben Raum synchronisiert.", + + "executable-path-tooltip" : u"Pfad zum ausgewählten, unterstützten Mediaplayer (MPC-HC, VLC, mplayer2 or mpv).", + "media-path-tooltip" : u"Pfad zum wiederzugebenden Video oder Stream. Notwendig für mpv und mplayer2.", + + "more-tooltip" : u"Weitere Einstellungen anzeigen.", + "filename-privacy-tooltip" : u"Privat-Modus beim senden des Namens der aktuellen Datei zum Server.", + "filesize-privacy-tooltip" : u"Privat-Modus beim senden der Größe der aktuellen Datei zum Server.", + "privacy-sendraw-tooltip" : u"Die Information im Klartext übertragen. Dies ist die Standard-Einstellung mit der besten Funktionalität.", + "privacy-sendhashed-tooltip" : u"Die Informationen gehasht übertragen, um sie für andere Clients schwerer lesbar zu machen.", + "privacy-dontsend-tooltip" : u"Diese Information nicht übertragen. Dies garantiert den größtmöglichen Datanschutz.", + "slowdown-tooltip" : u"Reduziert die Abspielgeschwindigkeit zeitweise, um dich wieder synchron zu den anderen Clients zu machen.", + "dontslowwithme-tooltip" : u"Lässt andere nicht langsamer werden oder zurückspringen, wenn deine Wiedergabe hängt.", + "pauseonleave-tooltip" : u"Wiedergabe anhalten, wenn deine Verbindung verloren geht oder jemand den Raum verlässt.", + "rewind-tooltip" : u"In der Zeit zurückspringen zum wiederherstellen der Synchronität. Empfohlen.", + "alwayshow-tooltip" : u"Der Konfigurations-Dislog wird immer angezeigt. Sogar, wenn du eine Datei mit Syncplay öffnest.", + "donotstore-tooltip" : u"Syncplay mit den angegebenen Einstellungen starten, diese aber nicht fauerhaft speichern.", + + "help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]", + + # Server messages to client + "new-syncplay-available-motd-message" : u" Du nutzt Syncplay Version {}, aber es gibt eine neuere Version auf http://syncplay.pl", # ClientVersion + + # Server notifications + "welcome-server-notification" : u"Willkommen zum Syncplay-Server, v. {0}", # version + "client-connected-room-server-notification" : u"{0}({2}) hat den Raum '{1}' betreten", # username, host, room + "client-left-server-notification" : u"{0} hat den Server verlassen", # name + + + # Server arguments + "server-argument-description" : u'Anwendung, um mehrere MPlayer, MPC-HC und VLC-Instanzen über das Internet zu synchronisieren. Server', + "server-argument-epilog" : u'Wenn keine Optionen angegeben sind, werden die _config-Werte verwendet', + "server-port-argument" : u'Server TCP-Port', + "server-password-argument" : u'Server Passwort', + "server-isolate-room-argument" : u'Sollen die Räume isoliert sein?', + "server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird", + "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 {}.", + "server-irc-verbose": u"Soll der Server aktiv über Änderungen in den Räumen informieren", + "server-irc-config": u"Pfad zu den config-Dateien des irc bot", + + # Server errors + "unknown-command-server-error" : u"Unbekannter Befehl {}", # message + "not-json-server-error" : u"Kein JSON-String {}", # message + "not-known-server-error" : u"Der Server muss dich kennen, bevor du diesen Befehl nutzen kannst", + "client-drop-server-error" : u"Client verloren: {} -- {}", # host, error + "password-required-server-error" : u"Passwort nötig", + "hello-server-error" : u"Zu wenige Hello-Argumente", + "version-mismatch-server-error" : u"Verschiedene Versionen auf Client und Server", + "wrong-password-server-error" : u"Ungültiges Passwort" + } messages = { "en": en, "pl": pl, "ru": ru, + "de": de, "current": None } From fe02b884dab8be8d615d4d44bf9d738826a7c460 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 15:27:36 +0000 Subject: [PATCH 101/142] Add "Warn About Missing Strings" option --- syncplay/client.py | 7 ++++++- syncplay/constants.py | 1 + syncplay/messages.py | 9 +++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index bd84a42..f7618dd 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -6,7 +6,7 @@ from twisted.internet.protocol import ClientFactory from twisted.internet import reactor, task from syncplay.protocols import SyncClientProtocol from syncplay import utils, constants -from syncplay.messages import getMessage +from syncplay.messages import getMessage, getMissingStrings import threading from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \ PRIVACY_HIDDENFILENAME, FILENAME_STRIP_REGEX @@ -102,6 +102,11 @@ class SyncplayClient(object): if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']: self.ui.showMessage(getMessage("relative-config-notification").format("; ".join(self._config['loadedRelativePaths'])), noPlayer=True, noTimestamp=True) + if constants.DEBUG_MODE and constants.WARN_ABOUT_MISSING_STRINGS: + missingStrings = getMissingStrings() + if missingStrings is not None and missingStrings is not "": + self.ui.showDebugMessage("MISSING STRINGS DETECTED:\n{}".format(missingStrings)) + def initProtocol(self, protocol): self._protocol = protocol diff --git a/syncplay/constants.py b/syncplay/constants.py index 4cd8f3d..59a5adb 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -14,6 +14,7 @@ LIST_RELATIVE_CONFIGS = True # Print list of relative configs loaded SHOW_CONTACT_INFO = True # Displays dev contact details below list in GUI SHOW_BUTTON_LABELS = True # If disabled, only shows icons for main GUI buttons SHOW_TOOLTIPS = True +WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled) #Overriden by config SHOW_OSD = True # Sends Syncplay messages to media player OSD diff --git a/syncplay/messages.py b/syncplay/messages.py index a190893..09e310c 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -783,6 +783,15 @@ messages = { def setLanguage(lang): messages["current"] = lang +def getMissingStrings(): + missingStrings = "" + for lang in messages: + if lang != "en" and lang != "current": + for message in messages["en"]: + if not messages[lang].has_key(message): + missingStrings = missingStrings + "({}) Missing: {}\n".format(lang, message) + return missingStrings + def getMessage(type_, locale=None): if constants.SHOW_BUTTON_LABELS == False: if "-guibuttonlabel" in type_: From 2d84ffc822b68250c4d6b3637aa86d294f79f86d Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 15:45:40 +0000 Subject: [PATCH 102/142] Move GuiConfig group titles to messages.py --- syncplay/messages.py | 5 +++++ syncplay/ui/GuiConfiguration.py | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 09e310c..c51f40b 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -152,8 +152,13 @@ en = { "showdurationnotification-label" : "Warn about media duration mismatches", "basics-label" : "Basics", "sync-label" : "Sync", + "sync-lagging-title" : "If others are lagging behind...", + "sync-other-title" : "Other sync options", "messages-label" : "Messages", + "messages-osd-title" : "On-screen Display settings", + "messages-other-title" : "Other display settings", "privacy-label" : "Privacy", + "privacy-title" : "Privacy settings", "help-label" : "Help", "reset-label" : "Restore defaults", diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 054d7c0..d858b1f 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -406,7 +406,7 @@ class ConfigDialog(QtGui.QDialog): self.syncSettingsFrame = QtGui.QFrame() self.syncSettingsLayout = QtGui.QVBoxLayout() - self.desyncSettingsGroup = QtGui.QGroupBox("If others are lagging behind...") + self.desyncSettingsGroup = QtGui.QGroupBox(getMessage("sync-lagging-title")) self.desyncOptionsFrame = QtGui.QFrame() self.desyncSettingsOptionsLayout = QtGui.QHBoxLayout() config = self.config @@ -480,7 +480,7 @@ class ConfigDialog(QtGui.QDialog): self.syncSettingsLayout.addWidget(self.desyncSettingsGroup) self.desyncFrame.setLayout(self.syncSettingsLayout) - self.othersyncSettingsGroup = QtGui.QGroupBox("Other sync options") + self.othersyncSettingsGroup = QtGui.QGroupBox(getMessage("sync-other-title")) self.othersyncOptionsFrame = QtGui.QFrame() self.othersyncSettingsLayout = QtGui.QGridLayout() @@ -505,7 +505,7 @@ class ConfigDialog(QtGui.QDialog): self.messageLayout = QtGui.QVBoxLayout() # OSD - self.osdSettingsGroup = QtGui.QGroupBox("On-screen Display settings") + self.osdSettingsGroup = QtGui.QGroupBox(getMessage("messages-osd-title")) self.osdSettingsLayout = QtGui.QVBoxLayout() self.osdSettingsFrame = QtGui.QFrame() @@ -541,7 +541,7 @@ class ConfigDialog(QtGui.QDialog): # Other display - self.displaySettingsGroup = QtGui.QGroupBox("Other display settings") + self.displaySettingsGroup = QtGui.QGroupBox(getMessage("messages-other-title")) self.displaySettingsLayout = QtGui.QVBoxLayout() self.displaySettingsFrame = QtGui.QFrame() @@ -571,7 +571,7 @@ class ConfigDialog(QtGui.QDialog): self.stackedLayout.addWidget(self.messageFrame) def addPrivacyTab(self): - self.privacySettingsGroup = QtGui.QGroupBox("Privacy settings") + self.privacySettingsGroup = QtGui.QGroupBox(getMessage("privacy-title")) self.privacySettingsLayout = QtGui.QVBoxLayout() self.privacySettingsFrame = QtGui.QFrame() self.privacyFrame = QtGui.QFrame() From 3f2a88e77b5ecfa66045f5c4653bc92dc95244a5 Mon Sep 17 00:00:00 2001 From: DerGenaue Date: Sat, 29 Nov 2014 16:54:57 +0100 Subject: [PATCH 103/142] German Translation finish Corrected Typos and wrong word orders in German translation --- buildPy2exe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 6155944..0ab0042 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -86,10 +86,10 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^SyncplayLanguage $${LANG_GERMAN} "de" LangString ^Associate $${LANG_GERMAN} "Syncplay mit Multimedia-Dateien assoziieren." - LangString ^VLC $${LANG_GERMAN} "Syncplay-Interface für VLC istallieren (ab VLC 2+)" + LangString ^VLC $${LANG_GERMAN} "Syncplay-Interface für VLC installieren (ab VLC 2+)" LangString ^Shortcut $${LANG_GERMAN} "Erstelle Verknüpfungen an folgenden Orten:" - LangString ^BrowseVLCBtn $${LANG_GERMAN} "Wählen VLC Ordner" - LangString ^StartMenu $${LANG_GERMAN} "Start Menü" + LangString ^BrowseVLCBtn $${LANG_GERMAN} "VLC-Ordner wählen" + LangString ^StartMenu $${LANG_GERMAN} "Startmenü" LangString ^Desktop $${LANG_GERMAN} "Desktop" LangString ^QuickLaunchBar $${LANG_GERMAN} "Schnellstartleiste" LangString ^UninstConfig $${LANG_GERMAN} "Konfigurationsdatei löschen." From e3255d1b92f932009565b118762c5ca039532098 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 17:17:39 +0000 Subject: [PATCH 104/142] Move controlled room messages to messages.py --- syncplay/client.py | 9 ++++----- syncplay/messages.py | 16 +++++++++++++++- syncplay/ui/gui.py | 14 +++++++------- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 2d42674..1c34c04 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -445,8 +445,7 @@ class SyncplayClient(object): @requireMinServerVersion("1.3.0") def createControlledRoom(self, roomName): - controlPassword = utils.RandomStringGenerator.generate_room_ - password() + controlPassword = utils.RandomStringGenerator.generate_room_password() self.ui.showMessage(u"Attempting to create controlled room '{}' with password '{}'...".format(roomName, controlPassword)) self.lastControlPasswordAttempt = controlPassword self._protocol.requestControlledRoom(roomName, controlPassword) @@ -467,18 +466,18 @@ class SyncplayClient(object): @requireMinServerVersion("1.3.0") def identifyAsController(self, controlPassword): controlPassword = self.stripControlPassword(controlPassword) - self.ui.showMessage(u"Identifying as room controller with password '{}'...".format(controlPassword)) + self.ui.showMessage(getMessage("identifying-as-controller-notification").format(controlPassword)) self.lastControlPasswordAttempt = controlPassword self._protocol.requestControlledRoom(self.getRoom(), controlPassword) def controllerIdentificationError(self, username, room): - self.ui.showErrorMessage(u"<{}> failed to identify as a room controller.".format(username)) + self.ui.showErrorMessage(getMessage("failed-to-identify-as-controller-notification").format(username)) def controllerIdentificationSuccess(self, username, roomname): self.userlist.setUserAsController(username) if self.userlist.isRoomSame(roomname): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD - self.ui.showMessage(u"<{}> authenticated as a room controller".format(username), hideFromOSD) + self.ui.showMessage(getMessage("authenticated-as-controller-notification").format(username), hideFromOSD) if username == self.userlist.currentUser.username: self.storeControlPassword(roomname, self.lastControlPasswordAttempt) self.ui.userListChange() diff --git a/syncplay/messages.py b/syncplay/messages.py index b5ad9a6..7d6d481 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -31,8 +31,12 @@ en = { "playing-notification" : "<{}> is playing '{}' ({})", # User, file, duration "playing-notification/room-addendum" : " in room: '{}'", # Room + "identifying-as-controller-notification" : u"Identifying as room controller with password '{}'...", + "failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room controller.", + "authenticated-as-controller-notification" : u"<{}> authenticated as a room controller", + "file-different-notification" : "File you are playing appears to be different from <{}>'s", # User - "file-differences-notification" : "Your file differs in the following way(s): ", + "file-differences-notification" : "Your file differs in the following way(s): ", # controlPassword "room-files-not-same" : "Not all files played in the room are the same", "alone-in-the-room": "You're alone in the room", @@ -188,12 +192,22 @@ en = { "exit-menu-label" : "E&xit", "advanced-menu-label" : "&Advanced", "setoffset-menu-label" : "Set &offset", + "createcontrolledroom-menu-label" : "&Create controlled room", + "identifyascontroller-menu-label" : "&Identify as room controller", + "help-menu-label" : "&Help", "userguide-menu-label" : "Open user &guide", "setoffset-msgbox-label" : "Set offset", "offsetinfo-msgbox-label" : "Offset (see http://syncplay.pl/guide/ for usage instructions):", + + "createcontrolledroom-msgbox-label" : "Create controlled room", + "controlledroominfo-msgbox-label" : "Enter name of controlled room\r\n(see http://syncplay.pl/guide/ for usage instructions):", + + "identifyascontroller-msgbox-label" : "Identify as Room Controller", + "identifyinfo-msgbox-label" : "Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):", + "megabyte-suffix" : " MB", # Tooltips diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 2a9e39e..c1765fc 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -364,16 +364,16 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient._player.openFile(fileName) def createControlledRoom(self): - controlroom, ok = QtGui.QInputDialog.getText(self, "Create controlled room", - "Enter name of controlled room\r\n(see http://syncplay.pl/guide/ for usage instructions):", QtGui.QLineEdit.Normal, + controlroom, ok = QtGui.QInputDialog.getText(self, getMessage("createcontrolledroom-msgbox-label"), + getMessage("controlledroominfo-msgbox-label"), QtGui.QLineEdit.Normal, utils.stripRoomName(self._syncplayClient.getRoom())) if ok and controlroom != '': self._syncplayClient.createControlledRoom(controlroom) def identifyAsController(self): - tooltip = "Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):" - name = "Identify as Room Controller" - controlpassword, ok = QtGui.QInputDialog.getText(self, name, tooltip, QtGui.QLineEdit.Normal, "") + msgboxtitle = getMessage("identifyascontroller-msgbox-label") + msgboxtext = getMessage("identifyinfo-msgbox-label") + controlpassword, ok = QtGui.QInputDialog.getText(self, msgboxtitle, msgboxtext, QtGui.QLineEdit.Normal, "") if ok and controlpassword != '': self._syncplayClient.identifyAsController(controlpassword) @@ -568,10 +568,10 @@ class MainWindow(QtGui.QMainWindow): window.setoffsetAction.triggered.connect(self.setOffset) window.createcontrolledroomAction = window.advancedMenu.addAction( - QtGui.QIcon(self.resourcespath + 'page_white_key.png'), "&Create controlled room suffix") + QtGui.QIcon(self.resourcespath + 'page_white_key.png'), getMessage("createcontrolledroom-menu-label")) window.createcontrolledroomAction.triggered.connect(self.createControlledRoom) window.identifyascontroller = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'key_go.png'), - "&Identify as room controller") + getMessage("identifyascontroller-menu-label")) window.identifyascontroller.triggered.connect(self.identifyAsController) window.menuBar.addMenu(window.advancedMenu) From c8e3426547203a860d519572405c713e3ad985c8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 17:30:29 +0000 Subject: [PATCH 105/142] Improve version checking --- syncplay/client.py | 6 +++--- syncplay/constants.py | 1 + syncplay/server.py | 4 ++-- syncplay/utils.py | 7 +++++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 1c34c04..d8b1684 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -436,14 +436,14 @@ class SyncplayClient(object): def requireMinVersionDecorator(f): @wraps(f) def wrapper(self, *args, **kwds): - if int(self.serverVersion.replace(".", "")) < int(minVersion.replace(".", "")): + if not utils.meetsMinVersion(self.serverVersion,minVersion): self.ui.showErrorMessage(u"This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.".format(minVersion, self.serverVersion)) return return f(self, *args, **kwds) return wrapper return requireMinVersionDecorator - @requireMinServerVersion("1.3.0") + @requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) def createControlledRoom(self, roomName): controlPassword = utils.RandomStringGenerator.generate_room_password() self.ui.showMessage(u"Attempting to create controlled room '{}' with password '{}'...".format(roomName, controlPassword)) @@ -463,7 +463,7 @@ class SyncplayClient(object): else: return "" - @requireMinServerVersion("1.3.0") + @requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) def identifyAsController(self, controlPassword): controlPassword = self.stripControlPassword(controlPassword) self.ui.showMessage(getMessage("identifying-as-controller-notification").format(controlPassword)) diff --git a/syncplay/constants.py b/syncplay/constants.py index 3ad462b..265921c 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -52,6 +52,7 @@ COMMANDS_AUTH = ['a','auth'] MPC_MIN_VER = "1.6.4" VLC_MIN_VERSION = "2.0.0" VLC_INTERFACE_MIN_VERSION = "0.2.1" +CONTROLLED_ROOMS_MIN_VERSION = "1.3.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/server.py b/syncplay/server.py index f2cad52..221b262 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 +from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion class SyncFactory(Factory): def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None): @@ -42,7 +42,7 @@ class SyncFactory(Factory): def getMotd(self, userIp, username, room, clientVersion): oldClient = False if constants.WARN_OLD_CLIENTS: - if int(clientVersion.replace(".", "")) < int(constants.RECENT_CLIENT_THRESHOLD.replace(".", "")): + if not meetsMinVersion(clientVersion, constants.RECENT_CLIENT_THRESHOLD): oldClient = True if self._motdFilePath and os.path.isfile(self._motdFilePath): tmpl = codecs.open(self._motdFilePath, "r", "utf-8-sig").read() diff --git a/syncplay/utils.py b/syncplay/utils.py index 9c2715d..f787c9a 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -210,6 +210,13 @@ def sameFileduration (duration1, duration2): else: return False +def meetsMinVersion(version, minVersion): + def versiontotuple(ver): + return tuple(map(int, ver.split("."))) + versionTuple = versiontotuple(version) + minVersionTuple = versiontotuple(minVersion) + return versionTuple >= minVersionTuple + class RoomPasswordProvider(object): CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$") PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}") From 6143786b7459fbdc2b295ebc6b5ecdd8e197615d Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 17:34:16 +0000 Subject: [PATCH 106/142] Simply version check util --- syncplay/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/syncplay/utils.py b/syncplay/utils.py index f787c9a..d3ab921 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -213,9 +213,7 @@ def sameFileduration (duration1, duration2): def meetsMinVersion(version, minVersion): def versiontotuple(ver): return tuple(map(int, ver.split("."))) - versionTuple = versiontotuple(version) - minVersionTuple = versiontotuple(minVersion) - return versionTuple >= minVersionTuple + return versiontotuple(version) >= versiontotuple(minVersion) class RoomPasswordProvider(object): CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$") From 9574a1887c794065c6257442d12a8829b5131032 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 18:17:55 +0000 Subject: [PATCH 107/142] Don't limit NotController colour to you --- syncplay/ui/gui.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index bca3a43..c028890 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -160,8 +160,7 @@ class MainWindow(QtGui.QMainWindow): if currentUser.username == user.username: font.setWeight(QtGui.QFont.Bold) if isControlledRoom and not isController: - if currentUser.username == user.username: - useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) + useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) useritem.setFont(font) useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable) From ba9058b244ddbb5157f088e72023e081afb18203 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 18:44:06 +0000 Subject: [PATCH 108/142] Don't have OSD fileDifferent for non-controllers on controlled rooms --- syncplay/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 1ee7968..dd610ed 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -692,7 +692,8 @@ class SyncplayUserlist(object): def areAllFilesInRoomSame(self): for user in self._users.itervalues(): if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file): - return False + if user.isController() or not utils.RoomPasswordProvider.isControlledRoom(user.room): + return False return True def areYouAloneInRoom(self): From 5d2a33341e15a65896772982241813f66c0f42c9 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 18:57:16 +0000 Subject: [PATCH 109/142] Remove showbuttonlabels and showtooltips config options --- syncplay/messages.py | 4 ---- syncplay/ui/ConfigurationGetter.py | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 6bf191a..ae64f18 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -153,8 +153,6 @@ en = { "showdifferentroomosd-label" : "Include events in other rooms", "showslowdownosd-label" :"Include slowing down / reverting notification", "showcontactinfo-label" : "Show contact info box", - "showbuttonlabels-label" : "Show labels on buttons", - "showtooltips-label" : "Show tooltips", "showdurationnotification-label" : "Warn about media duration mismatches", "basics-label" : "Basics", "sync-label" : "Sync", @@ -481,8 +479,6 @@ ru = { "showdifferentroomosd-label" : u"Показывать события других комнат", "showslowdownosd-label" : u"Показывать уведомления о замедлении/перемотке", "showcontactinfo-label" : u"Отображать контактную информацию разработчиков", - "showbuttonlabels-label" : u"Отображать надписи на кнопках", - "showtooltips-label" : u"Отображать всплывающие подсказки", "showdurationnotification-label" : u"Предупреждать о несовпадении продолжительности видео", "basics-label" : u"Основное", "sync-label" : u"Синхронизация", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 3f330cb..01ff417 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -51,8 +51,6 @@ class ConfigurationGetter(object): "showDifferentRoomOSD" : False, "showSameRoomOSD" : True, "showContactInfo" : True, - "showButtonLabels" : True, - "showTooltips" : True, "showDurationNotification" : True } @@ -88,8 +86,6 @@ class ConfigurationGetter(object): "showDifferentRoomOSD", "showSameRoomOSD", "showContactInfo" , - "showButtonLabels", - "showTooltips", "showDurationNotification" ] @@ -101,7 +97,7 @@ class ConfigurationGetter(object): self._iniStructure = { "server_data": ["host", "port", "password"], "client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "slowOnDesync", "rewindOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave"], - "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showContactInfo" , "showButtonLabels", "showTooltips", "showDurationNotification"], + "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showContactInfo" , "showDurationNotification"], "general": ["language"] } From ae75750a6d2df11d0fdf622f7239727be2af6543 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 18:58:48 +0000 Subject: [PATCH 110/142] Remove showbuttonlabels and showtooltips from GUI --- syncplay/ui/GuiConfiguration.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 2bbda7a..6721a89 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -553,15 +553,6 @@ class ConfigDialog(QtGui.QDialog): self.showcontactinfoCheckbox.setObjectName("showContactInfo") self.displaySettingsLayout.addWidget(self.showcontactinfoCheckbox) - self.showButtonLabelsCheckbox = QCheckBox(getMessage("showbuttonlabels-label")) - self.showButtonLabelsCheckbox.setObjectName("showButtonLabels") - self.displaySettingsLayout.addWidget(self.showButtonLabelsCheckbox) - - self.showTooltipsCheckbox = QCheckBox(getMessage("showtooltips-label")) - self.showTooltipsCheckbox.setObjectName("showTooltips") - self.displaySettingsLayout.addWidget(self.showTooltipsCheckbox) - - self.displaySettingsGroup.setLayout(self.displaySettingsLayout) self.displaySettingsLayout.setAlignment(Qt.AlignTop) self.messageLayout.addWidget(self.displaySettingsGroup) From 3130cc93d567b3ec162847a2b38a59b3e7dcda83 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 19:09:07 +0000 Subject: [PATCH 111/142] Remove ref to showButtonLabels --- syncplay/ui/gui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c028890..e44d262 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -73,7 +73,6 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient = client self.roomInput.setText(self._syncplayClient.getRoom()) self.config = self._syncplayClient.getConfig() - constants.SHOW_BUTTON_LABELS = self.config['showButtonLabels'] try: if self.contactLabel and not self.config['showContactInfo']: self.contactLabel.hide() From d2bd94a1df5a2bd2943fd47b87c2e8e849d7110b Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 19:12:57 +0000 Subject: [PATCH 112/142] Remove max width on play/pause buttons --- syncplay/ui/gui.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index e44d262..59ec007 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -534,12 +534,10 @@ class MainWindow(QtGui.QMainWindow): window.playButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_play_blue.png'), getMessage("play-guibuttonlabel")) window.playButton.pressed.connect(self.play) - window.playButton.setMaximumWidth(60) window.miscLayout.addWidget(window.playButton) window.pauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'), getMessage("pause-guibuttonlabel")) window.pauseButton.pressed.connect(self.pause) - window.pauseButton.setMaximumWidth(60) window.miscLayout.addWidget(window.pauseButton) self.playButton.setToolTip(getMessage("play-tooltip")) self.pauseButton.setToolTip(getMessage("pause-tooltip")) From 89384b73db8e3737be4e431007a12f5678f7648e Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 23:24:02 +0000 Subject: [PATCH 113/142] Add option to jump forward if lagging behind room controller (default on) --- syncplay/client.py | 10 ++++++++ syncplay/constants.py | 3 +++ syncplay/messages.py | 5 ++++ syncplay/ui/ConfigurationGetter.py | 7 ++++-- syncplay/ui/GuiConfiguration.py | 39 ++++++++++++++++++++++++++---- 5 files changed, 57 insertions(+), 7 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index dd610ed..d9833fd 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -190,6 +190,13 @@ class SyncplayClient(object): madeChangeOnPlayer = True return madeChangeOnPlayer + def _fastforwardPlayerDueToTimeDifference(self, position, setBy): + hideFromOSD = not constants.SHOW_SAME_ROOM_OSD + self.setPosition(position + constants.FASTFORWARD_EXTRA_TIME) + self.ui.showMessage(getMessage("fastforward-notification").format(setBy), hideFromOSD) + madeChangeOnPlayer = True + return madeChangeOnPlayer + def _serverUnpaused(self, setBy): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD self._player.setPaused(False) @@ -247,6 +254,9 @@ class SyncplayClient(object): madeChangeOnPlayer = self._serverSeeked(position, setBy) if diff > self._config['rewindThreshold'] and not doSeek and not self._config['rewindOnDesync'] == False: madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy) + if diff < (self._config['fastforwardThreshold'] * -1) and not doSeek and not self._config['fastforwardOnDesync'] == False: + if utils.RoomPasswordProvider.isControlledRoom(self.getRoom()) and not self.userlist.currentUser.isController(): + madeChangeOnPlayer = self._fastforwardPlayerDueToTimeDifference(position, setBy) if self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False: madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy) if paused == False and pauseChanged: diff --git a/syncplay/constants.py b/syncplay/constants.py index e55afd3..491fbdd 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -28,6 +28,9 @@ DEBUG_MODE = False #Changing these might be ok DEFAULT_REWIND_THRESHOLD = 4 MINIMUM_REWIND_THRESHOLD = 3 +DEFAULT_FASTFORWARD_THRESHOLD = 5 +MINIMUM_FASTFORWARD_THRESHOLD = 2 +FASTFORWARD_EXTRA_TIME = 0.25 SEEK_THRESHOLD = 1 SLOWDOWN_RATE = 0.95 DEFAULT_SLOWDOWN_KICKIN_THRESHOLD = 1.5 diff --git a/syncplay/messages.py b/syncplay/messages.py index ae64f18..ad27fa4 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -16,6 +16,7 @@ en = { "retrying-notification" : "%s, Retrying in %d seconds...", # Seconds "rewind-notification" : "Rewinded due to time difference with <{}>", # User + "fastforward-notification" : "Fast-forwarded due to time difference with <{}>", # User "slowdown-notification" : "Slowing down due to time difference with <{}>", # User "revert-notification" : "Reverting speed back to normal", @@ -134,6 +135,7 @@ en = { "more-title" : "Show more settings", "slowdown-threshold-label" : "Slow down threshold:", "rewind-threshold-label" : "Rewind threshold:", + "fastforward-threshold-label" : "Fast-forward threshold:", "never-rewind-value" : "Never", "seconds-suffix" : " secs", "privacy-sendraw-option" : "Send raw", @@ -226,6 +228,7 @@ en = { "more-tooltip" : "Display less frequently used settings.", "slowdown-threshold-tooltip" : "Time ahead of slowest client before temporarily reducing playback speed (default: {} secs).".format(constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD), "rewind-threshold-tooltip" : "Time ahead slowest client before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_REWIND_THRESHOLD), + "fastforward-threshold-tooltip" : "Time behind room controller before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_FASTFORWARD_THRESHOLD), "filename-privacy-tooltip" : "Privacy mode for sending currently playing filename to server.", "filesize-privacy-tooltip" : "Privacy mode for sending size of currently playing file to server.", "privacy-sendraw-tooltip" : "Send this information without obfuscation. This is the default option with most functionality.", @@ -235,9 +238,11 @@ en = { "dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging.", "pauseonleave-tooltip" : "Pause playback if you get disconnected or someone leaves from your room.", "rewindondesync-label" : "Rewind on major desync (highly recommended)", + "fastforwardondesync-label" : "Fast-forward if lagging behind room controller (recommended)", "forceguiprompt-tooltip" : "Configuration dialogue is not shown when opening a file with Syncplay.", # (Inverted) "nostore-tooltip" : "Run Syncplay with the given configuration, but do not permanently store the changes.", # (Inverted) "rewindondesync-tooltip" : "Jump back when needed to get back in sync. Disabling this option can result in major desyncs!", + "fastforwardondesync-tooltip" : "Jump forward when needed to get back in sync. Disabling this option can result in major desyncs!", "showosd-tooltip" : "Sends Syncplay messages to media player OSD.", "showosdwarnings-tooltip" : "Show warnings if playing different file, alone in room.", "showsameroomosd-tooltip" : "Show OSD notifications for events relating to room user is in.", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 01ff417..e85b245 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -36,8 +36,10 @@ class ConfigurationGetter(object): "playerClass": None, "slowdownThreshold": constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD, "rewindThreshold": constants.DEFAULT_REWIND_THRESHOLD, + "fastforwardThreshold": constants.DEFAULT_FASTFORWARD_THRESHOLD, "rewindOnDesync": True, "slowOnDesync": True, + "fastforwardOnDesync": True, "dontSlowDownWithMe": False, "filenamePrivacyMode": constants.PRIVACY_SENDRAW_MODE, "filesizePrivacyMode": constants.PRIVACY_SENDRAW_MODE, @@ -91,12 +93,13 @@ class ConfigurationGetter(object): self._numeric = [ "slowdownThreshold", - "rewindThreshold" + "rewindThreshold", + "fastforwardThreshold", ] self._iniStructure = { "server_data": ["host", "port", "password"], - "client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "slowOnDesync", "rewindOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave"], + "client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showContactInfo" , "showDurationNotification"], "general": ["language"] } diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 6721a89..1ed195a 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -15,7 +15,6 @@ class GuiConfiguration: self._availablePlayerPaths = [] self.error = error - def run(self): if QCoreApplication.instance() is None: self.app = QtGui.QApplication(sys.argv) @@ -206,8 +205,11 @@ class ConfigDialog(QtGui.QDialog): self.slowdownThresholdSpinbox.value = constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD if not self.rewindThresholdSpinbox.text: self.rewindThresholdSpinbox.value = constants.DEFAULT_REWIND_THRESHOLD + if not self.fastforwardThresholdSpinbox.text: + self.fastforwardThresholdSpinbox.value = constants.DEFAULT_FASTFORWARD_THRESHOLD self.config['slowdownThreshold'] = self.slowdownThresholdSpinbox.value() self.config['rewindThreshold'] = self.rewindThresholdSpinbox.value() + self.config['fastforwardThreshold'] = self.fastforwardThresholdSpinbox.value() self.pressedclosebutton = True self.close() @@ -415,6 +417,8 @@ class ConfigDialog(QtGui.QDialog): self.slowdownCheckbox.setObjectName("slowOnDesync") self.rewindCheckbox = QCheckBox(getMessage("rewindondesync-label")) self.rewindCheckbox.setObjectName("rewindOnDesync") + self.fastforwardCheckbox = QCheckBox(getMessage("fastforwardondesync-label")) + self.fastforwardCheckbox.setObjectName("fastforwardOnDesync") self.spaceLabel = QLabel() self.spaceLabel.setFixedHeight(5) @@ -458,6 +462,22 @@ class ConfigDialog(QtGui.QDialog): self.rewindThresholdSpinbox.setSuffix(getMessage("seconds-suffix")) self.rewindThresholdSpinbox.adjustSize() + self.fastforwardThresholdLabel = QLabel(getMessage("fastforward-threshold-label"), self) + self.fastforwardThresholdLabel.setStyleSheet(constants.STYLE_SUBLABEL.format(self.posixresourcespath + "chevrons_right.png")) + self.fastforwardThresholdSpinbox = QDoubleSpinBox() + try: + fastforwardThreshold = float(config['fastforwardThreshold']) + self.fastforwardThresholdSpinbox.setValue(fastforwardThreshold) + if fastforwardThreshold < constants.MINIMUM_FASTFORWARD_THRESHOLD: + constants.MINIMUM_FASTFORWARD_THRESHOLD = fastforwardThreshold + except ValueError: + self.fastforwardThresholdSpinbox.setValue(constants.DEFAULT_FASTFORWARD_THRESHOLD) + self.fastforwardThresholdSpinbox.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) + self.fastforwardThresholdSpinbox.setMinimum(constants.MINIMUM_FASTFORWARD_THRESHOLD) + self.fastforwardThresholdSpinbox.setSingleStep(0.1) + self.fastforwardThresholdSpinbox.setSuffix(getMessage("seconds-suffix")) + self.fastforwardThresholdSpinbox.adjustSize() + self.slowdownThresholdLabel.setObjectName("slowdown-threshold") self.slowdownThresholdSpinbox.setObjectName("slowdown-threshold") self.rewindThresholdLabel.setObjectName("rewind-threshold") @@ -484,14 +504,23 @@ class ConfigDialog(QtGui.QDialog): self.othersyncOptionsFrame = QtGui.QFrame() self.othersyncSettingsLayout = QtGui.QGridLayout() - self.dontslowwithmeCheckbox = QCheckBox(getMessage("dontslowdownwithme-label")) - self.pauseonleaveCheckbox = QCheckBox(getMessage("pauseonleave-label")) - self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox) - self.othersyncSettingsLayout.addWidget(self.pauseonleaveCheckbox) self.dontslowwithmeCheckbox.setObjectName("dontSlowDownWithMe") + + self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox, 1, 0, 1, 2, Qt.AlignLeft) + + self.pauseonleaveCheckbox = QCheckBox(getMessage("pauseonleave-label")) + self.othersyncSettingsLayout.addWidget(self.pauseonleaveCheckbox, 2, 0, 1, 2, Qt.AlignLeft) self.pauseonleaveCheckbox.setObjectName("pauseOnLeave") + self.fastforwardThresholdLabel.setObjectName("fastforward-threshold") + self.fastforwardThresholdSpinbox.setObjectName("fastforward-threshold") + + self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 3, 0,1,2, Qt.AlignLeft) + self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdLabel, 4, 0, 1, 1, Qt.AlignLeft) + self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdSpinbox, 4, 1, Qt.AlignLeft) + self.subitems['fastforwardOnDesync'] = ["fastforward-threshold"] + self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout) self.syncSettingsLayout.addWidget(self.othersyncSettingsGroup) From dd51106be3513148f480ec729daf225334b0af84 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 29 Nov 2014 23:33:03 +0000 Subject: [PATCH 114/142] Note that don't slow with me option is useful for room controllers --- syncplay/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index ad27fa4..5ecc3db 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -235,7 +235,7 @@ en = { "privacy-sendhashed-tooltip" : "Send a hashed version of the information, making it less visible to other clients.", "privacy-dontsend-tooltip" : "Do not send this information to the server. This provides for maximum privacy.", "slowondesync-tooltip" : "Reduce playback rate temporarily when needed to bring you back in sync with other viewers. Not supported on MPC-HC.", - "dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging.", + "dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging. Useful for room controllers.", "pauseonleave-tooltip" : "Pause playback if you get disconnected or someone leaves from your room.", "rewindondesync-label" : "Rewind on major desync (highly recommended)", "fastforwardondesync-label" : "Fast-forward if lagging behind room controller (recommended)", From a338ebe95fb1ad76fa4efd61c0dcfc55c5a43e4a Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 01:22:21 +0000 Subject: [PATCH 115/142] Allow changing of language from within GuiConfig --- syncplay/messages.py | 27 +++++++++++++++++++++------ syncplay/ui/GuiConfiguration.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 5ecc3db..7f2fef8 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -2,7 +2,8 @@ from syncplay import constants en = { - + "LANGUAGE" : "English", + # Client notifications "config-cleared-notification" : "Settings cleared. Changes will be saved when you store a valid configuration.", @@ -64,6 +65,7 @@ en = { "more-info-notification" : "More info available at: {}", # projectURL "gui-data-cleared-notification" : "Syncplay has cleared the path and window state data used by the GUI.", + "language-changed-msgbox-label" : "Language will be changed when you run Syncplay.", "vlc-version-mismatch": "Warning: You are running VLC version {}, but Syncplay is designed to run on VLC {} and above.", # VLC version, VLC min version "vlc-interface-version-mismatch": "Warning: You are running version {} of the Syncplay interface module for VLC, but Syncplay is designed to run with version {} and above.", # VLC interface version, VLC interface min version @@ -155,6 +157,7 @@ en = { "showdifferentroomosd-label" : "Include events in other rooms", "showslowdownosd-label" :"Include slowing down / reverting notification", "showcontactinfo-label" : "Show contact info box", + "language-label" : "Language", "showdurationnotification-label" : "Warn about media duration mismatches", "basics-label" : "Basics", "sync-label" : "Sync", @@ -252,6 +255,7 @@ en = { "showbuttonlabels-tooltip" : "Show the text alongside the icons for buttons in the main UI.", "showtooltips-tooltip" : "Show tooltip help messages when you mouseover an input element in Syncplay.", "showdurationnotification-tooltip" : "Useful for when a segment in a multi-part file is missing, but can result in false positives.", + "language-tooltip" : u"Language to be used by Syncplay.", "help-tooltip" : "Opens the Syncplay.pl user guide.", "reset-tooltip" : "Reset all settings to the default configuration.", @@ -302,6 +306,8 @@ en = { pl = { + "LANGUAGE" : "Polski", # (Polish) + # Client notifications "connection-attempt-notification" : u"Próba połączenia z {}:{}", # Port, IP "reconnection-attempt-notification" : u"Połączenie z serwerem zostało przerwane, ponowne łączenie", @@ -338,6 +344,7 @@ pl = { } ru = { + "LANGUAGE" : u"Русский", # (Russian) # Client notifications "config-cleared-notification" : u"Настройки сброшены. Изменения вступят в силу при сохранении корректной конфигурации.", @@ -603,6 +610,7 @@ ru = { } de = { + "LANGUAGE" : u"Deutsch", # (German) # Client notifications "relative-config-notification" : u"Relative Konfigurationsdatei(en) geladen: {}", @@ -794,22 +802,29 @@ de = { "version-mismatch-server-error" : u"Verschiedene Versionen auf Client und Server", "wrong-password-server-error" : u"Ungültiges Passwort" } - + messages = { "en": en, "pl": pl, "ru": ru, "de": de, - "current": None + "CURRENT": None } +def getLanguages(): + langList = {} + for lang in messages: + if lang != "CURRENT": + langList[lang] = getMessage("LANGUAGE", lang) + return langList + def setLanguage(lang): - messages["current"] = lang + messages["CURRENT"] = lang def getMissingStrings(): missingStrings = "" for lang in messages: - if lang != "en" and lang != "current": + if lang != "en" and lang != "CURRENT": for message in messages["en"]: if not messages[lang].has_key(message): missingStrings = missingStrings + "({}) Missing: {}\n".format(lang, message) @@ -822,7 +837,7 @@ def getMessage(type_, locale=None): if constants.SHOW_TOOLTIPS == False: if "-tooltip" in type_: return "" - lang = messages["current"] + lang = messages["CURRENT"] if locale and messages.has_key(locale): if messages[locale].has_key(type_): return unicode(messages[locale][type_]) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 1ed195a..45a79a2 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -5,7 +5,7 @@ from syncplay.players.playerFactory import PlayerFactory import os import sys -from syncplay.messages import getMessage +from syncplay.messages import getMessage, getLanguages, setLanguage from syncplay import constants class GuiConfiguration: @@ -112,6 +112,9 @@ class ConfigDialog(QtGui.QDialog): else: self.executableiconLabel.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage())) + def languageChanged(self): + setLanguage(unicode(self.languageCombobox.itemData(self.languageCombobox.currentIndex()))) + QtGui.QMessageBox.information(self, "Syncplay", getMessage("language-changed-msgbox-label")) def browsePlayerpath(self): options = QtGui.QFileDialog.Options() @@ -194,6 +197,7 @@ class ConfigDialog(QtGui.QDialog): else: self.config['host'] = None self.config['playerPath'] = unicode(self.executablepathCombobox.currentText()) + self.config['language'] = unicode(self.languageCombobox.itemData(self.languageCombobox.currentIndex())) if self.mediapathTextbox.text() == "": self.config['file'] = None elif os.path.isfile(os.path.abspath(self.mediapathTextbox.text())): @@ -572,6 +576,7 @@ class ConfigDialog(QtGui.QDialog): self.displaySettingsGroup = QtGui.QGroupBox(getMessage("messages-other-title")) self.displaySettingsLayout = QtGui.QVBoxLayout() + self.displaySettingsLayout.setAlignment(Qt.AlignTop) self.displaySettingsFrame = QtGui.QFrame() self.showDurationNotificationCheckbox = QCheckBox(getMessage("showdurationnotification-label")) @@ -582,6 +587,27 @@ class ConfigDialog(QtGui.QDialog): self.showcontactinfoCheckbox.setObjectName("showContactInfo") self.displaySettingsLayout.addWidget(self.showcontactinfoCheckbox) + self.languageFrame = QtGui.QFrame() + self.languageFrame.setContentsMargins(0,0,0,0) + self.languageLayout = QtGui.QHBoxLayout() + self.languageLayout.setSpacing(0) + self.languageLayout.setAlignment(Qt.AlignTop) + self.languageLabel = QLabel(getMessage("language-label"), self) + self.languageCombobox = QtGui.QComboBox(self) + self.languages = getLanguages() + for lang in self.languages: + self.languageCombobox.addItem(self.languages[lang], lang) + if lang == self.config['language']: + self.languageCombobox.setCurrentIndex(self.languageCombobox.count()-1) + self.languageCombobox.currentIndexChanged.connect(self.languageChanged) + self.languageLayout.addWidget(self.languageLabel) + self.languageLayout.addWidget(self.languageCombobox) + self.languageFrame.setLayout(self.languageLayout) + self.languageFrame.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) + self.displaySettingsLayout.addWidget(self.languageFrame) + self.languageLabel.setObjectName("language") + self.languageCombobox.setObjectName("language") + self.displaySettingsGroup.setLayout(self.displaySettingsLayout) self.displaySettingsLayout.setAlignment(Qt.AlignTop) self.messageLayout.addWidget(self.displaySettingsGroup) @@ -720,6 +746,7 @@ class ConfigDialog(QtGui.QDialog): self.ensureTabListIsVisible() self.setFixedWidth(self.minimumSizeHint().width()) self.executablepathCombobox.setFixedWidth(self.mediapathTextbox.width()) + self.languageLabel.setFixedWidth(self.languageLabel.width()) def clearGUIData(self, leaveMore=False): settings = QSettings("Syncplay", "PlayerList") From 721a8f872ff943fa37a94332a47e1584d12c5147 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 01:43:48 +0000 Subject: [PATCH 116/142] Add unused message detection to missing message detection --- syncplay/messages.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/syncplay/messages.py b/syncplay/messages.py index 7f2fef8..022bc0c 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -828,6 +828,10 @@ def getMissingStrings(): for message in messages["en"]: if not messages[lang].has_key(message): missingStrings = missingStrings + "({}) Missing: {}\n".format(lang, message) + for message in messages[lang]: + if not messages["en"].has_key(message): + missingStrings = missingStrings + "({}) Unused: {}\n".format(lang, message) + return missingStrings def getMessage(type_, locale=None): From 14425374003d7868f18eb5c4125f3a3cb7a06213 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 01:48:10 +0000 Subject: [PATCH 117/142] Fix broken username label/tooltips in German version --- syncplay/messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 022bc0c..2d4126d 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -717,7 +717,7 @@ de = { "connection-group-title" : u"Verbindungseinstellungen", "host-label" : u"Server-Adresse: ", - "username-label" : u"Benutzername (optional):", + "name-label" : u"Benutzername (optional):", "password-label" : u"Server-Passwort (falls nötig):", "room-label" : u"Standard-Raum: ", @@ -749,7 +749,7 @@ de = { # Tooltips "host-tooltip" : u"Hostname oder IP zu der verbunden werden soll. Ptional mit Port (z.B.. syncplay.pl:8999). Synchronisation findet nur mit Personen auf dem selben Server und Port statt.", - "username-tooltip" : u"Dein Benutzername. Keine Registrierung, kann einfach geändert werden. Bei fehlender Angabe wird ein zufälliger Name generiert.", + "name-tooltip" : u"Dein Benutzername. Keine Registrierung, kann einfach geändert werden. Bei fehlender Angabe wird ein zufälliger Name generiert.", "password-tooltip" : u"Passwörter sind nur bei Verbindung zu privaten Servern nötig.", "room-tooltip" : u"Der Raum, der betreten werden soll, kann ein x-beliebiger sein. Allerdings werden nur Clients im selben Raum synchronisiert.", From 3b6b7c4bbe44ea2acf9a97ade17251969a7d3f78 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 01:54:19 +0000 Subject: [PATCH 118/142] Use exit-menu-label --- syncplay/ui/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 59ec007..0bc77f2 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -555,7 +555,7 @@ class MainWindow(QtGui.QMainWindow): window.openAction.triggered.connect(self.browseMediapath) window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), - getMessage("file-menu-label")) + getMessage("exit-menu-label")) window.exitAction.triggered.connect(self.exitSyncplay) window.menuBar.addMenu(window.fileMenu) From b103363e41a684059c1dc9969b3ffa42787f28dd Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 10:00:00 +0000 Subject: [PATCH 119/142] Only trigger mismatch check if someone joins your room or changes file within your room --- syncplay/client.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index d9833fd..5e59412 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -653,7 +653,7 @@ class SyncplayUserlist(object): self._users[username] = user if not noMessage: self.__showUserChangeMessage(username, room, file_) - self.userListChange() + self.userListChange(room) def removeUser(self, username): hideFromOSD = not constants.SHOW_DIFFERENT_ROOM_OSD @@ -690,7 +690,7 @@ class SyncplayUserlist(object): self.__showUserChangeMessage(username, room, file_) else: self.addUser(username, room, file_) - self.userListChange() + self.userListChange(room) def setUserAsController(self, username): if self.currentUser.username == username: @@ -718,8 +718,9 @@ class SyncplayUserlist(object): return True return False - def userListChange(self): - self._roomUsersChanged = True + def userListChange(self, room = None): + if room is not None and self.isRoomSame(room): + self._roomUsersChanged = True self.ui.userListChange() def roomStateConfirmed(self): From f2a5d0242efcd41ee32baf4a70c611def8abd6b6 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 10:45:22 +0000 Subject: [PATCH 120/142] Add default option to exclude events from non-controllers in controlled rooms --- syncplay/client.py | 65 ++++++++++++++++++------------ syncplay/constants.py | 1 + syncplay/messages.py | 2 + syncplay/ui/ConfigurationGetter.py | 4 +- syncplay/ui/GuiConfiguration.py | 5 +++ 5 files changed, 51 insertions(+), 26 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 5e59412..329b774 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -596,6 +596,10 @@ class SyncplayUser(object): def isController(self): return self._controller + def canControl(self): + if self.isController() or not utils.RoomPasswordProvider.isControlledRoom(self.room): + return True + class SyncplayUserlist(object): def __init__(self, ui, client): self.currentUser = SyncplayUser() @@ -616,31 +620,33 @@ class SyncplayUserlist(object): showOnOSD = constants.SHOW_OSD_WARNINGS else: showOnOSD = constants.SHOW_DIFFERENT_ROOM_OSD + if constants.SHOW_NONCONTROLLER_OSD == False and self.canControl(username) == False: + showOnOSD = False hideFromOSD = not showOnOSD - if room and not file_: - message = getMessage("room-join-notification").format(username, room) - self.ui.showMessage(message, hideFromOSD) - elif room and file_: - duration = utils.formatTime(file_['duration']) - message = getMessage("playing-notification").format(username, file_['name'], duration) - if self.currentUser.room <> room or self.currentUser.username == username: - message += getMessage("playing-notification/room-addendum").format(room) - self.ui.showMessage(message, hideFromOSD) - if self.currentUser.file and not self.currentUser.isFileSame(file_) and self.currentUser.room == room: - message = getMessage("file-different-notification").format(username) - self.ui.showMessage(message, not constants.SHOW_OSD_WARNINGS) - differences = [] - differentName = not utils.sameFilename(self.currentUser.file['name'], file_['name']) - differentSize = not utils.sameFilesize(self.currentUser.file['size'], file_['size']) - differentDuration = not utils.sameFileduration(self.currentUser.file['duration'], file_['duration']) - if differentName: - differences.append("filename") - if differentSize: - differences.append("size") - if differentDuration: - differences.append("duration") - message = getMessage("file-differences-notification") + ", ".join(differences) - self.ui.showMessage(message, not constants.SHOW_OSD_WARNINGS) + if not file_: + message = getMessage("room-join-notification").format(username, room) + self.ui.showMessage(message, hideFromOSD) + else: + duration = utils.formatTime(file_['duration']) + message = getMessage("playing-notification").format(username, file_['name'], duration) + if self.currentUser.room <> room or self.currentUser.username == username: + message += getMessage("playing-notification/room-addendum").format(room) + self.ui.showMessage(message, hideFromOSD) + if self.currentUser.file and not self.currentUser.isFileSame(file_) and self.currentUser.room == room: + message = getMessage("file-different-notification").format(username) + self.ui.showMessage(message, hideFromOSD) + differences = [] + differentName = not utils.sameFilename(self.currentUser.file['name'], file_['name']) + differentSize = not utils.sameFilesize(self.currentUser.file['size'], file_['size']) + differentDuration = not utils.sameFileduration(self.currentUser.file['duration'], file_['duration']) + if differentName: + differences.append("filename") + if differentSize: + differences.append("size") + if differentDuration: + differences.append("duration") + message = getMessage("file-differences-notification") + ", ".join(differences) + self.ui.showMessage(message, hideFromOSD) def addUser(self, username, room, file_, noMessage=False, isController=None): if username == self.currentUser.username: @@ -702,7 +708,7 @@ class SyncplayUserlist(object): def areAllFilesInRoomSame(self): for user in self._users.itervalues(): if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file): - if user.isController() or not utils.RoomPasswordProvider.isControlledRoom(user.room): + if user.canControl(): return False return True @@ -718,6 +724,15 @@ class SyncplayUserlist(object): return True return False + def canControl(self, username): + if self.currentUser.username == username and self.currentUser.canControl(): + return True + + for user in self._users.itervalues(): + if user.username == username and user.canControl(): + return True + return False + def userListChange(self, room = None): if room is not None and self.isRoomSame(room): self._roomUsersChanged = True diff --git a/syncplay/constants.py b/syncplay/constants.py index 491fbdd..caddbaa 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -21,6 +21,7 @@ SHOW_OSD = True # Sends Syncplay messages to media player OSD SHOW_OSD_WARNINGS = True # Show warnings if playing different file, alone in room SHOW_SLOWDOWN_OSD = True # Show notifications of slowing down / reverting on time difference SHOW_SAME_ROOM_OSD = True # Show OSD notifications for events relating to room user is in +SHOW_NONCONTROLLER_OSD = False # Show OSD notifications for non-controllers in controlled rooms SHOW_DIFFERENT_ROOM_OSD = False # Show OSD notifications for events relating to room user is not in SHOW_DURATION_NOTIFICATION = True DEBUG_MODE = False diff --git a/syncplay/messages.py b/syncplay/messages.py index 2d4126d..82e244b 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -154,6 +154,7 @@ en = { "showosdwarnings-label" : "Include warnings (e.g. when files are different)", "showsameroomosd-label" : "Include events in your room", + "shownoncontrollerosd-label" : "Include events from non-controllers in controlled rooms", "showdifferentroomosd-label" : "Include events in other rooms", "showslowdownosd-label" :"Include slowing down / reverting notification", "showcontactinfo-label" : "Show contact info box", @@ -249,6 +250,7 @@ en = { "showosd-tooltip" : "Sends Syncplay messages to media player OSD.", "showosdwarnings-tooltip" : "Show warnings if playing different file, alone in room.", "showsameroomosd-tooltip" : "Show OSD notifications for events relating to room user is in.", + "shownoncontrollerosd-tooltip" : "Show OSD notifications for events relating to non-controllers who are in controllerd rooms.", "showdifferentroomosd-tooltip" : "Show OSD notifications for events relating to room user is not in.", "showslowdownosd-tooltip" :"Show notifications of slowing down / reverting on time difference.", "showcontactinfo-tooltip" : "Show information box about contacting Syncplay developers in main Syncplay window.", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index e85b245..37d18f4 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -52,6 +52,7 @@ class ConfigurationGetter(object): "showSlowdownOSD" : True, "showDifferentRoomOSD" : False, "showSameRoomOSD" : True, + "showNonControllerOSD" : False, "showContactInfo" : True, "showDurationNotification" : True } @@ -87,6 +88,7 @@ class ConfigurationGetter(object): "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", + "showNonControllerOSD", "showContactInfo" , "showDurationNotification" ] @@ -100,7 +102,7 @@ class ConfigurationGetter(object): self._iniStructure = { "server_data": ["host", "port", "password"], "client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave"], - "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showContactInfo" , "showDurationNotification"], + "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showContactInfo" , "showDurationNotification"], "general": ["language"] } diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 45a79a2..c947183 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -551,6 +551,11 @@ class ConfigDialog(QtGui.QDialog): self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) self.osdSettingsLayout.addWidget(self.showSameRoomOSDCheckbox) + self.showNonControllerOSDCheckbox = QCheckBox(getMessage("shownoncontrollerosd-label")) + self.showNonControllerOSDCheckbox.setObjectName("showNonControllerOSD") + self.showNonControllerOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) + self.osdSettingsLayout.addWidget(self.showNonControllerOSDCheckbox) + self.showDifferentRoomOSDCheckbox = QCheckBox(getMessage("showdifferentroomosd-label")) self.showDifferentRoomOSDCheckbox.setObjectName("showDifferentRoomOSD") self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) From d9dfffefbb5ae51d2fa056d3fb95fba3cd78c51f Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 11:44:43 +0000 Subject: [PATCH 121/142] (Hopefully) Allow URLs as executable path (e.g. for XBMC) --- syncplay/ui/GuiConfiguration.py | 35 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index c947183..23cde3d 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -70,6 +70,10 @@ class ConfigDialog(QtGui.QDialog): def openHelp(self): self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/client/") + def _isURL(self, path): + if "http://" in path: + return True + def _tryToFillPlayerPath(self, playerpath, playerpathlist): settings = QSettings("Syncplay", "PlayerList") settings.beginGroup("PlayerList") @@ -81,25 +85,38 @@ class ConfigDialog(QtGui.QDialog): foundpath = "" if playerpath != None and playerpath != "": - if not os.path.isfile(playerpath): - expandedpath = PlayerFactory().getExpandedPlayerPathByPath(playerpath) - if expandedpath != None and os.path.isfile(expandedpath): - playerpath = expandedpath - - if os.path.isfile(playerpath): + if self._isURL(playerpath): foundpath = playerpath self.executablepathCombobox.addItem(foundpath) + else: + if not os.path.isfile(playerpath): + expandedpath = PlayerFactory().getExpandedPlayerPathByPath(playerpath) + if expandedpath != None and os.path.isfile(expandedpath): + playerpath = expandedpath + + if os.path.isfile(playerpath): + foundpath = playerpath + self.executablepathCombobox.addItem(foundpath) + for path in playerpathlist: - if os.path.isfile(path) and os.path.normcase(os.path.normpath(path)) != os.path.normcase(os.path.normpath(foundpath)): + if self._isURL(playerpath): + foundpath = path + self.executablepathCombobox.addItem(path) + + elif os.path.isfile(path) and os.path.normcase(os.path.normpath(path)) != os.path.normcase(os.path.normpath(foundpath)): self.executablepathCombobox.addItem(path) if foundpath == "": foundpath = path if foundpath != "": settings.beginGroup("PlayerList") - playerpathlist.append(os.path.normcase(os.path.normpath(foundpath))) - settings.setValue("PlayerList", list(set(os.path.normcase(os.path.normpath(path)) for path in set(playerpathlist)))) + if self._isURL(foundpath): + playerpathlist.append(foundpath) + settings.setValue("PlayerList", list(set(path) for path in set(playerpathlist))) + else: + playerpathlist.append(os.path.normcase(os.path.normpath(foundpath))) + settings.setValue("PlayerList", list(set(os.path.normcase(os.path.normpath(path)) for path in set(playerpathlist)))) settings.endGroup() return foundpath From 30b28afa14c4507a26e5d81631c6e5b7eb14481d Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 12:01:33 +0000 Subject: [PATCH 122/142] Allow players to have custom open dialogs (e.g. for XBMC) --- syncplay/players/basePlayer.py | 8 +++++++- syncplay/players/mpc.py | 1 + syncplay/players/mplayer.py | 1 + syncplay/players/vlc.py | 1 + syncplay/ui/gui.py | 4 ++++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/syncplay/players/basePlayer.py b/syncplay/players/basePlayer.py index 97fc2a2..5093668 100644 --- a/syncplay/players/basePlayer.py +++ b/syncplay/players/basePlayer.py @@ -82,7 +82,13 @@ class BasePlayer(object): @staticmethod def getExpandedPath(path): raise NotImplementedError() - + + ''' + Opens a custom media browse dialog, and then changes to that media if appropriate + ''' + @staticmethod + def openCustomOpenDialog(self): + raise NotImplementedError() class DummyPlayer(BasePlayer): diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index d23d857..d79a9ef 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -306,6 +306,7 @@ class MpcHcApi: class MPCHCAPIPlayer(BasePlayer): speedSupported = False + customOpenDialog = False def __init__(self, client): from twisted.internet import reactor diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index c01ac5f..db14c91 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -9,6 +9,7 @@ import os class MplayerPlayer(BasePlayer): speedSupported = True + customOpenDialog = False RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX) POSITION_QUERY = 'time_pos' OSD_QUERY = 'osd_show_text' diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index 18a77a6..dd35517 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -13,6 +13,7 @@ from syncplay.messages import getMessage class VlcPlayer(BasePlayer): speedSupported = True + customOpenDialog = False RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX) SLAVE_ARGS = constants.VLC_SLAVE_ARGS if not sys.platform.startswith('darwin'): diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 0bc77f2..5b02d8b 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -341,6 +341,10 @@ class MainWindow(QtGui.QMainWindow): settings.endGroup() def browseMediapath(self): + if self._syncplayClient._player.customOpenDialog == True: + self._syncplayClient._player.openCustomOpenDialog() + return + self.loadMediaBrowseSettings() options = QtGui.QFileDialog.Options() if os.path.isdir(self.mediadirectory): From 9a554dd2222a3f447f46500854ad07bc16710731 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 13:46:23 +0000 Subject: [PATCH 123/142] Expand jump forward to include when you are out of sync with yourself --- syncplay/client.py | 3 +-- syncplay/messages.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 329b774..51c77ce 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -255,8 +255,7 @@ class SyncplayClient(object): if diff > self._config['rewindThreshold'] and not doSeek and not self._config['rewindOnDesync'] == False: madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy) if diff < (self._config['fastforwardThreshold'] * -1) and not doSeek and not self._config['fastforwardOnDesync'] == False: - if utils.RoomPasswordProvider.isControlledRoom(self.getRoom()) and not self.userlist.currentUser.isController(): - madeChangeOnPlayer = self._fastforwardPlayerDueToTimeDifference(position, setBy) + madeChangeOnPlayer = self._fastforwardPlayerDueToTimeDifference(position, setBy) if self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False: madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy) if paused == False and pauseChanged: diff --git a/syncplay/messages.py b/syncplay/messages.py index 82e244b..e81d5b2 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -242,11 +242,11 @@ en = { "dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging. Useful for room controllers.", "pauseonleave-tooltip" : "Pause playback if you get disconnected or someone leaves from your room.", "rewindondesync-label" : "Rewind on major desync (highly recommended)", - "fastforwardondesync-label" : "Fast-forward if lagging behind room controller (recommended)", + "fastforwardondesync-label" : "Fast-forward if lagging behind (recommended)", "forceguiprompt-tooltip" : "Configuration dialogue is not shown when opening a file with Syncplay.", # (Inverted) "nostore-tooltip" : "Run Syncplay with the given configuration, but do not permanently store the changes.", # (Inverted) "rewindondesync-tooltip" : "Jump back when needed to get back in sync. Disabling this option can result in major desyncs!", - "fastforwardondesync-tooltip" : "Jump forward when needed to get back in sync. Disabling this option can result in major desyncs!", + "fastforwardondesync-tooltip" : "Jump forward when out of sync with room controller (or your pretend position if 'Never slow down or rewind others' enabled).", "showosd-tooltip" : "Sends Syncplay messages to media player OSD.", "showosdwarnings-tooltip" : "Show warnings if playing different file, alone in room.", "showsameroomosd-tooltip" : "Show OSD notifications for events relating to room user is in.", From 344ec5e5d4402c41f4c7e34565a00bc30bf8cee1 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 13:47:19 +0000 Subject: [PATCH 124/142] Move "Pause on leave" above "Never slow down or rewind others" --- syncplay/ui/GuiConfiguration.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 23cde3d..6e55776 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -525,14 +525,14 @@ class ConfigDialog(QtGui.QDialog): self.othersyncOptionsFrame = QtGui.QFrame() self.othersyncSettingsLayout = QtGui.QGridLayout() + self.pauseonleaveCheckbox = QCheckBox(getMessage("pauseonleave-label")) + self.othersyncSettingsLayout.addWidget(self.pauseonleaveCheckbox, 1, 0, 1, 2, Qt.AlignLeft) + self.pauseonleaveCheckbox.setObjectName("pauseOnLeave") + self.dontslowwithmeCheckbox = QCheckBox(getMessage("dontslowdownwithme-label")) self.dontslowwithmeCheckbox.setObjectName("dontSlowDownWithMe") - self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox, 1, 0, 1, 2, Qt.AlignLeft) - - self.pauseonleaveCheckbox = QCheckBox(getMessage("pauseonleave-label")) - self.othersyncSettingsLayout.addWidget(self.pauseonleaveCheckbox, 2, 0, 1, 2, Qt.AlignLeft) - self.pauseonleaveCheckbox.setObjectName("pauseOnLeave") + self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox, 2, 0, 1, 2, Qt.AlignLeft) self.fastforwardThresholdLabel.setObjectName("fastforward-threshold") self.fastforwardThresholdSpinbox.setObjectName("fastforward-threshold") From dbb0ff067fb3b4aa4f73e96d20b0483f3878dce1 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 14:33:22 +0000 Subject: [PATCH 125/142] Unbreak reset config --- syncplay/ui/GuiConfiguration.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 6e55776..dc2db7e 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -71,6 +71,9 @@ class ConfigDialog(QtGui.QDialog): self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/client/") def _isURL(self, path): + if path is None: + return False + if "http://" in path: return True From 07f0a4a426517b678e6b704b007f00827da6772d Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 14:35:18 +0000 Subject: [PATCH 126/142] Move InvalidConfigValue errors into messages.py --- syncplay/messages.py | 6 ++++++ syncplay/ui/ConfigurationGetter.py | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index e81d5b2..e111f93 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -92,6 +92,12 @@ en = { "unable-to-start-client-error" : "Unable to start client", + "player-path-config-error": "Player path is not set properly", + "no-file-path-config-error" :"File must be selected before starting your player", + "no-hostname-config-error": "Hostname can't be empty", + "invalid-port-config-error" : "Port must be valid", + "empty-value-config-error" : "{} can't be empty", # Config option + "not-json-error" : "Not a json encoded string\n", "hello-arguments-error" : "Not enough Hello arguments\n", "version-mismatch-error" : "Mismatch between versions of client and server\n", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 37d18f4..610646a 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -144,20 +144,20 @@ class ConfigurationGetter(object): if player: self._config["playerClass"] = player else: - raise InvalidConfigValue("Player path is not set properly") + raise InvalidConfigValue(getMessage("player-path-config-error")) if player.__name__ in ['MpvPlayer', 'MplayerPlayer']: if not self._config['file']: - raise InvalidConfigValue("File must be selected before starting your player") + raise InvalidConfigValue(getMessage("no-file-path-config-error")) elif key == "host": self._config["host"], self._config["port"] = self._splitPortAndHost(self._config["host"]) hostNotValid = (self._config["host"] == "" or self._config["host"] is None) portNotValid = (_isPortValid(self._config["port"]) == False) if hostNotValid: - raise InvalidConfigValue("Hostname can't be empty") + raise InvalidConfigValue(getMessage("no-hostname-config-error")) elif portNotValid: - raise InvalidConfigValue("Port must be valid") + raise InvalidConfigValue(getMessage("invalid-port-config-error")) elif self._config[key] == "" or self._config[key] is None: - raise InvalidConfigValue("{} can't be empty".format(key.capitalize())) + raise InvalidConfigValue(getMessage("empty-value-config-error").format(key.capitalize())) def _overrideConfigWithArgs(self, args): for key, val in vars(args).items(): @@ -386,4 +386,4 @@ class SafeConfigParserUnicode(SafeConfigParser): if (value is not None) or (self._optcre == self.OPTCRE): key = " = ".join((key, unicode(value).replace('\n', '\n\t'))) fp.write("%s\n" % key) - fp.write("\n") + fp.write("\n") \ No newline at end of file From 45e576e4f2dc429fc60c8d408d2d60be5586901b Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 14:50:59 +0000 Subject: [PATCH 127/142] Move filePath check to getFilePathErrors --- syncplay/players/basePlayer.py | 18 +++++++++++++++++- syncplay/players/mpc.py | 4 ++++ syncplay/players/mplayer.py | 5 +++++ syncplay/players/vlc.py | 4 ++++ syncplay/ui/ConfigurationGetter.py | 6 +++--- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/syncplay/players/basePlayer.py b/syncplay/players/basePlayer.py index 5093668..4609757 100644 --- a/syncplay/players/basePlayer.py +++ b/syncplay/players/basePlayer.py @@ -89,7 +89,19 @@ class BasePlayer(object): @staticmethod def openCustomOpenDialog(self): raise NotImplementedError() - + + ''' + @type filePath: string + @return errorMessage: string + + Checks if the player has any problems with the given file (or lack of file) + If a problem is detected then it returns the error message + If the file is fine then it returns None + ''' + @staticmethod + def getFilePathErrors(filePath): + raise NotImplementedError() + class DummyPlayer(BasePlayer): @staticmethod @@ -107,3 +119,7 @@ class DummyPlayer(BasePlayer): @staticmethod def getExpandedPath(path): return path + + @staticmethod + def getFilePathErrors(filePath): + return None diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index d79a9ef..a62ac95 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -332,6 +332,10 @@ class MPCHCAPIPlayer(BasePlayer): self.__versionUpdate.set() self._mpcApi.sendRawCommand(MpcHcApi.CMD_CLOSEAPP, "") + @staticmethod + def getFilePathErrors(filePath): + return None + @staticmethod def run(client, playerPath, filePath, args): args.extend(['/open', '/new']) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index db14c91..c5f2a4f 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -212,6 +212,11 @@ class MplayerPlayer(BasePlayer): return True return False + @staticmethod + def getFilePathErrors(filePath): + if not filePath: + return getMessage("no-file-path-config-error") + @staticmethod def getExpandedPath(playerPath): if not os.path.isfile(playerPath): diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index dd35517..3e1b3ae 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -200,6 +200,10 @@ class VlcPlayer(BasePlayer): return True return False + @staticmethod + def getFilePathErrors(filePath): + return None + @staticmethod def getIconPath(path): return constants.VLC_ICONPATH diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 610646a..fc654aa 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -145,9 +145,9 @@ class ConfigurationGetter(object): self._config["playerClass"] = player else: raise InvalidConfigValue(getMessage("player-path-config-error")) - if player.__name__ in ['MpvPlayer', 'MplayerPlayer']: - if not self._config['file']: - raise InvalidConfigValue(getMessage("no-file-path-config-error")) + fileErrors = player.getFilePathErrors(self._config['file'] if self._config['file'] else None) + if fileErrors: + raise InvalidConfigValue(fileErrors) elif key == "host": self._config["host"], self._config["port"] = self._splitPortAndHost(self._config["host"]) hostNotValid = (self._config["host"] == "" or self._config["host"] is None) From 7ca29bf30b545774391af6636b1f47e93ddc94b7 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 15:05:29 +0000 Subject: [PATCH 128/142] Broaden getFilePathErrors into getPlayerPathErrors --- syncplay/players/basePlayer.py | 11 +++++------ syncplay/players/mpc.py | 2 +- syncplay/players/mplayer.py | 2 +- syncplay/players/vlc.py | 2 +- syncplay/ui/ConfigurationGetter.py | 6 +++--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/syncplay/players/basePlayer.py b/syncplay/players/basePlayer.py index 4609757..c8fdcc2 100644 --- a/syncplay/players/basePlayer.py +++ b/syncplay/players/basePlayer.py @@ -91,15 +91,14 @@ class BasePlayer(object): raise NotImplementedError() ''' + @type playerPath: string @type filePath: string @return errorMessage: string - Checks if the player has any problems with the given file (or lack of file) - If a problem is detected then it returns the error message - If the file is fine then it returns None + Checks if the player has any problems with the given player/file path ''' @staticmethod - def getFilePathErrors(filePath): + def getPlayerPathErrors(playerPath, filePath): raise NotImplementedError() class DummyPlayer(BasePlayer): @@ -121,5 +120,5 @@ class DummyPlayer(BasePlayer): return path @staticmethod - def getFilePathErrors(filePath): - return None + def getPlayerPathErrors(playerPath, filePath): + return None \ No newline at end of file diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index a62ac95..d9303a1 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -333,7 +333,7 @@ class MPCHCAPIPlayer(BasePlayer): self._mpcApi.sendRawCommand(MpcHcApi.CMD_CLOSEAPP, "") @staticmethod - def getFilePathErrors(filePath): + def getPlayerPathErrors(playerPath, filePath): return None @staticmethod diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index c5f2a4f..773df35 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -213,7 +213,7 @@ class MplayerPlayer(BasePlayer): return False @staticmethod - def getFilePathErrors(filePath): + def getPlayerPathErrors(playerPath, filePath): if not filePath: return getMessage("no-file-path-config-error") diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index 3e1b3ae..e7bba8b 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -201,7 +201,7 @@ class VlcPlayer(BasePlayer): return False @staticmethod - def getFilePathErrors(filePath): + def getPlayerPathErrors(playerPath, filePath): return None @staticmethod diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index fc654aa..b4de8a6 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -145,9 +145,9 @@ class ConfigurationGetter(object): self._config["playerClass"] = player else: raise InvalidConfigValue(getMessage("player-path-config-error")) - fileErrors = player.getFilePathErrors(self._config['file'] if self._config['file'] else None) - if fileErrors: - raise InvalidConfigValue(fileErrors) + playerPathErrors = player.getPlayerPathErrors(self._config["playerPath"], self._config['file'] if self._config['file'] else None) + if playerPathErrors: + raise InvalidConfigValue(playerPathErrors) elif key == "host": self._config["host"], self._config["port"] = self._splitPortAndHost(self._config["host"]) hostNotValid = (self._config["host"] == "" or self._config["host"] is None) From 74e723032f485a1e145946d5478765a8f0da7959 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 16:04:15 +0000 Subject: [PATCH 129/142] Pretend mebibytes are megabytes --- syncplay/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/utils.py b/syncplay/utils.py index d3ab921..165330e 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -99,7 +99,7 @@ def formatSize (bytes, precise=False): if bytes == 0: # E.g. when file size privacy is enabled return "???" try: - megabytes = int(bytes) / 1000000.0 + megabytes = int(bytes) / 1048576.0 # Technically this is a mebibyte, but whatever if precise: megabytes = round(megabytes, 1) else: From 490537d2286b3d221edb5badd45a79de5ff90383 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 17:28:50 +0000 Subject: [PATCH 130/142] Fix HTTP player path support --- syncplay/constants.py | 30 +++++++++++++++--------------- syncplay/ui/GuiConfiguration.py | 27 +++++++++++++++++---------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index caddbaa..c608a97 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -60,25 +60,25 @@ VLC_MIN_VERSION = "2.0.0" VLC_INTERFACE_MIN_VERSION = "0.2.1" CONTROLLED_ROOMS_MIN_VERSION = "1.3.0" MPC_PATHS = [ - r"C:\Program Files (x86)\MPC-HC\mpc-hc.exe", - r"C:\Program Files\MPC-HC\mpc-hc.exe", - r"C:\Program Files\MPC-HC\mpc-hc64.exe", - r"C:\Program Files\Media Player Classic - Home Cinema\mpc-hc.exe", - r"C:\Program Files\Media Player Classic - Home Cinema\mpc-hc64.exe", - r"C:\Program Files (x86)\Media Player Classic - Home Cinema\mpc-hc.exe", - r"C:\Program Files (x86)\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe", - r"C:\Program Files\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe", - r"C:\Program Files (x86)\Combined Community Codec Pack\MPC\mpc-hc.exe", - r"C:\Program Files\Combined Community Codec Pack\MPC\mpc-hc.exe", - r"C:\Program Files\MPC HomeCinema (x64)\mpc-hc64.exe", + r"c:\program files (x86)\mpc-hc\mpc-hc.exe", + r"c:\program files\mpc-hc\mpc-hc.exe", + r"c:\program files\mpc-hc\mpc-hc64.exe", + r"c:\program files\media player classic - home cinema\mpc-hc.exe", + r"c:\program files\media player classic - home cinema\mpc-hc64.exe", + r"c:\program files (x86)\media player classic - home cinema\mpc-hc.exe", + r"c:\program files (x86)\k-lite codec pack\media player classic\mpc-hc.exe", + r"c:\program files\k-lite codec pack\media Player classic\mpc-hc.exe", + r"c:\program files (x86)\combined community codec pack\mpc\mpc-hc.exe", + r"c:\program files\combined community codec pack\mpc\mpc-hc.exe", + r"c:\program files\mpc homecinema (x64)\mpc-hc64.exe", ] MPLAYER_PATHS = ["mplayer2", "mplayer"] -MPV_PATHS = ["mpv", "/opt/mpv/mpv", r"C:\Program Files\mpv\mpv.exe", r"C:\Program Files\mpv-player\mpv.exe", - r"C:\Program Files (x86)\mpv\mpv.exe", r"C:\Program Files (x86)\mpv-player\mpv.exe", +MPV_PATHS = ["mpv", "/opt/mpv/mpv", r"c:\program files\mpv\mpv.exe", r"c:\program files\mpv-player\mpv.exe", + r"c:\program Files (x86)\mpv\mpv.exe", r"c:\program Files (x86)\mpv-player\mpv.exe", "/Applications/mpv.app/Contents/MacOS/mpv"] VLC_PATHS = [ - r"C:\Program Files (x86)\VideoLAN\VLC\vlc.exe", - r"C:\Program Files\VideoLAN\VLC\vlc.exe", + r"c:\program files (x86)\videolan\vlc\vlc.exe", + r"c:\program files\videolan\vlc\vlc.exe", "/usr/bin/vlc", "/usr/bin/vlc-wrapper", "/Applications/VLC.app/Contents/MacOS/VLC", diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index dc2db7e..8f7df17 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -77,13 +77,22 @@ class ConfigDialog(QtGui.QDialog): if "http://" in path: return True + def safenormcaseandpath(self, path): + if self._isURL(path): + return path + else: + return os.path.normcase(os.path.normpath(path)) + def _tryToFillPlayerPath(self, playerpath, playerpathlist): settings = QSettings("Syncplay", "PlayerList") settings.beginGroup("PlayerList") savedPlayers = settings.value("PlayerList", []) if not isinstance(savedPlayers, list): savedPlayers = [] - playerpathlist = list(set(os.path.normcase(os.path.normpath(path)) for path in set(playerpathlist + savedPlayers))) + else: + for i, savedPlayer in enumerate(savedPlayers): + savedPlayers[i] = self.safenormcaseandpath(savedPlayer) + playerpathlist = list(set(playerpathlist + savedPlayers)) settings.endGroup() foundpath = "" @@ -103,9 +112,11 @@ class ConfigDialog(QtGui.QDialog): self.executablepathCombobox.addItem(foundpath) for path in playerpathlist: - if self._isURL(playerpath): - foundpath = path - self.executablepathCombobox.addItem(path) + if self._isURL(path): + if foundpath == "": + foundpath = path + if path != playerpath: + self.executablepathCombobox.addItem(path) elif os.path.isfile(path) and os.path.normcase(os.path.normpath(path)) != os.path.normcase(os.path.normpath(foundpath)): self.executablepathCombobox.addItem(path) @@ -114,12 +125,8 @@ class ConfigDialog(QtGui.QDialog): if foundpath != "": settings.beginGroup("PlayerList") - if self._isURL(foundpath): - playerpathlist.append(foundpath) - settings.setValue("PlayerList", list(set(path) for path in set(playerpathlist))) - else: - playerpathlist.append(os.path.normcase(os.path.normpath(foundpath))) - settings.setValue("PlayerList", list(set(os.path.normcase(os.path.normpath(path)) for path in set(playerpathlist)))) + playerpathlist.append(self.safenormcaseandpath(foundpath)) + settings.setValue("PlayerList", list(set(playerpathlist))) settings.endGroup() return foundpath From ac3e0f39c647d82462e791d5b8e6b62f440cabb2 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 17:49:29 +0000 Subject: [PATCH 131/142] Normalise player path before saving it --- syncplay/ui/GuiConfiguration.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 8f7df17..6e3acd1 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -223,7 +223,7 @@ class ConfigDialog(QtGui.QDialog): self.config['host'] = self.hostTextbox.text() if ":" in self.hostTextbox.text() else self.hostTextbox.text() + ":" + unicode(constants.DEFAULT_PORT) else: self.config['host'] = None - self.config['playerPath'] = unicode(self.executablepathCombobox.currentText()) + self.config['playerPath'] = unicode(self.safenormcaseandpath(self.executablepathCombobox.currentText())) self.config['language'] = unicode(self.languageCombobox.itemData(self.languageCombobox.currentIndex())) if self.mediapathTextbox.text() == "": self.config['file'] = None @@ -249,8 +249,6 @@ class ConfigDialog(QtGui.QDialog): def closeEvent(self, event): if self.pressedclosebutton == False: sys.exit() - raise GuiConfiguration.WindowClosed - event.accept() def dragEnterEvent(self, event): data = event.mimeData() From aa43e91fef6707a70f86a1aa941d98af5a71b21a Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 17:58:03 +0000 Subject: [PATCH 132/142] Warn of missing/unused strings when compiling EXE --- buildPy2exe.py | 6 ++++++ syncplay/client.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 4a74f64..49f1d46 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -16,6 +16,12 @@ import syncplay import os import subprocess +from syncplay.messages import getMissingStrings +missingStrings = getMissingStrings() +if missingStrings is not None and missingStrings is not "": + import warnings + warnings.warn("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings)) + p = "C:\\Program Files (x86)\\NSIS\\Unicode\\makensis.exe" #TODO: how to move that into proper place, huh NSIS_COMPILE = p if os.path.isfile(p) else "makensis.exe" OUT_DIR = "syncplay v{}".format(syncplay.version) diff --git a/syncplay/client.py b/syncplay/client.py index 51c77ce..0adefd1 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -112,7 +112,7 @@ class SyncplayClient(object): if constants.DEBUG_MODE and constants.WARN_ABOUT_MISSING_STRINGS: missingStrings = getMissingStrings() if missingStrings is not None and missingStrings is not "": - self.ui.showDebugMessage("MISSING STRINGS DETECTED:\n{}".format(missingStrings)) + self.ui.showDebugMessage("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings)) def initProtocol(self, protocol): self._protocol = protocol From 89565a1165972415fc6427cebf3084551c11e3e4 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 19:55:40 +0000 Subject: [PATCH 133/142] Press escape exits GuiConfig --- syncplay/ui/GuiConfiguration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 6e3acd1..13394f3 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -250,6 +250,10 @@ class ConfigDialog(QtGui.QDialog): if self.pressedclosebutton == False: sys.exit() + def keyPressEvent(self, event): + if event.key() == Qt.Key_Escape: + sys.exit() + def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() From 9149c805783eeaed95dc0c9f0b0d5f2b04c363f5 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 19:58:56 +0000 Subject: [PATCH 134/142] State 'never slow down' feature is experimental --- syncplay/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index e111f93..cf3a12a 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -152,7 +152,7 @@ en = { "filename-privacy-label" : "Filename information:", "filesize-privacy-label" : "File size information:", "slowondesync-label" : "Slow down on minor desync (not supported on MPC-HC)", - "dontslowdownwithme-label" : "Never slow down or rewind others", + "dontslowdownwithme-label" : "Never slow down or rewind others (experimental)", "pauseonleave-label" : "Pause when user leaves (e.g. if they are disconnected)", "forceguiprompt-label" : "Don't always show this dialog", # (Inverted) "nostore-label" : "Don't store this configuration", # (Inverted) From b56cb58079afc1885477d88f66589386a5636623 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 21:20:37 +0000 Subject: [PATCH 135/142] More consistent spacing for GuiConfig items --- syncplay/ui/GuiConfiguration.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 13394f3..cf4e823 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -379,6 +379,7 @@ class ConfigDialog(QtGui.QDialog): self.connectionSettingsLayout.addWidget(self.defaultroomLabel, 3, 0) self.connectionSettingsLayout.addWidget(self.defaultroomTextbox, 3, 1) self.connectionSettingsGroup.setLayout(self.connectionSettingsLayout) + self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height()) self.mediaplayerSettingsGroup = QtGui.QGroupBox(getMessage("media-setting-title")) self.executableiconImage = QtGui.QImage() @@ -430,8 +431,11 @@ class ConfigDialog(QtGui.QDialog): self.errorLabel.setAlignment(Qt.AlignCenter) self.basicOptionsLayout.addWidget(self.errorLabel, 0, 0) + self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height()) + self.basicOptionsLayout.setAlignment(Qt.AlignTop) self.basicOptionsLayout.addWidget(self.connectionSettingsGroup) self.basicOptionsLayout.addSpacing(12) + self.mediaplayerSettingsGroup.setMaximumHeight(self.mediaplayerSettingsGroup.minimumSizeHint().height()) self.basicOptionsLayout.addWidget(self.mediaplayerSettingsGroup) self.basicOptionsFrame.setLayout(self.basicOptionsLayout) @@ -555,6 +559,7 @@ class ConfigDialog(QtGui.QDialog): self.subitems['fastforwardOnDesync'] = ["fastforward-threshold"] self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout) + self.othersyncSettingsGroup.setMaximumHeight(self.othersyncSettingsGroup.minimumSizeHint().height()) self.syncSettingsLayout.addWidget(self.othersyncSettingsGroup) self.syncSettingsFrame.setLayout(self.syncSettingsLayout) @@ -603,29 +608,25 @@ class ConfigDialog(QtGui.QDialog): self.subitems['showOSD'] = ["showSameRoomOSD", "showDifferentRoomOSD", "showSlowdownOSD", "showOSDWarnings"] self.osdSettingsGroup.setLayout(self.osdSettingsLayout) + self.osdSettingsGroup.setMaximumHeight(self.osdSettingsGroup.minimumSizeHint().height()) self.osdSettingsLayout.setAlignment(Qt.AlignTop) self.messageLayout.addWidget(self.osdSettingsGroup) # Other display self.displaySettingsGroup = QtGui.QGroupBox(getMessage("messages-other-title")) - self.displaySettingsLayout = QtGui.QVBoxLayout() + self.displaySettingsLayout = QtGui.QGridLayout() self.displaySettingsLayout.setAlignment(Qt.AlignTop) self.displaySettingsFrame = QtGui.QFrame() self.showDurationNotificationCheckbox = QCheckBox(getMessage("showdurationnotification-label")) self.showDurationNotificationCheckbox.setObjectName("showDurationNotification") - self.displaySettingsLayout.addWidget(self.showDurationNotificationCheckbox) + self.displaySettingsLayout.addWidget(self.showDurationNotificationCheckbox, 0, 0, 1, 2) self.showcontactinfoCheckbox = QCheckBox(getMessage("showcontactinfo-label")) self.showcontactinfoCheckbox.setObjectName("showContactInfo") - self.displaySettingsLayout.addWidget(self.showcontactinfoCheckbox) + self.displaySettingsLayout.addWidget(self.showcontactinfoCheckbox, 1, 0, 1, 2) - self.languageFrame = QtGui.QFrame() - self.languageFrame.setContentsMargins(0,0,0,0) - self.languageLayout = QtGui.QHBoxLayout() - self.languageLayout.setSpacing(0) - self.languageLayout.setAlignment(Qt.AlignTop) self.languageLabel = QLabel(getMessage("language-label"), self) self.languageCombobox = QtGui.QComboBox(self) self.languages = getLanguages() @@ -634,15 +635,15 @@ class ConfigDialog(QtGui.QDialog): if lang == self.config['language']: self.languageCombobox.setCurrentIndex(self.languageCombobox.count()-1) self.languageCombobox.currentIndexChanged.connect(self.languageChanged) - self.languageLayout.addWidget(self.languageLabel) - self.languageLayout.addWidget(self.languageCombobox) - self.languageFrame.setLayout(self.languageLayout) - self.languageFrame.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) - self.displaySettingsLayout.addWidget(self.languageFrame) + self.displaySettingsLayout.addWidget(self.languageLabel, 2, 0, 1, 1) + self.displaySettingsLayout.addWidget(self.languageCombobox, 2, 1, 1, 1) + self.languageLabel.setObjectName("language") self.languageCombobox.setObjectName("language") + self.displaySettingsGroup.setLayout(self.displaySettingsLayout) + self.displaySettingsGroup.setMaximumHeight(self.displaySettingsGroup.minimumSizeHint().height()) self.displaySettingsLayout.setAlignment(Qt.AlignTop) self.messageLayout.addWidget(self.displaySettingsGroup) From d03a56cf4a4192388fa99b3adcfd9e3e55f42d97 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 30 Nov 2014 23:28:22 +0000 Subject: [PATCH 136/142] Increase group spacing on basic options tab --- syncplay/ui/GuiConfiguration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index cf4e823..6b4e20a 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -434,7 +434,7 @@ class ConfigDialog(QtGui.QDialog): self.connectionSettingsGroup.setMaximumHeight(self.connectionSettingsGroup.minimumSizeHint().height()) self.basicOptionsLayout.setAlignment(Qt.AlignTop) self.basicOptionsLayout.addWidget(self.connectionSettingsGroup) - self.basicOptionsLayout.addSpacing(12) + self.basicOptionsLayout.addSpacing(17) self.mediaplayerSettingsGroup.setMaximumHeight(self.mediaplayerSettingsGroup.minimumSizeHint().height()) self.basicOptionsLayout.addWidget(self.mediaplayerSettingsGroup) From 70be67c5c0030442073a17f4cd69c2393d0894b9 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 1 Dec 2014 00:39:17 +0000 Subject: [PATCH 137/142] Update renamed labels and tooltips (German) --- syncplay/messages.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index cf3a12a..ff3b758 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -740,12 +740,12 @@ de = { "privacy-dontsend-option" : u"Nicht senden", "filename-privacy-label" : u"Dateiname:", "filesize-privacy-label" : u"Dateigröße:", - "slowdown-label" : u"Verlangsamen wenn nicht synchron", - "dontslowwithme-label" : u"Nie verlangsamen oder andere zurückspulen", + "slowondesync-label" : u"Verlangsamen wenn nicht synchron", # TODO: Update new wording into German (should translate to "Slow down on minor desync (not supported on MPC-HC)") + "dontslowdownwithme-label" : u"Nie verlangsamen oder andere zurückspulen", "pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt", - "rewind-label" : u"Zurückspulen bei großer Zeitdifferent (empfohlen)", - "alwayshow-label" : u"Diesen Dialog immer anzeigen", - "donotstore-label" : u"Diese Konfiguration nicht speichern", + "rewindondesync-label" : u"Zurückspulen bei großer Zeitdifferent (empfohlen)", + "forceguiprompt-label" : u"Don't always show this dialog", # TODO: Translate into German (previous was "Diesen Dialog immer anzeigen" but the logic is now inverted) + "nostore-label" : u"Diese Konfiguration nicht speichern", "help-label" : u"Hilfe", "run-label" : u"Syncplay starten", @@ -770,12 +770,12 @@ de = { "privacy-sendraw-tooltip" : u"Die Information im Klartext übertragen. Dies ist die Standard-Einstellung mit der besten Funktionalität.", "privacy-sendhashed-tooltip" : u"Die Informationen gehasht übertragen, um sie für andere Clients schwerer lesbar zu machen.", "privacy-dontsend-tooltip" : u"Diese Information nicht übertragen. Dies garantiert den größtmöglichen Datanschutz.", - "slowdown-tooltip" : u"Reduziert die Abspielgeschwindigkeit zeitweise, um dich wieder synchron zu den anderen Clients zu machen.", - "dontslowwithme-tooltip" : u"Lässt andere nicht langsamer werden oder zurückspringen, wenn deine Wiedergabe hängt.", + "slowondesync-tooltip" : u"Reduziert die Abspielgeschwindigkeit zeitweise, um dich wieder synchron zu den anderen Clients zu machen.", + "dontslowdownwithme-tooltip" : u"Lässt andere nicht langsamer werden oder zurückspringen, wenn deine Wiedergabe hängt.", "pauseonleave-tooltip" : u"Wiedergabe anhalten, wenn deine Verbindung verloren geht oder jemand den Raum verlässt.", - "rewind-tooltip" : u"In der Zeit zurückspringen zum wiederherstellen der Synchronität. Empfohlen.", - "alwayshow-tooltip" : u"Der Konfigurations-Dislog wird immer angezeigt. Sogar, wenn du eine Datei mit Syncplay öffnest.", - "donotstore-tooltip" : u"Syncplay mit den angegebenen Einstellungen starten, diese aber nicht fauerhaft speichern.", + "rewindondesync-tooltip" : u"In der Zeit zurückspringen zum wiederherstellen der Synchronität. Empfohlen.", + "forceguiprompt-tooltip" : u"Configuration dialogue is not shown when opening a file with Syncplay.", # TODO: Translate into German (was "Der Konfigurations-Dislog wird immer angezeigt. Sogar, wenn du eine Datei mit Syncplay öffnest." but logic is now inverted) + "nostore-tooltip" : u"Syncplay mit den angegebenen Einstellungen starten, diese aber nicht fauerhaft speichern.", "help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]", From 23f1fb0d70efa666322bc973ab0725628fde9e17 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 1 Dec 2014 00:42:31 +0000 Subject: [PATCH 138/142] Remove unused messages (German) --- syncplay/messages.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index ff3b758..c331720 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -697,8 +697,6 @@ de = { "not-json-error" : u"Kein JSON-String\n", "hello-arguments-error" : u"Zu wenige Hello-Argumente\n", "version-mismatch-error" : u"Verschiedene Versionen auf Client und Server\n", - "vlc-error-echo": u"VLC-Fehler: {}", # VLC error line - "vlc-unicode-loadfile-error" : u"Die Datei kann nicht durch Syncplay geladen werden, da sie nicht-ASCII Zeichen enthält. Bitte öffne die Datei mit VLC.", "vlc-failed-connection": u"Kann nicht zu VLC verbinden. Wenn du syncplay.lua nicht installiert hast, findest du auf http://syncplay.pl/LUA/ für eine Anleitung.", "vlc-failed-noscript": u"Laut VLC ist das syncplay.lua Interface-Skript nicht installiert. Auf http://syncplay.pl/LUA/ findest du eine Anleitung.", "vlc-failed-versioncheck": u"Diese VLC-Version wird von Syncplay nicht unterstützt. Bitte nutze VLC 2.0", @@ -752,7 +750,6 @@ de = { "storeandrun-label" : u"Konfiguration speichern und Syncplay starten", "roomuser-heading-label" : u"Raum / Benutzer", - "fileplayed-heading-label" : u"Gespielte Datei", # Tooltips @@ -797,8 +794,6 @@ de = { "server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird", "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 {}.", - "server-irc-verbose": u"Soll der Server aktiv über Änderungen in den Räumen informieren", - "server-irc-config": u"Pfad zu den config-Dateien des irc bot", # Server errors "unknown-command-server-error" : u"Unbekannter Befehl {}", # message @@ -807,7 +802,6 @@ de = { "client-drop-server-error" : u"Client verloren: {} -- {}", # host, error "password-required-server-error" : u"Passwort nötig", "hello-server-error" : u"Zu wenige Hello-Argumente", - "version-mismatch-server-error" : u"Verschiedene Versionen auf Client und Server", "wrong-password-server-error" : u"Ungültiges Passwort" } From 5299609a29149999d747be5d01f8872b3eaaa58b Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 1 Dec 2014 01:39:00 +0000 Subject: [PATCH 139/142] Add missing translation placeholders (German) --- syncplay/messages.py | 130 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 5 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index c331720..fa53f37 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -621,6 +621,8 @@ de = { "LANGUAGE" : u"Deutsch", # (German) # Client notifications + "config-cleared-notification" : u"Settings cleared. Changes will be saved when you store a valid configuration.", # TODO: Translate into German + "relative-config-notification" : u"Relative Konfigurationsdatei(en) geladen: {}", "connection-attempt-notification" : u"Versuche zu verbinden nach {}:{}", # Port, IP @@ -631,6 +633,7 @@ de = { "retrying-notification" : u"%s, versuche erneut in %d Sekunden...", # Seconds "rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit <{}>", # User + "fastforward-notification" : u"Fast-forwarded due to time difference with <{}>", # User # TODO: Translate into German "slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit <{}>", # User "revert-notification" : u"Normalgeschwindigkeit", @@ -642,9 +645,14 @@ de = { "room-join-notification" : u"<{}> hat den Raum '{}' betreten", # User "left-notification" : u"<{}> ist gegangen", # User + "left-paused-notification" : u"<{}> left, <{}> paused", # User who left, User who paused # TODO: Translate into German "playing-notification" : u"<{}> spielt '{}' ({})", # User, file, duration "playing-notification/room-addendum" : u" in Raum: '{}'", # Room + "identifying-as-controller-notification" : u"Identifying as room controller with password '{}'...", # TODO: Translate into German + "failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room controller.", # TODO: Translate into German + "authenticated-as-controller-notification" : u"<{}> authenticated as a room controller", # TODO: Translate into German + "file-different-notification" : u"Deine Datei scheint sich von <{}>s zu unterscheiden", # User "file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: ", "room-files-not-same" : u"Nicht alle Dateien im Raum sind gleich", @@ -667,10 +675,13 @@ de = { "commandlist-notification/pause" : u"\tp - Pausieren / weiter", "commandlist-notification/seek" : u"\t[s][+-]Zeit - zu einer bestimmten Zeit spulen, ohne + oder - wird als absolute Zeit gewertet; Angabe in Sekungen oder Minuten:Sekunden", "commandlist-notification/help" : u"\th - Diese Hilfe", + "commandlist-notification/create" : "\tc [name] - create controlled room using name of current room", # TODO: Translate into German + "commandlist-notification/auth" : "\ta [password] - authenticate as room controller with controller password", # TODO: Translate into German "syncplay-version-notification" : u"Syncplay Version: {}", # syncplay.version "more-info-notification" : u"Weitere Informationen auf: {}", # projectURL "gui-data-cleared-notification" : u"Syncplay hat die Pfad und Fensterdaten der Syncplay-GUI zurückgesetzt.", + "language-changed-msgbox-label" : u"Language will be changed when you run Syncplay.", # TODO: Translate into German "vlc-version-mismatch": u"Warnung: Du nutzt VLC Version {}, aber Syncplay wurde für VLC ab Version {} entwickelt.", # VLC version, VLC min version "vlc-interface-version-mismatch": u"Warnung: Du nutzt Version {} des VLC-Syncplay Interface-Moduls, Syncplay benötigt aber mindestens Version {}.", # VLC interface version, VLC interface min version @@ -685,15 +696,24 @@ de = { "server-timeout-error" : u"Timeout: Verbindung zum Server fehlgeschlagen", "mpc-slave-error" : u"Kann MPC nicht im Slave-Modus starten!", "mpc-version-insufficient-error" : u"MPC-Version nicht ausreichend, bitte nutze `mpc-hc` >= `{}`", + "mpv-version-error" : "Syncplay is not compatible with this version of mpv. Please use a different version of mpv (e.g. Git HEAD).", # TODO: Translate into German "player-file-open-error" : u"Fehler beim Öffnen der Datei durch den Player", "player-path-error" : u"Ungültiger Player-Pfad", "hostname-empty-error" : u"Hostname darf nicht leer sein", "empty-error" : u"{} darf nicht leer sein", # Configuration + "media-player-error": "Media player error: \"{}\"", # Error line # TODO: Translate into German + "unable-import-gui-error": u"Could not import GUI libraries. If you do not have PySide installed then you will need to install it for the GUI to work.", # TODO: Translate into German "arguments-missing-error" : u"Notwendige Argumente fehlen, siehe --help", "unable-to-start-client-error" : u"Client kann nicht gestartet werden", + "player-path-config-error": u"Player path is not set properly", # TODO: Translate into German + "no-file-path-config-error": u"File must be selected before starting your player", # TODO: Translate into German + "no-hostname-config-error": u"Hostname can't be empty", # TODO: Translate into German + "invalid-port-config-error" : u"Port must be valid", # TODO: Translate into German + "empty-value-config-error" : u"{} can't be empty", # Config option # TODO: Translate into German + "not-json-error" : u"Kein JSON-String\n", "hello-arguments-error" : u"Zu wenige Hello-Argumente\n", "version-mismatch-error" : u"Verschiedene Versionen auf Client und Server\n", @@ -717,6 +737,10 @@ de = { "file-argument" : u'Zu spielende Datei', "args-argument" : u'Player-Einstellungen; Wenn du Einstellungen, die mit - beginnen, nutzen willst, stelle ein einzelnes \'--\'-Argument davor', "clear-gui-data-argument" : u'Setzt die Pfad- und GUI-Fenster-Daten, die in den QSettings gespeichert sind, zurück', + "language-argument" : u'language for Syncplay messages (de/en/pl/ru)', # TODO: Translate into German + + "version-argument" : 'prints your version', # TODO: Translate into German + "version-message" : "You're using Syncplay version {} ({})", # TODO: Translate into German # Client labels "config-window-title" : u"Syncplay Konfiguration", @@ -733,6 +757,11 @@ de = { "browse-label" : u"Durchsuchen", "more-title" : u"Mehr Einstellungen zeigen", + "slowdown-threshold-label" : u"Slow down threshold:", # TODO: Translate into German + "rewind-threshold-label" : u"Rewind threshold:", # TODO: Translate into German + "fastforward-threshold-label" : u"Fast-forward threshold:", # TODO: Translate into German + "never-rewind-value" : u"Never", # TODO: Translate into German + "seconds-suffix" : " secs", # TODO: Translate into German "privacy-sendraw-option" : u"Klartext senden", "privacy-sendhashed-option" : u"Hash senden", "privacy-dontsend-option" : u"Nicht senden", @@ -741,15 +770,76 @@ de = { "slowondesync-label" : u"Verlangsamen wenn nicht synchron", # TODO: Update new wording into German (should translate to "Slow down on minor desync (not supported on MPC-HC)") "dontslowdownwithme-label" : u"Nie verlangsamen oder andere zurückspulen", "pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt", - "rewindondesync-label" : u"Zurückspulen bei großer Zeitdifferent (empfohlen)", "forceguiprompt-label" : u"Don't always show this dialog", # TODO: Translate into German (previous was "Diesen Dialog immer anzeigen" but the logic is now inverted) "nostore-label" : u"Diese Konfiguration nicht speichern", + "showosd-label" : u"Enable OSD Messages", # TODO: Translate into German + + "showosdwarnings-label" : u"Include warnings (e.g. when files are different)", # TODO: Translate into German + "showsameroomosd-label" : u"Include events in your room", # TODO: Translate into German + "shownoncontrollerosd-label" : u"Include events from non-controllers in controlled rooms", # TODO: Translate into German + "showdifferentroomosd-label" : u"Include events in other rooms", # TODO: Translate into German + "showslowdownosd-label" : u"Include slowing down / reverting notification", # TODO: Translate into German + "showcontactinfo-label" : u"Show contact info box", # TODO: Translate into German + "language-label" : u"Language", # TODO: Translate into German + "showdurationnotification-label" : u"Warn about media duration mismatches", # TODO: Translate into German + "basics-label" : u"Basics", # TODO: Translate into German + "sync-label" : u"Sync", # TODO: Translate into German + "sync-lagging-title" : u"If others are lagging behind...", # TODO: Translate into German + "sync-other-title" : u"Other sync options", # TODO: Translate into German + "messages-label" : u"Messages", # TODO: Translate into German + "messages-osd-title" : u"On-screen Display settings", # TODO: Translate into German + "messages-other-title" : u"Other display settings", # TODO: Translate into German + "privacy-label" : u"Privacy", # TODO: Translate into German + "privacy-title" : u"Privacy settings", # TODO: Translate into German "help-label" : u"Hilfe", + "reset-label" : "Restore defaults", # TODO: Translate into German "run-label" : u"Syncplay starten", "storeandrun-label" : u"Konfiguration speichern und Syncplay starten", + "contact-label" : u"Have an idea, bug report or feedback? E-mail dev@syncplay.pl, chat via the #Syncplay IRC channel on irc.freenode.net or raise an issue via GitHub. Also check out http://syncplay.pl/ for info, help and updates.", #TODO: Translate into German + + "joinroom-guibuttonlabel" : u"Join room", # TODO: Translate into German + "seektime-guibuttonlabel" : u"Seek to time", # TODO: Translate into German + "undoseek-guibuttonlabel" : u"Undo seek", # TODO: Translate into German + "togglepause-guibuttonlabel" : u"Toggle pause", # TODO: Translate into German + "play-guibuttonlabel" : u"Play", # TODO: Translate into German + "pause-guibuttonlabel" : u"Pause", # TODO: Translate into German + "roomuser-heading-label" : u"Raum / Benutzer", + "size-heading-label" : u"Size", # TODO: Translate into German (Note: This is size as in filesize) + "duration-heading-label" : u"Length", # TODO: Translate into German (Note: This is length as in file duration) + "filename-heading-label" : u"Filename", # TODO: Translate into German + "notifications-heading-label" : u"Notifications", # TODO: Translate into German + "userlist-heading-label" : u"List of who is playing what", # TODO: Translate into German + "othercommands-heading-label" : u"Other commands", # TODO: Translate into German + "room-heading-label" : "Room", # TODO: Translate into German + "seek-heading-label" : "Seek", # TODO: Translate into German + + "browseformedia-label" : u"Browse for media files", # TODO: Translate into German + + "file-menu-label" : u"&File", # & precedes shortcut key # TODO: Translate into German + "openmedia-menu-label" : u"&Open media file", # TODO: Translate into German + "exit-menu-label" : u"E&xit", # TODO: Translate into German + "advanced-menu-label" : u"&Advanced", # TODO: Translate into German + "setoffset-menu-label" : u"Set &offset", # TODO: Translate into German + "createcontrolledroom-menu-label" : u"&Create controlled room", # TODO: Translate into German + "identifyascontroller-menu-label" : u"&Identify as room controller", # TODO: Translate into German + + "help-menu-label" : u"&Help", # TODO: Translate into German + "userguide-menu-label" : u"Open user &guide", # TODO: Translate into German + + "setoffset-msgbox-label" : u"Set offset", # TODO: Translate into German + "offsetinfo-msgbox-label" : u"Offset (see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into German + + + "createcontrolledroom-msgbox-label" : u"Create controlled room", # TODO: Translate into German + "controlledroominfo-msgbox-label" : u"Enter name of controlled room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into German + + "identifyascontroller-msgbox-label" : u"Identify as Room Controller", # TODO: Translate into German + "identifyinfo-msgbox-label" : u"Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into German + + "megabyte-suffix" : " MB", # TODO: Translate into German (or is MB fine?) # Tooltips @@ -762,6 +852,9 @@ de = { "media-path-tooltip" : u"Pfad zum wiederzugebenden Video oder Stream. Notwendig für mpv und mplayer2.", "more-tooltip" : u"Weitere Einstellungen anzeigen.", + "slowdown-threshold-tooltip" : u"Time ahead of slowest client before temporarily reducing playback speed (default: {} secs).".format(constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD), # Todo: Translate into German + "rewind-threshold-tooltip" : u"Time ahead slowest client before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_REWIND_THRESHOLD), # Todo: Translate into German + "fastforward-threshold-tooltip" : u"Time behind room controller before seeking to get back in sync (default: {} secs).".format(constants.DEFAULT_FASTFORWARD_THRESHOLD), # Todo: Translate into German "filename-privacy-tooltip" : u"Privat-Modus beim senden des Namens der aktuellen Datei zum Server.", "filesize-privacy-tooltip" : u"Privat-Modus beim senden der Größe der aktuellen Datei zum Server.", "privacy-sendraw-tooltip" : u"Die Information im Klartext übertragen. Dies ist die Standard-Einstellung mit der besten Funktionalität.", @@ -770,11 +863,37 @@ de = { "slowondesync-tooltip" : u"Reduziert die Abspielgeschwindigkeit zeitweise, um dich wieder synchron zu den anderen Clients zu machen.", "dontslowdownwithme-tooltip" : u"Lässt andere nicht langsamer werden oder zurückspringen, wenn deine Wiedergabe hängt.", "pauseonleave-tooltip" : u"Wiedergabe anhalten, wenn deine Verbindung verloren geht oder jemand den Raum verlässt.", - "rewindondesync-tooltip" : u"In der Zeit zurückspringen zum wiederherstellen der Synchronität. Empfohlen.", + "rewindondesync-label" : u"Zurückspulen bei großer Zeitdifferent (empfohlen)", + "fastforwardondesync-label" : u"Fast-forward if lagging behind (recommended)", # TODO: Translate into German "forceguiprompt-tooltip" : u"Configuration dialogue is not shown when opening a file with Syncplay.", # TODO: Translate into German (was "Der Konfigurations-Dislog wird immer angezeigt. Sogar, wenn du eine Datei mit Syncplay öffnest." but logic is now inverted) "nostore-tooltip" : u"Syncplay mit den angegebenen Einstellungen starten, diese aber nicht fauerhaft speichern.", + "rewindondesync-tooltip" : u"In der Zeit zurückspringen zum wiederherstellen der Synchronität. Empfohlen.", + "fastforwardondesync-tooltip" : u"Jump forward when out of sync with room controller (or your pretend position if 'Never slow down or rewind others' enabled).", # Translate into German (if you want) + "showosd-tooltip" : u"Sends Syncplay messages to media player OSD.", # Translate into German (if you want) + "showosdwarnings-tooltip" : u"Show warnings if playing different file, alone in room.", # TODO: Translate into German (if you want) + "showsameroomosd-tooltip" : u"Show OSD notifications for events relating to room user is in.", # TODO: Translate into German (if you want) + "shownoncontrollerosd-tooltip" : u"Show OSD notifications for events relating to non-controllers who are in controllerd rooms.", # Translate into German (if you want) + "showdifferentroomosd-tooltip" : u"Show OSD notifications for events relating to room user is not in.", # TODO: Translate into German (if you want) + "showslowdownosd-tooltip" : u"Show notifications of slowing down / reverting on time difference.", # TODO: Translate into German (if you want) + "showcontactinfo-tooltip" : u"Show information box about contacting Syncplay developers in main Syncplay window.", # TODO: Translate into German (if you want) + "showdurationnotification-tooltip" : u"Useful for when a segment in a multi-part file is missing, but can result in false positives.", # TODO: Translate into German (if you want) + "language-tooltip" : u"Language to be used by Syncplay.", # TODO: Translate into German (if you want) "help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]", + "reset-tooltip" : "Reset all settings to the default configuration.", # TODO: Translate into German + + "togglepause-tooltip" : u"Pause/unpause media.", # TODO: Translate into German (if you want) + "play-tooltip" : u"Unpause media.", # TODO: Translate into German (if you want) + "pause-tooltip" : u"Pause media.", # TODO: Translate into German (if you want) + "undoseek-tooltip" : u"Seek to where you were before the most recent seek.", # TODO: Translate into German(if you want) + "joinroom-tooltip" : u"Leave current room and joins specified room.", # TODO: Translate into German(if you want) + "seektime-tooltip" : u"Jump to specified time (in seconds / min:sec). Use +/- for relative seek.", # TODO: Translate into German + + # In-userlist notes (GUI) + "differentsize-note" : u"Different size!", # TODO: Translate into German + "differentsizeandduration-note" : u"Different size and duration!", # TODO: Translate into German + "differentduration-note" : u"Different duration!", # TODO: Translate into German + "nofile-note" : u"(No file being played)", # TODO: Translate into German # Server messages to client "new-syncplay-available-motd-message" : u" Du nutzt Syncplay Version {}, aber es gibt eine neuere Version auf http://syncplay.pl", # ClientVersion @@ -783,7 +902,7 @@ de = { "welcome-server-notification" : u"Willkommen zum Syncplay-Server, v. {0}", # version "client-connected-room-server-notification" : u"{0}({2}) hat den Raum '{1}' betreten", # username, host, room "client-left-server-notification" : u"{0} hat den Server verlassen", # name - + "no-salt-notification" : "PLEASE NOTE: To allow room control passwords generated by this server instance to still work when the server is restarted, please add the following command line argument when running the Syncplay server in the future: --salt {}", #Salt # TODO: Translate into German # Server arguments "server-argument-description" : u'Anwendung, um mehrere MPlayer, MPC-HC und VLC-Instanzen über das Internet zu synchronisieren. Server', @@ -791,6 +910,7 @@ de = { "server-port-argument" : u'Server TCP-Port', "server-password-argument" : u'Server Passwort', "server-isolate-room-argument" : u'Sollen die Räume isoliert sein?', + "server-salt-argument" : "random string used to generate controlled room passwords", # TODO: Translate into German "server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird", "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 {}.", @@ -801,8 +921,8 @@ de = { "not-known-server-error" : u"Der Server muss dich kennen, bevor du diesen Befehl nutzen kannst", "client-drop-server-error" : u"Client verloren: {} -- {}", # host, error "password-required-server-error" : u"Passwort nötig", - "hello-server-error" : u"Zu wenige Hello-Argumente", - "wrong-password-server-error" : u"Ungültiges Passwort" + "wrong-password-server-error" : u"Ungültiges Passwort", + "hello-server-error" : u"Zu wenige Hello-Argumente" } messages = { From a2768c3c7a66f0ff7e48c871c777f279e62f5072 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 1 Dec 2014 01:41:39 +0000 Subject: [PATCH 140/142] Remove unused messages (English/Russian) --- syncplay/messages.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index fa53f37..89ddbef 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -260,8 +260,6 @@ en = { "showdifferentroomosd-tooltip" : "Show OSD notifications for events relating to room user is not in.", "showslowdownosd-tooltip" :"Show notifications of slowing down / reverting on time difference.", "showcontactinfo-tooltip" : "Show information box about contacting Syncplay developers in main Syncplay window.", - "showbuttonlabels-tooltip" : "Show the text alongside the icons for buttons in the main UI.", - "showtooltips-tooltip" : "Show tooltip help messages when you mouseover an input element in Syncplay.", "showdurationnotification-tooltip" : "Useful for when a segment in a multi-part file is missing, but can result in false positives.", "language-tooltip" : u"Language to be used by Syncplay.", @@ -520,7 +518,6 @@ ru = { "pause-guibuttonlabel" : u"Пауза", "roomuser-heading-label" : u"Комната / Пользователь", - "fileplayed-heading-label" : u"Воспроизводимый файл", "notifications-heading-label" : u"Уведомления", "userlist-heading-label" : u"Кто что смотрит", "othercommands-heading-label" : u"Другие команды", @@ -570,8 +567,6 @@ ru = { "showdifferentroomosd-tooltip" : u"Показывать OSD-уведомления о событиях, относящихся к любым другим комнатам.", "showslowdownosd-tooltip" : u"Показывать уведомления о замедлении или перемотке в целях синхронизации.", "showcontactinfo-tooltip" : u"Показывать информационных блок с контактной информацией разработчиков Syncplay на главном окне Syncplay.", - "showbuttonlabels-tooltip" : u"Показывать текст рядом с иконками на кнопках в основном пользовательском интерфейсе.", - "showtooltips-tooltip" : u"Показывать всплывающие подсказки при наведении мыши на некоторые элементы в окне Syncplay.", "showdurationnotification-tooltip" : u"Полезно, когда сегмент составного файла отсутствует. Возможны ложные срабатывания.", "help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.", @@ -602,8 +597,6 @@ ru = { "server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение", "server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", "server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", - "server-irc-verbose" : u"Должен ли сервер активно сообщать о всех событиях в комнатах?", - "server-irc-config" : u"Путь к конфигурационным файлам IRC-бота.", # Server errors "unknown-command-server-error" : u"Неизвестная команда: {}", # message @@ -613,7 +606,6 @@ ru = { "password-required-server-error" : u"Необходимо указать пароль.", "wrong-password-server-error" : u"Указан неверный пароль.", "hello-server-error" : u"Не хватает аргументов Hello.", - "version-mismatch-server-error" : u"Конфликт версий между клиентом и сервером." } From 736384d35edc36441bacd4383fc80364197525a3 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 1 Dec 2014 01:49:59 +0000 Subject: [PATCH 141/142] Placholder messages should be unicode (German) --- syncplay/messages.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 89ddbef..f195a85 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -667,8 +667,8 @@ de = { "commandlist-notification/pause" : u"\tp - Pausieren / weiter", "commandlist-notification/seek" : u"\t[s][+-]Zeit - zu einer bestimmten Zeit spulen, ohne + oder - wird als absolute Zeit gewertet; Angabe in Sekungen oder Minuten:Sekunden", "commandlist-notification/help" : u"\th - Diese Hilfe", - "commandlist-notification/create" : "\tc [name] - create controlled room using name of current room", # TODO: Translate into German - "commandlist-notification/auth" : "\ta [password] - authenticate as room controller with controller password", # TODO: Translate into German + "commandlist-notification/create" : u"\tc [name] - create controlled room using name of current room", # TODO: Translate into German + "commandlist-notification/auth" : u"\ta [password] - authenticate as room controller with controller password", # TODO: Translate into German "syncplay-version-notification" : u"Syncplay Version: {}", # syncplay.version "more-info-notification" : u"Weitere Informationen auf: {}", # projectURL @@ -693,7 +693,7 @@ de = { "player-path-error" : u"Ungültiger Player-Pfad", "hostname-empty-error" : u"Hostname darf nicht leer sein", "empty-error" : u"{} darf nicht leer sein", # Configuration - "media-player-error": "Media player error: \"{}\"", # Error line # TODO: Translate into German + "media-player-error": u"Media player error: \"{}\"", # Error line # TODO: Translate into German "unable-import-gui-error": u"Could not import GUI libraries. If you do not have PySide installed then you will need to install it for the GUI to work.", # TODO: Translate into German "arguments-missing-error" : u"Notwendige Argumente fehlen, siehe --help", @@ -731,8 +731,8 @@ de = { "clear-gui-data-argument" : u'Setzt die Pfad- und GUI-Fenster-Daten, die in den QSettings gespeichert sind, zurück', "language-argument" : u'language for Syncplay messages (de/en/pl/ru)', # TODO: Translate into German - "version-argument" : 'prints your version', # TODO: Translate into German - "version-message" : "You're using Syncplay version {} ({})", # TODO: Translate into German + "version-argument" : u'prints your version', # TODO: Translate into German + "version-message" : u"You're using Syncplay version {} ({})", # TODO: Translate into German # Client labels "config-window-title" : u"Syncplay Konfiguration", @@ -753,7 +753,7 @@ de = { "rewind-threshold-label" : u"Rewind threshold:", # TODO: Translate into German "fastforward-threshold-label" : u"Fast-forward threshold:", # TODO: Translate into German "never-rewind-value" : u"Never", # TODO: Translate into German - "seconds-suffix" : " secs", # TODO: Translate into German + "seconds-suffix" : u" secs", # TODO: Translate into German (if needed) "privacy-sendraw-option" : u"Klartext senden", "privacy-sendhashed-option" : u"Hash senden", "privacy-dontsend-option" : u"Nicht senden", @@ -785,7 +785,7 @@ de = { "privacy-title" : u"Privacy settings", # TODO: Translate into German "help-label" : u"Hilfe", - "reset-label" : "Restore defaults", # TODO: Translate into German + "reset-label" : u"Restore defaults", # TODO: Translate into German "run-label" : u"Syncplay starten", "storeandrun-label" : u"Konfiguration speichern und Syncplay starten", @@ -805,8 +805,8 @@ de = { "notifications-heading-label" : u"Notifications", # TODO: Translate into German "userlist-heading-label" : u"List of who is playing what", # TODO: Translate into German "othercommands-heading-label" : u"Other commands", # TODO: Translate into German - "room-heading-label" : "Room", # TODO: Translate into German - "seek-heading-label" : "Seek", # TODO: Translate into German + "room-heading-label" : u"Room", # TODO: Translate into German + "seek-heading-label" : u"Seek", # TODO: Translate into German "browseformedia-label" : u"Browse for media files", # TODO: Translate into German @@ -831,7 +831,7 @@ de = { "identifyascontroller-msgbox-label" : u"Identify as Room Controller", # TODO: Translate into German "identifyinfo-msgbox-label" : u"Enter controller password for this room\r\n(see http://syncplay.pl/guide/ for usage instructions):", # TODO: Translate into German - "megabyte-suffix" : " MB", # TODO: Translate into German (or is MB fine?) + "megabyte-suffix" : u" MB", # TODO: Translate into German (or is MB fine?) # Tooltips @@ -872,7 +872,7 @@ de = { "language-tooltip" : u"Language to be used by Syncplay.", # TODO: Translate into German (if you want) "help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]", - "reset-tooltip" : "Reset all settings to the default configuration.", # TODO: Translate into German + "reset-tooltip" : u"Reset all settings to the default configuration.", # TODO: Translate into German "togglepause-tooltip" : u"Pause/unpause media.", # TODO: Translate into German (if you want) "play-tooltip" : u"Unpause media.", # TODO: Translate into German (if you want) @@ -902,7 +902,7 @@ de = { "server-port-argument" : u'Server TCP-Port', "server-password-argument" : u'Server Passwort', "server-isolate-room-argument" : u'Sollen die Räume isoliert sein?', - "server-salt-argument" : "random string used to generate controlled room passwords", # TODO: Translate into German + "server-salt-argument" : u"random string used to generate controlled room passwords", # TODO: Translate into German "server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird", "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 {}.", From 87c68da45d55f34748d6c9d006cd81499cede119 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 1 Dec 2014 01:59:29 +0000 Subject: [PATCH 142/142] Note/copy changes to English messages in German translation --- syncplay/messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index f195a85..8a3eaf6 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -646,7 +646,7 @@ de = { "authenticated-as-controller-notification" : u"<{}> authenticated as a room controller", # TODO: Translate into German "file-different-notification" : u"Deine Datei scheint sich von <{}>s zu unterscheiden", # User - "file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: ", + "file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: ", # controlPassword "room-files-not-same" : u"Nicht alle Dateien im Raum sind gleich", "alone-in-the-room": u"Du bist alleine im Raum", @@ -760,7 +760,7 @@ de = { "filename-privacy-label" : u"Dateiname:", "filesize-privacy-label" : u"Dateigröße:", "slowondesync-label" : u"Verlangsamen wenn nicht synchron", # TODO: Update new wording into German (should translate to "Slow down on minor desync (not supported on MPC-HC)") - "dontslowdownwithme-label" : u"Nie verlangsamen oder andere zurückspulen", + "dontslowdownwithme-label" : u"Nie verlangsamen oder andere zurückspulen", # TODO: Update new wording into German (should state "Experimental" in brackets at the end) "pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt", "forceguiprompt-label" : u"Don't always show this dialog", # TODO: Translate into German (previous was "Diesen Dialog immer anzeigen" but the logic is now inverted) "nostore-label" : u"Diese Konfiguration nicht speichern",