diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 0ea231bdd..2671bcae1 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -13,6 +13,7 @@ import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvid import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider; +import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; import org.cryptomator.logging.LogbackConfiguratorFactory; @@ -20,6 +21,7 @@ import org.cryptomator.networking.SSLContextProvider; import org.cryptomator.networking.SSLContextWithMacKeychain; import org.cryptomator.networking.SSLContextWithPKCS12TrustStore; import org.cryptomator.networking.SSLContextWithWindowsCertStore; +import org.cryptomator.ui.fxapp.JfxRevealPathService; import org.cryptomator.ui.fxapp.JfxUiAppearanceProvider; import org.cryptomator.ui.traymenu.AwtTrayMenuController; @@ -64,6 +66,7 @@ open module org.cryptomator.desktop { uses org.cryptomator.event.NotificationHandler; provides UiAppearanceProvider with JfxUiAppearanceProvider; + provides RevealPathService with JfxRevealPathService; provides TrayMenuController with AwtTrayMenuController; provides Configurator with LogbackConfiguratorFactory; provides SSLContextProvider with SSLContextWithWindowsCertStore, SSLContextWithMacKeychain, SSLContextWithPKCS12TrustStore; diff --git a/src/main/java/org/cryptomator/common/CommonsModule.java b/src/main/java/org/cryptomator/common/CommonsModule.java index 739c358a9..bcc5709af 100644 --- a/src/main/java/org/cryptomator/common/CommonsModule.java +++ b/src/main/java/org/cryptomator/common/CommonsModule.java @@ -22,8 +22,6 @@ import javax.inject.Named; import javax.inject.Singleton; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.Comparator; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.SynchronousQueue; @@ -76,8 +74,8 @@ public abstract class CommonsModule { @Provides @Singleton - static Optional provideRevealPathService() { - return RevealPathService.get().findFirst(); + static RevealPathService provideRevealPathService() { + return RevealPathService.get().findFirst().orElseThrow(); } diff --git a/src/main/java/org/cryptomator/ui/common/VaultService.java b/src/main/java/org/cryptomator/ui/common/VaultService.java index 95129f6ee..d02064caa 100644 --- a/src/main/java/org/cryptomator/ui/common/VaultService.java +++ b/src/main/java/org/cryptomator/ui/common/VaultService.java @@ -1,17 +1,16 @@ package org.cryptomator.ui.common; -import dagger.Lazy; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.integrations.mount.Mountpoint; import org.cryptomator.integrations.mount.UnmountFailedException; +import org.cryptomator.integrations.revealpath.RevealFailedException; +import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.ui.fxapp.FxApplicationScoped; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.application.Application; -import javafx.application.HostServices; import javafx.concurrent.Task; import javafx.stage.Stage; import java.io.IOException; @@ -28,12 +27,12 @@ public class VaultService { private static final Logger LOG = LoggerFactory.getLogger(VaultService.class); - private final Lazy application; + private final RevealPathService revealPathService; private final ExecutorService executorService; @Inject - public VaultService(Lazy application, ExecutorService executorService) { - this.application = application; + public VaultService(RevealPathService revealPathService, ExecutorService executorService) { + this.revealPathService = revealPathService; this.executorService = executorService; } @@ -47,9 +46,9 @@ public class VaultService { * @param vault The vault to reveal */ public Task createRevealTask(Vault vault) { - Task task = new RevealVaultTask(vault, application.get().getHostServices()); - task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayName())); - task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), evt.getSource().getException())); + Task task = new RevealVaultTask(vault, revealPathService); + task.setOnSucceeded(_ -> LOG.info("Revealed {}", vault.getDisplayName())); + task.setOnFailed(evt -> LOG.warn("Failed to reveal {}", vault.getDisplayName(), evt.getSource().getException())); return task; } @@ -110,19 +109,18 @@ public class VaultService { private static class RevealVaultTask extends Task { private final Vault vault; - private final HostServices hostServices; + private final RevealPathService rs; - public RevealVaultTask(Vault vault, HostServices hostServices) { + public RevealVaultTask(Vault vault, RevealPathService revealPathService) { this.vault = vault; - this.hostServices = hostServices; - setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), getException())); + this.rs = revealPathService; } @Override - protected Vault call() { + protected Vault call() throws RevealFailedException { switch (vault.getMountPoint()) { case null -> LOG.warn("Not currently mounted"); - case Mountpoint.WithPath m -> hostServices.showDocument(m.uri().toString()); + case Mountpoint.WithPath m -> rs.reveal(m.path()); case Mountpoint.WithUri m -> LOG.info("Vault mounted at {}", m.uri()); // TODO show in UI? } return vault; diff --git a/src/main/java/org/cryptomator/ui/eventview/EventListCellController.java b/src/main/java/org/cryptomator/ui/eventview/EventListCellController.java index 2327cc262..46bebbdb8 100644 --- a/src/main/java/org/cryptomator/ui/eventview/EventListCellController.java +++ b/src/main/java/org/cryptomator/ui/eventview/EventListCellController.java @@ -2,18 +2,17 @@ package org.cryptomator.ui.eventview; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Constants; -import org.cryptomator.cryptofs.event.FileIsInUseEvent; -import org.cryptomator.event.FSEventBucket; -import org.cryptomator.event.FSEventBucketContent; -import org.cryptomator.event.FileSystemEventAggregator; import org.cryptomator.common.Nullable; import org.cryptomator.common.ObservableUtil; -import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.event.BrokenDirFileEvent; import org.cryptomator.cryptofs.event.BrokenFileNodeEvent; import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent; import org.cryptomator.cryptofs.event.ConflictResolvedEvent; import org.cryptomator.cryptofs.event.DecryptionFailedEvent; +import org.cryptomator.cryptofs.event.FileIsInUseEvent; +import org.cryptomator.event.FSEventBucket; +import org.cryptomator.event.FSEventBucketContent; +import org.cryptomator.event.FileSystemEventAggregator; import org.cryptomator.integrations.revealpath.RevealFailedException; import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.ui.common.FxController; @@ -46,7 +45,6 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.Map; -import java.util.Optional; import java.util.ResourceBundle; import java.util.function.Function; @@ -57,7 +55,6 @@ public class EventListCellController implements FxController { private static final DateTimeFormatter LOCAL_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault()); private final FileSystemEventAggregator fileSystemEventAggregator; - @Nullable private final RevealPathService revealService; private final ResourceBundle resourceBundle; private final ObjectProperty> eventEntry; @@ -82,15 +79,17 @@ public class EventListCellController implements FxController { Button eventActionsButton; @Inject - public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, Optional revealService, ResourceBundle resourceBundle) { + public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, + RevealPathService revealService, + ResourceBundle resourceBundle) { this.fileSystemEventAggregator = fileSystemEventAggregator; - this.revealService = revealService.orElseGet(() -> null); + this.revealService = revealService; this.resourceBundle = resourceBundle; this.eventEntry = new SimpleObjectProperty<>(null); this.eventMessage = new SimpleStringProperty(); this.eventDescription = new SimpleStringProperty(); this.eventIcon = new SimpleObjectProperty<>(); - this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1? "" : "("+ e.getValue().count() +")", ""); + this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1 ? "" : "(" + e.getValue().count() + ")", ""); this.vaultUnlocked = ObservableUtil.mapWithDefault(eventEntry.flatMap(e -> e.getKey().vault().unlockedProperty()), Function.identity(), false); this.readableTime = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_TIME_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), ""); this.readableDate = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_DATE_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), ""); @@ -136,13 +135,8 @@ public class EventListCellController implements FxController { eventMessage.setValue(resourceBundle.getString("eventView.entry.inUse.message")); var indexFileName = fiiue.cleartextPath().lastIndexOf("/"); eventDescription.setValue(fiiue.cleartextPath().substring(indexFileName + 1)); - if (revealService != null) { - addLocalizedAction("eventView.entry.inUse.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(fiiue.cleartextPath()))); - addLocalizedAction("eventView.entry.inUse.showEncrypted", () -> reveal(revealService, fiiue.ciphertextPath())); - } else { - addLocalizedAction("eventView.entry.inUse.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(fiiue.cleartextPath()).toString())); - addLocalizedAction("eventView.entry.inUse.copyEncrypted", () -> copyToClipboard(fiiue.ciphertextPath().toString())); - } + addLocalizedAction("eventView.entry.inUse.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(fiiue.cleartextPath()))); + addLocalizedAction("eventView.entry.inUse.showEncrypted", () -> reveal(revealService, fiiue.ciphertextPath())); var userAndDevice = fiiue.owner().split(Constants.HUB_USER_DEVICE_SEPARATOR); var user = userAndDevice[0]; @@ -156,11 +150,7 @@ public class EventListCellController implements FxController { eventIcon.setValue(FontAwesome5Icon.TIMES); eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message")); eventDescription.setValue(bfe.ciphertextPath().getFileName().toString()); - if (revealService != null) { - addLocalizedAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, bfe.ciphertextPath())); - } else { - addLocalizedAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(bfe.ciphertextPath().toString())); - } + addLocalizedAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, bfe.ciphertextPath())); addLocalizedAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString())); } @@ -168,46 +158,29 @@ public class EventListCellController implements FxController { eventIcon.setValue(FontAwesome5Icon.CHECK); eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message")); eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString()); - if (revealService != null) { - addLocalizedAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath()))); - } else { - addLocalizedAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString())); - } + addLocalizedAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath()))); } private void adjustToConflictEvent(ConflictResolutionFailedEvent cfe) { eventIcon.setValue(FontAwesome5Icon.COMPRESS_ALT); eventMessage.setValue(resourceBundle.getString("eventView.entry.conflict.message")); eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString()); - if (revealService != null) { - addLocalizedAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath()))); - addLocalizedAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath())); - } else { - addLocalizedAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString())); - addLocalizedAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString())); - } + addLocalizedAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath()))); + addLocalizedAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath())); } private void adjustToDecryptionFailedEvent(DecryptionFailedEvent dfe) { eventIcon.setValue(FontAwesome5Icon.BAN); eventMessage.setValue(resourceBundle.getString("eventView.entry.decryptionFailed.message")); eventDescription.setValue(dfe.ciphertextPath().getFileName().toString()); - if (revealService != null) { - addLocalizedAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath())); - } else { - addLocalizedAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString())); - } + addLocalizedAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath())); } private void adjustToBrokenDirFileEvent(BrokenDirFileEvent bde) { eventIcon.setValue(FontAwesome5Icon.TIMES); eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenDirFile.message")); eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString()); - if (revealService != null) { - addLocalizedAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath())); - } else { - addLocalizedAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString())); - } + addLocalizedAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath())); } private void addLocalizedAction(String localizationKey, Runnable action) { @@ -270,7 +243,7 @@ public class EventListCellController implements FxController { } var mountPoint = v.getMountPoint().uri().getPath(); - if(SystemUtils.IS_OS_WINDOWS) { + if (SystemUtils.IS_OS_WINDOWS) { mountPoint = mountPoint.substring(1); //strip away any leading "/", otherwise there are errors } return Path.of(mountPoint, vaultInternalPath.substring(1)); //vaultPaths are always absolute diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 7fb5b523b..a6ae45efc 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -10,16 +10,20 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; +import javafx.application.Application; import javafx.application.Platform; import java.time.Duration; import java.time.Instant; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; @FxApplicationScoped public class FxApplication { private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); + static final AtomicReference INSTANCE = new AtomicReference<>(); + private final long startupTime; private final Environment environment; private final Settings settings; @@ -33,7 +37,8 @@ public class FxApplication { private final FxNotificationManager notificationManager; @Inject - FxApplication(@Named("startupTime") long startupTime, // + FxApplication(Application fxApp, + @Named("startupTime") long startupTime, // Environment environment, // Settings settings, // AppLaunchEventHandler launchEventHandler, // @@ -55,6 +60,8 @@ public class FxApplication { this.autoUnlocker = autoUnlocker; this.fxFSEventList = fxFSEventList; this.notificationManager = notificationManager; + + INSTANCE.set(fxApp); } public void start() { diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxRevealPathService.java b/src/main/java/org/cryptomator/ui/fxapp/JfxRevealPathService.java new file mode 100644 index 000000000..1271f42e8 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/JfxRevealPathService.java @@ -0,0 +1,37 @@ +package org.cryptomator.ui.fxapp; + +import org.cryptomator.integrations.common.DisplayName; +import org.cryptomator.integrations.common.OperatingSystem; +import org.cryptomator.integrations.common.Priority; +import org.cryptomator.integrations.revealpath.RevealFailedException; +import org.cryptomator.integrations.revealpath.RevealPathService; + +import java.nio.file.Path; + +/** + * A {@link RevealPathService} service implementation using the JavaFX {@link javafx.application.HostServices#showDocument(String)} to reveal documents. + *

+ * Internally the HostServices class uses GTK on Linux. + * + * @implNote {@link #reveal(Path)} only succeeds when the class {@link FxApplication} is initialized. + */ +@DisplayName("JavaFX HostServices (GTK)") +@OperatingSystem(OperatingSystem.Value.LINUX) +@Priority(10) +public class JfxRevealPathService implements RevealPathService { + + @Override + public void reveal(Path p) throws RevealFailedException { + var fxApp = FxApplication.INSTANCE.get(); + if (fxApp != null) { + fxApp.getHostServices().showDocument(p.toUri().toString()); + } else { + throw new RevealFailedException("JavaFX Application not initialized"); + } + } + + @Override + public boolean isSupported() { + return true; + } +} diff --git a/src/main/java/org/cryptomator/ui/health/ReportWriter.java b/src/main/java/org/cryptomator/ui/health/ReportWriter.java index 18b785e91..a61aab868 100644 --- a/src/main/java/org/cryptomator/ui/health/ReportWriter.java +++ b/src/main/java/org/cryptomator/ui/health/ReportWriter.java @@ -4,9 +4,12 @@ import com.google.common.base.Throwables; import org.cryptomator.common.Environment; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.integrations.revealpath.RevealFailedException; +import org.cryptomator.integrations.revealpath.RevealPathService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.application.Application; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; @@ -25,6 +28,7 @@ import java.util.stream.Collectors; @HealthCheckScoped public class ReportWriter { + private static final Logger LOG = LoggerFactory.getLogger(ReportWriter.class); private static final String REPORT_HEADER = """ ******************************************* * Cryptomator Vault Health Report * @@ -43,14 +47,14 @@ public class ReportWriter { private final Vault vault; private final VaultConfig vaultConfig; - private final Application application; + private final RevealPathService revealPathService; private final Path exportDestination; @Inject - public ReportWriter(@HealthCheckWindow Vault vault, AtomicReference vaultConfigRef, Application application, Environment env) { + public ReportWriter(@HealthCheckWindow Vault vault, AtomicReference vaultConfigRef, RevealPathService revealPathService, Environment env) { this.vault = vault; this.vaultConfig = Objects.requireNonNull(vaultConfigRef.get()); - this.application = application; + this.revealPathService = revealPathService; this.exportDestination = env.getLogDir().orElse(Path.of(System.getProperty("user.home"))).resolve("healthReport_" + vault.getDisplayName() + "_" + TIME_STAMP.format(Instant.now()) + ".log"); } @@ -92,7 +96,11 @@ public class ReportWriter { } private void reveal() { - application.getHostServices().showDocument(exportDestination.getParent().toUri().toString()); + try { + revealPathService.reveal(exportDestination.getParent()); + } catch (RevealFailedException e) { + LOG.warn("Failed to reveal export destination location of report", e); + } } } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java index be4f7f78c..7151572a4 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java @@ -2,14 +2,17 @@ package org.cryptomator.ui.mainwindow; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; +import org.cryptomator.integrations.revealpath.RevealFailedException; +import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.ui.common.Animations; import org.cryptomator.ui.common.AutoAnimator; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.controls.FontAwesome5Icon; import org.cryptomator.ui.controls.FontAwesome5IconView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.application.Application; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; @@ -19,10 +22,12 @@ import javafx.fxml.FXML; @MainWindowScoped public class VaultDetailController implements FxController { + private static final Logger LOG = LoggerFactory.getLogger(VaultDetailController.class); + private final ReadOnlyObjectProperty vault; - private final Application application; private final ObservableValue glyph; private final BooleanBinding anyVaultSelected; + private final RevealPathService revealPathService; private AutoAnimator spinAnimation; @@ -31,11 +36,11 @@ public class VaultDetailController implements FxController { @Inject - VaultDetailController(ObjectProperty vault, Application application) { + VaultDetailController(ObjectProperty vault, RevealPathService revealPathService) { this.vault = vault; - this.application = application; this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState); this.anyVaultSelected = vault.isNotNull(); + this.revealPathService = revealPathService; } public void initialize() { @@ -61,7 +66,11 @@ public class VaultDetailController implements FxController { @FXML public void revealStorageLocation() { - application.getHostServices().showDocument(vault.get().getPath().toUri().toString()); + try { + revealPathService.reveal(vault.get().getPath()); + } catch (RevealFailedException e) { + LOG.warn("Failed to reveal vault storage location", e); + } } /* Observable Properties */ diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java index 42a8fda7e..742e3baaa 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java @@ -21,7 +21,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; @@ -31,7 +30,6 @@ import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; -import javafx.scene.input.DataFormat; import javafx.scene.input.DragEvent; import javafx.scene.input.TransferMode; import javafx.stage.FileChooser; @@ -40,12 +38,8 @@ 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; @@ -60,7 +54,7 @@ public class VaultDetailUnlockedController implements FxController { private final VaultService vaultService; private final WrongFileAlertComponent.Builder wrongFileAlert; private final Stage mainWindow; - private final Optional revealPathService; + private final RevealPathService revealPathService; private final DecryptNameComponent.Factory decryptNameWindowFactory; private final ResourceBundle resourceBundle; private final LoadingCache vaultStats; @@ -84,7 +78,7 @@ public class VaultDetailUnlockedController implements FxController { VaultStatisticsComponent.Builder vaultStatsBuilder, // WrongFileAlertComponent.Builder wrongFileAlert, // @MainWindow Stage mainWindow, // - Optional revealPathService, // + RevealPathService revealPathService, // DecryptNameComponent.Factory decryptNameWindowFactory, // ResourceBundle resourceBundle) { this.vault = vault; @@ -111,7 +105,7 @@ public class VaultDetailUnlockedController implements FxController { public void initialize() { revealEncryptedDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverLocateEncrypted)); - revealEncryptedDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCiphertextPath, this::revealOrCopyPaths)); + revealEncryptedDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCiphertextPath, this::revealPaths)); revealEncryptedDropZone.setOnDragExited(_ -> draggingOverLocateEncrypted.setValue(false)); decryptNameDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverDecryptName)); @@ -156,7 +150,7 @@ public class VaultDetailUnlockedController implements FxController { if (cleartextFile != null) { var ciphertextPath = getCiphertextPath(cleartextFile.toPath()); if (ciphertextPath != null) { - revealOrCopyPaths(List.of(ciphertextPath)); + revealPaths(List.of(ciphertextPath)); } } } @@ -188,34 +182,18 @@ public class VaultDetailUnlockedController implements FxController { } } - private void revealOrCopyPaths(List paths) { - revealPathService.ifPresentOrElse(svc -> revealPaths(svc, paths), () -> { - LOG.warn("No service provider to reveal files found."); - copyPathsToClipboard(paths); - }); - } - - private void revealPaths(RevealPathService service, List paths) { + private void revealPaths(List paths) { paths.forEach(path -> { try { LOG.debug("Revealing {}", path); - service.reveal(path); + revealPathService.reveal(path); } catch (RevealFailedException e) { + //TODO: show popup in ui LOG.error("Revealing ciphertext file failed.", e); } }); } - private void copyPathsToClipboard(List paths) { - StringBuilder clipboardString = new StringBuilder(); - paths.forEach(p -> clipboardString.append(p.toString()).append("\n")); - Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, clipboardString.toString())); - ciphertextPathsCopied.setValue(true); - CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> { - ciphertextPathsCopied.set(false); - }); - } - private VaultStatisticsComponent buildVaultStats(Vault vault) { return vaultStatsBuilder.vault(vault).build(); } diff --git a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java index 088bad0dd..c59d03846 100644 --- a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java @@ -10,13 +10,14 @@ import org.cryptomator.integrations.common.NamedServiceProvider; import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.integrations.keychain.KeychainAccessProvider; import org.cryptomator.integrations.quickaccess.QuickAccessService; +import org.cryptomator.integrations.revealpath.RevealFailedException; +import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.application.Application; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.fxml.FXML; @@ -41,7 +42,7 @@ public class GeneralPreferencesController implements FxController { private final Settings settings; private final Optional autoStartProvider; private final List quickAccessServices; - private final Application application; + private final RevealPathService revealPathService; private final Environment environment; private final List keychainAccessProviders; private final KeychainManager keychain; @@ -60,9 +61,15 @@ public class GeneralPreferencesController implements FxController { private CompletionStage keychainMigrations = CompletableFuture.completedFuture(null); @Inject - GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional autoStartProvider, // - List keychainAccessProviders, KeychainManager keychain, Application application, // - Environment environment, FxApplicationWindows appWindows, ExecutorService backgroundExecutor) { + GeneralPreferencesController(@PreferencesWindow Stage window, // + Settings settings, // + Optional autoStartProvider, // + List keychainAccessProviders, // + KeychainManager keychain, // + RevealPathService revealPathService, // + Environment environment, // + FxApplicationWindows appWindows, // + ExecutorService backgroundExecutor) { this.window = window; this.settings = settings; this.autoStartProvider = autoStartProvider; @@ -70,7 +77,7 @@ public class GeneralPreferencesController implements FxController { this.keychain = keychain; this.backgroundExecutor = backgroundExecutor; this.quickAccessServices = QuickAccessService.get().toList(); - this.application = application; + this.revealPathService = revealPathService; this.environment = environment; this.appWindows = appWindows; } @@ -148,7 +155,11 @@ public class GeneralPreferencesController implements FxController { @FXML public void showLogfileDirectory() { - environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString())); + try { + revealPathService.reveal(environment.getLogDir().orElseThrow()); + } catch (RevealFailedException e) { + LOG.warn("Failed to reveal log files directory.", e); + } } /* Helper classes */ @@ -196,4 +207,5 @@ public class GeneralPreferencesController implements FxController { } } } + } diff --git a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java index 89c03800e..c21630bde 100644 --- a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java @@ -3,18 +3,19 @@ package org.cryptomator.ui.preferences; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.integrations.revealpath.RevealFailedException; +import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.integrations.update.UpdateStep; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.VaultService; -import org.cryptomator.updater.UpdateChecker; import org.cryptomator.updater.FallbackUpdateInfo; +import org.cryptomator.updater.UpdateChecker; import org.cryptomator.updater.UpdateService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.animation.PauseTransition; -import javafx.application.Application; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -49,7 +50,7 @@ public class UpdatesPreferencesController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(UpdatesPreferencesController.class); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault()); - private final Application application; + private final RevealPathService revealPathService; private final Environment environment; private final ResourceBundle resourceBundle; private final Settings settings; @@ -72,8 +73,8 @@ public class UpdatesPreferencesController implements FxController { public CheckBox checkForUpdatesCheckbox; @Inject - UpdatesPreferencesController(Application application, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker, ObservableList vaults, VaultService vaultService) { - this.application = application; + UpdatesPreferencesController(RevealPathService revealPathService, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker, ObservableList vaults, VaultService vaultService) { + this.revealPathService = revealPathService; this.environment = environment; this.resourceBundle = resourceBundle; this.settings = settings; @@ -106,9 +107,14 @@ public class UpdatesPreferencesController implements FxController { updateService.setOnFailed(this::updateFailed); } + @FXML public void showLogfileDirectory() { - environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString())); + try { + revealPathService.reveal(environment.getLogDir().orElseThrow()); + } catch (RevealFailedException e) { + LOG.warn("Failed to reveal log files directory.", e); + } } @FXML diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 8c5e062f8..759b80155 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -696,27 +696,19 @@ eventView.cell.actionsButton.tooltip=Event actions eventView.entry.vaultLocked.description=Unlock "%s" for details eventView.entry.conflictResolved.message=Resolved conflict eventView.entry.conflictResolved.showDecrypted=Show decrypted file -eventView.entry.conflictResolved.copyDecrypted=Copy decrypted path eventView.entry.conflict.message=Conflict resolution failed eventView.entry.conflict.showDecrypted=Show decrypted, original file -eventView.entry.conflict.copyDecrypted=Copy decrypted, original path eventView.entry.conflict.showEncrypted=Show conflicting, encrypted file -eventView.entry.conflict.copyEncrypted=Copy conflicting, encrypted path eventView.entry.decryptionFailed.message=Decryption failed eventView.entry.decryptionFailed.showEncrypted=Show encrypted file -eventView.entry.decryptionFailed.copyEncrypted=Copy encrypted path eventView.entry.brokenDirFile.message=Broken directory link eventView.entry.brokenDirFile.showEncrypted=Show broken, encrypted link -eventView.entry.brokenDirFile.copyEncrypted=Copy path of broken link eventView.entry.brokenFileNode.message=Broken filesystem node eventView.entry.brokenFileNode.showEncrypted=Show broken, encrypted node -eventView.entry.brokenFileNode.copyEncrypted=Copy path of broken, encrypted node eventView.entry.brokenFileNode.copyDecrypted=Copy decrypted path eventView.entry.inUse.message=File in use eventView.entry.inUse.showDecrypted=Show decrypted file -eventView.entry.inUse.copyDecrypted=Copy decrypted path eventView.entry.inUse.showEncrypted=Show encrypted file -eventView.entry.inUse.copyEncrypted=Copy encrypted path eventView.entry.inUse.copyUserAndDevice=Copy locking user and device name eventView.entry.inUse.ignoreLock=Ignore use status