Merge pull request #3 from Uriziel/master

.
This commit is contained in:
Etoh 2012-12-29 12:56:28 -08:00
commit 8c689648da
8 changed files with 325 additions and 288 deletions

View File

@ -1,3 +1,3 @@
version = '1.2.2'
version = '1.2.3'
milestone = 'Tequila'
projectURL = 'http://uriziel.github.com/syncplay/'

View File

@ -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])

View File

@ -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()

View File

@ -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"]

View File

@ -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()

View File

@ -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?')

View File

@ -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()

View File

@ -2,4 +2,4 @@
from syncplay.clientManager import SyncplayClientManager
if(__name__ == '__main__'):
SyncplayClientManager()
SyncplayClientManager().run()