simplified loading of SlipstreamConfig

This commit is contained in:
Leyla Becker 2025-08-16 22:17:07 -05:00
parent 9e8e830ab1
commit ccd50b0275
3 changed files with 84 additions and 128 deletions

View file

@ -1,19 +1,14 @@
package net.vhati.modmanager; package net.vhati.modmanager;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
import java.util.Properties;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ILoggingEvent;
@ -36,8 +31,8 @@ public class FTLModManager {
public static final String APP_NAME = "Slipstream Mod Manager"; public static final String APP_NAME = "Slipstream Mod Manager";
public static final ComparableVersion APP_VERSION = new ComparableVersion( "1.9.1" ); public static final ComparableVersion APP_VERSION = new ComparableVersion( "1.9.1" );
public static final String APP_URL = "https://subsetgames.com/forum/viewtopic.php?f=12&t=17102"; public static final String APP_URL = "TODO";
public static final String APP_AUTHOR = "Vhati"; public static final String APP_AUTHOR = "jan-leila";
public static void main( String[] args ) { public static void main( String[] args ) {
@ -53,7 +48,7 @@ public class FTLModManager {
PatternLayoutEncoder encoder = new PatternLayoutEncoder(); PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext( lc ); encoder.setContext( lc );
encoder.setCharset( Charset.forName( "UTF-8" ) ); encoder.setCharset(StandardCharsets.UTF_8);
encoder.setPattern( "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" ); encoder.setPattern( "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" );
encoder.start(); encoder.start();
@ -76,7 +71,7 @@ public class FTLModManager {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override @Override
public void uncaughtException( Thread t, Throwable e ) { public void uncaughtException( Thread t, Throwable e ) {
log.error( "Uncaught exception in thread: "+ t.toString(), e ); log.error("Uncaught exception in thread: {}", t.toString(), e);
} }
}); });
@ -86,20 +81,15 @@ public class FTLModManager {
} }
// Ensure all popups are triggered from the event dispatch thread. // Ensure all popups are triggered from the event dispatch thread.
SwingUtilities.invokeLater(FTLModManager::guiInit);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
guiInit();
}
});
} }
private static void guiInit() { private static void guiInit() {
try { try {
// TODO: get mods file from env var
// Nag if the jar was double-clicked. // Nag if the jar was double-clicked.
if ( new File( "./mods/" ).exists() == false ) { if (!new File("./mods/").exists()) {
String currentPath = new File( "." ).getAbsoluteFile().getParentFile().getAbsolutePath(); String currentPath = new File( "." ).getAbsoluteFile().getParentFile().getAbsolutePath();
log.error( String.format( "Slipstream could not find its own folder (Currently in \"%s\"), exiting...", currentPath ) ); log.error( String.format( "Slipstream could not find its own folder (Currently in \"%s\"), exiting...", currentPath ) );
@ -108,46 +98,13 @@ public class FTLModManager {
throw new ExitException(); throw new ExitException();
} }
// TODO: get config file from env var
File configFile = new File( "modman.cfg" ); File configFile = new File( "modman.cfg" );
boolean writeConfig = false; SlipstreamConfig appConfig = new SlipstreamConfig(configFile);
Properties props = new Properties();
props.setProperty( SlipstreamConfig.ALLOW_ZIP, "false" );
props.setProperty( SlipstreamConfig.FTL_DATS_PATH, "" ); // Prompt.
props.setProperty( SlipstreamConfig.STEAM_DISTRO, "" ); // Prompt.
props.setProperty( SlipstreamConfig.STEAM_EXE_PATH, "" ); // Prompt.
props.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "" ); // Prompt.
props.setProperty( SlipstreamConfig.NEVER_RUN_FTL, "false" );
props.setProperty( SlipstreamConfig.UPDATE_CATALOG, "" ); // Prompt.
props.setProperty( SlipstreamConfig.UPDATE_APP, "" ); // Prompt.
props.setProperty( SlipstreamConfig.USE_DEFAULT_UI, "false" );
props.setProperty( SlipstreamConfig.REMEMBER_GEOMETRY, "true" );
// "manager_geometry" doesn't have a default.
// Read the config file.
InputStream in = null;
try {
if ( configFile.exists() ) {
log.debug( "Loading config file" );
in = new FileInputStream( configFile );
props.load( new InputStreamReader( in, "UTF-8" ) );
} else {
writeConfig = true; // Create a new cfg, but only if necessary.
}
}
catch ( IOException e ) {
log.error( "Error loading config", e );
showErrorDialog( "Error loading config from "+ configFile.getPath() );
}
finally {
try {if ( in != null ) in.close();}
catch ( IOException e ) {}
}
SlipstreamConfig appConfig = new SlipstreamConfig( props, configFile );
// Look-and-Feel. // Look-and-Feel.
boolean useDefaultUI = "true".equals( appConfig.getProperty( SlipstreamConfig.USE_DEFAULT_UI, "false" ) ); boolean useDefaultUI = Boolean.parseBoolean(appConfig.getProperty(SlipstreamConfig.USE_DEFAULT_UI, "false"));
if ( !useDefaultUI ) { if ( !useDefaultUI ) {
LookAndFeel defaultLaf = UIManager.getLookAndFeel(); LookAndFeel defaultLaf = UIManager.getLookAndFeel();
@ -157,7 +114,7 @@ public class FTLModManager {
log.debug( "Setting system look and feel: "+ UIManager.getSystemLookAndFeelClassName() ); log.debug( "Setting system look and feel: "+ UIManager.getSystemLookAndFeelClassName() );
// SystemLaf is risky. It may throw an exception, or lead to graphical bugs. // SystemLaf is risky. It may throw an exception, or lead to graphical bugs.
// Problems are geneally caused by custom Windows themes. // Problems are generally caused by custom Windows themes.
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
} }
catch ( Exception e ) { catch ( Exception e ) {
@ -165,7 +122,6 @@ public class FTLModManager {
log.info( "Setting "+ SlipstreamConfig.USE_DEFAULT_UI +"=true in the config file to prevent this error..." ); log.info( "Setting "+ SlipstreamConfig.USE_DEFAULT_UI +"=true in the config file to prevent this error..." );
appConfig.setProperty( SlipstreamConfig.USE_DEFAULT_UI, "true" ); appConfig.setProperty( SlipstreamConfig.USE_DEFAULT_UI, "true" );
writeConfig = true;
try { try {
UIManager.setLookAndFeel( defaultLaf ); UIManager.setLookAndFeel( defaultLaf );
@ -220,7 +176,6 @@ public class FTLModManager {
if ( datsDir != null ) { if ( datsDir != null ) {
appConfig.setProperty( SlipstreamConfig.FTL_DATS_PATH, datsDir.getAbsolutePath() ); appConfig.setProperty( SlipstreamConfig.FTL_DATS_PATH, datsDir.getAbsolutePath() );
writeConfig = true;
log.info( "FTL dats located at: "+ datsDir.getAbsolutePath() ); log.info( "FTL dats located at: "+ datsDir.getAbsolutePath() );
} }
} }
@ -237,11 +192,9 @@ public class FTLModManager {
int steamBasedResponse = JOptionPane.showConfirmDialog( null, "Was FTL installed via Steam?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE ); int steamBasedResponse = JOptionPane.showConfirmDialog( null, "Was FTL installed via Steam?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE );
if ( steamBasedResponse == JOptionPane.YES_OPTION ) { if ( steamBasedResponse == JOptionPane.YES_OPTION ) {
appConfig.setProperty( SlipstreamConfig.STEAM_DISTRO, "true" ); appConfig.setProperty( SlipstreamConfig.STEAM_DISTRO, "true" );
writeConfig = true;
} }
else { else {
appConfig.setProperty( SlipstreamConfig.STEAM_DISTRO, "false" ); appConfig.setProperty( SlipstreamConfig.STEAM_DISTRO, "false" );
writeConfig = true;
} }
} }
@ -295,7 +248,6 @@ public class FTLModManager {
if ( steamExeFile != null ) { if ( steamExeFile != null ) {
appConfig.setProperty( SlipstreamConfig.STEAM_EXE_PATH, steamExeFile.getAbsolutePath() ); appConfig.setProperty( SlipstreamConfig.STEAM_EXE_PATH, steamExeFile.getAbsolutePath() );
writeConfig = true;
log.info( "Steam located at: "+ steamExeFile.getAbsolutePath() ); log.info( "Steam located at: "+ steamExeFile.getAbsolutePath() );
} }
} }
@ -308,11 +260,9 @@ public class FTLModManager {
int launchResponse = JOptionPane.showOptionDialog( null, "Would you prefer to launch FTL directly, or via Steam?", "How to Launch?", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, launchOptions, launchOptions[1] ); int launchResponse = JOptionPane.showOptionDialog( null, "Would you prefer to launch FTL directly, or via Steam?", "How to Launch?", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, launchOptions, launchOptions[1] );
if ( launchResponse == 0 ) { if ( launchResponse == 0 ) {
appConfig.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" ); appConfig.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" );
writeConfig = true;
} }
else if ( launchResponse == 1 ) { else if ( launchResponse == 1 ) {
appConfig.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "true" ); appConfig.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "true" );
writeConfig = true;
} }
} }
} }
@ -340,18 +290,6 @@ public class FTLModManager {
appConfig.setProperty( SlipstreamConfig.UPDATE_CATALOG, "0" ); appConfig.setProperty( SlipstreamConfig.UPDATE_CATALOG, "0" );
appConfig.setProperty( SlipstreamConfig.UPDATE_APP, "0" ); appConfig.setProperty( SlipstreamConfig.UPDATE_APP, "0" );
} }
writeConfig = true;
}
if ( writeConfig ) {
try {
appConfig.writeConfig();
}
catch ( IOException e ) {
String errorMsg = String.format( "Error writing config to \"%s\"", configFile.getPath() );
log.error( errorMsg, e );
showErrorDialog( errorMsg );
}
} }
ManagerFrame frame = null; ManagerFrame frame = null;
@ -388,8 +326,6 @@ public class FTLModManager {
JOptionPane.showMessageDialog( null, message, "Error", JOptionPane.ERROR_MESSAGE ); JOptionPane.showMessageDialog( null, message, "Error", JOptionPane.ERROR_MESSAGE );
} }
private static class ExitException extends RuntimeException { private static class ExitException extends RuntimeException {
public ExitException() { public ExitException() {
} }

View file

@ -88,7 +88,7 @@ public class SlipstreamCLI {
} }
File configFile = new File( "modman.cfg" ); File configFile = new File( "modman.cfg" );
SlipstreamConfig appConfig = getConfig( configFile ); SlipstreamConfig appConfig = new SlipstreamConfig( configFile );
if ( slipstreamCmd.listMods ) { if ( slipstreamCmd.listMods ) {
listMods(appConfig); listMods(appConfig);
@ -353,48 +353,6 @@ public class SlipstreamCLI {
return true; return true;
} }
/**
* Loads settings from a config file.
*
* If an error occurs, it'll be logged,
* and default settings will be returned.
*/
private static SlipstreamConfig getConfig( File configFile ) {
Properties props = new Properties();
props.setProperty( SlipstreamConfig.ALLOW_ZIP, "false" );
props.setProperty( SlipstreamConfig.FTL_DATS_PATH, "" );
props.setProperty( SlipstreamConfig.STEAM_EXE_PATH, "" );
props.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" );
props.setProperty( SlipstreamConfig.NEVER_RUN_FTL, "false" );
props.setProperty( SlipstreamConfig.USE_DEFAULT_UI, "false" );
props.setProperty( SlipstreamConfig.REMEMBER_GEOMETRY, "true" );
// "update_catalog" doesn't have a default.
// "update_app" doesn't have a default.
// "manager_geometry" doesn't have a default.
// Read the config file.
InputStream in = null;
try {
if ( configFile.exists() ) {
log.trace( "Loading properties from config file" );
in = new FileInputStream( configFile );
props.load( new InputStreamReader( in, "UTF-8" ) );
}
}
catch ( IOException e ) {
log.error( "Error loading config", e );
}
finally {
try {if ( in != null ) in.close();}
catch ( IOException e ) {}
}
SlipstreamConfig appConfig = new SlipstreamConfig( props, configFile );
return appConfig;
}
/** /**
* Checks the validity of the config's dats path and returns it. * Checks the validity of the config's dats path and returns it.
* Or exits if the path is invalid. * Or exits if the path is invalid.

View file

@ -1,18 +1,21 @@
package net.vhati.modmanager.core; package net.vhati.modmanager.core;
import java.io.File; import org.slf4j.Logger;
import java.io.FileOutputStream; import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream; import javax.swing.*;
import java.io.OutputStreamWriter; import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
public class SlipstreamConfig { public class SlipstreamConfig {
private static final Logger log = LoggerFactory.getLogger( SlipstreamConfig.class );
public static final String ALLOW_ZIP = "allow_zip"; public static final String ALLOW_ZIP = "allow_zip";
public static final String FTL_DATS_PATH = "ftl_dats_path"; public static final String FTL_DATS_PATH = "ftl_dats_path";
@ -26,12 +29,35 @@ public class SlipstreamConfig {
public static final String REMEMBER_GEOMETRY = "remember_geometry"; public static final String REMEMBER_GEOMETRY = "remember_geometry";
public static final String MANAGER_GEOMETRY = "manager_geometry"; public static final String MANAGER_GEOMETRY = "manager_geometry";
private Properties config; private final Properties config;
private File configFile; private final File configFile;
private final AtomicBoolean shutdownHookInitialized;
public SlipstreamConfig(File configFile) {
this.shutdownHookInitialized = new AtomicBoolean(false);
config = getProperties();
// Read the config file.
InputStream in = null;
try {
if ( configFile.exists() ) {
log.debug( "Loading config file" );
in = new FileInputStream( configFile );
config.load( new InputStreamReader( in, StandardCharsets.UTF_8) );
scheduleShutdown();
}
}
catch ( IOException e ) {
log.error( "Error loading config", e );
showErrorDialog( "Error loading config from "+ configFile.getPath() );
}
finally {
try {if ( in != null ) in.close();}
catch ( IOException ignored) {}
}
public SlipstreamConfig( Properties config, File configFile ) {
this.config = config;
this.configFile = configFile; this.configFile = configFile;
} }
@ -42,8 +68,23 @@ public class SlipstreamConfig {
this.configFile = srcConfig.getConfigFile(); this.configFile = srcConfig.getConfigFile();
this.config = new Properties(); this.config = new Properties();
this.config.putAll( srcConfig.getConfig() ); this.config.putAll( srcConfig.getConfig() );
this.shutdownHookInitialized = srcConfig.shutdownHookInitialized;
} }
private static Properties getProperties() {
Properties props = new Properties();
props.setProperty( SlipstreamConfig.ALLOW_ZIP, "false" );
props.setProperty( SlipstreamConfig.FTL_DATS_PATH, "" ); // Prompt.
props.setProperty( SlipstreamConfig.STEAM_DISTRO, "" ); // Prompt.
props.setProperty( SlipstreamConfig.STEAM_EXE_PATH, "" ); // Prompt.
props.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "" ); // Prompt.
props.setProperty( SlipstreamConfig.NEVER_RUN_FTL, "false" );
props.setProperty( SlipstreamConfig.UPDATE_CATALOG, "" ); // Prompt.
props.setProperty( SlipstreamConfig.UPDATE_APP, "" ); // Prompt.
props.setProperty( SlipstreamConfig.USE_DEFAULT_UI, "false" );
props.setProperty( SlipstreamConfig.REMEMBER_GEOMETRY, "true" );
return props;
}
public Properties getConfig() { return config; } public Properties getConfig() { return config; }
@ -51,9 +92,30 @@ public class SlipstreamConfig {
public Object setProperty( String key, String value ) { public Object setProperty( String key, String value ) {
scheduleShutdown();
return config.setProperty( key, value ); return config.setProperty( key, value );
} }
private void scheduleShutdown() {
if (!shutdownHookInitialized.compareAndExchange(false, true)) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
this.writeConfig();
} catch (IOException e) {
String errorMsg = String.format( "Error writing config to \"%s\"", configFile.getPath() );
log.error( errorMsg, e );
// TODO: only show this error when in gui mode
showErrorDialog( errorMsg );
}
}));
}
}
private static void showErrorDialog( String message ) {
JOptionPane.showMessageDialog( null, message, "Error", JOptionPane.ERROR_MESSAGE );
}
public int getPropertyAsInt( String key, int defaultValue ) { public int getPropertyAsInt( String key, int defaultValue ) {
String s = config.getProperty( key ); String s = config.getProperty( key );
if ( s != null && s.matches("^\\d+$") ) if ( s != null && s.matches("^\\d+$") )