mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 02:01:27 +00:00
Merge pull request #1440 from cryptomator/feature/#1439-finish-migration
closes #1439
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>1.9.13</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>0.1.6</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>0.1.0-beta1</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.win.version>0.2.0</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>0.1.0-beta3</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>0.1.0-beta2</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>1.2.6</cryptomator.fuse.version>
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.cryptomator.ui.preferences;
|
||||
|
||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
||||
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
@Deprecated
|
||||
class AutoStartMacStrategy implements AutoStartStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AutoStartMacStrategy.class);
|
||||
|
||||
private final AutoStartProvider autoStartProvider;
|
||||
|
||||
public AutoStartMacStrategy(AutoStartProvider autoStartProvider) {
|
||||
this.autoStartProvider = autoStartProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Boolean> isAutoStartEnabled() {
|
||||
return CompletableFuture.completedFuture(autoStartProvider.isEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableAutoStart() throws TogglingAutoStartFailedException {
|
||||
try {
|
||||
autoStartProvider.enable();
|
||||
LOG.debug("Added login item.");
|
||||
} catch (ToggleAutoStartFailedException e) {
|
||||
throw new TogglingAutoStartFailedException("Failed to add login item.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableAutoStart() throws TogglingAutoStartFailedException {
|
||||
try {
|
||||
autoStartProvider.disable();
|
||||
LOG.debug("Removed login item.");
|
||||
} catch (ToggleAutoStartFailedException e) {
|
||||
throw new TogglingAutoStartFailedException("Failed to remove login item.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.cryptomator.ui.preferences;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Deprecated
|
||||
@Module
|
||||
abstract class AutoStartModule {
|
||||
|
||||
@Provides
|
||||
@PreferencesScoped
|
||||
public static Optional<AutoStartStrategy> provideAutoStartStrategy(Optional<AutoStartProvider> autoStartProvider) {
|
||||
if (SystemUtils.IS_OS_MAC_OSX && autoStartProvider.isPresent()) {
|
||||
return Optional.of(new AutoStartMacStrategy(autoStartProvider.get()));
|
||||
} else if (SystemUtils.IS_OS_WINDOWS) {
|
||||
Optional<String> exeName = ProcessHandle.current().info().command();
|
||||
return exeName.map(AutoStartWinStrategy::new);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package org.cryptomator.ui.preferences;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
@Deprecated
|
||||
public interface AutoStartStrategy {
|
||||
|
||||
CompletionStage<Boolean> isAutoStartEnabled();
|
||||
|
||||
void enableAutoStart() throws TogglingAutoStartFailedException;
|
||||
|
||||
void disableAutoStart() throws TogglingAutoStartFailedException;
|
||||
|
||||
class TogglingAutoStartFailedException extends Exception {
|
||||
|
||||
public TogglingAutoStartFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TogglingAutoStartFailedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package org.cryptomator.ui.preferences;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* OS specific class to check, en- and disable the auto start on Windows.
|
||||
* <p>
|
||||
* Two strategies are implemented for this feature, the first uses the registry and the second one the autostart folder.
|
||||
* <p>
|
||||
* The registry strategy checks/add/removes at the registry key {@value HKCU_AUTOSTART_KEY} an entry for Cryptomator.
|
||||
* The folder strategy checks/add/removes at the location {@value WINDOWS_START_MENU_ENTRY}.
|
||||
* <p>
|
||||
* To check if the feature is active, both strategies are applied.
|
||||
* To enable the feature, first the registry is tried and only on failure the autostart folder is used.
|
||||
* To disable it, first it is determined by an internal state, 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 call its methods simultaniously by different threads.
|
||||
* @deprecated To be moved to integration-win project
|
||||
*/
|
||||
@Deprecated
|
||||
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 boolean activatedUsingFolder;
|
||||
private boolean activatedUsingRegistry;
|
||||
|
||||
public AutoStartWinStrategy(String exePath) {
|
||||
this.exePath = exePath;
|
||||
this.activatedUsingFolder = false;
|
||||
this.activatedUsingRegistry = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Boolean> isAutoStartEnabled() {
|
||||
return isAutoStartEnabledUsingRegistry().thenCombine(isAutoStartEnabledUsingFolder(), (bReg, bFolder) -> bReg || bFolder);
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> isAutoStartEnabledUsingFolder() {
|
||||
Path autoStartEntry = Path.of(System.getProperty("user.home") + WINDOWS_START_MENU_ENTRY);
|
||||
this.activatedUsingFolder = Files.exists(autoStartEntry);
|
||||
return CompletableFuture.completedFuture(activatedUsingFolder);
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> isAutoStartEnabledUsingRegistry() {
|
||||
ProcessBuilder regQuery = new ProcessBuilder("reg", "query", HKCU_AUTOSTART_KEY, //
|
||||
"/v", AUTOSTART_VALUE);
|
||||
try {
|
||||
Process proc = regQuery.start();
|
||||
return proc.onExit().thenApply(p -> {
|
||||
this.activatedUsingRegistry = p.exitValue() == 0;
|
||||
return activatedUsingRegistry;
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Failed to query {} from registry key {}", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableAutoStart() throws TogglingAutoStartFailedException {
|
||||
try {
|
||||
enableAutoStartUsingRegistry().thenAccept((Void v) -> this.activatedUsingRegistry = true).exceptionallyCompose(e -> {
|
||||
LOG.debug("Falling back to using autostart folder.");
|
||||
return this.enableAutoStartUsingFolder();
|
||||
}).thenAccept((Void v) -> this.activatedUsingFolder = 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 using registry and auto start folder.");
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> enableAutoStartUsingRegistry() {
|
||||
ProcessBuilder regAdd = new ProcessBuilder("reg", "add", HKCU_AUTOSTART_KEY, //
|
||||
"/v", AUTOSTART_VALUE, //
|
||||
"/t", "REG_SZ", //
|
||||
"/d", "\"" + exePath + "\"", //
|
||||
"/f");
|
||||
try {
|
||||
Process proc = regAdd.start();
|
||||
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 {
|
||||
throw new IOException("Process exited with error code " + proc.exitValue());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Registry could not be edited to set auto start.", e);
|
||||
return CompletableFuture.failedFuture(new SystemCommandException("Adding registry value failed."));
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> enableAutoStartUsingFolder() {
|
||||
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 exited 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 {
|
||||
if (activatedUsingRegistry) {
|
||||
disableAutoStartUsingRegistry().whenComplete((voit, ex) -> {
|
||||
if (ex == null) {
|
||||
this.activatedUsingRegistry = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (activatedUsingFolder) {
|
||||
disableAutoStartUsingFolder().whenComplete((voit, ex) -> {
|
||||
if (ex == null) {
|
||||
this.activatedUsingFolder = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (activatedUsingRegistry || activatedUsingFolder) {
|
||||
throw new TogglingAutoStartFailedException("Disabling auto start failed using registry and/or auto start folder.");
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> disableAutoStartUsingRegistry() {
|
||||
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 exited 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<Void> disableAutoStartUsingFolder() {
|
||||
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);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Timeout while reading registry", e);
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
if (!finishedInTime) {
|
||||
proc.destroyForcibly();
|
||||
}
|
||||
}
|
||||
return finishedInTime;
|
||||
}
|
||||
|
||||
private class SystemCommandException extends RuntimeException {
|
||||
|
||||
public SystemCommandException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.KeychainBackend;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
||||
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -14,10 +16,8 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import javafx.scene.control.CheckBox;
|
||||
@@ -42,7 +42,7 @@ public class GeneralPreferencesController implements FxController {
|
||||
private final Stage window;
|
||||
private final Settings settings;
|
||||
private final boolean trayMenuSupported;
|
||||
private final Optional<AutoStartStrategy> autoStartStrategy;
|
||||
private final Optional<AutoStartProvider> autoStartProvider;
|
||||
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final ExecutorService executor;
|
||||
@@ -61,11 +61,11 @@ public class GeneralPreferencesController implements FxController {
|
||||
public RadioButton nodeOrientationRtl;
|
||||
|
||||
@Inject
|
||||
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, Set<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
|
||||
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartProvider> autoStartProvider, Set<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
|
||||
this.window = window;
|
||||
this.settings = settings;
|
||||
this.trayMenuSupported = trayMenuSupported;
|
||||
this.autoStartStrategy = autoStartStrategy;
|
||||
this.autoStartProvider = autoStartProvider;
|
||||
this.keychainAccessProviders = keychainAccessProviders;
|
||||
this.selectedTabProperty = selectedTabProperty;
|
||||
this.licenseHolder = licenseHolder;
|
||||
@@ -89,11 +89,7 @@ public class GeneralPreferencesController implements FxController {
|
||||
|
||||
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
|
||||
|
||||
autoStartStrategy.ifPresent(autoStart -> {
|
||||
autoStart.isAutoStartEnabled().thenAccept(enabled -> {
|
||||
Platform.runLater(() -> autoStartCheckbox.setSelected(enabled));
|
||||
});
|
||||
});
|
||||
autoStartProvider.ifPresent(realProvider -> autoStartCheckbox.setSelected(realProvider.isEnabled()));
|
||||
|
||||
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
|
||||
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
|
||||
@@ -114,7 +110,7 @@ public class GeneralPreferencesController implements FxController {
|
||||
}
|
||||
|
||||
public boolean isAutoStartSupported() {
|
||||
return autoStartStrategy.isPresent();
|
||||
return autoStartProvider.isPresent();
|
||||
}
|
||||
|
||||
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||
@@ -129,15 +125,19 @@ public class GeneralPreferencesController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void toggleAutoStart() {
|
||||
autoStartStrategy.ifPresent(autoStart -> {
|
||||
autoStartProvider.ifPresent(autoStart -> {
|
||||
boolean enableAutoStart = autoStartCheckbox.isSelected();
|
||||
Task<Void> toggleTask = new ToggleAutoStartTask(autoStart, enableAutoStart);
|
||||
toggleTask.setOnFailed(event -> {
|
||||
try {
|
||||
if (enableAutoStart) {
|
||||
autoStart.enable();
|
||||
} else {
|
||||
autoStart.disable();
|
||||
}
|
||||
} catch (ToggleAutoStartFailedException e) {
|
||||
autoStartCheckbox.setSelected(!enableAutoStart); // restore previous state
|
||||
LOG.error("Failed to toggle autostart.", event.getSource().getException());
|
||||
errorComponent.cause(event.getSource().getException()).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||
});
|
||||
executor.execute(toggleTask);
|
||||
LOG.error("Failed to toggle autostart.", e);
|
||||
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -196,27 +196,4 @@ public class GeneralPreferencesController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ToggleAutoStartTask extends Task<Void> {
|
||||
|
||||
private final AutoStartStrategy autoStart;
|
||||
private final boolean enable;
|
||||
|
||||
public ToggleAutoStartTask(AutoStartStrategy autoStart, boolean enable) {
|
||||
this.autoStart = autoStart;
|
||||
this.enable = enable;
|
||||
|
||||
setOnFailed(event -> LOG.error("Failed to toggle Autostart", getException()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
if (enable) {
|
||||
autoStart.enableAutoStart();
|
||||
} else {
|
||||
autoStart.disableAutoStart();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(includes = {AutoStartModule.class})
|
||||
@Module
|
||||
abstract class PreferencesModule {
|
||||
|
||||
@Provides
|
||||
|
||||
Reference in New Issue
Block a user