mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
Merge branch 'develop' into release/1.16.0
# Conflicts: # src/main/java/org/cryptomator/common/vaults/Vault.java # src/main/java/org/cryptomator/ui/eventview/EventListCellController.java # src/main/java/org/cryptomator/ui/eventview/EventListCellFactory.java # src/main/java/org/cryptomator/ui/eventview/EventViewController.java # src/main/java/org/cryptomator/ui/eventview/EventViewModule.java # src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java # src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java # src/main/resources/css/dark_theme.css # src/main/resources/css/light_theme.css # src/main/resources/fxml/eventview.fxml # src/main/resources/fxml/vault_detail_unlocked.fxml # src/main/resources/fxml/vault_list.fxml # src/main/resources/i18n/strings.properties
This commit is contained in:
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@@ -16,6 +16,10 @@
|
||||
|
||||
- Suggest your change by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new/choose) and start writing code.
|
||||
|
||||
## Do you intend to add a new translation or change an existing one?
|
||||
|
||||
Translations are not managed directly in this repository. Instead, we use [Crowdin](https://translate.cryptomator.org/), which automatically synchronizes translations with this repository. If you want to help us with translations, please visit our translation project on Crowdin.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).
|
||||
|
||||
2
.github/workflows/appimage.yml
vendored
2
.github/workflows/appimage.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
JAVA_VERSION: '23.0.2'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
|
||||
66
.github/workflows/av-whitelist.yml
vendored
66
.github/workflows/av-whitelist.yml
vendored
@@ -13,15 +13,48 @@ on:
|
||||
description: "Url to the file to upload"
|
||||
required: true
|
||||
type: string
|
||||
avast:
|
||||
description: "Upload to Avast"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
kaspersky:
|
||||
description: "Upload to Kaspersky"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
allowlist:
|
||||
name: Anti Virus Allowlisting
|
||||
download-file:
|
||||
name: Downloads the file into the VM
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
fileName: ${{ steps.extractName.outputs.fileName}}
|
||||
steps:
|
||||
- name: Download file
|
||||
- name: Extract file name
|
||||
id: extractName
|
||||
run: |
|
||||
curl --remote-name ${{ inputs.url }} -L
|
||||
url="${{ inputs.url }}"
|
||||
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
|
||||
- name: Download file
|
||||
run: curl --remote-name ${{ inputs.url }} -L -o ${{steps.extractName.outputs.fileName}}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.extractName.outputs.fileName }}
|
||||
path: ${{ steps.extractName.outputs.fileName }}
|
||||
if-no-files-found: error
|
||||
allowlist-kaspersky:
|
||||
name: Anti Virus Allowlisting Kaspersky
|
||||
runs-on: ubuntu-latest
|
||||
needs: download-file
|
||||
if: github.event_name == 'workflow_call' || inputs.kaspersky
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.download-file.outputs.fileName }}
|
||||
path: upload
|
||||
- name: Upload to Kaspersky
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
@@ -30,11 +63,26 @@ jobs:
|
||||
port: 990
|
||||
username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
|
||||
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
|
||||
- name: Upload to Avast
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
local-dir: ./upload/
|
||||
allowlist-avast:
|
||||
name: Anti Virus Allowlisting Avast
|
||||
runs-on: ubuntu-latest
|
||||
needs: download-file
|
||||
if: github.event_name == 'workflow_call' || inputs.avast
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.download-file.outputs.fileName }}
|
||||
path: upload
|
||||
- name: Upload to Avast
|
||||
uses: wlixcc/SFTP-Deploy-Action@v1.2.5
|
||||
with:
|
||||
protocol: ftp
|
||||
server: whitelisting.avast.com
|
||||
port: 21
|
||||
port: 22
|
||||
username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
|
||||
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
|
||||
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
|
||||
ssh_private_key: ''
|
||||
sftp_only: true
|
||||
local_path: './upload/*'
|
||||
remote_path: '/data'
|
||||
2
.github/workflows/debian.yml
vendored
2
.github/workflows/debian.yml
vendored
@@ -17,7 +17,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
COFFEELIBS_JDK: 23
|
||||
COFFEELIBS_JDK_VERSION: '23.0.1+11-0ppa1'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip'
|
||||
|
||||
2
.github/workflows/mac-dmg-x64.yml
vendored
2
.github/workflows/mac-dmg-x64.yml
vendored
@@ -15,7 +15,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
|
||||
2
.github/workflows/mac-dmg.yml
vendored
2
.github/workflows/mac-dmg.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
|
||||
4
.github/workflows/win-exe.yml
vendored
4
.github/workflows/win-exe.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_windows-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AMD64_HASH: 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3'
|
||||
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
|
||||
@@ -394,7 +394,7 @@ jobs:
|
||||
|
||||
allowlist-exe:
|
||||
uses: ./.github/workflows/av-whitelist.yml
|
||||
needs: [publish]
|
||||
needs: [publish, allowlist-msi]
|
||||
with:
|
||||
url: ${{ needs.publish.outputs.download-url-exe }}
|
||||
secrets: inherit
|
||||
|
||||
@@ -83,6 +83,12 @@
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release date="2025-04-09" version="1.15.3">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.3</url>
|
||||
</release>
|
||||
<release date="2025-04-04" version="1.15.2">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.2</url>
|
||||
</release>
|
||||
<release date="2025-02-05" version="1.15.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.1</url>
|
||||
</release>
|
||||
|
||||
20
pom.xml
20
pom.xml
@@ -38,37 +38,37 @@
|
||||
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.3.0</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.5.3</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>5.0.3</cryptomator.fuse.version>
|
||||
<cryptomator.webdav.version>2.0.8</cryptomator.webdav.version>
|
||||
<cryptomator.fuse.version>5.0.5</cryptomator.fuse.version>
|
||||
<cryptomator.webdav.version>2.0.10</cryptomator.webdav.version>
|
||||
|
||||
<!-- 3rd party dependencies -->
|
||||
<commons-lang3.version>3.17.0</commons-lang3.version>
|
||||
<dagger.version>2.55</dagger.version>
|
||||
<dagger.version>2.56.1</dagger.version>
|
||||
<easybind.version>2.2</easybind.version>
|
||||
<jackson.version>2.18.3</jackson.version>
|
||||
<javafx.version>23.0.2</javafx.version>
|
||||
<jwt.version>4.5.0</jwt.version>
|
||||
<nimbus-jose.version>9.37.3</nimbus-jose.version>
|
||||
<logback.version>1.5.17</logback.version>
|
||||
<logback.version>1.5.18</logback.version>
|
||||
<slf4j.version>2.0.17</slf4j.version>
|
||||
<tinyoauth2.version>0.8.1</tinyoauth2.version>
|
||||
<zxcvbn.version>1.9.0</zxcvbn.version>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<junit.jupiter.version>5.11.4</junit.jupiter.version>
|
||||
<mockito.version>5.15.2</mockito.version>
|
||||
<junit.jupiter.version>5.12.2</junit.jupiter.version>
|
||||
<mockito.version>5.17.0</mockito.version>
|
||||
<hamcrest.version>3.0</hamcrest.version>
|
||||
|
||||
<!-- build-time dependencies -->
|
||||
<jetbrains.annotations.version>26.0.2</jetbrains.annotations.version>
|
||||
<dependency-check.version>12.1.0</dependency-check.version>
|
||||
<jacoco.version>0.8.12</jacoco.version>
|
||||
<dependency-check.version>12.1.1</dependency-check.version>
|
||||
<jacoco.version>0.8.13</jacoco.version>
|
||||
<license-generator.version>2.5.0</license-generator.version>
|
||||
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
|
||||
<mvn-compiler.version>3.13.0</mvn-compiler.version>
|
||||
<mvn-compiler.version>3.14.0</mvn-compiler.version>
|
||||
<mvn-resources.version>3.3.1</mvn-resources.version>
|
||||
<mvn-dependency.version>3.8.1</mvn-dependency.version>
|
||||
<mvn-surefire.version>3.5.2</mvn-surefire.version>
|
||||
<mvn-surefire.version>3.5.3</mvn-surefire.version>
|
||||
<mvn-jar.version>3.4.2</mvn-jar.version>
|
||||
|
||||
<!-- Property used by surefire to determine jacoco engine -->
|
||||
|
||||
@@ -10,7 +10,7 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.common.EventMap;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.common.mount.Mounter;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
@@ -78,7 +78,7 @@ public class Vault {
|
||||
private final ObjectBinding<Mountpoint> mountPoint;
|
||||
private final Mounter mounter;
|
||||
private final Settings settings;
|
||||
private final EventMap eventMap;
|
||||
private final FileSystemEventAggregator fileSystemEventAggregator;
|
||||
private final BooleanProperty showingStats;
|
||||
|
||||
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
||||
@@ -91,7 +91,7 @@ public class Vault {
|
||||
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
|
||||
VaultStats stats, //
|
||||
Mounter mounter, Settings settings, //
|
||||
EventMap eventMap) {
|
||||
FileSystemEventAggregator fileSystemEventAggregator) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.configCache = configCache;
|
||||
this.cryptoFileSystem = cryptoFileSystem;
|
||||
@@ -108,7 +108,7 @@ public class Vault {
|
||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||
this.mounter = mounter;
|
||||
this.settings = settings;
|
||||
this.eventMap = eventMap;
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
this.quickAccessEntry = new AtomicReference<>(null);
|
||||
}
|
||||
@@ -261,10 +261,7 @@ public class Vault {
|
||||
|
||||
|
||||
private void consumeVaultEvent(FilesystemEvent e) {
|
||||
var wrapper = new VaultEvent(this, e);
|
||||
Platform.runLater(() -> {
|
||||
eventMap.put(wrapper);
|
||||
});
|
||||
fileSystemEventAggregator.put(this, e);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
|
||||
8
src/main/java/org/cryptomator/event/FSEventBucket.java
Normal file
8
src/main/java/org/cryptomator/event/FSEventBucket.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record FSEventBucket(Vault vault, Path idPath, Class<? extends FilesystemEvent> c) {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
public record FSEventBucketContent(FilesystemEvent mostRecentEvent, int count) {}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
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.FilesystemEvent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Aggregator for {@link FilesystemEvent}s.
|
||||
* <p>
|
||||
* The aggregator groups filesystem events by the vault where the event occurred, an identifying path (clear- or ciphertext) and the event class (aka type).
|
||||
* A group is called an {@link FSEventBucket}, its {@link FSEventBucketContent} is the most recent event object and a count of how often the event already occurred.
|
||||
*/
|
||||
@Singleton
|
||||
public class FileSystemEventAggregator {
|
||||
|
||||
private final ConcurrentHashMap<FSEventBucket, FSEventBucketContent> map;
|
||||
private final AtomicBoolean hasUpdates;
|
||||
|
||||
@Inject
|
||||
public FileSystemEventAggregator() {
|
||||
this.map = new ConcurrentHashMap<>();
|
||||
this.hasUpdates = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given event to the map. If a bucket for this event already exists, only the count is updated and the event set as the most recent one.
|
||||
*
|
||||
* @param v Vault where the event occurred
|
||||
* @param e Actual {@link FilesystemEvent}
|
||||
*/
|
||||
public void put(Vault v, FilesystemEvent e) {
|
||||
var key = computeKey(v, e);
|
||||
map.compute(key, (k, val) -> {
|
||||
if (val == null) {
|
||||
return new FSEventBucketContent(e, 1);
|
||||
} else {
|
||||
return new FSEventBucketContent(e, val.count() + 1);
|
||||
}
|
||||
});
|
||||
hasUpdates.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event bucket from the map.
|
||||
*/
|
||||
public FSEventBucketContent remove(FSEventBucket key) {
|
||||
var content = map.remove(key);
|
||||
hasUpdates.set(true);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the event map.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
hasUpdates.set(true);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasMaybeUpdates() {
|
||||
return hasUpdates.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the map entries into a collection.
|
||||
* <p>
|
||||
* The collection is first cleared, then all map entries are added in one bulk operation. Cleans the hasUpdates status.
|
||||
*
|
||||
* @param target collection which is first cleared and then the EntrySet copied to.
|
||||
*/
|
||||
public void cloneTo(Collection<Map.Entry<FSEventBucket, FSEventBucketContent>> target) {
|
||||
hasUpdates.set(false);
|
||||
target.clear();
|
||||
target.addAll(map.entrySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to compute the identifying key for a given filesystem event
|
||||
*
|
||||
* @param v Vault where the event occurred
|
||||
* @param event Actual {@link FilesystemEvent}
|
||||
* @return a {@link FSEventBucket} used in the map and lru cache
|
||||
*/
|
||||
private static FSEventBucket computeKey(Vault v, FilesystemEvent event) {
|
||||
var p = switch (event) {
|
||||
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
|
||||
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
|
||||
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
|
||||
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
|
||||
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
|
||||
};
|
||||
return new FSEventBucket(v, p, event.getClass());
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public enum FxmlFile {
|
||||
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), //
|
||||
DECRYPTNAMES("/fxml/decryptnames.fxml"), //
|
||||
ERROR("/fxml/error.fxml"), //
|
||||
EVENT_VIEW("/fxml/eventview.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record CipherAndCleartext(Path ciphertext, String cleartextName) {
|
||||
|
||||
public String getCiphertextFilename() {
|
||||
return ciphertext.getFileName().toString();
|
||||
}
|
||||
|
||||
public ObservableValue<String> ciphertextFilenameProperty() {
|
||||
return new ReadOnlyStringWrapper(getCiphertextFilename());
|
||||
}
|
||||
|
||||
public String getCleartextName() {
|
||||
return cleartextName;
|
||||
}
|
||||
|
||||
public ObservableValue<String> cleartextNameProperty() {
|
||||
return new ReadOnlyStringWrapper(getCleartextName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.common.Constants;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
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.ListProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@DecryptNameScoped
|
||||
public class DecryptFileNamesViewController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DecryptFileNamesViewController.class);
|
||||
private static final KeyCodeCombination COPY_TO_CLIPBOARD_SHORTCUT = new KeyCodeCombination(KeyCode.C, KeyCodeCombination.SHORTCUT_DOWN);
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_WIN = "CTRL+C";
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_MAC = "⌘C";
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_LINUX = "CTRL+C";
|
||||
|
||||
private final ListProperty<CipherAndCleartext> mapping;
|
||||
private final StringProperty dropZoneText = new SimpleStringProperty();
|
||||
private final ObjectProperty<FontAwesome5Icon> dropZoneIcon = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty wrongFilesSelected = new SimpleBooleanProperty(false);
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final List<Path> initialList;
|
||||
|
||||
@FXML
|
||||
public TableColumn<CipherAndCleartext, String> ciphertextColumn;
|
||||
@FXML
|
||||
public TableColumn<CipherAndCleartext, String> cleartextColumn;
|
||||
@FXML
|
||||
public TableView<CipherAndCleartext> cipherToCleartextTable;
|
||||
|
||||
@Inject
|
||||
public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, @DecryptNameWindow List<Path> pathsToDecrypt, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.mapping = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
this.initialList = pathsToDecrypt;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
cipherToCleartextTable.setItems(mapping);
|
||||
cipherToCleartextTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
|
||||
//DragNDrop
|
||||
cipherToCleartextTable.setOnDragEntered(event -> {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
cipherToCleartextTable.setItems(FXCollections.emptyObservableList());
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragOver(event -> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragDropped(event -> {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
checkAndDecrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList());
|
||||
cipherToCleartextTable.setItems(mapping);
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragExited(_ -> cipherToCleartextTable.setItems(mapping));
|
||||
//selectionModel and copy-to-clipboard action
|
||||
cipherToCleartextTable.getSelectionModel().setCellSelectionEnabled(true);
|
||||
cipherToCleartextTable.setOnKeyPressed(keyEvent -> {
|
||||
if (COPY_TO_CLIPBOARD_SHORTCUT.match(keyEvent)) {
|
||||
copySingleCelltoClipboard();
|
||||
}
|
||||
});
|
||||
ciphertextColumn.setCellValueFactory(new PropertyValueFactory<>("ciphertextFilename"));
|
||||
cleartextColumn.setCellValueFactory(new PropertyValueFactory<>("cleartextName"));
|
||||
|
||||
dropZoneText.setValue(resourceBundle.getString("decryptNames.dropZone.message"));
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
|
||||
|
||||
wrongFilesSelected.addListener((_, _, areWrongFiles) -> {
|
||||
if (areWrongFiles) {
|
||||
CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
|
||||
dropZoneText.setValue(resourceBundle.getString("decryptNames.dropZone.message"));
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
|
||||
wrongFilesSelected.setValue(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!initialList.isEmpty()) {
|
||||
checkAndDecrypt(initialList);
|
||||
}
|
||||
}
|
||||
|
||||
private void copySingleCelltoClipboard() {
|
||||
cipherToCleartextTable.getSelectionModel().getSelectedCells().stream().findFirst().ifPresent(tablePosition -> {
|
||||
var selectedItem = cipherToCleartextTable.getSelectionModel().getSelectedItem();
|
||||
//TODO: give user feedback, if content is copied -> must be done via a custom cell factory to access the actual table cell!
|
||||
if (tablePosition.getTableColumn().equals(ciphertextColumn)) {
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, selectedItem.ciphertext().toString()));
|
||||
} else {
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, selectedItem.cleartextName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void selectFiles() {
|
||||
var fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(resourceBundle.getString("decryptNames.filePicker.title"));
|
||||
fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter(resourceBundle.getString("decryptNames.filePicker.extensionDescription"), List.of("*.c9r")));
|
||||
fileChooser.setInitialDirectory(vault.getPath().toFile());
|
||||
var ciphertextNodes = fileChooser.showOpenMultipleDialog(window);
|
||||
if (ciphertextNodes != null) {
|
||||
checkAndDecrypt(ciphertextNodes.stream().map(File::toPath).toList());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndDecrypt(List<Path> pathsToDecrypt) {
|
||||
mapping.clear();
|
||||
//Assumption: All files are in the same directory
|
||||
var testPath = pathsToDecrypt.getFirst();
|
||||
if (!testPath.startsWith(vault.getPath())) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.foreignFiles").formatted(vault.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
if (pathsToDecrypt.size() == 1 && testPath.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.vaultInternalFiles"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var newMapping = pathsToDecrypt.stream().filter(p -> !p.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)).map(this::getCleartextName).toList();
|
||||
mapping.addAll(newMapping);
|
||||
} catch (UncheckedIOException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.generic"));
|
||||
LOG.info("Failed to decrypt filenames for directory {}", testPath.getParent(), e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.vaultInternalFiles"));
|
||||
} catch (UnsupportedOperationException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.noDirIdBackup"));
|
||||
}
|
||||
}
|
||||
|
||||
private void setDropZoneError(String text) {
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
dropZoneText.setValue(text);
|
||||
wrongFilesSelected.setValue(true);
|
||||
}
|
||||
|
||||
private CipherAndCleartext getCleartextName(Path ciphertextNode) {
|
||||
try {
|
||||
var cleartextName = vault.getCleartextName(ciphertextNode);
|
||||
return new CipherAndCleartext(ciphertextNode, cleartextName);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//obvservable getter
|
||||
|
||||
public ObservableValue<String> dropZoneTextProperty() {
|
||||
return dropZoneText;
|
||||
}
|
||||
|
||||
public String getDropZoneText() {
|
||||
return dropZoneText.get();
|
||||
}
|
||||
|
||||
public ObservableValue<FontAwesome5Icon> dropZoneIconProperty() {
|
||||
return dropZoneIcon;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getDropZoneIcon() {
|
||||
return dropZoneIcon.get();
|
||||
}
|
||||
|
||||
public void clearTable() {
|
||||
mapping.clear();
|
||||
}
|
||||
|
||||
public void copyTableToClipboard() {
|
||||
var csv = mapping.stream().map(cipherAndClear -> "\"" + cipherAndClear.ciphertext() + "\", \"" + cipherAndClear.cleartextName() + "\"").collect(Collectors.joining("\n"));
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, csv));
|
||||
}
|
||||
|
||||
public String getCopyToClipboardShortcutString() {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_WIN;
|
||||
} else if (SystemUtils.IS_OS_MAC) {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_MAC;
|
||||
} else {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_LINUX;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@DecryptNameScoped
|
||||
@Subcomponent(modules = DecryptNameModule.class)
|
||||
public interface DecryptNameComponent {
|
||||
|
||||
Logger LOG = LoggerFactory.getLogger(DecryptNameComponent.class);
|
||||
|
||||
@DecryptNameWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.DECRYPTNAMES)
|
||||
Lazy<Scene> decryptNamesView();
|
||||
|
||||
@DecryptNameWindow
|
||||
Vault vault();
|
||||
|
||||
default void showDecryptFileNameWindow() {
|
||||
Stage s = window();
|
||||
s.setScene(decryptNamesView().get());
|
||||
s.sizeToScene();
|
||||
if (vault().isUnlocked()) {
|
||||
s.show();
|
||||
} else {
|
||||
LOG.error("Aborted showing DecryptFileName window: vault state is not {}, but {}.", VaultState.Value.UNLOCKED, vault().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
DecryptNameComponent create(@BindsInstance @DecryptNameWindow Vault vault, @BindsInstance @Named("windowOwner") Stage owner, @BindsInstance @DecryptNameWindow List<Path> pathsToDecrypt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
public abstract class DecryptNameModule {
|
||||
|
||||
@Provides
|
||||
@DecryptNameScoped
|
||||
@DecryptNameWindow
|
||||
static Stage provideStage(StageFactory factory, @Named("windowOwner") Stage owner, @DecryptNameWindow Vault vault, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setResizable(true);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.setTitle(resourceBundle.getString("decryptNames.title"));
|
||||
vault.stateProperty().addListener(((_, _, _) -> stage.close())); //as soon as the state changes from unlocked, close the window
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DecryptNameScoped
|
||||
@DecryptNameWindow
|
||||
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.DECRYPTNAMES)
|
||||
@DecryptNameScoped
|
||||
static Scene provideDecryptNamesViewScene(@DecryptNameWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.DECRYPTNAMES);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(DecryptFileNamesViewController.class)
|
||||
abstract FxController bindDecryptNamesViewController(DecryptFileNamesViewController controller);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface DecryptNameScoped {}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@interface DecryptNameWindow {}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.common.EventMap;
|
||||
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;
|
||||
@@ -9,7 +11,6 @@ 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.event.VaultEvent;
|
||||
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -41,6 +42,7 @@ import java.nio.file.Path;
|
||||
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;
|
||||
@@ -51,11 +53,11 @@ public class EventListCellController implements FxController {
|
||||
private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
|
||||
private static final DateTimeFormatter LOCAL_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
|
||||
|
||||
private final EventMap eventMap;
|
||||
private final FileSystemEventAggregator fileSystemEventAggregator;
|
||||
@Nullable
|
||||
private final RevealPathService revealService;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final ObjectProperty<VaultEvent> event;
|
||||
private final ObjectProperty<Map.Entry<FSEventBucket, FSEventBucketContent>> eventEntry;
|
||||
private final StringProperty eventMessage;
|
||||
private final StringProperty eventDescription;
|
||||
private final ObjectProperty<FontAwesome5Icon> eventIcon;
|
||||
@@ -77,18 +79,18 @@ public class EventListCellController implements FxController {
|
||||
Button eventActionsButton;
|
||||
|
||||
@Inject
|
||||
public EventListCellController(EventMap eventMap, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
|
||||
this.eventMap = eventMap;
|
||||
public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.revealService = revealService.orElseGet(() -> null);
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.event = new SimpleObjectProperty<>(null);
|
||||
this.eventEntry = new SimpleObjectProperty<>(null);
|
||||
this.eventMessage = new SimpleStringProperty();
|
||||
this.eventDescription = new SimpleStringProperty();
|
||||
this.eventIcon = new SimpleObjectProperty<>();
|
||||
this.eventCount = ObservableUtil.mapWithDefault(event, e -> e.count() == 1? "" : "("+ e.count() +")", "");
|
||||
this.vaultUnlocked = ObservableUtil.mapWithDefault(event.flatMap(e -> e.v().unlockedProperty()), Function.identity(), false);
|
||||
this.readableTime = ObservableUtil.mapWithDefault(event, e -> LOCAL_TIME_FORMATTER.format(e.actualEvent().getTimestamp()), "");
|
||||
this.readableDate = ObservableUtil.mapWithDefault(event, e -> LOCAL_DATE_FORMATTER.format(e.actualEvent().getTimestamp()), "");
|
||||
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()), "");
|
||||
this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage);
|
||||
this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription);
|
||||
this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon);
|
||||
@@ -108,13 +110,15 @@ public class EventListCellController implements FxController {
|
||||
return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover());
|
||||
}
|
||||
|
||||
public void setEvent(@NotNull VaultEvent item) {
|
||||
event.set(item);
|
||||
public void setEventEntry(@NotNull Map.Entry<FSEventBucket, FSEventBucketContent> item) {
|
||||
eventEntry.set(item);
|
||||
eventActionsMenu.hide();
|
||||
eventActionsMenu.getItems().clear();
|
||||
eventTooltip.setText(item.v().getDisplayName());
|
||||
addAction("generic.action.dismiss", () -> eventMap.remove(item));
|
||||
switch (item.actualEvent()) {
|
||||
eventTooltip.setText(item.getKey().vault().getDisplayName());
|
||||
addAction("generic.action.dismiss", () -> {
|
||||
fileSystemEventAggregator.remove(item.getKey());
|
||||
});
|
||||
switch (item.getValue().mostRecentEvent()) {
|
||||
case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
|
||||
case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);
|
||||
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
|
||||
@@ -202,23 +206,25 @@ public class EventListCellController implements FxController {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventMessage.getValue();
|
||||
} else {
|
||||
return resourceBundle.getString("eventView.entry.vaultLocked.message");
|
||||
return "***********";
|
||||
}
|
||||
}
|
||||
|
||||
private String selectDescription() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventDescription.getValue();
|
||||
} else if (eventEntry.getValue() != null) {
|
||||
var e = eventEntry.getValue().getKey();
|
||||
return resourceBundle.getString("eventView.entry.vaultLocked.description").formatted(e != null ? e.vault().getDisplayName() : "");
|
||||
} else {
|
||||
var e = event.getValue();
|
||||
return resourceBundle.getString("eventView.entry.vaultLocked.description").formatted(e != null ? e.v().getDisplayName() : "");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void toggleEventActionsMenu() {
|
||||
var e = event.get();
|
||||
var e = eventEntry.get();
|
||||
if (e != null) {
|
||||
if (eventActionsMenu.isShowing()) {
|
||||
eventActionsMenu.hide();
|
||||
@@ -232,7 +238,7 @@ public class EventListCellController implements FxController {
|
||||
if (!(p instanceof CryptoPath)) {
|
||||
throw new IllegalArgumentException("Path " + p + " is not a vault path");
|
||||
}
|
||||
var v = event.getValue().v();
|
||||
var v = eventEntry.getValue().getKey().vault();
|
||||
if (!v.isUnlocked()) {
|
||||
return Path.of(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -12,9 +13,10 @@ import javafx.scene.control.ListView;
|
||||
import javafx.util.Callback;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Map;
|
||||
|
||||
@EventViewScoped
|
||||
public class EventListCellFactory implements Callback<ListView<VaultEvent>, ListCell<VaultEvent>> {
|
||||
public class EventListCellFactory implements Callback<ListView<Map.Entry<FSEventBucket, FSEventBucketContent>>, ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>>> {
|
||||
|
||||
private static final String FXML_PATH = "/fxml/eventview_cell.fxml";
|
||||
|
||||
@@ -27,7 +29,7 @@ public class EventListCellFactory implements Callback<ListView<VaultEvent>, List
|
||||
|
||||
|
||||
@Override
|
||||
public ListCell<VaultEvent> call(ListView<VaultEvent> eventListView) {
|
||||
public ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> call(ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView) {
|
||||
try {
|
||||
FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH);
|
||||
return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
|
||||
@@ -36,7 +38,7 @@ public class EventListCellFactory implements Callback<ListView<VaultEvent>, List
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cell extends ListCell<VaultEvent> {
|
||||
private static class Cell extends ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> {
|
||||
|
||||
private final Parent root;
|
||||
private final EventListCellController controller;
|
||||
@@ -47,7 +49,7 @@ public class EventListCellFactory implements Callback<ListView<VaultEvent>, List
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(VaultEvent item, boolean empty) {
|
||||
protected void updateItem(Map.Entry<FSEventBucket, FSEventBucketContent> item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
@@ -57,7 +59,7 @@ public class EventListCellFactory implements Callback<ListView<VaultEvent>, List
|
||||
this.getStyleClass().addLast("list-cell");
|
||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
setGraphic(root);
|
||||
controller.setEvent(item);
|
||||
controller.setEventEntry(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.common.EventMap;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
@@ -17,17 +18,16 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.util.StringConverter;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@EventViewScoped
|
||||
public class EventViewController implements FxController {
|
||||
|
||||
private final EventMap eventMap;
|
||||
private final ObservableList<VaultEvent> eventList;
|
||||
private final FilteredList<VaultEvent> filteredEventList;
|
||||
private final FilteredList<Map.Entry<FSEventBucket, FSEventBucketContent>> filteredEventList;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final SortedList<VaultEvent> reversedEventList;
|
||||
private final FileSystemEventAggregator aggregator;
|
||||
private final SortedList<Map.Entry<FSEventBucket, FSEventBucketContent>> sortedEventList;
|
||||
private final ObservableList<Vault> choiceBoxEntries;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final EventListCellFactory cellFactory;
|
||||
@@ -35,20 +35,45 @@ public class EventViewController implements FxController {
|
||||
@FXML
|
||||
ChoiceBox<Vault> vaultFilterChoiceBox;
|
||||
@FXML
|
||||
ListView<VaultEvent> eventListView;
|
||||
ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView;
|
||||
|
||||
@Inject
|
||||
public EventViewController(EventMap eventMap, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory) {
|
||||
this.eventMap = eventMap;
|
||||
this.eventList = FXCollections.observableArrayList();
|
||||
this.filteredEventList = eventList.filtered(_ -> true);
|
||||
public EventViewController(FxFSEventList fxFSEventList, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory, FileSystemEventAggregator aggregator) {
|
||||
this.filteredEventList = fxFSEventList.getObservableList().filtered(_ -> true);
|
||||
this.vaults = vaults;
|
||||
this.reversedEventList = new SortedList<>(filteredEventList, Comparator.reverseOrder());
|
||||
this.aggregator = aggregator;
|
||||
this.sortedEventList = new SortedList<>(filteredEventList, this::compareBuckets);
|
||||
this.choiceBoxEntries = FXCollections.observableArrayList();
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.cellFactory = cellFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison method for the lru cache. During comparsion the map is accessed.
|
||||
* First the entries are compared by the event timestamp, then vaultId, then identifying path and lastly by class name.
|
||||
*
|
||||
* @param left an entry of a {@link FSEventBucket} and its content
|
||||
* @param right another entry of a {@link FSEventBucket} plus content, compared to {@code left}
|
||||
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
|
||||
*/
|
||||
private int compareBuckets(Map.Entry<FSEventBucket, FSEventBucketContent> left, Map.Entry<FSEventBucket, FSEventBucketContent> right) {
|
||||
var t1 = left.getValue().mostRecentEvent().getTimestamp();
|
||||
var t2 = right.getValue().mostRecentEvent().getTimestamp();
|
||||
var timeComparison = t1.compareTo(t2);
|
||||
if (timeComparison != 0) {
|
||||
return -timeComparison; //we need the reverse timesorting
|
||||
}
|
||||
var vaultIdComparison = left.getKey().vault().getId().compareTo(right.getKey().vault().getId());
|
||||
if (vaultIdComparison != 0) {
|
||||
return vaultIdComparison;
|
||||
}
|
||||
var pathComparison = left.getKey().idPath().compareTo(right.getKey().idPath());
|
||||
if (pathComparison != 0) {
|
||||
return pathComparison;
|
||||
}
|
||||
return left.getKey().c().getName().compareTo(right.getKey().c().getName());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
choiceBoxEntries.add(null);
|
||||
@@ -60,39 +85,25 @@ public class EventViewController implements FxController {
|
||||
}
|
||||
});
|
||||
|
||||
eventList.addAll(eventMap.values());
|
||||
eventMap.addListener((MapChangeListener<? super EventMap.EventKey, ? super VaultEvent>) this::updateList);
|
||||
eventListView.setCellFactory(cellFactory);
|
||||
eventListView.setItems(reversedEventList);
|
||||
eventListView.setItems(sortedEventList);
|
||||
|
||||
vaultFilterChoiceBox.setItems(choiceBoxEntries);
|
||||
vaultFilterChoiceBox.valueProperty().addListener(this::applyVaultFilter);
|
||||
vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle));
|
||||
}
|
||||
|
||||
private void updateList(MapChangeListener.Change<? extends EventMap.EventKey, ? extends VaultEvent> change) {
|
||||
if (change.wasAdded() && change.wasRemoved()) {
|
||||
//entry updated
|
||||
eventList.remove(change.getValueRemoved());
|
||||
eventList.addLast(change.getValueAdded());
|
||||
} else if (change.wasAdded()) {
|
||||
eventList.addLast(change.getValueAdded());
|
||||
} else { //removed
|
||||
eventList.remove(change.getValueRemoved());
|
||||
}
|
||||
}
|
||||
|
||||
private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) {
|
||||
if (newV == null) {
|
||||
filteredEventList.setPredicate(_ -> true);
|
||||
} else {
|
||||
filteredEventList.setPredicate(e -> e.v().equals(newV));
|
||||
filteredEventList.setPredicate(e -> e.getKey().vault().equals(newV));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void clearEvents() {
|
||||
eventMap.clear();
|
||||
aggregator.clear();
|
||||
}
|
||||
|
||||
private static class VaultConverter extends StringConverter<Vault> {
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javafx.scene.Scene;
|
||||
@@ -25,12 +26,17 @@ abstract class EventViewModule {
|
||||
@Provides
|
||||
@EventViewScoped
|
||||
@EventViewWindow
|
||||
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
|
||||
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle, FxFSEventList fxFSEventList) {
|
||||
Stage stage = factory.create();
|
||||
stage.setHeight(498);
|
||||
stage.setTitle(resourceBundle.getString("eventView.title"));
|
||||
stage.setResizable(true);
|
||||
stage.initModality(Modality.NONE);
|
||||
stage.focusedProperty().addListener((_,_,isFocused) -> {
|
||||
if(isFocused) {
|
||||
fxFSEventList.unreadEventsProperty().setValue(false);
|
||||
}
|
||||
});
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ public class FxApplication {
|
||||
private final FxApplicationStyle applicationStyle;
|
||||
private final FxApplicationTerminator applicationTerminator;
|
||||
private final AutoUnlocker autoUnlocker;
|
||||
private final FxFSEventList fxFSEventList;
|
||||
|
||||
@Inject
|
||||
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
|
||||
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker, FxFSEventList fxFSEventList) {
|
||||
this.startupTime = startupTime;
|
||||
this.environment = environment;
|
||||
this.settings = settings;
|
||||
@@ -41,6 +42,7 @@ public class FxApplication {
|
||||
this.applicationStyle = applicationStyle;
|
||||
this.applicationTerminator = applicationTerminator;
|
||||
this.autoUnlocker = autoUnlocker;
|
||||
this.fxFSEventList = fxFSEventList;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -85,6 +87,7 @@ public class FxApplication {
|
||||
migrateAndInformDokanyRemoval();
|
||||
|
||||
launchEventHandler.startHandlingLaunchEvents();
|
||||
fxFSEventList.schedulePollForUpdates();
|
||||
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package org.cryptomator.ui.fxapp;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.ui.decryptname.DecryptNameComponent;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.eventview.EventViewComponent;
|
||||
import org.cryptomator.ui.health.HealthCheckComponent;
|
||||
@@ -20,11 +21,15 @@ import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.image.Image;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, //
|
||||
DecryptNameComponent.class, //
|
||||
MainWindowComponent.class, //
|
||||
PreferencesComponent.class, //
|
||||
VaultOptionsComponent.class, //
|
||||
|
||||
67
src/main/java/org/cryptomator/ui/fxapp/FxFSEventList.java
Normal file
67
src/main/java/org/cryptomator/ui/fxapp/FxFSEventList.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* List of all occurred filesystem events.
|
||||
* <p>
|
||||
* The list exposes an observable list and a property to listen for updates. Internally it polls the {@link FileSystemEventAggregator} in a regular interval for updates.
|
||||
* If an update is available, the list from the {@link FileSystemEventAggregator } is cloned to this list on the FX application thread.
|
||||
*/
|
||||
@FxApplicationScoped
|
||||
public class FxFSEventList {
|
||||
|
||||
private final ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> events;
|
||||
private final FileSystemEventAggregator eventAggregator;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final BooleanProperty unreadEvents;
|
||||
|
||||
@Inject
|
||||
public FxFSEventList(FileSystemEventAggregator fsEventAggregator, ScheduledExecutorService scheduler) {
|
||||
this.events = FXCollections.observableArrayList();
|
||||
this.eventAggregator = fsEventAggregator;
|
||||
this.scheduler = scheduler;
|
||||
this.unreadEvents = new SimpleBooleanProperty(false);
|
||||
}
|
||||
|
||||
public void schedulePollForUpdates() {
|
||||
scheduler.schedule(this::checkForEventUpdates, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for event updates and reschedules.
|
||||
* If updates are available, the aggregated events are copied from back- to the frontend.
|
||||
* Reschedules itself on successful execution
|
||||
*/
|
||||
private void checkForEventUpdates() {
|
||||
if (eventAggregator.hasMaybeUpdates()) {
|
||||
Platform.runLater(() -> {
|
||||
eventAggregator.cloneTo(events);
|
||||
unreadEvents.setValue(true);
|
||||
schedulePollForUpdates();
|
||||
});
|
||||
} else {
|
||||
schedulePollForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> getObservableList() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public BooleanProperty unreadEventsProperty() {
|
||||
return unreadEvents;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.util.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@PassphraseEntryScoped
|
||||
public class PassphraseEntryController implements FxController {
|
||||
@@ -49,6 +50,7 @@ public class PassphraseEntryController implements FxController {
|
||||
private final ForgetPasswordComponent.Builder forgetPassword;
|
||||
private final KeychainManager keychain;
|
||||
private final StringBinding vaultName;
|
||||
private final ExecutorService backgroundExecutorService;
|
||||
private final BooleanProperty unlockInProgress = new SimpleBooleanProperty();
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.when(unlockInProgress).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||
private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty();
|
||||
@@ -64,7 +66,7 @@ public class PassphraseEntryController implements FxController {
|
||||
public Animation unlockAnimation;
|
||||
|
||||
@Inject
|
||||
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
|
||||
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain, ExecutorService backgroundExecutorService) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.result = result;
|
||||
@@ -72,8 +74,8 @@ public class PassphraseEntryController implements FxController {
|
||||
this.forgetPassword = forgetPassword;
|
||||
this.keychain = keychain;
|
||||
this.vaultName = WeakBindings.bindString(vault.displayNameProperty());
|
||||
this.backgroundExecutorService = backgroundExecutorService;
|
||||
window.setOnHiding(this::windowClosed);
|
||||
result.whenCompleteAsync((r, t) -> unlockInProgress.set(false), Platform::runLater);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -119,8 +121,6 @@ public class PassphraseEntryController implements FxController {
|
||||
new KeyFrame(Duration.millis(800), legsExtendedY, legsExtendedX, faceHidden), //
|
||||
new KeyFrame(Duration.millis(1000), faceVisible) //
|
||||
);
|
||||
|
||||
result.whenCompleteAsync((r, t) -> stopUnlockAnimation());
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -133,6 +133,9 @@ public class PassphraseEntryController implements FxController {
|
||||
result.cancel(true);
|
||||
LOG.debug("Unlock canceled by user.");
|
||||
}
|
||||
if( passwordField != null) {
|
||||
passwordField.getCharacters().destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -142,7 +145,7 @@ public class PassphraseEntryController implements FxController {
|
||||
unlockInProgress.set(true);
|
||||
CharSequence pwFieldContents = passwordField.getCharacters();
|
||||
Passphrase pw = Passphrase.copyOf(pwFieldContents);
|
||||
result.complete(new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected()));
|
||||
result.completeAsync(() -> new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected()), backgroundExecutorService);
|
||||
startUnlockAnimation();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ 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;
|
||||
@@ -47,7 +48,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultDetailUnlockedController implements FxController {
|
||||
@@ -61,6 +61,7 @@ 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;
|
||||
@@ -70,7 +71,6 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
private final BooleanProperty draggingOverLocateEncrypted = new SimpleBooleanProperty();
|
||||
private final BooleanProperty draggingOverDecryptName = new SimpleBooleanProperty();
|
||||
private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty();
|
||||
private final BooleanProperty cleartextNamesCopied = new SimpleBooleanProperty();
|
||||
|
||||
@FXML
|
||||
public Button revealEncryptedDropZone;
|
||||
@@ -78,13 +78,22 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
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;
|
||||
@@ -106,7 +115,7 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
revealEncryptedDropZone.setOnDragExited(_ -> draggingOverLocateEncrypted.setValue(false));
|
||||
|
||||
decryptNameDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverDecryptName));
|
||||
decryptNameDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCleartextName, this::copyDecryptedNamesToClipboard));
|
||||
decryptNameDropZone.setOnDragDropped(e -> showDecryptNameWindow(e.getDragboard().getFiles().stream().map(File::toPath).toList()));
|
||||
decryptNameDropZone.setOnDragExited(_ -> draggingOverDecryptName.setValue(false));
|
||||
|
||||
EasyBind.includeWhen(revealEncryptedDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverLocateEncrypted);
|
||||
@@ -153,39 +162,12 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void chooseEncryptedFileAndCopyNames() {
|
||||
var fileChooser = new FileChooser();
|
||||
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());
|
||||
copyDecryptedNamesToClipboard(List.of(nodeName));
|
||||
}
|
||||
public void showDecryptNameWindow() {
|
||||
showDecryptNameWindow(List.of());
|
||||
}
|
||||
|
||||
private void copyDecryptedNamesToClipboard(List<CipherToCleartext> mapping) {
|
||||
if (mapping.size() == 1) {
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, mapping.getFirst().cleartext));
|
||||
} else {
|
||||
var content = mapping.stream().map(CipherToCleartext::toString).collect(Collectors.joining("\n"));
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, content));
|
||||
}
|
||||
cleartextNamesCopied.setValue(true);
|
||||
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
|
||||
cleartextNamesCopied.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CipherToCleartext getCleartextName(Path ciphertextNode) {
|
||||
try {
|
||||
return new CipherToCleartext(ciphertextNode.getFileName().toString(), vault.get().getCleartextName(ciphertextNode));
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to decrypt filename for {}", ciphertextNode, e);
|
||||
return null;
|
||||
}
|
||||
private void showDecryptNameWindow(List<Path> pathsToDecrypt) {
|
||||
decryptNameWindowFactory.create(vault.get(), mainWindow, pathsToDecrypt).showDecryptFileNameWindow();
|
||||
}
|
||||
|
||||
private boolean startsWithVaultAccessPoint(Path path) {
|
||||
@@ -260,14 +242,6 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
|
||||
}
|
||||
|
||||
record CipherToCleartext(String ciphertext, String cleartext) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ciphertext + " > " + cleartext;
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|
||||
@@ -310,11 +284,5 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
return ciphertextPathsCopied.get();
|
||||
}
|
||||
|
||||
public BooleanProperty cleartextNamesCopiedProperty() {
|
||||
return cleartextNamesCopied;
|
||||
}
|
||||
|
||||
public boolean isCleartextNamesCopied() {
|
||||
return cleartextNamesCopied.get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.EventMap;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.DirStructure;
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.slf4j.Logger;
|
||||
@@ -25,7 +24,6 @@ import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
@@ -69,8 +67,7 @@ public class VaultListController implements FxController {
|
||||
private final VaultListCellFactory cellFactory;
|
||||
private final AddVaultWizardComponent.Builder addVaultWizard;
|
||||
private final BooleanBinding emptyVaultList;
|
||||
private final EventMap eventMap;
|
||||
private final BooleanProperty newEventsPresent;
|
||||
private final BooleanProperty unreadEvents;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
private final ResourceBundle resourceBundle;
|
||||
@@ -97,7 +94,7 @@ public class VaultListController implements FxController {
|
||||
FxApplicationWindows appWindows, //
|
||||
Settings settings, //
|
||||
Dialogs dialogs, //
|
||||
EventMap eventMap) {
|
||||
FxFSEventList fxFSEventList) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaults = vaults;
|
||||
this.selectedVault = selectedVault;
|
||||
@@ -110,13 +107,7 @@ public class VaultListController implements FxController {
|
||||
this.dialogs = dialogs;
|
||||
|
||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||
this.eventMap = eventMap;
|
||||
this.newEventsPresent = new SimpleBooleanProperty(false);
|
||||
eventMap.addListener((MapChangeListener<? super EventMap.EventKey, ? super VaultEvent>) change -> {
|
||||
if (change.wasAdded()) {
|
||||
newEventsPresent.setValue(true);
|
||||
}
|
||||
});
|
||||
this.unreadEvents = fxFSEventList.unreadEventsProperty();
|
||||
|
||||
selectedVault.addListener(this::selectedVaultDidChange);
|
||||
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
|
||||
@@ -279,7 +270,6 @@ public class VaultListController implements FxController {
|
||||
@FXML
|
||||
public void showEventViewer() {
|
||||
appWindows.showEventViewer();
|
||||
newEventsPresent.setValue(false);
|
||||
}
|
||||
// Getter and Setter
|
||||
|
||||
@@ -307,11 +297,11 @@ public class VaultListController implements FxController {
|
||||
return cellSize.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> newEventsPresentProperty() {
|
||||
return newEventsPresent;
|
||||
public ObservableValue<Boolean> unreadEventsPresentProperty() {
|
||||
return unreadEvents;
|
||||
}
|
||||
|
||||
public boolean getNewEventsPresent() {
|
||||
return newEventsPresent.getValue();
|
||||
public boolean getUnreadEventsPresent() {
|
||||
return unreadEvents.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +130,7 @@
|
||||
}
|
||||
|
||||
.cryptic-text {
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-fill: TEXT_FILL;
|
||||
-fx-font-family: 'Fira Code';
|
||||
-fx-font-size: 1.1em;
|
||||
}
|
||||
@@ -313,6 +312,10 @@
|
||||
-fx-font-size: 1.0em;
|
||||
}
|
||||
|
||||
.list-cell .header-misc {
|
||||
-fx-font-size: 1.0em;
|
||||
}
|
||||
|
||||
.list-cell .detail-label {
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-font-size: 0.8em;
|
||||
@@ -652,11 +655,8 @@
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.update-indicator {
|
||||
-fx-background-color: white, RED_5;
|
||||
-fx-background-insets: 1px, 2px;
|
||||
-fx-background-radius: 6px, 5px;
|
||||
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
|
||||
.icon-update-indicator {
|
||||
-fx-fill: RED_5;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -1022,4 +1022,167 @@
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Decrypt Name Window
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.decrypt-name-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
|
||||
-fx-border-width: 0 0 1px 0;
|
||||
}
|
||||
|
||||
.decrypt-name-window .button-bar .button-right {
|
||||
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.decrypt-name-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.decrypt-name-window .table-view {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0,1;
|
||||
/* There is some oddness if padding is in em values rather than pixels,
|
||||
in particular, the left border of the control doesn't show. */
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.table-view > .placeholder {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0px;
|
||||
}
|
||||
|
||||
.table-view > .placeholder > .button {
|
||||
-fx-border-width: 0;
|
||||
-fx-border-color: transparent;
|
||||
-fx-background-radius: 0px;
|
||||
}
|
||||
|
||||
.table-view:focused {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-background-radius: 0, 0;
|
||||
/* There is some oddness if padding is in em values rather than pixels,
|
||||
in particular, the left border of the control doesn't show. */
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.table-view > .virtual-flow > .scroll-bar:vertical {
|
||||
-fx-background-insets: 0, 0 0 0 1;
|
||||
-fx-padding: -1 -1 -1 0;
|
||||
}
|
||||
|
||||
.table-view > .virtual-flow > .corner {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL ;
|
||||
-fx-background-insets: 0, 1 0 0 1;
|
||||
}
|
||||
|
||||
/* Each row in the table is a table-row-cell. Inside a table-row-cell is any
|
||||
number of table-cell. */
|
||||
.table-row-cell {
|
||||
-fx-background-color: GRAY_3, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 1 0;
|
||||
-fx-padding: 0.0em; /* 0 */
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
.table-row-cell:odd {
|
||||
-fx-background-color: GRAY_3, GRAY_1;
|
||||
-fx-background-insets: 0, 0 0 1 0;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
-fx-padding: 3px 6px 3px 6px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent CONTROL_BORDER_NORMAL transparent transparent;
|
||||
-fx-border-width: 1px;
|
||||
-fx-cell-size: 30px;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-text-overrun: center-ellipsis;
|
||||
}
|
||||
|
||||
.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected > .table-cell {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
/* selected, hover - not specified */
|
||||
|
||||
/* selected, focused, hover */
|
||||
/* selected, focused */
|
||||
/* selected */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:selected,
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected,
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected:hover {
|
||||
-fx-background-color: CONTROL_PRIMARY_BG_NORMAL, PRIMARY_D1;
|
||||
-fx-background-insets: 0 0 0 0, 1 1 1 3;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* focused */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* focused, hover */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:hover {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , PRIMARY_D2;
|
||||
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* hover */
|
||||
.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:hover {
|
||||
-fx-background-color: PRIMARY_D2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-background-insets: 0 0 1 0;
|
||||
}
|
||||
|
||||
/* The column-resize-line is shown when the user is attempting to resize a column. */
|
||||
.table-view .column-resize-line {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
-fx-padding: 0.0em 0.0416667em 0.0em 0.0416667em; /* 0 0.571429 0 0.571429 */
|
||||
}
|
||||
|
||||
/* This is the area behind the column headers. An ideal place to specify background
|
||||
and border colors for the whole area (not individual column-header's). */
|
||||
.table-view .column-header-background {
|
||||
-fx-background-color: GRAY_2;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
/* The column header row is made up of a number of column-header, one for each
|
||||
TableColumn, and a 'filler' area that extends from the right-most column
|
||||
to the edge of the tableview, or up to the 'column control' button. */
|
||||
.table-view .column-header {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-font-size: 1.083333em; /* 13pt ; 1 more than the default font */
|
||||
-fx-size: 24;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-color:
|
||||
transparent
|
||||
GRAY_3
|
||||
GRAY_3
|
||||
transparent;
|
||||
-fx-border-insets: 0 0 0 0;
|
||||
-fx-border-width: 0.083333em;
|
||||
}
|
||||
|
||||
.table-view .column-header .label {
|
||||
-fx-alignment: center;
|
||||
}
|
||||
|
||||
.table-view .empty-table {
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
PROGRESS_INDICATOR_END: GRAY_4;
|
||||
PROGRESS_BAR_BG: GRAY_8;
|
||||
|
||||
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-font-family: 'Open Sans';
|
||||
@@ -129,8 +130,7 @@
|
||||
}
|
||||
|
||||
.cryptic-text {
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-fill: TEXT_FILL;
|
||||
-fx-font-family: 'Fira Code';
|
||||
-fx-font-size: 1.1em;
|
||||
}
|
||||
@@ -655,11 +655,8 @@
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.update-indicator {
|
||||
-fx-background-color: white, RED_5;
|
||||
-fx-background-insets: 1px, 2px;
|
||||
-fx-background-radius: 6px, 5px;
|
||||
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
|
||||
.icon-update-indicator {
|
||||
-fx-fill: RED_5;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -1025,4 +1022,167 @@
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Decrypt Name Window
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.decrypt-name-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
|
||||
-fx-border-width: 0 0 1px 0;
|
||||
}
|
||||
|
||||
.decrypt-name-window .button-bar .button-right {
|
||||
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.decrypt-name-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.decrypt-name-window .table-view {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0,1;
|
||||
/* There is some oddness if padding is in em values rather than pixels,
|
||||
in particular, the left border of the control doesn't show. */
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.table-view > .placeholder {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0px;
|
||||
}
|
||||
|
||||
.table-view > .placeholder > .button {
|
||||
-fx-border-width: 0;
|
||||
-fx-border-color: transparent;
|
||||
-fx-background-radius: 0px;
|
||||
}
|
||||
|
||||
.table-view:focused {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-background-radius: 0, 0;
|
||||
/* There is some oddness if padding is in em values rather than pixels,
|
||||
in particular, the left border of the control doesn't show. */
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.table-view > .virtual-flow > .scroll-bar:vertical {
|
||||
-fx-background-insets: 0, 0 0 0 1;
|
||||
-fx-padding: -1 -1 -1 0;
|
||||
}
|
||||
|
||||
.table-view > .virtual-flow > .corner {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL ;
|
||||
-fx-background-insets: 0, 1 0 0 1;
|
||||
}
|
||||
|
||||
/* Each row in the table is a table-row-cell. Inside a table-row-cell is any
|
||||
number of table-cell. */
|
||||
.table-row-cell {
|
||||
-fx-background-color: GRAY_6, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 1 0;
|
||||
-fx-padding: 0.0em; /* 0 */
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
.table-row-cell:odd {
|
||||
-fx-background-color: GRAY_6, GRAY_9;
|
||||
-fx-background-insets: 0, 0 0 1 0;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
-fx-padding: 3px 6px 3px 6px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent CONTROL_BORDER_NORMAL transparent transparent;
|
||||
-fx-border-width: 1px;
|
||||
-fx-cell-size: 30px;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-text-overrun: center-ellipsis;
|
||||
}
|
||||
|
||||
.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected > .table-cell {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
/* selected, hover - not specified */
|
||||
|
||||
/* selected, focused, hover */
|
||||
/* selected, focused */
|
||||
/* selected */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:selected,
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected,
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected:hover {
|
||||
-fx-background-color: CONTROL_PRIMARY_BG_NORMAL, CONTROL_BG_SELECTED;
|
||||
-fx-background-insets: 0 0 0 0, 1 1 1 3;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* focused */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* focused, hover */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:hover {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , PRIMARY_L2;
|
||||
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* hover */
|
||||
.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:hover {
|
||||
-fx-background-color: PRIMARY_L2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-background-insets: 0 0 1 0;
|
||||
}
|
||||
|
||||
/* The column-resize-line is shown when the user is attempting to resize a column. */
|
||||
.table-view .column-resize-line {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
-fx-padding: 0.0em 0.0416667em 0.0em 0.0416667em; /* 0 0.571429 0 0.571429 */
|
||||
}
|
||||
|
||||
/* This is the area behind the column headers. An ideal place to specify background
|
||||
and border colors for the whole area (not individual column-header's). */
|
||||
.table-view .column-header-background {
|
||||
-fx-background-color: GRAY_7;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
/* The column header row is made up of a number of column-header, one for each
|
||||
TableColumn, and a 'filler' area that extends from the right-most column
|
||||
to the edge of the tableview, or up to the 'column control' button. */
|
||||
.table-view .column-header {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-font-size: 1.083333em; /* 13pt ; 1 more than the default font */
|
||||
-fx-size: 24;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-color:
|
||||
CONTROL_BORDER_NORMAL
|
||||
GRAY_5
|
||||
GRAY_5
|
||||
transparent;
|
||||
-fx-border-insets: 0 0 0 0;
|
||||
-fx-border-width: 0.083333em;
|
||||
}
|
||||
|
||||
.table-view .column-header .label {
|
||||
-fx-alignment: center;
|
||||
}
|
||||
|
||||
.table-view .empty-table {
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
|
||||
}
|
||||
|
||||
70
src/main/resources/fxml/decryptnames.fxml
Normal file
70
src/main/resources/fxml/decryptnames.fxml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.decryptname.DecryptFileNamesViewController"
|
||||
styleClass="decrypt-name-window"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145">
|
||||
<HBox styleClass="button-bar" alignment="CENTER">
|
||||
<padding>
|
||||
<Insets left="6"/>
|
||||
</padding>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#copyTableToClipboard">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CLIPBOARD" glyphSize="16"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%decryptNames.copyTable.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#clearTable">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%decryptNames.clearTable.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
</HBox>
|
||||
<TableView fx:id="cipherToCleartextTable" VBox.vgrow="ALWAYS">
|
||||
<placeholder>
|
||||
<Button alignment="CENTER" onAction="#selectFiles" text="${controller.dropZoneText}" contentDisplay="TOP" maxWidth="Infinity" maxHeight="Infinity">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="${controller.dropZoneIcon}" glyphSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</placeholder>
|
||||
<columns>
|
||||
<TableColumn fx:id="ciphertextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="LOCK"/>
|
||||
</graphic>
|
||||
</TableColumn>
|
||||
<TableColumn fx:id="cleartextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="LOCK_OPEN"/>
|
||||
</graphic>
|
||||
</TableColumn>
|
||||
</columns>
|
||||
</TableView>
|
||||
<HBox>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<FormattedLabel styleClass="label-small" format="%decryptNames.copyHint" arg1="${controller.copyToClipboardShortcutString}"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
@@ -20,7 +20,7 @@
|
||||
<padding>
|
||||
<Insets left="6" />
|
||||
</padding>
|
||||
<ChoiceBox fx:id="vaultFilterChoiceBox"/>
|
||||
<ChoiceBox fx:id="vaultFilterChoiceBox" minWidth="42"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button styleClass="button-right" onAction="#clearEvents" contentDisplay="GRAPHIC_ONLY">
|
||||
<graphic>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<Tooltip text="%main.vaultDetail.locateEncryptedFileBtn.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<!-- TODO: instead of showing a button, show on error a small dialog and if copied, show a tooltip -->
|
||||
<Button styleClass="drag-n-drop" text="%main.vaultDetail.encryptedPathsCopied" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseDecryptedFileAndReveal" contentDisplay="TOP" visible="${controller.ciphertextPathsCopied}" managed="${controller.ciphertextPathsCopied}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
|
||||
@@ -65,25 +66,14 @@
|
||||
</Button>
|
||||
</StackPane>
|
||||
<!-- decrypt file name -->
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="0"/>
|
||||
</padding>
|
||||
<Button fx:id="decryptNameDropZone" styleClass="drag-n-drop" text="%main.vaultDetail.decryptName.buttonLabel" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseEncryptedFileAndCopyNames" contentDisplay="TOP" visible="${!controller.cleartextNamesCopied}" managed="${!controller.cleartextNamesCopied}">
|
||||
<graphic>
|
||||
<Text styleClass="cryptic-text" text="101010 → abc"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.vaultDetail.decryptName.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button styleClass="drag-n-drop" text="%main.vaultDetail.decryptName.copied" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseEncryptedFileAndCopyNames" contentDisplay="TOP" visible="${controller.cleartextNamesCopied}" managed="${controller.cleartextNamesCopied}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</StackPane>
|
||||
|
||||
<Button fx:id="decryptNameDropZone" styleClass="drag-n-drop" text="%main.vaultDetail.decryptName.buttonLabel" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#showDecryptNameWindow" contentDisplay="TOP">
|
||||
<graphic>
|
||||
<Text styleClass="cryptic-text" text="101010 → abc"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.vaultDetail.decryptName.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
|
||||
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM" prefHeight="72">
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Arc?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:id="root"
|
||||
@@ -51,7 +55,9 @@
|
||||
<Tooltip text="%main.vaultlist.showEventsButton.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Region styleClass="update-indicator" visible="${controller.newEventsPresent}" mouseTransparent="true" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
||||
<AnchorPane mouseTransparent="true" minWidth="12" maxWidth="12" minHeight="12" maxHeight="12" StackPane.alignment="CENTER">
|
||||
<Circle radius="4" styleClass="icon-update-indicator" AnchorPane.topAnchor="-8" AnchorPane.rightAnchor="-6" visible="${controller.unreadEventsPresent}" />
|
||||
</AnchorPane>
|
||||
</StackPane>
|
||||
<Button onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
|
||||
<graphic>
|
||||
|
||||
@@ -427,9 +427,7 @@ main.vaultDetail.locateEncryptedFileBtn.tooltip=Choose a file from your vault to
|
||||
main.vaultDetail.encryptedPathsCopied=Paths Copied to Clipboard!
|
||||
main.vaultDetail.locateEncrypted.filePickerTitle=Select File Inside Vault
|
||||
main.vaultDetail.decryptName.buttonLabel=Decrypt File Name
|
||||
main.vaultDetail.decryptName.filePickerTitle=Select encrypted file
|
||||
main.vaultDetail.decryptName.tooltip=Choose an encrypted vault file to decrypt its name
|
||||
main.vaultDetail.decryptName.copied=Names copied to clipboard!
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
|
||||
main.vaultDetail.missing.recheck=Recheck
|
||||
@@ -585,12 +583,25 @@ shareVault.hub.instruction.1=1. Share access of the encrypted vault folder via c
|
||||
shareVault.hub.instruction.2=2. Grant access to team member in Cryptomator Hub.
|
||||
shareVault.hub.openHub=Open Cryptomator Hub
|
||||
|
||||
# Decrypt File Names
|
||||
decryptNames.title=Decrypt File Names
|
||||
decryptNames.filePicker.title=Select encrypted file
|
||||
decryptNames.filePicker.extensionDescription=Cryptomator encrypted file
|
||||
decryptNames.copyTable.tooltip=Copy table
|
||||
decryptNames.clearTable.tooltip=Clear table
|
||||
decryptNames.copyHint=Copy cell content with %s
|
||||
decryptNames.dropZone.message=Drop files or click to select
|
||||
decryptNames.dropZone.error.vaultInternalFiles=Vault internal files with no decrypt-able name selected
|
||||
decryptNames.dropZone.error.foreignFiles=Files do not belong to vault "%s"
|
||||
decryptNames.dropZone.error.noDirIdBackup=Directory of selected files does not contain dirId.c9r file
|
||||
decryptNames.dropZone.error.generic=Failed to decrypt file names
|
||||
|
||||
|
||||
# Event View
|
||||
eventView.title=Events
|
||||
eventView.filter.allVaults=All
|
||||
eventView.clearListButton.tooltip=Dismiss all
|
||||
eventView.clearListButton.tooltip=Clear list
|
||||
## event list entries
|
||||
eventView.entry.vaultLocked.message=***********
|
||||
eventView.entry.vaultLocked.description=Unlock "%s" for details
|
||||
eventView.entry.conflictResolved.message=Resolved conflict
|
||||
eventView.entry.conflictResolved.showDecrypted=Show decrypted file
|
||||
|
||||
Reference in New Issue
Block a user