|
@@ -6,12 +6,14 @@ 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.VaultService;
|
|
|
+import org.cryptomator.ui.decryptname.DecryptNameComponent;
|
|
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
|
|
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
|
|
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
|
@@ -39,10 +41,13 @@ 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 {
|
|
@@ -56,26 +61,39 @@ public class VaultDetailUnlockedController implements FxController {
|
|
|
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
|
|
private final Stage mainWindow;
|
|
|
private final Optional<RevealPathService> revealPathService;
|
|
|
+ private final DecryptNameComponent.Factory decryptNameWindowFactory;
|
|
|
private final ResourceBundle resourceBundle;
|
|
|
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
|
|
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
|
|
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) {
|
|
|
+ public VaultDetailUnlockedController(ObjectProperty<Vault> vault, //
|
|
|
+ FxApplicationWindows appWindows, //
|
|
|
+ VaultService vaultService, //
|
|
|
+ VaultStatisticsComponent.Builder vaultStatsBuilder, //
|
|
|
+ WrongFileAlertComponent.Builder wrongFileAlert, //
|
|
|
+ @MainWindow Stage mainWindow, //
|
|
|
+ Optional<RevealPathService> revealPathService, //
|
|
|
+ DecryptNameComponent.Factory decryptNameWindowFactory, //
|
|
|
+ ResourceBundle resourceBundle) {
|
|
|
this.vault = vault;
|
|
|
this.appWindows = appWindows;
|
|
|
this.vaultService = vaultService;
|
|
|
this.wrongFileAlert = wrongFileAlert;
|
|
|
this.mainWindow = mainWindow;
|
|
|
this.revealPathService = revealPathService;
|
|
|
+ this.decryptNameWindowFactory = decryptNameWindowFactory;
|
|
|
this.resourceBundle = resourceBundle;
|
|
|
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
|
|
this.vaultStatsBuilder = vaultStatsBuilder;
|
|
@@ -92,89 +110,81 @@ 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 -> showDecryptNameWindow(e.getDragboard().getFiles().stream().map(File::toPath).toList()));
|
|
|
+ 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();
|
|
|
- if (ciphertextPaths.isEmpty()) {
|
|
|
+ prop.set(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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.filePickerTitle"));
|
|
|
+ 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 showDecryptNameWindow() {
|
|
|
+ showDecryptNameWindow(List.of());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void showDecryptNameWindow(List<Path> pathsToDecrypt) {
|
|
|
+ decryptNameWindowFactory.create(vault.get(), mainWindow, pathsToDecrypt).showDecryptFileNameWindow();
|
|
|
+ }
|
|
|
+
|
|
|
private boolean startsWithVaultAccessPoint(Path path) {
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -206,6 +216,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() {
|
|
@@ -247,4 +283,6 @@ public class VaultDetailUnlockedController implements FxController {
|
|
|
public boolean isCiphertextPathsCopied() {
|
|
|
return ciphertextPathsCopied.get();
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
+
|