mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-18 10:41:26 +00:00
Merge branch 'develop' into feature/hub
# Conflicts: # .idea/runConfigurations/Cryptomator_Linux.xml # .idea/runConfigurations/Cryptomator_Linux_Dev.xml # .idea/runConfigurations/Cryptomator_Windows.xml # .idea/runConfigurations/Cryptomator_Windows_Dev.xml # .idea/runConfigurations/Cryptomator_macOS.xml # .idea/runConfigurations/Cryptomator_macOS_Dev.xml # pom.xml
This commit is contained in:
@@ -56,6 +56,7 @@ module org.cryptomator.desktop {
|
||||
opens org.cryptomator.ui.health to javafx.fxml;
|
||||
opens org.cryptomator.ui.keyloading.hub to javafx.fxml;
|
||||
opens org.cryptomator.ui.keyloading.masterkeyfile to javafx.fxml;
|
||||
opens org.cryptomator.ui.lock to javafx.fxml;
|
||||
opens org.cryptomator.ui.mainwindow to javafx.fxml;
|
||||
opens org.cryptomator.ui.migration to javafx.fxml;
|
||||
opens org.cryptomator.ui.preferences to javafx.fxml;
|
||||
|
||||
@@ -5,6 +5,7 @@ public interface Constants {
|
||||
String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||
String MASTERKEY_BACKUP_SUFFIX = ".bkup";
|
||||
String VAULTCONFIG_FILENAME = "vault.cryptomator";
|
||||
String CRYPTOMATOR_FILENAME_EXT = ".cryptomator";
|
||||
byte[] PEPPER = new byte[0];
|
||||
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ public class Environment {
|
||||
LOG.debug("cryptomator.ipcSocketPath: {}", System.getProperty("cryptomator.ipcSocketPath"));
|
||||
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
|
||||
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
|
||||
LOG.debug("cryptomator.pluginDir: {}", System.getProperty("cryptomator.pluginDir"));
|
||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
||||
LOG.debug("cryptomator.appVersion: {}", System.getProperty("cryptomator.appVersion"));
|
||||
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
||||
LOG.debug("cryptomator.showTrayIcon: {}", System.getProperty("cryptomator.showTrayIcon"));
|
||||
LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
|
||||
@@ -68,10 +70,18 @@ public class Environment {
|
||||
return getPath("cryptomator.logDir").map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
public Optional<Path> getPluginDir() {
|
||||
return getPath("cryptomator.pluginDir").map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
public Optional<Path> getMountPointsDir() {
|
||||
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
public Optional<String> getAppVersion() {
|
||||
return Optional.ofNullable(System.getProperty("cryptomator.appVersion"));
|
||||
}
|
||||
|
||||
public Optional<String> getBuildNumber() {
|
||||
return Optional.ofNullable(System.getProperty("cryptomator.buildNumber"));
|
||||
}
|
||||
|
||||
142
src/main/java/org/cryptomator/common/ErrorCode.java
Normal file
142
src/main/java/org/cryptomator/common/ErrorCode.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Holds a throwable and provides a human-readable {@link #toString() three-component string representation}
|
||||
* aiming to allow documentation and lookup of same or similar errors.
|
||||
*/
|
||||
public class ErrorCode {
|
||||
|
||||
private final static int A_PRIME = 31;
|
||||
private final static int SEED = 0xdeadbeef;
|
||||
public final static String DELIM = ":";
|
||||
|
||||
private final static int LATEST_FRAME = 1;
|
||||
private final static int ALL_FRAMES = Integer.MAX_VALUE;
|
||||
|
||||
private final Throwable throwable;
|
||||
private final Throwable rootCause;
|
||||
private final int rootCauseSpecificFrames;
|
||||
|
||||
private ErrorCode(Throwable throwable, Throwable rootCause, int rootCauseSpecificFrames) {
|
||||
this.throwable = Objects.requireNonNull(throwable);
|
||||
this.rootCause = Objects.requireNonNull(rootCause);
|
||||
this.rootCauseSpecificFrames = rootCauseSpecificFrames;
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
String methodCode() {
|
||||
return format(traceCode(rootCause, LATEST_FRAME));
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
String rootCauseCode() {
|
||||
return format(traceCode(rootCause, rootCauseSpecificFrames));
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
String throwableCode() {
|
||||
return format(traceCode(throwable, ALL_FRAMES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces an error code consisting of three {@value DELIM}-separated components.
|
||||
* <p>
|
||||
* A full match of the error code indicates the exact same throwable (to the extent possible
|
||||
* without hash collisions). A partial match of the first or second component indicates related problems
|
||||
* with the same root cause.
|
||||
*
|
||||
* @return A three-part error code
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return methodCode() + DELIM + rootCauseCode() + DELIM + throwableCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministically creates an error code from the stack trace of the given <code>cause</code>.
|
||||
* <p>
|
||||
* The code consists of three parts separated by {@value DELIM}:
|
||||
* <ul>
|
||||
* <li>The first part depends on the root cause and the method that threw it</li>
|
||||
* <li>The second part depends on the root cause and its stack trace</li>
|
||||
* <li>The third part depends on all the cause hierarchy</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Parts may be identical if the cause is the root cause or the root cause has just one single item in its stack trace.
|
||||
*
|
||||
* @param throwable The exception
|
||||
* @return A three-part error code
|
||||
*/
|
||||
public static ErrorCode of(Throwable throwable) {
|
||||
var causalChain = Throwables.getCausalChain(throwable);
|
||||
if (causalChain.size() > 1) {
|
||||
var rootCause = causalChain.get(causalChain.size() - 1);
|
||||
var parentOfRootCause = causalChain.get(causalChain.size() - 2);
|
||||
var rootSpecificFrames = countTopmostFrames(rootCause.getStackTrace(), parentOfRootCause.getStackTrace());
|
||||
return new ErrorCode(throwable, rootCause, rootSpecificFrames);
|
||||
} else {
|
||||
return new ErrorCode(throwable, throwable, ALL_FRAMES);
|
||||
}
|
||||
}
|
||||
|
||||
private String format(int value) {
|
||||
// Cut off highest 12 bits (only leave 20 least significant bits) and XOR rest with cutoff
|
||||
value = (value & 0xfffff) ^ (value >>> 20);
|
||||
return Strings.padStart(Integer.toString(value, 32).toUpperCase(Locale.ROOT), 4, '0');
|
||||
}
|
||||
|
||||
private int traceCode(Throwable e, int frameCount) {
|
||||
int result = SEED;
|
||||
if (e.getCause() != null) {
|
||||
result = traceCode(e.getCause(), frameCount);
|
||||
}
|
||||
result = result * A_PRIME + e.getClass().getName().hashCode();
|
||||
var stack = e.getStackTrace();
|
||||
for (int i = 0; i < Math.min(stack.length, frameCount); i++) {
|
||||
result = result * A_PRIME + stack[i].getClassName().hashCode();
|
||||
result = result * A_PRIME + stack[i].getMethodName().hashCode();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of <em>additional</em> frames contained in <code>allFrames</code> but not in <code>bottomFrames</code>.
|
||||
* <p>
|
||||
* If <code>allFrames</code> does not end with <code>bottomFrames</code>, it is considered distinct and all its frames are counted.
|
||||
*
|
||||
* @param allFrames Some stack frames
|
||||
* @param bottomFrames Other stack frames, potentially forming the bottom of the stack of <code>allFrames</code>
|
||||
* @return The number of additional frames in <code>allFrames</code>. In most cases this should be equal to the difference in size.
|
||||
*/
|
||||
// visible for testing
|
||||
static int countTopmostFrames(StackTraceElement[] allFrames, StackTraceElement[] bottomFrames) {
|
||||
if (allFrames.length < bottomFrames.length) {
|
||||
// if frames had been stacked on top of bottomFrames, allFrames would be larger
|
||||
return allFrames.length;
|
||||
} else {
|
||||
return allFrames.length - commonSuffixLength(allFrames, bottomFrames);
|
||||
}
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
static <T> int commonSuffixLength(T[] set, T[] subset) {
|
||||
Preconditions.checkArgument(set.length >= subset.length);
|
||||
// iterate items backwards as long as they are identical
|
||||
var iterator = reverseStream(subset).iterator();
|
||||
return (int) reverseStream(set).takeWhile(item -> iterator.hasNext() && iterator.next().equals(item)).count();
|
||||
}
|
||||
|
||||
private static <T> Stream<T> reverseStream(T[] array) {
|
||||
return IntStream.rangeClosed(1, array.length).mapToObj(i -> array[array.length - i]);
|
||||
}
|
||||
|
||||
}
|
||||
66
src/main/java/org/cryptomator/common/PluginClassLoader.java
Normal file
66
src/main/java/org/cryptomator/common/PluginClassLoader.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class PluginClassLoader extends URLClassLoader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PluginClassLoader.class);
|
||||
private static final String NAME = "PluginClassLoader";
|
||||
private static final String JAR_SUFFIX = ".jar";
|
||||
|
||||
@Inject
|
||||
public PluginClassLoader(Environment env) {
|
||||
super(NAME, env.getPluginDir().map(PluginClassLoader::findJars).orElse(new URL[0]), PluginClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
private static URL[] findJars(Path path) {
|
||||
if (!Files.isDirectory(path)) {
|
||||
return new URL[0];
|
||||
} else {
|
||||
try {
|
||||
var visitor = new JarVisitor();
|
||||
Files.walkFileTree(path, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, visitor);
|
||||
return visitor.urls.toArray(URL[]::new);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to scan plugin dir " + path, e);
|
||||
return new URL[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class JarVisitor extends SimpleFileVisitor<Path> {
|
||||
|
||||
private final List<URL> urls = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
if (attrs.isRegularFile() && file.getFileName().toString().toLowerCase().endsWith(JAR_SUFFIX)) {
|
||||
try {
|
||||
urls.add(file.toUri().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
LOG.warn("Failed to create URL for jar file {}", file);
|
||||
}
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,11 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String displayName() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
getKeychainOrFail().storePassphrase(key, passphrase);
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.cryptomator.common.keychain;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.PluginClassLoader;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
|
||||
@@ -17,8 +18,8 @@ public class KeychainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Set<ServiceLoader.Provider<KeychainAccessProvider>> provideAvailableKeychainAccessProviderFactories() {
|
||||
return ServiceLoader.load(KeychainAccessProvider.class).stream().collect(Collectors.toUnmodifiableSet());
|
||||
static Set<ServiceLoader.Provider<KeychainAccessProvider>> provideAvailableKeychainAccessProviderFactories(PluginClassLoader classLoader) {
|
||||
return ServiceLoader.load(KeychainAccessProvider.class, classLoader).stream().collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -24,6 +24,6 @@ class AvailableDriveLetterChooser implements MountPointChooser {
|
||||
|
||||
@Override
|
||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||
return this.windowsDriveLetters.getAvailableDriveLetterPath();
|
||||
return this.windowsDriveLetters.getDesiredAvailableDriveLetterPath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ public class Settings {
|
||||
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
||||
public static final String DEFAULT_LICENSE_KEY = "";
|
||||
public static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
|
||||
public static final String DEFAULT_DISPLAY_CONFIGURATION = "";
|
||||
|
||||
|
||||
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||
@@ -59,6 +61,12 @@ public class Settings {
|
||||
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
||||
private final BooleanProperty showMinimizeButton = new SimpleBooleanProperty(DEFAULT_SHOW_MINIMIZE_BUTTON);
|
||||
private final BooleanProperty showTrayIcon;
|
||||
private final IntegerProperty windowXPosition = new SimpleIntegerProperty();
|
||||
private final IntegerProperty windowYPosition = new SimpleIntegerProperty();
|
||||
private final IntegerProperty windowWidth = new SimpleIntegerProperty();
|
||||
private final IntegerProperty windowHeight = new SimpleIntegerProperty();
|
||||
private final ObjectProperty<String> displayConfiguration = new SimpleObjectProperty<>(DEFAULT_DISPLAY_CONFIGURATION);
|
||||
|
||||
|
||||
private Consumer<Settings> saveCmd;
|
||||
|
||||
@@ -83,6 +91,11 @@ public class Settings {
|
||||
licenseKey.addListener(this::somethingChanged);
|
||||
showMinimizeButton.addListener(this::somethingChanged);
|
||||
showTrayIcon.addListener(this::somethingChanged);
|
||||
windowXPosition.addListener(this::somethingChanged);
|
||||
windowYPosition.addListener(this::somethingChanged);
|
||||
windowWidth.addListener(this::somethingChanged);
|
||||
windowHeight.addListener(this::somethingChanged);
|
||||
displayConfiguration.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||
@@ -141,7 +154,7 @@ public class Settings {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public ObjectProperty<String> keychainProvider() { return keychainProvider; }
|
||||
public ObjectProperty<String> keychainProvider() {return keychainProvider;}
|
||||
|
||||
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||
return userInterfaceOrientation;
|
||||
@@ -158,4 +171,24 @@ public class Settings {
|
||||
public BooleanProperty showTrayIcon() {
|
||||
return showTrayIcon;
|
||||
}
|
||||
|
||||
public IntegerProperty windowXPositionProperty() {
|
||||
return windowXPosition;
|
||||
}
|
||||
|
||||
public IntegerProperty windowYPositionProperty() {
|
||||
return windowYPosition;
|
||||
}
|
||||
|
||||
public IntegerProperty windowWidthProperty() {
|
||||
return windowWidth;
|
||||
}
|
||||
|
||||
public IntegerProperty windowHeightProperty() {
|
||||
return windowHeight;
|
||||
}
|
||||
|
||||
public ObjectProperty<String> displayConfigurationProperty() {
|
||||
return displayConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
out.name("licenseKey").value(value.licenseKey().get());
|
||||
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
|
||||
out.name("showTrayIcon").value(value.showTrayIcon().get());
|
||||
out.name("windowXPosition").value((value.windowXPositionProperty().get()));
|
||||
out.name("windowYPosition").value((value.windowYPositionProperty().get()));
|
||||
out.name("windowWidth").value((value.windowWidthProperty().get()));
|
||||
out.name("windowHeight").value((value.windowHeightProperty().get()));
|
||||
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
|
||||
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
@@ -86,6 +92,12 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
|
||||
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
|
||||
case "windowXPosition" -> settings.windowXPositionProperty().set(in.nextInt());
|
||||
case "windowYPosition" -> settings.windowYPositionProperty().set(in.nextInt());
|
||||
case "windowWidth" -> settings.windowWidthProperty().set(in.nextInt());
|
||||
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
|
||||
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
|
||||
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
|
||||
@@ -17,9 +17,6 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
@@ -38,8 +35,6 @@ import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.EnumSet;
|
||||
@@ -62,6 +57,7 @@ public class Vault {
|
||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||
private final VaultState state;
|
||||
private final ObjectProperty<Exception> lastKnownException;
|
||||
private final VaultConfigCache configCache;
|
||||
private final VaultStats stats;
|
||||
private final StringBinding displayName;
|
||||
private final StringBinding displayablePath;
|
||||
@@ -78,8 +74,9 @@ public class Vault {
|
||||
private volatile Volume volume;
|
||||
|
||||
@Inject
|
||||
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
|
||||
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.configCache = configCache;
|
||||
this.volumeProvider = volumeProvider;
|
||||
this.defaultMountFlags = defaultMountFlags;
|
||||
this.cryptoFileSystem = cryptoFileSystem;
|
||||
@@ -107,10 +104,10 @@ public class Vault {
|
||||
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||
flags.add(FileSystemFlags.READONLY);
|
||||
} else if(vaultSettings.maxCleartextFilenameLength().get() == -1) {
|
||||
} else if (vaultSettings.maxCleartextFilenameLength().get() == -1) {
|
||||
LOG.debug("Determining cleartext filename length limitations...");
|
||||
var checker = new FileSystemCapabilityChecker();
|
||||
int shorteningThreshold = getUnverifiedVaultConfig().allegedShorteningThreshold();
|
||||
int shorteningThreshold = configCache.get().allegedShorteningThreshold();
|
||||
int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath());
|
||||
if (ciphertextLimit < shorteningThreshold) {
|
||||
int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath());
|
||||
@@ -328,19 +325,6 @@ public class Vault {
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read the vault config file and parse it without verifying its integrity.
|
||||
*
|
||||
* @return an unverified vault config
|
||||
* @throws VaultConfigLoadException if the read file cannot be properly parsed
|
||||
* @throws IOException if reading the file fails
|
||||
*
|
||||
*/
|
||||
public UnverifiedVaultConfig getUnverifiedVaultConfig() throws IOException {
|
||||
Path configPath = getPath().resolve(org.cryptomator.common.Constants.VAULTCONFIG_FILENAME);
|
||||
String token = Files.readString(configPath, StandardCharsets.US_ASCII);
|
||||
return VaultConfig.decode(token);
|
||||
}
|
||||
|
||||
public Observable[] observables() {
|
||||
return new Observable[]{state};
|
||||
@@ -375,6 +359,10 @@ public class Vault {
|
||||
}
|
||||
}
|
||||
|
||||
public VaultConfigCache getVaultConfigCache() {
|
||||
return configCache;
|
||||
}
|
||||
|
||||
public void setCustomMountFlags(String mountFlags) {
|
||||
vaultSettings.mountFlags().set(mountFlags);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ public interface VaultComponent {
|
||||
@BindsInstance
|
||||
Builder vaultSettings(VaultSettings vaultSettings);
|
||||
|
||||
@BindsInstance
|
||||
Builder vaultConfigCache(VaultConfigCache configCache);
|
||||
|
||||
@BindsInstance
|
||||
Builder initialVaultState(VaultState.Value vaultState);
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Wrapper for lazy loading and on-demand reloading of the vault configuration.
|
||||
*/
|
||||
public class VaultConfigCache {
|
||||
|
||||
private final VaultSettings settings;
|
||||
private final AtomicReference<VaultConfig.UnverifiedVaultConfig> config;
|
||||
|
||||
VaultConfigCache(VaultSettings settings) {
|
||||
this.settings = settings;
|
||||
this.config = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
void reloadConfig() throws IOException {
|
||||
try {
|
||||
config.set(readConfigFromStorage(this.settings.path().get()));
|
||||
} catch (IOException e) {
|
||||
config.set(null);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public VaultConfig.UnverifiedVaultConfig get() throws IOException {
|
||||
if (config.get() == null) {
|
||||
reloadConfig();
|
||||
}
|
||||
return config.get();
|
||||
}
|
||||
|
||||
public VaultConfig.UnverifiedVaultConfig getUnchecked() {
|
||||
try {
|
||||
return get();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to read the vault config file and parse it without verifying its integrity.
|
||||
*
|
||||
* @throws VaultConfigLoadException if the read file cannot be properly parsed
|
||||
* @throws IOException if reading the file fails
|
||||
*/
|
||||
static VaultConfig.UnverifiedVaultConfig readConfigFromStorage(Path vaultPath) throws IOException {
|
||||
Path configPath = vaultPath.resolve(Constants.VAULTCONFIG_FILENAME);
|
||||
String token = Files.readString(configPath, StandardCharsets.US_ASCII);
|
||||
return VaultConfig.decode(token);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -31,6 +30,7 @@ import java.util.ResourceBundle;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||
|
||||
@Singleton
|
||||
public class VaultListManager {
|
||||
@@ -96,6 +96,11 @@ public class VaultListManager {
|
||||
VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings);
|
||||
try {
|
||||
VaultState.Value vaultState = determineVaultState(vaultSettings.path().get());
|
||||
VaultConfigCache wrapper = new VaultConfigCache(vaultSettings);
|
||||
compBuilder.vaultConfigCache(wrapper); //first set the wrapper in the builder, THEN try to load config
|
||||
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
||||
wrapper.reloadConfig();
|
||||
}
|
||||
compBuilder.initialVaultState(vaultState);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e);
|
||||
@@ -112,6 +117,9 @@ public class VaultListManager {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
||||
try {
|
||||
var determinedState = determineVaultState(vault.getPath());
|
||||
if (determinedState == LOCKED) {
|
||||
vault.getVaultConfigCache().reloadConfig();
|
||||
}
|
||||
state.set(determinedState);
|
||||
yield determinedState;
|
||||
} catch (IOException e) {
|
||||
@@ -132,7 +140,9 @@ public class VaultListManager {
|
||||
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
|
||||
case VAULT -> VaultState.Value.LOCKED;
|
||||
case UNRELATED -> VaultState.Value.MISSING;
|
||||
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? VaultState.Value.NEEDS_MIGRATION : VaultState.Value.MISSING;
|
||||
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? //
|
||||
VaultState.Value.NEEDS_MIGRATION //
|
||||
: VaultState.Value.MISSING;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class WebDavVolume implements Volume {
|
||||
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specific one or there is no free.
|
||||
Supplier<String> driveLetterSupplier;
|
||||
if (System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
|
||||
driveLetterSupplier = () -> windowsDriveLetters.getAvailableDriveLetter().orElse(null);
|
||||
driveLetterSupplier = () -> windowsDriveLetters.getDesiredAvailableDriveLetter().orElse(null);
|
||||
} else {
|
||||
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ import java.util.stream.StreamSupport;
|
||||
@Singleton
|
||||
public final class WindowsDriveLetters {
|
||||
|
||||
private static final Set<String> C_TO_Z;
|
||||
private static final Set<String> A_TO_Z;
|
||||
|
||||
static {
|
||||
try (IntStream stream = IntStream.rangeClosed('C', 'Z')) {
|
||||
C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
|
||||
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
|
||||
A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public final class WindowsDriveLetters {
|
||||
}
|
||||
|
||||
public Set<String> getAllDriveLetters() {
|
||||
return C_TO_Z;
|
||||
return A_TO_Z;
|
||||
}
|
||||
|
||||
public Set<String> getOccupiedDriveLetters() {
|
||||
@@ -59,6 +59,26 @@ public final class WindowsDriveLetters {
|
||||
return getAvailableDriveLetter().map(this::toPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips A and B and only returns them if all other are occupied.
|
||||
*
|
||||
* @return an Optional containing either the letter of a free drive letter or empty, if none is available
|
||||
*/
|
||||
public Optional<String> getDesiredAvailableDriveLetter() {
|
||||
var availableDriveLetters = getAvailableDriveLetters();
|
||||
var optString = availableDriveLetters.stream().filter(s -> !(s.equals("A") || s.equals("B"))).findFirst();
|
||||
return optString.or(() -> availableDriveLetters.stream().findFirst());
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips A and B and only returns them if all other are occupied.
|
||||
*
|
||||
* @return an Optional containing either the path to a free drive letter or empty, if none is available
|
||||
*/
|
||||
public Optional<Path> getDesiredAvailableDriveLetterPath() {
|
||||
return getDesiredAvailableDriveLetter().map(this::toPath);
|
||||
}
|
||||
|
||||
public Path toPath(String driveLetter) {
|
||||
return Path.of(driveLetter + ":\\");
|
||||
}
|
||||
|
||||
@@ -44,7 +44,9 @@ public interface IpcCommunicator extends Closeable {
|
||||
}
|
||||
// Didn't get any connection yet? I.e. we're the first app instance, so let's launch a server:
|
||||
try {
|
||||
return Server.create(socketPaths.iterator().next());
|
||||
final var socketPath = socketPaths.iterator().next();
|
||||
Files.deleteIfExists(socketPath); // ensure path does not exist before creating it
|
||||
return Server.create(socketPath);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to create IPC server", e);
|
||||
return new LoopbackCommunicator();
|
||||
|
||||
@@ -27,6 +27,7 @@ class Server implements IpcCommunicator {
|
||||
}
|
||||
|
||||
public static Server create(Path socketPath) throws IOException {
|
||||
Files.createDirectories(socketPath.getParent());
|
||||
var address = UnixDomainSocketAddress.of(socketPath);
|
||||
var serverSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
|
||||
serverSocketChannel.bind(address);
|
||||
|
||||
@@ -38,18 +38,16 @@ public class Cryptomator {
|
||||
private final DebugMode debugMode;
|
||||
private final Environment env;
|
||||
private final Lazy<IpcMessageHandler> ipcMessageHandler;
|
||||
private final Optional<String> applicationVersion;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
private final ShutdownHook shutdownHook;
|
||||
private final Lazy<UiLauncher> uiLauncher;
|
||||
|
||||
@Inject
|
||||
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, Lazy<UiLauncher> uiLauncher) {
|
||||
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, Lazy<UiLauncher> uiLauncher) {
|
||||
this.logConfig = logConfig;
|
||||
this.debugMode = debugMode;
|
||||
this.env = env;
|
||||
this.ipcMessageHandler = ipcMessageHandler;
|
||||
this.applicationVersion = applicationVersion;
|
||||
this.shutdownLatch = shutdownLatch;
|
||||
this.shutdownHook = shutdownHook;
|
||||
this.uiLauncher = uiLauncher;
|
||||
@@ -69,7 +67,7 @@ public class Cryptomator {
|
||||
*/
|
||||
private int run(String[] args) {
|
||||
logConfig.init();
|
||||
LOG.info("Starting Cryptomator {} on {} {} ({})", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
debugMode.initialize();
|
||||
|
||||
/*
|
||||
|
||||
@@ -18,11 +18,4 @@ class CryptomatorModule {
|
||||
return new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("applicationVersion")
|
||||
static Optional<String> provideApplicationVersion() {
|
||||
return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,11 +56,10 @@ public class ChooseExistingVaultController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
final String resource = SystemUtils.IS_OS_MAC ? "/img/select-masterkey-mac.png" : "/img/select-masterkey-win.png";
|
||||
try (InputStream in = getClass().getResourceAsStream(resource)) {
|
||||
this.screenshot = new Image(in);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
if (SystemUtils.IS_OS_MAC) {
|
||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-mac.png").toString());
|
||||
} else {
|
||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-win.png").toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +72,7 @@ public class ChooseExistingVaultController implements FxController {
|
||||
public void chooseFileAndNext() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Vault", "*.cryptomator"));
|
||||
File masterkeyFile = fileChooser.showOpenDialog(window);
|
||||
if (masterkeyFile != null) {
|
||||
vaultPath.setValue(masterkeyFile.toPath().toAbsolutePath().getParent());
|
||||
|
||||
@@ -29,16 +29,20 @@ import javafx.scene.control.ToggleGroup;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.UUID;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class CreateNewVaultLocationController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class);
|
||||
private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home"));
|
||||
private static final String TEMP_FILE_FORMAT = "cryptomator-%s.tmp";
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> chooseNameScene;
|
||||
@@ -92,7 +96,7 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
statusText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
|
||||
statusGraphic.set(badLocation);
|
||||
return false;
|
||||
} else if (!Files.isWritable(p.getParent())) {
|
||||
} else if (!isActuallyWritable(p.getParent())) {
|
||||
statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsNotWritable"));
|
||||
statusGraphic.set(badLocation);
|
||||
return false;
|
||||
@@ -107,6 +111,21 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActuallyWritable(Path p) {
|
||||
Path tmpFile = p.resolve(String.format(TEMP_FILE_FORMAT, UUID.randomUUID()));
|
||||
try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) {
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
Files.deleteIfExists(tmpFile);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to delete temporary file {}. Needs to be deleted manually.", tmpFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
||||
|
||||
@@ -44,8 +44,10 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Comparator;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
@@ -195,12 +197,28 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
} catch (CryptoException e) {
|
||||
throw new IOException("Failed initialize vault.", e);
|
||||
}
|
||||
} finally {
|
||||
AtomicBoolean cleanupFailed = new AtomicBoolean(false);
|
||||
Files.walk(path)
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.forEach(p -> {
|
||||
try {
|
||||
Files.deleteIfExists(p);
|
||||
} catch (IOException e) {
|
||||
cleanupFailed.set(false);
|
||||
}
|
||||
});
|
||||
if(cleanupFailed.get()) {
|
||||
LOG.warn("Failed to cleanup after failed vault creation at {}. Leftovers need to be deleted manually.", path);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. write vault-external readme file:
|
||||
String storagePathReadmeFileName = resourceBundle.getString("addvault.new.readme.storageLocation.fileName");
|
||||
try (WritableByteChannel ch = Files.newByteChannel(path.resolve(storagePathReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
||||
ch.write(US_ASCII.encode(readmeGenerator.createVaultStorageLocationReadmeRtf()));
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to create vault storage location readme.", e);
|
||||
}
|
||||
|
||||
LOG.info("Created vault at {}", path);
|
||||
|
||||
@@ -14,7 +14,7 @@ public class LocationPresets {
|
||||
private static final String USER_HOME = System.getProperty("user.home");
|
||||
private static final String[] ICLOUDDRIVE_LOCATIONS = {"~/Library/Mobile Documents/iCloud~com~setolabs~Cryptomator/Documents", "~/iCloudDrive/iCloud~com~setolabs~Cryptomator"};
|
||||
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
|
||||
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
|
||||
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive/My Drive", "~/Google Drive"};
|
||||
private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"};
|
||||
private static final String[] MEGA_LOCATIONS = {"~/MEGA"};
|
||||
private static final String[] PCLOUD_LOCATIONS = {"~/pCloudDrive"};
|
||||
|
||||
@@ -1,22 +1,47 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import org.cryptomator.common.ErrorCode;
|
||||
import org.cryptomator.common.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.stage.Stage;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ErrorController implements FxController {
|
||||
|
||||
private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s";
|
||||
private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s";
|
||||
private static final String SEARCH_ERRORCODE_DELIM = " OR ";
|
||||
private static final String REPORT_BODY_TEMPLATE = """
|
||||
<!-- ✏️ Please describe what happened as accurately as possible. -->
|
||||
<!-- 📋 Please also copy and paste the detail text from the error window. -->
|
||||
""";
|
||||
|
||||
private final Application application;
|
||||
private final String stackTrace;
|
||||
private final ErrorCode errorCode;
|
||||
private final Scene previousScene;
|
||||
private final Stage window;
|
||||
|
||||
private BooleanProperty copiedDetails = new SimpleBooleanProperty();
|
||||
|
||||
@Inject
|
||||
ErrorController(@Named("stackTrace") String stackTrace, @Nullable Scene previousScene, Stage window) {
|
||||
ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window) {
|
||||
this.application = application;
|
||||
this.stackTrace = stackTrace;
|
||||
this.errorCode = errorCode;
|
||||
this.previousScene = previousScene;
|
||||
this.window = window;
|
||||
}
|
||||
@@ -33,6 +58,31 @@ public class ErrorController implements FxController {
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void searchError() {
|
||||
var searchTerm = URLEncoder.encode(getErrorCode().replace(ErrorCode.DELIM, SEARCH_ERRORCODE_DELIM), StandardCharsets.UTF_8);
|
||||
application.getHostServices().showDocument(SEARCH_URL_FORMAT.formatted(searchTerm));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void reportError() {
|
||||
var title = URLEncoder.encode(getErrorCode(), StandardCharsets.UTF_8);
|
||||
var body = URLEncoder.encode(REPORT_BODY_TEMPLATE, StandardCharsets.UTF_8);
|
||||
application.getHostServices().showDocument(REPORT_URL_FORMAT.formatted(title, body));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void copyDetails() {
|
||||
ClipboardContent clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putString(getDetailText());
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
|
||||
copiedDetails.set(true);
|
||||
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
|
||||
copiedDetails.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public boolean isPreviousScenePresent() {
|
||||
@@ -42,4 +92,20 @@ public class ErrorController implements FxController {
|
||||
public String getStackTrace() {
|
||||
return stackTrace;
|
||||
}
|
||||
|
||||
public String getErrorCode() {
|
||||
return errorCode.toString();
|
||||
}
|
||||
|
||||
public String getDetailText() {
|
||||
return "```\nError Code " + getErrorCode() + "\n" + getStackTrace() + "\n```";
|
||||
}
|
||||
|
||||
public BooleanProperty copiedDetailsProperty() {
|
||||
return copiedDetails;
|
||||
}
|
||||
|
||||
public boolean getCopiedDetails() {
|
||||
return copiedDetails.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.ErrorCode;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -31,6 +32,11 @@ abstract class ErrorModule {
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static ErrorCode provideErrorCode(Throwable cause) {
|
||||
return ErrorCode.of(cause);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(ErrorController.class)
|
||||
|
||||
@@ -12,7 +12,6 @@ public enum FxmlFile {
|
||||
ERROR("/fxml/error.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
HEALTH_START("/fxml/health_start.fxml"), //
|
||||
HEALTH_START_FAIL("/fxml/health_start_fail.fxml"), //
|
||||
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
|
||||
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
|
||||
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
|
||||
|
||||
@@ -12,6 +12,7 @@ public enum FontAwesome5Icon {
|
||||
CARET_RIGHT("\uF0Da"), //
|
||||
CHECK("\uF00C"), //
|
||||
CLOCK("\uF017"), //
|
||||
CLIPBOARD("\uF328"), //
|
||||
COG("\uF013"), //
|
||||
COGS("\uF085"), //
|
||||
COPY("\uF0C5"), //
|
||||
|
||||
@@ -10,6 +10,7 @@ package org.cryptomator.ui.controls;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
@@ -27,7 +28,6 @@ import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import java.awt.Toolkit;
|
||||
import java.nio.CharBuffer;
|
||||
import java.text.Normalizer;
|
||||
import java.text.Normalizer.Form;
|
||||
@@ -123,8 +123,7 @@ public class SecurePasswordField extends TextField {
|
||||
}
|
||||
|
||||
private void updateCapsLocked() {
|
||||
//TODO: fixed in JavaFX 17. AWT code needed until update (see https://bugs.openjdk.java.net/browse/JDK-8259680)
|
||||
capsLocked.set(isFocused() && Toolkit.getDefaultToolkit().getLockingKeyState(java.awt.event.KeyEvent.VK_CAPS_LOCK));
|
||||
capsLocked.set(Platform.isKeyLocked(KeyCode.CAPS).orElse(false));
|
||||
}
|
||||
|
||||
private void updateContainingNonPrintableChars() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -31,9 +32,9 @@ public class UpdateChecker {
|
||||
private final ScheduledService<String> updateCheckerService;
|
||||
|
||||
@Inject
|
||||
UpdateChecker(Settings settings, @Named("applicationVersion") Optional<String> applicationVersion, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
|
||||
UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
|
||||
this.settings = settings;
|
||||
this.applicationVersion = applicationVersion;
|
||||
this.applicationVersion = env.getAppVersion();
|
||||
this.latestVersionProperty = latestVersionProperty;
|
||||
this.semVerComparator = semVerComparator;
|
||||
this.updateCheckerService = updateCheckerService;
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.cryptomator.ui.fxapp;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -51,9 +52,9 @@ public abstract class UpdateCheckerModule {
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static HttpRequest provideCheckForUpdatesRequest(@Named("applicationVersion") Optional<String> applicationVersion) {
|
||||
static HttpRequest provideCheckForUpdatesRequest(Environment env) {
|
||||
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", //
|
||||
applicationVersion.orElse("SNAPSHOT"), //
|
||||
env.getAppVersion().orElse("SNAPSHOT"), //
|
||||
SystemUtils.OS_NAME, //
|
||||
SystemUtils.OS_VERSION, //
|
||||
SystemUtils.OS_ARCH); //
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import com.tobiasdiez.easybind.Subscription;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CheckListCellController implements FxController {
|
||||
|
||||
@@ -18,26 +16,26 @@ public class CheckListCellController implements FxController {
|
||||
private final ObjectProperty<Check> check;
|
||||
private final Binding<String> checkName;
|
||||
private final Binding<Boolean> checkRunnable;
|
||||
private final List<Subscription> subscriptions;
|
||||
|
||||
/* FXML */
|
||||
public CheckBox forRunSelectedCheckBox;
|
||||
public CheckBox checkbox;
|
||||
|
||||
@Inject
|
||||
public CheckListCellController() {
|
||||
check = new SimpleObjectProperty<>();
|
||||
checkRunnable = EasyBind.wrapNullable(check).mapObservable(Check::stateProperty).map(Check.CheckState.RUNNABLE::equals).orElse(false);
|
||||
checkName = EasyBind.wrapNullable(check).map(Check::getName).orElse("");
|
||||
subscriptions = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
subscriptions.add(EasyBind.subscribe(check, c -> {
|
||||
forRunSelectedCheckBox.selectedProperty().unbind();
|
||||
if (c != null) {
|
||||
forRunSelectedCheckBox.selectedProperty().bindBidirectional(c.chosenForExecutionProperty());
|
||||
check.addListener((observable, oldVal, newVal) -> {
|
||||
if (oldVal != null) {
|
||||
Bindings.unbindBidirectional(checkbox.selectedProperty(), oldVal.chosenForExecutionProperty());
|
||||
}
|
||||
}));
|
||||
if (newVal != null) {
|
||||
Bindings.bindBidirectional(checkbox.selectedProperty(), newVal.chosenForExecutionProperty());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ObjectProperty<Check> checkProperty() {
|
||||
|
||||
@@ -16,26 +16,15 @@ import javafx.stage.Stage;
|
||||
@Subcomponent(modules = {HealthCheckModule.class})
|
||||
public interface HealthCheckComponent {
|
||||
|
||||
LoadUnverifiedConfigResult loadConfig();
|
||||
|
||||
@HealthCheckWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.HEALTH_START)
|
||||
Lazy<Scene> startScene();
|
||||
|
||||
@FxmlScene(FxmlFile.HEALTH_START_FAIL)
|
||||
Lazy<Scene> failScene();
|
||||
|
||||
default Stage showHealthCheckWindow() {
|
||||
Stage stage = window();
|
||||
// TODO reevaluate config loading, as soon as we have the new generic error screen
|
||||
var unverifiedConf = loadConfig();
|
||||
if (unverifiedConf.config() != null) {
|
||||
stage.setScene(startScene().get());
|
||||
} else {
|
||||
stage.setScene(failScene().get());
|
||||
}
|
||||
stage.setScene(startScene().get());
|
||||
stage.show();
|
||||
return stage;
|
||||
}
|
||||
@@ -52,5 +41,4 @@ public interface HealthCheckComponent {
|
||||
HealthCheckComponent build();
|
||||
}
|
||||
|
||||
record LoadUnverifiedConfigResult(VaultConfig.UnverifiedVaultConfig config, Throwable error) {}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -37,18 +36,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@Module(subcomponents = {KeyLoadingComponent.class})
|
||||
abstract class HealthCheckModule {
|
||||
|
||||
// TODO reevaluate config loading, as soon as we have the new generic error screen
|
||||
@Provides
|
||||
@HealthCheckScoped
|
||||
static HealthCheckComponent.LoadUnverifiedConfigResult provideLoadConfigResult(@HealthCheckWindow Vault vault) {
|
||||
try {
|
||||
return new HealthCheckComponent.LoadUnverifiedConfigResult(vault.getUnverifiedVaultConfig(), null);
|
||||
} catch (IOException e) {
|
||||
return new HealthCheckComponent.LoadUnverifiedConfigResult(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@HealthCheckScoped
|
||||
static AtomicReference<Masterkey> provideMasterkeyRef() {
|
||||
@@ -129,13 +116,6 @@ abstract class HealthCheckModule {
|
||||
return fxmlLoaders.createScene(FxmlFile.HEALTH_START);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.HEALTH_START_FAIL)
|
||||
@HealthCheckScoped
|
||||
static Scene provideHealthStartFailScene(@HealthCheckWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.HEALTH_START_FAIL);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.HEALTH_CHECK_LIST)
|
||||
@HealthCheckScoped
|
||||
@@ -148,11 +128,6 @@ abstract class HealthCheckModule {
|
||||
@FxControllerKey(StartController.class)
|
||||
abstract FxController bindStartController(StartController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(StartFailController.class)
|
||||
abstract FxController bindStartFailController(StartFailController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(CheckListController.class)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultConfigCache;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
import org.cryptomator.cryptofs.VaultKeyInvalidException;
|
||||
@@ -18,8 +19,6 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
@@ -35,7 +34,7 @@ public class StartController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Stage unlockWindow;
|
||||
private final ObjectProperty<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
|
||||
private final VaultConfigCache vaultConfig;
|
||||
private final KeyLoadingStrategy keyLoadingStrategy;
|
||||
private final ExecutorService executor;
|
||||
private final AtomicReference<Masterkey> masterkeyRef;
|
||||
@@ -44,11 +43,10 @@ public class StartController implements FxController {
|
||||
private final Lazy<ErrorComponent.Builder> errorComponent;
|
||||
|
||||
@Inject
|
||||
public StartController(@HealthCheckWindow Stage window, HealthCheckComponent.LoadUnverifiedConfigResult configLoadResult, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, Lazy<ErrorComponent.Builder> errorComponent, @Named("unlockWindow") Stage unlockWindow) {
|
||||
Preconditions.checkNotNull(configLoadResult.config());
|
||||
public StartController(@HealthCheckWindow Stage window, @HealthCheckWindow Vault vault, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, Lazy<ErrorComponent.Builder> errorComponent, @Named("unlockWindow") Stage unlockWindow) {
|
||||
this.window = window;
|
||||
this.unlockWindow = unlockWindow;
|
||||
this.unverifiedVaultConfig = new SimpleObjectProperty<>(configLoadResult.config());
|
||||
this.vaultConfig = vault.getVaultConfigCache();
|
||||
this.keyLoadingStrategy = keyLoadingStrategy;
|
||||
this.executor = executor;
|
||||
this.masterkeyRef = masterkeyRef;
|
||||
@@ -71,7 +69,6 @@ public class StartController implements FxController {
|
||||
|
||||
private void loadKey() {
|
||||
assert !Platform.isFxApplicationThread();
|
||||
assert unverifiedVaultConfig.get() != null;
|
||||
try {
|
||||
keyLoadingStrategy.use(this::verifyVaultConfig);
|
||||
} catch (VaultConfigLoadException | UnlockCancelledException e) {
|
||||
@@ -80,7 +77,7 @@ public class StartController implements FxController {
|
||||
}
|
||||
|
||||
private void verifyVaultConfig(KeyLoadingStrategy keyLoadingStrategy) throws VaultConfigLoadException {
|
||||
var unverifiedCfg = unverifiedVaultConfig.get();
|
||||
var unverifiedCfg = vaultConfig.getUnchecked();
|
||||
try (var masterkey = keyLoadingStrategy.loadKey(unverifiedCfg.getKeyId())) {
|
||||
var verifiedCfg = unverifiedCfg.verify(masterkey.getEncoded(), unverifiedCfg.allegedVaultVersion());
|
||||
vaultConfigRef.set(verifiedCfg);
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
// TODO reevaluate config loading, as soon as we have the new generic error screen
|
||||
@HealthCheckScoped
|
||||
public class StartFailController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final ObjectProperty<Throwable> loadError;
|
||||
private final ObjectProperty<FontAwesome5Icon> moreInfoIcon;
|
||||
|
||||
/* FXML */
|
||||
public TitledPane moreInfoPane;
|
||||
|
||||
@Inject
|
||||
public StartFailController(@HealthCheckWindow Stage window, HealthCheckComponent.LoadUnverifiedConfigResult configLoadResult) {
|
||||
Preconditions.checkNotNull(configLoadResult.error());
|
||||
this.window = window;
|
||||
this.loadError = new SimpleObjectProperty<>(configLoadResult.error());
|
||||
this.moreInfoIcon = new SimpleObjectProperty<>(FontAwesome5Icon.CARET_RIGHT);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
moreInfoPane.expandedProperty().addListener(this::setMoreInfoIcon);
|
||||
}
|
||||
|
||||
private void setMoreInfoIcon(ObservableValue<? extends Boolean> observable, boolean wasExpanded, boolean willExpand) {
|
||||
moreInfoIcon.set(willExpand ? FontAwesome5Icon.CARET_DOWN : FontAwesome5Icon.CARET_RIGHT);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
/* Getter & Setter */
|
||||
|
||||
public ObjectProperty<FontAwesome5Icon> moreInfoIconProperty() {
|
||||
return moreInfoIcon;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getMoreInfoIcon() {
|
||||
return moreInfoIcon.getValue();
|
||||
}
|
||||
|
||||
public String getStackTrace() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
loadError.get().printStackTrace(new PrintStream(baos));
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public String getLocalizedErrorMessage() {
|
||||
return loadError.get().getLocalizedMessage();
|
||||
}
|
||||
|
||||
public boolean isParseException() {
|
||||
return loadError.get() instanceof VaultConfigLoadException;
|
||||
}
|
||||
|
||||
public boolean isIoException() {
|
||||
return !isParseException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package org.cryptomator.ui.keyloading;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
@@ -12,9 +11,7 @@ import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(includes = {MasterkeyFileLoadingModule.class, HubKeyLoadingModule.class})
|
||||
@@ -32,7 +29,7 @@ abstract class KeyLoadingModule {
|
||||
@KeyLoadingScoped
|
||||
static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Vault vault, Map<String, Provider<KeyLoadingStrategy>> strategies) {
|
||||
try {
|
||||
String scheme = vault.getUnverifiedVaultConfig().getKeyId().getScheme();
|
||||
String scheme = vault.getVaultConfigCache().get().getKeyId().getScheme();
|
||||
var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));
|
||||
return strategies.getOrDefault(scheme, () -> fallback).get();
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -77,6 +77,7 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
|
||||
boolean success = false;
|
||||
try {
|
||||
user.use(this);
|
||||
success = true;
|
||||
} catch (MasterkeyLoadingFailedException e) {
|
||||
if (recoverFromException(e)) {
|
||||
LOG.info("Unlock attempt threw {}. Reattempting...", e.getClass().getSimpleName());
|
||||
|
||||
@@ -40,7 +40,7 @@ public abstract class HubKeyLoadingModule {
|
||||
@KeyLoadingScoped
|
||||
static HubConfig provideHubConfig(@KeyLoading Vault vault) {
|
||||
try {
|
||||
return vault.getUnverifiedVaultConfig().getHeader("hub", HubConfig.class);
|
||||
return vault.getVaultConfigCache().get().getHeader("hub", HubConfig.class);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ public class ReceiveKeyController implements FxController {
|
||||
|
||||
private static URI getVaultBaseUri(Vault vault) {
|
||||
try {
|
||||
var kid = vault.getUnverifiedVaultConfig().getKeyId();
|
||||
var kid = vault.getVaultConfigCache().get().getKeyId();
|
||||
assert kid.getScheme().startsWith(SCHEME_PREFIX);
|
||||
var hubUriScheme = kid.getScheme().substring(SCHEME_PREFIX.length());
|
||||
return new URI(hubUriScheme, kid.getSchemeSpecificPart(), kid.getFragment());
|
||||
|
||||
@@ -11,12 +11,11 @@ import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.application.Platform;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
||||
|
||||
@Singleton
|
||||
class AppLaunchEventHandler {
|
||||
@@ -69,7 +68,7 @@ class AppLaunchEventHandler {
|
||||
assert Platform.isFxApplicationThread();
|
||||
try {
|
||||
final Vault v;
|
||||
if (potentialVaultPath.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
||||
if (potentialVaultPath.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||
v = vaultListManager.add(potentialVaultPath.getParent());
|
||||
} else {
|
||||
v = vaultListManager.add(potentialVaultPath);
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.cryptomator.ui.launcher;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.PluginClassLoader;
|
||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
||||
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
||||
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
|
||||
@@ -33,21 +34,21 @@ public abstract class UiLauncherModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Optional<UiAppearanceProvider> provideAppearanceProvider() {
|
||||
return ServiceLoader.load(UiAppearanceProvider.class).findFirst();
|
||||
static Optional<UiAppearanceProvider> provideAppearanceProvider(PluginClassLoader classLoader) {
|
||||
return ServiceLoader.load(UiAppearanceProvider.class, classLoader).findFirst();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Optional<AutoStartProvider> provideAutostartProvider() {
|
||||
return ServiceLoader.load(AutoStartProvider.class).findFirst();
|
||||
static Optional<AutoStartProvider> provideAutostartProvider(PluginClassLoader classLoader) {
|
||||
return ServiceLoader.load(AutoStartProvider.class, classLoader).findFirst();
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Optional<TrayIntegrationProvider> provideTrayIntegrationProvider() {
|
||||
return ServiceLoader.load(TrayIntegrationProvider.class).findFirst();
|
||||
static Optional<TrayIntegrationProvider> provideTrayIntegrationProvider(PluginClassLoader classLoader) {
|
||||
return ServiceLoader.load(TrayIntegrationProvider.class, classLoader).findFirst();
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -23,12 +23,11 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
|
||||
@@ -55,7 +54,7 @@ public class MainWindowController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
LOG.debug("init MainWindowController");
|
||||
LOG.trace("init MainWindowController");
|
||||
root.setOnDragEntered(this::handleDragEvent);
|
||||
root.setOnDragOver(this::handleDragEvent);
|
||||
root.setOnDragDropped(this::handleDragEvent);
|
||||
@@ -96,6 +95,9 @@ public class MainWindowController implements FxController {
|
||||
|
||||
private boolean containsVault(Path path) {
|
||||
try {
|
||||
if (path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||
path = path.getParent();
|
||||
}
|
||||
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
@@ -104,7 +106,7 @@ public class MainWindowController implements FxController {
|
||||
|
||||
private void addVault(Path pathToVault) {
|
||||
try {
|
||||
if (pathToVault.getFileName().toString().equals(VAULTCONFIG_FILENAME)) {
|
||||
if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||
vaultListManager.add(pathToVault.getParent());
|
||||
} else {
|
||||
vaultListManager.add(pathToVault);
|
||||
|
||||
@@ -6,29 +6,32 @@ import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.health.HealthCheckComponent;
|
||||
import org.cryptomator.ui.migration.MigrationComponent;
|
||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, HealthCheckComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class})
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, HealthCheckComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
|
||||
abstract class MainWindowModule {
|
||||
|
||||
@Provides
|
||||
@@ -49,15 +52,23 @@ abstract class MainWindowModule {
|
||||
@MainWindowScoped
|
||||
static Stage provideStage(StageFactory factory) {
|
||||
Stage stage = factory.create(StageStyle.UNDECORATED);
|
||||
// TODO: min/max values chosen arbitrarily. We might wanna take a look at the user's resolution...
|
||||
stage.setMinWidth(650);
|
||||
stage.setMinHeight(440);
|
||||
stage.setMaxWidth(1000);
|
||||
stage.setMaxHeight(700);
|
||||
stage.setTitle("Cryptomator");
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MainWindowScoped
|
||||
@Named("errorWindow")
|
||||
static Stage provideErrorStage(@MainWindow Stage window, StageFactory factory, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create(StageStyle.DECORATED);
|
||||
stage.setTitle(resourceBundle.getString("main.vaultDetail.error.windowTitle"));
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.initOwner(window);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.MAIN_WINDOW)
|
||||
@MainWindowScoped
|
||||
|
||||
@@ -16,6 +16,7 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@@ -53,22 +54,43 @@ public class MainWindowTitleController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
LOG.debug("init MainWindowTitleController");
|
||||
LOG.trace("init MainWindowTitleController");
|
||||
updateChecker.automaticallyCheckForUpdatesIfEnabled();
|
||||
titleBar.setOnMousePressed(event -> {
|
||||
xOffset = event.getSceneX();
|
||||
yOffset = event.getSceneY();
|
||||
|
||||
});
|
||||
titleBar.setOnMouseClicked(event -> {
|
||||
if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) {
|
||||
window.setFullScreen(!window.isFullScreen());
|
||||
}
|
||||
});
|
||||
titleBar.setOnMouseDragged(event -> {
|
||||
if (window.isFullScreen()) return;
|
||||
window.setX(event.getScreenX() - xOffset);
|
||||
window.setY(event.getScreenY() - yOffset);
|
||||
});
|
||||
titleBar.setOnDragDetected(mouseDragEvent -> {
|
||||
titleBar.startFullDrag();
|
||||
});
|
||||
titleBar.setOnMouseDragReleased(mouseDragEvent -> {
|
||||
saveWindowSettings();
|
||||
});
|
||||
|
||||
window.setOnCloseRequest(event -> {
|
||||
close();
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
private void saveWindowSettings() {
|
||||
settings.windowYPositionProperty().setValue(window.getY());
|
||||
settings.windowXPositionProperty().setValue(window.getX());
|
||||
settings.windowWidthProperty().setValue(window.getWidth());
|
||||
settings.windowHeightProperty().setValue(window.getHeight());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
if (trayMenuInitialized) {
|
||||
|
||||
@@ -1,41 +1,100 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@MainWindow
|
||||
public class ResizeController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResizeController.class);
|
||||
|
||||
private final Stage window;
|
||||
|
||||
public Region tlResizer;
|
||||
public Region trResizer;
|
||||
public Region blResizer;
|
||||
public Region brResizer;
|
||||
public Region tResizer;
|
||||
public Region rResizer;
|
||||
public Region bResizer;
|
||||
public Region lResizer;
|
||||
public Region lDefaultRegion;
|
||||
public Region tDefaultRegion;
|
||||
public Region rDefaultRegion;
|
||||
public Region bDefaultRegion;
|
||||
|
||||
private double origX, origY, origW, origH;
|
||||
|
||||
private final Settings settings;
|
||||
|
||||
private final BooleanBinding showResizingArrows;
|
||||
|
||||
@Inject
|
||||
ResizeController(@MainWindow Stage window) {
|
||||
ResizeController(@MainWindow Stage window, Settings settings) {
|
||||
this.window = window;
|
||||
// TODO inject settings and save current position and size
|
||||
this.settings = settings;
|
||||
this.showResizingArrows = Bindings.createBooleanBinding(this::isShowResizingArrows, window.fullScreenProperty());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
tlResizer.setOnMousePressed(this::startResize);
|
||||
trResizer.setOnMousePressed(this::startResize);
|
||||
blResizer.setOnMousePressed(this::startResize);
|
||||
brResizer.setOnMousePressed(this::startResize);
|
||||
tlResizer.setOnMouseDragged(this::resizeTopLeft);
|
||||
trResizer.setOnMouseDragged(this::resizeTopRight);
|
||||
blResizer.setOnMouseDragged(this::resizeBottomLeft);
|
||||
brResizer.setOnMouseDragged(this::resizeBottomRight);
|
||||
LOG.trace("init ResizeController");
|
||||
|
||||
if (neverTouched()) {
|
||||
settings.displayConfigurationProperty().setValue(getMonitorSizes());
|
||||
return;
|
||||
} else {
|
||||
if (didDisplayConfigurationChange()) {
|
||||
//If the position is illegal, then the window appears on the main screen in the middle of the window.
|
||||
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
|
||||
window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2);
|
||||
window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2);
|
||||
window.setWidth(window.getMinWidth());
|
||||
window.setHeight(window.getMinHeight());
|
||||
} else {
|
||||
window.setHeight(settings.windowHeightProperty().get() > window.getMinHeight() ? settings.windowHeightProperty().get() : window.getMinHeight());
|
||||
window.setWidth(settings.windowWidthProperty().get() > window.getMinWidth() ? settings.windowWidthProperty().get() : window.getMinWidth());
|
||||
window.setX(settings.windowXPositionProperty().get());
|
||||
window.setY(settings.windowYPositionProperty().get());
|
||||
}
|
||||
}
|
||||
savePositionalSettings();
|
||||
}
|
||||
|
||||
private boolean neverTouched() {
|
||||
return (settings.windowHeightProperty().get() == 0) && (settings.windowWidthProperty().get() == 0) && (settings.windowXPositionProperty().get() == 0) && (settings.windowYPositionProperty().get() == 0);
|
||||
}
|
||||
|
||||
private boolean didDisplayConfigurationChange() {
|
||||
String currentDisplayConfiguration = getMonitorSizes();
|
||||
String settingsDisplayConfiguration = settings.displayConfigurationProperty().get();
|
||||
boolean configurationHasChanged = !settingsDisplayConfiguration.equals(currentDisplayConfiguration);
|
||||
if (configurationHasChanged) settings.displayConfigurationProperty().setValue(currentDisplayConfiguration);
|
||||
return configurationHasChanged;
|
||||
}
|
||||
|
||||
private String getMonitorSizes() {
|
||||
ObservableList<Screen> screens = Screen.getScreens();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < screens.size(); i++) {
|
||||
Rectangle2D screenBounds = screens.get(i).getBounds();
|
||||
if (!sb.isEmpty()) sb.append(" ");
|
||||
sb.append("displayId: " + i + ", " + screenBounds.getWidth() + "x" + screenBounds.getHeight() + ";");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void startResize(MouseEvent evt) {
|
||||
@@ -45,27 +104,33 @@ public class ResizeController implements FxController {
|
||||
origH = window.getHeight();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resizeTopLeft(MouseEvent evt) {
|
||||
resizeTop(evt);
|
||||
resizeLeft(evt);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resizeTopRight(MouseEvent evt) {
|
||||
resizeTop(evt);
|
||||
resizeRight(evt);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resizeBottomLeft(MouseEvent evt) {
|
||||
resizeBottom(evt);
|
||||
resizeLeft(evt);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resizeBottomRight(MouseEvent evt) {
|
||||
resizeBottom(evt);
|
||||
resizeRight(evt);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resizeTop(MouseEvent evt) {
|
||||
startResize(evt);
|
||||
double newY = evt.getScreenY();
|
||||
double dy = newY - origY;
|
||||
double newH = origH - dy;
|
||||
@@ -75,7 +140,9 @@ public class ResizeController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resizeLeft(MouseEvent evt) {
|
||||
startResize(evt);
|
||||
double newX = evt.getScreenX();
|
||||
double dx = newX - origX;
|
||||
double newW = origW - dx;
|
||||
@@ -85,6 +152,7 @@ public class ResizeController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resizeBottom(MouseEvent evt) {
|
||||
double newH = evt.getSceneY();
|
||||
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
|
||||
@@ -92,6 +160,7 @@ public class ResizeController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resizeRight(MouseEvent evt) {
|
||||
double newW = evt.getSceneX();
|
||||
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
|
||||
@@ -99,4 +168,21 @@ public class ResizeController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@FXML
|
||||
public void savePositionalSettings() {
|
||||
settings.windowHeightProperty().setValue(window.getHeight());
|
||||
settings.windowWidthProperty().setValue(window.getWidth());
|
||||
settings.windowYPositionProperty().setValue(window.getY());
|
||||
settings.windowXPositionProperty().setValue(window.getX());
|
||||
}
|
||||
|
||||
public BooleanBinding showResizingArrowsProperty() {
|
||||
return showResizingArrows;
|
||||
}
|
||||
|
||||
public boolean isShowResizingArrows() {
|
||||
//If in fullscreen resizing is not be possible;
|
||||
return !window.isFullScreen();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,45 +2,46 @@ package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultDetailUnknownErrorController implements FxController {
|
||||
|
||||
private final Binding<String> stackTrace;
|
||||
private final ObjectProperty<Vault> vault;
|
||||
private final ErrorComponent.Builder errorComponentBuilder;
|
||||
private final Stage errorWindow;
|
||||
private final RemoveVaultComponent.Builder removeVault;
|
||||
|
||||
@Inject
|
||||
public VaultDetailUnknownErrorController(ObjectProperty<Vault> vault) {
|
||||
this.stackTrace = EasyBind.select(vault) //
|
||||
.selectObject(Vault::lastKnownExceptionProperty) //
|
||||
.map(this::provideStackTrace);
|
||||
public VaultDetailUnknownErrorController(ObjectProperty<Vault> vault, ErrorComponent.Builder errorComponentBuilder, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) {
|
||||
this.vault = vault;
|
||||
this.errorComponentBuilder = errorComponentBuilder;
|
||||
this.errorWindow = errorWindow;
|
||||
this.removeVault = removeVault;
|
||||
}
|
||||
|
||||
private String provideStackTrace(Throwable cause) {
|
||||
// TODO deduplicate ErrorModule.java
|
||||
if (cause != null) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
cause.printStackTrace(new PrintStream(baos));
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
@FXML
|
||||
public void showError() {
|
||||
errorComponentBuilder.window(errorWindow).cause(vault.get().getLastKnownException()).build().showErrorScene();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Binding<String> stackTraceProperty() {
|
||||
return stackTrace;
|
||||
@FXML
|
||||
public void reload() {
|
||||
VaultListManager.redetermineVaultState(vault.get());
|
||||
}
|
||||
|
||||
public String getStackTrace() {
|
||||
return stackTrace.getValue();
|
||||
@FXML
|
||||
void didClickRemoveVault() {
|
||||
removeVault.vault(vault.get()).build().showRemoveVault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ abstract class PreferencesModule {
|
||||
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("preferences.title"));
|
||||
stage.setResizable(false);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@ package org.cryptomator.ui.traymenu;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.integrations.uiappearance.Theme;
|
||||
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
|
||||
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -12,38 +9,25 @@ import javax.inject.Inject;
|
||||
import java.awt.AWTException;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.TrayIcon;
|
||||
import java.util.Optional;
|
||||
|
||||
@TrayMenuScoped
|
||||
public class TrayIconController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TrayIconController.class);
|
||||
|
||||
private final TrayImageFactory imageFactory;
|
||||
private final Optional<UiAppearanceProvider> appearanceProvider;
|
||||
private final TrayMenuController trayMenuController;
|
||||
private final TrayIcon trayIcon;
|
||||
private volatile boolean initialized;
|
||||
|
||||
@Inject
|
||||
TrayIconController(TrayImageFactory imageFactory, TrayMenuController trayMenuController, Optional<UiAppearanceProvider> appearanceProvider) {
|
||||
TrayIconController(TrayImageFactory imageFactory, TrayMenuController trayMenuController) {
|
||||
this.trayMenuController = trayMenuController;
|
||||
this.imageFactory = imageFactory;
|
||||
this.appearanceProvider = appearanceProvider;
|
||||
this.trayIcon = new TrayIcon(imageFactory.loadImage(), "Cryptomator", trayMenuController.getMenu());
|
||||
}
|
||||
|
||||
public synchronized void initializeTrayIcon() throws IllegalStateException {
|
||||
Preconditions.checkState(!initialized);
|
||||
|
||||
appearanceProvider.ifPresent(appearanceProvider -> {
|
||||
try {
|
||||
appearanceProvider.addListener(this::systemInterfaceThemeChanged);
|
||||
} catch (UiAppearanceException e) {
|
||||
LOG.error("Failed to enable automatic tray icon theme switching.");
|
||||
}
|
||||
});
|
||||
|
||||
trayIcon.setImageAutoSize(true);
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
trayIcon.addActionListener(trayMenuController::showMainWindow);
|
||||
@@ -61,10 +45,6 @@ public class TrayIconController {
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
private void systemInterfaceThemeChanged(Theme theme) {
|
||||
trayIcon.setImage(imageFactory.loadImage()); // TODO refactor "theme" is re-queried in loadImage()
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@@ -25,11 +25,7 @@ class TrayImageFactory {
|
||||
}
|
||||
|
||||
private String getMacResourceName() {
|
||||
var theme = appearanceProvider.map(UiAppearanceProvider::getSystemTheme).orElse(Theme.LIGHT);
|
||||
return switch (theme) {
|
||||
case DARK -> "/img/tray_icon_mac_white.png";
|
||||
case LIGHT -> "/img/tray_icon_mac_black.png";
|
||||
};
|
||||
return "/img/tray_icon_mac.png";
|
||||
}
|
||||
|
||||
private String getWinOrLinuxResourceName() {
|
||||
|
||||
@@ -27,6 +27,7 @@ import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.StringConverter;
|
||||
import java.io.File;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
@@ -94,7 +95,9 @@ public class MountOptionsController implements FxController {
|
||||
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
|
||||
driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get());
|
||||
|
||||
if (vault.getVaultSettings().useCustomMountPath().get() && !getRestrictToStableFuseOnWindows() /* to prevent invalid states */) {
|
||||
if (vault.getVaultSettings().useCustomMountPath().get()
|
||||
&& vault.getVaultSettings().getCustomMountPath().isPresent()
|
||||
&& !getRestrictToStableFuseOnWindows() /* to prevent invalid states */) {
|
||||
mountPoint.selectToggle(mountPointCustomDir);
|
||||
} else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) {
|
||||
mountPoint.selectToggle(mountPointWinDriveLetter);
|
||||
@@ -125,25 +128,30 @@ public class MountOptionsController implements FxController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void chooseCustomMountPoint() {
|
||||
public void chooseCustomMountPoint() {
|
||||
chooseCustomMountPointOrReset(mountPointCustomDir);
|
||||
}
|
||||
|
||||
private void chooseCustomMountPointOrReset(Toggle previousMountToggle) {
|
||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
|
||||
try {
|
||||
directoryChooser.setInitialDirectory(Path.of(System.getProperty("user.home")).toFile());
|
||||
} catch (Exception e) {
|
||||
//NO-OP
|
||||
var initialDir = vault.getVaultSettings().getCustomMountPath().orElse(System.getProperty("user.home"));
|
||||
directoryChooser.setInitialDirectory(Path.of(initialDir).toFile());
|
||||
} catch (InvalidPathException e) {
|
||||
// no-op
|
||||
}
|
||||
File file = directoryChooser.showDialog(window);
|
||||
if (file != null) {
|
||||
vault.getVaultSettings().customMountPath().set(file.getAbsolutePath());
|
||||
} else {
|
||||
vault.getVaultSettings().customMountPath().set(null);
|
||||
mountPoint.selectToggle(previousMountToggle);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleMountPoint(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||
private void toggleMountPoint(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) {
|
||||
if (mountPointCustomDir.equals(newValue) && Strings.isNullOrEmpty(vault.getVaultSettings().customMountPath().get())) {
|
||||
chooseCustomMountPoint();
|
||||
chooseCustomMountPointOrReset(oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,4 @@ public enum SelectedVaultOptionsTab {
|
||||
*/
|
||||
KEY,
|
||||
|
||||
/**
|
||||
* Show Auto-Lock tab
|
||||
*/
|
||||
AUTOLOCK,
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ public class VaultOptionsController implements FxController {
|
||||
public Tab generalTab;
|
||||
public Tab mountTab;
|
||||
public Tab keyTab;
|
||||
public Tab autoLockTab;
|
||||
|
||||
@Inject
|
||||
VaultOptionsController(@VaultOptionsWindow Stage window, ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty) {
|
||||
@@ -48,7 +47,6 @@ public class VaultOptionsController implements FxController {
|
||||
case ANY, GENERAL -> generalTab;
|
||||
case MOUNT -> mountTab;
|
||||
case KEY -> keyTab;
|
||||
case AUTOLOCK -> autoLockTab;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user