diff --git a/syncplay/client.py b/syncplay/client.py index 04bd22f..5f98ca1 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -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 @@ -324,7 +324,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) 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..51bab9a 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -5,6 +5,7 @@ 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 diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 5001547..cbb4794 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,115 @@ 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, + } + self._required = [ + "host", + "port", + "name", + "playerPath", + "playerType", + ] + self._iniStructure = { + "server_data": ["host", "port", "password"], + "client_settings": ["name", "room", "playerPath"] + } + + 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.ini') + 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 +135,79 @@ 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): + 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()