فهرست منبع

App lifecycle fixes

Sebastian Stenzel 5 سال پیش
والد
کامیت
e1f44fb48a

+ 0 - 2
main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java

@@ -5,7 +5,6 @@
  *******************************************************************************/
 package org.cryptomator.launcher;
 
-import javafx.application.Platform;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.logging.DebugMode;
 import org.cryptomator.logging.LoggerConfiguration;
@@ -91,7 +90,6 @@ public class Cryptomator {
 		try {
 			uiLauncher.launch();
 			shutdownLatch.await();
-			Platform.exit();
 			LOG.info("UI shut down");
 			return 0;
 		} catch (InterruptedException e) {

+ 9 - 5
main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java

@@ -21,8 +21,10 @@ import java.nio.file.FileSystems;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Objects;
 import java.util.concurrent.BlockingQueue;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 @Singleton
@@ -40,7 +42,7 @@ class FileOpenRequestHandler {
 	}
 
 	private void openFiles(OpenFilesEvent evt) {
-		Stream<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath);
+		Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).collect(Collectors.toList());
 		AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
 		tryToEnqueueFileOpenRequest(launchEvent);
 	}
@@ -51,16 +53,18 @@ class FileOpenRequestHandler {
 
 	// visible for testing
 	void handleLaunchArgs(FileSystem fs, String[] args) {
-		Stream<Path> pathsToOpen = Arrays.stream(args).map(str -> {
+		Collection<Path> pathsToOpen = Arrays.stream(args).map(str -> {
 			try {
 				return fs.getPath(str);
 			} catch (InvalidPathException e) {
 				LOG.trace("Argument not a valid path: {}", str);
 				return null;
 			}
-		}).filter(Objects::nonNull);
-		AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
-		tryToEnqueueFileOpenRequest(launchEvent);
+		}).filter(Objects::nonNull).collect(Collectors.toList());
+		if (!pathsToOpen.isEmpty()) {
+			AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
+			tryToEnqueueFileOpenRequest(launchEvent);
+		}
 	}
 
 

+ 2 - 1
main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocolImpl.java

@@ -8,6 +8,7 @@ import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.concurrent.BlockingQueue;
 import java.util.stream.Stream;
 
@@ -27,7 +28,7 @@ class IpcProtocolImpl implements IpcProtocol {
 
 	@Override
 	public void revealRunningApp() {
-		launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Stream.empty()));
+		launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Collections.emptyList()));
 	}
 
 	@Override

+ 8 - 12
main/launcher/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java

@@ -15,16 +15,14 @@ import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
-import java.io.IOException;
 import java.nio.file.FileSystem;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.List;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 public class FileOpenRequestHandlerTest {
 
@@ -39,32 +37,30 @@ public class FileOpenRequestHandlerTest {
 
 	@Test
 	@DisplayName("./cryptomator.exe foo bar")
-	public void testOpenArgsWithCorrectPaths() throws IOException {
+	public void testOpenArgsWithCorrectPaths() {
 		inTest.handleLaunchArgs(new String[]{"foo", "bar"});
 
 		AppLaunchEvent evt = queue.poll();
 		Assertions.assertNotNull(evt);
-		List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
+		Collection<Path> paths = evt.getPathsToOpen();
 		MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar")));
 	}
 
 	@Test
 	@DisplayName("./cryptomator.exe foo (with 'foo' being an invalid path)")
-	public void testOpenArgsWithIncorrectPaths() throws IOException {
+	public void testOpenArgsWithIncorrectPaths() {
 		FileSystem fs = Mockito.mock(FileSystem.class);
 		Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
 		inTest.handleLaunchArgs(fs, new String[]{"foo"});
 
 		AppLaunchEvent evt = queue.poll();
-		Assertions.assertNotNull(evt);
-		List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
-		Assertions.assertTrue(paths.isEmpty());
+		Assertions.assertNull(evt);
 	}
 
 	@Test
 	@DisplayName("./cryptomator.exe foo (with full event queue)")
-	public void testOpenArgsWithFullQueue() throws IOException {
-		queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Stream.empty()));
+	public void testOpenArgsWithFullQueue() {
+		queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Collections.emptyList()));
 		Assumptions.assumeTrue(queue.remainingCapacity() == 0);
 
 		inTest.handleLaunchArgs(new String[]{"foo"});

+ 4 - 3
main/ui/src/main/java/org/cryptomator/ui/launcher/AppLaunchEvent.java

@@ -1,16 +1,17 @@
 package org.cryptomator.ui.launcher;
 
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.stream.Stream;
 
 public class AppLaunchEvent {
 
-	private final Stream<Path> pathsToOpen;
 	private final EventType type;
+	private final Collection<Path> pathsToOpen;
 
 	public enum EventType {REVEAL_APP, OPEN_FILE}
 
-	public AppLaunchEvent(EventType type, Stream<Path> pathsToOpen) {
+	public AppLaunchEvent(EventType type, Collection<Path> pathsToOpen) {
 		this.type = type;
 		this.pathsToOpen = pathsToOpen;
 	}
@@ -19,7 +20,7 @@ public class AppLaunchEvent {
 		return type;
 	}
 
-	public Stream<Path> getPathsToOpen() {
+	public Collection<Path> getPathsToOpen() {
 		return pathsToOpen;
 	}
 }

+ 32 - 25
main/ui/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java

@@ -15,6 +15,7 @@ import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 import java.awt.Desktop;
+import java.awt.EventQueue;
 import java.awt.desktop.QuitResponse;
 import java.util.EnumSet;
 import java.util.EventObject;
@@ -31,14 +32,14 @@ public class AppLifecycleListener {
 	private final FxApplicationStarter fxApplicationStarter;
 	private final CountDownLatch shutdownLatch;
 	private final ObservableList<Vault> vaults;
-	private final AtomicBoolean allowSuddenTermination;
+	private final AtomicBoolean allowQuitWithoutPrompt;
 
 	@Inject
 	AppLifecycleListener(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
 		this.fxApplicationStarter = fxApplicationStarter;
 		this.shutdownLatch = shutdownLatch;
 		this.vaults = vaults;
-		this.allowSuddenTermination = new AtomicBoolean(true);
+		this.allowQuitWithoutPrompt = new AtomicBoolean(true);
 		vaults.addListener(this::vaultListChanged);
 
 		// register preferences shortcut
@@ -51,11 +52,6 @@ public class AppLifecycleListener {
 			Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
 		}
 
-		// allow sudden termination
-		if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
-			Desktop.getDesktop().enableSuddenTermination();
-		}
-
 		shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
 	}
 
@@ -66,7 +62,7 @@ public class AppLifecycleListener {
 		handleQuitRequest(null, new QuitResponse() {
 			@Override
 			public void performQuit() {
-				shutdownLatch.countDown();
+				System.exit(0);
 			}
 
 			@Override
@@ -76,18 +72,37 @@ public class AppLifecycleListener {
 		});
 	}
 
+	private void handleQuitRequest(@SuppressWarnings("unused") EventObject e, QuitResponse response) {
+		QuitResponse decoratedQuitResponse = decorateQuitResponse(response);
+		if (allowQuitWithoutPrompt.get()) {
+			decoratedQuitResponse.performQuit();
+		} else {
+			fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
+		}
+	}
+
+	private QuitResponse decorateQuitResponse(QuitResponse originalQuitResponse) {
+		return new QuitResponse() {
+			@Override
+			public void performQuit() {
+				Platform.exit(); // will be no-op, if JavaFX never started.
+				shutdownLatch.countDown(); // main thread is waiting for this latch
+				EventQueue.invokeLater(originalQuitResponse::performQuit); // this will eventually call System.exit(0)
+			}
+
+			@Override
+			public void cancelQuit() {
+				originalQuitResponse.cancelQuit();
+			}
+		};
+	}
+
 	private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
 		assert Platform.isFxApplicationThread();
 		boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
-		boolean suddenTerminationChanged = allowSuddenTermination.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination);
-		if (suddenTerminationChanged && Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
-			if (allVaultsAllowTermination) {
-				Desktop.getDesktop().enableSuddenTermination();
-				LOG.debug("sudden termination enabled");
-			} else {
-				Desktop.getDesktop().disableSuddenTermination();
-				LOG.debug("sudden termination disabled");
-			}
+		boolean suddenTerminationChanged = allowQuitWithoutPrompt.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination);
+		if (suddenTerminationChanged) {
+			LOG.debug("Allow quitting without prompt: {}", allVaultsAllowTermination);
 		}
 	}
 
@@ -95,14 +110,6 @@ public class AppLifecycleListener {
 		fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
 	}
 
-	private void handleQuitRequest(@SuppressWarnings("unused") EventObject e, QuitResponse response) {
-		if (allowSuddenTermination.get()) {
-			response.performQuit(); // really?
-		} else {
-			fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(response));
-		}
-	}
-
 	private void forceUnmountRemainingVaults() {
 		for (Vault vault : vaults) {
 			if (vault.isUnlocked()) {