commit
8c689648da
@ -1,3 +1,3 @@
|
||||
version = '1.2.2'
|
||||
version = '1.2.3'
|
||||
milestone = 'Tequila'
|
||||
projectURL = 'http://uriziel.github.com/syncplay/'
|
||||
@ -6,6 +6,7 @@ from twisted.internet.protocol import ClientFactory
|
||||
from twisted.internet import reactor, task
|
||||
from syncplay.protocols import SyncClientProtocol
|
||||
from syncplay import utils, constants
|
||||
from syncplay.messages import getMessage
|
||||
|
||||
class SyncClientFactory(ClientFactory):
|
||||
def __init__(self, client, retry = constants.RECONNECT_RETRIES):
|
||||
@ -19,23 +20,22 @@ class SyncClientFactory(ClientFactory):
|
||||
|
||||
def startedConnecting(self, connector):
|
||||
destination = connector.getDestination()
|
||||
self._client.ui.showMessage('Attempting to connect to {}:{}'.format(destination.host, destination.port))
|
||||
message = getMessage("en", "connection-attempt-notification").format(destination.host, destination.port)
|
||||
self._client.ui.showMessage(message)
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
if self._timesTried < self.retry:
|
||||
self._timesTried += 1
|
||||
message = 'Connection with server lost, attempting to reconnect'
|
||||
self._client.ui.showMessage(message)
|
||||
self._client.ui.showMessage(getMessage("en", "reconnection-attempt-notification"))
|
||||
self.reconnecting = True
|
||||
reactor.callLater(0.1*(2**self._timesTried), connector.connect)
|
||||
else:
|
||||
message = 'Disconnected from server'
|
||||
message = getMessage("en", "disconnection-notification")
|
||||
self._client.ui.showMessage(message)
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
if not self.reconnecting:
|
||||
message = 'Connection with server failed'
|
||||
self._client.ui.showMessage(message)
|
||||
self._client.ui.showMessage(getMessage("en", "connection-failed-notification"))
|
||||
self._client.stop(True)
|
||||
else:
|
||||
self.clientConnectionLost(connector, reason)
|
||||
@ -47,27 +47,27 @@ class SyncClientFactory(ClientFactory):
|
||||
self._timesTried = self.retry
|
||||
|
||||
class SyncplayClient(object):
|
||||
def __init__(self, playerClass, ui, args):
|
||||
def __init__(self, playerClass, ui, config):
|
||||
self.protocolFactory = SyncClientFactory(self)
|
||||
self.ui = UiManager(self, ui)
|
||||
self.userlist = SyncplayUserlist(self.ui, self)
|
||||
self._protocol = None
|
||||
if(args.room == None or args.room == ''):
|
||||
args.room = constants.DEFAULT_ROOM
|
||||
self.defaultRoom = args.room
|
||||
if(config['room'] == None or config['room'] == ''):
|
||||
config['room'] = constants.DEFAULT_ROOM
|
||||
self.defaultRoom = config['room']
|
||||
self.playerPositionBeforeLastSeek = 0.0
|
||||
self.setUsername(args.name)
|
||||
self.setRoom(args.room)
|
||||
if(args.password):
|
||||
args.password = hashlib.md5(args.password).hexdigest()
|
||||
self._serverPassword = args.password
|
||||
if(not args.file):
|
||||
self.setUsername(config['name'])
|
||||
self.setRoom(config['room'])
|
||||
if(config['password']):
|
||||
config['password'] = hashlib.md5(config['password']).hexdigest()
|
||||
self._serverPassword = config['password']
|
||||
if(not config['file']):
|
||||
self.__getUserlistOnLogon = True
|
||||
else:
|
||||
self.__getUserlistOnLogon = False
|
||||
self._player = None
|
||||
self._playerClass = playerClass
|
||||
self._startupArgs = args
|
||||
self._config = config
|
||||
|
||||
self._running = False
|
||||
self._askPlayerTimer = None
|
||||
@ -108,7 +108,7 @@ class SyncplayClient(object):
|
||||
def checkIfConnected(self):
|
||||
if(self._lastGlobalUpdate and self._protocol and time.time() - self._lastGlobalUpdate > constants.PROTOCOL_TIMEOUT):
|
||||
self._lastGlobalUpdate = None
|
||||
self.ui.showErrorMessage("Connection with server timed out")
|
||||
self.ui.showErrorMessage(getMessage("en", "server-timeout-error"))
|
||||
self._protocol.drop()
|
||||
return False
|
||||
return True
|
||||
@ -117,8 +117,8 @@ class SyncplayClient(object):
|
||||
pauseChange = self.getPlayerPaused() != paused and self.getGlobalPaused() != paused
|
||||
_playerDiff = abs(self.getPlayerPosition() - position)
|
||||
_globalDiff = abs(self.getGlobalPosition() - position)
|
||||
seeked = _playerDiff > constants.SEEK_BOUNDARY and _globalDiff > constants.SEEK_BOUNDARY
|
||||
return pauseChange, seeked
|
||||
seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD
|
||||
return pauseChange, seeked
|
||||
|
||||
def updatePlayerStatus(self, paused, position):
|
||||
position -= self.getUserOffset()
|
||||
@ -149,16 +149,14 @@ class SyncplayClient(object):
|
||||
|
||||
def _rewindPlayerDueToTimeDifference(self, position, setBy):
|
||||
self.setPosition(position)
|
||||
message = "Rewinded due to time difference with <{}>".format(setBy)
|
||||
self.ui.showMessage(message)
|
||||
self.ui.showMessage(getMessage("en", "rewind-notification").format(setBy))
|
||||
madeChangeOnPlayer = True
|
||||
return madeChangeOnPlayer
|
||||
|
||||
def _serverUnpaused(self, setBy):
|
||||
self._player.setPaused(False)
|
||||
madeChangeOnPlayer = True
|
||||
message = '<{}> unpaused'.format(setBy)
|
||||
self.ui.showMessage(message)
|
||||
self.ui.showMessage(getMessage("en", "unpause-notification").format(setBy))
|
||||
return madeChangeOnPlayer
|
||||
|
||||
def _serverPaused(self, setBy, diff):
|
||||
@ -166,8 +164,7 @@ class SyncplayClient(object):
|
||||
self.setPosition(self.getGlobalPosition())
|
||||
self._player.setPaused(True)
|
||||
madeChangeOnPlayer = True
|
||||
message = '<{}> paused'.format(setBy)
|
||||
self.ui.showMessage(message)
|
||||
self.ui.showMessage(getMessage("en", "pause-notification").format(setBy))
|
||||
return madeChangeOnPlayer
|
||||
|
||||
def _serverSeeked(self, position, setBy):
|
||||
@ -177,21 +174,19 @@ class SyncplayClient(object):
|
||||
madeChangeOnPlayer = True
|
||||
else:
|
||||
madeChangeOnPlayer = False
|
||||
message = '<{}> jumped from {} to {}'.format(setBy, utils.formatTime(self.playerPositionBeforeLastSeek), utils.formatTime(position))
|
||||
message = getMessage("en", "seek-notification").format(setBy, utils.formatTime(self.playerPositionBeforeLastSeek), utils.formatTime(position))
|
||||
self.ui.showMessage(message)
|
||||
return madeChangeOnPlayer
|
||||
|
||||
def _slowDownToCoverTimeDifference(self, diff, setBy):
|
||||
if(constants.SLOWDOWN_KICKIN_BOUNDARY < diff and not self._speedChanged):
|
||||
if(constants.SLOWDOWN_KICKIN_THRESHOLD < diff and not self._speedChanged):
|
||||
self._player.setSpeed(constants.SLOWDOWN_RATE)
|
||||
self._speedChanged = True
|
||||
message = "Slowing down due to time difference with <{}>".format(setBy)
|
||||
self.ui.showMessage(message)
|
||||
elif(self._speedChanged and diff < constants.SLOWDOWN_RESET_BOUNDARY):
|
||||
self.ui.showMessage(getMessage("en", "slowdown-notification").format(setBy))
|
||||
elif(self._speedChanged and diff < constants.SLOWDOWN_RESET_THRESHOLD):
|
||||
self._player.setSpeed(1.00)
|
||||
self._speedChanged = False
|
||||
message = "Reverting speed back to normal"
|
||||
self.ui.showMessage(message)
|
||||
self.ui.showMessage(getMessage("en", "revert-notification"))
|
||||
madeChangeOnPlayer = True
|
||||
return madeChangeOnPlayer
|
||||
|
||||
@ -206,7 +201,7 @@ class SyncplayClient(object):
|
||||
self._lastGlobalUpdate = time.time()
|
||||
if (doSeek):
|
||||
madeChangeOnPlayer = self._serverSeeked(position, setBy)
|
||||
if (diff > 4 and not doSeek):
|
||||
if (diff > constants.REWIND_THRESHOLD and not doSeek):
|
||||
madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy)
|
||||
if (self._player.speedSupported and not doSeek and not paused):
|
||||
madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy)
|
||||
@ -234,9 +229,8 @@ class SyncplayClient(object):
|
||||
|
||||
def setUserOffset(self, time):
|
||||
self._userOffset = time
|
||||
message = "Current offset: {} seconds".format(self._userOffset)
|
||||
self.setPosition(self.getGlobalPosition())
|
||||
self.ui.showMessage(message)
|
||||
self.ui.showMessage(getMessage("en", "current-offset-notification").format(self._userOffset))
|
||||
|
||||
def getPlayerPosition(self):
|
||||
if(not self._lastPlayerUpdate):
|
||||
@ -247,8 +241,7 @@ class SyncplayClient(object):
|
||||
position = self._playerPosition
|
||||
if(not self._playerPaused):
|
||||
diff = time.time() - self._lastPlayerUpdate
|
||||
if diff < 0.5:
|
||||
position += diff
|
||||
position += diff
|
||||
return position
|
||||
|
||||
def getPlayerPaused(self):
|
||||
@ -324,7 +317,7 @@ class SyncplayClient(object):
|
||||
return
|
||||
self._running = True
|
||||
if self._playerClass:
|
||||
self._playerClass.run(self, self._startupArgs.player_path, self._startupArgs.file, self._startupArgs._args)
|
||||
self._playerClass.run(self, self._config['playerPath'], self._config['file'], self._config['playerArgs'])
|
||||
self._playerClass = None
|
||||
self.protocolFactory = SyncClientFactory(self)
|
||||
reactor.connectTCP(host, port, self.protocolFactory)
|
||||
@ -341,7 +334,7 @@ class SyncplayClient(object):
|
||||
self._player.drop()
|
||||
reactor.callLater(0.1, reactor.stop)
|
||||
if(promptForAction):
|
||||
self.ui.promptFor("Press enter to exit\n")
|
||||
self.ui.promptFor(getMessage("en", "enter-to-exit-prompt"))
|
||||
|
||||
class SyncplayUser(object):
|
||||
def __init__(self, username = None, room = None, file_ = None, position = 0):
|
||||
@ -363,7 +356,7 @@ class SyncplayUser(object):
|
||||
return False
|
||||
sameName = self.file['name'] == file_['name']
|
||||
sameSize = self.file['size'] == file_['size']
|
||||
sameDuration = int(self.file['duration']) - int(file_['duration']) < constants.DIFFFERENT_DURATION_BOUNDARY
|
||||
sameDuration = int(self.file['duration']) - int(file_['duration']) < constants.DIFFFERENT_DURATION_THRESHOLD
|
||||
return sameName and sameSize and sameDuration
|
||||
|
||||
def __lt__(self, other):
|
||||
@ -378,16 +371,16 @@ class SyncplayUserlist(object):
|
||||
|
||||
def __showUserChangeMessage(self, username, room, file_):
|
||||
if (room and not file_):
|
||||
message = "<{}> has joined the room: '{}'".format(username, room)
|
||||
message = getMessage("en", "room-join-notification").format(username, room)
|
||||
self.ui.showMessage(message)
|
||||
elif (room and file_ and username != self.currentUser.username):
|
||||
duration = utils.formatTime(file_['duration'])
|
||||
message = "<{}> is playing '{}' ({})".format(username, file_['name'], duration)
|
||||
message = getMessage("en", "playing-notification").format(username, file_['name'], duration)
|
||||
if(self.currentUser.room <> room or self.currentUser.username == username):
|
||||
message += " in room: '{}'".format(room)
|
||||
message += getMessage("en", "playing-notification/room-addendum").format(room)
|
||||
self.ui.showMessage(message)
|
||||
if(self.currentUser.file and not self.currentUser.isFileSame(file_) and self.currentUser.room == room):
|
||||
message = "File you are playing appears to be different from <{}>'s".format(username)
|
||||
message = getMessage("en", "file-different-notification").format(username)
|
||||
self.ui.showMessage(message)
|
||||
differences = []
|
||||
if(self.currentUser.file['name'] <> file_['name']):
|
||||
@ -396,7 +389,7 @@ class SyncplayUserlist(object):
|
||||
differences.append("size")
|
||||
if(self.currentUser.file['duration'] <> file_['duration']):
|
||||
differences.append("duration")
|
||||
message = "Your file differs in the following way(s): " + ", ".join(differences)
|
||||
message = getMessage("en", "file-differences-notification") + ", ".join(differences)
|
||||
self.ui.showMessage(message)
|
||||
|
||||
def addUser(self, username, room, file_, position = 0, noMessage = False):
|
||||
@ -411,7 +404,7 @@ class SyncplayUserlist(object):
|
||||
def removeUser(self, username):
|
||||
if(self._users.has_key(username)):
|
||||
self._users.pop(username)
|
||||
message = "<{}> has left".format(username)
|
||||
message = getMessage("en", "left-notification").format(username)
|
||||
self.ui.showMessage(message)
|
||||
|
||||
def __displayModUserMessage(self, username, room, file_, user):
|
||||
@ -463,12 +456,12 @@ class SyncplayUserlist(object):
|
||||
if(self.currentUser.file):
|
||||
fileHasSameSizeAsYour = user.file['size'] == self.currentUser.file['size']
|
||||
fileHasSameNameYour = user.file['name'] == self.currentUser.file['name']
|
||||
differentFileMessage = " (their file size is different from yours!)"
|
||||
differentFileMessage = getMessage("en", "different-filesize-notification")
|
||||
message += differentFileMessage if not fileHasSameSizeAsYour and fileHasSameNameYour else ""
|
||||
return message
|
||||
|
||||
def __displayFileWatchersInRoomList(self, key, users):
|
||||
self.ui.showMessage("File: {} is being played by:".format(key), True, True)
|
||||
self.ui.showMessage(getMessage("en", "file-played-by-notification").format(key), True, True)
|
||||
for user in sorted(users.itervalues()):
|
||||
message = "<"+user.username+">"
|
||||
if(self.currentUser.username == user.username):
|
||||
@ -478,13 +471,13 @@ class SyncplayUserlist(object):
|
||||
|
||||
def __displayPeopleInRoomWithNoFile(self, noFileList):
|
||||
if (noFileList):
|
||||
self.ui.showMessage("People who are not playing any file:", True, True)
|
||||
self.ui.showMessage(getMessage("en", "notplaying-notification"), True, True)
|
||||
for user in sorted(noFileList.itervalues()):
|
||||
self.ui.showMessage("\t<" + user.username + ">", True, True)
|
||||
|
||||
def __displayListOfPeople(self, rooms):
|
||||
for roomName in sorted(rooms.iterkeys()):
|
||||
self.ui.showMessage("In room '{}':".format(roomName), True, False)
|
||||
self.ui.showMessage(getMessage("en", "userlist-room-notification").format(roomName), True, False)
|
||||
noFileList = rooms[roomName].pop("__noFile__") if (rooms[roomName].has_key("__noFile__")) else None
|
||||
for key in sorted(rooms[roomName].iterkeys()):
|
||||
self.__displayFileWatchersInRoomList(key, rooms[roomName][key])
|
||||
|
||||
@ -1,61 +1,26 @@
|
||||
from syncplay.client import SyncplayClient
|
||||
import sys
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter, InvalidConfigValue
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
from syncplay import ui
|
||||
try:
|
||||
from syncplay.players.mpc import MPCHCAPIPlayer
|
||||
except ImportError:
|
||||
MPCHCAPIPlayer = None
|
||||
from syncplay.players.mplayer import MplayerPlayer
|
||||
try:
|
||||
from syncplay.ui.GuiConfiguration import GuiConfiguration
|
||||
except ImportError:
|
||||
GuiConfiguration = None
|
||||
|
||||
class SyncplayClientManager(object):
|
||||
def __init__(self):
|
||||
self._prepareArguments()
|
||||
self.interface = ui.getUi(graphical=not self.args.no_gui)
|
||||
self._checkAndSaveConfiguration()
|
||||
def run(self):
|
||||
config = ConfigurationGetter().getConfiguration()
|
||||
interface = ui.getUi(graphical=not config["noGui"])
|
||||
syncplayClient = None
|
||||
if(self.argsGetter.playerType == "mpc"):
|
||||
syncplayClient = SyncplayClient(MPCHCAPIPlayer, self.interface, self.args)
|
||||
elif(self.argsGetter.playerType == "mplayer"):
|
||||
syncplayClient = SyncplayClient(MplayerPlayer, self.interface, self.args)
|
||||
if(config['playerType'] == "mpc"):
|
||||
syncplayClient = SyncplayClient(MPCHCAPIPlayer, interface, config)
|
||||
elif(config['playerType'] == "mplayer"):
|
||||
syncplayClient = SyncplayClient(MplayerPlayer, interface, config)
|
||||
if(syncplayClient):
|
||||
self.interface.addClient(syncplayClient)
|
||||
syncplayClient.start(self.args.host, self.args.port)
|
||||
interface.addClient(syncplayClient)
|
||||
syncplayClient.start(config['host'], config['port'])
|
||||
else:
|
||||
self.interface.showErrorMessage("Unable to start client")
|
||||
interface.showErrorMessage("Unable to start client")
|
||||
|
||||
def _checkAndSaveConfiguration(self):
|
||||
try:
|
||||
self._promptForMissingArguments()
|
||||
self.argsGetter.saveValuesIntoConfigFile()
|
||||
except InvalidConfigValue:
|
||||
self._checkAndSaveConfiguration()
|
||||
except Exception, e:
|
||||
print e.message
|
||||
sys.exit()
|
||||
|
||||
def _prepareArguments(self):
|
||||
self.argsGetter = ConfigurationGetter()
|
||||
self.args = self.argsGetter.getConfiguration()
|
||||
|
||||
def _guiPromptForMissingArguments(self):
|
||||
if(GuiConfiguration):
|
||||
self.args = GuiConfiguration(self.args, self.args.force_gui_prompt).getProcessedConfiguration()
|
||||
|
||||
def _promptForMissingArguments(self):
|
||||
if(self.args.no_gui):
|
||||
if (self.args.host == None):
|
||||
self.args.host = self.interface.promptFor(prompt="Hostname: ", message="You must supply hostname on the first run, it's easier through command line arguments.")
|
||||
if (self.args.name == None):
|
||||
self.args.name = self.interface.promptFor(prompt="Username: ", message="You must supply username on the first run, it's easier through command line arguments.")
|
||||
if (self.args.player_path == None):
|
||||
self.args.player_path = self.interface.promptFor(prompt="Player executable: ", message="You must supply path to your player on the first run, it's easier through command line arguments.")
|
||||
else:
|
||||
self._guiPromptForMissingArguments()
|
||||
|
||||
|
||||
|
||||
|
||||
@ -5,16 +5,19 @@ MPC_OSD_POSITION = 2 #Right corner, 1 for left
|
||||
MPLAYER_OSD_LEVEL = 1
|
||||
UI_TIME_FORMAT = "[%X] "
|
||||
DEFAULT_ROOM = 'default'
|
||||
DEFAULT_CONFIG_NAME = ".syncplay"
|
||||
|
||||
#Changing these might be ok
|
||||
SEEK_BOUNDARY = 1
|
||||
REWIND_THRESHOLD = 4
|
||||
SEEK_THRESHOLD = 1
|
||||
SLOWDOWN_RATE = 0.95
|
||||
SLOWDOWN_KICKIN_BOUNDARY = 1.5
|
||||
SLOWDOWN_RESET_BOUNDARY = 0.1
|
||||
DIFFFERENT_DURATION_BOUNDARY = 1
|
||||
SLOWDOWN_KICKIN_THRESHOLD = 1.5
|
||||
SLOWDOWN_RESET_THRESHOLD = 0.1
|
||||
DIFFFERENT_DURATION_THRESHOLD = 1
|
||||
PROTOCOL_TIMEOUT = 5
|
||||
RECONNECT_RETRIES = 10
|
||||
|
||||
|
||||
#Usually there's no need to adjust these
|
||||
COMMANDS_UNDO = ["u", "undo", "revert"]
|
||||
COMMANDS_LIST = ["l", "list", "users"]
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
#coding:utf8
|
||||
|
||||
en = {
|
||||
|
||||
# Client notifications
|
||||
@ -27,6 +29,7 @@ en = {
|
||||
"different-filesize-notification" : " (their file size is different from yours!)",
|
||||
"file-played-by-notification" : "File: {} is being played by:", #User
|
||||
"notplaying-notification" : "People who are not playing any file:",
|
||||
"userlist-room-notification" : "In room '{}':", #Room
|
||||
|
||||
# Client prompts
|
||||
"enter-to-exit-prompt" : "Press enter to exit\n",
|
||||
@ -35,8 +38,46 @@ en = {
|
||||
"server-timeout-error" : "Connection with server timed out"
|
||||
}
|
||||
|
||||
pl = {
|
||||
|
||||
# Client notifications
|
||||
"connection-attempt-notification" : "Próba połączenia z {}:{}", #Port, IP
|
||||
"reconnection-attempt-notification" : "Połączenie z serwerem zostało przerwane, ponowne łączenie",
|
||||
"disconnection-notification" : "Odłączono od serwera",
|
||||
"connection-failed-notification" : "Połączenie z serwerem zakończone fiaskiem",
|
||||
|
||||
"rewind-notification" : "Cofnięto z powodu różnicy czasu z <{}>", #User
|
||||
"slowdown-notification" : "Zwolniono z powodu różnicy czasu z <{}>", #User
|
||||
"revert-notification" : "Przywrócono normalną prędkość odtwarzania",
|
||||
|
||||
"pause-notification" : "<{}> zatrzymał odtwarzanie", #User
|
||||
"unpause-notification" : "<{}> wznowił odtwarzanie", #User
|
||||
"seek-notification" : "<{}> skoczył z {} do {}", #User, from time, to time
|
||||
|
||||
"current-offset-notification" : "Obecny offset: {} seconds", #Offset
|
||||
|
||||
"room-join-notification" : "<{}> dołączył do pokoju: '{}'", #User
|
||||
"left-notification" : "<{}> wyszedł", #User
|
||||
"playing-notification" : "<{}> odtwarza '{}' ({})", #User, file, duration
|
||||
"playing-notification/room-addendum" : " w pokoju: '{}'", #Room
|
||||
|
||||
"file-different-notification" : "Plik, który odtwarzasz wydaje się być różny od <{}>", #User
|
||||
"file-differences-notification" : "Twój plik różni się następującymi parametrami: ",
|
||||
|
||||
"different-filesize-notification" : " (inny rozmiar pliku!)",
|
||||
"file-played-by-notification" : "Plik: {} jest odtwarzany przez:", #User
|
||||
"notplaying-notification" : "Osoby, które nie odtwarzają żadnych plików:",
|
||||
"userlist-room-notification" : "W pokoju '{}':", #Room
|
||||
# Client prompts
|
||||
"enter-to-exit-prompt" : "Wciśnij Enter, aby zakończyć działanie programu\n",
|
||||
|
||||
# Client errors
|
||||
"server-timeout-error" : "Przekroczono czas oczekiwania na odpowiedź serwera"
|
||||
}
|
||||
|
||||
messages = {
|
||||
"en": en
|
||||
"en": en,
|
||||
"pl": pl
|
||||
}
|
||||
|
||||
def getMessage(locale, type_):
|
||||
@ -46,4 +87,4 @@ def getMessage(locale, type_):
|
||||
if(messages["en"].has_key(type_)):
|
||||
return messages["en"][type_]
|
||||
else:
|
||||
raise KeyError()
|
||||
raise KeyError()
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import ConfigParser
|
||||
from ConfigParser import SafeConfigParser
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from syncplay import constants
|
||||
try:
|
||||
from syncplay.ui.GuiConfiguration import GuiConfiguration
|
||||
except ImportError:
|
||||
GuiConfiguration = None
|
||||
|
||||
class InvalidConfigValue(Exception):
|
||||
def __init__(self, message):
|
||||
@ -10,14 +14,122 @@ class InvalidConfigValue(Exception):
|
||||
|
||||
class ConfigurationGetter(object):
|
||||
def __init__(self):
|
||||
self._config = None
|
||||
self._args = None
|
||||
self._syncplayClient = None
|
||||
self._configFile = None
|
||||
self._parser = None
|
||||
self._configName = ".syncplay"
|
||||
self.playerType = None
|
||||
self._config = {
|
||||
"host": None,
|
||||
"port": constants.DEFAULT_PORT,
|
||||
"name": None,
|
||||
"debug": False,
|
||||
"forceGuiPrompt": False,
|
||||
"noGui": False,
|
||||
"noStore": False,
|
||||
"room": constants.DEFAULT_ROOM,
|
||||
"password": None,
|
||||
"playerPath": None,
|
||||
"file": None,
|
||||
"playerArgs": [],
|
||||
"playerType": None,
|
||||
}
|
||||
|
||||
#
|
||||
#Custom validation in self._validateArguments
|
||||
#
|
||||
self._required = [
|
||||
"host",
|
||||
"port",
|
||||
"name",
|
||||
"playerPath",
|
||||
"playerType",
|
||||
]
|
||||
|
||||
self._iniStructure = {
|
||||
"server_data": ["host", "port", "password"],
|
||||
"client_settings": ["name", "room", "playerPath"]
|
||||
}
|
||||
|
||||
#
|
||||
#Watch out for the method self._overrideConfigWithArgs when you're adding custom multi-word command line arguments
|
||||
#
|
||||
self._argparser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network.',
|
||||
epilog='If no options supplied values from configuration file will be used')
|
||||
self._argparser.add_argument('--no-gui', action='store_true', help='show no GUI')
|
||||
self._argparser.add_argument('-a', '--host', metavar='hostname', type=str, help='server\'s address')
|
||||
self._argparser.add_argument('-n', '--name', metavar='username', type=str, help='desired username')
|
||||
self._argparser.add_argument('-d', '--debug', action='store_true', help='debug mode')
|
||||
self._argparser.add_argument('-g', '--force-gui-prompt', action='store_true', help='make configuration prompt appear')
|
||||
self._argparser.add_argument('--no-store', action='store_true', help='don\'t store values in .syncplay')
|
||||
self._argparser.add_argument('-r', '--room', metavar='room', type=str, nargs='?', help='default room')
|
||||
self._argparser.add_argument('-p', '--password', metavar='password', type=str, nargs='?', help='server password')
|
||||
self._argparser.add_argument('--player-path', metavar='path', type=str, help='path to your player executable')
|
||||
self._argparser.add_argument('file', metavar='file', type=str, nargs='?', help='file to play')
|
||||
self._argparser.add_argument('_args', metavar='options', type=str, nargs='*', help='player options, if you need to pass options starting with - prepend them with single \'--\' argument')
|
||||
|
||||
def _validateArguments(self):
|
||||
for key in self._required:
|
||||
if(key == "playerPath"):
|
||||
if(self._isPlayerMPCAndValid(self._config["playerPath"])):
|
||||
self._config["playerType"] = "mpc"
|
||||
self.__addSpecialMPCFlags()
|
||||
elif(self._isMplayerPathAndValid(self._config["playerPath"])):
|
||||
self._config["playerType"] = "mplayer"
|
||||
else:
|
||||
raise InvalidConfigValue("Player path is not set properly")
|
||||
elif(key == "host"):
|
||||
self._config["host"], self._config["port"] = self._splitPortAndHost(self._config["host"])
|
||||
hostNotValid = (self._config["host"] == "" or self._config["host"] is None)
|
||||
portNotValid = (self._config["port"] == "" or self._config["port"] is None)
|
||||
if(hostNotValid or portNotValid):
|
||||
raise InvalidConfigValue("Hostname can't be empty")
|
||||
elif(self._config[key] == "" or self._config[key] is None):
|
||||
raise InvalidConfigValue("{} can't be empty".format(key))
|
||||
|
||||
def _overrideConfigWithArgs(self, args):
|
||||
for key, val in vars(args).items():
|
||||
if(val):
|
||||
if(key == "force_gui_prompt"):
|
||||
key = "forceGuiPrompt"
|
||||
if(key == "no_store"):
|
||||
key = "noStore"
|
||||
if(key == "player_path"):
|
||||
key = "playerPath"
|
||||
if(key == "_args"):
|
||||
key = "playerArgs"
|
||||
if(key == "no_gui"):
|
||||
key = "noGui"
|
||||
self._config[key] = val
|
||||
|
||||
def _isPlayerMPCAndValid(self, path):
|
||||
if(os.path.isfile(path)):
|
||||
if(path[-10:] == 'mpc-hc.exe' or path[-12:] == 'mpc-hc64.exe'):
|
||||
return True
|
||||
if(os.path.isfile(path + "\\mpc-hc.exe")):
|
||||
path += "\\mpc-hc.exe"
|
||||
return True
|
||||
if(os.path.isfile(path + "\\mpc-hc64.exe")):
|
||||
path += "\\mpc-hc64.exe"
|
||||
return True
|
||||
return False
|
||||
|
||||
def __addSpecialMPCFlags(self):
|
||||
self._config['playerArgs'].extend(['/open', '/new'])
|
||||
|
||||
def _isMplayerPathAndValid(self, playerPath):
|
||||
if("mplayer" in playerPath):
|
||||
if os.access(playerPath, os.X_OK):
|
||||
return True
|
||||
for path in os.environ['PATH'].split(':'):
|
||||
path = os.path.join(os.path.realpath(path), playerPath)
|
||||
if os.access(path, os.X_OK):
|
||||
self._config['playerPath'] = path
|
||||
return True
|
||||
return False
|
||||
|
||||
def _splitPortAndHost(self, host):
|
||||
port = constants.DEFAULT_PORT if not self._config["port"] else self._config["port"]
|
||||
if(host):
|
||||
if ':' in host:
|
||||
host, port = host.split(':', 1)
|
||||
return host, int(port)
|
||||
|
||||
def _findWorkingDir(self):
|
||||
frozen = getattr(sys, 'frozen', '')
|
||||
if not frozen:
|
||||
@ -30,154 +142,81 @@ class ConfigurationGetter(object):
|
||||
|
||||
def _checkForPortableFile(self):
|
||||
path = self._findWorkingDir()
|
||||
if(os.path.isfile(os.path.join(path, self._configName))):
|
||||
return os.path.join(path, self._configName)
|
||||
if(os.path.isfile(os.path.join(path, constants.DEFAULT_CONFIG_NAME))):
|
||||
return os.path.join(path, constants.DEFAULT_CONFIG_NAME)
|
||||
|
||||
def _getConfigurationFilePath(self):
|
||||
self._configFile = self._checkForPortableFile()
|
||||
if(not self._configFile):
|
||||
configFile = self._checkForPortableFile()
|
||||
if(not configFile):
|
||||
if(os.name <> 'nt'):
|
||||
self._configFile = os.path.join(os.getenv('HOME', '.'), self._configName)
|
||||
configFile = os.path.join(os.getenv('HOME', '.'), constants.DEFAULT_CONFIG_NAME)
|
||||
else:
|
||||
self._configFile = os.path.join(os.getenv('APPDATA', '.'), self._configName)
|
||||
configFile = os.path.join(os.getenv('APPDATA', '.'), constants.DEFAULT_CONFIG_NAME)
|
||||
return configFile
|
||||
|
||||
def _prepareArgParser(self):
|
||||
self._parser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network.',
|
||||
epilog='If no options supplied values from .syncplay file will be used')
|
||||
self._parser.add_argument('--no-gui', action='store_true', help='show no GUI')
|
||||
self._parser.add_argument('-a', '--host', metavar='hostname', type=str, help='server\'s address')
|
||||
self._parser.add_argument('-n', '--name', metavar='username', type=str, help='desired username')
|
||||
self._parser.add_argument('-d', '--debug', action='store_true', help='debug mode')
|
||||
self._parser.add_argument('-g', '--force-gui-prompt', action='store_true', help='make configuration prompt appear')
|
||||
self._parser.add_argument('--no-store', action='store_true', help='don\'t store values in syncplay.ini')
|
||||
self._parser.add_argument('-r', '--room', metavar='room', type=str, nargs='?', help='default room')
|
||||
self._parser.add_argument('-p', '--password', metavar='password', type=str, nargs='?', help='server password')
|
||||
self._parser.add_argument('--player-path', metavar='path', type=str, help='path to your player executable')
|
||||
self._parser.add_argument('file', metavar='file', type=str, nargs='?', help='file to play')
|
||||
self._parser.add_argument('_args', metavar='options', type=str, nargs='*', help='player options, if you need to pass options starting with - prepend them with single \'--\' argument')
|
||||
|
||||
def _openConfigFile(self):
|
||||
if(not self._config):
|
||||
self._config = ConfigParser.RawConfigParser(allow_no_value=True)
|
||||
self._config.read(self._configFile)
|
||||
|
||||
def _getSectionName(self):
|
||||
return 'sync' if not self._args.debug else 'debug'
|
||||
|
||||
def saveValuesIntoConfigFile(self):
|
||||
self._splitPortAndHost()
|
||||
self._openConfigFile()
|
||||
section_name = self._getSectionName()
|
||||
self._validateArguments()
|
||||
if(not self._args.no_store):
|
||||
with open(self._configFile, 'wb') as configfile:
|
||||
if(not self._config.has_section(section_name)):
|
||||
self._config.add_section(section_name)
|
||||
self._config.set(section_name, 'host', self._args.host+":"+str(self._args.port))
|
||||
self._config.set(section_name, 'name', self._args.name)
|
||||
self._config.set(section_name, 'room', self._args.room)
|
||||
self._config.set(section_name, 'password', self._args.password)
|
||||
self._config.set(section_name, 'player_path', self._args.player_path)
|
||||
self._config.write(configfile)
|
||||
|
||||
def _validateArguments(self):
|
||||
if(not (self._args.host <> "" and self._args.host is not None)):
|
||||
self._args.host = None
|
||||
raise InvalidConfigValue("Hostname can't be empty")
|
||||
if(not (self._args.name <> "" and self._args.name is not None)):
|
||||
self._args.name = None
|
||||
raise InvalidConfigValue("Username can't be empty")
|
||||
if(self._isPlayerMPCAndValid()):
|
||||
self._addSpecialMPCFlags()
|
||||
self.playerType = "mpc"
|
||||
elif(self._isMplayerPathValid()):
|
||||
self.playerType = "mplayer"
|
||||
else:
|
||||
self._args.player_path = None
|
||||
raise InvalidConfigValue('Path to player is not valid')
|
||||
def _parseConfigFile(self, iniPath):
|
||||
parser = SafeConfigParser()
|
||||
parser.read(iniPath)
|
||||
for section, options in self._iniStructure.items():
|
||||
if(parser.has_section(section)):
|
||||
for option in options:
|
||||
if(parser.has_option(section, option)):
|
||||
self._config[option] = parser.get(section, option)
|
||||
|
||||
def _readConfigValue(self, section_name, name):
|
||||
def _checkConfig(self):
|
||||
try:
|
||||
return self._config.get(section_name, name)
|
||||
except ConfigParser.NoOptionError:
|
||||
return None
|
||||
|
||||
def _readMissingValuesFromConfigFile(self):
|
||||
self._openConfigFile()
|
||||
section_name = self._getSectionName()
|
||||
try:
|
||||
self._valuesToReadFromConfig(section_name)
|
||||
except ConfigParser.NoSectionError:
|
||||
pass
|
||||
self._validateArguments()
|
||||
except InvalidConfigValue:
|
||||
try:
|
||||
for key, value in self._promptForMissingArguments().items():
|
||||
self._config[key] = value
|
||||
self._checkConfig()
|
||||
except:
|
||||
sys.exit()
|
||||
|
||||
def _isPlayerMPCAndValid(self):
|
||||
if(os.path.isfile(self._args.player_path)):
|
||||
if(self._args.player_path[-10:] == 'mpc-hc.exe' or self._args.player_path[-12:] == 'mpc-hc64.exe'):
|
||||
return True
|
||||
if(os.path.isfile(self._args.player_path + "\\mpc-hc.exe")):
|
||||
self._args.player_path += "\\mpc-hc.exe"
|
||||
return True
|
||||
if(os.path.isfile(self._args.player_path + "\\mpc-hc64.exe")):
|
||||
self._args.player_path += "\\mpc-hc64.exe"
|
||||
return True
|
||||
return False
|
||||
def _promptForMissingArguments(self):
|
||||
if(self._config['noGui']):
|
||||
print "Some necessary arguments are missing, refer to --help"
|
||||
sys.exit()
|
||||
elif(GuiConfiguration):
|
||||
return GuiConfiguration(self._config).getProcessedConfiguration()
|
||||
|
||||
def _addSpecialMPCFlags(self):
|
||||
self._args._args.extend(['/open', '/new'])
|
||||
|
||||
def _isMplayerPathValid(self):
|
||||
if("mplayer" in self._args.player_path):
|
||||
if os.access(self._args.player_path, os.X_OK):
|
||||
return True
|
||||
for path in os.environ['PATH'].split(':'):
|
||||
path = os.path.join(os.path.realpath(path), self._args.player_path)
|
||||
if os.access(path, os.X_OK):
|
||||
self._args.player_path = path
|
||||
return True
|
||||
return False
|
||||
|
||||
def _valuesToReadFromConfig(self, section_name):
|
||||
if (self._args.host == None):
|
||||
self._args.host = self._readConfigValue(section_name, 'host')
|
||||
if (self._args.name == None):
|
||||
self._args.name = self._readConfigValue(section_name, 'name')
|
||||
if (self._args.room == None):
|
||||
self._args.room = self._readConfigValue(section_name, 'room')
|
||||
if (self._args.password == None):
|
||||
self._args.password = self._readConfigValue(section_name, 'password')
|
||||
if (self._args.player_path == None):
|
||||
self._args.player_path = self._readConfigValue(section_name, 'player_path')
|
||||
|
||||
def _splitPortAndHost(self):
|
||||
if(self._args.host):
|
||||
if ':' in self._args.host:
|
||||
self._args.host, port = self._args.host.split(':', 1)
|
||||
self._args.port = int(port)
|
||||
elif("port" not in self._args):
|
||||
self._args.port = constants.DEFAULT_PORT
|
||||
|
||||
def setConfiguration(self, args):
|
||||
self._args = args
|
||||
def _saveConfig(self, iniPath):
|
||||
if(self._config['noStore']):
|
||||
return
|
||||
parser = SafeConfigParser()
|
||||
for section, options in self._iniStructure.items():
|
||||
if(not parser.has_section(section)):
|
||||
parser.add_section(section)
|
||||
for option in options:
|
||||
parser.set(section, option, str(self._config[option]))
|
||||
parser.write(file(iniPath, "w"))
|
||||
|
||||
def getConfiguration(self):
|
||||
self._getConfigurationFilePath()
|
||||
self._prepareArgParser()
|
||||
self._args = self._parser.parse_args()
|
||||
self._readMissingValuesFromConfigFile()
|
||||
self._splitPortAndHost()
|
||||
return self._args
|
||||
iniPath = self._getConfigurationFilePath()
|
||||
self._parseConfigFile(iniPath)
|
||||
args = self._argparser.parse_args()
|
||||
self._overrideConfigWithArgs(args)
|
||||
if(self._config['forceGuiPrompt']):
|
||||
try:
|
||||
self._promptForMissingArguments()
|
||||
except:
|
||||
sys.exit()
|
||||
self._checkConfig()
|
||||
self._saveConfig(iniPath)
|
||||
return self._config
|
||||
|
||||
class ServerConfigurationGetter(ConfigurationGetter):
|
||||
class ServerConfigurationGetter(object):
|
||||
def getConfiguration(self):
|
||||
self._prepareArgParser()
|
||||
self._args = self._parser.parse_args()
|
||||
self._args = self._argparser.parse_args()
|
||||
if(self._args.port == None):
|
||||
self._args.port = constants.DEFAULT_PORT
|
||||
return self._args
|
||||
|
||||
def _prepareArgParser(self):
|
||||
self._parser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network. Server instance',
|
||||
self._argparser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network. Server instance',
|
||||
epilog='If no options supplied _config values will be used')
|
||||
self._parser.add_argument('--port', metavar='port', type=str, nargs='?', help='server TCP port')
|
||||
self._parser.add_argument('--password', metavar='password', type=str, nargs='?', help='server password')
|
||||
self._parser.add_argument('--isolate-rooms', action='store_true', help='should rooms be isolated?')
|
||||
self._argparser.add_argument('--port', metavar='port', type=str, nargs='?', help='server TCP port')
|
||||
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help='server password')
|
||||
self._argparser.add_argument('--isolate-rooms', action='store_true', help='should rooms be isolated?')
|
||||
|
||||
@ -7,68 +7,64 @@ gtk.set_interactive(False)
|
||||
import cairo, gio, pango, atk, pangocairo, gobject #@UnusedImport
|
||||
|
||||
class GuiConfiguration:
|
||||
def __init__(self, args, force=False):
|
||||
self.args = args
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.closedAndNotSaved = False
|
||||
if(args.player_path == None or args.host == None or args.name == None or force):
|
||||
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
self.window.set_title("Syncplay Configuration")
|
||||
self.window.connect("delete_event", lambda w, e: self._windowClosed())
|
||||
vbox = gtk.VBox(False, 0)
|
||||
self.window.add(vbox)
|
||||
vbox.show()
|
||||
self._addLabeledEntries(args, vbox)
|
||||
|
||||
self.hostEntry.select_region(0, len(self.hostEntry.get_text()))
|
||||
button = gtk.Button(stock=gtk.STOCK_SAVE)
|
||||
button.connect("clicked", lambda w: self._saveDataAndLeave())
|
||||
vbox.pack_start(button, True, True, 0)
|
||||
button.set_flags(gtk.CAN_DEFAULT)
|
||||
button.grab_default()
|
||||
button.show()
|
||||
self.window.show()
|
||||
gtk.main()
|
||||
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
self.window.set_title("Syncplay Configuration")
|
||||
self.window.connect("delete_event", lambda w, e: self._windowClosed())
|
||||
vbox = gtk.VBox(False, 0)
|
||||
self.window.add(vbox)
|
||||
vbox.show()
|
||||
self._addLabeledEntries(config, vbox)
|
||||
|
||||
self.hostEntry.select_region(0, len(self.hostEntry.get_text()))
|
||||
button = gtk.Button(stock=gtk.STOCK_SAVE)
|
||||
button.connect("clicked", lambda w: self._saveDataAndLeave())
|
||||
vbox.pack_start(button, True, True, 0)
|
||||
button.set_flags(gtk.CAN_DEFAULT)
|
||||
button.grab_default()
|
||||
button.show()
|
||||
self.window.show()
|
||||
gtk.main()
|
||||
|
||||
def _windowClosed(self):
|
||||
self.window.destroy()
|
||||
gtk.main_quit()
|
||||
self.closedAndNotSaved = True
|
||||
|
||||
def _addLabeledEntries(self, args, vbox):
|
||||
if(args.host == None):
|
||||
def _addLabeledEntries(self, config, vbox):
|
||||
if(config['host'] == None):
|
||||
host = ""
|
||||
elif(":" in args.host):
|
||||
host = args.host
|
||||
elif("port" in args):
|
||||
host = args.host+":"+str(args.port)
|
||||
elif(":" in config['host']):
|
||||
host = config['host']
|
||||
else:
|
||||
host = args.host
|
||||
self.hostEntry = self._addLabeledEntryToVbox('Host: ', host, vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.userEntry = self._addLabeledEntryToVbox('Username: ', args.name, vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.roomEntry = self._addLabeledEntryToVbox('Default room (optional): ', args.room, vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.passEntry = self._addLabeledEntryToVbox('Server password (optional): ', args.password, vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self._tryToFillUpMpcPath()
|
||||
self.mpcEntry = self._addLabeledEntryToVbox('Path to player executable: ', self.args.player_path, vbox, lambda __, _: self._saveDataAndLeave())
|
||||
host = config['host']+":"+str(config['port'])
|
||||
|
||||
def _tryToFillUpMpcPath(self):
|
||||
if(self.args.player_path == None):
|
||||
self.hostEntry = self._addLabeledEntryToVbox('Host: ', host, vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.userEntry = self._addLabeledEntryToVbox('Username: ', config['name'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.roomEntry = self._addLabeledEntryToVbox('Default room (optional): ', config['room'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.passEntry = self._addLabeledEntryToVbox('Server password (optional): ', config['password'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.mpcEntry = self._addLabeledEntryToVbox('Path to player executable: ', self._tryToFillUpMpcPath(config['playerPath']), vbox, lambda __, _: self._saveDataAndLeave())
|
||||
|
||||
def _tryToFillUpMpcPath(self, playerPath):
|
||||
if(playerPath == None):
|
||||
for path in constants.MPC_PATHS:
|
||||
if(os.path.isfile(path)):
|
||||
self.args.player_path = path
|
||||
return
|
||||
return path
|
||||
return playerPath
|
||||
|
||||
|
||||
def getProcessedConfiguration(self):
|
||||
if(self.closedAndNotSaved):
|
||||
raise self.WindowClosed
|
||||
return self.args
|
||||
return self.config
|
||||
|
||||
def _saveDataAndLeave(self):
|
||||
self.args.host = self.hostEntry.get_text()
|
||||
self.args.name = self.userEntry.get_text()
|
||||
self.args.room = self.roomEntry.get_text()
|
||||
self.args.password = self.passEntry.get_text()
|
||||
self.args.player_path = self.mpcEntry.get_text()
|
||||
self.config['host'] = self.hostEntry.get_text()
|
||||
self.config['name'] = self.userEntry.get_text()
|
||||
self.config['room'] = self.roomEntry.get_text()
|
||||
self.config['password'] = self.passEntry.get_text()
|
||||
self.config['playerPath'] = self.mpcEntry.get_text()
|
||||
self.window.destroy()
|
||||
gtk.main_quit()
|
||||
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
|
||||
from syncplay.clientManager import SyncplayClientManager
|
||||
if(__name__ == '__main__'):
|
||||
SyncplayClientManager()
|
||||
SyncplayClientManager().run()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user