changeset 2561:879eb9424133

Add splash screen functionality to thermostat local and gui commands A splash screen can now be displayed during startup of the thermostat local and gui commands by supplying the "--show-splash" option Reviewed-by: aazores, neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-November/021550.html
author Alex Macdonald <almacdon@redhat.com>
date Wed, 18 Jan 2017 11:19:51 -0500
parents e7fd8adb5f9c
children 7746392aec3c
files client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java config/src/main/java/com/redhat/thermostat/shared/config/CommonPaths.java config/src/main/java/com/redhat/thermostat/shared/config/internal/CommonPathsImpl.java config/src/test/java/com/redhat/thermostat/shared/config/DirectoryStructureCreatorTest.java distribution/config/commands/gui.properties distribution/packaging/shared/desktop/thermostat.desktop distribution/packaging/shared/icons/splash-image.png distribution/scripts/thermostat local/command/src/main/java/com/redhat/thermostat/local/command/internal/LocalCommand.java local/command/src/main/resources/com/redhat/thermostat/config/locale/strings.properties local/command/src/test/java/com/redhat/thermostat/local/command/internal/LocalCommandTest.java local/distribution/thermostat-plugin.xml
diffstat 12 files changed, 168 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Tue Jan 17 10:49:00 2017 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Wed Jan 18 11:19:51 2017 -0500
@@ -52,10 +52,14 @@
 import java.awt.event.WindowEvent;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
+import java.io.IOException;
 import java.io.PrintStream;
 import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.swing.BorderFactory;
@@ -468,9 +472,18 @@
         setTitle(title);
     }
 
+    private void deleteSplashScreenStamp() {
+        try {
+            Files.deleteIfExists(commonPaths.getUserSplashScreenStampFile().toPath());
+        } catch (IOException | SecurityException | NullPointerException e) {
+            logger.log(Level.WARNING, "Unable to delete splashscreen.stamp", e);
+        }
+    }
+
     @Override
     public void showMainWindow() {
         try {
+            deleteSplashScreenStamp();
             new EdtHelper().callAndWait(new Runnable() {
 
                 @Override
--- a/config/src/main/java/com/redhat/thermostat/shared/config/CommonPaths.java	Tue Jan 17 10:49:00 2017 -0500
+++ b/config/src/main/java/com/redhat/thermostat/shared/config/CommonPaths.java	Wed Jan 18 11:19:51 2017 -0500
@@ -84,6 +84,9 @@
     /** A file indicating that Thermostat commands are ready to be run **/
     public File getUserSetupCompleteStampFile() throws InvalidConfigurationException;
 
+    /** A file indicating the current use of a splash screen **/
+    public File getUserSplashScreenStampFile() throws InvalidConfigurationException;
+
     /** A location that contains data that is persisted */
     public File getUserPersistentDataDirectory() throws InvalidConfigurationException;
 
--- a/config/src/main/java/com/redhat/thermostat/shared/config/internal/CommonPathsImpl.java	Tue Jan 17 10:49:00 2017 -0500
+++ b/config/src/main/java/com/redhat/thermostat/shared/config/internal/CommonPathsImpl.java	Wed Jan 18 11:19:51 2017 -0500
@@ -315,6 +315,11 @@
         return setupCompleteStamp;
     }
 
+    @Override
+    public File getUserSplashScreenStampFile() throws InvalidConfigurationException {
+        return new File(getUserPersistentDataDirectory(), "splashscreen.stamp");
+    }
+
     // TODO add logging files here (see LoggingUtils)
     // TODO add ssl.properties file here (see SSLConfiguration)
 
--- a/config/src/test/java/com/redhat/thermostat/shared/config/DirectoryStructureCreatorTest.java	Tue Jan 17 10:49:00 2017 -0500
+++ b/config/src/test/java/com/redhat/thermostat/shared/config/DirectoryStructureCreatorTest.java	Wed Jan 18 11:19:51 2017 -0500
@@ -250,6 +250,11 @@
                 throws InvalidConfigurationException {
             return null; // Only directories need to be created
         }
+
+        @Override
+        public File getUserSplashScreenStampFile() throws InvalidConfigurationException{
+            return null; // Only directories need to be created
+        }
         
         @Override
         public File getUserIPCConfigurationFile() throws InvalidConfigurationException {
--- a/distribution/config/commands/gui.properties	Tue Jan 17 10:49:00 2017 -0500
+++ b/distribution/config/commands/gui.properties	Wed Jan 18 11:19:51 2017 -0500
@@ -14,8 +14,13 @@
 
 description = Launch the GUI client. The GUI client provides a graphical interface to all the features.
 
-usage = gui [-l <level>]
+usage = gui [-l <level>] [--show-splash]
+
+options = AUTO_LOG_OPTION, splashscreen
 
-options = AUTO_LOG_OPTION
+splashscreen.long = show-splash
+splashscreen.required = false
+splashscreen.hasarg = false
+splashscreen.description = display a splash screen while launching Thermostat gui
 
 environments = cli
--- a/distribution/packaging/shared/desktop/thermostat.desktop	Tue Jan 17 10:49:00 2017 -0500
+++ b/distribution/packaging/shared/desktop/thermostat.desktop	Wed Jan 18 11:19:51 2017 -0500
@@ -3,5 +3,5 @@
 Type=Application
 Name=${thermostat.desktop.app.name}
 Comment=A monitoring and serviceability tool for OpenJDK
-Exec=${thermostat.home}/bin/thermostat local
+Exec=${thermostat.home}/bin/thermostat local --show-splash
 Icon=${pkg_name}
Binary file distribution/packaging/shared/icons/splash-image.png has changed
--- a/distribution/scripts/thermostat	Tue Jan 17 10:49:00 2017 -0500
+++ b/distribution/scripts/thermostat	Wed Jan 18 11:19:51 2017 -0500
@@ -71,6 +71,7 @@
 RUN_IN_BG=0
 PID_FILE=""
 
+PATH_TO_SPLASHIMAGE="$THERMOSTAT_HOME/../../packaging/shared/icons/splash-image.png"
 i=0
 j=0
 
@@ -90,6 +91,13 @@
         ;;
     *)
         ARGS[$j]="$1"
+        if [ $1 = "--show-splash" ] && [ $j > 0 ]; then
+            if [ ${ARGS[$j-1]} = "local" ]; then
+                JAVA_ARGS+=(-splash:$PATH_TO_SPLASHIMAGE)
+            elif [ ${ARGS[$j-1]} = "gui" ] && [ ! -f $USER_THERMOSTAT_HOME/data/splashscreen.stamp ]; then
+                JAVA_ARGS+=(-splash:$PATH_TO_SPLASHIMAGE)
+            fi
+        fi
         j=$((j+1))
         shift
         ;;
--- a/local/command/src/main/java/com/redhat/thermostat/local/command/internal/LocalCommand.java	Tue Jan 17 10:49:00 2017 -0500
+++ b/local/command/src/main/java/com/redhat/thermostat/local/command/internal/LocalCommand.java	Wed Jan 18 11:19:51 2017 -0500
@@ -40,19 +40,36 @@
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.cli.DependencyServices;
+import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.launcher.Launcher;
 
+import java.awt.SplashScreen;
+import java.io.File;
 import java.lang.ProcessBuilder.Redirect;
 import java.io.IOException;
 
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+
 public class LocalCommand extends AbstractCommand {
 
+    private static final Logger logger = LoggingUtils.getLogger(LocalCommand.class);
     private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+    private static String SHOW_SPLASH = "--show-splash";
     private final DependencyServices dependentServices = new DependencyServices();
     private Launcher launcher;
     private CommonPaths paths;
+    private boolean splashScreenEnabled;
 
     public void run(CommandContext ctx) throws CommandException {
         this.paths = dependentServices.getRequiredService(CommonPaths.class);
@@ -60,6 +77,16 @@
 
         ServiceLauncher serviceLauncher = createServiceLauncher();
         serviceLauncher.start();
+        if (ctx.getArguments().hasArgument(SHOW_SPLASH)) {
+            splashScreenEnabled = true;
+            File stamp = paths.getUserSplashScreenStampFile();
+            try {
+                stamp.createNewFile();
+            } catch (IOException | SecurityException e) {
+                logger.log(Level.WARNING, "Unable to create file splashscreen.stamp", e);
+            }
+        }
+        
         try {
             // this blocks
             runGui();
@@ -83,6 +110,10 @@
             throw new CommandException(t.localize(LocaleResources.ERROR_STARTING_GUI));
         }
 
+        if (isSplashScreenEnabled()) {
+            closeSplashScreen();
+        }
+
         int exitStatus;
         try {
             exitStatus = gui.waitFor();
@@ -103,6 +134,34 @@
         return pb.start();
     }
 
+    // package private for testing
+    boolean isSplashScreenEnabled() {
+        return splashScreenEnabled;
+    }
+
+    private void closeSplashScreen() {
+        try {
+            WatchService watcher = FileSystems.getDefault().newWatchService();
+            Path splashStampPath = paths.getUserSplashScreenStampFile().toPath().getParent();
+            splashStampPath.register(watcher, ENTRY_DELETE);
+            while(paths.getUserSplashScreenStampFile().exists()) {
+                WatchKey key = watcher.poll(5L, TimeUnit.MINUTES);
+                for (WatchEvent<?> event : key.pollEvents()) {
+                    if (paths.getUserSplashScreenStampFile().getName().equals(event.context().toString())) {
+                        try {
+                            SplashScreen.getSplashScreen().close();
+                        } catch (NullPointerException e) {
+                            logger.log(Level.WARNING, "Unable to close SplashScreen!", e);
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            logger.log(Level.WARNING, "Failed to complete WatcherService.", e);
+            SplashScreen.getSplashScreen().close();
+        }
+    }
+
     public void setPaths(CommonPaths paths) {
         dependentServices.addService(CommonPaths.class, paths);
     }
--- a/local/command/src/main/resources/com/redhat/thermostat/config/locale/strings.properties	Tue Jan 17 10:49:00 2017 -0500
+++ b/local/command/src/main/resources/com/redhat/thermostat/config/locale/strings.properties	Wed Jan 18 11:19:51 2017 -0500
@@ -3,4 +3,4 @@
 SERVICE_WAIT_INTERRUPTED=Waiting for {0} interrupted
 ERROR_STARTING_GUI="Error starting thermostat GUI"
 ERROR_RUNNING_GUI="Error running thermostat GUI"
-RUNNING_GUI_INTERRUPTED="Running thermostat GUI interrupted"
\ No newline at end of file
+RUNNING_GUI_INTERRUPTED="Running thermostat GUI interrupted"
--- a/local/command/src/test/java/com/redhat/thermostat/local/command/internal/LocalCommandTest.java	Tue Jan 17 10:49:00 2017 -0500
+++ b/local/command/src/test/java/com/redhat/thermostat/local/command/internal/LocalCommandTest.java	Wed Jan 18 11:19:51 2017 -0500
@@ -36,16 +36,20 @@
 
 package com.redhat.thermostat.local.command.internal;
 
+import com.redhat.thermostat.common.cli.Arguments;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.launcher.Launcher;
 import com.redhat.thermostat.shared.config.CommonPaths;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -56,6 +60,7 @@
 import java.io.IOException;
 
 public class LocalCommandTest {
+    private static final String SHOW_SPLASH = "--show-splash";
     private LocalCommand cmd;
     private CommandContext ctxt;
     private CommonPaths paths;
@@ -70,6 +75,59 @@
         ctxt = mock(CommandContext.class);
     }
 
+    @Rule
+    public TemporaryFolder tempFolder = new TemporaryFolder();
+
+    @Test(timeout=1000)
+    public void testSplashScreenFunctionalityAndClosing() throws CommandException, InterruptedException, IOException {
+        cmd = createLocalCommandForSplashTests();
+        final File stamp = tempFolder.newFile("splashscreen.stamp");
+        assertTrue(stamp.exists());
+        when(ctxt.getArguments().hasArgument(SHOW_SPLASH)).thenReturn(true);
+        when(paths.getUserPersistentDataDirectory()).thenReturn(tempFolder.getRoot());
+        when(paths.getUserSplashScreenStampFile()).thenReturn(stamp);
+        Runnable deleteStampFile = new Runnable() {
+            public void run() {
+                try {
+                    // sleep so the stamp file is deleted while closeSplashScreen() is watching
+                    Thread.sleep(250L);
+                    stamp.delete();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        };
+        new Thread(deleteStampFile).start();
+        cmd.run(ctxt);
+        assertTrue(cmd.isSplashScreenEnabled());
+        assertFalse(stamp.exists());
+    }
+
+    @Test
+    public void testNoShowSplashOption() throws CommandException, InterruptedException {
+        cmd = createLocalCommandForSplashTests();
+        cmd.run(ctxt);
+        assertFalse(cmd.isSplashScreenEnabled());
+    }
+
+    private LocalCommand createLocalCommandForSplashTests() throws CommandException, InterruptedException {
+        cmd = new LocalCommand() {
+            @Override
+            ServiceLauncher createServiceLauncher() {
+                return mock(ServiceLauncher.class);
+            }
+
+            @Override
+            Process execProcess(String... command) throws IOException {
+                return mock(Process.class);
+            }
+        };
+        when(ctxt.getArguments()).thenReturn(mock(Arguments.class));
+        cmd.setPaths(paths);
+        cmd.setLauncher(launcher);
+        return cmd;
+    }
+
     @Test
     public void testPathsNotSetFailure() throws CommandException {
         cmd = createLocalCommand();
@@ -113,6 +171,7 @@
 
         final Process mockProcess = mock(Process.class);
         when(mockProcess.waitFor()).thenReturn(0);
+        when(ctxt.getArguments()).thenReturn(mock(Arguments.class));
 
         cmd = new LocalCommand() {
             @Override
--- a/local/distribution/thermostat-plugin.xml	Tue Jan 17 10:49:00 2017 -0500
+++ b/local/distribution/thermostat-plugin.xml	Wed Jan 18 11:19:51 2017 -0500
@@ -50,6 +50,13 @@
         is installed and then start the GUI client as a separate process. On exit,
         shut them all down.
       </description>
+      <options>
+        <option>
+          <long>show-splash</long>
+          <required>false</required>
+          <description>display a splash screen while launching Thermostat local</description>
+        </option>
+      </options>
       <environments>
         <environment>cli</environment>
       </environments>