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' milestone = 'Tequila'
projectURL = 'http://uriziel.github.com/syncplay/' 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 twisted.internet import reactor, task
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
class SyncClientFactory(ClientFactory): class SyncClientFactory(ClientFactory):
def __init__(self, client, retry = constants.RECONNECT_RETRIES): def __init__(self, client, retry = constants.RECONNECT_RETRIES):
@ -19,23 +20,22 @@ class SyncClientFactory(ClientFactory):
def startedConnecting(self, connector): def startedConnecting(self, connector):
destination = connector.getDestination() 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): def clientConnectionLost(self, connector, reason):
if self._timesTried < self.retry: if self._timesTried < self.retry:
self._timesTried += 1 self._timesTried += 1
message = 'Connection with server lost, attempting to reconnect' self._client.ui.showMessage(getMessage("en", "reconnection-attempt-notification"))
self._client.ui.showMessage(message)
self.reconnecting = True self.reconnecting = True
reactor.callLater(0.1*(2**self._timesTried), connector.connect) reactor.callLater(0.1*(2**self._timesTried), connector.connect)
else: else:
message = 'Disconnected from server' message = getMessage("en", "disconnection-notification")
self._client.ui.showMessage(message) self._client.ui.showMessage(message)
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
if not self.reconnecting: if not self.reconnecting:
message = 'Connection with server failed' self._client.ui.showMessage(getMessage("en", "connection-failed-notification"))
self._client.ui.showMessage(message)
self._client.stop(True) self._client.stop(True)
else: else:
self.clientConnectionLost(connector, reason) self.clientConnectionLost(connector, reason)
@ -47,27 +47,27 @@ class SyncClientFactory(ClientFactory):
self._timesTried = self.retry self._timesTried = self.retry
class SyncplayClient(object): class SyncplayClient(object):
def __init__(self, playerClass, ui, args): def __init__(self, playerClass, ui, config):
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
if(args.room == None or args.room == ''): if(config['room'] == None or config['room'] == ''):
args.room = constants.DEFAULT_ROOM config['room'] = constants.DEFAULT_ROOM
self.defaultRoom = args.room self.defaultRoom = config['room']
self.playerPositionBeforeLastSeek = 0.0 self.playerPositionBeforeLastSeek = 0.0
self.setUsername(args.name) self.setUsername(config['name'])
self.setRoom(args.room) self.setRoom(config['room'])
if(args.password): if(config['password']):
args.password = hashlib.md5(args.password).hexdigest() config['password'] = hashlib.md5(config['password']).hexdigest()
self._serverPassword = args.password self._serverPassword = config['password']
if(not args.file): if(not config['file']):
self.__getUserlistOnLogon = True self.__getUserlistOnLogon = True
else: else:
self.__getUserlistOnLogon = False self.__getUserlistOnLogon = False
self._player = None self._player = None
self._playerClass = playerClass self._playerClass = playerClass
self._startupArgs = args self._config = config
self._running = False self._running = False
self._askPlayerTimer = None self._askPlayerTimer = None
@ -108,7 +108,7 @@ class SyncplayClient(object):
def checkIfConnected(self): def checkIfConnected(self):
if(self._lastGlobalUpdate and self._protocol and time.time() - self._lastGlobalUpdate > constants.PROTOCOL_TIMEOUT): if(self._lastGlobalUpdate and self._protocol and time.time() - self._lastGlobalUpdate > constants.PROTOCOL_TIMEOUT):
self._lastGlobalUpdate = None self._lastGlobalUpdate = None
self.ui.showErrorMessage("Connection with server timed out") self.ui.showErrorMessage(getMessage("en", "server-timeout-error"))
self._protocol.drop() self._protocol.drop()
return False return False
return True return True
@ -117,8 +117,8 @@ class SyncplayClient(object):
pauseChange = self.getPlayerPaused() != paused and self.getGlobalPaused() != paused pauseChange = self.getPlayerPaused() != paused and self.getGlobalPaused() != paused
_playerDiff = abs(self.getPlayerPosition() - position) _playerDiff = abs(self.getPlayerPosition() - position)
_globalDiff = abs(self.getGlobalPosition() - position) _globalDiff = abs(self.getGlobalPosition() - position)
seeked = _playerDiff > constants.SEEK_BOUNDARY and _globalDiff > constants.SEEK_BOUNDARY seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD
return pauseChange, seeked return pauseChange, seeked
def updatePlayerStatus(self, paused, position): def updatePlayerStatus(self, paused, position):
position -= self.getUserOffset() position -= self.getUserOffset()
@ -149,16 +149,14 @@ class SyncplayClient(object):
def _rewindPlayerDueToTimeDifference(self, position, setBy): def _rewindPlayerDueToTimeDifference(self, position, setBy):
self.setPosition(position) self.setPosition(position)
message = "Rewinded due to time difference with <{}>".format(setBy) self.ui.showMessage(getMessage("en", "rewind-notification").format(setBy))
self.ui.showMessage(message)
madeChangeOnPlayer = True madeChangeOnPlayer = True
return madeChangeOnPlayer return madeChangeOnPlayer
def _serverUnpaused(self, setBy): def _serverUnpaused(self, setBy):
self._player.setPaused(False) self._player.setPaused(False)
madeChangeOnPlayer = True madeChangeOnPlayer = True
message = '<{}> unpaused'.format(setBy) self.ui.showMessage(getMessage("en", "unpause-notification").format(setBy))
self.ui.showMessage(message)
return madeChangeOnPlayer return madeChangeOnPlayer
def _serverPaused(self, setBy, diff): def _serverPaused(self, setBy, diff):
@ -166,8 +164,7 @@ class SyncplayClient(object):
self.setPosition(self.getGlobalPosition()) self.setPosition(self.getGlobalPosition())
self._player.setPaused(True) self._player.setPaused(True)
madeChangeOnPlayer = True madeChangeOnPlayer = True
message = '<{}> paused'.format(setBy) self.ui.showMessage(getMessage("en", "pause-notification").format(setBy))
self.ui.showMessage(message)
return madeChangeOnPlayer return madeChangeOnPlayer
def _serverSeeked(self, position, setBy): def _serverSeeked(self, position, setBy):
@ -177,21 +174,19 @@ class SyncplayClient(object):
madeChangeOnPlayer = True madeChangeOnPlayer = True
else: else:
madeChangeOnPlayer = False 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) self.ui.showMessage(message)
return madeChangeOnPlayer return madeChangeOnPlayer
def _slowDownToCoverTimeDifference(self, diff, setBy): 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._player.setSpeed(constants.SLOWDOWN_RATE)
self._speedChanged = True self._speedChanged = True
message = "Slowing down due to time difference with <{}>".format(setBy) self.ui.showMessage(getMessage("en", "slowdown-notification").format(setBy))
self.ui.showMessage(message) elif(self._speedChanged and diff < constants.SLOWDOWN_RESET_THRESHOLD):
elif(self._speedChanged and diff < constants.SLOWDOWN_RESET_BOUNDARY):
self._player.setSpeed(1.00) self._player.setSpeed(1.00)
self._speedChanged = False self._speedChanged = False
message = "Reverting speed back to normal" self.ui.showMessage(getMessage("en", "revert-notification"))
self.ui.showMessage(message)
madeChangeOnPlayer = True madeChangeOnPlayer = True
return madeChangeOnPlayer return madeChangeOnPlayer
@ -206,7 +201,7 @@ class SyncplayClient(object):
self._lastGlobalUpdate = time.time() self._lastGlobalUpdate = time.time()
if (doSeek): if (doSeek):
madeChangeOnPlayer = self._serverSeeked(position, setBy) madeChangeOnPlayer = self._serverSeeked(position, setBy)
if (diff > 4 and not doSeek): if (diff > constants.REWIND_THRESHOLD and not doSeek):
madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy) madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy)
if (self._player.speedSupported and not doSeek and not paused): if (self._player.speedSupported and not doSeek and not paused):
madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy) madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy)
@ -234,9 +229,8 @@ class SyncplayClient(object):
def setUserOffset(self, time): def setUserOffset(self, time):
self._userOffset = time self._userOffset = time
message = "Current offset: {} seconds".format(self._userOffset)
self.setPosition(self.getGlobalPosition()) self.setPosition(self.getGlobalPosition())
self.ui.showMessage(message) self.ui.showMessage(getMessage("en", "current-offset-notification").format(self._userOffset))
def getPlayerPosition(self): def getPlayerPosition(self):
if(not self._lastPlayerUpdate): if(not self._lastPlayerUpdate):
@ -247,8 +241,7 @@ class SyncplayClient(object):
position = self._playerPosition position = self._playerPosition
if(not self._playerPaused): if(not self._playerPaused):
diff = time.time() - self._lastPlayerUpdate diff = time.time() - self._lastPlayerUpdate
if diff < 0.5: position += diff
position += diff
return position return position
def getPlayerPaused(self): def getPlayerPaused(self):
@ -324,7 +317,7 @@ class SyncplayClient(object):
return return
self._running = True self._running = True
if self._playerClass: 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._playerClass = None
self.protocolFactory = SyncClientFactory(self) self.protocolFactory = SyncClientFactory(self)
reactor.connectTCP(host, port, self.protocolFactory) reactor.connectTCP(host, port, self.protocolFactory)
@ -341,7 +334,7 @@ class SyncplayClient(object):
self._player.drop() self._player.drop()
reactor.callLater(0.1, reactor.stop) reactor.callLater(0.1, reactor.stop)
if(promptForAction): if(promptForAction):
self.ui.promptFor("Press enter to exit\n") self.ui.promptFor(getMessage("en", "enter-to-exit-prompt"))
class SyncplayUser(object): class SyncplayUser(object):
def __init__(self, username = None, room = None, file_ = None, position = 0): def __init__(self, username = None, room = None, file_ = None, position = 0):
@ -363,7 +356,7 @@ class SyncplayUser(object):
return False return False
sameName = self.file['name'] == file_['name'] sameName = self.file['name'] == file_['name']
sameSize = self.file['size'] == file_['size'] 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 return sameName and sameSize and sameDuration
def __lt__(self, other): def __lt__(self, other):
@ -378,16 +371,16 @@ class SyncplayUserlist(object):
def __showUserChangeMessage(self, username, room, file_): def __showUserChangeMessage(self, username, room, file_):
if (room and not 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) self.ui.showMessage(message)
elif (room and file_ and username != self.currentUser.username): elif (room and file_ and username != self.currentUser.username):
duration = utils.formatTime(file_['duration']) 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): 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) self.ui.showMessage(message)
if(self.currentUser.file and not self.currentUser.isFileSame(file_) and self.currentUser.room == room): 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) self.ui.showMessage(message)
differences = [] differences = []
if(self.currentUser.file['name'] <> file_['name']): if(self.currentUser.file['name'] <> file_['name']):
@ -396,7 +389,7 @@ class SyncplayUserlist(object):
differences.append("size") differences.append("size")
if(self.currentUser.file['duration'] <> file_['duration']): if(self.currentUser.file['duration'] <> file_['duration']):
differences.append("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) self.ui.showMessage(message)
def addUser(self, username, room, file_, position = 0, noMessage = False): def addUser(self, username, room, file_, position = 0, noMessage = False):
@ -411,7 +404,7 @@ class SyncplayUserlist(object):
def removeUser(self, username): def removeUser(self, username):
if(self._users.has_key(username)): if(self._users.has_key(username)):
self._users.pop(username) self._users.pop(username)
message = "<{}> has left".format(username) message = getMessage("en", "left-notification").format(username)
self.ui.showMessage(message) self.ui.showMessage(message)
def __displayModUserMessage(self, username, room, file_, user): def __displayModUserMessage(self, username, room, file_, user):
@ -463,12 +456,12 @@ class SyncplayUserlist(object):
if(self.currentUser.file): if(self.currentUser.file):
fileHasSameSizeAsYour = user.file['size'] == self.currentUser.file['size'] fileHasSameSizeAsYour = user.file['size'] == self.currentUser.file['size']
fileHasSameNameYour = user.file['name'] == self.currentUser.file['name'] 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 "" message += differentFileMessage if not fileHasSameSizeAsYour and fileHasSameNameYour else ""
return message return message
def __displayFileWatchersInRoomList(self, key, users): 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()): for user in sorted(users.itervalues()):
message = "<"+user.username+">" message = "<"+user.username+">"
if(self.currentUser.username == user.username): if(self.currentUser.username == user.username):
@ -478,13 +471,13 @@ class SyncplayUserlist(object):
def __displayPeopleInRoomWithNoFile(self, noFileList): def __displayPeopleInRoomWithNoFile(self, noFileList):
if (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()): for user in sorted(noFileList.itervalues()):
self.ui.showMessage("\t<" + user.username + ">", True, True) self.ui.showMessage("\t<" + user.username + ">", True, True)
def __displayListOfPeople(self, rooms): def __displayListOfPeople(self, rooms):
for roomName in sorted(rooms.iterkeys()): 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 noFileList = rooms[roomName].pop("__noFile__") if (rooms[roomName].has_key("__noFile__")) else None
for key in sorted(rooms[roomName].iterkeys()): for key in sorted(rooms[roomName].iterkeys()):
self.__displayFileWatchersInRoomList(key, rooms[roomName][key]) self.__displayFileWatchersInRoomList(key, rooms[roomName][key])

View File

@ -1,61 +1,26 @@
from syncplay.client import SyncplayClient from syncplay.client import SyncplayClient
import sys from syncplay.ui.ConfigurationGetter import ConfigurationGetter
from syncplay.ui.ConfigurationGetter import ConfigurationGetter, InvalidConfigValue
from syncplay import ui from syncplay import ui
try: try:
from syncplay.players.mpc import MPCHCAPIPlayer from syncplay.players.mpc import MPCHCAPIPlayer
except ImportError: except ImportError:
MPCHCAPIPlayer = None MPCHCAPIPlayer = None
from syncplay.players.mplayer import MplayerPlayer from syncplay.players.mplayer import MplayerPlayer
try:
from syncplay.ui.GuiConfiguration import GuiConfiguration
except ImportError:
GuiConfiguration = None
class SyncplayClientManager(object): class SyncplayClientManager(object):
def __init__(self): def run(self):
self._prepareArguments() config = ConfigurationGetter().getConfiguration()
self.interface = ui.getUi(graphical=not self.args.no_gui) interface = ui.getUi(graphical=not config["noGui"])
self._checkAndSaveConfiguration()
syncplayClient = None syncplayClient = None
if(self.argsGetter.playerType == "mpc"): if(config['playerType'] == "mpc"):
syncplayClient = SyncplayClient(MPCHCAPIPlayer, self.interface, self.args) syncplayClient = SyncplayClient(MPCHCAPIPlayer, interface, config)
elif(self.argsGetter.playerType == "mplayer"): elif(config['playerType'] == "mplayer"):
syncplayClient = SyncplayClient(MplayerPlayer, self.interface, self.args) syncplayClient = SyncplayClient(MplayerPlayer, interface, config)
if(syncplayClient): if(syncplayClient):
self.interface.addClient(syncplayClient) interface.addClient(syncplayClient)
syncplayClient.start(self.args.host, self.args.port) syncplayClient.start(config['host'], config['port'])
else: 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 MPLAYER_OSD_LEVEL = 1
UI_TIME_FORMAT = "[%X] " UI_TIME_FORMAT = "[%X] "
DEFAULT_ROOM = 'default' DEFAULT_ROOM = 'default'
DEFAULT_CONFIG_NAME = ".syncplay"
#Changing these might be ok #Changing these might be ok
SEEK_BOUNDARY = 1 REWIND_THRESHOLD = 4
SEEK_THRESHOLD = 1
SLOWDOWN_RATE = 0.95 SLOWDOWN_RATE = 0.95
SLOWDOWN_KICKIN_BOUNDARY = 1.5 SLOWDOWN_KICKIN_THRESHOLD = 1.5
SLOWDOWN_RESET_BOUNDARY = 0.1 SLOWDOWN_RESET_THRESHOLD = 0.1
DIFFFERENT_DURATION_BOUNDARY = 1 DIFFFERENT_DURATION_THRESHOLD = 1
PROTOCOL_TIMEOUT = 5 PROTOCOL_TIMEOUT = 5
RECONNECT_RETRIES = 10 RECONNECT_RETRIES = 10
#Usually there's no need to adjust these #Usually there's no need to adjust these
COMMANDS_UNDO = ["u", "undo", "revert"] COMMANDS_UNDO = ["u", "undo", "revert"]
COMMANDS_LIST = ["l", "list", "users"] COMMANDS_LIST = ["l", "list", "users"]

View File

@ -1,3 +1,5 @@
#coding:utf8
en = { en = {
# Client notifications # Client notifications
@ -27,6 +29,7 @@ en = {
"different-filesize-notification" : " (their file size is different from yours!)", "different-filesize-notification" : " (their file size is different from yours!)",
"file-played-by-notification" : "File: {} is being played by:", #User "file-played-by-notification" : "File: {} is being played by:", #User
"notplaying-notification" : "People who are not playing any file:", "notplaying-notification" : "People who are not playing any file:",
"userlist-room-notification" : "In room '{}':", #Room
# Client prompts # Client prompts
"enter-to-exit-prompt" : "Press enter to exit\n", "enter-to-exit-prompt" : "Press enter to exit\n",
@ -35,8 +38,46 @@ en = {
"server-timeout-error" : "Connection with server timed out" "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 = { messages = {
"en": en "en": en,
"pl": pl
} }
def getMessage(locale, type_): def getMessage(locale, type_):
@ -46,4 +87,4 @@ def getMessage(locale, type_):
if(messages["en"].has_key(type_)): if(messages["en"].has_key(type_)):
return messages["en"][type_] return messages["en"][type_]
else: else:
raise KeyError() raise KeyError()

View File

@ -1,8 +1,12 @@
import ConfigParser from ConfigParser import SafeConfigParser
import argparse import argparse
import os import os
import sys import sys
from syncplay import constants from syncplay import constants
try:
from syncplay.ui.GuiConfiguration import GuiConfiguration
except ImportError:
GuiConfiguration = None
class InvalidConfigValue(Exception): class InvalidConfigValue(Exception):
def __init__(self, message): def __init__(self, message):
@ -10,14 +14,122 @@ class InvalidConfigValue(Exception):
class ConfigurationGetter(object): class ConfigurationGetter(object):
def __init__(self): def __init__(self):
self._config = None self._config = {
self._args = None "host": None,
self._syncplayClient = None "port": constants.DEFAULT_PORT,
self._configFile = None "name": None,
self._parser = None "debug": False,
self._configName = ".syncplay" "forceGuiPrompt": False,
self.playerType = None "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): def _findWorkingDir(self):
frozen = getattr(sys, 'frozen', '') frozen = getattr(sys, 'frozen', '')
if not frozen: if not frozen:
@ -30,154 +142,81 @@ class ConfigurationGetter(object):
def _checkForPortableFile(self): def _checkForPortableFile(self):
path = self._findWorkingDir() path = self._findWorkingDir()
if(os.path.isfile(os.path.join(path, self._configName))): if(os.path.isfile(os.path.join(path, constants.DEFAULT_CONFIG_NAME))):
return os.path.join(path, self._configName) return os.path.join(path, constants.DEFAULT_CONFIG_NAME)
def _getConfigurationFilePath(self): def _getConfigurationFilePath(self):
self._configFile = self._checkForPortableFile() configFile = self._checkForPortableFile()
if(not self._configFile): if(not configFile):
if(os.name <> 'nt'): 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: 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): def _parseConfigFile(self, iniPath):
self._parser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network.', parser = SafeConfigParser()
epilog='If no options supplied values from .syncplay file will be used') parser.read(iniPath)
self._parser.add_argument('--no-gui', action='store_true', help='show no GUI') for section, options in self._iniStructure.items():
self._parser.add_argument('-a', '--host', metavar='hostname', type=str, help='server\'s address') if(parser.has_section(section)):
self._parser.add_argument('-n', '--name', metavar='username', type=str, help='desired username') for option in options:
self._parser.add_argument('-d', '--debug', action='store_true', help='debug mode') if(parser.has_option(section, option)):
self._parser.add_argument('-g', '--force-gui-prompt', action='store_true', help='make configuration prompt appear') self._config[option] = parser.get(section, option)
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 _readConfigValue(self, section_name, name): def _checkConfig(self):
try: try:
return self._config.get(section_name, name) self._validateArguments()
except ConfigParser.NoOptionError: except InvalidConfigValue:
return None try:
for key, value in self._promptForMissingArguments().items():
def _readMissingValuesFromConfigFile(self): self._config[key] = value
self._openConfigFile() self._checkConfig()
section_name = self._getSectionName() except:
try: sys.exit()
self._valuesToReadFromConfig(section_name)
except ConfigParser.NoSectionError:
pass
def _isPlayerMPCAndValid(self): def _promptForMissingArguments(self):
if(os.path.isfile(self._args.player_path)): if(self._config['noGui']):
if(self._args.player_path[-10:] == 'mpc-hc.exe' or self._args.player_path[-12:] == 'mpc-hc64.exe'): print "Some necessary arguments are missing, refer to --help"
return True sys.exit()
if(os.path.isfile(self._args.player_path + "\\mpc-hc.exe")): elif(GuiConfiguration):
self._args.player_path += "\\mpc-hc.exe" return GuiConfiguration(self._config).getProcessedConfiguration()
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 _addSpecialMPCFlags(self): def _saveConfig(self, iniPath):
self._args._args.extend(['/open', '/new']) if(self._config['noStore']):
return
def _isMplayerPathValid(self): parser = SafeConfigParser()
if("mplayer" in self._args.player_path): for section, options in self._iniStructure.items():
if os.access(self._args.player_path, os.X_OK): if(not parser.has_section(section)):
return True parser.add_section(section)
for path in os.environ['PATH'].split(':'): for option in options:
path = os.path.join(os.path.realpath(path), self._args.player_path) parser.set(section, option, str(self._config[option]))
if os.access(path, os.X_OK): parser.write(file(iniPath, "w"))
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 getConfiguration(self): def getConfiguration(self):
self._getConfigurationFilePath() iniPath = self._getConfigurationFilePath()
self._prepareArgParser() self._parseConfigFile(iniPath)
self._args = self._parser.parse_args() args = self._argparser.parse_args()
self._readMissingValuesFromConfigFile() self._overrideConfigWithArgs(args)
self._splitPortAndHost() if(self._config['forceGuiPrompt']):
return self._args try:
self._promptForMissingArguments()
except:
sys.exit()
self._checkConfig()
self._saveConfig(iniPath)
return self._config
class ServerConfigurationGetter(ConfigurationGetter): class ServerConfigurationGetter(object):
def getConfiguration(self): def getConfiguration(self):
self._prepareArgParser() self._prepareArgParser()
self._args = self._parser.parse_args() self._args = self._argparser.parse_args()
if(self._args.port == None): if(self._args.port == None):
self._args.port = constants.DEFAULT_PORT self._args.port = constants.DEFAULT_PORT
return self._args return self._args
def _prepareArgParser(self): 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') 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._argparser.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._argparser.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('--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 import cairo, gio, pango, atk, pangocairo, gobject #@UnusedImport
class GuiConfiguration: class GuiConfiguration:
def __init__(self, args, force=False): def __init__(self, config):
self.args = args self.config = config
self.closedAndNotSaved = False 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 = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("Syncplay Configuration")
self.window.set_title("Syncplay Configuration") self.window.connect("delete_event", lambda w, e: self._windowClosed())
self.window.connect("delete_event", lambda w, e: self._windowClosed()) vbox = gtk.VBox(False, 0)
vbox = gtk.VBox(False, 0) self.window.add(vbox)
self.window.add(vbox) vbox.show()
vbox.show() self._addLabeledEntries(config, vbox)
self._addLabeledEntries(args, vbox)
self.hostEntry.select_region(0, len(self.hostEntry.get_text()))
self.hostEntry.select_region(0, len(self.hostEntry.get_text())) button = gtk.Button(stock=gtk.STOCK_SAVE)
button = gtk.Button(stock=gtk.STOCK_SAVE) button.connect("clicked", lambda w: self._saveDataAndLeave())
button.connect("clicked", lambda w: self._saveDataAndLeave()) vbox.pack_start(button, True, True, 0)
vbox.pack_start(button, True, True, 0) button.set_flags(gtk.CAN_DEFAULT)
button.set_flags(gtk.CAN_DEFAULT) button.grab_default()
button.grab_default() button.show()
button.show() self.window.show()
self.window.show() gtk.main()
gtk.main()
def _windowClosed(self): def _windowClosed(self):
self.window.destroy() self.window.destroy()
gtk.main_quit() gtk.main_quit()
self.closedAndNotSaved = True self.closedAndNotSaved = True
def _addLabeledEntries(self, args, vbox): def _addLabeledEntries(self, config, vbox):
if(args.host == None): if(config['host'] == None):
host = "" host = ""
elif(":" in args.host): elif(":" in config['host']):
host = args.host host = config['host']
elif("port" in args):
host = args.host+":"+str(args.port)
else: else:
host = args.host host = config['host']+":"+str(config['port'])
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())
def _tryToFillUpMpcPath(self): self.hostEntry = self._addLabeledEntryToVbox('Host: ', host, vbox, lambda __, _: self._saveDataAndLeave())
if(self.args.player_path == None): 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: for path in constants.MPC_PATHS:
if(os.path.isfile(path)): if(os.path.isfile(path)):
self.args.player_path = path return path
return return playerPath
def getProcessedConfiguration(self): def getProcessedConfiguration(self):
if(self.closedAndNotSaved): if(self.closedAndNotSaved):
raise self.WindowClosed raise self.WindowClosed
return self.args return self.config
def _saveDataAndLeave(self): def _saveDataAndLeave(self):
self.args.host = self.hostEntry.get_text() self.config['host'] = self.hostEntry.get_text()
self.args.name = self.userEntry.get_text() self.config['name'] = self.userEntry.get_text()
self.args.room = self.roomEntry.get_text() self.config['room'] = self.roomEntry.get_text()
self.args.password = self.passEntry.get_text() self.config['password'] = self.passEntry.get_text()
self.args.player_path = self.mpcEntry.get_text() self.config['playerPath'] = self.mpcEntry.get_text()
self.window.destroy() self.window.destroy()
gtk.main_quit() gtk.main_quit()

View File

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