|
@@ -6,14 +6,13 @@ import com.google.common.cache.CacheLoader;
|
|
|
import com.google.common.cache.LoadingCache;
|
|
|
import com.tobiasdiez.easybind.EasyBind;
|
|
|
import org.apache.commons.lang3.SystemUtils;
|
|
|
+import org.cryptomator.common.Nullable;
|
|
|
import org.cryptomator.common.vaults.Vault;
|
|
|
import org.cryptomator.integrations.mount.Mountpoint;
|
|
|
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
|
|
import org.cryptomator.integrations.revealpath.RevealPathService;
|
|
|
import org.cryptomator.ui.common.FxController;
|
|
|
-import org.cryptomator.ui.common.StageFactory;
|
|
|
import org.cryptomator.ui.common.VaultService;
|
|
|
-import org.cryptomator.ui.dialogs.SimpleDialog;
|
|
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
|
|
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
|
|
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
|
@@ -37,17 +36,19 @@ import javafx.scene.input.DataFormat;
|
|
|
import javafx.scene.input.DragEvent;
|
|
|
import javafx.scene.input.TransferMode;
|
|
|
import javafx.stage.FileChooser;
|
|
|
-import javafx.stage.Popup;
|
|
|
import javafx.stage.Stage;
|
|
|
import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.nio.file.Path;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
import java.util.Optional;
|
|
|
import java.util.ResourceBundle;
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.function.Consumer;
|
|
|
+import java.util.function.Function;
|
|
|
|
|
|
@MainWindowScoped
|
|
|
public class VaultDetailUnlockedController implements FxController {
|
|
@@ -67,11 +68,14 @@ public class VaultDetailUnlockedController implements FxController {
|
|
|
private final ObservableValue<Boolean> accessibleViaPath;
|
|
|
private final ObservableValue<Boolean> accessibleViaUri;
|
|
|
private final ObservableValue<String> mountPoint;
|
|
|
- private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
|
|
+ private final BooleanProperty draggingOverLocateEncrypted = new SimpleBooleanProperty();
|
|
|
+ private final BooleanProperty draggingOverDecryptName = new SimpleBooleanProperty();
|
|
|
private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty();
|
|
|
|
|
|
- //FXML
|
|
|
- public Button dropZone;
|
|
|
+ @FXML
|
|
|
+ public Button revealEncryptedDropZone;
|
|
|
+ @FXML
|
|
|
+ public Button decryptNameDropZone;
|
|
|
|
|
|
@Inject
|
|
|
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, WrongFileAlertComponent.Builder wrongFileAlert, @MainWindow Stage mainWindow, Optional<RevealPathService> revealPathService, ResourceBundle resourceBundle) {
|
|
@@ -97,93 +101,92 @@ public class VaultDetailUnlockedController implements FxController {
|
|
|
}
|
|
|
|
|
|
public void initialize() {
|
|
|
- dropZone.setOnDragEntered(this::handleDragEvent);
|
|
|
- dropZone.setOnDragOver(this::handleDragEvent);
|
|
|
- dropZone.setOnDragDropped(this::handleDragEvent);
|
|
|
- dropZone.setOnDragExited(this::handleDragEvent);
|
|
|
+ revealEncryptedDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverLocateEncrypted));
|
|
|
+ revealEncryptedDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCiphertextPath, this::revealOrCopyPaths));
|
|
|
+ revealEncryptedDropZone.setOnDragExited(_ -> draggingOverLocateEncrypted.setValue(false));
|
|
|
+
|
|
|
+ decryptNameDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverDecryptName));
|
|
|
+ decryptNameDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCleartextName, this::doSomethingWithFileNames));
|
|
|
+ decryptNameDropZone.setOnDragExited(_ -> draggingOverDecryptName.setValue(false));
|
|
|
|
|
|
- EasyBind.includeWhen(dropZone.getStyleClass(), ACTIVE_CLASS, draggingOver);
|
|
|
+ EasyBind.includeWhen(revealEncryptedDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverLocateEncrypted);
|
|
|
+ EasyBind.includeWhen(decryptNameDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverDecryptName);
|
|
|
}
|
|
|
|
|
|
- private void handleDragEvent(DragEvent event) {
|
|
|
- if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
|
|
- if(SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
|
|
|
+ private void handleDragOver(DragEvent event, BooleanProperty prop) {
|
|
|
+ if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
|
|
+ if (SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
|
|
|
event.acceptTransferModes(TransferMode.LINK);
|
|
|
} else {
|
|
|
event.acceptTransferModes(TransferMode.ANY);
|
|
|
}
|
|
|
- draggingOver.set(true);
|
|
|
- } else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
|
|
- List<Path> ciphertextPaths = event.getDragboard().getFiles().stream().map(File::toPath).map(this::getCiphertextPath).flatMap(Optional::stream).toList();
|
|
|
- //TODO: differ between encrypted and decrypted files
|
|
|
- if (ciphertextPaths.isEmpty()) {
|
|
|
+ prop.set(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //TODO: split in two dedicated Methods
|
|
|
+ private <T> void handleDragDropped(DragEvent event, Function<Path, T> computation, Consumer<List<T>> positiveAction) {
|
|
|
+ if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
|
|
+ List<T> objects = event.getDragboard().getFiles().stream().map(File::toPath).map(computation).filter(Objects::nonNull).toList();
|
|
|
+ if (objects.isEmpty()) {
|
|
|
wrongFileAlert.build().showWrongFileAlertWindow();
|
|
|
} else {
|
|
|
- revealOrCopyPaths(ciphertextPaths);
|
|
|
+ positiveAction.accept(objects);
|
|
|
}
|
|
|
- event.setDropCompleted(!ciphertextPaths.isEmpty());
|
|
|
+ event.setDropCompleted(!objects.isEmpty());
|
|
|
event.consume();
|
|
|
- } else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
|
|
- draggingOver.set(false);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
|
|
- return vaultStatsBuilder.vault(vault).build();
|
|
|
- }
|
|
|
-
|
|
|
- @FXML
|
|
|
- public void revealAccessLocation() {
|
|
|
- vaultService.reveal(vault.get());
|
|
|
- }
|
|
|
-
|
|
|
- @FXML
|
|
|
- public void copyMountUri() {
|
|
|
- ClipboardContent clipboardContent = new ClipboardContent();
|
|
|
- clipboardContent.putString(mountPoint.getValue());
|
|
|
- Clipboard.getSystemClipboard().setContent(clipboardContent);
|
|
|
- }
|
|
|
-
|
|
|
- @FXML
|
|
|
- public void lock() {
|
|
|
- appWindows.startLockWorkflow(vault.get(), mainWindow);
|
|
|
- }
|
|
|
-
|
|
|
@FXML
|
|
|
- public void showVaultStatistics() {
|
|
|
- vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
|
|
|
- }
|
|
|
-
|
|
|
- @FXML
|
|
|
- public void chooseFileAndReveal() {
|
|
|
+ public void chooseDecryptedFileAndReveal() {
|
|
|
Preconditions.checkState(accessibleViaPath.getValue());
|
|
|
var fileChooser = new FileChooser();
|
|
|
- fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.decryptedFilePickerTitle"));
|
|
|
+ fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.locateEncrypted.filePickerTitle"));
|
|
|
fileChooser.setInitialDirectory(Path.of(mountPoint.getValue()).toFile());
|
|
|
var cleartextFile = fileChooser.showOpenDialog(mainWindow);
|
|
|
if (cleartextFile != null) {
|
|
|
- var ciphertextPaths = getCiphertextPath(cleartextFile.toPath()).stream().toList();
|
|
|
- revealOrCopyPaths(ciphertextPaths);
|
|
|
+ var ciphertextPath = getCiphertextPath(cleartextFile.toPath());
|
|
|
+ if (ciphertextPath != null) {
|
|
|
+ revealOrCopyPaths(List.of(ciphertextPath));
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@FXML
|
|
|
public void chooseEncryptedFileAndGetName() {
|
|
|
var fileChooser = new FileChooser();
|
|
|
- fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.encryptedFilePickerTitle"));
|
|
|
+ fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.decryptName.filePickerTitle"));
|
|
|
|
|
|
fileChooser.setInitialDirectory(vault.getValue().getPath().toFile());
|
|
|
var ciphertextNode = fileChooser.showOpenDialog(mainWindow);
|
|
|
+ if (ciphertextNode != null) {
|
|
|
+ var nodeName = getCleartextName(ciphertextNode.toPath());
|
|
|
+ Alert dialog;
|
|
|
+ if (nodeName != null) {
|
|
|
+ dialog = new Alert(Alert.AlertType.INFORMATION, "The answer is: %s".formatted(nodeName), ButtonType.OK);
|
|
|
+ } else {
|
|
|
+ dialog = new Alert(Alert.AlertType.ERROR, "Unable to get Name", ButtonType.OK);
|
|
|
+ }
|
|
|
+ dialog.showAndWait();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void doSomethingWithFileNames(List<String> names) {
|
|
|
+ Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, String.join(", ", names)));
|
|
|
+ ciphertextPathsCopied.setValue(true);
|
|
|
+ CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
|
|
|
+ ciphertextPathsCopied.set(false);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Nullable
|
|
|
+ private String getCleartextName(Path ciphertextNode) {
|
|
|
try {
|
|
|
- var nodeName = vault.get().getCleartextName(ciphertextNode.toPath());
|
|
|
- var alert = new Alert(Alert.AlertType.INFORMATION, "The answer is: %s".formatted(nodeName), ButtonType.OK);
|
|
|
- alert.showAndWait();
|
|
|
- //.filter(response -> response == ButtonType.OK)
|
|
|
- //.ifPresent(response -> formatSystem());
|
|
|
- } catch (Exception e) {
|
|
|
+ return vault.get().getCleartextName(ciphertextNode);
|
|
|
+ } catch (IOException e) {
|
|
|
LOG.warn("Failed to decrypt filename for {}", ciphertextNode, e);
|
|
|
- var alert = new Alert(Alert.AlertType.ERROR, "The exception is: %s".formatted(e.getClass()), ButtonType.OK);
|
|
|
- alert.showAndWait();
|
|
|
+ return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -191,16 +194,17 @@ public class VaultDetailUnlockedController implements FxController {
|
|
|
return path.startsWith(Path.of(mountPoint.getValue()));
|
|
|
}
|
|
|
|
|
|
- private Optional<Path> getCiphertextPath(Path path) {
|
|
|
+ @Nullable
|
|
|
+ private Path getCiphertextPath(Path path) {
|
|
|
if (!startsWithVaultAccessPoint(path)) {
|
|
|
- LOG.debug("Path does not start with access point of selected vault: {}", path);
|
|
|
- return Optional.empty();
|
|
|
+ LOG.debug("Path does not start with mount point of selected vault: {}", path);
|
|
|
+ return null;
|
|
|
}
|
|
|
try {
|
|
|
- return Optional.of(vault.get().getCiphertextPath(path));
|
|
|
+ return vault.get().getCiphertextPath(path);
|
|
|
} catch (IOException e) {
|
|
|
LOG.warn("Unable to get ciphertext path from path: {}", path, e);
|
|
|
- return Optional.empty();
|
|
|
+ return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -232,6 +236,32 @@ public class VaultDetailUnlockedController implements FxController {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
|
|
+ return vaultStatsBuilder.vault(vault).build();
|
|
|
+ }
|
|
|
+
|
|
|
+ @FXML
|
|
|
+ public void revealAccessLocation() {
|
|
|
+ vaultService.reveal(vault.get());
|
|
|
+ }
|
|
|
+
|
|
|
+ @FXML
|
|
|
+ public void copyMountUri() {
|
|
|
+ ClipboardContent clipboardContent = new ClipboardContent();
|
|
|
+ clipboardContent.putString(mountPoint.getValue());
|
|
|
+ Clipboard.getSystemClipboard().setContent(clipboardContent);
|
|
|
+ }
|
|
|
+
|
|
|
+ @FXML
|
|
|
+ public void lock() {
|
|
|
+ appWindows.startLockWorkflow(vault.get(), mainWindow);
|
|
|
+ }
|
|
|
+
|
|
|
+ @FXML
|
|
|
+ public void showVaultStatistics() {
|
|
|
+ vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
|
|
|
+ }
|
|
|
+
|
|
|
/* Getter/Setter */
|
|
|
|
|
|
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|