Create attractive VASL scenarios, with loads of useful information embedded to assist with game play. https://vasl-templates.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
vasl-templates/vasl_templates/server_settings.py

261 lines
11 KiB

"""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 )