|
|
|
"""Implement the "server settings" dialog."""
|
|
|
|
|
|
|
|
import os
|
|
|
|
import shutil
|
|
|
|
import logging
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
from PyQt5 import uic
|
|
|
|
from PyQt5.QtWidgets import QDialog, QFileDialog, QGroupBox
|
|
|
|
from PyQt5.QtGui import QIcon
|
|
|
|
|
|
|
|
from vasl_templates.main import app_settings
|
|
|
|
from vasl_templates.main_window import MainWindow
|
|
|
|
from vasl_templates.utils import show_msg_store
|
|
|
|
from vasl_templates.webapp.vassal import VassalShim, SUPPORTED_VASSAL_VERSIONS_DISPLAY
|
|
|
|
from vasl_templates.webapp.vasl_mod import set_vasl_mod, SUPPORTED_VASL_MOD_VERSIONS_DISPLAY
|
|
|
|
from vasl_templates.webapp.utils import MsgStore
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
_EXE_FSPEC = [ "Executable files (*.exe)" ] if os.name == "nt" else []
|
|
|
|
|
|
|
|
SERVER_SETTINGS = {
|
|
|
|
"vassal-dir": { "type": "dir", "name": "VASSAL directory" },
|
|
|
|
"vasl-mod": { "type": "file", "name": "VASL module", "fspec": ["VASL module files (*.vmod)"] },
|
|
|
|
"vasl-extns-dir": { "type": "dir", "name": "VASL extensions directory" },
|
|
|
|
"boards-dir": { "type": "dir", "name": "VASL boards directory" },
|
|
|
|
"java-path": { "type": "file", "name": "Java executable", "allow_on_path": True, "fspec": _EXE_FSPEC },
|
|
|
|
"webdriver-path": { "type": "file", "name": "webdriver", "allow_on_path": True, "fspec": _EXE_FSPEC },
|
|
|
|
"chapter-h-notes-dir": { "type": "dir", "name": "Chapter H notes directory" },
|
|
|
|
"chapter-h-image-scaling": { "type": "int", "name": "Chapter H image scaling" },
|
|
|
|
"user-files-dir": { "type": "dir", "name": "user files directory", "allow_urls": True },
|
|
|
|
}
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
class ServerSettingsDialog( QDialog ):
|
|
|
|
"""Let the user configure the server settings."""
|
|
|
|
|
|
|
|
def __init__( self, parent ) :
|
|
|
|
|
|
|
|
# initialize
|
|
|
|
super().__init__( parent=parent )
|
|
|
|
|
|
|
|
# initialize the UI
|
|
|
|
base_dir = os.path.split( __file__ )[0]
|
|
|
|
dname = os.path.join( base_dir, "ui/server_settings.ui" )
|
|
|
|
uic.loadUi( dname, self )
|
|
|
|
self.setFixedSize( self.size() )
|
|
|
|
|
|
|
|
# initialize the UI
|
|
|
|
for key in SERVER_SETTINGS:
|
|
|
|
btn = getattr( self, "select_{}_button".format( key.replace("-","_") ), None )
|
|
|
|
if btn:
|
|
|
|
btn.setIcon( QIcon( os.path.join( base_dir, "resources/file_browser.png" ) ) )
|
|
|
|
self.vassal_dir.setToolTip( "Supported versions: {}".format( SUPPORTED_VASSAL_VERSIONS_DISPLAY ) )
|
|
|
|
self.vasl_mod.setToolTip( "Supported versions: {}".format( SUPPORTED_VASL_MOD_VERSIONS_DISPLAY ) )
|
|
|
|
self.webdriver_path.setToolTip( "Configure either geckodriver or chromedriver here." )
|
|
|
|
|
|
|
|
# initialize the UI
|
|
|
|
for attr in dir(self):
|
|
|
|
attr = getattr( self, attr )
|
|
|
|
if isinstance( attr, QGroupBox ):
|
|
|
|
attr.setStyleSheet( "QGroupBox { font-weight: bold; } " )
|
|
|
|
|
|
|
|
# initialize click handlers
|
|
|
|
def make_click_handler( func, *args ): #pylint: disable=missing-docstring
|
|
|
|
# FUDGE! Python looks up variables passed in to a lambda when it is *invoked*, so we need
|
|
|
|
# this intermediate function to create lambda's with their arguments at *creation time*.
|
|
|
|
return lambda: func( *args )
|
|
|
|
for key,vals in SERVER_SETTINGS.items():
|
|
|
|
key2 = key.replace( "-", "_" )
|
|
|
|
btn = getattr( self, "select_{}_button".format( key2 ), None )
|
|
|
|
if btn:
|
|
|
|
ctrl = self._get_control( key )
|
|
|
|
if vals["type"] == "dir":
|
|
|
|
func = make_click_handler( self._on_select_dir, ctrl, vals["name"] )
|
|
|
|
elif vals["type"] == "file":
|
|
|
|
func = make_click_handler( self._on_select_file, ctrl, vals["name"], vals["fspec"] )
|
|
|
|
else:
|
|
|
|
assert False
|
|
|
|
btn.clicked.connect( func )
|
|
|
|
self.ok_button.clicked.connect( self.on_ok )
|
|
|
|
self.cancel_button.clicked.connect( self.on_cancel )
|
|
|
|
|
|
|
|
# initialize handlers
|
|
|
|
self.chapter_h_notes_dir.textChanged.connect( self.on_chapter_h_notes_dir_changed )
|
|
|
|
|
|
|
|
# load the current server settings
|
|
|
|
for key in SERVER_SETTINGS:
|
|
|
|
val = app_settings.value( "ServerSettings/"+key ) or ""
|
|
|
|
ctrl = self._get_control( key )
|
|
|
|
ctrl.setText( str(val).strip() )
|
|
|
|
|
|
|
|
def _on_select_dir( self, ctrl, name ):
|
|
|
|
"""Ask the user to select a directory."""
|
|
|
|
dname = QFileDialog.getExistingDirectory(
|
|
|
|
self, "Select {}".format( name ),
|
|
|
|
ctrl.text(),
|
|
|
|
QFileDialog.ShowDirsOnly
|
|
|
|
)
|
|
|
|
if dname:
|
|
|
|
ctrl.setText( dname )
|
|
|
|
|
|
|
|
def _on_select_file( self, ctrl, name, fspec ):
|
|
|
|
"""Ask the user to select a file."""
|
|
|
|
assert isinstance( fspec, list )
|
|
|
|
fspec = fspec[:]
|
|
|
|
fspec.append( "All files ({})".format( "*.*" if os.name == "nt" else "*" ) )
|
|
|
|
fname = QFileDialog.getOpenFileName(
|
|
|
|
self, "Select {}".format( name ),
|
|
|
|
ctrl.text(),
|
|
|
|
";;".join( fspec )
|
|
|
|
)[0]
|
|
|
|
if fname:
|
|
|
|
ctrl.setText( fname )
|
|
|
|
|
|
|
|
def on_ok( self ):
|
|
|
|
"""Accept the new server settings."""
|
|
|
|
|
|
|
|
# save a copy of the current settings
|
|
|
|
prev_settings = {
|
|
|
|
key: app_settings.value( "ServerSettings/"+key, "" )
|
|
|
|
for key in SERVER_SETTINGS
|
|
|
|
}
|
|
|
|
|
|
|
|
# unload the dialog
|
|
|
|
# NOTE: Typing an unknown path into QFileDialog.getExistingDirectory() causes that directory
|
|
|
|
# to be created!?!? It doesn't really matter, since the user could have also manually typed
|
|
|
|
# an unknown path into an edit box, so we need to validate everything anyway.
|
|
|
|
new_settings = {}
|
|
|
|
for key, vals in SERVER_SETTINGS.items():
|
|
|
|
ctrl = self._get_control( key )
|
|
|
|
func = getattr( self, "_unload_"+vals["type"] )
|
|
|
|
args, kwargs = [ vals["name"] ], {}
|
|
|
|
for k in ("allow_on_path","allow_urls"):
|
|
|
|
if k in vals:
|
|
|
|
kwargs[ k ] = vals[ k ]
|
|
|
|
val = func( ctrl, *args, **kwargs )
|
|
|
|
if val is None:
|
|
|
|
# nb: something failed validation, an error message has already been shown
|
|
|
|
return
|
|
|
|
new_settings[ key ] = val
|
|
|
|
|
|
|
|
# install the new settings
|
|
|
|
for key in SERVER_SETTINGS:
|
|
|
|
app_settings.setValue( "ServerSettings/"+key, new_settings[key] )
|
|
|
|
try:
|
|
|
|
install_server_settings( False )
|
|
|
|
except Exception as ex: #pylint: disable=broad-except
|
|
|
|
logging.error( traceback.format_exc() )
|
|
|
|
MainWindow.showErrorMsg( "Couldn't install the server settings:\n\n{}".format( ex ) )
|
|
|
|
# rollback the changes
|
|
|
|
for key,val in prev_settings.items():
|
|
|
|
app_settings.setValue( "ServerSettings/"+key, val )
|
|
|
|
try:
|
|
|
|
install_server_settings( False )
|
|
|
|
except Exception as ex2: #pylint: disable=broad-except
|
|
|
|
logging.error( traceback.format_exc() )
|
|
|
|
MainWindow.showErrorMsg( "Couldn't rollback the server settings:\n\n{}".format( ex2 ) )
|
|
|
|
return
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
# check if any key settings were changed
|
|
|
|
KEY_SETTINGS = [ "vassal-dir", "vasl-mod", "vasl-extns-dir", "chapter-h-notes-dir" ]
|
|
|
|
changed = [
|
|
|
|
key for key in KEY_SETTINGS
|
|
|
|
if app_settings.value( "ServerSettings/"+key, "" ) != prev_settings[key]
|
|
|
|
]
|
|
|
|
if len(changed) == 1:
|
|
|
|
MainWindow.showInfoMsg( "The {} was changed - you should restart the program.".format(
|
|
|
|
SERVER_SETTINGS[ changed[0] ][ "name" ]
|
|
|
|
) )
|
|
|
|
elif len(changed) > 1:
|
|
|
|
MainWindow.showInfoMsg( "Some key settings were changed - you should restart the program." )
|
|
|
|
|
|
|
|
def on_cancel( self ):
|
|
|
|
"""Cancel the dialog."""
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
def _update_ui( self ):
|
|
|
|
"""Update the UI."""
|
|
|
|
rc = self.chapter_h_notes_dir.text().strip() != ""
|
|
|
|
self.chapter_h_image_scaling_label.setEnabled( rc )
|
|
|
|
self.chapter_h_image_scaling_label2.setEnabled( rc )
|
|
|
|
self.chapter_h_image_scaling.setEnabled( rc )
|
|
|
|
|
|
|
|
def on_chapter_h_notes_dir_changed( self, val ): #pylint: disable=unused-argument
|
|
|
|
"""Called when the Chapter H notes directory is changed."""
|
|
|
|
self._update_ui()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _unload_dir( ctrl, name, allow_urls=False ):
|
|
|
|
"""Unload and validate a directory path."""
|
|
|
|
dname = ctrl.text().strip()
|
|
|
|
if allow_urls and dname.startswith( ("http://","https://") ):
|
|
|
|
return dname
|
|
|
|
if dname and not os.path.isdir( dname ):
|
|
|
|
MainWindow.showErrorMsg( "Can't find the {}:\n {}".format( name, dname ) )
|
|
|
|
ctrl.setFocus()
|
|
|
|
return None
|
|
|
|
return dname
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _unload_file( ctrl, name, allow_on_path=False ):
|
|
|
|
"""Unload and validate a file path."""
|
|
|
|
fname = ctrl.text().strip()
|
|
|
|
def is_valid( fname ): #pylint: disable=missing-docstring
|
|
|
|
if not os.path.isabs(fname) and allow_on_path:
|
|
|
|
return shutil.which( fname ) is not None
|
|
|
|
return os.path.isfile( fname )
|
|
|
|
if fname and not is_valid(fname):
|
|
|
|
if not os.path.isabs(fname) and allow_on_path:
|
|
|
|
MainWindow.showErrorMsg( "Can't find the {} on the PATH:\n {}".format( name, fname ) )
|
|
|
|
else:
|
|
|
|
MainWindow.showErrorMsg( "Can't find the {}:\n {}".format( name, fname ) )
|
|
|
|
ctrl.setFocus()
|
|
|
|
return None
|
|
|
|
return fname
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _unload_int( ctrl, name ):
|
|
|
|
"""Unload and validate an integer value."""
|
|
|
|
val = ctrl.text().strip()
|
|
|
|
if val and not val.isdigit():
|
|
|
|
MainWindow.showErrorMsg( "{} must be a numeric value.".format( name ) )
|
|
|
|
ctrl.setFocus()
|
|
|
|
return None
|
|
|
|
return val
|
|
|
|
|
|
|
|
def _get_control( self, key ):
|
|
|
|
"""Return the UI control for the specified server setting."""
|
|
|
|
return getattr( self, key.replace("-","_") )
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
def install_server_settings( is_startup ):
|
|
|
|
"""Install the server settings."""
|
|
|
|
|
|
|
|
# install the server settings
|
|
|
|
from vasl_templates.webapp import app
|
|
|
|
for key in SERVER_SETTINGS:
|
|
|
|
key2 = key.replace( "-", "_" ).upper()
|
|
|
|
app.config[ key2 ] = app_settings.value( "ServerSettings/"+key )
|
|
|
|
|
|
|
|
# initialize
|
|
|
|
if is_startup:
|
|
|
|
msg_store = None # nb: we let the web page show startup messages
|
|
|
|
else:
|
|
|
|
msg_store = MsgStore()
|
|
|
|
|
|
|
|
# load the VASL module
|
|
|
|
fname = app_settings.value( "ServerSettings/vasl-mod" )
|
|
|
|
set_vasl_mod( fname, msg_store )
|
|
|
|
|
|
|
|
# check the VASSAL version
|
|
|
|
VassalShim.check_vassal_version( msg_store )
|
|
|
|
|
|
|
|
# show any messages
|
|
|
|
if msg_store:
|
|
|
|
show_msg_store( msg_store )
|