diff --git a/syncplay/__init__.py b/syncplay/__init__.py index f305ec3..dcb85cb 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,3 +1,3 @@ -version = '1.2.2' +version = '1.2.3' milestone = 'Tequila' projectURL = 'http://uriziel.github.com/syncplay/' \ No newline at end of file diff --git a/syncplay/client.py b/syncplay/client.py index 04bd22f..3cb476c 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -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]) diff --git a/syncplay/clientManager.py b/syncplay/clientManager.py index a60d075..58df21a 100644 --- a/syncplay/clientManager.py +++ b/syncplay/clientManager.py @@ -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() - diff --git a/syncplay/constants.py b/syncplay/constants.py index fc036b9..84360ac 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -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"] diff --git a/syncplay/messages.py b/syncplay/messages.py index 67b7883..5a96723 100644 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -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() \ No newline at end of file + raise KeyError() diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 5001547..f914e4c 100644 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -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?') diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 6185ed5..c006ec0 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -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() diff --git a/syncplayClient.py b/syncplayClient.py index 5e289dc..a9b4a85 100644 --- a/syncplayClient.py +++ b/syncplayClient.py @@ -2,4 +2,4 @@ from syncplay.clientManager import SyncplayClientManager if(__name__ == '__main__'): - SyncplayClientManager() + SyncplayClientManager().run()