~exprez135/cryptomator-libre

1cd4da07965e2538e92d23a973528d2e24713ce6 — Sebastian Stenzel 10 months ago 6381227 + 935aacc
Merge branch 'develop' into feature/#1228-forcedUnmountDialog
M .github/ISSUE_TEMPLATE/bug.md => .github/ISSUE_TEMPLATE/bug.md +13 -3
@@ 5,8 5,11 @@ labels: type:bug
---

<!--
⚠️⚠️⚠️ READ CAREFULLY ⚠️⚠️⚠️

**************************************
*                                    *
*   ⚠️⚠️⚠️ READ CAREFULLY ⚠️⚠️⚠️   *
*                                    *
**************************************
Do you want to ask a QUESTION? Are you looking for SUPPORT?
We're happy to help you via our support channels! Please read: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md



@@ 14,7 17,14 @@ By filing an issue, you are expected to comply with our code of conduct: https:/

Of course, we also expect you to search for existing similar issues first! ;) https://github.com/cryptomator/cryptomator/issues?q=

⚠️ IMPORTANT: If you don't stick to this template, the issue will get closed. To proof that you read this, please remove the X from the following line:

⚠️ IMPORTANT: If you don't stick to this template, the issue will get closed.

*****************************************************************************
*                                                                           *
*   To proof that you read this, please remove the X from the line below:   *
*                                                                           *
*****************************************************************************
-->
<!-- oooXooo -->


M main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java => main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java +1 -1
@@ 28,7 28,7 @@ class MacVolumeMountChooser implements MountPointChooser {

	@Override
	public Optional<Path> chooseMountPoint(Volume caller) {
		return Optional.of(VOLUME_PATH).map(dir -> this.helper.chooseTemporaryMountPoint(this.vaultSettings, dir));
		return Optional.of(helper.chooseTemporaryMountPoint(vaultSettings, VOLUME_PATH));
	}

	@Override

M main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java => main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java +5 -6
@@ 20,11 20,10 @@ import java.util.Optional;
class MountPointHelper {

	public static Logger LOG = LoggerFactory.getLogger(MountPointHelper.class);

	private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;

	private final Optional<Path> tmpMountPointDir;
	private volatile boolean alreadyChecked = false;
	private volatile boolean unmountDebrisCleared = false;

	@Inject
	public MountPointHelper(Environment env) {


@@ 55,13 54,13 @@ class MountPointHelper {
	}

	public synchronized void clearIrregularUnmountDebrisIfNeeded() {
		if (alreadyChecked || tmpMountPointDir.isEmpty()) {
			return; //nuthin to do
		if (unmountDebrisCleared || tmpMountPointDir.isEmpty()) {
			return; // nothing to do
		}
		if (Files.exists(tmpMountPointDir.get(), LinkOption.NOFOLLOW_LINKS)) {
			clearIrregularUnmountDebris(tmpMountPointDir.get());
		}
		alreadyChecked = true;
		unmountDebrisCleared = true;
	}

	private void clearIrregularUnmountDebris(Path dirContainingMountPoints) {


@@ 92,7 91,7 @@ class MountPointHelper {
		} catch (IOException e) {
			LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e);
		} finally {
			alreadyChecked = true;
			unmountDebrisCleared = true;
		}
	}


M main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java => main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java +3 -2
@@ 20,7 20,8 @@ public abstract class AbstractVolume implements Volume {
	}

	protected Path determineMountPoint() throws InvalidMountPointException {
		for (var chooser : Iterables.filter(choosers, c -> c.isApplicable(this))) {
		var applicableChoosers = Iterables.filter(choosers, c -> c.isApplicable(this));
		for (var chooser : applicableChoosers) {
			Optional<Path> chosenPath = chooser.chooseMountPoint(this);
			if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint
				continue;


@@ 29,7 30,7 @@ public abstract class AbstractVolume implements Volume {
			this.usedChooser = chooser;
			return chosenPath.get();
		}
		throw new InvalidMountPointException("No feasible MountPoint found!");
		throw new InvalidMountPointException(String.format("No feasible MountPoint found by choosers: %s", applicableChoosers));
	}

	protected void cleanupMountPoint() {

M main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java => main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java +12 -4
@@ 13,7 13,6 @@ import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import java.util.SortedSet;
import java.util.concurrent.ExecutorService;

public class DokanyVolume extends AbstractVolume {


@@ 42,7 41,6 @@ public class DokanyVolume extends AbstractVolume {
	@Override
	public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
		this.mountPoint = determineMountPoint();
		String mountName = vaultSettings.mountName().get();
		try {
			this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
		} catch (MountFailedException e) {


@@ 62,8 60,18 @@ public class DokanyVolume extends AbstractVolume {
	}

	@Override
	public void unmount() {
		mount.close();
	public void unmount() throws VolumeException {
		try {
			mount.unmount();
		} catch (IllegalStateException e) {
			throw new VolumeException("Unmount Failed.", e);
		}
		cleanupMountPoint();
	}

	@Override
	public void unmountForced() {
		mount.close(); //TODO: with next dokany-nio-release, change this to unmountForced()
		cleanupMountPoint();
	}


M main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java => main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +3 -4
@@ 1,6 1,5 @@
package org.cryptomator.ui.addvaultwizard;

import com.tobiasdiez.easybind.EasyBind;
import dagger.Lazy;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;


@@ 84,11 83,11 @@ public class CreateNewVaultLocationController implements FxController {
	public void initialize() {
		predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
		usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
		EasyBind.subscribe(vaultPath, this::vaultPathDidChange);
		vaultPath.addListener(this::vaultPathDidChange);
	}

	private void vaultPathDidChange(Path newValue) {
		if (newValue != null && !Files.notExists(newValue)) {
	private void vaultPathDidChange(@SuppressWarnings("unused") ObservableValue<? extends Path> observable, @SuppressWarnings("unused") Path oldValue, Path newValue) {
		if (!Files.notExists(newValue)) {
			warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
		} else {
			warningText.set(null);

M main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java => main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +6 -7
@@ 1,6 1,5 @@
package org.cryptomator.ui.fxapp;

import com.tobiasdiez.easybind.EasyBind;
import dagger.Lazy;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;


@@ 41,9 40,9 @@ public class FxApplication extends Application {
	private final Settings settings;
	private final Lazy<MainWindowComponent> mainWindow;
	private final Lazy<PreferencesComponent> preferencesWindow;
	private final Lazy<QuitComponent> quitWindow;
	private final Provider<UnlockComponent.Builder> unlockWindowBuilderProvider;
	private final Provider<LockComponent.Builder> lockWindowBuilderProvider;
	private final Provider<QuitComponent.Builder> quitWindowBuilderProvider;
	private final Optional<TrayIntegrationProvider> trayIntegration;
	private final Optional<UiAppearanceProvider> appearanceProvider;
	private final VaultService vaultService;


@@ 52,13 51,13 @@ public class FxApplication extends Application {
	private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;

	@Inject
	FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<LockComponent.Builder> lockWindowBuilderProvider, Provider<QuitComponent.Builder> quitWindowBuilderProvider, Optional<TrayIntegrationProvider> trayIntegration, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet<Stage> visibleStages) {
	FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<LockComponent.Builder> lockWindowBuilderProvider, Lazy<QuitComponent> quitWindow, Optional<TrayIntegrationProvider> trayIntegration, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet<Stage> visibleStages) {
		this.settings = settings;
		this.mainWindow = mainWindow;
		this.preferencesWindow = preferencesWindow;
		this.unlockWindowBuilderProvider = unlockWindowBuilderProvider;
		this.lockWindowBuilderProvider = lockWindowBuilderProvider;
		this.quitWindowBuilderProvider = quitWindowBuilderProvider;
		this.quitWindow = quitWindow;
		this.trayIntegration = trayIntegration;
		this.appearanceProvider = appearanceProvider;
		this.vaultService = vaultService;


@@ 70,7 69,7 @@ public class FxApplication extends Application {
		LOG.trace("FxApplication.start()");
		Platform.setImplicitExit(false);

		EasyBind.subscribe(hasVisibleStages, this::hasVisibleStagesChanged);
		hasVisibleStages.addListener(this::hasVisibleStagesChanged);

		settings.theme().addListener(this::appThemeChanged);
		loadSelectedStyleSheet(settings.theme().get());


@@ 81,7 80,7 @@ public class FxApplication extends Application {
		throw new UnsupportedOperationException("Use start() instead.");
	}

	private void hasVisibleStagesChanged(boolean newValue) {
	private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
		if (newValue) {
			trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
		} else {


@@ 119,7 118,7 @@ public class FxApplication extends Application {

	public void showQuitWindow(QuitResponse response) {
		Platform.runLater(() -> {
			quitWindowBuilderProvider.get().quitResponse(response).build().showQuitWindow();
			quitWindow.get().showQuitWindow(response);
			LOG.debug("Showing QuitWindow");
		});
	}

M main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java => main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +4 -0
@@ 89,4 89,8 @@ abstract class FxApplicationModule {
		return builder.build();
	}

	@Provides
	static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
		return builder.build();
	}
}

M main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java => main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +6 -5
@@ 1,6 1,5 @@
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.addvaultwizard.AddVaultWizardComponent;


@@ 13,6 12,7 @@ import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;


@@ 41,7 41,7 @@ public class VaultListController implements FxController {
		this.removeVault = removeVault;
		this.noVaultSelected = selectedVault.isNull();
		this.emptyVaultList = Bindings.isEmpty(vaults);
		EasyBind.subscribe(selectedVault, this::selectedVaultDidChange);
		selectedVault.addListener(this::selectedVaultDidChange);
	}

	public void initialize() {


@@ 58,10 58,11 @@ public class VaultListController implements FxController {
		});
	}

	private void selectedVaultDidChange(Vault newValue) {
		if (newValue != null) {
			VaultListManager.redetermineVaultState(newValue);
	private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue<? extends Vault> observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) {
		if (newValue == null) {
			return;
		}
		VaultListManager.redetermineVaultState(newValue);
	}

	@FXML

M main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java => main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java +11 -0
@@ 1,5 1,6 @@
package org.cryptomator.ui.preferences;

import com.google.common.base.CharMatcher;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;


@@ 10,6 11,7 @@ import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;

@PreferencesScoped
public class DonationKeyPreferencesController implements FxController {


@@ 32,6 34,15 @@ public class DonationKeyPreferencesController implements FxController {
	public void initialize() {
		donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
		donationKeyField.textProperty().addListener(this::registrationKeyChanged);
		donationKeyField.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
	}

	private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) {
		if (change.isContentChange()) {
			var strippedText = CharMatcher.whitespace().removeFrom(change.getText());
			change.setText(strippedText);
		}
		return change;
	}

	private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {

M main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java => main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java +4 -5
@@ 5,7 5,6 @@
 *******************************************************************************/
package org.cryptomator.ui.quit;

import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.ui.common.FxmlFile;


@@ 25,7 24,10 @@ public interface QuitComponent {
	@FxmlScene(FxmlFile.QUIT)
	Lazy<Scene> scene();

	default Stage showQuitWindow() {
	QuitController controller();

	default Stage showQuitWindow(QuitResponse response) {
		controller().updateQuitRequest(response);
		Stage stage = window();
		stage.setScene(scene().get());
		stage.show();


@@ 36,9 38,6 @@ public interface QuitComponent {
	@Subcomponent.Builder
	interface Builder {

		@BindsInstance
		Builder quitResponse(QuitResponse response);

		QuitComponent build();
	}


M main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java => main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java +24 -8
@@ 16,6 16,8 @@ import javafx.stage.Stage;
import java.awt.desktop.QuitResponse;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@QuitScoped


@@ 24,26 26,40 @@ public class QuitController implements FxController {
	private static final Logger LOG = LoggerFactory.getLogger(QuitController.class);

	private final Stage window;
	private final QuitResponse response;
	private final ObservableList<Vault> unlockedVaults;
	private final ExecutorService executorService;
	private final VaultService vaultService;
	private final AtomicReference<QuitResponse> quitResponse = new AtomicReference<>();
	public Button lockAndQuitButton;

	@Inject
	QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService) {
	QuitController(@QuitWindow Stage window, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService) {
		this.window = window;
		this.response = response;
		this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
		this.executorService = executorService;
		this.vaultService = vaultService;
		window.setOnCloseRequest(windowEvent -> cancel());
	}

	public void updateQuitRequest(QuitResponse newResponse) {
		var oldResponse = quitResponse.getAndSet(newResponse);
		if (oldResponse != null) {
			oldResponse.cancelQuit();
		}
	}

	private void respondToQuitRequest(Consumer<QuitResponse> action) {
		var response = quitResponse.getAndSet(null);
		if (response != null) {
			action.accept(response);
		}
	}

	@FXML
	public void cancel() {
		LOG.info("Quitting application canceled by user.");
		window.close();
		response.cancelQuit();
		respondToQuitRequest(QuitResponse::cancelQuit);
	}

	@FXML


@@ 56,16 72,16 @@ public class QuitController implements FxController {
			LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayName).collect(Collectors.joining(", ")));
			if (unlockedVaults.isEmpty()) {
				window.close();
				response.performQuit();
				respondToQuitRequest(QuitResponse::performQuit);
			}
		});
		lockAllTask.setOnFailed(evt -> {
			LOG.warn("Locking failed", lockAllTask.getException());
			lockAndQuitButton.setDisable(false);
			lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
			// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
			// see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
			response.cancelQuit();
			// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!) (see https://github.com/cryptomator/cryptomator/pull/1416)
			window.close();
			respondToQuitRequest(QuitResponse::cancelQuit);
		});
		executorService.execute(lockAllTask);
	}

M main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java => main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java +1 -1
@@ 43,7 43,7 @@ abstract class QuitModule {
	@Provides
	@FxmlScene(FxmlFile.QUIT)
	@QuitScoped
	static Scene provideUnlockScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) {
	static Scene provideQuitScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) {
		return fxmlLoaders.createScene("/fxml/quit.fxml");
	}


M main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java => main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java +2 -2
@@ 39,7 39,7 @@ public class GeneralVaultOptionsController implements FxController {
	public void initialize() {
		vaultName.textProperty().set(vault.getVaultSettings().displayName().get());
		vaultName.focusedProperty().addListener(this::trimVaultNameOnFocusLoss);
		vaultName.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
		vaultName.setTextFormatter(new TextFormatter<>(this::removeWhitespaces));
		unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup());
		actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values());
		actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock());


@@ 53,7 53,7 @@ public class GeneralVaultOptionsController implements FxController {
		}
	}

	private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) {
	private TextFormatter.Change removeWhitespaces(TextFormatter.Change change) {
		if (change.isContentChange() && change.getControlNewText().length() > VAULTNAME_TRUNCATE_THRESHOLD) {
			return null; // reject any change that would lead to a text exceeding threshold
		} else {