diff --git a/src/main/java/net/vhati/modmanager/FTLModManager.java b/src/main/java/net/vhati/modmanager/FTLModManager.java index a89e8dd..4958a34 100644 --- a/src/main/java/net/vhati/modmanager/FTLModManager.java +++ b/src/main/java/net/vhati/modmanager/FTLModManager.java @@ -73,6 +73,13 @@ public class FTLModManager { log.debug( "OS: {} {}", System.getProperty( "os.name" ), System.getProperty( "os.version" ) ); log.debug( "VM: {}, {}, {}", System.getProperty( "java.vm.name" ), System.getProperty( "java.version" ), System.getProperty( "os.arch" ) ); + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException( Thread t, Throwable e ) { + log.error( "Uncaught exception in thread: "+ t.toString(), e ); + } + }); + if ( args.length > 0 ) { SlipstreamCLI.main( args ); return; @@ -348,14 +355,26 @@ public class FTLModManager { } // Create the main window. + ManagerFrame frame = null; try { - ManagerFrame frame = new ManagerFrame( appConfig, APP_NAME, APP_VERSION, APP_URL, APP_AUTHOR ); + frame = new ManagerFrame( appConfig, APP_NAME, APP_VERSION, APP_URL, APP_AUTHOR ); frame.init(); frame.setVisible( true ); } catch ( Exception e ) { log.error( "Exception while creating ManagerFrame", e ); + // If the frame is constructed, but an exception prevents it + // becoming visible, that *must* be caught. The frame registers + // itself as a global uncaught exception handler. It doesn't + // dispose() itself in the handler, so EDT will wait forever + // for an invisible window to close. + + if ( frame != null && frame.isDisplayable() ) { + frame.setDisposeNormally( false ); + frame.dispose(); + } + throw new ExitException(); } } diff --git a/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java b/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java index 38a1c57..13a6b0c 100644 --- a/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java +++ b/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java @@ -103,6 +103,10 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse private File appUpdateFile = new File( backupDir, "auto_update.json" ); private File appUpdateETagFile = new File( backupDir, "auto_update_etag.txt" ); + private boolean disposeNormally = true; + private boolean ranInit = false; + private Thread.UncaughtExceptionHandler previousUncaughtExceptionHandler = null; + private final Lock managerLock = new ReentrantLock(); private final Condition scanEndedCond = managerLock.newCondition(); private boolean scanning = false; @@ -113,8 +117,8 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse private String appURL; private String appAuthor; - private HashMap modFileHashes = new HashMap(); - private HashMap modFileDates = new HashMap(); + private Map modFileHashes = new HashMap(); + private Map modFileDates = new HashMap(); private ModDB catalogModDB = new ModDB(); private ModDB localModDB = new ModDB(); @@ -261,6 +265,12 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse @Override public void windowClosed( WindowEvent e ) { // dispose() was called. + + // Restore the previous exception handler. + if ( ranInit ) Thread.setDefaultUncaughtExceptionHandler( previousUncaughtExceptionHandler ); + + if ( !disposeNormally ) return; // Something bad happened. Exit quickly. + ListState tableState = getCurrentModsTableState(); saveModsTableState( tableState ); @@ -404,9 +414,14 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse } /** - * Extra initialization that must be called after the constructor. + * Extra one-time initialization that must be called after the constructor. */ public void init() { + if ( ranInit ) return; + ranInit = true; + + previousUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler( this ); ManagerInitThread initThread = new ManagerInitThread( this, @@ -421,7 +436,6 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse ); initThread.setDaemon( true ); initThread.setPriority( Thread.MIN_PRIORITY ); - initThread.setDefaultUncaughtExceptionHandler( this ); initThread.start(); } @@ -524,7 +538,6 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse ModsScanThread scanThread = new ModsScanThread( modFiles, localModDB, this ); scanThread.setDaemon( true ); scanThread.setPriority( Thread.MIN_PRIORITY ); - scanThread.setDefaultUncaughtExceptionHandler( this ); scanThread.start(); } @@ -726,7 +739,6 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse log.info( "Patching..." ); log.info( "" ); ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, false, patchDlg ); - patchThread.setDefaultUncaughtExceptionHandler( this ); patchThread.start(); patchDlg.setVisible( true ); @@ -801,7 +813,6 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse File datsDir = new File( appConfig.getProperty( SlipstreamConfig.FTL_DATS_PATH ) ); DatExtractDialog extractDlg = new DatExtractDialog( this, extractDir, datsDir ); - extractDlg.getWorkerThread().setDefaultUncaughtExceptionHandler( this ); extractDlg.extract(); extractDlg.setVisible( true ); } @@ -1048,6 +1059,14 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse updateBtn.setEnabled( isUpdateAvailable ); } + /** + * Toggles whether to perform the usual actions after disposal. + * + * Set this to false before an abnormal exit. + */ + public void setDisposeNormally( boolean b ) { + disposeNormally = false; + } @Override public void uncaughtException( Thread t, Throwable e ) {