Merge branch 'master' into 1.4.x

This commit is contained in:
Etoh 2017-01-06 11:44:43 +00:00 committed by GitHub
commit d7fb9cb0ec
15 changed files with 219 additions and 26 deletions

View File

@ -661,6 +661,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
'resources/film_edit.png', 'resources/film_edit.png',
'resources/shield_edit.png', 'resources/shield_edit.png',
'resources/shield_add.png', 'resources/shield_add.png',
'resources/email_go.png',
'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng' 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng'
] ]
resources = ["resources/icon.ico", "resources/syncplay.png"] resources = ["resources/icon.ico", "resources/syncplay.png"]
@ -684,7 +685,7 @@ info = dict(
options={'py2exe': { options={'py2exe': {
'dist_dir': OUT_DIR, 'dist_dir': OUT_DIR,
'packages': 'PySide.QtUiTools', 'packages': 'PySide.QtUiTools',
'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl, ast', 'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl, ast, unicodedata',
'excludes': 'venv, _ssl, doctest, pdb, unittest, win32clipboard, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process, Tkinter', 'excludes': 'venv, _ssl, doctest, pdb, unittest, win32clipboard, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process, Tkinter',
'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll', 'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll',
'optimize': 2, 'optimize': 2,

BIN
resources/email_go.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

View File

@ -1,4 +1,4 @@
version = '1.4.0' version = '1.5.0'
milestone = 'Yoitsu' milestone = 'Yoitsu'
release_number = '38' release_number = '39'
projectURL = 'http://syncplay.pl/' projectURL = 'http://syncplay.pl/'

View File

@ -39,7 +39,7 @@ class SyncClientFactory(ClientFactory):
self._timesTried += 1 self._timesTried += 1
self._client.ui.showMessage(getMessage("reconnection-attempt-notification")) self._client.ui.showMessage(getMessage("reconnection-attempt-notification"))
self.reconnecting = True self.reconnecting = True
reactor.callLater(0.1 * (2 ** self._timesTried), connector.connect) reactor.callLater(0.1 * (2 ** min(self._timesTried,5)), connector.connect)
else: else:
message = getMessage("disconnection-notification") message = getMessage("disconnection-notification")
self._client.ui.showErrorMessage(message) self._client.ui.showErrorMessage(message)
@ -71,6 +71,8 @@ class SyncplayClient(object):
self.lastControlPasswordAttempt = None self.lastControlPasswordAttempt = None
self.serverVersion = "0.0.0" self.serverVersion = "0.0.0"
self.serverFeatures = {}
self.lastRewindTime = None self.lastRewindTime = None
self.lastLeftTime = 0 self.lastLeftTime = 0
self.lastPausedOnLeaveTime = None self.lastPausedOnLeaveTime = None
@ -201,7 +203,7 @@ class SyncplayClient(object):
if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD:
self.playlist.advancePlaylistCheck() self.playlist.advancePlaylistCheck()
elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): elif pauseChange and self.serverFeatures["readiness"]:
if currentLength == 0 or currentLength == -1 or\ if currentLength == 0 or currentLength == -1 or\
not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD): not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD):
pauseChange = self._toggleReady(pauseChange, paused) pauseChange = self._toggleReady(pauseChange, paused)
@ -532,13 +534,25 @@ class SyncplayClient(object):
size = 0 size = 0
return filename, size return filename, size
def setServerVersion(self, version): def setServerVersion(self, version, featureList):
self.serverVersion = version self.serverVersion = version
self.checkForFeatureSupport() self.checkForFeatureSupport(featureList)
def checkForFeatureSupport(self): def checkForFeatureSupport(self, featureList):
self.serverFeatures = {
"featureList": utils.meetsMinVersion(self.serverVersion, constants.FEATURE_LIST_MIN_VERSION),
"sharedPlaylists": utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION),
"chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION),
"readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION),
"managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION)
}
if featureList:
self.serverFeatures.update(featureList)
if not utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION): if not utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION):
self.ui.showErrorMessage(getMessage("shared-playlists-not-supported-by-server-error").format(constants.SHARED_PLAYLIST_MIN_VERSION, self.serverVersion)) self.ui.showErrorMessage(getMessage("shared-playlists-not-supported-by-server-error").format(constants.SHARED_PLAYLIST_MIN_VERSION, self.serverVersion))
elif not self.serverFeatures["sharedPlaylists"]:
self.ui.showErrorMessage(getMessage("shared-playlists-disabled-by-server-error"))
self.ui.setFeatures(self.serverFeatures)
def getSanitizedCurrentUserFile(self): def getSanitizedCurrentUserFile(self):
if self.userlist.currentUser.file: if self.userlist.currentUser.file:
@ -572,6 +586,11 @@ class SyncplayClient(object):
if resetAutoplay: if resetAutoplay:
self.resetAutoPlayState() self.resetAutoPlayState()
def sendChat(self,message):
if self._protocol and self._protocol.logged:
message = utils.truncateText(message,constants.MAX_CHAT_MESSAGE_LENGTH)
self._protocol.sendChatMessage(message)
def sendRoom(self): def sendRoom(self):
room = self.userlist.currentUser.room room = self.userlist.currentUser.room
if self._protocol and self._protocol.logged and room: if self._protocol and self._protocol.logged and room:
@ -590,7 +609,11 @@ class SyncplayClient(object):
return self._protocol and self._protocol.logged and self.userlist.currentUser.room return self._protocol and self._protocol.logged and self.userlist.currentUser.room
def sharedPlaylistIsEnabled(self): def sharedPlaylistIsEnabled(self):
return self._config['sharedPlaylistEnabled'] if self.serverFeatures.has_key("sharedPlaylists") and not self.serverFeatures["sharedPlaylists"]:
sharedPlaylistEnabled = False
else:
sharedPlaylistEnabled = self._config['sharedPlaylistEnabled']
return sharedPlaylistEnabled
def connected(self): def connected(self):
readyState = self._config['readyAtStart'] if self.userlist.currentUser.isReady() is None else self.userlist.currentUser.isReady() readyState = self._config['readyAtStart'] if self.userlist.currentUser.isReady() is None else self.userlist.currentUser.isReady()
@ -1025,13 +1048,12 @@ class SyncplayUserlist(object):
self._roomUsersChanged = True self._roomUsersChanged = True
def isReadinessSupported(self): def isReadinessSupported(self):
# TODO: Return False if server is run with --disable-ready
if not utils.meetsMinVersion(self._client.serverVersion,constants.USER_READY_MIN_VERSION): if not utils.meetsMinVersion(self._client.serverVersion,constants.USER_READY_MIN_VERSION):
return False return False
elif self.onlyUserInRoomWhoSupportsReadiness(): elif self.onlyUserInRoomWhoSupportsReadiness():
return False return False
else: else:
return True return self._client.serverFeatures["readiness"]
def isRoomSame(self, room): def isRoomSame(self, room):
if room and self.currentUser.room and self.currentUser.room == room: if room and self.currentUser.room and self.currentUser.room == room:
@ -1314,6 +1336,9 @@ class UiManager(object):
def fileSwitchFoundFiles(self): def fileSwitchFoundFiles(self):
self.__ui.fileSwitchFoundFiles() self.__ui.fileSwitchFoundFiles()
def setFeatures(self, featureList):
self.__ui.setFeatures(featureList)
def showDebugMessage(self, message): def showDebugMessage(self, message):
if constants.DEBUG_MODE and message.rstrip(): if constants.DEBUG_MODE and message.rstrip():
sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip()))

View File

@ -46,7 +46,7 @@ MINIMUM_SLOWDOWN_THRESHOLD = 1.3
SLOWDOWN_RESET_THRESHOLD = 0.1 SLOWDOWN_RESET_THRESHOLD = 0.1
DIFFERENT_DURATION_THRESHOLD = 2.5 DIFFERENT_DURATION_THRESHOLD = 2.5
PROTOCOL_TIMEOUT = 12.5 PROTOCOL_TIMEOUT = 12.5
RECONNECT_RETRIES = 10 RECONNECT_RETRIES = 999
SERVER_STATE_INTERVAL = 1 SERVER_STATE_INTERVAL = 1
WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1
AUTOPLAY_DELAY = 3.0 AUTOPLAY_DELAY = 3.0
@ -55,6 +55,12 @@ SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear
PLAYLIST_MAX_CHARACTERS = 10000 PLAYLIST_MAX_CHARACTERS = 10000
PLAYLIST_MAX_ITEMS = 250 PLAYLIST_MAX_ITEMS = 250
# Maximum character lengths (for client and server)
MAX_CHAT_MESSAGE_LENGTH = 50 # Number of displayed characters
MAX_USERNAME_LENGTH = 12 # Number of displayed characters
MAX_ROOM_NAME_LENGTH = 35 # Number of displayed characters
MAX_FILENAME_LENGTH = 250 # Number of displayed characters
# Options for the File Switch feature: # Options for the File Switch feature:
FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 15.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up) FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 15.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up)
FOLDER_SEARCH_TIMEOUT = 6.0 # Secs - How long to wait until searches in folder to update cache are aborted (after first file is found) FOLDER_SEARCH_TIMEOUT = 6.0 # Secs - How long to wait until searches in folder to update cache are aborted (after first file is found)
@ -66,6 +72,7 @@ FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]"
CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]" CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]"
ROOM_NAME_STRIP_REGEX = u"^(\+)(?P<roomnamebase>.*)(:)(\w{12})$" ROOM_NAME_STRIP_REGEX = u"^(\+)(?P<roomnamebase>.*)(:)(\w{12})$"
COMMANDS_UNDO = ["u", "undo", "revert"] COMMANDS_UNDO = ["u", "undo", "revert"]
COMMANDS_CHAT = ["ch","chat"]
COMMANDS_LIST = ["l", "list", "users"] COMMANDS_LIST = ["l", "list", "users"]
COMMANDS_PAUSE = ["p", "play", "pause"] COMMANDS_PAUSE = ["p", "play", "pause"]
COMMANDS_ROOM = ["r", "room"] COMMANDS_ROOM = ["r", "room"]
@ -81,6 +88,8 @@ MPV_UNRESPONSIVE_THRESHOLD = 60.0
CONTROLLED_ROOMS_MIN_VERSION = "1.3.0" CONTROLLED_ROOMS_MIN_VERSION = "1.3.0"
USER_READY_MIN_VERSION = "1.3.0" USER_READY_MIN_VERSION = "1.3.0"
SHARED_PLAYLIST_MIN_VERSION = "1.4.0" SHARED_PLAYLIST_MIN_VERSION = "1.4.0"
CHAT_MIN_VERSION = "1.5.0"
FEATURE_LIST_MIN_VERSION = "1.5.0"
MPC_PATHS = [ MPC_PATHS = [
r"c:\program files (x86)\mpc-hc\mpc-hc.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-hc.exe",
@ -143,7 +152,8 @@ STYLE_READY_PUSHBUTTON = "QPushButton { text-align: left; padding: 10px 5px 10px
STYLE_AUTO_PLAY_PUSHBUTTON = "QPushButton { text-align: left; padding: 5px 5px 5px 5px; }" STYLE_AUTO_PLAY_PUSHBUTTON = "QPushButton { text-align: left; padding: 5px 5px 5px 5px; }"
STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }" STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }"
STYLE_CONTACT_INFO = u"<span style=\"color: grey\"><strong><small>{}</span><br /><br />" # Contact info message STYLE_CONTACT_INFO = u"<span style=\"color: grey\"><strong><small>{}</span><br /><br />" # Contact info message
STYLE_USERNAME = "color: #367AA9; font-weight:bold;" STYLE_USER_MESSAGE = u"<span style=\"{}\">&lt;{}&gt;</span> {}"
STYLE_USERNAME = u"color: #367AA9; font-weight:bold;"
STYLE_ERRORNOTIFICATION = "color: red;" STYLE_ERRORNOTIFICATION = "color: red;"
STYLE_DIFFERENTITEM_COLOR = 'red' STYLE_DIFFERENTITEM_COLOR = 'red'
STYLE_NOFILEITEM_COLOR = 'blue' STYLE_NOFILEITEM_COLOR = 'blue'
@ -170,6 +180,7 @@ UI_COMMAND_REGEX = r"^(?P<command>[^\ ]+)(?:\ (?P<parameter>.+))?"
UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P<sign>[/+-])?(?P<time>\d{1,9}(?:[^\d\.](?:\d{1,9})){0,2}(?:\.(?:\d{1,3}))?)$" UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P<sign>[/+-])?(?P<time>\d{1,9}(?:[^\d\.](?:\d{1,9})){0,2}(?:\.(?:\d{1,3}))?)$"
UI_SEEK_REGEX = r"^(?:s|seek)?\ ?(?P<sign>[+-])?(?P<time>\d{1,4}(?:[^\d\.](?:\d{1,6})){0,2}(?:\.(?:\d{1,3}))?)$" UI_SEEK_REGEX = r"^(?:s|seek)?\ ?(?P<sign>[+-])?(?P<time>\d{1,4}(?:[^\d\.](?:\d{1,6})){0,2}(?:\.(?:\d{1,3}))?)$"
PARSE_TIME_REGEX = r'(:?(?:(?P<hours>\d+?)[^\d\.])?(?:(?P<minutes>\d+?))?[^\d\.])?(?P<seconds>\d+?)(?:\.(?P<miliseconds>\d+?))?$' PARSE_TIME_REGEX = r'(:?(?:(?P<hours>\d+?)[^\d\.])?(?:(?P<minutes>\d+?))?[^\d\.])?(?P<seconds>\d+?)(?:\.(?P<miliseconds>\d+?))?$'
MESSAGE_WITH_USERNAME_REGEX = "^(<(?P<username>[^<>]+)>)(?P<message>.*)"
SERVER_MAX_TEMPLATE_LENGTH = 10000 SERVER_MAX_TEMPLATE_LENGTH = 10000
PRIVACY_SENDRAW_MODE = "SendRaw" PRIVACY_SENDRAW_MODE = "SendRaw"
PRIVACY_SENDHASHED_MODE = "SendHashed" PRIVACY_SENDHASHED_MODE = "SendHashed"

View File

@ -85,6 +85,7 @@ de = {
"commandlist-notification/toggle" : u"\tt - Bereitschaftsanzeige umschalten", "commandlist-notification/toggle" : u"\tt - Bereitschaftsanzeige umschalten",
"commandlist-notification/create" : u"\tc [name] - erstelle zentral gesteuerten Raum mit dem aktuellen Raumnamen", "commandlist-notification/create" : u"\tc [name] - erstelle zentral gesteuerten Raum mit dem aktuellen Raumnamen",
"commandlist-notification/auth" : u"\ta [password] - authentifiziere als Raumleiter mit Passwort", "commandlist-notification/auth" : u"\ta [password] - authentifiziere als Raumleiter mit Passwort",
"commandlist-notification/chat" : "\tch [message] - send a chat message in a room", # TODO: Translate
"syncplay-version-notification" : u"Syncplay Version: {}", # syncplay.version "syncplay-version-notification" : u"Syncplay Version: {}", # syncplay.version
"more-info-notification" : u"Weitere Informationen auf: {}", # projectURL "more-info-notification" : u"Weitere Informationen auf: {}", # projectURL
@ -135,6 +136,7 @@ de = {
"not-supported-by-server-error" : u"Dieses Feature wird vom Server nicht unterstützt. Es wird ein Server mit Syncplay Version {}+ benötigt, aktuell verwendet wird jedoch Version {}.", #minVersion, serverVersion "not-supported-by-server-error" : u"Dieses Feature wird vom Server nicht unterstützt. Es wird ein Server mit Syncplay Version {}+ benötigt, aktuell verwendet wird jedoch Version {}.", #minVersion, serverVersion
"shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion # TODO: Translate "shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion # TODO: Translate
"shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.", # TODO: Translate
"invalid-seek-value" : u"Ungültige Zeitangabe", "invalid-seek-value" : u"Ungültige Zeitangabe",
"invalid-offset-value" : u"Ungültiger Offset-Wert", "invalid-offset-value" : u"Ungültiger Offset-Wert",
@ -249,6 +251,8 @@ de = {
"autoplay-guipushbuttonlabel" : u"Automatisch abspielen wenn alle bereit sind", "autoplay-guipushbuttonlabel" : u"Automatisch abspielen wenn alle bereit sind",
"autoplay-minimum-label" : u"Minimum an Nutzern:", "autoplay-minimum-label" : u"Minimum an Nutzern:",
"sendmessage-label" : u"Send", # TODO: Translate
"ready-guipushbuttonlabel" : u"Ich bin bereit den Film anzuschauen!", "ready-guipushbuttonlabel" : u"Ich bin bereit den Film anzuschauen!",
"roomuser-heading-label" : u"Raum / Benutzer", "roomuser-heading-label" : u"Raum / Benutzer",
@ -353,6 +357,7 @@ de = {
"ready-tooltip" : u"Zeigt an, ob du bereit zum anschauen bist", "ready-tooltip" : u"Zeigt an, ob du bereit zum anschauen bist",
"autoplay-tooltip" : u"Automatisch abspielen, wenn alle Nutzer bereit sind oder die minimale Nutzerzahl erreicht ist.", "autoplay-tooltip" : u"Automatisch abspielen, wenn alle Nutzer bereit sind oder die minimale Nutzerzahl erreicht ist.",
"switch-to-file-tooltip" : u"Doppelklicken um zu {} zu wechseln", # Filename "switch-to-file-tooltip" : u"Doppelklicken um zu {} zu wechseln", # Filename
"sendmessage-tooltip" : u"Send message to room", # TODO: Translate
# In-userlist notes (GUI) # In-userlist notes (GUI)
"differentsize-note" : u"Verschiedene Größe!", "differentsize-note" : u"Verschiedene Größe!",
@ -378,6 +383,7 @@ de = {
"server-salt-argument" : u"zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird", "server-salt-argument" : u"zufällige Zeichenkette, die zur Erstellung von Passwörtern verwendet wird",
"server-disable-ready-argument" : u"Bereitschaftsfeature deaktivieren", "server-disable-ready-argument" : u"Bereitschaftsfeature deaktivieren",
"server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird", "server-motd-argument": u"Pfad zur Datei, von der die Nachricht des Tages geladen wird",
"server-chat-argument" : "Should chat be disabled?", # TODO: Translate
"server-messed-up-motd-unescaped-placeholders": u"Die Nachricht des Tages hat unmaskierte Platzhalter. Alle $-Zeichen sollten verdoppelt werden ($$).", "server-messed-up-motd-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-messed-up-motd-too-long": u"Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.",

View File

@ -85,6 +85,7 @@ en = {
"commandlist-notification/toggle" : u"\tt - toggles whether you are ready to watch or not", "commandlist-notification/toggle" : u"\tt - toggles whether you are ready to watch or not",
"commandlist-notification/create" : "\tc [name] - create managed room using name of current room", "commandlist-notification/create" : "\tc [name] - create managed room using name of current room",
"commandlist-notification/auth" : "\ta [password] - authenticate as room operator with operator password", "commandlist-notification/auth" : "\ta [password] - authenticate as room operator with operator password",
"commandlist-notification/chat" : "\tch [message] - send a chat message in a room",
"syncplay-version-notification" : "Syncplay version: {}", # syncplay.version "syncplay-version-notification" : "Syncplay version: {}", # syncplay.version
"more-info-notification" : "More info available at: {}", # projectURL "more-info-notification" : "More info available at: {}", # projectURL
@ -133,6 +134,7 @@ en = {
"not-supported-by-server-error" : "This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion "not-supported-by-server-error" : "This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion
"shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion "shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion
"shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.",
"invalid-seek-value" : u"Invalid seek value", "invalid-seek-value" : u"Invalid seek value",
"invalid-offset-value" : u"Invalid offset value", "invalid-offset-value" : u"Invalid offset value",
@ -249,6 +251,8 @@ en = {
"autoplay-guipushbuttonlabel" : u"Play when all ready", "autoplay-guipushbuttonlabel" : u"Play when all ready",
"autoplay-minimum-label" : u"Min users:", "autoplay-minimum-label" : u"Min users:",
"sendmessage-label" : u"Send",
"ready-guipushbuttonlabel" : u"I'm ready to watch!", "ready-guipushbuttonlabel" : u"I'm ready to watch!",
"roomuser-heading-label" : "Room / User", "roomuser-heading-label" : "Room / User",
@ -351,6 +355,7 @@ en = {
"ready-tooltip" : "Indicates whether you are ready to watch.", "ready-tooltip" : "Indicates whether you are ready to watch.",
"autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.", "autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.",
"switch-to-file-tooltip" : u"Double click to switch to {}", # Filename "switch-to-file-tooltip" : u"Double click to switch to {}", # Filename
"sendmessage-tooltip" : u"Send message to room",
# In-userlist notes (GUI) # In-userlist notes (GUI)
"differentsize-note" : "Different size!", "differentsize-note" : "Different size!",
@ -377,6 +382,7 @@ en = {
"server-salt-argument" : "random string used to generate managed room passwords", "server-salt-argument" : "random string used to generate managed room passwords",
"server-disable-ready-argument" : u"disable readiness feature", "server-disable-ready-argument" : u"disable readiness feature",
"server-motd-argument": "path to file from which motd will be fetched", "server-motd-argument": "path to file from which motd will be fetched",
"server-chat-argument" : "Should chat be disabled?",
"server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).",
"server-messed-up-motd-too-long": u"Message of the Day is too long - maximum of {} chars, {} given.", "server-messed-up-motd-too-long": u"Message of the Day is too long - maximum of {} chars, {} given.",
@ -403,6 +409,7 @@ en = {
"addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist", "addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist",
"editplaylist-menu-label": u"Edit playlist", "editplaylist-menu-label": u"Edit playlist",
"open-containing-folder": u"Open folder containing this file",
"addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator "addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator
"addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator "addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator
"openusersstream-menu-label" : u"Open {} stream", # [username]'s "openusersstream-menu-label" : u"Open {} stream", # [username]'s

View File

@ -85,6 +85,7 @@ ru = {
"commandlist-notification/toggle" : u"\tt - переключить статус готов/не готов к просмотру", "commandlist-notification/toggle" : u"\tt - переключить статус готов/не готов к просмотру",
"commandlist-notification/create" : u"\tc [name] - создать управляемую комнату с таким же именем, как у текущей", "commandlist-notification/create" : u"\tc [name] - создать управляемую комнату с таким же именем, как у текущей",
"commandlist-notification/auth" : u"\ta [password] - авторизоваться как оператор комнаты с помощью пароля", "commandlist-notification/auth" : u"\ta [password] - авторизоваться как оператор комнаты с помощью пароля",
"commandlist-notification/chat" : "\tch [message] - send a chat message in a room", # TODO: Translate
"syncplay-version-notification" : u"Версия Syncplay: {}", # syncplay.version "syncplay-version-notification" : u"Версия Syncplay: {}", # syncplay.version
"more-info-notification" : u"Больше информации на {}", # projectURL "more-info-notification" : u"Больше информации на {}", # projectURL
@ -135,6 +136,7 @@ ru = {
"not-supported-by-server-error" : u"Эта возможность не поддерживается сервером. Требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion "not-supported-by-server-error" : u"Эта возможность не поддерживается сервером. Требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion
"shared-playlists-not-supported-by-server-error" : u"Общие списки воспроизведения могут не поддерживаться сервером. Для корректной работы требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion "shared-playlists-not-supported-by-server-error" : u"Общие списки воспроизведения могут не поддерживаться сервером. Для корректной работы требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion
"shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.", # TODO: Translate
"invalid-seek-value" : u"Некорректное значение для перемотки", "invalid-seek-value" : u"Некорректное значение для перемотки",
"invalid-offset-value" : u"Некорректное смещение", "invalid-offset-value" : u"Некорректное смещение",
@ -251,10 +253,12 @@ ru = {
"autoplay-menu-label" : u"Показывать кнопку &автовоспроизведения", "autoplay-menu-label" : u"Показывать кнопку &автовоспроизведения",
"autoplay-guipushbuttonlabel" : u"Стартовать, когда все будут готовы", "autoplay-guipushbuttonlabel" : u"Стартовать, когда все будут готовы",
"autoplay-minimum-label" : u"Минимум зрителей:", "autoplay-minimum-label" : u"Минимум зрителей:",
"sendmessage-label" : u"Send", # TODO: Translate
"ready-guipushbuttonlabel" : u"Я готов", "ready-guipushbuttonlabel" : u"Я готов",
"roomuser-heading-label" : u"Комната / Зритель", "roomuser-heading-label" : u"Комната / Зритель",
"size-heading-label" : u"Размер", "size-heading-label" : u"Размер",
"duration-heading-label" : u"Время", "duration-heading-label" : u"Время",
"filename-heading-label" : u"Имя файла", "filename-heading-label" : u"Имя файла",
@ -353,6 +357,7 @@ ru = {
"ready-tooltip" : u"Показывает, готовы ли Вы к просмотру или нет.", "ready-tooltip" : u"Показывает, готовы ли Вы к просмотру или нет.",
"autoplay-tooltip" : u"Автоматическое воспроизведение, когда все пользователи с индикаторами готовности будут готовы и присутствует достаточное число зрителей.", "autoplay-tooltip" : u"Автоматическое воспроизведение, когда все пользователи с индикаторами готовности будут готовы и присутствует достаточное число зрителей.",
"switch-to-file-tooltip" : u"Кликните два раза для воспроизведения {}", # Filename "switch-to-file-tooltip" : u"Кликните два раза для воспроизведения {}", # Filename
"sendmessage-tooltip" : u"Send message to room", # TODO: Translate
# In-userlist notes (GUI) # In-userlist notes (GUI)
"differentsize-note" : u"Размер файла не совпадает!", "differentsize-note" : u"Размер файла не совпадает!",
@ -378,6 +383,7 @@ ru = {
"server-salt-argument" : u"генерировать пароли к управляемым комнатам на основании указанной строки (соли)", "server-salt-argument" : u"генерировать пароли к управляемым комнатам на основании указанной строки (соли)",
"server-disable-ready-argument" : u"отключить статусы готов/не готов", "server-disable-ready-argument" : u"отключить статусы готов/не готов",
"server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение", "server-motd-argument" : u"путь к файлу, из которого будет извлекаться MOTD-сообщение",
"server-chat-argument" : "Should chat be disabled?", # TODO: Translate
"server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", "server-messed-up-motd-unescaped-placeholders" : u"MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).",
"server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", "server-messed-up-motd-too-long" : u"MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).",

View File

@ -22,6 +22,8 @@ class JSONCommandProtocol(LineReceiver):
self.handleState(message[1]) self.handleState(message[1])
elif command == "Error": elif command == "Error":
self.handleError(message[1]) self.handleError(message[1])
elif command == "Chat":
self.handleChat(message[1])
else: else:
self.dropWithError(getMessage("unknown-command-server-error").format(message[1])) # TODO: log, not drop self.dropWithError(getMessage("unknown-command-server-error").format(message[1])) # TODO: log, not drop
@ -78,10 +80,11 @@ class SyncClientProtocol(JSONCommandProtocol):
version = hello["version"] if hello.has_key("version") 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 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 motd = hello["motd"] if hello.has_key("motd") else None
return username, roomName, version, motd features = hello["features"] if hello.has_key("features") else None
return username, roomName, version, motd, features
def handleHello(self, hello): def handleHello(self, hello):
username, roomName, version, motd = self._extractHelloArguments(hello) username, roomName, version, motd, featureList = self._extractHelloArguments(hello)
if not username or not roomName or not version: if not username or not roomName or not version:
self.dropWithError(getMessage("hello-server-error").format(hello)) self.dropWithError(getMessage("hello-server-error").format(hello))
else: else:
@ -93,7 +96,7 @@ class SyncClientProtocol(JSONCommandProtocol):
self._client.ui.showMessage(getMessage("connected-successful-notification")) self._client.ui.showMessage(getMessage("connected-successful-notification"))
self._client.connected() self._client.connected()
self._client.sendFile() self._client.sendFile()
self._client.setServerVersion(version) self._client.setServerVersion(version, featureList)
def sendHello(self): def sendHello(self):
hello = {} hello = {}
@ -158,6 +161,9 @@ class SyncClientProtocol(JSONCommandProtocol):
self.sendSet({"file": file_}) self.sendSet({"file": file_})
self.sendList() self.sendList()
def sendChatMessage(self,chatMessage):
self.sendMessage({"Chat": chatMessage})
def handleList(self, userList): def handleList(self, userList):
self._client.userlist.clearList() self._client.userlist.clearList()
for room in userList.iteritems(): for room in userList.iteritems():
@ -242,6 +248,10 @@ class SyncClientProtocol(JSONCommandProtocol):
"password": password "password": password
} }
}) })
def handleChat(self,message):
userMessage = message['message']
messageString = u"<{}> {}".format(message['username'], userMessage)
self._client.ui.showMessage(messageString)
def setReady(self, isReady, manuallyInitiated=True): def setReady(self, isReady, manuallyInitiated=True):
self.sendSet({ self.sendSet({
@ -275,6 +285,7 @@ class SyncClientProtocol(JSONCommandProtocol):
class SyncServerProtocol(JSONCommandProtocol): class SyncServerProtocol(JSONCommandProtocol):
def __init__(self, factory): def __init__(self, factory):
self._factory = factory self._factory = factory
self._version = None
self._logged = False self._logged = False
self.clientIgnoringOnTheFly = 0 self.clientIgnoringOnTheFly = 0
self.serverIgnoringOnTheFly = 0 self.serverIgnoringOnTheFly = 0
@ -311,6 +322,12 @@ class SyncServerProtocol(JSONCommandProtocol):
def isLogged(self): def isLogged(self):
return self._logged return self._logged
def meetsMinVersion(self, version):
return self._version >= version
def getVersion(self):
return self._version
def _extractHelloArguments(self, hello): def _extractHelloArguments(self, hello):
roomName = None roomName = None
username = hello["username"] if hello.has_key("username") else None username = hello["username"] if hello.has_key("username") else None
@ -342,10 +359,15 @@ class SyncServerProtocol(JSONCommandProtocol):
else: else:
if not self._checkPassword(serverPassword): if not self._checkPassword(serverPassword):
return return
self._version = version
self._factory.addWatcher(self, username, roomName) self._factory.addWatcher(self, username, roomName)
self._logged = True self._logged = True
self.sendHello(version) self.sendHello(version)
def handleChat(self,chatMessage):
if not self._factory.disableChat:
self._factory.sendChat(self._watcher,chatMessage)
def setWatcher(self, watcher): def setWatcher(self, watcher):
self._watcher = watcher self._watcher = watcher
@ -359,6 +381,7 @@ class SyncServerProtocol(JSONCommandProtocol):
hello["version"] = clientVersion # Used so 1.2.X client works on newer server hello["version"] = clientVersion # Used so 1.2.X client works on newer server
hello["realversion"] = syncplay.version hello["realversion"] = syncplay.version
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion) hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
hello["features"] = self._factory.getFeatures()
self.sendMessage({"Hello": hello}) self.sendMessage({"Hello": hello})
@requireLogged @requireLogged

View File

@ -11,10 +11,11 @@ import codecs
import os import os
from string import Template from string import Template
import argparse import argparse
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText
class SyncFactory(Factory): class SyncFactory(Factory):
def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False): def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False,disableChat=False):
self.isolateRooms = isolateRooms
print getMessage("welcome-server-notification").format(syncplay.version) print getMessage("welcome-server-notification").format(syncplay.version)
if password: if password:
password = hashlib.md5(password).hexdigest() password = hashlib.md5(password).hexdigest()
@ -25,6 +26,7 @@ class SyncFactory(Factory):
self._salt = salt self._salt = salt
self._motdFilePath = motdFilePath self._motdFilePath = motdFilePath
self.disableReady = disableReady self.disableReady = disableReady
self.disableChat = disableChat
if not isolateRooms: if not isolateRooms:
self._roomManager = RoomManager() self._roomManager = RoomManager()
else: else:
@ -40,6 +42,14 @@ class SyncFactory(Factory):
setBy = room.getSetBy() setBy = room.getSetBy()
watcher.sendState(position, paused, doSeek, setBy, forcedUpdate) watcher.sendState(position, paused, doSeek, setBy, forcedUpdate)
def getFeatures(self):
features = dict()
features["isolateRooms"] = self.isolateRooms
features["readiness"] = not self.disableReady
features["managedRooms"] = True
features["chat"] = not self.disableChat
return features
def getMotd(self, userIp, username, room, clientVersion): def getMotd(self, userIp, username, room, clientVersion):
oldClient = False oldClient = False
if constants.WARN_OLD_CLIENTS: if constants.WARN_OLD_CLIENTS:
@ -62,11 +72,13 @@ class SyncFactory(Factory):
return "" return ""
def addWatcher(self, watcherProtocol, username, roomName): def addWatcher(self, watcherProtocol, username, roomName):
roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH)
username = self._roomManager.findFreeUsername(username) username = self._roomManager.findFreeUsername(username)
watcher = Watcher(self, watcherProtocol, username) watcher = Watcher(self, watcherProtocol, username)
self.setWatcherRoom(watcher, roomName, asJoin=True) self.setWatcherRoom(watcher, roomName, asJoin=True)
def setWatcherRoom(self, watcher, roomName, asJoin=False): def setWatcherRoom(self, watcher, roomName, asJoin=False):
roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH)
self._roomManager.moveWatcher(watcher, roomName) self._roomManager.moveWatcher(watcher, roomName)
if asJoin: if asJoin:
self.sendJoinMessage(watcher) self.sendJoinMessage(watcher)
@ -96,7 +108,7 @@ class SyncFactory(Factory):
self._roomManager.broadcast(watcher, l) self._roomManager.broadcast(watcher, l)
def sendJoinMessage(self, watcher): def sendJoinMessage(self, watcher):
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True}) if w != watcher else None l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True, "version": watcher.getVersion()}) if w != watcher else None
self._roomManager.broadcast(watcher, l) self._roomManager.broadcast(watcher, l)
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False)) self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
@ -134,6 +146,11 @@ class SyncFactory(Factory):
except ValueError: except ValueError:
self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name)) self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name))
def sendChat(self,watcher,message):
message = truncateText(message, constants.MAX_CHAT_MESSAGE_LENGTH)
messageDict={"message":message,"username" : watcher.getName()}
self._roomManager.broadcastRoom(watcher, lambda w: w.sendChatMessage(messageDict))
def setReady(self, watcher, isReady, manuallyInitiated=True): def setReady(self, watcher, isReady, manuallyInitiated=True):
watcher.setReady(isReady) watcher.setReady(isReady)
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), manuallyInitiated)) self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), manuallyInitiated))
@ -178,6 +195,7 @@ class RoomManager(object):
return watchers return watchers
def moveWatcher(self, watcher, roomName): def moveWatcher(self, watcher, roomName):
roomName = truncateText(roomName, constants.MAX_ROOM_NAME_LENGTH)
self.removeWatcher(watcher) self.removeWatcher(watcher)
room = self._getRoom(roomName) room = self._getRoom(roomName)
room.addWatcher(watcher) room.addWatcher(watcher)
@ -204,6 +222,7 @@ class RoomManager(object):
del self._rooms[room.getName()] del self._rooms[room.getName()]
def findFreeUsername(self, username): def findFreeUsername(self, username):
username = truncateText(username,constants.MAX_USERNAME_LENGTH)
allnames = [] allnames = []
for room in self._rooms.itervalues(): for room in self._rooms.itervalues():
for watcher in room.getWatchers(): for watcher in room.getWatchers():
@ -378,6 +397,9 @@ class Watcher(object):
reactor.callLater(0.1, self._scheduleSendState) reactor.callLater(0.1, self._scheduleSendState)
def setFile(self, file_): def setFile(self, file_):
print file_
if file_ and file_.has_key("name"):
file_["name"] = truncateText(file_["name"],constants.MAX_FILENAME_LENGTH)
self._file = file_ self._file = file_
self._server.sendFileUpdate(self) self._server.sendFileUpdate(self)
@ -403,6 +425,9 @@ class Watcher(object):
def getName(self): def getName(self):
return self._name return self._name
def getVersion(self):
return self._connector.getVersion()
def getFile(self): def getFile(self):
return self._file return self._file
@ -427,6 +452,10 @@ class Watcher(object):
def sendControlledRoomAuthStatus(self, success, username, room): def sendControlledRoomAuthStatus(self, success, username, room):
self._connector.sendControlledRoomAuthStatus(success, username, room) self._connector.sendControlledRoomAuthStatus(success, username, room)
def sendChatMessage(self,message):
if self._connector.meetsMinVersion(constants.CHAT_MIN_VERSION):
self._connector.sendMessage({"Chat" : message})
def sendSetReady(self, username, isReady, manuallyInitiated=True): def sendSetReady(self, username, isReady, manuallyInitiated=True):
self._connector.sendSetReady(username, isReady, manuallyInitiated) self._connector.sendSetReady(username, isReady, manuallyInitiated)
@ -507,5 +536,6 @@ class ConfigurationGetter(object):
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-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('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument"))
self._argparser.add_argument('--disable-ready', action='store_true', help=getMessage("server-disable-ready-argument")) self._argparser.add_argument('--disable-ready', action='store_true', help=getMessage("server-disable-ready-argument"))
self._argparser.add_argument('--disable-chat', action='store_true', help=getMessage("server-chat-argument"))
self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument")) self._argparser.add_argument('--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")) self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument"))

View File

@ -542,6 +542,9 @@ class ConfigDialog(QtGui.QDialog):
self.defaultroomLabel.setObjectName("room") self.defaultroomLabel.setObjectName("room")
self.defaultroomTextbox.setObjectName("room") self.defaultroomTextbox.setObjectName("room")
self.usernameTextbox.setMaxLength(constants.MAX_USERNAME_LENGTH)
self.defaultroomTextbox.setMaxLength(constants.MAX_ROOM_NAME_LENGTH)
self.connectionSettingsLayout = QtGui.QGridLayout() self.connectionSettingsLayout = QtGui.QGridLayout()
self.connectionSettingsLayout.addWidget(self.hostLabel, 0, 0) self.connectionSettingsLayout.addWidget(self.hostLabel, 0, 0)
self.connectionSettingsLayout.addWidget(self.hostCombobox, 0, 1) self.connectionSettingsLayout.addWidget(self.hostCombobox, 0, 1)

View File

@ -86,6 +86,9 @@ class ConsoleUI(threading.Thread):
def fileSwitchFoundFiles(self): def fileSwitchFoundFiles(self):
pass pass
def setFeatures(self, featureList):
pass
def showMessage(self, message, noTimestamp=False): def showMessage(self, message, noTimestamp=False):
message = message.encode(sys.stdout.encoding, 'replace') message = message.encode(sys.stdout.encoding, 'replace')
if noTimestamp: if noTimestamp:
@ -143,6 +146,9 @@ class ConsoleUI(threading.Thread):
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
elif command.group('command') in constants.COMMANDS_LIST: elif command.group('command') in constants.COMMANDS_LIST:
self._syncplayClient.getUserList() self._syncplayClient.getUserList()
elif command.group('command') in constants.COMMANDS_CHAT:
message= command.group('parameter')
self._syncplayClient.sendChat(message)
elif command.group('command') in constants.COMMANDS_PAUSE: elif command.group('command') in constants.COMMANDS_PAUSE:
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused()) self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
elif command.group('command') in constants.COMMANDS_ROOM: elif command.group('command') in constants.COMMANDS_ROOM:
@ -181,6 +187,7 @@ class ConsoleUI(threading.Thread):
self.showMessage(getMessage("commandlist-notification/toggle"), True) self.showMessage(getMessage("commandlist-notification/toggle"), True)
self.showMessage(getMessage("commandlist-notification/create"), True) self.showMessage(getMessage("commandlist-notification/create"), True)
self.showMessage(getMessage("commandlist-notification/auth"), True) self.showMessage(getMessage("commandlist-notification/auth"), True)
self.showMessage(getMessage("commandlist-notification/chat"), True)
self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True) self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True)
self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True) self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True)

View File

@ -327,11 +327,24 @@ class MainWindow(QtGui.QMainWindow):
# TODO: Prompt user # TODO: Prompt user
return None return None
def setFeatures(self, featureList):
if not featureList["readiness"]:
self.readyPushButton.setEnabled(False)
if not featureList["chat"]:
self.chatFrame.setEnabled(False)
if not featureList["sharedPlaylists"]:
self.playlistGroup.setEnabled(False)
def showMessage(self, message, noTimestamp=False): def showMessage(self, message, noTimestamp=False):
message = unicode(message) message = unicode(message)
message = message.replace(u"&", u"&amp;").replace(u'"', u"&quot;").replace(u"<", u"&lt;").replace(">", u"&gt;") username = None
message = message.replace(u"&lt;", u"<span style=\"{}\">&lt;".format(constants.STYLE_USERNAME)) messageWithUsername = re.match(constants.MESSAGE_WITH_USERNAME_REGEX, message, re.UNICODE)
message = message.replace(u"&gt;", u"&gt;</span>") if messageWithUsername:
username = messageWithUsername.group("username")
message = messageWithUsername.group("message")
message = message.replace(u"&", u"&amp;").replace(u'"', u"&quot;").replace(u"<", u"&lt;").replace(u">", u"&gt;")
if username:
message = constants.STYLE_USER_MESSAGE.format(constants.STYLE_USERNAME, username, message)
message = message.replace(u"\n", u"<br />") message = message.replace(u"\n", u"<br />")
if noTimestamp: if noTimestamp:
self.newMessage(u"{}<br />".format(message)) self.newMessage(u"{}<br />".format(message))
@ -564,6 +577,10 @@ class MainWindow(QtGui.QMainWindow):
if self._syncplayClient.isUntrustedTrustableURI(filename): if self._syncplayClient.isUntrustedTrustableURI(filename):
domain = utils.getDomainFromURL(filename) domain = utils.getDomainFromURL(filename)
menu.addAction(QtGui.QPixmap(resourcespath + u"shield_add.png"),getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain)) menu.addAction(QtGui.QPixmap(resourcespath + u"shield_add.png"),getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain))
if not isURL(filename):
path = self._syncplayClient.fileSwitch.findFilepath(filename)
menu.addAction(QtGui.QPixmap(resourcespath + u"film_folder_edit.png"), getMessage('open-containing-folder'), lambda: utils.open_system_file_browser(path))
else: else:
return return
menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) menu.exec_(self.listTreeView.viewport().mapToGlobal(position))
@ -1025,6 +1042,11 @@ class MainWindow(QtGui.QMainWindow):
self._syncplayClient.playlist.changePlaylist(newPlaylist) self._syncplayClient.playlist.changePlaylist(newPlaylist)
self._syncplayClient.fileSwitch.updateInfo() self._syncplayClient.fileSwitch.updateInfo()
def sendChatMessage(self):
if self.chatInput.text() <> "":
self._syncplayClient.sendChat(self.chatInput.text())
self.chatInput.setText("")
def addTopLayout(self, window): def addTopLayout(self, window):
window.topSplit = self.topSplitter(Qt.Horizontal, self) window.topSplit = self.topSplitter(Qt.Horizontal, self)
@ -1040,12 +1062,29 @@ class MainWindow(QtGui.QMainWindow):
window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
window.outputlabel = QtGui.QLabel(getMessage("notifications-heading-label")) window.outputlabel = QtGui.QLabel(getMessage("notifications-heading-label"))
window.chatInput = QtGui.QLineEdit()
window.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH)
window.chatInput.returnPressed.connect(self.sendChatMessage)
window.chatButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'email_go.png'),
getMessage("sendmessage-label"))
window.chatButton.pressed.connect(self.sendChatMessage)
window.chatLayout = QtGui.QHBoxLayout()
window.chatFrame = QtGui.QFrame()
window.chatFrame.setLayout(self.chatLayout)
window.chatFrame.setContentsMargins(0,0,0,0)
window.chatFrame.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
window.chatLayout.setContentsMargins(0,0,0,0)
self.chatButton.setToolTip(getMessage("sendmessage-tooltip"))
window.chatLayout.addWidget(window.chatInput)
window.chatLayout.addWidget(window.chatButton)
window.chatFrame.setMaximumHeight(window.chatFrame.sizeHint().height())
window.outputFrame = QtGui.QFrame() window.outputFrame = QtGui.QFrame()
window.outputFrame.setLineWidth(0) window.outputFrame.setLineWidth(0)
window.outputFrame.setMidLineWidth(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.outputlabel)
window.outputLayout.addWidget(window.outputbox) window.outputLayout.addWidget(window.outputbox)
window.outputLayout.addWidget(window.chatFrame)
window.outputFrame.setLayout(window.outputLayout) window.outputFrame.setLayout(window.outputLayout)
window.listLayout = QtGui.QVBoxLayout() window.listLayout = QtGui.QVBoxLayout()
@ -1079,6 +1118,7 @@ class MainWindow(QtGui.QMainWindow):
window.listLayout.addWidget(window.listSplit) window.listLayout.addWidget(window.listSplit)
window.roomInput = QtGui.QLineEdit() window.roomInput = QtGui.QLineEdit()
window.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH)
window.roomInput.returnPressed.connect(self.joinRoom) window.roomInput.returnPressed.connect(self.joinRoom)
window.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'), window.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'),
getMessage("joinroom-label")) getMessage("joinroom-label"))

View File

@ -11,6 +11,9 @@ import random
import string import string
import urllib import urllib
import ast import ast
import unicodedata
import platform
import subprocess
folderSearchEnabled = True folderSearchEnabled = True
@ -160,6 +163,22 @@ def blackholeStdoutForFrozenWindow():
sys.stdout = Blackhole() sys.stdout = Blackhole()
del Blackhole del Blackhole
def truncateText(unicodeText, maxLength):
try:
unicodeText = unicodedata.normalize('NFC', unicodeText)
except:
pass
try:
maxSaneLength= maxLength*5
if len(unicodeText) > maxSaneLength:
unicodeText = unicode(unicodeText.encode("utf-8")[:maxSaneLength], "utf-8", errors="ignore")
while len(unicodeText) > maxLength:
unicodeText = unicode(unicodeText.encode("utf-8")[:-1], "utf-8", errors="ignore")
return unicodeText
except:
pass
return ""
# Relate to file hashing / difference checking: # Relate to file hashing / difference checking:
def stripfilename(filename, stripURL): def stripfilename(filename, stripURL):
@ -202,6 +221,11 @@ def hashFilesize(size):
return hashlib.sha256(str(size)).hexdigest()[:12] return hashlib.sha256(str(size)).hexdigest()[:12]
def sameHashed(string1raw, string1hashed, string2raw, string2hashed): def sameHashed(string1raw, string1hashed, string2raw, string2hashed):
try:
if string1raw.lower() == string2raw.lower():
return True
except AttributeError:
pass
if string1raw == string2raw: if string1raw == string2raw:
return True return True
elif string1raw == string2hashed: elif string1raw == string2hashed:
@ -289,6 +313,17 @@ def getDomainFromURL(URL):
except: except:
return None return None
def open_system_file_browser(path):
if isURL(path):
return
path = os.path.dirname(path)
if platform.system() == "Windows":
os.startfile(path)
elif platform.system() == "Darwin":
subprocess.Popen(["open", path])
else:
subprocess.Popen(["xdg-open", path])
def getListOfPublicServers(): def getListOfPublicServers():
try: try:
import urllib, syncplay, sys, messages, json import urllib, syncplay, sys, messages, json

View File

@ -19,6 +19,5 @@ from syncplay.server import SyncFactory, ConfigurationGetter
if __name__ == '__main__': if __name__ == '__main__':
argsGetter = ConfigurationGetter() argsGetter = ConfigurationGetter()
args = argsGetter.getConfiguration() args = argsGetter.getConfiguration()
reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready,args.disable_chat))
reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready))
reactor.run() reactor.run()