org.cryptomator.ui.controllers.MainController.java Source code

Java tutorial

Introduction

Here is the source code for org.cryptomator.ui.controllers.MainController.java

Source

/*******************************************************************************
 * Copyright (c) 2014, 2016 Sebastian Stenzel
 * This file is licensed under the terms of the MIT license.
 * See the LICENSE.txt file for more info.
 * 
 * Contributors:
 *     Sebastian Stenzel - initial API and implementation
 *     Jean-Nol Charon - confirmation dialog on vault removal
 ******************************************************************************/
package org.cryptomator.ui.controllers;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.VaultFactory;
import org.cryptomator.ui.model.Vaults;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dagger.Lazy;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.Parent;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

@Singleton
public class MainController extends LocalizedFXMLViewController {

    private static final Logger LOG = LoggerFactory.getLogger(MainController.class);

    private final Stage mainWindow;
    private final VaultFactory vaultFactoy;
    private final Lazy<WelcomeController> welcomeController;
    private final Lazy<InitializeController> initializeController;
    private final Lazy<NotFoundController> notFoundController;
    private final Lazy<UpgradeController> upgradeController;
    private final Lazy<UnlockController> unlockController;
    private final Provider<UnlockedController> unlockedControllerProvider;
    private final Lazy<ChangePasswordController> changePasswordController;
    private final Lazy<SettingsController> settingsController;
    private final Lazy<UpgradeStrategies> upgradeStrategies;
    private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
    private final Vaults vaults;
    private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
    private final BooleanExpression isSelectedVaultUnlocked = BooleanBinding
            .booleanExpression(EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty).orElse(false));
    private final BooleanExpression isSelectedVaultValid = BooleanBinding
            .booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false));
    private final BooleanExpression canEditSelectedVault = selectedVault.isNotNull()
            .and(isSelectedVaultUnlocked.not());
    private final BooleanBinding isShowingSettings;
    private final Map<Vault, UnlockedController> unlockedVaults = new HashMap<>();

    @Inject
    public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings,
            VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController,
            Lazy<InitializeController> initializeController, Lazy<NotFoundController> notFoundController,
            Lazy<UpgradeController> upgradeController, Lazy<UnlockController> unlockController,
            Provider<UnlockedController> unlockedControllerProvider,
            Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController,
            Lazy<UpgradeStrategies> upgradeStrategies, Vaults vaults) {
        super(localization);
        this.mainWindow = mainWindow;
        this.vaultFactoy = vaultFactoy;
        this.welcomeController = welcomeController;
        this.initializeController = initializeController;
        this.notFoundController = notFoundController;
        this.upgradeController = upgradeController;
        this.unlockController = unlockController;
        this.unlockedControllerProvider = unlockedControllerProvider;
        this.changePasswordController = changePasswordController;
        this.settingsController = settingsController;
        this.upgradeStrategies = upgradeStrategies;
        this.vaults = vaults;

        // derived bindings:
        this.isShowingSettings = activeController.isEqualTo(settingsController.get());
    }

    @FXML
    private ContextMenu vaultListCellContextMenu;

    @FXML
    private MenuItem changePasswordMenuItem;

    @FXML
    private ContextMenu addVaultContextMenu;

    @FXML
    private HBox rootPane;

    @FXML
    private ListView<Vault> vaultList;

    @FXML
    private ToggleButton addVaultButton;

    @FXML
    private Button removeVaultButton;

    @FXML
    private ToggleButton settingsButton;

    @FXML
    private Pane contentPane;

    @FXML
    private Pane emptyListInstructions;

    @Override
    public void initialize() {
        vaultList.setItems(vaults);
        vaultList.setCellFactory(this::createDirecoryListCell);
        activeController.set(welcomeController.get());
        selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
        removeVaultButton.disableProperty().bind(canEditSelectedVault.not());
        emptyListInstructions.visibleProperty().bind(Bindings.isEmpty(vaults));
        changePasswordMenuItem.visibleProperty().bind(isSelectedVaultValid);

        EasyBind.subscribe(selectedVault, this::selectedVaultDidChange);
        EasyBind.subscribe(activeController, this::activeControllerDidChange);
        EasyBind.subscribe(isShowingSettings, settingsButton::setSelected);
        EasyBind.subscribe(addVaultContextMenu.showingProperty(), addVaultButton::setSelected);
    }

    @Override
    protected URL getFxmlResourceUrl() {
        return getClass().getResource("/fxml/main.fxml");
    }

    private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
        final DirectoryListCell cell = new DirectoryListCell();
        cell.setVaultContextMenu(vaultListCellContextMenu);
        return cell;
    }

    // ****************************************
    // UI Events
    // ****************************************

    @FXML
    private void didClickAddVault(ActionEvent event) {
        if (addVaultContextMenu.isShowing()) {
            addVaultContextMenu.hide();
        } else {
            addVaultContextMenu.show(addVaultButton, Side.BOTTOM, 0.0, 0.0);
        }
    }

    @FXML
    private void didClickCreateNewVault(ActionEvent event) {
        final FileChooser fileChooser = new FileChooser();
        final File file = fileChooser.showSaveDialog(mainWindow);
        if (file == null) {
            return;
        }
        try {
            final Path vaultDir = file.toPath();
            if (!Files.exists(vaultDir)) {
                Files.createDirectory(vaultDir);
            }
            addVault(vaultDir, true);
        } catch (IOException e) {
            LOG.error("Unable to create vault", e);
        }
    }

    @FXML
    private void didClickAddExistingVaults(ActionEvent event) {
        final FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters()
                .add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*" + Vault.VAULT_FILE_EXTENSION));
        final List<File> files = fileChooser.showOpenMultipleDialog(mainWindow);
        if (files != null) {
            for (final File file : files) {
                addVault(file.toPath(), false);
            }
        }
    }

    /**
     * adds the given directory or selects it if it is already in the list of directories.
     * 
     * @param path non-null, writable, existing directory
     */
    public void addVault(final Path path, boolean select) {
        // TODO: `|| !Files.isWritable(path)` is broken on windows. Fix in Java 8u72, see https://bugs.openjdk.java.net/browse/JDK-8034057
        if (path == null) {
            return;
        }

        final Path vaultPath;
        if (path != null && Files.isDirectory(path)) {
            vaultPath = path;
        } else if (path != null && Files.isRegularFile(path)) {
            vaultPath = path.getParent();
        } else {
            return;
        }

        final Vault vault = vaultFactoy.createVault(vaultPath);
        if (!vaults.contains(vault)) {
            vaults.add(vault);
        }
        vaultList.getSelectionModel().select(vault);
    }

    @FXML
    private void didClickRemoveSelectedEntry(ActionEvent e) {
        Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( //
                localization.getString("main.directoryList.remove.confirmation.title"), //
                localization.getString("main.directoryList.remove.confirmation.header"), //
                localization.getString("main.directoryList.remove.confirmation.content"), //
                SystemUtils.IS_OS_MAC_OSX ? ButtonType.CANCEL : ButtonType.OK);

        Optional<ButtonType> choice = confirmDialog.showAndWait();
        if (ButtonType.OK.equals(choice.get())) {
            vaults.remove(selectedVault.get());
            if (vaults.isEmpty()) {
                activeController.set(welcomeController.get());
            }
        }
    }

    @FXML
    private void didClickChangePassword(ActionEvent e) {
        showChangePasswordView();
    }

    @FXML
    private void didClickShowSettings(ActionEvent e) {
        if (settingsController.get().equals(activeController.get())) {
            activeController.set(welcomeController.get());
        } else {
            activeController.set(settingsController.get());
        }
        vaultList.getSelectionModel().clearSelection();
    }

    // ****************************************
    // Binding Listeners
    // ****************************************

    private void activeControllerDidChange(AbstractFXMLViewController newValue) {
        final Parent root = newValue.loadFxml();
        contentPane.getChildren().clear();
        contentPane.getChildren().add(root);
    }

    private void selectedVaultDidChange(Vault newValue) {
        if (newValue == null) {
            return;
        }
        if (newValue.isUnlocked()) {
            this.showUnlockedView(newValue);
        } else if (!newValue.doesVaultDirectoryExist()) {
            this.showNotFoundView();
        } else if (newValue.isValidVaultDirectory()
                && upgradeStrategies.get().getUpgradeStrategy(newValue).isPresent()) {
            this.showUpgradeView();
        } else if (newValue.isValidVaultDirectory()) {
            this.showUnlockView();
        } else {
            this.showInitializeView();
        }
    }

    // ****************************************
    // Public Bindings
    // ****************************************

    public Binding<String> windowTitle() {
        return EasyBind.monadic(selectedVault).flatMap(Vault::name).orElse(localization.getString("app.name"));
    }

    // ****************************************
    // Subcontroller for right panel
    // ****************************************

    private void showNotFoundView() {
        final NotFoundController ctrl = notFoundController.get();
        activeController.set(ctrl);
    }

    private void showInitializeView() {
        final InitializeController ctrl = initializeController.get();
        ctrl.loadFxml();
        ctrl.setVault(selectedVault.get());
        ctrl.setListener(this::didInitialize);
        activeController.set(ctrl);
    }

    public void didInitialize() {
        showUnlockView();
    }

    private void showUpgradeView() {
        final UpgradeController ctrl = upgradeController.get();
        ctrl.loadFxml();
        ctrl.setVault(selectedVault.get());
        ctrl.setListener(this::didUpgrade);
        activeController.set(ctrl);
    }

    public void didUpgrade() {
        showUnlockView();
    }

    private void showUnlockView() {
        final UnlockController ctrl = unlockController.get();
        ctrl.loadFxml();
        ctrl.setVault(selectedVault.get());
        ctrl.setListener(this::didUnlock);
        activeController.set(ctrl);
    }

    public void didUnlock(Vault vault) {
        Platform.setImplicitExit(false);
        if (vault.equals(selectedVault.getValue())) {
            this.showUnlockedView(vault);
        }
    }

    private void showUnlockedView(Vault vault) {
        final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> {
            return unlockedControllerProvider.get();
        });
        ctrl.loadFxml();
        ctrl.setVault(vault);
        ctrl.setListener(this::didLock);
        activeController.set(ctrl);
    }

    public void didLock(UnlockedController ctrl) {
        unlockedVaults.remove(ctrl.getVault());
        showUnlockView();
        if (!vaults.stream().anyMatch(Vault::isUnlocked)) {
            Platform.setImplicitExit(true);
        }
    }

    private void showChangePasswordView() {
        final ChangePasswordController ctrl = changePasswordController.get();
        ctrl.loadFxml();
        ctrl.setVault(selectedVault.get());
        ctrl.setListener(this::didChangePassword);
        activeController.set(ctrl);
    }

    public void didChangePassword() {
        showUnlockView();
    }

}