syncplay/syncplay/ui/ConfigurationGetter.py
2015-01-03 13:58:18 +00:00

408 lines
18 KiB
Python
Executable File

from ConfigParser import SafeConfigParser, DEFAULTSECT
import argparse
import os
import sys
from syncplay import constants, utils, version, milestone
from syncplay.messages import getMessage, setLanguage, isValidLanguage
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:
print getMessage("unable-import-gui-error")
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,
"slowdownThreshold": constants.DEFAULT_SLOWDOWN_KICKIN_THRESHOLD,
"rewindThreshold": constants.DEFAULT_REWIND_THRESHOLD,
"fastforwardThreshold": constants.DEFAULT_FASTFORWARD_THRESHOLD,
"rewindOnDesync": True,
"slowOnDesync": True,
"fastforwardOnDesync": True,
"dontSlowDownWithMe": False,
"filenamePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
"filesizePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
"pauseOnLeave": False,
"readyAtStart": False,
"clearGUIData": False,
"language" : "",
"checkForUpdatesAutomatically" : None,
"lastCheckedForUpdates" : "",
"resetConfig" : False,
"showOSD" : True,
"showOSDWarnings" : True,
"showSlowdownOSD" : True,
"showDifferentRoomOSD" : False,
"showSameRoomOSD" : True,
"showNonControllerOSD" : False,
"showContactInfo" : True,
"showDurationNotification" : True
}
self._defaultConfig = self._config.copy()
#
# Custom validation in self._validateArguments
#
self._required = [
"host",
"port",
"room",
"playerPath",
"playerClass",
]
self._boolean = [
"debug",
"forceGuiPrompt",
"noGui",
"noStore",
"dontSlowDownWithMe",
"pauseOnLeave",
"readyAtStart",
"clearGUIData",
"rewindOnDesync",
"slowOnDesync",
"fastforwardOnDesync",
"pauseOnLeave",
"clearGUIData",
"resetConfig",
"showOSD",
"showOSDWarnings",
"showSlowdownOSD",
"showDifferentRoomOSD",
"showSameRoomOSD",
"showNonControllerOSD",
"showContactInfo" ,
"showDurationNotification"
]
self._tristate = [
"checkForUpdatesAutomatically"
]
self._numeric = [
"slowdownThreshold",
"rewindThreshold",
"fastforwardThreshold",
]
self._iniStructure = {
"server_data": ["host", "port", "password"],
"client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave", "readyAtStart"],
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showContactInfo" , "showDurationNotification"],
"general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"]
}
self._playerFactory = PlayerFactory()
def _validateArguments(self):
if self._config['resetConfig']:
language = self._config['language']
self._config = self._defaultConfig
self._config['language'] = language
raise InvalidConfigValue("*"+getMessage("config-cleared-notification"))
if not isValidLanguage(self._config['language']):
self._config['language'] = ""
def _isPortValid(varToTest):
try:
if varToTest == "" or varToTest is None:
return False
if str(varToTest).isdigit() == False:
return False
varToTest = int(varToTest)
if varToTest > 65535 or varToTest < 1:
return False
return True
except:
return False
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._tristate:
if self._config[key] == "True":
self._config[key] = True
elif self._config[key] == "False":
self._config[key] = False
elif self._config[key] == "None":
self._config[key] = None
for key in self._numeric:
self._config[key] = float(self._config[key])
for key in self._required:
if key == "playerPath":
player = None
if self._config["playerPath"]:
player = self._playerFactory.getPlayerByPath(self._config["playerPath"])
if player:
self._config["playerClass"] = player
else:
raise InvalidConfigValue(getMessage("player-path-config-error"))
playerPathErrors = player.getPlayerPathErrors(self._config["playerPath"], self._config['file'] if self._config['file'] else None)
if playerPathErrors:
raise InvalidConfigValue(playerPathErrors)
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 = (_isPortValid(self._config["port"]) == False)
if hostNotValid:
raise InvalidConfigValue(getMessage("no-hostname-config-error"))
elif portNotValid:
raise InvalidConfigValue(getMessage("invalid-port-config-error"))
elif self._config[key] == "" or self._config[key] is None:
raise InvalidConfigValue(getMessage("empty-value-config-error").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)
try:
port = int(port)
except ValueError:
try:
port = port.encode('ascii', 'ignore')
except:
port = ""
return host, 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'] or not GuiConfiguration:
if error:
print "{}!".format(error)
print getMessage("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]).replace('%', '%%'))
if changed:
parser.write(codecs.open(iniPath, "wb", "utf_8_sig"))
def _forceGuiPrompt(self):
if GuiConfiguration:
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()
else:
try:
self._validateArguments()
except InvalidConfigValue:
self._promptForMissingArguments()
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()
loadedPaths = []
for location in locations:
for name in constants.CONFIG_NAMES:
path = location + os.path.sep + name
if os.path.isfile(path) and (os.name == 'nt' or path != os.path.join(os.getenv('HOME', '.'), constants.DEFAULT_CONFIG_NAME_LINUX)):
loadedPaths.append("'" + os.path.normpath(path) + "'")
self._parseConfigFile(path, createConfig=False)
self._checkConfig()
return loadedPaths
def getConfiguration(self):
iniPath = self._getConfigurationFilePath()
self._parseConfigFile(iniPath)
#
# Watch out for the method self._overrideConfigWithArgs when you're adding custom multi-word command line arguments
#
if self._config['language']:
setLanguage(self._config['language'])
self._argparser = argparse.ArgumentParser(description=getMessage("argument-description"),
epilog=getMessage("argument-epilog"))
self._argparser.add_argument('--no-gui', action='store_true', help=getMessage("nogui-argument"))
self._argparser.add_argument('-a', '--host', metavar='hostname', type=str, help=getMessage("host-argument"))
self._argparser.add_argument('-n', '--name', metavar='username', type=str, help=getMessage("name-argument"))
self._argparser.add_argument('-d', '--debug', action='store_true', help=getMessage("debug-argument"))
self._argparser.add_argument('-g', '--force-gui-prompt', action='store_true', help=getMessage("force-gui-prompt-argument"))
self._argparser.add_argument('--no-store', action='store_true', help=getMessage("no-store-argument"))
self._argparser.add_argument('-r', '--room', metavar='room', type=str, nargs='?', help=getMessage("room-argument"))
self._argparser.add_argument('-p', '--password', metavar='password', type=str, nargs='?', help=getMessage("password-argument"))
self._argparser.add_argument('--player-path', metavar='path', type=str, help=getMessage("player-path-argument"))
self._argparser.add_argument('--language', metavar='language', type=str, help=getMessage("language-argument"))
self._argparser.add_argument('file', metavar='file', type=str, nargs='?', help=getMessage("file-argument"))
self._argparser.add_argument('--clear-gui-data', action='store_true', help=getMessage("clear-gui-data-argument"))
self._argparser.add_argument('-v', '--version', action='store_true', help=getMessage("version-argument"))
self._argparser.add_argument('_args', metavar='options', type=str, nargs='*', help=getMessage("args-argument"))
args = self._argparser.parse_args()
if args.version:
print getMessage("version-message").format(version, milestone)
sys.exit()
self._overrideConfigWithArgs(args)
if self._config['file'] and self._config['file'][:2] == "--":
self._config['playerArgs'].insert(0, self._config['file'])
self._config['file'] = None
# Arguments not validated yet - booleans are still text values
if self._config['language']:
setLanguage(self._config['language'])
if (self._config['forceGuiPrompt'] == "True" or not self._config['file']) and GuiConfiguration and not self._config['noGui']:
self._forceGuiPrompt()
self._checkConfig()
self._saveConfig(iniPath)
if self._config['file']:
self._config['loadedRelativePaths'] = self._loadRelativeConfiguration()
if self._config['language']:
setLanguage(self._config['language'])
if not GuiConfiguration:
self._config['noGui'] = True
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")