syncplay/syncplay/ui/ConfigurationGetter.py

296 lines
13 KiB
Python

from ConfigParser import SafeConfigParser, DEFAULTSECT
import argparse
import os
import sys
from syncplay import constants, utils
from syncplay.messages import getMessage
from syncplay.players.playerFactory import PlayerFactory
import codecs
try:
from syncplay.ui.GuiConfiguration import GuiConfiguration
from PySide import QtGui #@UnresolvedImport
from PySide.QtCore import QCoreApplication
except ImportError:
GuiConfiguration = None
class InvalidConfigValue(Exception):
def __init__(self, message):
Exception.__init__(self, message)
class ConfigurationGetter(object):
def __init__(self):
self._config = {
"host": None,
"port": constants.DEFAULT_PORT,
"name": None,
"debug": False,
"forceGuiPrompt": True,
"noGui": False,
"noStore": False,
"room": "",
"password": None,
"playerPath": None,
"file": None,
"playerArgs": [],
"playerClass": None,
"slowOnDesync": True,
"rewindOnDesync": True,
"malUsername": "",
"malPassword": "",
"malPassword": "",
"filenamePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
"filesizePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
"pauseOnLeave": False,
"clearGUIData": False
}
#
#Custom validation in self._validateArguments
#
self._required = [
"host",
"port",
"name",
"room",
"playerPath",
"playerClass",
]
self._boolean = [
"debug",
"forceGuiPrompt",
"noGui",
"noStore",
"slowOnDesync",
"pauseOnLeave",
"rewindOnDesync",
"clearGUIData"
]
self._iniStructure = {
"server_data": ["host", "port", "password"],
"client_settings": ["name", "room", "playerPath", "slowOnDesync", "rewindOnDesync", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave"],
"mal": ["malPassword", "malUsername"]
}
#
#Watch out for the method self._overrideConfigWithArgs when you're adding custom multi-word command line arguments
#
self._argparser = argparse.ArgumentParser(description=getMessage("en", "argument-description"),
epilog=getMessage("en", "argument-epilog"))
self._argparser.add_argument('--no-gui', action='store_true', help=getMessage("en", "nogui-argument"))
self._argparser.add_argument('-a', '--host', metavar='hostname', type=str, help=getMessage("en", "host-argument"))
self._argparser.add_argument('-n', '--name', metavar='username', type=str, help=getMessage("en", "name-argument"))
self._argparser.add_argument('-d', '--debug', action='store_true', help=getMessage("en", "debug-argument"))
self._argparser.add_argument('-g', '--force-gui-prompt', action='store_true', help=getMessage("en", "force-gui-prompt-argument"))
self._argparser.add_argument('--no-store', action='store_true', help=getMessage("en", "no-store-argument"))
self._argparser.add_argument('-r', '--room', metavar='room', type=str, nargs='?', help=getMessage("en", "room-argument"))
self._argparser.add_argument('-p', '--password', metavar='password', type=str, nargs='?', help=getMessage("en", "password-argument"))
self._argparser.add_argument('--player-path', metavar='path', type=str, help=getMessage("en", "player-path-argument"))
self._argparser.add_argument('file', metavar='file', type=str, nargs='?', help=getMessage("en", "file-argument"))
self._argparser.add_argument('--clear-gui-data', action='store_true', help=getMessage("en", "clear-gui-data-argument"))
self._argparser.add_argument('_args', metavar='options', type=str, nargs='*', help=getMessage("en", "args-argument"))
self._playerFactory = PlayerFactory()
def _validateArguments(self):
for key in self._boolean:
if(self._config[key] == "True"):
self._config[key] = True
elif(self._config[key] == "False"):
self._config[key] = False
for key in self._required:
if(key == "playerPath"):
player = self._playerFactory.getPlayerByPath(self._config["playerPath"])
if(player):
self._config["playerClass"] = player
else:
raise InvalidConfigValue("Player path is not set properly")
if player.__name__ in ['MpvPlayer', 'MplayerPlayer']:
if not self._config['file']:
raise InvalidConfigValue("File must be selected before starting your player")
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.capitalize()))
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"
if(key == "clear_gui_data"):
key = "clearGUIData"
self._config[key] = val
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 _checkForPortableFile(self):
path = utils.findWorkingDir()
for name in constants.CONFIG_NAMES:
if(os.path.isfile(os.path.join(path, name))):
return os.path.join(path, name)
def _getConfigurationFilePath(self):
configFile = self._checkForPortableFile()
if not configFile:
for name in constants.CONFIG_NAMES:
if(configFile and os.path.isfile(configFile)):
break
if(os.name <> 'nt'):
configFile = os.path.join(os.getenv('HOME', '.'), name)
else:
configFile = os.path.join(os.getenv('APPDATA', '.'), name)
if(configFile and not os.path.isfile(configFile)):
if(os.name <> 'nt'):
configFile = os.path.join(os.getenv('HOME', '.'), constants.DEFAULT_CONFIG_NAME_LINUX)
else:
configFile = os.path.join(os.getenv('APPDATA', '.'), constants.DEFAULT_CONFIG_NAME_WINDOWS)
return configFile
def _parseConfigFile(self, iniPath, createConfig = True):
parser = SafeConfigParserUnicode()
if(not os.path.isfile(iniPath)):
if(createConfig):
open(iniPath, 'w').close()
else:
return
parser.readfp(codecs.open(iniPath, "r", "utf_8_sig"))
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 _checkConfig(self):
try:
self._validateArguments()
except InvalidConfigValue as e:
try:
for key, value in self._promptForMissingArguments(e.message).items():
self._config[key] = value
self._checkConfig()
except:
sys.exit()
def _promptForMissingArguments(self, error = None):
if(self._config['noGui']):
print getMessage("en", "missing-arguments-error")
sys.exit()
elif(GuiConfiguration):
gc = GuiConfiguration(self._config, error = error)
gc.setAvailablePaths(self._playerFactory.getAvailablePlayerPaths())
gc.run()
return gc.getProcessedConfiguration()
def __wasOptionChanged(self, parser, section, option):
if (parser.has_option(section, option)):
if (parser.get(section, option) != unicode(self._config[option])):
return True
else:
return True
def _saveConfig(self, iniPath):
changed = False
if(self._config['noStore']):
return
parser = SafeConfigParserUnicode()
parser.readfp(codecs.open(iniPath, "r", "utf_8_sig"))
for section, options in self._iniStructure.items():
if(not parser.has_section(section)):
parser.add_section(section)
changed = True
for option in options:
if(self.__wasOptionChanged(parser, section, option)):
changed = True
parser.set(section, option, unicode(self._config[option]))
if(changed):
parser.write(codecs.open(iniPath, "wb", "utf_8_sig"))
def _forceGuiPrompt(self):
try:
self._validateArguments()
except InvalidConfigValue:
pass
try:
if(self._config['noGui'] == False):
for key, value in self._promptForMissingArguments().items():
self._config[key] = value
except GuiConfiguration.WindowClosed:
sys.exit()
def __getRelativeConfigLocations(self):
locations = []
path = os.path.dirname(os.path.realpath(self._config['file']))
locations.append(path)
while path != os.path.dirname(path):
path = os.path.dirname(path)
locations.append(path)
locations.reverse()
return locations
def _loadRelativeConfiguration(self):
locations = self.__getRelativeConfigLocations()
for location in locations:
for name in constants.CONFIG_NAMES:
path = location + os.path.sep + name
self._parseConfigFile(path, createConfig = False)
self._checkConfig()
def getConfiguration(self):
iniPath = self._getConfigurationFilePath()
self._parseConfigFile(iniPath)
args = self._argparser.parse_args()
self._overrideConfigWithArgs(args)
#Arguments not validated yet - booleans are still text values
if(self._config['forceGuiPrompt'] == "True" or not self._config['file']):
self._forceGuiPrompt()
self._checkConfig()
self._saveConfig(iniPath)
if(self._config['file']):
self._loadRelativeConfiguration()
if(not self._config['noGui']):
from syncplay.vendor import qt4reactor
if QCoreApplication.instance() is None:
self.app = QtGui.QApplication(sys.argv)
qt4reactor.install()
return self._config
class SafeConfigParserUnicode(SafeConfigParser):
def write(self, fp):
"""Write an .ini-format representation of the configuration state."""
if self._defaults:
fp.write("[%s]\n" % DEFAULTSECT)
for (key, value) in self._defaults.items():
fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
fp.write("\n")
for section in self._sections:
fp.write("[%s]\n" % section)
for (key, value) in self._sections[section].items():
if key == "__name__":
continue
if (value is not None) or (self._optcre == self.OPTCRE):
key = " = ".join((key, unicode(value).replace('\n', '\n\t')))
fp.write("%s\n" % (key))
fp.write("\n")