Merge remote-tracking branch 'origin/controlledRooms-new'
Conflicts: syncplay/client.py syncplay/constants.py syncplay/players/mplayer.py syncplay/ui/gui.py
@ -616,7 +616,9 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
|
|||||||
'resources/timeline_marker.png','resources/control_play_blue.png',
|
'resources/timeline_marker.png','resources/control_play_blue.png',
|
||||||
'resources/mpc-hc.png','resources/mpc-hc64.png','resources/mplayer.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/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'
|
||||||
]
|
]
|
||||||
resources = ["resources/icon.ico", "resources/syncplay.png"]
|
resources = ["resources/icon.ico", "resources/syncplay.png"]
|
||||||
resources.extend(guiIcons)
|
resources.extend(guiIcons)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 211 B |
BIN
resources/chevrons_right.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
resources/key_go.png
Normal file
|
After Width: | Height: | Size: 744 B |
BIN
resources/lock.png
Normal file
|
After Width: | Height: | Size: 884 B |
BIN
resources/lock_open.png
Normal file
|
After Width: | Height: | Size: 824 B |
BIN
resources/page_white_key.png
Normal file
|
After Width: | Height: | Size: 616 B |
BIN
resources/tick.png
Normal file
|
After Width: | Height: | Size: 537 B |
BIN
resources/user_key.png
Normal file
|
After Width: | Height: | Size: 834 B |
@ -1,3 +1,3 @@
|
|||||||
version = '1.2.9'
|
version = '1.3.0'
|
||||||
milestone = 'Pineapple, Pulverize and Destroy!'
|
milestone = 'Akki'
|
||||||
projectURL = 'http://syncplay.pl/'
|
projectURL = 'http://syncplay.pl/'
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import time
|
|||||||
import re
|
import re
|
||||||
from twisted.internet.protocol import ClientFactory
|
from twisted.internet.protocol import ClientFactory
|
||||||
from twisted.internet import reactor, task
|
from twisted.internet import reactor, task
|
||||||
|
from functools import wraps
|
||||||
from syncplay.protocols import SyncClientProtocol
|
from syncplay.protocols import SyncClientProtocol
|
||||||
from syncplay import utils, constants
|
from syncplay import utils, constants
|
||||||
from syncplay.messages import getMessage, getMissingStrings
|
from syncplay.messages import getMissingStrings, getMessage
|
||||||
import threading
|
|
||||||
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
|
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
|
||||||
PRIVACY_HIDDENFILENAME, FILENAME_STRIP_REGEX
|
PRIVACY_HIDDENFILENAME
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
class SyncClientFactory(ClientFactory):
|
class SyncClientFactory(ClientFactory):
|
||||||
@ -62,14 +62,20 @@ class SyncplayClient(object):
|
|||||||
constants.SHOW_SAME_ROOM_OSD = config['showSameRoomOSD']
|
constants.SHOW_SAME_ROOM_OSD = config['showSameRoomOSD']
|
||||||
constants.SHOW_DURATION_NOTIFICATION = config['showDurationNotification']
|
constants.SHOW_DURATION_NOTIFICATION = config['showDurationNotification']
|
||||||
constants.DEBUG_MODE = config['debug']
|
constants.DEBUG_MODE = config['debug']
|
||||||
|
|
||||||
|
self.controlpasswords = {}
|
||||||
|
self.lastControlPasswordAttempt = None
|
||||||
|
self.serverVersion = "0.0.0"
|
||||||
|
|
||||||
self.lastLeftTime = 0
|
self.lastLeftTime = 0
|
||||||
self.lastLeftUser = u""
|
self.lastLeftUser = u""
|
||||||
self.protocolFactory = SyncClientFactory(self)
|
self.protocolFactory = SyncClientFactory(self)
|
||||||
self.ui = UiManager(self, ui)
|
self.ui = UiManager(self, ui)
|
||||||
self.userlist = SyncplayUserlist(self.ui, self)
|
self.userlist = SyncplayUserlist(self.ui, self)
|
||||||
self._protocol = None
|
self._protocol = None
|
||||||
|
""":type : SyncClientProtocol|None"""
|
||||||
self._player = 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
|
config['room'] = config['name'] # ticket #58
|
||||||
self.defaultRoom = config['room']
|
self.defaultRoom = config['room']
|
||||||
self.playerPositionBeforeLastSeek = 0.0
|
self.playerPositionBeforeLastSeek = 0.0
|
||||||
@ -100,7 +106,8 @@ class SyncplayClient(object):
|
|||||||
|
|
||||||
self._warnings = self._WarningManager(self._player, self.userlist, self.ui)
|
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']:
|
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)
|
||||||
|
|
||||||
if constants.DEBUG_MODE and constants.WARN_ABOUT_MISSING_STRINGS:
|
if constants.DEBUG_MODE and constants.WARN_ABOUT_MISSING_STRINGS:
|
||||||
missingStrings = getMissingStrings()
|
missingStrings = getMissingStrings()
|
||||||
@ -229,7 +236,7 @@ class SyncplayClient(object):
|
|||||||
|
|
||||||
def _changePlayerStateAccordingToGlobalState(self, position, paused, doSeek, setBy):
|
def _changePlayerStateAccordingToGlobalState(self, position, paused, doSeek, setBy):
|
||||||
madeChangeOnPlayer = False
|
madeChangeOnPlayer = False
|
||||||
pauseChanged = paused != self.getGlobalPaused()
|
pauseChanged = paused != self.getGlobalPaused() or paused != self.getPlayerPaused()
|
||||||
diff = self.getPlayerPosition() - position
|
diff = self.getPlayerPosition() - position
|
||||||
if self._lastGlobalUpdate is None:
|
if self._lastGlobalUpdate is None:
|
||||||
madeChangeOnPlayer = self._initPlayerState(position, paused)
|
madeChangeOnPlayer = self._initPlayerState(position, paused)
|
||||||
@ -240,7 +247,7 @@ class SyncplayClient(object):
|
|||||||
madeChangeOnPlayer = self._serverSeeked(position, setBy)
|
madeChangeOnPlayer = self._serverSeeked(position, setBy)
|
||||||
if diff > self._config['rewindThreshold'] and not doSeek and not self._config['rewindOnDesync'] == False:
|
if diff > self._config['rewindThreshold'] and not doSeek and not self._config['rewindOnDesync'] == False:
|
||||||
madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy)
|
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)
|
madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy)
|
||||||
if paused == False and pauseChanged:
|
if paused == False and pauseChanged:
|
||||||
madeChangeOnPlayer = self._serverUnpaused(setBy)
|
madeChangeOnPlayer = self._serverUnpaused(setBy)
|
||||||
@ -323,7 +330,6 @@ class SyncplayClient(object):
|
|||||||
size = os.path.getsize(path)
|
size = os.path.getsize(path)
|
||||||
except OSError: # file not accessible (stream?)
|
except OSError: # file not accessible (stream?)
|
||||||
size = 0
|
size = 0
|
||||||
rawfilename = filename
|
|
||||||
filename, size = self.__executePrivacySettings(filename, size)
|
filename, size = self.__executePrivacySettings(filename, size)
|
||||||
self.userlist.currentUser.setFile(filename, duration, size)
|
self.userlist.currentUser.setFile(filename, duration, size)
|
||||||
self.sendFile()
|
self.sendFile()
|
||||||
@ -339,6 +345,9 @@ class SyncplayClient(object):
|
|||||||
size = 0
|
size = 0
|
||||||
return filename, size
|
return filename, size
|
||||||
|
|
||||||
|
def setServerVersion(self, version):
|
||||||
|
self.serverVersion = version
|
||||||
|
|
||||||
def sendFile(self):
|
def sendFile(self):
|
||||||
file_ = self.userlist.currentUser.file
|
file_ = self.userlist.currentUser.file
|
||||||
if self._protocol and self._protocol.logged and file_:
|
if self._protocol and self._protocol.logged and file_:
|
||||||
@ -364,6 +373,17 @@ class SyncplayClient(object):
|
|||||||
if self._protocol and self._protocol.logged and room:
|
if self._protocol and self._protocol.logged and room:
|
||||||
self._protocol.sendRoomSetting(room)
|
self._protocol.sendRoomSetting(room)
|
||||||
self.getUserList()
|
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):
|
def getRoom(self):
|
||||||
return self.userlist.currentUser.room
|
return self.userlist.currentUser.room
|
||||||
@ -420,6 +440,64 @@ class SyncplayClient(object):
|
|||||||
if promptForAction:
|
if promptForAction:
|
||||||
self.ui.promptFor(getMessage("enter-to-exit-prompt"))
|
self.ui.promptFor(getMessage("enter-to-exit-prompt"))
|
||||||
|
|
||||||
|
def requireMinServerVersion(minVersion):
|
||||||
|
def requireMinVersionDecorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def wrapper(self, *args, **kwds):
|
||||||
|
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(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))
|
||||||
|
self.lastControlPasswordAttempt = controlPassword
|
||||||
|
self._protocol.requestControlledRoom(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(roomName, controlPassword)
|
||||||
|
self.ui.updateRoomName(roomName)
|
||||||
|
|
||||||
|
def stripControlPassword(self, controlPassword):
|
||||||
|
if controlPassword:
|
||||||
|
return re.sub(constants.CONTROL_PASSWORD_STRIP_REGEX, "", controlPassword).upper()
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION)
|
||||||
|
def identifyAsController(self, controlPassword):
|
||||||
|
controlPassword = self.stripControlPassword(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(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(getMessage("authenticated-as-controller-notification").format(username), hideFromOSD)
|
||||||
|
if username == self.userlist.currentUser.username:
|
||||||
|
self.storeControlPassword(roomname, self.lastControlPasswordAttempt)
|
||||||
|
self.ui.userListChange()
|
||||||
|
|
||||||
|
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):
|
class _WarningManager(object):
|
||||||
def __init__(self, player, userlist, ui):
|
def __init__(self, player, userlist, ui):
|
||||||
self._player = player
|
self._player = player
|
||||||
@ -472,6 +550,7 @@ class SyncplayUser(object):
|
|||||||
self.username = username
|
self.username = username
|
||||||
self.room = room
|
self.room = room
|
||||||
self.file = file_
|
self.file = file_
|
||||||
|
self._controller = False
|
||||||
|
|
||||||
def setFile(self, filename, duration, size):
|
def setFile(self, filename, duration, size):
|
||||||
file_ = {
|
file_ = {
|
||||||
@ -490,7 +569,10 @@ class SyncplayUser(object):
|
|||||||
return sameName and sameSize and sameDuration
|
return sameName and sameSize and sameDuration
|
||||||
|
|
||||||
def __lt__(self, other):
|
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):
|
def __repr__(self, *args, **kwargs):
|
||||||
if self.file:
|
if self.file:
|
||||||
@ -498,6 +580,12 @@ class SyncplayUser(object):
|
|||||||
else:
|
else:
|
||||||
return "{}".format(self.username)
|
return "{}".format(self.username)
|
||||||
|
|
||||||
|
def setControllerStatus(self, isController):
|
||||||
|
self._controller = isController
|
||||||
|
|
||||||
|
def isController(self):
|
||||||
|
return self._controller
|
||||||
|
|
||||||
class SyncplayUserlist(object):
|
class SyncplayUserlist(object):
|
||||||
def __init__(self, ui, client):
|
def __init__(self, ui, client):
|
||||||
self.currentUser = SyncplayUser()
|
self.currentUser = SyncplayUser()
|
||||||
@ -544,10 +632,14 @@ class SyncplayUserlist(object):
|
|||||||
message = getMessage("file-differences-notification") + ", ".join(differences)
|
message = getMessage("file-differences-notification") + ", ".join(differences)
|
||||||
self.ui.showMessage(message, not constants.SHOW_OSD_WARNINGS)
|
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=None):
|
||||||
if username == self.currentUser.username:
|
if username == self.currentUser.username:
|
||||||
|
if isController is not None:
|
||||||
|
self.currentUser.setControllerStatus(isController)
|
||||||
return
|
return
|
||||||
user = SyncplayUser(username, room, file_)
|
user = SyncplayUser(username, room, file_)
|
||||||
|
if isController is not None:
|
||||||
|
user.setControllerStatus(isController)
|
||||||
self._users[username] = user
|
self._users[username] = user
|
||||||
if not noMessage:
|
if not noMessage:
|
||||||
self.__showUserChangeMessage(username, room, file_)
|
self.__showUserChangeMessage(username, room, file_)
|
||||||
@ -578,6 +670,8 @@ class SyncplayUserlist(object):
|
|||||||
if self._users.has_key(username):
|
if self._users.has_key(username):
|
||||||
user = self._users[username]
|
user = self._users[username]
|
||||||
oldRoom = user.room if user.room else None
|
oldRoom = user.room if user.room else None
|
||||||
|
if user.room != room:
|
||||||
|
user.setControllerStatus(isController=False)
|
||||||
self.__displayModUserMessage(username, room, file_, user, oldRoom)
|
self.__displayModUserMessage(username, room, file_, user, oldRoom)
|
||||||
user.room = room
|
user.room = room
|
||||||
if file_:
|
if file_:
|
||||||
@ -588,6 +682,13 @@ class SyncplayUserlist(object):
|
|||||||
self.addUser(username, room, file_)
|
self.addUser(username, room, file_)
|
||||||
self.userListChange()
|
self.userListChange()
|
||||||
|
|
||||||
|
def setUserAsController(self, username):
|
||||||
|
if self.currentUser.username == username:
|
||||||
|
self.currentUser.setControllerStatus(True)
|
||||||
|
elif self._users.has_key(username):
|
||||||
|
user = self._users[username]
|
||||||
|
user.setControllerStatus(True)
|
||||||
|
|
||||||
def areAllFilesInRoomSame(self):
|
def areAllFilesInRoomSame(self):
|
||||||
for user in self._users.itervalues():
|
for user in self._users.itervalues():
|
||||||
if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file):
|
if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file):
|
||||||
@ -658,6 +759,9 @@ class UiManager(object):
|
|||||||
if constants.SHOW_OSD and self._client._player:
|
if constants.SHOW_OSD and self._client._player:
|
||||||
self._client._player.displayMessage(message, duration * 1000)
|
self._client._player.displayMessage(message, duration * 1000)
|
||||||
|
|
||||||
|
def setControllerStatus(self, username, isController):
|
||||||
|
self.__ui.setControllerStatus(username, isController)
|
||||||
|
|
||||||
def showErrorMessage(self, message, criticalerror=False):
|
def showErrorMessage(self, message, criticalerror=False):
|
||||||
if message <> self.lastError: # Avoid double call bug
|
if message <> self.lastError: # Avoid double call bug
|
||||||
self.lastError = message
|
self.lastError = message
|
||||||
@ -672,5 +776,10 @@ class UiManager(object):
|
|||||||
def markEndOfUserlist(self):
|
def markEndOfUserlist(self):
|
||||||
self.__ui.markEndOfUserlist()
|
self.__ui.markEndOfUserlist()
|
||||||
|
|
||||||
|
def updateRoomName(self, room=""):
|
||||||
|
self.__ui.updateRoomName(room)
|
||||||
|
|
||||||
def drop(self):
|
def drop(self):
|
||||||
self.__ui.drop()
|
self.__ui.drop()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -42,14 +42,19 @@ MERGE_PLAYPAUSE_BUTTONS = False
|
|||||||
SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players
|
SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players
|
||||||
#Usually there's no need to adjust these
|
#Usually there's no need to adjust these
|
||||||
FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]"
|
FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]"
|
||||||
|
CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]"
|
||||||
|
ROOM_NAME_STRIP_REGEX = u"^(\+)(?P<roomnamebase>.*)(:)(\w{12})$"
|
||||||
COMMANDS_UNDO = ["u", "undo", "revert"]
|
COMMANDS_UNDO = ["u", "undo", "revert"]
|
||||||
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"]
|
||||||
COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?']
|
COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?']
|
||||||
|
COMMANDS_CREATE = ['c','create']
|
||||||
|
COMMANDS_AUTH = ['a','auth']
|
||||||
MPC_MIN_VER = "1.6.4"
|
MPC_MIN_VER = "1.6.4"
|
||||||
VLC_MIN_VERSION = "2.0.0"
|
VLC_MIN_VERSION = "2.0.0"
|
||||||
VLC_INTERFACE_MIN_VERSION = "0.2.1"
|
VLC_INTERFACE_MIN_VERSION = "0.2.1"
|
||||||
|
CONTROLLED_ROOMS_MIN_VERSION = "1.3.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",
|
||||||
@ -99,8 +104,8 @@ VLC_MAX_PORT = 55000
|
|||||||
|
|
||||||
#These are not changes you're looking for
|
#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_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_SUBCHECKBOX = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 21px; 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_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_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_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; }"
|
STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }"
|
||||||
@ -108,6 +113,9 @@ STYLE_USERNAME = "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'
|
||||||
|
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', '-af', 'scaletempo']
|
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
|
# --quiet works with both mpv 0.2 and 0.3
|
||||||
@ -135,3 +143,5 @@ ERROR_MESSAGE_MARKER = "*"
|
|||||||
LOAD_SAVE_MANUALLY_MARKER = "!"
|
LOAD_SAVE_MANUALLY_MARKER = "!"
|
||||||
CONFIG_NAME_MARKER = ":"
|
CONFIG_NAME_MARKER = ":"
|
||||||
CONFIG_VALUE_MARKER = "="
|
CONFIG_VALUE_MARKER = "="
|
||||||
|
USERITEM_CONTROLLER_ROLE = 0
|
||||||
|
USERITEM_READY_ROLE = 1
|
||||||
|
|||||||
@ -31,8 +31,12 @@ en = {
|
|||||||
"playing-notification" : "<{}> is playing '{}' ({})", # User, file, duration
|
"playing-notification" : "<{}> is playing '{}' ({})", # User, file, duration
|
||||||
"playing-notification/room-addendum" : " in room: '{}'", # Room
|
"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-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",
|
"room-files-not-same" : "Not all files played in the room are the same",
|
||||||
"alone-in-the-room": "You're alone in the room",
|
"alone-in-the-room": "You're alone in the room",
|
||||||
|
|
||||||
@ -53,6 +57,8 @@ en = {
|
|||||||
"commandlist-notification/pause" : "\tp - toggle pause",
|
"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/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/help" : "\th - this help",
|
||||||
|
"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
|
"syncplay-version-notification" : "Syncplay version: {}", # syncplay.version
|
||||||
"more-info-notification" : "More info available at: {}", # projectURL
|
"more-info-notification" : "More info available at: {}", # projectURL
|
||||||
|
|
||||||
@ -175,7 +181,9 @@ en = {
|
|||||||
"pause-guibuttonlabel" : "Pause",
|
"pause-guibuttonlabel" : "Pause",
|
||||||
|
|
||||||
"roomuser-heading-label" : "Room / User",
|
"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",
|
"notifications-heading-label" : "Notifications",
|
||||||
"userlist-heading-label" : "List of who is playing what",
|
"userlist-heading-label" : "List of who is playing what",
|
||||||
"othercommands-heading-label" : "Other commands",
|
"othercommands-heading-label" : "Other commands",
|
||||||
@ -189,12 +197,24 @@ en = {
|
|||||||
"exit-menu-label" : "E&xit",
|
"exit-menu-label" : "E&xit",
|
||||||
"advanced-menu-label" : "&Advanced",
|
"advanced-menu-label" : "&Advanced",
|
||||||
"setoffset-menu-label" : "Set &offset",
|
"setoffset-menu-label" : "Set &offset",
|
||||||
|
"createcontrolledroom-menu-label" : "&Create controlled room",
|
||||||
|
"identifyascontroller-menu-label" : "&Identify as room controller",
|
||||||
|
|
||||||
"help-menu-label" : "&Help",
|
"help-menu-label" : "&Help",
|
||||||
"userguide-menu-label" : "Open user &guide",
|
"userguide-menu-label" : "Open user &guide",
|
||||||
|
|
||||||
"setoffset-msgbox-label" : "Set offset",
|
"setoffset-msgbox-label" : "Set offset",
|
||||||
"offsetinfo-msgbox-label" : "Offset (see http://syncplay.pl/guide/ for usage instructions):",
|
"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
|
# 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.",
|
"host-tooltip" : "Hostname or IP to connect to, optionally including port (e.g. syncplay.pl:8999). Only synchronised with people on same server/port.",
|
||||||
@ -253,6 +273,7 @@ en = {
|
|||||||
"welcome-server-notification" : "Welcome to Syncplay server, ver. {0}", # version
|
"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-connected-room-server-notification" : "{0}({2}) connected to room '{1}'", # username, host, room
|
||||||
"client-left-server-notification" : "{0} left server", # name
|
"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
|
# Server arguments
|
||||||
@ -261,11 +282,10 @@ en = {
|
|||||||
"server-port-argument" : 'server TCP port',
|
"server-port-argument" : 'server TCP port',
|
||||||
"server-password-argument" : 'server password',
|
"server-password-argument" : 'server password',
|
||||||
"server-isolate-room-argument" : 'should rooms be isolated?',
|
"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-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-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-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
|
# Server errors
|
||||||
"unknown-command-server-error" : "Unknown command {}", # message
|
"unknown-command-server-error" : "Unknown command {}", # message
|
||||||
@ -275,9 +295,6 @@ en = {
|
|||||||
"password-required-server-error" : "Password required",
|
"password-required-server-error" : "Password required",
|
||||||
"wrong-password-server-error" : "Wrong password supplied",
|
"wrong-password-server-error" : "Wrong password supplied",
|
||||||
"hello-server-error" : "Not enough Hello arguments",
|
"hello-server-error" : "Not enough Hello arguments",
|
||||||
"version-mismatch-server-error" : "Mismatch between versions of client and server"
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pl = {
|
pl = {
|
||||||
|
|||||||
@ -145,6 +145,14 @@ class MplayerPlayer(BasePlayer):
|
|||||||
def lineReceived(self, line):
|
def lineReceived(self, line):
|
||||||
if line:
|
if line:
|
||||||
self._client.ui.showDebugMessage("player << {}".format(line))
|
self._client.ui.showDebugMessage("player << {}".format(line))
|
||||||
|
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)
|
match = self.RE_ANSWER.match(line)
|
||||||
if not match:
|
if not match:
|
||||||
self._handleUnknownLine(line)
|
self._handleUnknownLine(line)
|
||||||
|
|||||||
@ -76,6 +76,7 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
username = hello["username"] if hello.has_key("username") else None
|
username = hello["username"] if hello.has_key("username") else None
|
||||||
roomName = hello["room"]["name"] if hello.has_key("room") 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["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
|
motd = hello["motd"] if hello.has_key("motd") else None
|
||||||
return username, roomName, version, motd
|
return username, roomName, version, motd
|
||||||
|
|
||||||
@ -83,8 +84,6 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
username, roomName, version, motd = self._extractHelloArguments(hello)
|
username, roomName, version, motd = 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))
|
||||||
elif version.split(".")[0:2] != syncplay.version.split(".")[0:2]:
|
|
||||||
self.dropWithError(getMessage("version-mismatch-server-error".format(hello)))
|
|
||||||
else:
|
else:
|
||||||
self._client.setUsername(username)
|
self._client.setUsername(username)
|
||||||
self._client.setRoom(roomName)
|
self._client.setRoom(roomName)
|
||||||
@ -92,7 +91,9 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
if motd:
|
if motd:
|
||||||
self._client.ui.showMessage(motd, True, True)
|
self._client.ui.showMessage(motd, True, True)
|
||||||
self._client.ui.showMessage(getMessage("connected-successful-notification"))
|
self._client.ui.showMessage(getMessage("connected-successful-notification"))
|
||||||
|
self._client.connected()
|
||||||
self._client.sendFile()
|
self._client.sendFile()
|
||||||
|
self._client.setServerVersion(version)
|
||||||
|
|
||||||
def sendHello(self):
|
def sendHello(self):
|
||||||
hello = {}
|
hello = {}
|
||||||
@ -101,7 +102,8 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
if password: hello["password"] = password
|
if password: hello["password"] = password
|
||||||
room = self._client.getRoom()
|
room = self._client.getRoom()
|
||||||
if room: hello["room"] = {"name" :room}
|
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})
|
self.sendMessage({"Hello": hello})
|
||||||
|
|
||||||
def _SetUser(self, users):
|
def _SetUser(self, users):
|
||||||
@ -119,13 +121,21 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
self._client.userlist.modUser(username, room, file_)
|
self._client.userlist.modUser(username, room, file_)
|
||||||
|
|
||||||
def handleSet(self, settings):
|
def handleSet(self, settings):
|
||||||
for set_ in settings.iteritems():
|
for (command, values) in settings.iteritems():
|
||||||
command = set_[0]
|
|
||||||
if command == "room":
|
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)
|
self._client.setRoom(roomName)
|
||||||
elif command == "user":
|
elif command == "user":
|
||||||
self._SetUser(set_[1])
|
self._SetUser(values)
|
||||||
|
elif command == "controllerAuth":
|
||||||
|
if values['success']:
|
||||||
|
self._client.controllerIdentificationSuccess(values["user"], values["room"])
|
||||||
|
else:
|
||||||
|
self._client.controllerIdentificationError(values["user"], values["room"])
|
||||||
|
elif command == "newControlledRoom":
|
||||||
|
controlPassword = values['password']
|
||||||
|
roomName = values['roomName']
|
||||||
|
self._client.controlledRoomCreated(roomName, controlPassword)
|
||||||
|
|
||||||
def sendSet(self, setting):
|
def sendSet(self, setting):
|
||||||
self.sendMessage({"Set": setting})
|
self.sendMessage({"Set": setting})
|
||||||
@ -147,7 +157,8 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
for user in room[1].iteritems():
|
for user in room[1].iteritems():
|
||||||
userName = user[0]
|
userName = user[0]
|
||||||
file_ = user[1]['file'] if user[1]['file'] <> {} else None
|
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()
|
self._client.userlist.showUserList()
|
||||||
|
|
||||||
def sendList(self):
|
def sendList(self):
|
||||||
@ -215,8 +226,16 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
||||||
self.sendMessage({"State": state})
|
self.sendMessage({"State": state})
|
||||||
|
|
||||||
|
def requestControlledRoom(self, room, password):
|
||||||
|
self.sendSet({
|
||||||
|
"controllerAuth": {
|
||||||
|
"room": room,
|
||||||
|
"password": password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
def handleError(self, error):
|
def handleError(self, error):
|
||||||
self.dropWithError(error["message"]) # TODO: more processing and fallbacking
|
self.dropWithError(error["message"])
|
||||||
|
|
||||||
def sendError(self, message):
|
def sendError(self, message):
|
||||||
self.sendMessage({"Error": {"message": message}})
|
self.sendMessage({"Error": {"message": message}})
|
||||||
@ -261,7 +280,7 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
return self._logged
|
return self._logged
|
||||||
|
|
||||||
def _extractHelloArguments(self, hello):
|
def _extractHelloArguments(self, hello):
|
||||||
roomName, roomPassword = None, None
|
roomName = None
|
||||||
username = hello["username"] if hello.has_key("username") else None
|
username = hello["username"] if hello.has_key("username") else None
|
||||||
username = username.strip()
|
username = username.strip()
|
||||||
serverPassword = hello["password"] if hello.has_key("password") else None
|
serverPassword = hello["password"] if hello.has_key("password") else None
|
||||||
@ -269,9 +288,9 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
if room:
|
if room:
|
||||||
roomName = room["name"] if room.has_key("name") else None
|
roomName = room["name"] if room.has_key("name") else None
|
||||||
roomName = roomName.strip()
|
roomName = roomName.strip()
|
||||||
roomPassword = room["password"] if room.has_key("password") else None
|
|
||||||
version = hello["version"] if hello.has_key("version") else None
|
version = hello["version"] if hello.has_key("version") else None
|
||||||
return username, serverPassword, roomName, roomPassword, version
|
version = hello["realversion"] if hello.has_key("realversion") else version
|
||||||
|
return username, serverPassword, roomName, version
|
||||||
|
|
||||||
def _checkPassword(self, serverPassword):
|
def _checkPassword(self, serverPassword):
|
||||||
if self._factory.password:
|
if self._factory.password:
|
||||||
@ -284,15 +303,14 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def handleHello(self, hello):
|
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:
|
if not username or not roomName or not version:
|
||||||
self.dropWithError(getMessage("hello-server-error"))
|
self.dropWithError(getMessage("hello-server-error"))
|
||||||
elif version.split(".")[0:2] != syncplay.version.split(".")[0:2]:
|
return
|
||||||
self.dropWithError(getMessage("version-mismatch-server-error"))
|
|
||||||
else:
|
else:
|
||||||
if not self._checkPassword(serverPassword):
|
if not self._checkPassword(serverPassword):
|
||||||
return
|
return
|
||||||
self._factory.addWatcher(self, username, roomName, roomPassword)
|
self._factory.addWatcher(self, username, roomName)
|
||||||
self._logged = True
|
self._logged = True
|
||||||
self.sendHello(version)
|
self.sendHello(version)
|
||||||
|
|
||||||
@ -306,7 +324,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
userIp = self.transport.getPeer().host
|
userIp = self.transport.getPeer().host
|
||||||
room = self._watcher.getRoom()
|
room = self._watcher.getRoom()
|
||||||
if room: hello["room"] = {"name": room.getName()}
|
if room: hello["room"] = {"name": room.getName()}
|
||||||
hello["version"] = syncplay.version
|
hello["version"] = clientVersion # Used so 1.2.X client works on newer server
|
||||||
|
hello["realversion"] = syncplay.version
|
||||||
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
|
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
|
||||||
self.sendMessage({"Hello": hello})
|
self.sendMessage({"Hello": hello})
|
||||||
|
|
||||||
@ -319,14 +338,34 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
self._factory.setWatcherRoom(self._watcher, roomName)
|
self._factory.setWatcherRoom(self._watcher, roomName)
|
||||||
elif command == "file":
|
elif command == "file":
|
||||||
self._watcher.setFile(set_[1])
|
self._watcher.setFile(set_[1])
|
||||||
|
elif command == "controllerAuth":
|
||||||
|
password = set_[1]["password"] if set_[1].has_key("password") else None
|
||||||
|
room = set_[1]["room"] if set_[1].has_key("room") else None
|
||||||
|
self._factory.authRoomController(self._watcher, password, room)
|
||||||
|
|
||||||
def sendSet(self, setting):
|
def sendSet(self, setting):
|
||||||
self.sendMessage({"Set": setting})
|
self.sendMessage({"Set": setting})
|
||||||
|
|
||||||
|
def sendNewControlledRoom(self, roomName, password):
|
||||||
|
self.sendSet({
|
||||||
|
"newControlledRoom": {
|
||||||
|
"password": password,
|
||||||
|
"roomName": roomName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def sendControlledRoomAuthStatus(self, success, username, roomname):
|
||||||
|
self.sendSet({
|
||||||
|
"controllerAuth": {
|
||||||
|
"user": username,
|
||||||
|
"room": roomname,
|
||||||
|
"success": success
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
def sendUserSetting(self, username, room, file_, event):
|
def sendUserSetting(self, username, room, file_, event):
|
||||||
room = {"name": room.getName()}
|
room = {"name": room.getName()}
|
||||||
user = {}
|
user = {username: {}}
|
||||||
user[username] = {}
|
|
||||||
user[username]["room"] = room
|
user[username]["room"] = room
|
||||||
if file_:
|
if file_:
|
||||||
user[username]["file"] = file_
|
user[username]["file"] = file_
|
||||||
@ -339,7 +378,11 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
if room:
|
if room:
|
||||||
if room.getName() not in userlist:
|
if room.getName() not in userlist:
|
||||||
userlist[room.getName()] = {}
|
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
|
userlist[room.getName()][watcher.getName()] = userFile
|
||||||
|
|
||||||
def sendList(self):
|
def sendList(self):
|
||||||
@ -362,8 +405,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
"position": position if position else 0,
|
"position": position if position else 0,
|
||||||
"paused": paused,
|
"paused": paused,
|
||||||
"doSeek": doSeek,
|
"doSeek": doSeek,
|
||||||
"setBy": setBy.getName()
|
"setBy": setBy.getName() if setBy else None
|
||||||
}
|
}
|
||||||
ping = {
|
ping = {
|
||||||
"latencyCalculation": self._pingService.newTimestamp(),
|
"latencyCalculation": self._pingService.newTimestamp(),
|
||||||
"serverRtt": self._pingService.getRtt()
|
"serverRtt": self._pingService.getRtt()
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
import random
|
||||||
from twisted.internet import task, reactor
|
from twisted.internet import task, reactor
|
||||||
from twisted.internet.protocol import Factory
|
from twisted.internet.protocol import Factory
|
||||||
import syncplay
|
import syncplay
|
||||||
@ -10,13 +11,18 @@ 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
|
||||||
|
|
||||||
class SyncFactory(Factory):
|
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)
|
print getMessage("welcome-server-notification").format(syncplay.version)
|
||||||
if password:
|
if password:
|
||||||
password = hashlib.md5(password).hexdigest()
|
password = hashlib.md5(password).hexdigest()
|
||||||
self.password = password
|
self.password = password
|
||||||
|
if salt is None:
|
||||||
|
salt = RandomStringGenerator.generate_server_salt()
|
||||||
|
print getMessage("no-salt-notification").format(salt)
|
||||||
|
self._salt = salt
|
||||||
self._motdFilePath = motdFilePath
|
self._motdFilePath = motdFilePath
|
||||||
if not isolateRooms:
|
if not isolateRooms:
|
||||||
self._roomManager = RoomManager()
|
self._roomManager = RoomManager()
|
||||||
@ -36,7 +42,7 @@ class SyncFactory(Factory):
|
|||||||
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:
|
||||||
if int(clientVersion.replace(".", "")) < int(constants.RECENT_CLIENT_THRESHOLD.replace(".", "")):
|
if not meetsMinVersion(clientVersion, constants.RECENT_CLIENT_THRESHOLD):
|
||||||
oldClient = True
|
oldClient = True
|
||||||
if self._motdFilePath and os.path.isfile(self._motdFilePath):
|
if self._motdFilePath and os.path.isfile(self._motdFilePath):
|
||||||
tmpl = codecs.open(self._motdFilePath, "r", "utf-8-sig").read()
|
tmpl = codecs.open(self._motdFilePath, "r", "utf-8-sig").read()
|
||||||
@ -54,7 +60,7 @@ class SyncFactory(Factory):
|
|||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def addWatcher(self, watcherProtocol, username, roomName, roomPassword):
|
def addWatcher(self, watcherProtocol, username, roomName):
|
||||||
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)
|
||||||
@ -65,13 +71,16 @@ class SyncFactory(Factory):
|
|||||||
self.sendJoinMessage(watcher)
|
self.sendJoinMessage(watcher)
|
||||||
else:
|
else:
|
||||||
self.sendRoomSwitchMessage(watcher)
|
self.sendRoomSwitchMessage(watcher)
|
||||||
|
if RoomPasswordProvider.isControlledRoom(roomName):
|
||||||
|
for controller in watcher.getRoom().getControllers():
|
||||||
|
watcher.sendControlledRoomAuthStatus(True, controller, roomName)
|
||||||
|
|
||||||
def sendRoomSwitchMessage(self, watcher):
|
def sendRoomSwitchMessage(self, watcher):
|
||||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None)
|
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, None)
|
||||||
self._roomManager.broadcast(watcher, l)
|
self._roomManager.broadcast(watcher, l)
|
||||||
|
|
||||||
def removeWatcher(self, watcher):
|
def removeWatcher(self, watcher):
|
||||||
if watcher.getRoom():
|
if watcher and watcher.getRoom():
|
||||||
self.sendLeftMessage(watcher)
|
self.sendLeftMessage(watcher)
|
||||||
self._roomManager.removeWatcher(watcher)
|
self._roomManager.removeWatcher(watcher)
|
||||||
|
|
||||||
@ -83,21 +92,40 @@ class SyncFactory(Factory):
|
|||||||
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}) if w != watcher else None
|
||||||
self._roomManager.broadcast(watcher, l)
|
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)
|
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), watcher.getFile(), None)
|
||||||
self._roomManager.broadcast(watcher, l)
|
self._roomManager.broadcast(watcher, l)
|
||||||
|
|
||||||
def forcePositionUpdate(self, room, watcher, doSeek):
|
def forcePositionUpdate(self, watcher, doSeek, watcherPauseState):
|
||||||
room = watcher.getRoom()
|
room = watcher.getRoom()
|
||||||
paused, position = room.isPaused(), watcher.getPosition()
|
if room.canControl(watcher):
|
||||||
setBy = watcher
|
paused, position = room.isPaused(), watcher.getPosition()
|
||||||
room.setPosition(watcher.getPosition(), setBy)
|
setBy = watcher
|
||||||
l = lambda w: w.sendState(position, paused, doSeek, setBy, True)
|
l = lambda w: w.sendState(position, paused, doSeek, setBy, True)
|
||||||
self._roomManager.broadcastRoom(watcher, l)
|
room.setPosition(watcher.getPosition(), setBy)
|
||||||
|
self._roomManager.broadcastRoom(watcher, l)
|
||||||
|
else:
|
||||||
|
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):
|
def getAllWatchersForUser(self, forUser):
|
||||||
return self._roomManager.getAllWatchersForUser(forUser)
|
return self._roomManager.getAllWatchersForUser(forUser)
|
||||||
|
|
||||||
|
def authRoomController(self, watcher, password, roomBaseName=None):
|
||||||
|
room = watcher.getRoom()
|
||||||
|
roomName = roomBaseName if roomBaseName else room.getName()
|
||||||
|
try:
|
||||||
|
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(roomName, password, self._salt)
|
||||||
|
watcher.sendNewControlledRoom(newName, password)
|
||||||
|
except ValueError:
|
||||||
|
self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name))
|
||||||
|
|
||||||
|
|
||||||
class RoomManager(object):
|
class RoomManager(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._rooms = {}
|
self._rooms = {}
|
||||||
@ -135,7 +163,10 @@ class RoomManager(object):
|
|||||||
if roomName in self._rooms:
|
if roomName in self._rooms:
|
||||||
return self._rooms[roomName]
|
return self._rooms[roomName]
|
||||||
else:
|
else:
|
||||||
room = Room(roomName)
|
if RoomPasswordProvider.isControlledRoom(roomName):
|
||||||
|
room = ControlledRoom(roomName)
|
||||||
|
else:
|
||||||
|
room = Room(roomName)
|
||||||
self._rooms[roomName] = room
|
self._rooms[roomName] = room
|
||||||
return room
|
return room
|
||||||
|
|
||||||
@ -228,6 +259,44 @@ class Room(object):
|
|||||||
def getSetBy(self):
|
def getSetBy(self):
|
||||||
return self._setBy
|
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):
|
class Watcher(object):
|
||||||
def __init__(self, server, connector, name):
|
def __init__(self, server, connector, name):
|
||||||
self._server = server
|
self._server = server
|
||||||
@ -243,7 +312,7 @@ class Watcher(object):
|
|||||||
|
|
||||||
def setFile(self, file_):
|
def setFile(self, file_):
|
||||||
self._file = file_
|
self._file = file_
|
||||||
self._server.sendFileUpdate(self, file_)
|
self._server.sendFileUpdate(self)
|
||||||
|
|
||||||
def setRoom(self, room):
|
def setRoom(self, room):
|
||||||
self._room = room
|
self._room = room
|
||||||
@ -277,16 +346,22 @@ class Watcher(object):
|
|||||||
def sendSetting(self, user, room, file_, event):
|
def sendSetting(self, user, room, file_, event):
|
||||||
self._connector.sendUserSetting(user, room, file_, event)
|
self._connector.sendUserSetting(user, room, file_, event)
|
||||||
|
|
||||||
|
def sendNewControlledRoom(self, roomBaseName, password):
|
||||||
|
self._connector.sendNewControlledRoom(roomBaseName, password)
|
||||||
|
|
||||||
|
def sendControlledRoomAuthStatus(self, success, username, room):
|
||||||
|
self._connector.sendControlledRoomAuthStatus(success, username, room)
|
||||||
|
|
||||||
def __lt__(self, b):
|
def __lt__(self, b):
|
||||||
if self.getPosition() is None or self._file is None:
|
if self.getPosition() is None or self._file is None:
|
||||||
return False
|
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 True
|
||||||
return self.getPosition() < b.getPosition()
|
return self.getPosition() < b.getPosition()
|
||||||
|
|
||||||
def _scheduleSendState(self):
|
def _scheduleSendState(self):
|
||||||
self._sendStateTimer = task.LoopingCall(self._askForStateUpdate)
|
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):
|
def _askForStateUpdate(self, doSeek=False, forcedUpdate=False):
|
||||||
self._server.sendState(self, doSeek, forcedUpdate)
|
self._server.sendState(self, doSeek, forcedUpdate)
|
||||||
@ -313,18 +388,25 @@ class Watcher(object):
|
|||||||
return False
|
return False
|
||||||
return self._room.isPaused() and not paused or not self._room.isPaused() and paused
|
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):
|
def updateState(self, position, paused, doSeek, messageAge):
|
||||||
pauseChanged = self.__hasPauseChanged(paused)
|
pauseChanged = self.__hasPauseChanged(paused)
|
||||||
self._lastUpdatedOn = time.time()
|
self._lastUpdatedOn = time.time()
|
||||||
if pauseChanged:
|
if pauseChanged:
|
||||||
self.getRoom().setPaused(Room.STATE_PAUSED if paused else Room.STATE_PLAYING, self)
|
self.getRoom().setPaused(Room.STATE_PAUSED if paused else Room.STATE_PLAYING, self)
|
||||||
if position is not None:
|
if position is not None:
|
||||||
if not paused:
|
position = self._updatePositionByAge(messageAge, paused, position)
|
||||||
position += messageAge
|
|
||||||
self.setPosition(position)
|
self.setPosition(position)
|
||||||
if doSeek or pauseChanged:
|
if doSeek or pauseChanged:
|
||||||
self._server.forcePositionUpdate(self._room, self, doSeek)
|
self._server.forcePositionUpdate(self, doSeek, paused)
|
||||||
|
|
||||||
|
def isController(self):
|
||||||
|
return RoomPasswordProvider.isControlledRoom(self._room.getName()) \
|
||||||
|
and self._room.canControl(self)
|
||||||
|
|
||||||
class ConfigurationGetter(object):
|
class ConfigurationGetter(object):
|
||||||
def getConfiguration(self):
|
def getConfiguration(self):
|
||||||
@ -340,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('--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('--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('--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"))
|
||||||
@ -426,7 +426,7 @@ class ConfigDialog(QtGui.QDialog):
|
|||||||
self.desyncFrame.setMidLineWidth(0)
|
self.desyncFrame.setMidLineWidth(0)
|
||||||
|
|
||||||
self.slowdownThresholdLabel = QLabel(getMessage("slowdown-threshold-label"), self)
|
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()
|
self.slowdownThresholdSpinbox = QDoubleSpinBox()
|
||||||
try:
|
try:
|
||||||
@ -443,7 +443,7 @@ class ConfigDialog(QtGui.QDialog):
|
|||||||
self.slowdownThresholdSpinbox.adjustSize()
|
self.slowdownThresholdSpinbox.adjustSize()
|
||||||
|
|
||||||
self.rewindThresholdLabel = QLabel(getMessage("rewind-threshold-label"), self)
|
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()
|
self.rewindThresholdSpinbox = QDoubleSpinBox()
|
||||||
try:
|
try:
|
||||||
rewindThreshold = float(config['rewindThreshold'])
|
rewindThreshold = float(config['rewindThreshold'])
|
||||||
@ -515,22 +515,22 @@ class ConfigDialog(QtGui.QDialog):
|
|||||||
|
|
||||||
self.showSameRoomOSDCheckbox = QCheckBox(getMessage("showsameroomosd-label"))
|
self.showSameRoomOSDCheckbox = QCheckBox(getMessage("showsameroomosd-label"))
|
||||||
self.showSameRoomOSDCheckbox.setObjectName("showSameRoomOSD")
|
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.osdSettingsLayout.addWidget(self.showSameRoomOSDCheckbox)
|
||||||
|
|
||||||
self.showDifferentRoomOSDCheckbox = QCheckBox(getMessage("showdifferentroomosd-label"))
|
self.showDifferentRoomOSDCheckbox = QCheckBox(getMessage("showdifferentroomosd-label"))
|
||||||
self.showDifferentRoomOSDCheckbox.setObjectName("showDifferentRoomOSD")
|
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.osdSettingsLayout.addWidget(self.showDifferentRoomOSDCheckbox)
|
||||||
|
|
||||||
self.slowdownOSDCheckbox = QCheckBox(getMessage("showslowdownosd-label"))
|
self.slowdownOSDCheckbox = QCheckBox(getMessage("showslowdownosd-label"))
|
||||||
self.slowdownOSDCheckbox.setObjectName("showSlowdownOSD")
|
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.osdSettingsLayout.addWidget(self.slowdownOSDCheckbox)
|
||||||
|
|
||||||
self.showOSDWarningsCheckbox = QCheckBox(getMessage("showosdwarnings-label"))
|
self.showOSDWarningsCheckbox = QCheckBox(getMessage("showosdwarnings-label"))
|
||||||
self.showOSDWarningsCheckbox.setObjectName("showOSDWarnings")
|
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.osdSettingsLayout.addWidget(self.showOSDWarningsCheckbox)
|
||||||
|
|
||||||
self.subitems['showOSD'] = ["showSameRoomOSD", "showDifferentRoomOSD", "showSlowdownOSD", "showOSDWarnings"]
|
self.subitems['showOSD'] = ["showSameRoomOSD", "showDifferentRoomOSD", "showSlowdownOSD", "showOSDWarnings"]
|
||||||
|
|||||||
@ -35,7 +35,10 @@ class ConsoleUI(threading.Thread):
|
|||||||
self._executeCommand(data)
|
self._executeCommand(data)
|
||||||
except EOFError:
|
except EOFError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def updateRoomName(self, room=""):
|
||||||
|
pass
|
||||||
|
|
||||||
def promptFor(self, prompt=">", message=""):
|
def promptFor(self, prompt=">", message=""):
|
||||||
if message <> "":
|
if message <> "":
|
||||||
print(message)
|
print(message)
|
||||||
@ -49,14 +52,17 @@ class ConsoleUI(threading.Thread):
|
|||||||
message = u"In room '{}':".format(room)
|
message = u"In room '{}':".format(room)
|
||||||
self.showMessage(message, True)
|
self.showMessage(message, True)
|
||||||
for user in rooms[room]:
|
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:
|
if user.file:
|
||||||
message = u"{} is playing:".format(username)
|
message = u"{} is playing:".format(username)
|
||||||
self.showMessage(message, True)
|
self.showMessage(message, True)
|
||||||
message = u" File: '{}' ({})".format(user.file['name'], formatTime(user.file['duration']))
|
message = u" File: '{}' ({})".format(user.file['name'], formatTime(user.file['duration']))
|
||||||
if currentUser.file:
|
if currentUser.file:
|
||||||
if user.file['name'] == currentUser.file['name'] and user.file['size'] != currentUser.file['size']:
|
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)
|
self.showMessage(message, True)
|
||||||
else:
|
else:
|
||||||
message = u"{} is not playing a file".format(username)
|
message = u"{} is not playing a file".format(username)
|
||||||
@ -74,7 +80,7 @@ class ConsoleUI(threading.Thread):
|
|||||||
|
|
||||||
def showDebugMessage(self, message):
|
def showDebugMessage(self, message):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
def showErrorMessage(self, message, criticalerror = False):
|
def showErrorMessage(self, message, criticalerror = False):
|
||||||
print("ERROR:\t" + message)
|
print("ERROR:\t" + message)
|
||||||
|
|
||||||
@ -134,6 +140,15 @@ class ConsoleUI(threading.Thread):
|
|||||||
|
|
||||||
self._syncplayClient.setRoom(room)
|
self._syncplayClient.setRoom(room)
|
||||||
self._syncplayClient.sendRoom()
|
self._syncplayClient.sendRoom()
|
||||||
|
elif command.group('command') in constants.COMMANDS_CREATE:
|
||||||
|
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)
|
||||||
else:
|
else:
|
||||||
if self._tryAdvancedCommands(data):
|
if self._tryAdvancedCommands(data):
|
||||||
return
|
return
|
||||||
@ -146,6 +161,8 @@ class ConsoleUI(threading.Thread):
|
|||||||
self.showMessage(getMessage("commandlist-notification/pause"), True)
|
self.showMessage(getMessage("commandlist-notification/pause"), True)
|
||||||
self.showMessage(getMessage("commandlist-notification/seek"), True)
|
self.showMessage(getMessage("commandlist-notification/seek"), True)
|
||||||
self.showMessage(getMessage("commandlist-notification/help"), True)
|
self.showMessage(getMessage("commandlist-notification/help"), True)
|
||||||
|
self.showMessage(getMessage("commandlist-notification/create"), True)
|
||||||
|
self.showMessage(getMessage("commandlist-notification/auth"), 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)
|
||||||
|
|
||||||
|
|||||||
@ -1,45 +1,90 @@
|
|||||||
from PySide import QtGui #@UnresolvedImport
|
from PySide import QtGui
|
||||||
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 import utils, constants, version
|
||||||
from syncplay.messages import getMessage
|
from syncplay.messages import getMessage
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import threading
|
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize
|
||||||
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration
|
|
||||||
|
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:
|
||||||
|
currentQAbstractItemModel = indexQModelIndex.model()
|
||||||
|
itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), 0, indexQModelIndex.parent())
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
resourcespath = utils.findWorkingDir() + "\\resources\\"
|
||||||
|
else:
|
||||||
|
resourcespath = utils.findWorkingDir() + "/resources/"
|
||||||
|
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)
|
||||||
|
userReady = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_READY_ROLE)
|
||||||
|
|
||||||
|
if roomController and not controlIconQPixmap.isNull():
|
||||||
|
itemQPainter.drawPixmap (
|
||||||
|
optionQStyleOptionViewItem.rect.x()+6,
|
||||||
|
optionQStyleOptionViewItem.rect.y(),
|
||||||
|
controlIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio))
|
||||||
|
|
||||||
|
if userReady and not tickIconQPixmap.isNull():
|
||||||
|
itemQPainter.drawPixmap (
|
||||||
|
(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()-10),
|
||||||
|
optionQStyleOptionViewItem.rect.y(),
|
||||||
|
crossIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio))
|
||||||
|
isUserRow = indexQModelIndex.parent() != indexQModelIndex.parent().parent()
|
||||||
|
if isUserRow:
|
||||||
|
optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+constants.USERLIST_GUI_USERNAME_OFFSET)
|
||||||
|
QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
|
||||||
|
|
||||||
class MainWindow(QtGui.QMainWindow):
|
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):
|
def addClient(self, client):
|
||||||
self._syncplayClient = client
|
self._syncplayClient = client
|
||||||
self.roomInput.setText(self._syncplayClient.getRoom())
|
self.roomInput.setText(self._syncplayClient.getRoom())
|
||||||
self.config = self._syncplayClient.getConfig()
|
self.config = self._syncplayClient.getConfig()
|
||||||
|
constants.SHOW_BUTTON_LABELS = self.config['showButtonLabels']
|
||||||
try:
|
try:
|
||||||
if self.contactLabel and not self.config['showContactInfo']:
|
if self.contactLabel and not self.config['showContactInfo']:
|
||||||
self.contactLabel.hide()
|
self.contactLabel.hide()
|
||||||
if not self.config['showButtonLabels']:
|
if not constants.SHOW_BUTTON_LABELS:
|
||||||
if constants.MERGE_PLAYPAUSE_BUTTONS:
|
self.hideRoomSeekLabels()
|
||||||
self.playpauseButton.setText("")
|
self.hideMiscLabels()
|
||||||
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())
|
|
||||||
except ():
|
except ():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def promptFor(self, prompt=">", message=""):
|
def promptFor(self, prompt=">", message=""):
|
||||||
#TODO: Prompt user
|
# TODO: Prompt user
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def showMessage(self, message, noTimestamp=False):
|
def showMessage(self, message, noTimestamp=False):
|
||||||
@ -52,86 +97,133 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.newMessage(message + "<br />")
|
self.newMessage(message + "<br />")
|
||||||
else:
|
else:
|
||||||
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
||||||
|
|
||||||
def showUserList(self, currentUser, rooms):
|
def showUserList(self, currentUser, rooms):
|
||||||
self._usertreebuffer = QtGui.QStandardItemModel()
|
self._usertreebuffer = QtGui.QStandardItemModel()
|
||||||
self._usertreebuffer.setColumnCount(2)
|
self._usertreebuffer.setHorizontalHeaderLabels(
|
||||||
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()
|
usertreeRoot = self._usertreebuffer.invisibleRootItem()
|
||||||
|
|
||||||
for room in rooms:
|
for room in rooms:
|
||||||
roomitem = QtGui.QStandardItem(room)
|
roomitem = QtGui.QStandardItem(room)
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setItalic(True)
|
||||||
if room == currentUser.room:
|
if room == currentUser.room:
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setWeight(QtGui.QFont.Bold)
|
font.setWeight(QtGui.QFont.Bold)
|
||||||
roomitem.setFont(font)
|
roomitem.setFont(font)
|
||||||
blankitem = QtGui.QStandardItem("")
|
roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable)
|
||||||
roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable)
|
usertreeRoot.appendRow(roomitem)
|
||||||
blankitem.setFlags(blankitem.flags() & ~Qt.ItemIsEditable)
|
isControlledRoom = RoomPasswordProvider.isControlledRoom(room)
|
||||||
usertreeRoot.appendRow((roomitem, blankitem))
|
|
||||||
|
if isControlledRoom:
|
||||||
|
if room == currentUser.room and currentUser.isController():
|
||||||
|
roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'lock_open.png'))
|
||||||
|
else:
|
||||||
|
roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'lock.png'))
|
||||||
|
else:
|
||||||
|
roomitem.setIcon(QtGui.QIcon(self.resourcespath + 'chevrons_right.png'))
|
||||||
|
|
||||||
for user in rooms[room]:
|
for user in rooms[room]:
|
||||||
useritem = QtGui.QStandardItem(user.username)
|
useritem = QtGui.QStandardItem(user.username)
|
||||||
fileitem = QtGui.QStandardItem("")
|
isController = user.isController()
|
||||||
|
useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE)
|
||||||
if user.file:
|
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:
|
if currentUser.file:
|
||||||
sameName = sameFilename(user.file['name'], currentUser.file['name'])
|
sameName = sameFilename(user.file['name'], currentUser.file['name'])
|
||||||
sameSize = sameFilesize(user.file['size'], currentUser.file['size'])
|
sameSize = sameFilesize(user.file['size'], currentUser.file['size'])
|
||||||
sameDuration = sameFileduration(user.file['duration'], currentUser.file['duration'])
|
sameDuration = sameFileduration(user.file['duration'], currentUser.file['duration'])
|
||||||
sameRoom = room == currentUser.room
|
sameRoom = room == currentUser.room
|
||||||
differentName = not sameName
|
underlinefont = QtGui.QFont()
|
||||||
differentSize = not sameSize
|
underlinefont.setUnderline(True)
|
||||||
differentDuration = not sameDuration
|
if sameRoom:
|
||||||
if sameName or sameRoom:
|
if not sameName:
|
||||||
if differentSize and sameDuration:
|
filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR)))
|
||||||
fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentsize-note")))
|
filenameitem.setFont(underlinefont)
|
||||||
elif differentSize and differentDuration:
|
if not sameSize:
|
||||||
fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentsizeandduration-note")))
|
if currentUser.file is not None and formatSize(user.file['size']) == formatSize(currentUser.file['size']):
|
||||||
elif differentDuration:
|
filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'],precise=True))
|
||||||
fileitem = QtGui.QStandardItem(u"{} ({}) ({})".format(user.file['name'], formatTime(user.file['duration']), getMessage("differentduration-note")))
|
filesizeitem.setFont(underlinefont)
|
||||||
if sameRoom and (differentName or differentSize or differentDuration):
|
filesizeitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR)))
|
||||||
fileitem.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:
|
else:
|
||||||
fileitem = QtGui.QStandardItem(getMessage("nofile-note"))
|
filenameitem = QtGui.QStandardItem(getMessage("nofile-note"))
|
||||||
|
filedurationitem = QtGui.QStandardItem("")
|
||||||
|
filesizeitem = QtGui.QStandardItem("")
|
||||||
if room == currentUser.room:
|
if room == currentUser.room:
|
||||||
fileitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR)))
|
filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR)))
|
||||||
|
font = QtGui.QFont()
|
||||||
if currentUser.username == user.username:
|
if currentUser.username == user.username:
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setWeight(QtGui.QFont.Bold)
|
font.setWeight(QtGui.QFont.Bold)
|
||||||
useritem.setFont(font)
|
if isControlledRoom and not isController:
|
||||||
useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable)
|
if currentUser.username == user.username:
|
||||||
fileitem.setFlags(fileitem.flags() & ~Qt.ItemIsEditable)
|
useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR)))
|
||||||
roomitem.appendRow((useritem, fileitem))
|
useritem.setFont(font)
|
||||||
|
useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable)
|
||||||
|
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.listTreeModel = self._usertreebuffer
|
||||||
self.listTreeView.setModel(self.listTreeModel)
|
self.listTreeView.setModel(self.listTreeModel)
|
||||||
|
self.listTreeView.setItemDelegate(UserlistItemDelegate())
|
||||||
self.listTreeView.setItemsExpandable(False)
|
self.listTreeView.setItemsExpandable(False)
|
||||||
|
self.listTreeView.setRootIsDecorated(False)
|
||||||
self.listTreeView.expandAll()
|
self.listTreeView.expandAll()
|
||||||
self.listTreeView.resizeColumnToContents(0)
|
self.updateListGeometry()
|
||||||
self.listTreeView.resizeColumnToContents(1)
|
|
||||||
|
def updateListGeometry(self):
|
||||||
|
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):
|
def roomClicked(self, item):
|
||||||
while item.parent().row() != -1:
|
while item.parent().row() != -1:
|
||||||
item = item.parent()
|
item = item.parent()
|
||||||
self.joinRoom(item.sibling(item.row(), 0).data())
|
self.joinRoom(item.sibling(item.row(), 0).data())
|
||||||
|
|
||||||
def userListChange(self):
|
def userListChange(self):
|
||||||
self._syncplayClient.showUserList()
|
self._syncplayClient.showUserList()
|
||||||
|
|
||||||
def showErrorMessage(self, message, criticalerror = False):
|
def updateRoomName(self, room=""):
|
||||||
|
self.roomInput.setText(room)
|
||||||
|
|
||||||
|
def showDebugMessage(self, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
def showErrorMessage(self, message, criticalerror=False):
|
||||||
message = unicode(message)
|
message = unicode(message)
|
||||||
if criticalerror:
|
if criticalerror:
|
||||||
QtGui.QMessageBox.critical(self,"Syncplay", message)
|
QtGui.QMessageBox.critical(self, "Syncplay", message)
|
||||||
message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">")
|
message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">")
|
||||||
message = message.replace("\n", "<br />")
|
message = message.replace("\n", "<br />")
|
||||||
message = "<span style=\"{}\">".format(constants.STYLE_ERRORNOTIFICATION) + message + "</span>"
|
message = "<span style=\"{}\">".format(constants.STYLE_ERRORNOTIFICATION) + message + "</span>"
|
||||||
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
||||||
|
|
||||||
def joinRoom(self, room = None):
|
def joinRoom(self, room=None):
|
||||||
if room == None:
|
if room == None:
|
||||||
room = self.roomInput.text()
|
room = self.roomInput.text()
|
||||||
if room == "":
|
if room == "":
|
||||||
if self._syncplayClient.userlist.currentUser.file:
|
if self._syncplayClient.userlist.currentUser.file:
|
||||||
room = self._syncplayClient.userlist.currentUser.file["name"]
|
room = self._syncplayClient.userlist.currentUser.file["name"]
|
||||||
else:
|
else:
|
||||||
room = self._syncplayClient.defaultRoom
|
room = self._syncplayClient.defaultRoom
|
||||||
@ -148,45 +240,108 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
if t is None:
|
if t is None:
|
||||||
return
|
return
|
||||||
if sign:
|
if sign:
|
||||||
t = self._syncplayClient.getGlobalPosition() + sign * t
|
t = self._syncplayClient.getGlobalPosition() + sign * t
|
||||||
self._syncplayClient.setPosition(t)
|
self._syncplayClient.setPosition(t)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.showErrorMessage("Invalid seek value")
|
self.showErrorMessage("Invalid seek value")
|
||||||
|
|
||||||
def undoSeek(self):
|
def undoSeek(self):
|
||||||
tmp_pos = self._syncplayClient.getPlayerPosition()
|
tmp_pos = self._syncplayClient.getPlayerPosition()
|
||||||
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
|
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
|
||||||
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
|
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
|
||||||
|
|
||||||
def togglePause(self):
|
def togglePause(self):
|
||||||
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
|
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
|
||||||
|
|
||||||
def play(self):
|
def play(self):
|
||||||
self._syncplayClient.setPaused(False)
|
self._syncplayClient.setPaused(False)
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
self._syncplayClient.setPaused(True)
|
self._syncplayClient.setPaused(True)
|
||||||
|
|
||||||
def exitSyncplay(self):
|
def exitSyncplay(self):
|
||||||
self._syncplayClient.stop()
|
self._syncplayClient.stop()
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self.exitSyncplay()
|
self.exitSyncplay()
|
||||||
self.saveSettings()
|
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):
|
def loadMediaBrowseSettings(self):
|
||||||
settings = QSettings("Syncplay", "MediaBrowseDialog")
|
settings = QSettings("Syncplay", "MediaBrowseDialog")
|
||||||
settings.beginGroup("MediaBrowseDialog")
|
settings.beginGroup("MediaBrowseDialog")
|
||||||
self.mediadirectory = settings.value("mediadir", "")
|
self.mediadirectory = settings.value("mediadir", "")
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
|
|
||||||
def saveMediaBrowseSettings(self):
|
def saveMediaBrowseSettings(self):
|
||||||
settings = QSettings("Syncplay", "MediaBrowseDialog")
|
settings = QSettings("Syncplay", "MediaBrowseDialog")
|
||||||
settings.beginGroup("MediaBrowseDialog")
|
settings.beginGroup("MediaBrowseDialog")
|
||||||
settings.setValue("mediadir", self.mediadirectory)
|
settings.setValue("mediadir", self.mediadirectory)
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
|
|
||||||
def browseMediapath(self):
|
def browseMediapath(self):
|
||||||
self.loadMediaBrowseSettings()
|
self.loadMediaBrowseSettings()
|
||||||
options = QtGui.QFileDialog.Options()
|
options = QtGui.QFileDialog.Options()
|
||||||
@ -198,16 +353,30 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation)
|
defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation)
|
||||||
else:
|
else:
|
||||||
defaultdirectory = ""
|
defaultdirectory = ""
|
||||||
browserfilter = "All files (*)"
|
browserfilter = "All files (*)"
|
||||||
fileName, filtr = QtGui.QFileDialog.getOpenFileName(self,getMessage("browseformedia-label"),defaultdirectory,
|
fileName, filtr = QtGui.QFileDialog.getOpenFileName(self, getMessage("browseformedia-label"), defaultdirectory,
|
||||||
browserfilter, "", options)
|
browserfilter, "", options)
|
||||||
if fileName:
|
if fileName:
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
fileName = fileName.replace("/","\\")
|
fileName = fileName.replace("/", "\\")
|
||||||
self.mediadirectory = os.path.dirname(fileName)
|
self.mediadirectory = os.path.dirname(fileName)
|
||||||
self.saveMediaBrowseSettings()
|
self.saveMediaBrowseSettings()
|
||||||
self._syncplayClient._player.openFile(fileName)
|
self._syncplayClient._player.openFile(fileName)
|
||||||
|
|
||||||
|
def createControlledRoom(self):
|
||||||
|
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):
|
||||||
|
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)
|
||||||
|
|
||||||
def _extractSign(self, m):
|
def _extractSign(self, m):
|
||||||
if m:
|
if m:
|
||||||
if m == "-":
|
if m == "-":
|
||||||
@ -216,11 +385,11 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def setOffset(self):
|
def setOffset(self):
|
||||||
newoffset, ok = QtGui.QInputDialog.getText(self,getMessage("setoffset-msgbox-label"),
|
newoffset, ok = QtGui.QInputDialog.getText(self, getMessage("setoffset-msgbox-label"),
|
||||||
getMessage("offsetinfo-msgbox-label"), QtGui.QLineEdit.Normal,
|
getMessage("offsetinfo-msgbox-label"), QtGui.QLineEdit.Normal,
|
||||||
"")
|
"")
|
||||||
if ok and newoffset != '':
|
if ok and newoffset != '':
|
||||||
o = re.match(constants.UI_OFFSET_REGEX, "o " + newoffset)
|
o = re.match(constants.UI_OFFSET_REGEX, "o " + newoffset)
|
||||||
if o:
|
if o:
|
||||||
@ -229,13 +398,13 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
if t is None:
|
if t is None:
|
||||||
return
|
return
|
||||||
if o.group('sign') == "/":
|
if o.group('sign') == "/":
|
||||||
t = self._syncplayClient.getPlayerPosition() - t
|
t = self._syncplayClient.getPlayerPosition() - t
|
||||||
elif sign:
|
elif sign:
|
||||||
t = self._syncplayClient.getUserOffset() + sign * t
|
t = self._syncplayClient.getUserOffset() + sign * t
|
||||||
self._syncplayClient.setUserOffset(t)
|
self._syncplayClient.setUserOffset(t)
|
||||||
else:
|
else:
|
||||||
self.showErrorMessage("Invalid offset value")
|
self.showErrorMessage("Invalid offset value")
|
||||||
|
|
||||||
def openUserGuide(self):
|
def openUserGuide(self):
|
||||||
if sys.platform.startswith('linux'):
|
if sys.platform.startswith('linux'):
|
||||||
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/linux/")
|
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/linux/")
|
||||||
@ -246,9 +415,9 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
|
|
||||||
def drop(self):
|
def drop(self):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def addTopLayout(self, window):
|
def addTopLayout(self, window):
|
||||||
window.topSplit = QtGui.QSplitter(Qt.Horizontal)
|
window.topSplit = self.topSplitter(Qt.Horizontal, self)
|
||||||
|
|
||||||
window.outputLayout = QtGui.QVBoxLayout()
|
window.outputLayout = QtGui.QVBoxLayout()
|
||||||
window.outputbox = QtGui.QTextEdit()
|
window.outputbox = QtGui.QTextEdit()
|
||||||
@ -257,21 +426,22 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
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.outputFrame.setLayout(window.outputLayout)
|
window.outputFrame.setLayout(window.outputLayout)
|
||||||
|
|
||||||
window.listLayout = QtGui.QVBoxLayout()
|
window.listLayout = QtGui.QVBoxLayout()
|
||||||
window.listTreeModel = QtGui.QStandardItemModel()
|
window.listTreeModel = QtGui.QStandardItemModel()
|
||||||
window.listTreeView = QtGui.QTreeView()
|
window.listTreeView = QtGui.QTreeView()
|
||||||
window.listTreeView.setModel(window.listTreeModel)
|
window.listTreeView.setModel(window.listTreeModel)
|
||||||
|
window.listTreeView.setIndentation(21)
|
||||||
window.listTreeView.doubleClicked.connect(self.roomClicked)
|
window.listTreeView.doubleClicked.connect(self.roomClicked)
|
||||||
window.listlabel = QtGui.QLabel(getMessage("userlist-heading-label"))
|
window.listlabel = QtGui.QLabel(getMessage("userlist-heading-label"))
|
||||||
window.listFrame = QtGui.QFrame()
|
window.listFrame = QtGui.QFrame()
|
||||||
window.listFrame.setLineWidth(0)
|
window.listFrame.setLineWidth(0)
|
||||||
window.listFrame.setMidLineWidth(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.listlabel)
|
||||||
window.listLayout.addWidget(window.listTreeView)
|
window.listLayout.addWidget(window.listTreeView)
|
||||||
window.contactLabel = QtGui.QLabel()
|
window.contactLabel = QtGui.QLabel()
|
||||||
@ -285,13 +455,13 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
window.contactLabel.setOpenExternalLinks(True)
|
window.contactLabel.setOpenExternalLinks(True)
|
||||||
window.listLayout.addWidget(window.contactLabel)
|
window.listLayout.addWidget(window.contactLabel)
|
||||||
window.listFrame.setLayout(window.listLayout)
|
window.listFrame.setLayout(window.listLayout)
|
||||||
|
|
||||||
window.topSplit.addWidget(window.outputFrame)
|
window.topSplit.addWidget(window.outputFrame)
|
||||||
window.topSplit.addWidget(window.listFrame)
|
window.topSplit.addWidget(window.listFrame)
|
||||||
window.topSplit.setStretchFactor(0,4)
|
window.topSplit.setStretchFactor(0,4)
|
||||||
window.topSplit.setStretchFactor(1,5)
|
window.topSplit.setStretchFactor(1,5)
|
||||||
window.mainLayout.addWidget(window.topSplit)
|
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):
|
def addBottomLayout(self, window):
|
||||||
window.bottomLayout = QtGui.QHBoxLayout()
|
window.bottomLayout = QtGui.QHBoxLayout()
|
||||||
@ -300,134 +470,152 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
window.addSeekBox(MainWindow)
|
window.addSeekBox(MainWindow)
|
||||||
window.addMiscBox(MainWindow)
|
window.addMiscBox(MainWindow)
|
||||||
|
|
||||||
window.bottomLayout.addWidget(window.roomGroup, Qt.AlignLeft)
|
window.bottomLayout.addWidget(window.roomGroup)
|
||||||
window.bottomLayout.addWidget(window.seekGroup, Qt.AlignLeft)
|
window.bottomLayout.addWidget(window.seekGroup)
|
||||||
window.bottomLayout.addWidget(window.miscGroup, Qt.AlignLeft)
|
window.bottomLayout.addWidget(window.miscGroup)
|
||||||
|
|
||||||
window.mainLayout.addLayout(window.bottomLayout, Qt.AlignLeft)
|
window.mainLayout.addLayout(window.bottomLayout, Qt.AlignLeft)
|
||||||
|
|
||||||
def addRoomBox(self, window):
|
def addRoomBox(self, window):
|
||||||
window.roomGroup = QtGui.QGroupBox(getMessage("room-heading-label"))
|
window.roomGroup = QtGui.QGroupBox(getMessage("room-heading-label"))
|
||||||
|
|
||||||
window.roomInput = QtGui.QLineEdit()
|
window.roomInput = QtGui.QLineEdit()
|
||||||
window.roomInput.returnPressed.connect(self.joinRoom)
|
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.roomButton.pressed.connect(self.joinRoom)
|
||||||
window.roomLayout = QtGui.QHBoxLayout()
|
window.roomLayout = QtGui.QHBoxLayout()
|
||||||
window.roomInput.setFixedWidth(150)
|
window.roomInput.setFixedWidth(150)
|
||||||
|
|
||||||
self.roomButton.setToolTip(getMessage("joinroom-tooltip"))
|
self.roomButton.setToolTip(getMessage("joinroom-tooltip"))
|
||||||
|
|
||||||
window.roomLayout.addWidget(window.roomInput)
|
window.roomLayout.addWidget(window.roomInput)
|
||||||
window.roomLayout.addWidget(window.roomButton)
|
window.roomLayout.addWidget(window.roomButton)
|
||||||
|
|
||||||
window.roomGroup.setLayout(window.roomLayout)
|
window.roomGroup.setLayout(window.roomLayout)
|
||||||
window.roomGroup.setFixedSize(window.roomGroup.sizeHint())
|
window.roomGroup.setFixedHeight(window.roomGroup.sizeHint().height())
|
||||||
|
|
||||||
def addSeekBox(self, window):
|
def addSeekBox(self, window):
|
||||||
window.seekGroup = QtGui.QGroupBox(getMessage("seek-heading-label"))
|
window.seekGroup = QtGui.QGroupBox(getMessage("seek-heading-label"))
|
||||||
|
|
||||||
window.seekInput = QtGui.QLineEdit()
|
window.seekInput = QtGui.QLineEdit()
|
||||||
window.seekInput.returnPressed.connect(self.seekPosition)
|
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)
|
window.seekButton.pressed.connect(self.seekPosition)
|
||||||
|
|
||||||
self.seekButton.setToolTip(getMessage("seektime-tooltip"))
|
self.seekButton.setToolTip(getMessage("seektime-tooltip"))
|
||||||
|
|
||||||
window.seekLayout = QtGui.QHBoxLayout()
|
window.seekLayout = QtGui.QHBoxLayout()
|
||||||
window.seekInput.setText("0:00")
|
window.seekInput.setText("0:00")
|
||||||
window.seekInput.setFixedWidth(60)
|
window.seekInput.setFixedWidth(60)
|
||||||
|
|
||||||
window.seekLayout.addWidget(window.seekInput)
|
window.seekLayout.addWidget(window.seekInput)
|
||||||
window.seekLayout.addWidget(window.seekButton)
|
window.seekLayout.addWidget(window.seekButton)
|
||||||
|
|
||||||
window.seekGroup.setLayout(window.seekLayout)
|
window.seekGroup.setLayout(window.seekLayout)
|
||||||
window.seekGroup.setFixedSize(window.seekGroup.sizeHint())
|
window.seekGroup.setFixedHeight(window.seekGroup.sizeHint().height())
|
||||||
|
|
||||||
def addMiscBox(self, window):
|
def addMiscBox(self, window):
|
||||||
window.miscGroup = QtGui.QGroupBox(getMessage("othercommands-heading-label"))
|
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)
|
window.unseekButton.pressed.connect(self.undoSeek)
|
||||||
self.unseekButton.setToolTip(getMessage("undoseek-tooltip"))
|
self.unseekButton.setToolTip(getMessage("undoseek-tooltip"))
|
||||||
|
|
||||||
window.miscLayout = QtGui.QHBoxLayout()
|
window.miscLayout = QtGui.QHBoxLayout()
|
||||||
window.miscLayout.addWidget(window.unseekButton)
|
window.miscLayout.addWidget(window.unseekButton)
|
||||||
if constants.MERGE_PLAYPAUSE_BUTTONS == True:
|
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.playpauseButton.pressed.connect(self.togglePause)
|
||||||
window.miscLayout.addWidget(window.playpauseButton)
|
window.miscLayout.addWidget(window.playpauseButton)
|
||||||
self.playpauseButton.setToolTip(getMessage("togglepause-tooltip"))
|
self.playpauseButton.setToolTip(getMessage("togglepause-tooltip"))
|
||||||
else:
|
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.pressed.connect(self.play)
|
||||||
window.playButton.setMaximumWidth(60)
|
window.playButton.setMaximumWidth(60)
|
||||||
window.miscLayout.addWidget(window.playButton)
|
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.pressed.connect(self.pause)
|
||||||
window.pauseButton.setMaximumWidth(60)
|
window.pauseButton.setMaximumWidth(60)
|
||||||
window.miscLayout.addWidget(window.pauseButton)
|
window.miscLayout.addWidget(window.pauseButton)
|
||||||
self.playButton.setToolTip(getMessage("play-tooltip"))
|
self.playButton.setToolTip(getMessage("play-tooltip"))
|
||||||
self.pauseButton.setToolTip(getMessage("pause-tooltip"))
|
self.pauseButton.setToolTip(getMessage("pause-tooltip"))
|
||||||
|
|
||||||
window.miscGroup.setLayout(window.miscLayout)
|
window.miscGroup.setLayout(window.miscLayout)
|
||||||
window.miscGroup.setFixedSize(window.miscGroup.sizeHint())
|
window.miscGroup.setFixedHeight(window.miscGroup.sizeHint().height())
|
||||||
|
|
||||||
|
|
||||||
def addMenubar(self, window):
|
def addMenubar(self, window):
|
||||||
window.menuBar = QtGui.QMenuBar()
|
window.menuBar = QtGui.QMenuBar()
|
||||||
|
|
||||||
window.fileMenu = QtGui.QMenu(getMessage("file-menu-label"), self)
|
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.openAction.triggered.connect(self.browseMediapath)
|
||||||
window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), getMessage("exit-menu-label"))
|
|
||||||
|
window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'),
|
||||||
|
getMessage("file-menu-label"))
|
||||||
window.exitAction.triggered.connect(self.exitSyncplay)
|
window.exitAction.triggered.connect(self.exitSyncplay)
|
||||||
window.menuBar.addMenu(window.fileMenu)
|
window.menuBar.addMenu(window.fileMenu)
|
||||||
|
|
||||||
window.advancedMenu = QtGui.QMenu(getMessage("advanced-menu-label"), self)
|
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.setoffsetAction.triggered.connect(self.setOffset)
|
||||||
|
|
||||||
|
window.createcontrolledroomAction = window.advancedMenu.addAction(
|
||||||
|
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'),
|
||||||
|
getMessage("identifyascontroller-menu-label"))
|
||||||
|
window.identifyascontroller.triggered.connect(self.identifyAsController)
|
||||||
window.menuBar.addMenu(window.advancedMenu)
|
window.menuBar.addMenu(window.advancedMenu)
|
||||||
|
|
||||||
window.helpMenu = QtGui.QMenu(getMessage("help-menu-label"), self)
|
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.userguideAction.triggered.connect(self.openUserGuide)
|
||||||
|
|
||||||
window.menuBar.addMenu(window.helpMenu)
|
window.menuBar.addMenu(window.helpMenu)
|
||||||
window.mainLayout.setMenuBar(window.menuBar)
|
window.mainLayout.setMenuBar(window.menuBar)
|
||||||
|
|
||||||
def addMainFrame(self, window):
|
def addMainFrame(self, window):
|
||||||
window.mainFrame = QtGui.QFrame()
|
window.mainFrame = QtGui.QFrame()
|
||||||
window.mainFrame.setLineWidth(0)
|
window.mainFrame.setLineWidth(0)
|
||||||
window.mainFrame.setMidLineWidth(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.mainFrame.setLayout(window.mainLayout)
|
||||||
|
|
||||||
window.setCentralWidget(window.mainFrame)
|
window.setCentralWidget(window.mainFrame)
|
||||||
|
|
||||||
def newMessage(self, message):
|
def newMessage(self, message):
|
||||||
self.outputbox.moveCursor(QtGui.QTextCursor.End)
|
self.outputbox.moveCursor(QtGui.QTextCursor.End)
|
||||||
self.outputbox.insertHtml(message)
|
self.outputbox.insertHtml(message)
|
||||||
self.outputbox.moveCursor(QtGui.QTextCursor.End)
|
self.outputbox.moveCursor(QtGui.QTextCursor.End)
|
||||||
|
|
||||||
def resetList(self):
|
def resetList(self):
|
||||||
self.listbox.setText("")
|
self.listbox.setText("")
|
||||||
|
|
||||||
def newListItem(self, item):
|
def newListItem(self, item):
|
||||||
self.listbox.moveCursor(QtGui.QTextCursor.End)
|
self.listbox.moveCursor(QtGui.QTextCursor.End)
|
||||||
self.listbox.insertHtml(item)
|
self.listbox.insertHtml(item)
|
||||||
self.listbox.moveCursor(QtGui.QTextCursor.End)
|
self.listbox.moveCursor(QtGui.QTextCursor.End)
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
data = event.mimeData()
|
data = event.mimeData()
|
||||||
urls = data.urls()
|
urls = data.urls()
|
||||||
if urls and urls[0].scheme() == 'file':
|
if urls and urls[0].scheme() == 'file':
|
||||||
event.acceptProposedAction()
|
event.acceptProposedAction()
|
||||||
|
|
||||||
def dropEvent(self, event):
|
def dropEvent(self, event):
|
||||||
rewindFile = False
|
rewindFile = False
|
||||||
if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction:
|
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
|
rewindFile = True
|
||||||
data = event.mimeData()
|
data = event.mimeData()
|
||||||
urls = data.urls()
|
urls = data.urls()
|
||||||
@ -439,14 +627,14 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self._syncplayClient.setPosition(0)
|
self._syncplayClient.setPosition(0)
|
||||||
self._syncplayClient._player.openFile(dropfilepath, resetPosition=True)
|
self._syncplayClient._player.openFile(dropfilepath, resetPosition=True)
|
||||||
self._syncplayClient.setPosition(0)
|
self._syncplayClient.setPosition(0)
|
||||||
|
|
||||||
def saveSettings(self):
|
def saveSettings(self):
|
||||||
settings = QSettings("Syncplay", "MainWindow")
|
settings = QSettings("Syncplay", "MainWindow")
|
||||||
settings.beginGroup("MainWindow")
|
settings.beginGroup("MainWindow")
|
||||||
settings.setValue("size", self.size())
|
settings.setValue("size", self.size())
|
||||||
settings.setValue("pos", self.pos())
|
settings.setValue("pos", self.pos())
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
|
|
||||||
def loadSettings(self):
|
def loadSettings(self):
|
||||||
settings = QSettings("Syncplay", "MainWindow")
|
settings = QSettings("Syncplay", "MainWindow")
|
||||||
settings.beginGroup("MainWindow")
|
settings.beginGroup("MainWindow")
|
||||||
@ -468,8 +656,9 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.addBottomLayout(self)
|
self.addBottomLayout(self)
|
||||||
self.addMenubar(self)
|
self.addMenubar(self)
|
||||||
self.addMainFrame(self)
|
self.addMainFrame(self)
|
||||||
|
self.setupSizes()
|
||||||
self.loadSettings()
|
self.loadSettings()
|
||||||
self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png"))
|
self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png"))
|
||||||
self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint)
|
self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint)
|
||||||
self.show()
|
self.show()
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import itertools
|
import itertools
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
||||||
"""Retry calling the decorated function using an exponential backoff.
|
"""Retry calling the decorated function using an exponential backoff.
|
||||||
@ -93,6 +95,19 @@ def formatTime(timeInSeconds, weeksAsTitles=True):
|
|||||||
formattedTime = "{0:} (Title {1:.0f})".format(formattedTime, title)
|
formattedTime = "{0:} (Title {1:.0f})".format(formattedTime, title)
|
||||||
return formattedTime
|
return formattedTime
|
||||||
|
|
||||||
|
def formatSize (bytes, precise=False):
|
||||||
|
if bytes == 0: # E.g. when file size privacy is enabled
|
||||||
|
return "???"
|
||||||
|
try:
|
||||||
|
megabytes = int(bytes) / 1000000.0
|
||||||
|
if precise:
|
||||||
|
megabytes = round(megabytes, 1)
|
||||||
|
else:
|
||||||
|
megabytes = int(megabytes)
|
||||||
|
return str(megabytes) + getMessage("megabyte-suffix")
|
||||||
|
except: # E.g. when filesize is hashed
|
||||||
|
return "???"
|
||||||
|
|
||||||
def findWorkingDir():
|
def findWorkingDir():
|
||||||
frozen = getattr(sys, 'frozen', '')
|
frozen = getattr(sys, 'frozen', '')
|
||||||
if not frozen:
|
if not frozen:
|
||||||
@ -141,7 +156,19 @@ def blackholeStdoutForFrozenWindow():
|
|||||||
# Relate to file hashing / difference checking:
|
# Relate to file hashing / difference checking:
|
||||||
|
|
||||||
def stripfilename(filename):
|
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<roomnamebase>", RoomName)
|
||||||
|
except IndexError:
|
||||||
|
return RoomName
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
def hashFilename(filename):
|
def hashFilename(filename):
|
||||||
return hashlib.sha256(stripfilename(filename).encode('utf-8')).hexdigest()[:12]
|
return hashlib.sha256(stripfilename(filename).encode('utf-8')).hexdigest()[:12]
|
||||||
@ -182,3 +209,69 @@ def sameFileduration (duration1, duration2):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def meetsMinVersion(version, minVersion):
|
||||||
|
def versiontotuple(ver):
|
||||||
|
return tuple(map(int, ver.split(".")))
|
||||||
|
return versiontotuple(version) >= versiontotuple(minVersion)
|
||||||
|
|
||||||
|
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 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()
|
||||||
|
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):
|
||||||
|
roomName = roomName.encode('utf8')
|
||||||
|
salt = hashlib.sha256(salt).hexdigest()
|
||||||
|
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
|
||||||
@ -20,5 +20,5 @@ 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))
|
reactor.listenTCP(int(args.port), SyncFactory(args.password, args.motd_file, args.isolate_rooms, args.salt))
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|||||||