diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartStrategy.java
index 10a431e1f..99b21b4cd 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartStrategy.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartStrategy.java
@@ -21,11 +21,4 @@ public interface AutoStartStrategy {
}
}
- class TogglingAutoStartWithPowershellFailedException extends TogglingAutoStartFailedException {
-
- public TogglingAutoStartWithPowershellFailedException(String message, Throwable cause) {
- super(message, cause);
- }
-
- }
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartWinStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartWinStrategy.java
index 02e4faa26..0d2f20536 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartWinStrategy.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/AutoStartWinStrategy.java
@@ -1,87 +1,185 @@
package org.cryptomator.ui.preferences;
-import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
+/**
+ * OS specific class to check, en- and disable the auto start on Windows.
+ *
+ * Two strategies are implemented for this feature, the first uses the registry and the second one the autostart folder.
+ *
+ * To check if it is enabled at all, both locations are checked.
+ * To enable it, first the registry is tried and only on failure the autostart folder is used.
+ * To disable it, first it is determined, which strategies must be used and in the second step those are executed.
+ *
+ * @apiNote This class is not thread safe, hence it should be avoided to be used simultaniously by the same threads.
+ */
class AutoStartWinStrategy implements AutoStartStrategy {
private static final Logger LOG = LoggerFactory.getLogger(AutoStartWinStrategy.class);
private static final String HKCU_AUTOSTART_KEY = "\"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\"";
private static final String AUTOSTART_VALUE = "Cryptomator";
+ private static final String WINDOWS_START_MENU_ENTRY = "\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Cryptomator.lnk";
+
private final String exePath;
- private static final String WINDOWS_START_MENU_FOLDER = "\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs";
+
+ private boolean activatedOverFolder;
+ private boolean activatedOverRegistry;
public AutoStartWinStrategy(String exePath) {
this.exePath = exePath;
+ this.activatedOverFolder = false;
+ this.activatedOverRegistry = false;
}
@Override
public CompletionStage isAutoStartEnabled() {
+ return isAutoStartEnabledOverRegistry().thenCombine(isAutoStartEnabledOverFolder(), (bReg, bFolder) -> bReg || bFolder);
+ }
+
+ private CompletableFuture isAutoStartEnabledOverFolder() {
+ Path autoStartEntry = Path.of(System.getProperty("user.home") + WINDOWS_START_MENU_ENTRY);
+ this.activatedOverFolder = Files.exists(autoStartEntry);
+ return CompletableFuture.completedFuture(activatedOverFolder);
+ }
+
+ private CompletableFuture isAutoStartEnabledOverRegistry() {
ProcessBuilder regQuery = new ProcessBuilder("reg", "query", HKCU_AUTOSTART_KEY, //
"/v", AUTOSTART_VALUE);
try {
Process proc = regQuery.start();
- return proc.onExit().thenApply(p -> p.exitValue() == 0);
+ return proc.onExit().thenApply(p -> {
+ this.activatedOverRegistry = p.exitValue() == 0;
+ return activatedOverRegistry;
+ });
} catch (IOException e) {
- LOG.warn("Failed to query {} from registry key {}", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
+ LOG.debug("Failed to query {} from registry key {}", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
return CompletableFuture.completedFuture(false);
}
}
@Override
public void enableAutoStart() throws TogglingAutoStartFailedException {
+ try {
+ enableAutoStartOverRegistry().thenAccept((Void v) -> this.activatedOverRegistry = true).exceptionallyCompose(e -> {
+ LOG.debug("Falling back to using autostart folder.");
+ return this.enableAutoStartOverFolder();
+ }).thenAccept((Void v) -> this.activatedOverFolder = true).get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new TogglingAutoStartFailedException("Execution of enabling auto start setting was interrupted.");
+ } catch (ExecutionException e) {
+ throw new TogglingAutoStartFailedException("Enabling auto start failed both over registry and auto start folder.");
+ }
+ }
+
+ private CompletableFuture enableAutoStartOverRegistry() {
ProcessBuilder regAdd = new ProcessBuilder("reg", "add", HKCU_AUTOSTART_KEY, //
"/v", AUTOSTART_VALUE, //
"/t", "REG_SZ", //
"/d", "\"" + exePath + "\"", //
"/f");
- String command = regAdd.command().stream().collect(Collectors.joining(" "));
try {
Process proc = regAdd.start();
- boolean finishedInTime = waitForProcess(proc, 5, TimeUnit.SECONDS);
+ boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
if (finishedInTime && proc.exitValue() == 0) {
LOG.debug("Added {} to registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
+ return CompletableFuture.completedFuture(null);
} else {
- LOG.debug("Registry could not be edited. Error code was {}.", proc.exitValue());
- addShortcutOfAppToAutostartFolder();
- throw new TogglingAutoStartFailedException("Adding registry value failed.");
+ throw new IOException("Process existed with error code " + proc.exitValue());
}
} catch (IOException e) {
- addShortcutOfAppToAutostartFolder();
- throw new TogglingAutoStartFailedException("Adding registry value failed. " + command, e);
+ LOG.debug("Registry could not be edited to set auto start.", e);
+ return CompletableFuture.failedFuture(new SystemCommandException("Adding registry value failed."));
}
}
+ private CompletableFuture enableAutoStartOverFolder() {
+ String autoStartFolderEntry = System.getProperty("user.home") + WINDOWS_START_MENU_ENTRY;
+ String createShortcutCommand = "$s=(New-Object -COM WScript.Shell).CreateShortcut('" + autoStartFolderEntry + "');$s.TargetPath='" + exePath + "';$s.Save();";
+ ProcessBuilder shortcutAdd = new ProcessBuilder("cmd", "/c", "Start powershell " + createShortcutCommand);
+ try {
+ Process proc = shortcutAdd.start();
+ boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
+ if (finishedInTime && proc.exitValue() == 0) {
+ LOG.debug("Created file {} for auto start.", autoStartFolderEntry);
+ return CompletableFuture.completedFuture(null);
+ } else {
+ throw new IOException("Process existed with error code " + proc.exitValue());
+ }
+ } catch (IOException e) {
+ LOG.debug("Adding entry to auto start folder failed.", e);
+ return CompletableFuture.failedFuture(new SystemCommandException("Adding entry to auto start folder failed."));
+ }
+ }
+
+
@Override
public void disableAutoStart() throws TogglingAutoStartFailedException {
- ProcessBuilder regRemove = new ProcessBuilder("reg", "delete", HKCU_AUTOSTART_KEY, //
- "/v", AUTOSTART_VALUE, //
- "/f");
- String command = regRemove.command().stream().collect(Collectors.joining(" "));
- try {
- Process proc = regRemove.start();
- boolean finishedInTime = waitForProcess(proc, 5, TimeUnit.SECONDS);
- if (finishedInTime && proc.exitValue() == 0) {
- LOG.debug("Removed {} from registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
- } else {
- LOG.debug("Registry could not be edited. Error code was {}.", proc.exitValue());
- removeShortcutOfAppFromAutostartFolder();
- throw new TogglingAutoStartFailedException("Removing registry value failed.");
- }
- } catch (IOException e) {
- removeShortcutOfAppFromAutostartFolder();
- throw new TogglingAutoStartFailedException("Removing registry value failed. " + command, e);
+ if (activatedOverRegistry) {
+ disableAutoStartOverRegistry().whenComplete((voit, ex) -> {
+ if (ex == null) {
+ this.activatedOverRegistry = false;
+ }
+ });
+ }
+
+ if (activatedOverFolder) {
+ disableAutoStartOverFolder().whenComplete((voit, ex) -> {
+ if (ex == null) {
+ this.activatedOverFolder = false;
+ }
+ });
+ }
+
+ if (activatedOverRegistry || activatedOverFolder) {
+ throw new TogglingAutoStartFailedException("Disabling auto start failed both over registry and auto start folder.");
}
}
- private static boolean waitForProcess(Process proc, int timeout, TimeUnit timeUnit) {
+ public CompletableFuture disableAutoStartOverRegistry() {
+ ProcessBuilder regRemove = new ProcessBuilder("reg", "delete", HKCU_AUTOSTART_KEY, //
+ "/v", AUTOSTART_VALUE, //
+ "/f");
+ try {
+ Process proc = regRemove.start();
+ boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
+ if (finishedInTime && proc.exitValue() == 0) {
+ LOG.debug("Removed {} from registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
+ return CompletableFuture.completedFuture(null);
+ } else {
+ throw new IOException("Process existed with error code " + proc.exitValue());
+ }
+ } catch (IOException e) {
+ LOG.debug("Registry could not be edited to remove auto start.", e);
+ return CompletableFuture.failedFuture(new SystemCommandException("Removing registry value failed."));
+ }
+ }
+
+ private CompletableFuture disableAutoStartOverFolder() {
+ try {
+ Files.delete(Path.of(WINDOWS_START_MENU_ENTRY));
+ LOG.debug("Successfully deleted {}.", WINDOWS_START_MENU_ENTRY);
+ return CompletableFuture.completedFuture(null);
+ } catch (NoSuchFileException e) {
+ //that is also okay
+ return CompletableFuture.completedFuture(null);
+ } catch (IOException e) {
+ LOG.debug("Failed to delete entry from auto start folder.", e);
+ return CompletableFuture.failedFuture(e);
+ }
+ }
+
+ private static boolean waitForProcessOrCancel(Process proc, int timeout, TimeUnit timeUnit) {
boolean finishedInTime = false;
try {
finishedInTime = proc.waitFor(timeout, timeUnit);
@@ -96,26 +194,11 @@ class AutoStartWinStrategy implements AutoStartStrategy {
return finishedInTime;
}
- private void addShortcutOfAppToAutostartFolder() throws TogglingAutoStartWithPowershellFailedException {
- String startMenuDirectory = System.getProperty("user.home") + WINDOWS_START_MENU_FOLDER + "\\Cryptomator.lnk";
- String createShortcutCommand = "$s=(New-Object -COM WScript.Shell).CreateShortcut('" + startMenuDirectory + "');$s.TargetPath='" + exePath + "';$s.Save();";
- ProcessBuilder shortcutAdd = new ProcessBuilder("cmd", "/c", "Start powershell " + createShortcutCommand);
- try {
- shortcutAdd.start();
- } catch (IOException e) {
- throw new TogglingAutoStartWithPowershellFailedException("Adding shortcut to autostart folder failed.", e);
+ public class SystemCommandException extends RuntimeException {
+
+ public SystemCommandException(String msg) {
+ super(msg);
}
}
- private void removeShortcutOfAppFromAutostartFolder() throws TogglingAutoStartWithPowershellFailedException {
- String startMenuDirectory = System.getProperty("user.home") + WINDOWS_START_MENU_FOLDER + "\\Cryptomator.lnk";
- ProcessBuilder shortcutRemove = new ProcessBuilder("cmd", "/c del \"" + startMenuDirectory + "\"");
- try {
- shortcutRemove.start();
- } catch (IOException e) {
- throw new TogglingAutoStartWithPowershellFailedException("Removing shortcut from autostart folder failed.", e);
- }
- }
-
-
}