mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 16:51:28 +00:00
Compare commits
121 Commits
feature/hy
...
feature/tm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0f3facab8 | ||
|
|
b9512c9e93 | ||
|
|
dd724220f8 | ||
|
|
94410f1839 | ||
|
|
cc8aa00326 | ||
|
|
e8e2fcb0b3 | ||
|
|
151b35355d | ||
|
|
37d5353f77 | ||
|
|
7b6953445f | ||
|
|
fcd3db63ce | ||
|
|
d67085d57d | ||
|
|
1f60d9f5e8 | ||
|
|
5378769467 | ||
|
|
913ed5e109 | ||
|
|
f4cfe19fdc | ||
|
|
a001dfd8a8 | ||
|
|
893a4bcae9 | ||
|
|
e61fb74367 | ||
|
|
fbbbc1cb40 | ||
|
|
f27f3c46c1 | ||
|
|
83b557b6be | ||
|
|
32a0df06d0 | ||
|
|
5350e07f62 | ||
|
|
cc5c46743b | ||
|
|
bc7f3fe7db | ||
|
|
750ca3f39c | ||
|
|
2bbffc3623 | ||
|
|
36dd98127d | ||
|
|
6d55331ef2 | ||
|
|
181e45a596 | ||
|
|
3c8e506805 | ||
|
|
f0a7379575 | ||
|
|
0d12b11c48 | ||
|
|
14a025f769 | ||
|
|
598fc9bbc8 | ||
|
|
fbab2df00f | ||
|
|
48c2d49c86 | ||
|
|
00d68393f6 | ||
|
|
7a50ba0b43 | ||
|
|
b2bee99286 | ||
|
|
88d384afdf | ||
|
|
7af82b831f | ||
|
|
a86f42fa44 | ||
|
|
70b0413777 | ||
|
|
577a77af38 | ||
|
|
3ba7e9ba00 | ||
|
|
b2250e8ce0 | ||
|
|
f2a480c7a0 | ||
|
|
bda38096e1 | ||
|
|
7e66a61294 | ||
|
|
3aa627a467 | ||
|
|
ae1b5fc925 | ||
|
|
b1076f9c86 | ||
|
|
ede1c72595 | ||
|
|
d2fcd5b64f | ||
|
|
6d9704ffa2 | ||
|
|
40df3d015a | ||
|
|
4e22706d92 | ||
|
|
341710f724 | ||
|
|
8386183f7a | ||
|
|
b76a7d6895 | ||
|
|
4a397a8151 | ||
|
|
2239a3e6a3 | ||
|
|
5318368879 | ||
|
|
41a279da9d | ||
|
|
478c69f82c | ||
|
|
e3073a3613 | ||
|
|
3333dc7f22 | ||
|
|
12ec6fbec8 | ||
|
|
034278c0a0 | ||
|
|
92b3a9e1bd | ||
|
|
fc7169f2a0 | ||
|
|
c39710ede0 | ||
|
|
cc0b6aed15 | ||
|
|
f8a83c9cc8 | ||
|
|
43ce64f01e | ||
|
|
d585a03f76 | ||
|
|
f953be6237 | ||
|
|
e81bbe197b | ||
|
|
738fa4da12 | ||
|
|
9a08dcc46d | ||
|
|
74ef8d915d | ||
|
|
025a7a5582 | ||
|
|
3b2ddcf98d | ||
|
|
c10fc0ee5b | ||
|
|
aaa37e2c7a | ||
|
|
7af7c920e3 | ||
|
|
a4f79fb98a | ||
|
|
f429f2d3e7 | ||
|
|
4f6e091c13 | ||
|
|
2df779f7ab | ||
|
|
5b9d9150c5 | ||
|
|
f0aaec2058 | ||
|
|
4adc4a9175 | ||
|
|
5a97060fac | ||
|
|
db96074119 | ||
|
|
7ecc10bc79 | ||
|
|
44fe4a6f8a | ||
|
|
76a4ef50cb | ||
|
|
ca2f80024a | ||
|
|
dcd7077b08 | ||
|
|
31482d7d18 | ||
|
|
5ef666154e | ||
|
|
a810eff797 | ||
|
|
664158eb84 | ||
|
|
6b43881909 | ||
|
|
126004b1f8 | ||
|
|
ba34cfa9d5 | ||
|
|
03b4ad85ef | ||
|
|
1abfcd495f | ||
|
|
69964a80f1 | ||
|
|
aa8306ea4a | ||
|
|
3fecde37f4 | ||
|
|
2fc5fd99fb | ||
|
|
7edacfea70 | ||
|
|
7020fa49d9 | ||
|
|
7cea0bb33c | ||
|
|
3d622b18dc | ||
|
|
62cd506588 | ||
|
|
b25b5bd5a3 | ||
|
|
6365c22297 |
18
.github/ISSUE_TEMPLATE/bug.yml
vendored
18
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,7 +1,14 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
labels: ["type:bug"]
|
||||
type: "Bug"
|
||||
body:
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
@@ -11,13 +18,6 @@ body:
|
||||
required: true
|
||||
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: software-versions
|
||||
attributes:
|
||||
@@ -97,4 +97,4 @@ body:
|
||||
id: further-info
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
|
||||
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
|
||||
16
.github/ISSUE_TEMPLATE/feature.yml
vendored
16
.github/ISSUE_TEMPLATE/feature.yml
vendored
@@ -1,7 +1,14 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
labels: ["type:feature-request"]
|
||||
type: "Feature"
|
||||
body:
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
@@ -11,13 +18,6 @@ body:
|
||||
required: true
|
||||
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
|
||||
2
.github/workflows/debian.yml
vendored
2
.github/workflows/debian.yml
vendored
@@ -28,7 +28,7 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
name: Build Debian Package
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: versions
|
||||
|
||||
25
.idea/compiler.xml
generated
25
.idea/compiler.xml
generated
@@ -39,31 +39,6 @@
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.55/dagger-compiler-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.55/dagger-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.55/dagger-spi-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.0.21-1.0.28/symbol-processing-api-2.0.21-1.0.28.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-jre.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/3.41.0/checker-qual-3.41.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.23.0/error_prone_annotations-2.23.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
||||
</processorPath>
|
||||
<module name="cryptomator" />
|
||||
</profile>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[](https://cryptomator.org/)
|
||||
|
||||
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
||||
[](https://github.com/cryptomator/cryptomator/actions/workflows/build.yml?query=branch%3Adevelop)
|
||||
[](https://snyk.io/test/github/cryptomator/cryptomator)
|
||||
[](https://sonarcloud.io/dashboard?id=cryptomator_cryptomator)
|
||||
[](https://mastodon.online/@cryptomator)
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2018 Armin Schrenk <armin.schrenk@zoho.eu> -->
|
||||
<component type="desktop-application">
|
||||
<id>org.cryptomator.Cryptomator</id>
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<project_license>GPL-3.0-or-later</project_license>
|
||||
<name>Cryptomator</name>
|
||||
<summary>Encryption made easy and optimized for the cloud</summary>
|
||||
<summary>Encryption for your cloud made easy</summary>
|
||||
|
||||
<keywords>
|
||||
<keyword>encryption</keyword>
|
||||
<keyword>security</keyword>
|
||||
<keyword>privacy</keyword>
|
||||
</keywords>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
@@ -44,12 +49,16 @@
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Light theme</caption>
|
||||
<image>https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png</image>
|
||||
<caption>Encrypts your data, protects your privacy</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Dark theme</caption>
|
||||
<image>https://user-images.githubusercontent.com/11858409/156986113-6c5d7801-86e0-4643-bc2f-aff9d95d3ce0.png</image>
|
||||
<caption>Dark theme available</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_dark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Easy to use - work on encrypted files as if they were not</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
@@ -74,6 +83,9 @@
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release date="2025-02-05" version="1.15.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.1</url>
|
||||
</release>
|
||||
<release date="2025-02-03" version="1.15.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.0</url>
|
||||
</release>
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -33,7 +33,7 @@
|
||||
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>2.8.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.cryptofs.version>2.9.0-beta2</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.5.0</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.2.4</cryptomator.integrations.mac.version>
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
<!-- build-time dependencies -->
|
||||
<jetbrains.annotations.version>26.0.1</jetbrains.annotations.version>
|
||||
<dependency-check.version>12.0.1</dependency-check.version>
|
||||
<dependency-check.version>12.1.0</dependency-check.version>
|
||||
<jacoco.version>0.8.12</jacoco.version>
|
||||
<license-generator.version>2.5.0</license-generator.version>
|
||||
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
|
||||
|
||||
@@ -58,6 +58,7 @@ public class VaultSettings {
|
||||
public final StringExpression mountName;
|
||||
public final StringProperty mountService;
|
||||
public final IntegerProperty port;
|
||||
public final StringProperty lastKnownKeyLoader;
|
||||
|
||||
VaultSettings(VaultSettingsJson json) {
|
||||
this.id = json.id;
|
||||
@@ -74,6 +75,7 @@ public class VaultSettings {
|
||||
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
|
||||
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
|
||||
this.port = new SimpleIntegerProperty(this, "port", json.port);
|
||||
this.lastKnownKeyLoader = new SimpleStringProperty(this, "lastKnownKeyLoader", json.lastKnownKeyLoader);
|
||||
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
|
||||
final String name;
|
||||
@@ -99,7 +101,7 @@ public class VaultSettings {
|
||||
}
|
||||
|
||||
Observable[] observables() {
|
||||
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService};
|
||||
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService, lastKnownKeyLoader};
|
||||
}
|
||||
|
||||
public static VaultSettings withRandomId() {
|
||||
@@ -130,6 +132,7 @@ public class VaultSettings {
|
||||
json.mountPoint = mountPoint.map(Path::toString).getValue();
|
||||
json.mountService = mountService.get();
|
||||
json.port = port.get();
|
||||
json.lastKnownKeyLoader = lastKnownKeyLoader.get();
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ class VaultSettingsJson {
|
||||
@JsonProperty("mountService")
|
||||
String mountService;
|
||||
|
||||
@JsonProperty("lastKnownKeyLoader")
|
||||
String lastKnownKeyLoader;
|
||||
|
||||
@JsonProperty("port")
|
||||
int port = VaultSettings.DEFAULT_PORT;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.common.mount.Mounter;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
@@ -18,6 +19,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
@@ -74,6 +76,7 @@ public class Vault {
|
||||
private final ObjectBinding<Mountpoint> mountPoint;
|
||||
private final Mounter mounter;
|
||||
private final Settings settings;
|
||||
private final FileSystemEventAggregator fileSystemEventAggregator;
|
||||
private final BooleanProperty showingStats;
|
||||
|
||||
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
||||
@@ -85,7 +88,8 @@ public class Vault {
|
||||
VaultState state, //
|
||||
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
|
||||
VaultStats stats, //
|
||||
Mounter mounter, Settings settings) {
|
||||
Mounter mounter, Settings settings, //
|
||||
FileSystemEventAggregator fileSystemEventAggregator) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.configCache = configCache;
|
||||
this.cryptoFileSystem = cryptoFileSystem;
|
||||
@@ -102,6 +106,7 @@ public class Vault {
|
||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||
this.mounter = mounter;
|
||||
this.settings = settings;
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
this.quickAccessEntry = new AtomicReference<>(null);
|
||||
}
|
||||
@@ -143,6 +148,7 @@ public class Vault {
|
||||
.withFlags(flags) //
|
||||
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
|
||||
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
|
||||
.withFilesystemEventConsumer(this::consumeVaultEvent) //
|
||||
.build();
|
||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||
}
|
||||
@@ -251,6 +257,11 @@ public class Vault {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void consumeVaultEvent(FilesystemEvent e) {
|
||||
fileSystemEventAggregator.put(this, e);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Observable Properties
|
||||
// *******************************************************************************
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@@ -49,9 +50,9 @@ public class VaultListManager {
|
||||
@Inject
|
||||
public VaultListManager(ObservableList<Vault> vaultList, //
|
||||
AutoLocker autoLocker, //
|
||||
List<MountService> mountServices,
|
||||
VaultComponent.Factory vaultComponentFactory,
|
||||
ResourceBundle resourceBundle,
|
||||
List<MountService> mountServices, //
|
||||
VaultComponent.Factory vaultComponentFactory, //
|
||||
ResourceBundle resourceBundle, //
|
||||
Settings settings) {
|
||||
this.vaultList = vaultList;
|
||||
this.autoLocker = autoLocker;
|
||||
@@ -114,6 +115,10 @@ public class VaultListManager {
|
||||
private Vault create(VaultSettings vaultSettings) {
|
||||
var wrapper = new VaultConfigCache(vaultSettings);
|
||||
try {
|
||||
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
|
||||
var keyIdScheme = wrapper.get().getKeyId().getScheme();
|
||||
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
|
||||
}
|
||||
var vaultState = determineVaultState(vaultSettings.path.get());
|
||||
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
||||
wrapper.reloadConfig();
|
||||
|
||||
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,101 @@
|
||||
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;
|
||||
|
||||
@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);
|
||||
hasUpdates.set(true);
|
||||
map.compute(key, (k, val) -> {
|
||||
if (val == null) {
|
||||
return new FSEventBucketContent(e, 1);
|
||||
} else {
|
||||
return new FSEventBucketContent(e, val.count() + 1);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event bucket from the map.
|
||||
*/
|
||||
public FSEventBucketContent remove(FSEventBucket key) {
|
||||
hasUpdates.set(true);
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the event map.
|
||||
*/
|
||||
public void clear() {
|
||||
hasUpdates.set(true);
|
||||
map.clear();
|
||||
}
|
||||
|
||||
|
||||
public boolean hasUpdates() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class CreateNewVaultExpertSettingsController implements FxController {
|
||||
|
||||
public static final int MAX_SHORTENING_THRESHOLD = 220;
|
||||
public static final int MIN_SHORTENING_THRESHOLD = 36;
|
||||
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/en/1.7/security/architecture/#name-shortening";
|
||||
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/architecture/#name-shortening";
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Application> application;
|
||||
|
||||
@@ -76,8 +76,10 @@ public class ReadmeGenerator {
|
||||
input.chars().forEachOrdered(c -> {
|
||||
if (c < 128) {
|
||||
sb.append((char) c);
|
||||
} else if (c <= 0xFF) {
|
||||
sb.append("\\'").append(String.format("%02X", c));
|
||||
} else if (c < 0xFFFF) {
|
||||
sb.append("\\u").append(c);
|
||||
sb.append("\\uc1\\u").append(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public enum FxmlFile {
|
||||
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), //
|
||||
ERROR("/fxml/error.fxml"), //
|
||||
EVENT_VIEW("/fxml/eventview.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
HEALTH_START("/fxml/health_start.fxml"), //
|
||||
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
|
||||
|
||||
@@ -7,6 +7,7 @@ public enum FontAwesome5Icon {
|
||||
ANCHOR("\uF13D"), //
|
||||
ARROW_UP("\uF062"), //
|
||||
BAN("\uF05E"), //
|
||||
BELL("\uF0F3"), //
|
||||
BUG("\uF188"), //
|
||||
CARET_DOWN("\uF0D7"), //
|
||||
CARET_RIGHT("\uF0Da"), //
|
||||
@@ -15,10 +16,12 @@ public enum FontAwesome5Icon {
|
||||
CLIPBOARD("\uF328"), //
|
||||
COG("\uF013"), //
|
||||
COGS("\uF085"), //
|
||||
COMPRESS_ALT("\uF422"), //
|
||||
COPY("\uF0C5"), //
|
||||
CROWN("\uF521"), //
|
||||
DONATE("\uF4B9"), //
|
||||
EDIT("\uF044"), //
|
||||
ELLIPSIS_V("\uF142"), //
|
||||
EXCHANGE_ALT("\uF362"), //
|
||||
EXCLAMATION("\uF12A"), //
|
||||
EXCLAMATION_CIRCLE("\uF06A"), //
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
@@ -108,6 +109,7 @@ public class HubToPasswordConvertController implements FxController {
|
||||
.thenRunAsync(this::convertInternal, backgroundExecutorService) //
|
||||
.whenCompleteAsync((result, exception) -> {
|
||||
if (exception == null) {
|
||||
vault.getVaultSettings().lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
|
||||
LOG.info("Conversion of vault {} succeeded.", vault.getPath());
|
||||
window.setScene(successScene.get());
|
||||
} else {
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Dialogs {
|
||||
.setMessageKey("removeVault.message") //
|
||||
.setDescriptionKey("removeVault.description") //
|
||||
.setIcon(FontAwesome5Icon.QUESTION) //
|
||||
.setOkButtonKey("removeVault.confirmBtn") //
|
||||
.setOkButtonKey("generic.button.remove") //
|
||||
.setCancelButtonKey("generic.button.cancel") //
|
||||
.setOkAction(stage -> {
|
||||
LOG.debug("Removing vault {}.", vault.getDisplayName());
|
||||
@@ -54,7 +54,7 @@ public class Dialogs {
|
||||
.setMessageKey("removeCert.message") //
|
||||
.setDescriptionKey("removeCert.description") //
|
||||
.setIcon(FontAwesome5Icon.QUESTION) //
|
||||
.setOkButtonKey("removeCert.confirmBtn") //
|
||||
.setOkButtonKey("generic.button.remove") //
|
||||
.setCancelButtonKey("generic.button.cancel") //
|
||||
.setOkAction(stage -> {
|
||||
settings.licenseKey.set(null);
|
||||
|
||||
@@ -31,8 +31,9 @@ public class SimpleDialog {
|
||||
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
|
||||
new SimpleDialogController(resolveText(builder.messageKey, null), //
|
||||
resolveText(builder.descriptionKey, null), //
|
||||
builder.icon, resolveText(builder.okButtonKey, null), //
|
||||
resolveText(builder.cancelButtonKey, null), //
|
||||
builder.icon, //
|
||||
resolveText(builder.okButtonKey, null), //
|
||||
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
|
||||
() -> builder.okAction.accept(dialogStage), //
|
||||
() -> builder.cancelAction.accept(dialogStage)), //
|
||||
Scene::new, builder.resourceBundle);
|
||||
@@ -67,7 +68,6 @@ public class SimpleDialog {
|
||||
private String descriptionKey;
|
||||
private String okButtonKey;
|
||||
private String cancelButtonKey;
|
||||
|
||||
private FontAwesome5Icon icon;
|
||||
private Consumer<Stage> okAction = Stage::close;
|
||||
private Consumer<Stage> cancelAction = Stage::close;
|
||||
@@ -128,7 +128,6 @@ public class SimpleDialog {
|
||||
Objects.requireNonNull(messageKey, "SimpleDialog messageKey must be set.");
|
||||
Objects.requireNonNull(descriptionKey, "SimpleDialog descriptionKey must be set.");
|
||||
Objects.requireNonNull(okButtonKey, "SimpleDialog okButtonKey must be set.");
|
||||
Objects.requireNonNull(cancelButtonKey, "SimpleDialog cancelButtonKey must be set.");
|
||||
|
||||
try {
|
||||
return new SimpleDialog(this);
|
||||
|
||||
@@ -14,6 +14,7 @@ public class SimpleDialogController implements FxController {
|
||||
private final String cancelButtonText;
|
||||
private final Runnable okAction;
|
||||
private final Runnable cancelAction;
|
||||
private final boolean cancelButtonVisible;
|
||||
|
||||
public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) {
|
||||
this.message = message;
|
||||
@@ -23,6 +24,11 @@ public class SimpleDialogController implements FxController {
|
||||
this.cancelButtonText = cancelButtonText;
|
||||
this.okAction = okAction;
|
||||
this.cancelAction = cancelAction;
|
||||
this.cancelButtonVisible = cancelButtonText != null && !cancelButtonText.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isCancelButtonVisible() {
|
||||
return cancelButtonVisible;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.ObservableUtil;
|
||||
import org.cryptomator.cryptofs.CryptoPath;
|
||||
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
|
||||
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
|
||||
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
|
||||
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.util.Duration;
|
||||
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;
|
||||
|
||||
public class EventListCellController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EventListCellController.class);
|
||||
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 FileSystemEventAggregator fileSystemEventAggregator;
|
||||
@Nullable
|
||||
private final RevealPathService revealService;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final ObjectProperty<Map.Entry<FSEventBucket, FSEventBucketContent>> eventEntry;
|
||||
private final StringProperty eventMessage;
|
||||
private final StringProperty eventDescription;
|
||||
private final ObjectProperty<FontAwesome5Icon> eventIcon;
|
||||
private final ObservableValue<String> eventCount;
|
||||
private final ObservableValue<Boolean> vaultUnlocked;
|
||||
private final ObservableValue<String> readableTime;
|
||||
private final ObservableValue<String> readableDate;
|
||||
private final ObservableValue<String> message;
|
||||
private final ObservableValue<String> description;
|
||||
private final ObservableValue<FontAwesome5Icon> icon;
|
||||
private final BooleanProperty actionsButtonVisible;
|
||||
private final Tooltip eventTooltip;
|
||||
|
||||
@FXML
|
||||
HBox root;
|
||||
@FXML
|
||||
ContextMenu eventActionsMenu;
|
||||
@FXML
|
||||
Button eventActionsButton;
|
||||
|
||||
@Inject
|
||||
public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.revealService = revealService.orElseGet(() -> null);
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.eventEntry = new SimpleObjectProperty<>(null);
|
||||
this.eventMessage = new SimpleStringProperty();
|
||||
this.eventDescription = new SimpleStringProperty();
|
||||
this.eventIcon = new SimpleObjectProperty<>();
|
||||
this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1? "" : "("+ e.getValue().count() +")", "");
|
||||
this.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);
|
||||
this.actionsButtonVisible = new SimpleBooleanProperty();
|
||||
this.eventTooltip = new Tooltip();
|
||||
eventTooltip.setShowDelay(Duration.millis(500.0));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
actionsButtonVisible.bind(Bindings.createBooleanBinding(this::determineActionsButtonVisibility, root.hoverProperty(), eventActionsMenu.showingProperty(), vaultUnlocked));
|
||||
vaultUnlocked.addListener((_, _, newValue) -> eventActionsMenu.hide());
|
||||
Tooltip.install(root, eventTooltip);
|
||||
}
|
||||
|
||||
private boolean determineActionsButtonVisibility() {
|
||||
return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover());
|
||||
}
|
||||
|
||||
public void setEventEntry(@NotNull Map.Entry<FSEventBucket, FSEventBucketContent> item) {
|
||||
eventEntry.set(item);
|
||||
eventActionsMenu.hide();
|
||||
eventActionsMenu.getItems().clear();
|
||||
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);
|
||||
case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse);
|
||||
case BrokenFileNodeEvent fse -> this.adjustToBrokenFileNodeEvent(fse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void adjustToBrokenFileNodeEvent(BrokenFileNodeEvent bfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message"));
|
||||
eventDescription.setValue(bfe.ciphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, convertVaultPathToSystemPath(bfe.ciphertextPath())));
|
||||
} else {
|
||||
addAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.ciphertextPath()).toString()));
|
||||
}
|
||||
addAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
|
||||
}
|
||||
|
||||
private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
|
||||
eventIcon.setValue(FontAwesome5Icon.CHECK);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message"));
|
||||
eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
|
||||
} else {
|
||||
addAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToConflictEvent(ConflictResolutionFailedEvent cfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.COMPRESS_ALT);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflict.message"));
|
||||
eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
|
||||
addAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
|
||||
addAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToDecryptionFailedEvent(DecryptionFailedEvent dfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.BAN);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.decryptionFailed.message"));
|
||||
eventDescription.setValue(dfe.ciphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToBrokenDirFileEvent(BrokenDirFileEvent bde) {
|
||||
eventIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenDirFile.message"));
|
||||
eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void addAction(String localizationKey, Runnable action) {
|
||||
var entry = new MenuItem(resourceBundle.getString(localizationKey));
|
||||
entry.getStyleClass().addLast("dropdown-button-context-menu-item");
|
||||
entry.setOnAction(_ -> action.run());
|
||||
eventActionsMenu.getItems().addLast(entry);
|
||||
}
|
||||
|
||||
|
||||
private FontAwesome5Icon selectIcon() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventIcon.getValue();
|
||||
} else {
|
||||
return FontAwesome5Icon.LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
private String selectMessage() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventMessage.getValue();
|
||||
} else {
|
||||
return resourceBundle.getString("eventView.entry.vaultLocked.message");
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void toggleEventActionsMenu() {
|
||||
var e = eventEntry.get();
|
||||
if (e != null) {
|
||||
if (eventActionsMenu.isShowing()) {
|
||||
eventActionsMenu.hide();
|
||||
} else {
|
||||
eventActionsMenu.show(eventActionsButton, Side.BOTTOM, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path convertVaultPathToSystemPath(Path p) {
|
||||
if (!(p instanceof CryptoPath)) {
|
||||
throw new IllegalArgumentException("Path " + p + " is not a vault path");
|
||||
}
|
||||
var v = eventEntry.getValue().getKey().vault();
|
||||
if (!v.isUnlocked()) {
|
||||
return Path.of(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
var mountUri = v.getMountPoint().uri();
|
||||
var internalPath = p.toString().substring(1);
|
||||
return Path.of(mountUri.getPath().concat(internalPath).substring(1));
|
||||
}
|
||||
|
||||
private void reveal(RevealPathService s, Path p) {
|
||||
try {
|
||||
s.reveal(p);
|
||||
} catch (RevealFailedException e) {
|
||||
LOG.warn("Failed to show path {}", p, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyToClipboard(String s) {
|
||||
var content = new ClipboardContent();
|
||||
content.putString(s);
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
}
|
||||
|
||||
//-- property accessors --
|
||||
public ObservableValue<String> messageProperty() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> countProperty() {
|
||||
return eventCount;
|
||||
}
|
||||
|
||||
public String getCount() {
|
||||
return eventCount.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> descriptionProperty() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<FontAwesome5Icon> iconProperty() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getIcon() {
|
||||
return icon.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> actionsButtonVisibleProperty() {
|
||||
return actionsButtonVisible;
|
||||
}
|
||||
|
||||
public boolean isActionsButtonVisible() {
|
||||
return actionsButtonVisible.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> eventLocalTimeProperty() {
|
||||
return readableTime;
|
||||
}
|
||||
|
||||
public String getEventLocalTime() {
|
||||
return readableTime.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> eventLocalDateProperty() {
|
||||
return readableDate;
|
||||
}
|
||||
|
||||
public String getEventLocalDate() {
|
||||
return readableDate.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> vaultUnlockedProperty() {
|
||||
return vaultUnlocked;
|
||||
}
|
||||
|
||||
public boolean isVaultUnlocked() {
|
||||
return vaultUnlocked.getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.ListCell;
|
||||
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<Map.Entry<FSEventBucket, FSEventBucketContent>>, ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>>> {
|
||||
|
||||
private static final String FXML_PATH = "/fxml/eventview_cell.fxml";
|
||||
|
||||
private final FxmlLoaderFactory fxmlLoaders;
|
||||
|
||||
@Inject
|
||||
EventListCellFactory(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
this.fxmlLoaders = fxmlLoaders;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to load %s.".formatted(FXML_PATH), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cell extends ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> {
|
||||
|
||||
private final Parent root;
|
||||
private final EventListCellController controller;
|
||||
|
||||
public Cell(Parent root, EventListCellController controller) {
|
||||
this.root = root;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Map.Entry<FSEventBucket, FSEventBucketContent> item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
this.getStyleClass().remove("list-cell");
|
||||
} else {
|
||||
this.getStyleClass().addLast("list-cell");
|
||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
setGraphic(root);
|
||||
controller.setEventEntry(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@EventViewScoped
|
||||
@Subcomponent(modules = {EventViewModule.class})
|
||||
public interface EventViewComponent {
|
||||
|
||||
@EventViewWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.EVENT_VIEW)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default Stage showEventViewerWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
stage.requestFocus();
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
EventViewComponent create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
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.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.util.StringConverter;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@EventViewScoped
|
||||
public class EventViewController implements FxController {
|
||||
|
||||
private final FilteredList<Map.Entry<FSEventBucket, FSEventBucketContent>> filteredEventList;
|
||||
private final ObservableList<Vault> vaults;
|
||||
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;
|
||||
|
||||
@FXML
|
||||
ChoiceBox<Vault> vaultFilterChoiceBox;
|
||||
@FXML
|
||||
ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView;
|
||||
|
||||
@Inject
|
||||
public EventViewController(FxFSEventList fxFSEventList, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory, FileSystemEventAggregator aggregator) {
|
||||
this.filteredEventList = fxFSEventList.getObservableList().filtered(_ -> true);
|
||||
this.vaults = vaults;
|
||||
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);
|
||||
choiceBoxEntries.addAll(vaults);
|
||||
vaults.addListener((ListChangeListener<? super Vault>) c -> {
|
||||
while (c.next()) {
|
||||
choiceBoxEntries.removeAll(c.getRemoved());
|
||||
choiceBoxEntries.addAll(c.getAddedSubList());
|
||||
}
|
||||
});
|
||||
|
||||
eventListView.setCellFactory(cellFactory);
|
||||
eventListView.setItems(sortedEventList);
|
||||
|
||||
vaultFilterChoiceBox.setItems(choiceBoxEntries);
|
||||
vaultFilterChoiceBox.valueProperty().addListener(this::applyVaultFilter);
|
||||
vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle));
|
||||
}
|
||||
|
||||
private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) {
|
||||
if (newV == null) {
|
||||
filteredEventList.setPredicate(_ -> true);
|
||||
} else {
|
||||
filteredEventList.setPredicate(e -> e.getKey().vault().equals(newV));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void clearEvents() {
|
||||
aggregator.clear();
|
||||
}
|
||||
|
||||
private static class VaultConverter extends StringConverter<Vault> {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
VaultConverter(ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Vault v) {
|
||||
if (v == null) {
|
||||
return resourceBundle.getString("eventView.filter.allVaults");
|
||||
} else {
|
||||
return v.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vault fromString(String displayLanguage) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
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 org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
|
||||
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
|
||||
abstract class EventViewModule {
|
||||
|
||||
@Provides
|
||||
@EventViewScoped
|
||||
@EventViewWindow
|
||||
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;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EventViewScoped
|
||||
@EventViewWindow
|
||||
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.EVENT_VIEW)
|
||||
@EventViewScoped
|
||||
static Scene provideEventViewerScene(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.EVENT_VIEW);
|
||||
}
|
||||
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(EventViewController.class)
|
||||
abstract FxController bindEventViewController(EventViewController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(EventListCellController.class)
|
||||
abstract FxController bindEventListCellController(EventListCellController controller);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
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 EventViewScoped {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
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 EventViewWindow {
|
||||
|
||||
}
|
||||
@@ -43,8 +43,8 @@ public class AutoUnlocker {
|
||||
private CompletionStage<Void> unlockSequentially(Stream<Vault> vaultStream) {
|
||||
// this is an attempt to run all the unlock workflows sequentially, i.e. start the next workflow only after completing/failing the previous workflow.
|
||||
return vaultStream.filter(Vault::isLocked).reduce(CompletableFuture.completedFuture(null),
|
||||
(prevUnlock, nextVault) -> prevUnlock.thenCompose(unused -> appWindows.startUnlockWorkflow(nextVault, null)),
|
||||
(prevUnlock, nextUnlock) -> nextUnlock.exceptionally(e -> null) // we don't care here about the exception, logged elsewhere
|
||||
(prevUnlock, nextVault) -> prevUnlock.thenCompose(_ -> appWindows.startUnlockWorkflow(nextVault, null)),
|
||||
(_, nextUnlock) -> nextUnlock.exceptionally(_ -> null) // we don't care here about the exception, logged elsewhere
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ public class FxApplication {
|
||||
private final FxApplicationStyle applicationStyle;
|
||||
private final FxApplicationTerminator applicationTerminator;
|
||||
private final AutoUnlocker autoUnlocker;
|
||||
private final FxFSEventList fxFSEventList; //not unused! By injecting it here, the object gets initiated the service starts
|
||||
|
||||
@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() {
|
||||
|
||||
@@ -8,6 +8,7 @@ package org.cryptomator.ui.fxapp;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.eventview.EventViewComponent;
|
||||
import org.cryptomator.ui.health.HealthCheckComponent;
|
||||
import org.cryptomator.ui.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
@@ -19,6 +20,9 @@ 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;
|
||||
@@ -33,7 +37,8 @@ import java.io.InputStream;
|
||||
ErrorComponent.class, //
|
||||
HealthCheckComponent.class, //
|
||||
UpdateReminderComponent.class, //
|
||||
ShareVaultComponent.class})
|
||||
ShareVaultComponent.class, //
|
||||
EventViewComponent.class})
|
||||
abstract class FxApplicationModule {
|
||||
|
||||
private static Image createImageFromResource(String resourceName) throws IOException {
|
||||
@@ -66,4 +71,10 @@ abstract class FxApplicationModule {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static EventViewComponent provideEventViewComponent(EventViewComponent.Factory factory) {
|
||||
return factory.create();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.dialogs.SimpleDialog;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.eventview.EventViewComponent;
|
||||
import org.cryptomator.ui.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
@@ -51,6 +53,7 @@ public class FxApplicationWindows {
|
||||
private final UpdateReminderComponent.Factory updateReminderWindowFactory;
|
||||
private final LockComponent.Factory lockWorkflowFactory;
|
||||
private final ErrorComponent.Factory errorWindowFactory;
|
||||
private final Lazy<EventViewComponent> eventViewWindow;
|
||||
private final ExecutorService executor;
|
||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||
private final ShareVaultComponent.Factory shareVaultWindow;
|
||||
@@ -69,6 +72,7 @@ public class FxApplicationWindows {
|
||||
ErrorComponent.Factory errorWindowFactory, //
|
||||
VaultOptionsComponent.Factory vaultOptionsWindow, //
|
||||
ShareVaultComponent.Factory shareVaultWindow, //
|
||||
Lazy<EventViewComponent> eventViewWindow, //
|
||||
ExecutorService executor, //
|
||||
Dialogs dialogs) {
|
||||
this.primaryStage = primaryStage;
|
||||
@@ -80,6 +84,7 @@ public class FxApplicationWindows {
|
||||
this.updateReminderWindowFactory = updateReminderWindowFactory;
|
||||
this.lockWorkflowFactory = lockWorkflowFactory;
|
||||
this.errorWindowFactory = errorWindowFactory;
|
||||
this.eventViewWindow = eventViewWindow;
|
||||
this.executor = executor;
|
||||
this.vaultOptionsWindow = vaultOptionsWindow;
|
||||
this.shareVaultWindow = shareVaultWindow;
|
||||
@@ -93,17 +98,17 @@ public class FxApplicationWindows {
|
||||
|
||||
// register preferences shortcut
|
||||
if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) {
|
||||
desktop.setPreferencesHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ANY));
|
||||
desktop.setPreferencesHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ANY));
|
||||
}
|
||||
|
||||
// register preferences shortcut
|
||||
if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
|
||||
desktop.setAboutHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
|
||||
desktop.setAboutHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
|
||||
}
|
||||
|
||||
// register app reopen listener
|
||||
if (desktop.isSupported(Desktop.Action.APP_EVENT_REOPENED)) {
|
||||
desktop.addAppEventListener((AppReopenedListener) e -> showMainWindow());
|
||||
desktop.addAppEventListener((AppReopenedListener) _ -> showMainWindow());
|
||||
}
|
||||
|
||||
// observe visible windows
|
||||
@@ -135,11 +140,12 @@ public class FxApplicationWindows {
|
||||
}
|
||||
|
||||
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
|
||||
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
|
||||
return showMainWindow().thenApplyAsync(_ -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater) //
|
||||
.whenComplete(this::reportErrors);
|
||||
}
|
||||
|
||||
public void showQuitWindow(QuitResponse response, boolean forced) {
|
||||
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
|
||||
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response, forced), Platform::runLater);
|
||||
}
|
||||
|
||||
public void showUpdateReminderWindow() {
|
||||
@@ -147,13 +153,14 @@ public class FxApplicationWindows {
|
||||
}
|
||||
|
||||
public void showDokanySupportEndWindow() {
|
||||
CompletableFuture.runAsync(() -> dialogs.prepareDokanySupportEndDialog(
|
||||
mainWindow.get().window(),
|
||||
stage -> {
|
||||
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
|
||||
stage.close();
|
||||
}
|
||||
).build().showAndWait(), Platform::runLater);
|
||||
CompletableFuture.runAsync(() -> createDokanySupportEndDialog().showAndWait(), Platform::runLater);
|
||||
}
|
||||
|
||||
private SimpleDialog createDokanySupportEndDialog() {
|
||||
return dialogs.prepareDokanySupportEndDialog(mainWindow.get().window(), stage -> {
|
||||
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
|
||||
stage.close();
|
||||
}).build();
|
||||
}
|
||||
|
||||
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
|
||||
@@ -162,8 +169,7 @@ public class FxApplicationWindows {
|
||||
LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
|
||||
return unlockWorkflowFactory.create(vault, owner).unlockWorkflow();
|
||||
}, Platform::runLater) //
|
||||
.thenAcceptAsync(UnlockWorkflow::run, executor)
|
||||
.exceptionally(e -> {
|
||||
.thenAcceptAsync(UnlockWorkflow::run, executor).exceptionally(e -> {
|
||||
showErrorWindow(e, owner == null ? primaryStage : owner, null);
|
||||
return null;
|
||||
});
|
||||
@@ -182,6 +188,11 @@ public class FxApplicationWindows {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public CompletionStage<Stage> showEventViewer() {
|
||||
return CompletableFuture.supplyAsync(() -> eventViewWindow.get().showEventViewerWindow(), Platform::runLater).whenComplete(this::reportErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the generic error scene in the given window.
|
||||
*
|
||||
@@ -199,5 +210,4 @@ public class FxApplicationWindows {
|
||||
LOG.error("Failed to display stage", error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
65
src/main/java/org/cryptomator/ui/fxapp/FxFSEventList.java
Normal file
65
src/main/java/org/cryptomator/ui/fxapp/FxFSEventList.java
Normal file
@@ -0,0 +1,65 @@
|
||||
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.CountDownLatch;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class FxFSEventList {
|
||||
|
||||
private final ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> events;
|
||||
private final FileSystemEventAggregator eventAggregator;
|
||||
private final ScheduledFuture<?> scheduledTask;
|
||||
private final BooleanProperty unreadEvents;
|
||||
|
||||
@Inject
|
||||
public FxFSEventList(FileSystemEventAggregator fsEventAggregator, ScheduledExecutorService scheduler) {
|
||||
this.events = FXCollections.observableArrayList();
|
||||
this.eventAggregator = fsEventAggregator;
|
||||
this.unreadEvents = new SimpleBooleanProperty(false);
|
||||
this.scheduledTask = scheduler.scheduleWithFixedDelay(() -> {
|
||||
if (fsEventAggregator.hasUpdates()) {
|
||||
flush();
|
||||
}
|
||||
}, 1000, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the clone task on the FX thread and wait till it is completed
|
||||
*/
|
||||
private void flush() {
|
||||
var latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
eventAggregator.cloneTo(events);
|
||||
unreadEvents.setValue(true);
|
||||
latch.countDown();
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> getObservableList() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public BooleanProperty unreadEventsProperty() {
|
||||
return unreadEvents;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package org.cryptomator.ui.keyloading;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -28,6 +30,33 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
|
||||
@Override
|
||||
Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException;
|
||||
|
||||
/**
|
||||
* Determines whether the provided key loader scheme corresponds to a Hub Vault.
|
||||
* <p>
|
||||
* This method compares the {@code keyLoader} parameter with the known Hub Vault schemes
|
||||
* {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTP} and {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTPS}.
|
||||
*
|
||||
* @param keyLoader A string representing the key loader scheme to be checked.
|
||||
* @return {@code true} if the given key loader scheme represents a Hub Vault; {@code false} otherwise.
|
||||
*/
|
||||
static boolean isHubVault(String keyLoader) {
|
||||
return HubKeyLoadingStrategy.SCHEME_HUB_HTTP.equals(keyLoader) || HubKeyLoadingStrategy.SCHEME_HUB_HTTPS.equals(keyLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the provided key loader scheme corresponds to a Masterkey File Vault.
|
||||
* <p>
|
||||
* This method checks if the {@code keyLoader} parameter matches the known Masterkey File Vault scheme
|
||||
* {@link MasterkeyFileLoadingStrategy#SCHEME}.
|
||||
* </p>
|
||||
*
|
||||
* @param keyLoader A string representing the key loader scheme to be checked.
|
||||
* @return {@code true} if the given key loader scheme represents a Masterkey File Vault; {@code false} otherwise.
|
||||
*/
|
||||
static boolean isMasterkeyFileVault(String keyLoader) {
|
||||
return MasterkeyFileLoadingStrategy.SCHEME.equals(keyLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the loader to try and recover from an exception thrown during the last attempt.
|
||||
*
|
||||
|
||||
@@ -165,6 +165,7 @@ public class ReceiveKeyController implements FxController {
|
||||
var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token");
|
||||
var request = HttpRequest.newBuilder(vaultKeyUri) //
|
||||
.header("Authorization", "Bearer " + bearerToken) //
|
||||
.header("Hub-Device-ID", deviceId) //
|
||||
.GET() //
|
||||
.timeout(REQ_TIMEOUT) //
|
||||
.build();
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
@@ -21,6 +13,17 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@MainWindowScoped
|
||||
public class MainWindowController implements FxController {
|
||||
@@ -63,27 +66,76 @@ public class MainWindowController implements FxController {
|
||||
}
|
||||
window.focusedProperty().addListener(this::mainWindowFocusChanged);
|
||||
|
||||
if (!neverTouched()) {
|
||||
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
|
||||
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
|
||||
window.setX(settings.windowXPosition.get());
|
||||
window.setY(settings.windowYPosition.get());
|
||||
int x = settings.windowXPosition.get();
|
||||
int y = settings.windowYPosition.get();
|
||||
int width = settings.windowWidth.get();
|
||||
int height = settings.windowHeight.get();
|
||||
if (windowPositionSaved(x, y, width, height)) {
|
||||
window.setX(x);
|
||||
window.setY(y);
|
||||
window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
|
||||
window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
|
||||
}
|
||||
window.widthProperty().addListener((_, _, _) -> savePositionalSettings());
|
||||
window.heightProperty().addListener((_, _, _) -> savePositionalSettings());
|
||||
window.xProperty().addListener((_, _, _) -> savePositionalSettings());
|
||||
window.yProperty().addListener((_, _, _) -> savePositionalSettings());
|
||||
|
||||
window.setOnShowing(this::checkDisplayBounds);
|
||||
|
||||
settings.windowXPosition.bind(window.xProperty());
|
||||
settings.windowYPosition.bind(window.yProperty());
|
||||
settings.windowWidth.bind(window.widthProperty());
|
||||
settings.windowHeight.bind(window.heightProperty());
|
||||
}
|
||||
|
||||
private boolean neverTouched() {
|
||||
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
|
||||
private boolean windowPositionSaved(int x, int y, int width, int height) {
|
||||
return x != 0 || y != 0 || width != 0 || height != 0;
|
||||
}
|
||||
|
||||
public void savePositionalSettings() {
|
||||
settings.windowWidth.setValue(window.getWidth());
|
||||
settings.windowHeight.setValue(window.getHeight());
|
||||
settings.windowXPosition.setValue(window.getX());
|
||||
settings.windowYPosition.setValue(window.getY());
|
||||
private void checkDisplayBounds(WindowEvent windowEvent) {
|
||||
int x = settings.windowXPosition.get();
|
||||
int y = settings.windowYPosition.get();
|
||||
int width = settings.windowWidth.get();
|
||||
int height = settings.windowHeight.get();
|
||||
|
||||
// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000)
|
||||
// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453
|
||||
// If the position is (-32000, -32000), restore to the last saved position
|
||||
if (window.getX() == -32000 && window.getY() == -32000) {
|
||||
window.setX(x);
|
||||
window.setY(y);
|
||||
window.setWidth(width);
|
||||
window.setHeight(height);
|
||||
}
|
||||
|
||||
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
|
||||
if (!isWithinDisplayBounds(x, y, width, height)) { //use stored window position
|
||||
LOG.debug("Resetting window position due to insufficient screen overlap");
|
||||
var centeredX = (primaryScreenBounds.getWidth() - window.getMinWidth()) / 2;
|
||||
var centeredY = (primaryScreenBounds.getHeight() - window.getMinHeight()) / 2;
|
||||
//check if we can keep width and height
|
||||
if (isWithinDisplayBounds((int) centeredX, (int) centeredY, width, height)) {
|
||||
//if so, keep window size
|
||||
window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
|
||||
window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
|
||||
}
|
||||
//reset position of upper left corner
|
||||
window.setX(centeredX);
|
||||
window.setY(centeredY);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWithinDisplayBounds(int x, int y, int width, int height) {
|
||||
// define a rect which is inset on all sides from the window's rect:
|
||||
final int shrinkedX = x + 20; // 20px left
|
||||
final int shrinkedY = y + 5; // 5px top
|
||||
final int shrinkedWidth = width - 40; // 20px left + 20px right
|
||||
final int shrinkedHeigth = height - 25; // 5px top + 20px bottom
|
||||
return isRectangleWithinBounds(shrinkedX, shrinkedY, 0, shrinkedHeigth) // Left pixel column
|
||||
&& isRectangleWithinBounds(shrinkedX + shrinkedWidth, shrinkedY, 0, shrinkedHeigth) // Right pixel column
|
||||
&& isRectangleWithinBounds(shrinkedX, shrinkedY, shrinkedWidth, 0) // Top pixel row
|
||||
&& isRectangleWithinBounds(shrinkedX, shrinkedY + shrinkedHeigth, shrinkedWidth, 0); // Bottom pixel row
|
||||
}
|
||||
|
||||
private boolean isRectangleWithinBounds(int x, int y, int width, int height) {
|
||||
return !Screen.getScreensForRectangle(x, y, width, height).isEmpty();
|
||||
}
|
||||
|
||||
private void mainWindowFocusChanged(Observable observable) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Lazy;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
@@ -14,9 +15,11 @@ import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.common.StageInitializer;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
|
||||
import org.cryptomator.ui.fxapp.PrimaryStage;
|
||||
import org.cryptomator.ui.migration.MigrationComponent;
|
||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -35,11 +38,19 @@ abstract class MainWindowModule {
|
||||
@Provides
|
||||
@MainWindow
|
||||
@MainWindowScoped
|
||||
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) {
|
||||
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer, FxApplicationTerminator terminator, Lazy<TrayMenuComponent> trayMenu) {
|
||||
initializer.accept(stage);
|
||||
stage.setTitle("Cryptomator");
|
||||
stage.setMinWidth(650);
|
||||
stage.setMinHeight(498);
|
||||
stage.setOnCloseRequest(e -> {
|
||||
if (!trayMenu.get().isInitialized()) {
|
||||
terminator.terminate();
|
||||
e.consume();
|
||||
} else {
|
||||
stage.close();
|
||||
}
|
||||
});
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ 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;
|
||||
@@ -26,6 +27,7 @@ import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
@@ -34,7 +36,6 @@ import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
@@ -66,6 +67,7 @@ public class VaultListController implements FxController {
|
||||
private final VaultListCellFactory cellFactory;
|
||||
private final AddVaultWizardComponent.Builder addVaultWizard;
|
||||
private final BooleanBinding emptyVaultList;
|
||||
private final BooleanProperty unreadEvents;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
private final ResourceBundle resourceBundle;
|
||||
@@ -76,7 +78,7 @@ public class VaultListController implements FxController {
|
||||
public ListView<Vault> vaultList;
|
||||
public StackPane root;
|
||||
@FXML
|
||||
private HBox addVaultButton;
|
||||
private Button addVaultButton;
|
||||
@FXML
|
||||
private ContextMenu addVaultContextMenu;
|
||||
|
||||
@@ -91,7 +93,8 @@ public class VaultListController implements FxController {
|
||||
ResourceBundle resourceBundle, //
|
||||
FxApplicationWindows appWindows, //
|
||||
Settings settings, //
|
||||
Dialogs dialogs) {
|
||||
Dialogs dialogs, //
|
||||
FxFSEventList fxFSEventList) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaults = vaults;
|
||||
this.selectedVault = selectedVault;
|
||||
@@ -104,6 +107,7 @@ public class VaultListController implements FxController {
|
||||
this.dialogs = dialogs;
|
||||
|
||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||
this.unreadEvents = fxFSEventList.unreadEventsProperty();
|
||||
|
||||
selectedVault.addListener(this::selectedVaultDidChange);
|
||||
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
|
||||
@@ -263,6 +267,11 @@ public class VaultListController implements FxController {
|
||||
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showEventViewer() {
|
||||
appWindows.showEventViewer();
|
||||
unreadEvents.setValue(false);
|
||||
}
|
||||
// Getter and Setter
|
||||
|
||||
public BooleanBinding emptyVaultListProperty() {
|
||||
@@ -289,4 +298,11 @@ public class VaultListController implements FxController {
|
||||
return cellSize.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> unreadEventsPresentProperty() {
|
||||
return unreadEvents;
|
||||
}
|
||||
|
||||
public boolean getUnreadEventsPresent() {
|
||||
return unreadEvents.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import javafx.fxml.FXML;
|
||||
public class WelcomeController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
|
||||
private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/en/1.7/desktop/getting-started/";
|
||||
private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/desktop/getting-started/";
|
||||
|
||||
private final Application application;
|
||||
private final BooleanBinding noVaultPresent;
|
||||
|
||||
@@ -10,7 +10,7 @@ import javafx.stage.Stage;
|
||||
|
||||
public class MigrationImpossibleController implements FxController {
|
||||
|
||||
private static final String HELP_URI = "https://docs.cryptomator.org/en/1.7/help/manual-migration/";
|
||||
private static final String HELP_URI = "https://docs.cryptomator.org/help/manual-migration/";
|
||||
|
||||
private final Application application;
|
||||
private final Stage window;
|
||||
|
||||
@@ -19,6 +19,8 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -32,7 +34,10 @@ import java.util.ResourceBundle;
|
||||
@PreferencesScoped
|
||||
public class UpdatesPreferencesController implements FxController {
|
||||
|
||||
private static final String DOWNLOADS_URI = "https://cryptomator.org/downloads";
|
||||
private static final String DOWNLOADS_URI_TEMPLATE = "https://cryptomator.org/downloads/" //
|
||||
+ "?utm_source=cryptomator-desktop" //
|
||||
+ "&utm_medium=update-notification&" //
|
||||
+ "utm_campaign=app-update-%s";
|
||||
|
||||
private final Application application;
|
||||
private final Environment environment;
|
||||
@@ -50,6 +55,7 @@ public class UpdatesPreferencesController implements FxController {
|
||||
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
|
||||
private final DateTimeFormatter formatter;
|
||||
private final BooleanBinding upToDate;
|
||||
private final String downloadsUri;
|
||||
|
||||
/* FXML */
|
||||
public CheckBox checkForUpdatesCheckbox;
|
||||
@@ -65,12 +71,13 @@ public class UpdatesPreferencesController implements FxController {
|
||||
this.latestVersion = updateChecker.latestVersionProperty();
|
||||
this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty();
|
||||
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck);
|
||||
this.currentVersion = updateChecker.getCurrentVersion();
|
||||
this.currentVersion = environment.getAppVersion();
|
||||
this.updateAvailable = updateChecker.updateAvailableProperty();
|
||||
this.formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
|
||||
this.upToDate = updateChecker.updateCheckStateProperty().isEqualTo(UpdateChecker.UpdateCheckState.CHECK_SUCCESSFUL).and(latestVersion.isEqualTo(currentVersion));
|
||||
this.checkFailed = updateChecker.checkFailedProperty();
|
||||
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, lastSuccessfulUpdateCheck);
|
||||
this.downloadsUri = DOWNLOADS_URI_TEMPLATE.formatted(URLEncoder.encode(currentVersion, StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -93,7 +100,7 @@ public class UpdatesPreferencesController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void visitDownloadsPage() {
|
||||
application.getHostServices().showDocument(DOWNLOADS_URI);
|
||||
application.getHostServices().showDocument(downloadsUri);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.ResourceBundle;
|
||||
@PreferencesScoped
|
||||
public class VolumePreferencesController implements FxController {
|
||||
|
||||
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
|
||||
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/desktop/volume-type/";
|
||||
public static final int MIN_PORT = 1024;
|
||||
public static final int MAX_PORT = 65535;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.cryptomator.ui.sharevault;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Application;
|
||||
@@ -33,8 +33,7 @@ public class ShareVaultController implements FxController {
|
||||
this.window = window;
|
||||
this.application = application;
|
||||
this.vault = vault;
|
||||
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
|
||||
this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
|
||||
this.hubVault = KeyLoadingStrategy.isHubVault(vault.getVaultSettings().lastKnownKeyLoader.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.cryptomator.ui.vaultoptions;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
import org.slf4j.Logger;
|
||||
@@ -42,11 +43,11 @@ public class VaultOptionsController implements FxController {
|
||||
window.setOnShowing(this::windowWillAppear);
|
||||
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
||||
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
|
||||
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
|
||||
if(!vaultScheme.equals(MasterkeyFileLoadingStrategy.SCHEME)){
|
||||
var vaultKeyLoader = vault.getVaultSettings().lastKnownKeyLoader.get();
|
||||
if(!KeyLoadingStrategy.isMasterkeyFileVault(vaultKeyLoader)){
|
||||
tabPane.getTabs().remove(keyTab);
|
||||
}
|
||||
if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
|
||||
if(!KeyLoadingStrategy.isHubVault(vaultKeyLoader)){
|
||||
tabPane.getTabs().remove(hubTab);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.io.UncheckedIOException;
|
||||
@WrongFileAlertScoped
|
||||
public class WrongFileAlertController implements FxController {
|
||||
|
||||
private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/en/1.7/desktop/accessing-vaults/";
|
||||
private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/desktop/accessing-vaults/";
|
||||
|
||||
private final Application app;
|
||||
private final Stage window;
|
||||
|
||||
@@ -183,19 +183,37 @@
|
||||
}
|
||||
|
||||
.main-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
}
|
||||
|
||||
.main-window .button-left {
|
||||
.main-window .button-bar .button-left {
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 1px 0 0;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.main-window .button-right {
|
||||
.main-window .button-bar .button-right {
|
||||
-fx-border-color: 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;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-left:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -312,6 +330,42 @@
|
||||
-fx-fill: transparent;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Event List *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.event-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;
|
||||
}
|
||||
|
||||
.event-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;
|
||||
}
|
||||
|
||||
.event-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.event-window .list-view .list-cell:hover {
|
||||
-fx-background-color: CONTROL_BG_SELECTED;
|
||||
}
|
||||
|
||||
.event-window .list-view .list-cell:selected {
|
||||
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
|
||||
-fx-background-insets: 0, 0 0 0 3px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* NotificationBar *
|
||||
@@ -341,6 +395,12 @@
|
||||
-fx-background-color: PRIMARY;
|
||||
}
|
||||
|
||||
.notification-debug:hover .notification-label,
|
||||
.notification-update:hover .notification-label,
|
||||
.notification-support:hover .notification-label {
|
||||
-fx-underline:true;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ScrollBar *
|
||||
@@ -575,6 +635,16 @@
|
||||
-fx-graphic-text-gap: 9px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Update indicator
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.update-indicator {
|
||||
-fx-fill: RED_5;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Hyperlinks *
|
||||
|
||||
@@ -182,6 +182,8 @@
|
||||
}
|
||||
|
||||
.main-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
@@ -190,11 +192,27 @@
|
||||
.main-window .button-bar .button-left {
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 1px 0 0;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-right {
|
||||
-fx-border-color: 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;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-left:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -283,6 +301,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;
|
||||
@@ -311,6 +333,42 @@
|
||||
-fx-fill: transparent;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Event List *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.event-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;
|
||||
}
|
||||
|
||||
.event-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;
|
||||
}
|
||||
|
||||
.event-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.event-window .list-view .list-cell:hover {
|
||||
-fx-background-color: CONTROL_BG_SELECTED;
|
||||
}
|
||||
|
||||
.event-window .list-view .list-cell:selected {
|
||||
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
|
||||
-fx-background-insets: 0, 0 0 0 3px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* NotificationBar *
|
||||
@@ -340,6 +398,12 @@
|
||||
-fx-background-color: PRIMARY;
|
||||
}
|
||||
|
||||
.notification-debug:hover .notification-label,
|
||||
.notification-update:hover .notification-label,
|
||||
.notification-support:hover .notification-label {
|
||||
-fx-underline:true;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ScrollBar *
|
||||
@@ -574,6 +638,16 @@
|
||||
-fx-graphic-text-gap: 9px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Update indicator
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.icon-update-indicator {
|
||||
-fx-fill: RED_5;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Hyperlinks *
|
||||
@@ -786,11 +860,11 @@
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Add Vault - MenuItem *
|
||||
* Dropdown button context menu
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.add-vault-menu-item {
|
||||
.dropdown-button-context-menu-item {
|
||||
-fx-padding: 4px 8px;
|
||||
}
|
||||
|
||||
|
||||
35
src/main/resources/fxml/eventview.fxml
Normal file
35
src/main/resources/fxml/eventview.fxml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.control.ChoiceBox?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.eventview.EventViewController"
|
||||
minWidth="300"
|
||||
prefWidth="300"
|
||||
styleClass="event-window"
|
||||
>
|
||||
<HBox styleClass="button-bar" alignment="CENTER">
|
||||
<padding>
|
||||
<Insets left="6" />
|
||||
</padding>
|
||||
<ChoiceBox fx:id="vaultFilterChoiceBox"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button styleClass="button-right" onAction="#clearEvents" contentDisplay="GRAPHIC_ONLY">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%eventView.clearListButton.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
</HBox>
|
||||
<ListView fx:id="eventListView" fixedCellSize="60" VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
45
src/main/resources/fxml/eventview_cell.fxml
Normal file
45
src/main/resources/fxml/eventview_cell.fxml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.eventview.EventListCellController"
|
||||
prefHeight="60"
|
||||
prefWidth="200"
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT"
|
||||
fx:id="root">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
|
||||
<VBox alignment="CENTER" minWidth="20">
|
||||
<FontAwesome5IconView glyph="${controller.icon}" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</VBox>
|
||||
<VBox spacing="4" HBox.hgrow="ALWAYS">
|
||||
<HBox spacing="4">
|
||||
<Label styleClass="header-label" text="${controller.message}"/>
|
||||
<Label styleClass="header-misc" text="${controller.count}" visible="${controller.vaultUnlocked}"/>
|
||||
</HBox>
|
||||
<Label text="${controller.description}"/>
|
||||
</VBox>
|
||||
<Button fx:id="eventActionsButton" contentDisplay="GRAPHIC_ONLY" onAction="#toggleEventActionsMenu" managed="${controller.actionsButtonVisible}" visible="${controller.actionsButtonVisible}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="ELLIPSIS_V" glyphSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<VBox alignment="CENTER" maxWidth="64" minWidth="64" visible="${!controller.actionsButtonVisible}" managed="${!controller.actionsButtonVisible}">
|
||||
<Label text="${controller.eventLocalTime}" />
|
||||
<Label text="${controller.eventLocalDate}" />
|
||||
</VBox>
|
||||
|
||||
<fx:define>
|
||||
<ContextMenu fx:id="eventActionsMenu"/>
|
||||
</fx:define>
|
||||
</HBox>
|
||||
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.dialogs.SimpleDialogController"
|
||||
@@ -41,7 +41,7 @@
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel"/>
|
||||
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel" visible="${controller.cancelButtonVisible}" managed="${controller.cancelButtonVisible}"/>
|
||||
<Button text="${controller.okButtonText}" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#handleOk"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?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"
|
||||
@@ -35,33 +40,44 @@
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<HBox styleClass="button-bar">
|
||||
<HBox fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<FontAwesome5IconView glyph="PLUS" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</HBox>
|
||||
<Button fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="PLUS" glyphSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<HBox onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<FontAwesome5IconView glyph="COG" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</HBox>
|
||||
<StackPane>
|
||||
<Button onMouseClicked="#showEventViewer" styleClass="button-right" minWidth="20" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="BELL" glyphSize="16"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.vaultlist.showEventsButton.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<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>
|
||||
<FontAwesome5IconView glyph="COG" glyphSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</HBox>
|
||||
</VBox>
|
||||
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
|
||||
<fx:define>
|
||||
<ContextMenu fx:id="addVaultContextMenu">
|
||||
<items>
|
||||
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
|
||||
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
|
||||
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14"/>
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
|
||||
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
|
||||
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14"/>
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
</items>
|
||||
|
||||
@@ -14,17 +14,15 @@
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT">
|
||||
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
|
||||
<children>
|
||||
<VBox alignment="CENTER" minWidth="20">
|
||||
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</VBox>
|
||||
<VBox spacing="4" HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
|
||||
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
|
||||
<tooltip>
|
||||
<Tooltip text="${controller.vault.displayablePath}"/>
|
||||
</tooltip>
|
||||
</Label>
|
||||
</VBox>
|
||||
</children>
|
||||
<VBox alignment="CENTER" minWidth="20">
|
||||
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</VBox>
|
||||
<VBox spacing="4" HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
|
||||
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
|
||||
<tooltip>
|
||||
<Tooltip text="${controller.vault.displayablePath}"/>
|
||||
</tooltip>
|
||||
</Label>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
additionalStyleSheets=
|
||||
|
||||
# Generics
|
||||
generic.action.dismiss=Dismiss
|
||||
## Button
|
||||
generic.button.apply=Apply
|
||||
generic.button.back=Back
|
||||
@@ -283,7 +284,7 @@ preferences.title=Preferences
|
||||
## General
|
||||
preferences.general=General
|
||||
preferences.general.startHidden=Hide window when starting Cryptomator
|
||||
preferences.general.autoCloseVaults=Lock open vaults automatically when quitting application
|
||||
preferences.general.autoCloseVaults=Lock vaults without asking when quitting application
|
||||
preferences.general.debugLogging=Enable debug logging
|
||||
preferences.general.debugDirectory=Reveal log files
|
||||
preferences.general.autoStart=Launch Cryptomator on system start
|
||||
@@ -395,6 +396,7 @@ main.vaultlist.contextMenu.vaultoptions=Show Vault Options
|
||||
main.vaultlist.contextMenu.reveal=Reveal Drive
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault...
|
||||
main.vaultlist.showEventsButton.tooltip=Open event view
|
||||
##Notificaition
|
||||
main.notification.updateAvailable=Update is available.
|
||||
main.notification.support=Support Cryptomator.
|
||||
@@ -577,4 +579,30 @@ shareVault.hub.message=How to share a Hub vault
|
||||
shareVault.hub.description=In order to share the vault content with another team member, you have to perform two steps:
|
||||
shareVault.hub.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
|
||||
shareVault.hub.instruction.2=2. Grant access to team member in Cryptomator Hub.
|
||||
shareVault.hub.openHub=Open Cryptomator Hub
|
||||
shareVault.hub.openHub=Open Cryptomator Hub
|
||||
|
||||
# Event View
|
||||
eventView.title=Events
|
||||
eventView.filter.allVaults=All
|
||||
eventView.clearListButton.tooltip=Dismiss all
|
||||
## 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
|
||||
eventView.entry.conflictResolved.copyDecrypted=Copy decrypted path
|
||||
eventView.entry.conflict.message=Conflict resolution failed
|
||||
eventView.entry.conflict.showDecrypted=Show decrypted, original file
|
||||
eventView.entry.conflict.copyDecrypted=Copy decrypted, original path
|
||||
eventView.entry.conflict.showEncrypted=Show conflicting, encrypted file
|
||||
eventView.entry.conflict.copyEncrypted=Copy conflicting, encrypted path
|
||||
eventView.entry.decryptionFailed.message=Decryption failed
|
||||
eventView.entry.decryptionFailed.showEncrypted=Show encrypted file
|
||||
eventView.entry.decryptionFailed.copyEncrypted=Copy encrypted path
|
||||
eventView.entry.brokenDirFile.message=Broken directory link
|
||||
eventView.entry.brokenDirFile.showEncrypted=Show broken, encrypted link
|
||||
eventView.entry.brokenDirFile.copyEncrypted=Copy path of broken link
|
||||
eventView.entry.brokenFileNode.message=Broken filesystem node
|
||||
eventView.entry.brokenFileNode.showEncrypted=Show broken, encrypted node
|
||||
eventView.entry.brokenFileNode.copyEncrypted=Copy path of broken, encrypted node
|
||||
eventView.entry.brokenFileNode.copyDecrypted=Copy decrypted path
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
# Locale Specific CSS files such as CJK, RTL,...
|
||||
|
||||
# Generics
|
||||
## Button
|
||||
generic.button.apply=Apply
|
||||
generic.button.back=Back
|
||||
generic.button.cancel=Cancel
|
||||
generic.button.change=Change
|
||||
generic.button.choose=Choose…
|
||||
generic.button.close=Close
|
||||
generic.button.done=Done
|
||||
generic.button.next=Next
|
||||
|
||||
# Error
|
||||
|
||||
# Defaults
|
||||
|
||||
# Tray Menu
|
||||
traymenu.showMainWindow=Show
|
||||
traymenu.showPreferencesWindow=Preferences
|
||||
traymenu.quitApplication=Quit
|
||||
traymenu.vault.unlock=Unlock
|
||||
traymenu.vault.lock=Lock
|
||||
traymenu.vault.reveal=Reveal
|
||||
|
||||
# Add Vault Wizard
|
||||
addvaultwizard.title=Add Vault
|
||||
## New
|
||||
### Name
|
||||
addvaultwizard.new.nameInstruction=Choose a name for the vault
|
||||
addvaultwizard.new.namePrompt=Vault Name
|
||||
### Location
|
||||
addvaultwizard.new.locationInstruction=Where should Cryptomator store the encrypted files of your vault?
|
||||
addvaultwizard.new.locationLabel=Storage location
|
||||
addvaultwizard.new.locationPrompt=…
|
||||
addvaultwizard.new.directoryPickerButton=Choose…
|
||||
addvaultwizard.new.directoryPickerTitle=Select Directory
|
||||
### Expert Settings
|
||||
### Password
|
||||
addvaultwizard.new.createVaultBtn=Create Vault
|
||||
### Information
|
||||
## Existing
|
||||
addvaultwizard.existing.chooseBtn=Choose…
|
||||
## Success
|
||||
addvaultwizard.success.nextStepsInstructions=Added vault "%s".\nYou need to unlock this vault to access or add contents. Alternatively you can unlock it at any later point in time.
|
||||
addvaultwizard.success.unlockNow=Unlock Now
|
||||
|
||||
# Remove Vault
|
||||
removeVault.confirmBtn=Remove Vault
|
||||
|
||||
# Change Password
|
||||
changepassword.title=Change Password
|
||||
changepassword.enterOldPassword=Enter the current password for "%s"
|
||||
|
||||
# Forget Password
|
||||
forgetPassword.title=Forget Password
|
||||
forgetPassword.confirmBtn=Forget Password
|
||||
|
||||
# Unlock
|
||||
unlock.passwordPrompt=Enter password for "%s":
|
||||
unlock.unlockBtn=Unlock
|
||||
## Select
|
||||
unlock.chooseMasterkey.filePickerTitle=Select Masterkey File
|
||||
## Success
|
||||
unlock.success.revealBtn=Reveal Drive
|
||||
## Failure
|
||||
## Hub
|
||||
### Waiting
|
||||
### Receive Key
|
||||
### Register Device
|
||||
### Register Device Legacy
|
||||
### Registration Success
|
||||
hub.registerSuccess.unlockBtn=Unlock
|
||||
### Registration Failed
|
||||
### Unauthorized
|
||||
### Requires Account Initialization
|
||||
### License Exceeded
|
||||
|
||||
# Lock
|
||||
## Force
|
||||
## Failure
|
||||
|
||||
# Migration
|
||||
migration.title=Upgrade Vault
|
||||
## Start
|
||||
migration.start.header=Upgrade Vault
|
||||
## Run
|
||||
migration.run.enterPassword=Enter the password for "%s"
|
||||
migration.run.startMigrationBtn=Migrate Vault
|
||||
## Success
|
||||
migration.success.nextStepsInstructions=Migrated "%s" successfully.\nYou can now unlock your vault.
|
||||
migration.success.unlockNow=Unlock Now
|
||||
## Missing file system capabilities
|
||||
## Impossible
|
||||
|
||||
# Health Check
|
||||
## Start
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
preferences.title=Preferences
|
||||
## General
|
||||
preferences.general=General
|
||||
preferences.general.startHidden=Hide window when starting Cryptomator
|
||||
preferences.general.debugLogging=Enable debug logging
|
||||
## Interface
|
||||
## Volume
|
||||
preferences.volume=Virtual Drive
|
||||
## Updates
|
||||
preferences.updates=Updates
|
||||
preferences.updates.currentVersion=Current Version: %s
|
||||
preferences.updates.autoUpdateCheck=Check for updates automatically
|
||||
preferences.updates.checkNowBtn=Check Now
|
||||
preferences.updates.updateAvailable=Update to version %s available.
|
||||
|
||||
## Contribution
|
||||
|
||||
### Remove License Key Dialog
|
||||
#<-- Add entries for donations and code/translation/documentation contribution -->
|
||||
|
||||
## About
|
||||
|
||||
# Vault Statistics
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
## Vault List
|
||||
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
|
||||
main.vaultlist.contextMenu.lock=Lock
|
||||
main.vaultlist.contextMenu.unlockNow=Unlock Now
|
||||
main.vaultlist.contextMenu.reveal=Reveal Drive
|
||||
##Notificaition
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
### Locked
|
||||
main.vaultDetail.lockedStatus=LOCKED
|
||||
main.vaultDetail.unlockNowBtn=Unlock Now
|
||||
main.vaultDetail.optionsBtn=Vault Options
|
||||
### Unlocked
|
||||
main.vaultDetail.unlockedStatus=UNLOCKED
|
||||
main.vaultDetail.accessLocation=Your vault's contents are accessible here:
|
||||
main.vaultDetail.revealBtn=Reveal Drive
|
||||
main.vaultDetail.lockBtn=Lock
|
||||
main.vaultDetail.throughput.idle=idle
|
||||
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
||||
### Missing
|
||||
### Needs Migration
|
||||
main.vaultDetail.migrateButton=Upgrade Vault
|
||||
### Error
|
||||
|
||||
# Wrong File Alert
|
||||
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=General
|
||||
vaultOptions.general.vaultName=Vault Name
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Reveal Drive
|
||||
|
||||
## Mount
|
||||
vaultOptions.mount=Mounting
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
|
||||
## Master Key
|
||||
vaultOptions.masterkey.changePasswordBtn=Change Password
|
||||
## Hub
|
||||
|
||||
# Recovery Key
|
||||
## Display Recovery Key
|
||||
## Reset Password
|
||||
### Enter Recovery Key
|
||||
### Reset Password
|
||||
### Recovery Key Password Reset Success
|
||||
|
||||
# Convert Vault
|
||||
|
||||
# New Password
|
||||
passwordStrength.messageLabel.0=Very weak
|
||||
passwordStrength.messageLabel.1=Weak
|
||||
passwordStrength.messageLabel.2=Fair
|
||||
passwordStrength.messageLabel.3=Strong
|
||||
passwordStrength.messageLabel.4=Very strong
|
||||
|
||||
# Quit
|
||||
|
||||
# Forced Quit
|
||||
|
||||
# Update Reminder
|
||||
|
||||
#Dokany Support End
|
||||
|
||||
# Share Vault
|
||||
@@ -6,7 +6,7 @@ generic.button.apply=적용
|
||||
generic.button.back=이전
|
||||
generic.button.cancel=취소
|
||||
generic.button.change=변경
|
||||
generic.button.choose=선택
|
||||
generic.button.choose=선택…
|
||||
generic.button.close=닫기
|
||||
generic.button.copy=복사
|
||||
generic.button.copied=복사됨!
|
||||
@@ -16,16 +16,16 @@ generic.button.print=인쇄
|
||||
generic.button.remove=제거
|
||||
|
||||
# Error
|
||||
error.message=오류가 발생했습니다
|
||||
error.description=예상치 못한 에러가 발생했습니다. 온라인에 에러를 검색해서 해결하십시오. 만약 신고된 적이 없는 에러일 경우, 새로 신고해도 좋습니다.
|
||||
error.message=오류 발생
|
||||
error.description=예상치 못한 에러가 발생했습니다. 해결법을 검색하십시오. 만약 보고된 적이 없는 에러일 경우, 새로 신고해도 좋습니다.
|
||||
error.hyperlink.lookup=에러 검색하기
|
||||
error.hyperlink.report=에러 보고하기
|
||||
error.technicalDetails=상세 정보:
|
||||
error.existingSolutionDescription=Cryptomator에 알수 없는 문제가 발생했습니다. 하지만 이 오류에 대한 기존 해결책이 있습니다. 다음 링크를 살펴보시기 바랍니다.
|
||||
error.hyperlink.solution=솔루션 찾기
|
||||
error.existingSolutionDescription=Cryptomator에 알 수 없는 문제가 발생했습니다. 하지만 이 오류에 대한 기존 해결법이 있습니다. 다음 링크를 살펴보십시오.
|
||||
error.hyperlink.solution=해결법 찾기
|
||||
error.lookupPermissionMessage=Cryptomator는 온라인에서 이 문제에 대한 해결책을 찾아볼 수 있습니다. 그러면 귀하의 IP 주소가 문제 데이터베이스로 전송됩니다.
|
||||
error.dismiss=무시
|
||||
error.lookUpSolution=솔루션 찾기
|
||||
error.lookUpSolution=해결법 찾기
|
||||
|
||||
# Defaults
|
||||
defaults.vault.vaultName=Vault
|
||||
@@ -42,7 +42,7 @@ traymenu.vault.reveal=표시
|
||||
# Add Vault Wizard
|
||||
addvaultwizard.title=Vault 추가
|
||||
## New
|
||||
addvaultwizard.new.title=새로운 금고 추가
|
||||
addvaultwizard.new.title=새로운 Vault 추가
|
||||
### Name
|
||||
addvaultwizard.new.nameInstruction=새 Vault의 이름을 입력하십시오
|
||||
addvaultwizard.new.namePrompt=Vault 이름
|
||||
@@ -52,7 +52,7 @@ addvaultwizard.new.locationLoading=기본 클라우드 저장소 디렉터리에
|
||||
addvaultwizard.new.locationLabel=저장 위치
|
||||
addvaultwizard.new.locationPrompt=…
|
||||
addvaultwizard.new.directoryPickerLabel=사용자 지정 위치
|
||||
addvaultwizard.new.directoryPickerButton=선택
|
||||
addvaultwizard.new.directoryPickerButton=선택…
|
||||
addvaultwizard.new.directoryPickerTitle=디렉터리 선택
|
||||
addvaultwizard.new.fileAlreadyExists=Vault 내에 이미 존재하는 파일 또는 디렉터리 이름입니다.
|
||||
addvaultwizard.new.locationDoesNotExist=지정된 디렉터리가 존재하지 않거나 접근 할 수 없습니다.
|
||||
@@ -65,7 +65,7 @@ addvaultwizard.new.validCharacters.chars=문자 (예시: a, ж or 수)
|
||||
addvaultwizard.new.validCharacters.numbers=숫자
|
||||
addvaultwizard.new.validCharacters.dashes=대시 (%s) 또는 언더바 (%s)
|
||||
### Expert Settings
|
||||
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=전문가용 설정 활성화
|
||||
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=전문가 설정 활성화
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=36과 220 사이 숫자를 입력해주세요 (기본값: 220)
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=더 자세한 정보는 관련 문서에서 볼 수 있습니다.
|
||||
addvaultwizard.new.expertSettings.shorteningThreshold.title=암호화된 파일명의 최대 길이
|
||||
@@ -73,29 +73,29 @@ addvaultwizard.new.expertSettings.shorteningThreshold.valid=유효
|
||||
### Password
|
||||
addvaultwizard.new.createVaultBtn=Vault 생성
|
||||
addvaultwizard.new.generateRecoveryKeyChoice=비밀번호가 없으면 데이터에 접근할 수 없습니다. 비밀번호를 잊었을 때를 대비한 복구 키를 원하십니까?
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=네, 보안보다 비밀번호를 잊어버리는 것이 더 걱정됩니다
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=네, 보안보다 비밀번호를 잊어버리는 것이 더 걱정됩니다.
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=아니요, 나는 비밀번호를 잊지 않을겁니다.
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=IMPORTANT.rtf
|
||||
addvault.new.readme.storageLocation.1=⚠️ VAULT 파일 ⚠️
|
||||
addvault.new.readme.storageLocation.2=해당 디렉토리는 당신의 Vault 저장 위치 입니다.
|
||||
addvault.new.readme.storageLocation.3=금지사항
|
||||
addvault.new.readme.storageLocation.2=해당 디렉토리는 당신의 Vault 저장 위치입니다.
|
||||
addvault.new.readme.storageLocation.3=금지 사항
|
||||
addvault.new.readme.storageLocation.4=• 이 디렉터리를 포함한 어떤 파일도 다른 파일로 교체하거나
|
||||
addvault.new.readme.storageLocation.5=• 암호화를 위한 파일을 이 디렉터리에 붙여넣지 마십시요.
|
||||
addvault.new.readme.storageLocation.5=• 암호화하고자 하는 파일을 이 디렉터리에 붙여넣지 마십시오.
|
||||
addvault.new.readme.storageLocation.6=파일을 암호화하고 Vault 의 내용을 보려면 다음을 수행하십시오.
|
||||
addvault.new.readme.storageLocation.7=1. 이 Vault를 Cryptomator에 추가하십시요.
|
||||
addvault.new.readme.storageLocation.8=2. Cryptomator에서 Vault 잠금을 해제하십시요.
|
||||
addvault.new.readme.storageLocation.9=3. "표시" 버튼을 클릭하여 Vault에 접근하십시요.
|
||||
addvault.new.readme.storageLocation.10=만일 도움이 필요하신 경우, 다음의 문서를 참조하십시요: %s
|
||||
addvault.new.readme.storageLocation.7=1. 이 Vault를 Cryptomator에 추가하십시오.
|
||||
addvault.new.readme.storageLocation.8=2. Cryptomator에서 Vault 잠금을 해제하십시오.
|
||||
addvault.new.readme.storageLocation.9=3. "표시" 버튼을 클릭하여 Vault에 접근하십시오.
|
||||
addvault.new.readme.storageLocation.10=만일 도움이 필요하신 경우, 다음의 문서를 참조하십시오: %s
|
||||
addvault.new.readme.accessLocation.fileName=WELCOME.rtf
|
||||
addvault.new.readme.accessLocation.1=🔐️ 암호화 된 볼륨 🔐️
|
||||
addvault.new.readme.accessLocation.2=이것은 당신의 Vault 접근 위치입니다.
|
||||
addvault.new.readme.accessLocation.3=이 볼륨에 추가된 모든 파일은 Cryptomator로 암호화됩니다. 다른 드라이브/폴더처럼 작업할 수 있습니다. 볼륨의 내용은 복호화 된 것 처럼 보여지지만, 모든 파일은 항상 암호화되어 하드디스크에 저장됩니다.
|
||||
addvault.new.readme.accessLocation.3=이 볼륨에 추가된 모든 파일은 Cryptomator로 암호화됩니다. 다른 드라이브/폴더처럼 작업할 수 있습니다. 볼륨의 내용은 복호화 된 것처럼 보이지만, 모든 파일은 항상 암호화되어 하드디스크에 저장됩니다.
|
||||
addvault.new.readme.accessLocation.4=이 파일은 지우셔도 무방합니다.
|
||||
## Existing
|
||||
addvaultwizard.existing.title=기존 금고 추가
|
||||
addvaultwizard.existing.instruction=이미 존재하는 vault 폴더 내에서 "vault.cryptomator" 파일을 선택하세요. 만약 "masterkey.cryptomator"만 있다면 그걸 대신 선택하세요.
|
||||
addvaultwizard.existing.chooseBtn=선택
|
||||
addvaultwizard.existing.chooseBtn=선택…
|
||||
addvaultwizard.existing.filePickerTitle=Vault 파일 선택
|
||||
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault
|
||||
## Success
|
||||
@@ -105,37 +105,37 @@ addvaultwizard.success.unlockNow=지금 잠금해제
|
||||
# Remove Vault
|
||||
removeVault.title=Vault 제거
|
||||
removeVault.message=Vault를 삭제하시겠습니까?
|
||||
removeVault.description=이 행위는 단지 Cryptomator에서 이 Vault를 잊게합니다. 나중에 다시 추가 할 수 있습니다. 암호화된 파일은 하드디스크에서 삭제되지 않을 것입니다.
|
||||
removeVault.description=이 행위는 Cryptomator에서만 이 Vault를 지웁니다. 나중에 다시 추가할 수 있습니다. 암호화된 파일은 하드디스크에서 삭제되지 않습니다.
|
||||
|
||||
# Change Password
|
||||
changepassword.title=비밀번호 변경
|
||||
changepassword.enterOldPassword="%s"의 비밀번호를 입력하여 주십시요.
|
||||
changepassword.finalConfirmation=비밀번호를 잊어버리면, 데이터에 접근할 수 없음을 이해했습니다.
|
||||
changepassword.finalConfirmation=비밀번호를 잊어버리면, 데이터에 접근할 수 없다는 것을 이해했습니다.
|
||||
|
||||
# Forget Password
|
||||
forgetPassword.title=비밀번호 분실
|
||||
forgetPassword.title=비밀번호 삭제
|
||||
forgetPassword.message=저장된 비밀번호를 삭제할까요?
|
||||
forgetPassword.description=시스템 키체인에서 이 Vault의 저장된 비밀번호가 삭제될 것입니다.
|
||||
forgetPassword.confirmBtn=비밀번호 분실
|
||||
forgetPassword.confirmBtn=비밀번호 삭제
|
||||
|
||||
# Unlock
|
||||
unlock.title="%s" 잠금 해제
|
||||
unlock.passwordPrompt="%s"의 비밀번호를 입력하십시요.
|
||||
unlock.passwordPrompt="%s"의 비밀번호를 입력하십시오.
|
||||
unlock.savePassword=비밀번호 기억
|
||||
unlock.unlockBtn=잠금해제
|
||||
## Select
|
||||
unlock.chooseMasterkey.message=마스터키 파일을 찾을 수 없습니다
|
||||
unlock.chooseMasterkey.description=추정되는 위치에서 이 Vault의 마스터 키를 찾지 못했습니다. 마스터 키 위치를 수동으로 선택하여 주십시요.
|
||||
unlock.chooseMasterkey.filePickerTitle=마스터키 파일 선택
|
||||
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator 마스터키
|
||||
unlock.chooseMasterkey.description=이 Vault의 Masterkey를 찾지 못했습니다. 마스터 키 위치를 수동으로 선택하여 주십시오.
|
||||
unlock.chooseMasterkey.filePickerTitle=Masterkey 파일 선택
|
||||
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
|
||||
## Success
|
||||
unlock.success.message=잠금 해제 성공
|
||||
unlock.success.description="%s"이(가) 성공적으로 잠금 해제되었습니다. 이제 이 Vault를 마운트 지점으로 접근할 수 있습니다.
|
||||
unlock.success.rememberChoice=선택 기억함, 다시 묻지 않음
|
||||
unlock.success.rememberChoice=선택 기억하기, 다시 묻지 않음
|
||||
unlock.success.revealBtn=드라이브 표시
|
||||
## Failure
|
||||
unlock.error.customPath.message=Vault을 사용자 정의 경로에 마운트할 수 없습니다.
|
||||
unlock.error.customPath.description.notSupported=사용자 지정 경로를 계속 사용하려면 기본 설정으로 이동하여 이를 지원하는 볼륨 유형을 선택하세요. 그렇지 않으면 볼트 옵션으로 이동하여 지원되는 마운트 지점을 선택하십시오.
|
||||
unlock.error.customPath.message=Vault를 사용자 정의 경로에 마운트할 수 없습니다.
|
||||
unlock.error.customPath.description.notSupported=사용자 지정 경로를 계속 사용하려면 설정으로 이동하여 이를 지원하는 볼륨 유형을 선택하십시오. 그렇지 않으면 볼트 옵션으로 이동하여 지원되는 마운트 지점을 선택하십시오.
|
||||
unlock.error.customPath.description.notExists=사용자 정의 마운트 경로가 존재하지 않습니다. 로컬 파일 시스템에서 생성하거나 볼트 옵션에서 변경하세요.
|
||||
unlock.error.customPath.description.inUse=드라이브 문자 또는 사용자 정의 마운트 경로 "%s"가 이미 사용 중입니다.
|
||||
unlock.error.customPath.description.hideawayNotDir=잠금 해제에 사용된 임시 숨김 파일 "%3$s"을 제거할 수 없습니다. 파일을 확인한 후 수동으로 삭제해 주세요.
|
||||
@@ -143,11 +143,11 @@ unlock.error.customPath.description.couldNotBeCleaned=Vault를 "%s" 경로에
|
||||
unlock.error.customPath.description.notEmptyDir=사용자 정의 마운트 경로 "%s"은 빈 폴더가 아닙니다. 빈 폴더를 선택하고 다시 시도하세요.
|
||||
unlock.error.customPath.description.generic=이 볼트에 대한 사용자 정의 마운트 경로를 선택했지만 다음 메시지와 함께 해당 경로를 사용하지 못했습니다: %2$s
|
||||
unlock.error.restartRequired.message=Vault을 잠금 해제할 수 없습니다.
|
||||
unlock.error.restartRequired.description=볼트 옵션에서 볼륨 유형을 변경하거나 Cryptomator를 다시 시작하세요.
|
||||
unlock.error.restartRequired.description=볼트 옵션에서 볼륨 유형을 변경하거나 Cryptomator를 다시 시작하십시오.
|
||||
unlock.error.title="%s" 잠금 해제 실패
|
||||
## Hub
|
||||
hub.noKeychain.message=장치 키에 액세스할 수 없습니다
|
||||
hub.noKeychain.description=허브 저장소를 잠금 해제하려면 키체인을 사용하여 보호되는 장치 키가 필요합니다. 계속하려면 "%s"을 활성화하고 기본 설정에서 키체인을 선택하세요.
|
||||
hub.noKeychain.description=허브 저장소를 잠금 해제하려면 키체인을 사용하여 보호되는 장치 키가 필요합니다. 계속하려면 "%s"을 활성화하고 기본 설정에서 키체인을 선택하십시오.
|
||||
hub.noKeychain.openBtn=설정 열기
|
||||
### Waiting
|
||||
hub.auth.message=인증 대기중…
|
||||
@@ -273,7 +273,7 @@ health.result.fixStateFilter.fixing=문제 해결중…
|
||||
health.result.fixStateFilter.fixed=문제 해결됨
|
||||
health.result.fixStateFilter.fixFailed=문제 해결 실패
|
||||
## Fix Application
|
||||
health.fix.fixBtn=문제해결
|
||||
health.fix.fixBtn=문제 해결
|
||||
health.fix.successTip=문제 해결이 성공적으로 완료되었습니다
|
||||
health.fix.failTip=문제 해결 실패, 상세 정보는 로그를 참조하십시요.
|
||||
|
||||
@@ -290,7 +290,7 @@ preferences.general.keychainBackend=다음 경로에 비밀번호 저장
|
||||
preferences.general.quickAccessService=열린 Vault를 빠른 접근 위치에 추가하기
|
||||
## Interface
|
||||
preferences.interface=인터페이스
|
||||
preferences.interface.theme=테마설정
|
||||
preferences.interface.theme=테마
|
||||
preferences.interface.theme.automatic=자동
|
||||
preferences.interface.theme.dark=어둡게
|
||||
preferences.interface.theme.light=밝게
|
||||
@@ -331,7 +331,7 @@ preferences.updates.upToDate=현재 최신 버전의 Cryptomator를 사용하고
|
||||
|
||||
## Contribution
|
||||
preferences.contribute=후원하기
|
||||
preferences.contribute.registeredFor=%s (으)로 후원자 인증 등록됨
|
||||
preferences.contribute.registeredFor=%s(으)로 후원자 인증 등록됨
|
||||
preferences.contribute.noCertificate=Cryptomator를 후원하시고 후원자 인증을 받으십시요. 라이선스 키와 비슷하지만 무료 소프트웨어를 사용하는 멋진 사람들을 위한 것입니다. ;-)
|
||||
preferences.contribute.getCertificate=아직 후원자 인증이 없으신가요? 어떻게 얻는지 배울 수 있습니다.
|
||||
preferences.contribute.promptText=후원자 인증코드를 여기에 붙여넣기
|
||||
@@ -406,7 +406,7 @@ main.vaultDetail.unlockBtn=잠금 해제...
|
||||
main.vaultDetail.unlockNowBtn=지금 잠금 해제
|
||||
main.vaultDetail.optionsBtn=Vault 옵션
|
||||
main.vaultDetail.passwordSavedInKeychain=비밀번호 저장됨
|
||||
main.vaultDetail.share=공유하기
|
||||
main.vaultDetail.share=공유하기…
|
||||
### Unlocked
|
||||
main.vaultDetail.unlockedStatus=잠금 해제됨
|
||||
main.vaultDetail.accessLocation=이 Vault의 내용은 다음의 경로에서 접근할 수 있습니다:
|
||||
@@ -450,7 +450,7 @@ wrongFileAlert.link=더 많은 지원을 위해, 다음을 방문하십시오
|
||||
## General
|
||||
vaultOptions.general=일반
|
||||
vaultOptions.general.vaultName=Vault 이름
|
||||
vaultOptions.general.autoLock.lockAfterTimePart1=다음 시간동안 유휴상태 시 잠금 :
|
||||
vaultOptions.general.autoLock.lockAfterTimePart1=다음 시간 동안 유휴상태 시 잠그기
|
||||
vaultOptions.general.autoLock.lockAfterTimePart2=분
|
||||
vaultOptions.general.unlockAfterStartup=Cryptomator를 시작할 때 Vault 잠금 해제
|
||||
vaultOptions.general.actionAfterUnlock=성공적으로 잠금해제 후
|
||||
@@ -553,11 +553,15 @@ dokanySupportEnd.description=Cryptomator에서 Dokany 볼륨 형식은 더이상
|
||||
dokanySupportEnd.preferencesBtn=설정 열기
|
||||
|
||||
#Retry If Readonly
|
||||
retryIfReadonly.title=Vault 접근 제한됨
|
||||
retryIfReadonly.message=Vault 디렉터리에 쓰기 권한 없음
|
||||
retryIfReadonly.description=Cryptomator가 Vault 디렉터리에 쓸 수 없습니다. Vault를 읽기 전용으로 설정하고 다시 시도할 수 있습니다. 이 옵션은 Vault 옵션에서 바꿀 수 있습니다.
|
||||
retryIfReadonly.retry=바꾸고 다시 시도하기
|
||||
|
||||
# Share Vault
|
||||
shareVault.title=Vault 공유
|
||||
shareVault.message=Vault를 다른 사람과 공유하려 하십니까?
|
||||
shareVault.description=Vault를 타인과 공유할 때에는 항상 주의하십시오. 간단히 이 단계들을 따르세요.
|
||||
shareVault.description=Vault를 타인과 공유할 때에는 항상 주의하십시오. 간단히 이 단계들을 따르십시오.
|
||||
shareVault.instruction.1=1. 암호화된 Vault 폴더를 클라우드 스토리지를 통해 공유하십시오.
|
||||
shareVault.instruction.2=2. Vault의 비밀번호를 안전한 방식으로 전달하십시오.
|
||||
shareVault.remarkBestPractices=문서에 있는 권장사항을 통해 더 많은 정보를 확인하십시오.
|
||||
|
||||
@@ -15,8 +15,8 @@ public class ReadMeGeneratorTest {
|
||||
@ParameterizedTest
|
||||
@CsvSource({ //
|
||||
"test,test", //
|
||||
"t\u00E4st,t\\u228st", //
|
||||
"t\uD83D\uDE09st,t\\u55357\\u56841st", //
|
||||
"t\u00E4st,t\\'E4st", //
|
||||
"t\uD83D\uDE09st,t\\uc1\\u55357\\uc1\\u56841st", //
|
||||
})
|
||||
public void testEscapeNonAsciiChars(String input, String expectedResult) {
|
||||
ReadmeGenerator readmeGenerator = new ReadmeGenerator(null);
|
||||
@@ -40,7 +40,7 @@ public class ReadMeGeneratorTest {
|
||||
MatcherAssert.assertThat(result, CoreMatchers.startsWith("{\\rtf1\\fbidis\\ansi\\uc0\\fs32"));
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Dear User,}\\par"));
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 \\b please don't touch the \"d\" directory.}\\par "));
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Thank you for your cooperation \\u55357\\u56841}\\par"));
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Thank you for your cooperation \\uc1\\u55357\\uc1\\u56841}\\par"));
|
||||
MatcherAssert.assertThat(result, CoreMatchers.endsWith("}"));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user