diff --git a/src/main/java/net/vhati/modmanager/FTLModManager.java b/src/main/java/net/vhati/modmanager/FTLModManager.java index b4fa76b..c431262 100644 --- a/src/main/java/net/vhati/modmanager/FTLModManager.java +++ b/src/main/java/net/vhati/modmanager/FTLModManager.java @@ -1,19 +1,14 @@ package net.vhati.modmanager; import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.IOException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Date; -import java.util.Properties; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; import javax.swing.UIManager; -import javax.swing.UIManager.LookAndFeelInfo; import ch.qos.logback.classic.LoggerContext; 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 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_AUTHOR = "Vhati"; + public static final String APP_URL = "TODO"; + public static final String APP_AUTHOR = "jan-leila"; public static void main( String[] args ) { @@ -53,7 +48,7 @@ public class FTLModManager { PatternLayoutEncoder encoder = new PatternLayoutEncoder(); 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.start(); @@ -76,7 +71,7 @@ public class FTLModManager { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override 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. - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - guiInit(); - } - }); + SwingUtilities.invokeLater(FTLModManager::guiInit); } private static void guiInit() { try { + // TODO: get mods file from env var // 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(); 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(); } + // TODO: get config file from env var File configFile = new File( "modman.cfg" ); - boolean writeConfig = false; - 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 ); + SlipstreamConfig appConfig = new SlipstreamConfig(configFile); // 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 ) { LookAndFeel defaultLaf = UIManager.getLookAndFeel(); @@ -157,7 +114,7 @@ public class FTLModManager { log.debug( "Setting system look and feel: "+ UIManager.getSystemLookAndFeelClassName() ); // 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() ); } 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..." ); appConfig.setProperty( SlipstreamConfig.USE_DEFAULT_UI, "true" ); - writeConfig = true; try { UIManager.setLookAndFeel( defaultLaf ); @@ -220,7 +176,6 @@ public class FTLModManager { if ( datsDir != null ) { appConfig.setProperty( SlipstreamConfig.FTL_DATS_PATH, datsDir.getAbsolutePath() ); - writeConfig = true; 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 ); if ( steamBasedResponse == JOptionPane.YES_OPTION ) { appConfig.setProperty( SlipstreamConfig.STEAM_DISTRO, "true" ); - writeConfig = true; } else { appConfig.setProperty( SlipstreamConfig.STEAM_DISTRO, "false" ); - writeConfig = true; } } @@ -295,7 +248,6 @@ public class FTLModManager { if ( steamExeFile != null ) { appConfig.setProperty( SlipstreamConfig.STEAM_EXE_PATH, steamExeFile.getAbsolutePath() ); - writeConfig = true; 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] ); if ( launchResponse == 0 ) { appConfig.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" ); - writeConfig = true; } else if ( launchResponse == 1 ) { 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_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; @@ -388,8 +326,6 @@ public class FTLModManager { JOptionPane.showMessageDialog( null, message, "Error", JOptionPane.ERROR_MESSAGE ); } - - private static class ExitException extends RuntimeException { public ExitException() { } diff --git a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java index a3776c2..a10f407 100644 --- a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java +++ b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java @@ -88,7 +88,7 @@ public class SlipstreamCLI { } File configFile = new File( "modman.cfg" ); - SlipstreamConfig appConfig = getConfig( configFile ); + SlipstreamConfig appConfig = new SlipstreamConfig( configFile ); if ( slipstreamCmd.listMods ) { listMods(appConfig); @@ -353,48 +353,6 @@ public class SlipstreamCLI { 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. * Or exits if the path is invalid. diff --git a/src/main/java/net/vhati/modmanager/core/SlipstreamConfig.java b/src/main/java/net/vhati/modmanager/core/SlipstreamConfig.java index fe66234..c6fa448 100644 --- a/src/main/java/net/vhati/modmanager/core/SlipstreamConfig.java +++ b/src/main/java/net/vhati/modmanager/core/SlipstreamConfig.java @@ -1,18 +1,21 @@ package net.vhati.modmanager.core; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; public class SlipstreamConfig { + private static final Logger log = LoggerFactory.getLogger( SlipstreamConfig.class ); public static final String ALLOW_ZIP = "allow_zip"; 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 MANAGER_GEOMETRY = "manager_geometry"; - private Properties config; - private File configFile; + private final Properties config; + 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; } @@ -42,8 +68,23 @@ public class SlipstreamConfig { this.configFile = srcConfig.getConfigFile(); this.config = new Properties(); 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; } @@ -51,9 +92,30 @@ public class SlipstreamConfig { public Object setProperty( String key, String value ) { + scheduleShutdown(); 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 ) { String s = config.getProperty( key ); if ( s != null && s.matches("^\\d+$") )