Compare commits

...

181 Commits

Author SHA1 Message Date
Armin Schrenk
a1f58ffb0e Use the javaimage to extract wixhelper 2026-02-18 15:10:22 +01:00
Armin Schrenk
f21fae5b47 temurin 2026-02-18 14:46:46 +01:00
Armin Schrenk
5ab12c1b1a Update to latest JDK 25 2026-02-18 11:49:13 +01:00
Armin Schrenk
0ac63e7aba Closes #4136 2026-02-18 11:26:42 +01:00
Armin Schrenk
ac6f34fe17 reorder changelog 2026-02-18 11:15:46 +01:00
Armin Schrenk
dba3de230b Update dependencies
* org.cryptomator:integrations-win from 1.5.1 to 1.6.0
* org.cryptomator:webdav-nio-adapter from 3.0.0 to 3.0.1
2026-02-18 11:15:00 +01:00
Armin Schrenk
e11d42bb07 update IDE file 2026-02-18 11:07:18 +01:00
Jan-Peter Klein
171d5d5a4c Fixes #4141 2026-02-18 10:55:49 +01:00
Armin Schrenk
b86d8ca790 [skip ci] update changelog 2026-02-17 15:30:20 +01:00
Armin Schrenk
74fc77ab8d fix pom inconsistency 2026-02-17 15:21:16 +01:00
Armin Schrenk
044dbcb11f Update cryptofs to version 2.10.0-beta3 2026-02-17 15:13:46 +01:00
Armin Schrenk
1b243bb725 suppress logging of expected exception 2026-02-17 15:12:25 +01:00
Armin Schrenk
f21cb90e7d Merge pull request #4025 from purejava/new-secret-service
New keychain backend SecretService for Linux
2026-02-17 13:16:06 +01:00
Armin Schrenk
1e8f272f6a Merge pull request #4143 from cryptomator/dependabot/maven/java-production-dependencies-d98826387a
Bump the java-production-dependencies group across 1 directory with 11 updates
2026-02-17 13:15:07 +01:00
Armin Schrenk
5c1f8e7576 update IDE file 2026-02-17 12:46:19 +01:00
Armin Schrenk
bc0bb38e4c remove dagger formatting due to compile error 2026-02-17 12:45:46 +01:00
Armin Schrenk
b9b02acbe5 Merge pull request #4134 from cryptomator/feature/javafx-color-scheme-change
Feature: Use JavaFX built ins to adapt to OS color scheme
2026-02-17 10:36:23 +01:00
Armin Schrenk
e53598cfce update azure signing to stable version 2026-02-16 17:06:00 +01:00
Armin Schrenk
a79fd63634 [skip ci] update changelog 2026-02-16 16:15:05 +01:00
Armin Schrenk
f753ddc9be Apply suggestions from code review
Co-authored-by: Sebastian Stenzel <overheadhunter@users.noreply.github.com>
2026-02-16 16:05:26 +01:00
Armin Schrenk
158b454b0d fix NPE in EventualLogger 2026-02-16 12:45:57 +01:00
Armin Schrenk
9e6bd913cb refactor FxApplicationStyle
use pattern (removeOldListeners, addNewListerner, applyTheme)
2026-02-16 12:37:46 +01:00
Armin Schrenk
83ef9d06d9 simplify JfxUiAppearanceProvider
and move loading of appearance service into the fx app
2026-02-16 11:55:03 +01:00
Armin Schrenk
cf0052b4f5 revert lambda processing 2026-02-16 11:07:04 +01:00
Armin Schrenk
0b4d86768a Closes #4088 2026-02-16 10:13:21 +01:00
dependabot[bot]
a77e90738a Bump the java-production-dependencies group across 1 directory with 11 updates
Bumps the java-production-dependencies group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [org.cryptomator:cryptolib](https://github.com/cryptomator/cryptolib) | `2.2.1` | `2.2.2` |
| [org.cryptomator:fuse-nio-adapter](https://github.com/cryptomator/fuse-nio-adapter) | `5.1.0` | `6.0.0` |
| [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) | `1.5.19` | `1.5.31` |
| [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) | `1.5.19` | `1.5.31` |
| org.apache.commons:commons-lang3 | `3.19.0` | `3.20.0` |
| [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) | `2.20.0` | `2.21.0` |
| com.fasterxml.jackson.datatype:jackson-datatype-jsr310 | `2.20.0` | `2.21.0` |
| [com.google.dagger:dagger](https://github.com/google/dagger) | `2.57.2` | `2.59.1` |
| [com.google.dagger:dagger-compiler](https://github.com/google/dagger) | `2.57.2` | `2.59.1` |
| [com.github.ben-manes.caffeine:caffeine](https://github.com/ben-manes/caffeine) | `3.2.2` | `3.2.3` |
| [org.cryptomator:integrations-linux](https://github.com/cryptomator/integrations-linux) | `1.7.0-beta3` | `1.7.0-beta4` |



Updates `org.cryptomator:cryptolib` from 2.2.1 to 2.2.2
- [Release notes](https://github.com/cryptomator/cryptolib/releases)
- [Changelog](https://github.com/cryptomator/cryptolib/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/cryptolib/compare/2.2.1...2.2.2)

Updates `org.cryptomator:fuse-nio-adapter` from 5.1.0 to 6.0.0
- [Release notes](https://github.com/cryptomator/fuse-nio-adapter/releases)
- [Changelog](https://github.com/cryptomator/fuse-nio-adapter/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/fuse-nio-adapter/compare/5.1.0...6.0.0)

Updates `ch.qos.logback:logback-core` from 1.5.19 to 1.5.31
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.31)

Updates `ch.qos.logback:logback-classic` from 1.5.19 to 1.5.31
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.31)

Updates `ch.qos.logback:logback-classic` from 1.5.19 to 1.5.31
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.31)

Updates `org.apache.commons:commons-lang3` from 3.19.0 to 3.20.0

Updates `com.fasterxml.jackson.core:jackson-databind` from 2.20.0 to 2.21.0
- [Commits](https://github.com/FasterXML/jackson/commits)

Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.21.0

Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.21.0

Updates `com.google.dagger:dagger` from 2.57.2 to 2.59.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.2...dagger-2.59.1)

Updates `com.google.dagger:dagger-compiler` from 2.57.2 to 2.59.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.2...dagger-2.59.1)

Updates `com.github.ben-manes.caffeine:caffeine` from 3.2.2 to 3.2.3
- [Release notes](https://github.com/ben-manes/caffeine/releases)
- [Commits](https://github.com/ben-manes/caffeine/compare/v3.2.2...v3.2.3)

Updates `com.google.dagger:dagger-compiler` from 2.57.2 to 2.59.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.2...dagger-2.59.1)

Updates `org.cryptomator:integrations-linux` from 1.7.0-beta3 to 1.7.0-beta4
- [Release notes](https://github.com/cryptomator/integrations-linux/releases)
- [Changelog](https://github.com/cryptomator/integrations-linux/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/integrations-linux/compare/1.7.0-beta3...1.7.0-beta4)

---
updated-dependencies:
- dependency-name: org.cryptomator:cryptolib
  dependency-version: 2.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: org.cryptomator:fuse-nio-adapter
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-core
  dependency-version: 1.5.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: org.apache.commons:commons-lang3
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-version: 2.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  dependency-version: 2.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  dependency-version: 2.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: java-production-dependencies
- dependency-name: com.google.dagger:dagger
  dependency-version: 2.59.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: java-production-dependencies
- dependency-name: com.google.dagger:dagger-compiler
  dependency-version: 2.59.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: java-production-dependencies
- dependency-name: com.github.ben-manes.caffeine:caffeine
  dependency-version: 3.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: com.google.dagger:dagger-compiler
  dependency-version: 2.59.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: java-production-dependencies
- dependency-name: org.cryptomator:integrations-linux
  dependency-version: 1.7.0-beta4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 06:06:54 +00:00
mindmonk
0bec741c2b Merge pull request #4120 from cryptomator/feature/disable-password-tab-if-masterkey-missing
Disable recovery key actions in Vault Password options when masterkey is missing
2026-02-12 18:08:52 +01:00
Armin Schrenk
af43aeca15 Merge branch 'develop' into feature/javafx-color-scheme-change 2026-02-12 10:18:32 +01:00
Armin Schrenk
1629eae5d3 [skip ci] update changelog 2026-02-11 15:55:34 +01:00
Armin Schrenk
99898b74fb minor cleanup 2026-02-11 15:55:19 +01:00
Armin Schrenk
327d6065e9 Merge pull request #4105 from cryptomator/feature/admin-properties
Feature: Admin Config
2026-02-11 15:25:31 +01:00
Armin Schrenk
2a5ef5d999 log inconsistent state 2026-02-11 15:21:55 +01:00
Armin Schrenk
34e5d19a04 prevent resource leak 2026-02-11 15:16:11 +01:00
Armin Schrenk
33851a8559 Refactor admin props
* rename class to AdminPropertiesFactory
* rename factory method to "create"
* remove side effects from methods
* returned properties default to system properties
2026-02-11 15:12:52 +01:00
Armin Schrenk
fb54d96997 fix wrong method delegation 2026-02-11 14:49:56 +01:00
Armin Schrenk
cff47b1c73 wrap system properties instead of direct modification 2026-02-11 14:36:55 +01:00
Armin Schrenk
5db05d8bc7 Apply suggestions from codereview 2026-02-11 14:33:01 +01:00
Armin Schrenk
b85780eae9 Disable JavaFX based UiAppearanceProvider for macOS 2026-02-11 12:22:22 +01:00
Ralph Plawetzki
bdfd22c483 Partially reverts dd1af8cd78
Not needed anymore, see https://github.com/cryptomator/integrations-linux/pull/125#issuecomment-3876060158
2026-02-11 06:07:01 +01:00
Armin Schrenk
e3433cb312 Refactor FxApplicationStyle class 2026-02-10 18:17:09 +01:00
Armin Schrenk
46d1d605ad refactor JfxUiAppearanceProvider class
* use delegate pattern for initialization
* add logging
2026-02-10 18:01:36 +01:00
Armin Schrenk
a4eadd4817 remove theme change restrictions 2026-02-10 17:58:29 +01:00
Armin Schrenk
c6717bd4e1 Rely on javafx to change color theme 2026-02-09 17:48:23 +01:00
Armin Schrenk
53f368108a Rename jvm property and actual config file 2026-02-09 16:54:04 +01:00
Armin Schrenk
5e52811c74 Remove hard coded default locations for admin config 2026-02-09 16:49:12 +01:00
Armin Schrenk
cad7b45808 Refactor to first check system property cryptomator.adminConfig for config path 2026-02-09 16:16:26 +01:00
Armin Schrenk
a057bf6f70 Merge branch 'develop' into feature/admin-properties 2026-02-09 15:03:06 +01:00
Armin Schrenk
4cf11cf293 [skip ci] update changelog 2026-02-09 14:56:37 +01:00
Armin Schrenk
2523609996 disable loading of user defined config for windows msi (#4132) 2026-02-09 14:49:07 +01:00
dependabot[bot]
80276ee992 Bump the github-actions group across 1 directory with 8 updates (#4124)
Bumps the github-actions group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `6.0.1` | `6.0.2` |
| [actions/setup-java](https://github.com/actions/setup-java) | `5.1.0` | `5.2.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `5.0.0` | `6.0.0` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `6.0.0` | `7.0.0` |
| [actions/cache](https://github.com/actions/cache) | `4.3.0` | `5.0.3` |
| [skymatic/workflows/.github/workflows/run-dependency-check.yml](https://github.com/skymatic/workflows) | `3.0.1` | `3.0.2` |
| [fjogeleit/http-request-action](https://github.com/fjogeleit/http-request-action) | `1.16.6` | `2.0.0` |
| [skymatic/workflows](https://github.com/skymatic/workflows) | `3.0.1` | `3.0.2` |



Updates `actions/checkout` from 6.0.1 to 6.0.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8e8c483db8...de0fac2e45)

Updates `actions/setup-java` from 5.1.0 to 5.2.0
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](f2beeb24e1...be666c2fcd)

Updates `actions/upload-artifact` from 5.0.0 to 6.0.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](330a01c490...b7c566a772)

Updates `actions/download-artifact` from 6.0.0 to 7.0.0
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](018cc2cf5b...37930b1c2a)

Updates `actions/cache` from 4.3.0 to 5.0.3
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](0057852bfa...cdf6c1fa76)

Updates `skymatic/workflows/.github/workflows/run-dependency-check.yml` from 3.0.1 to 3.0.2
- [Release notes](https://github.com/skymatic/workflows/releases)
- [Commits](1074588008...2d0c27a7b0)

Updates `fjogeleit/http-request-action` from 1.16.6 to 2.0.0
- [Release notes](https://github.com/fjogeleit/http-request-action/releases)
- [Changelog](https://github.com/fjogeleit/http-request-action/blob/main/RELEASE.md)
- [Commits](c0b95d02a0...551353b829)

Updates `skymatic/workflows` from 3.0.1 to 3.0.2
- [Release notes](https://github.com/skymatic/workflows/releases)
- [Commits](1074588008...2d0c27a7b0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/setup-java
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/cache
  dependency-version: 5.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: skymatic/workflows/.github/workflows/run-dependency-check.yml
  dependency-version: 3.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: fjogeleit/http-request-action
  dependency-version: 2.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: skymatic/workflows
  dependency-version: 3.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 09:27:49 +01:00
Ralph Plawetzki
561432bc98 Revert "Make SecretServiceKeychainAccess the default selection"
This reverts commit 9a74194391.
Users decide when to migrate.
2026-02-05 08:15:10 +01:00
Ralph Plawetzki
cd0a640a36 Enable migrating KeychainEntries on Linux as well 2026-02-05 07:09:38 +01:00
Armin Schrenk
a7b8541912 Update Slack webhook URL secret in workflow 2026-01-30 15:27:31 +01:00
Jan-Peter Klein
d4382f3130 remove docTooltip 2026-01-28 18:40:56 +01:00
Jan-Peter Klein
2983745be5 remove docs link 2026-01-28 18:20:18 +01:00
Armin Schrenk
77983fe00a simplify EventualLogger init 2026-01-28 17:44:48 +01:00
Armin Schrenk
a4836f6528 cleanup 2026-01-28 17:36:52 +01:00
Armin Schrenk
e9b3b505a8 apply suggestions from code review 2026-01-28 17:35:34 +01:00
Armin Schrenk
19c9eada9d check first the filesize before opening a Reader to the config file 2026-01-28 17:32:10 +01:00
Jan-Peter Klein
2a5bce2c5c disable password tab when masterkey.file is missing 2026-01-27 17:42:04 +01:00
mindmonk
9ee81a0e35 Merge pull request #4106 from cryptomator/feature/move-recover-io-tasks-to-background
Move recovery I/O to background thread
2026-01-26 13:30:39 +01:00
Jan-Peter Klein
8b05ae0a54 dedup createTask 2026-01-23 16:09:06 +01:00
Jan-Peter Klein
aa898c634f refactor recovery restore to sync logic with async task wrapper for testability 2026-01-23 13:57:38 +01:00
Armin Schrenk
efbd107fb5 increase null safety 2026-01-21 18:09:26 +01:00
Armin Schrenk
300cac5441 don't forget log markers 2026-01-21 16:36:56 +01:00
Armin Schrenk
b651b9ac26 config file can be exactly the max size 2026-01-21 16:08:00 +01:00
Armin Schrenk
29e76e7f93 keep throwables in EventualLogger 2026-01-21 16:07:17 +01:00
Armin Schrenk
5cac6b8114 also adjust distribution 2026-01-21 15:55:04 +01:00
Armin Schrenk
35c2141fd6 cleanup 2026-01-21 15:34:34 +01:00
Armin Schrenk
b00c81c20a use "cryptomator.config" for config file 2026-01-21 15:34:24 +01:00
Armin Schrenk
7ee0606306 adjust unit tests 2026-01-21 15:34:08 +01:00
Armin Schrenk
046372f95b refactored BufferedLog
* rename to EventualLogger
* adhere to slf4j API
* ensure single instance
2026-01-21 15:33:26 +01:00
Armin Schrenk
c198adaf3f switch back to properties 2026-01-21 11:53:31 +01:00
Armin Schrenk
d53f0880ca add test for AdminPropertiesSetter 2026-01-15 18:06:24 +01:00
Armin Schrenk
088b177c0e Ensure that null map is also logged 2026-01-15 15:11:46 +01:00
Armin Schrenk
287ab4e792 [skip ci] Update changelog 2026-01-15 15:11:12 +01:00
Armin Schrenk
0e9cad5b09 Merge pull request #4103 from cryptomator/feature/basic-accessibility
Feature: Improve accessibility for core functionality
2026-01-15 10:44:16 +01:00
Armin Schrenk
a0c9caeb21 [skip ci] update changelog 2026-01-14 17:57:24 +01:00
Armin Schrenk
f620c6685a apply suggestions from AI review 2026-01-14 17:53:07 +01:00
Armin Schrenk
61be8c449c fixed flush does not clear BufferedLog 2026-01-14 17:39:49 +01:00
Armin Schrenk
43343b9954 fix wrong Linux path 2026-01-14 17:36:07 +01:00
Armin Schrenk
460b7e6225 [skip ci] update changelog 2026-01-14 17:33:45 +01:00
Armin Schrenk
e2430bfb22 add null check 2026-01-14 17:29:56 +01:00
Armin Schrenk
ac7c9c2165 use a bracket to "describe" the vault state 2026-01-14 17:28:20 +01:00
Jan-Peter Klein
efd73e0d3e move recovery IO operations to background 2026-01-14 17:14:23 +01:00
Armin Schrenk
b441803462 also adjust wix installer to new config format 2026-01-14 16:28:48 +01:00
Armin Schrenk
42b06aa556 Switch to JSON as config format
Java Properties require ISO 8859-1 character encoding, leading to manual edits of file.
2026-01-14 14:12:28 +01:00
Armin Schrenk
b1c21501a6 Add dummy admin properties file in Windows installer 2026-01-13 18:40:53 +01:00
Armin Schrenk
45633837e0 only allow overwriting a subset of JVM properties 2026-01-13 17:26:48 +01:00
Armin Schrenk
b23bd0b27a Implement logic to overwrite system properties set in app internal config file 2026-01-13 13:14:40 +01:00
Armin Schrenk
b3f3faf4ee add DIALOG role to more dialogs 2026-01-13 11:02:01 +01:00
Armin Schrenk
33f26bf804 Refactor notificationbar
* rename to InfoBar
* replace closeButton text with graphic and add accessibilityText
* use common base class for styling and use CSS lookup for variants
2026-01-12 23:31:37 +01:00
Armin Schrenk
a06accb80f add accessibletext to vault storage location 2026-01-12 23:15:40 +01:00
Armin Schrenk
a50e372f05 add correct accessible role to dialogs 2026-01-12 23:14:09 +01:00
Armin Schrenk
06726303fb add proper accessibility text for vault list 2026-01-12 23:13:15 +01:00
Armin Schrenk
11c86f7287 impl idea 2026-01-12 11:59:17 +01:00
mindmonk
8e68a62ab0 Merge pull request #4081 from cryptomator/feature/hub-vault-archived-dialog
Feature: Show dedicated “Archived Vault” screen on unlock when Hub returns 410
2026-01-06 11:20:29 +01:00
Jan-Peter Klein
787d0b8e12 code cleanup 2026-01-05 14:00:20 +01:00
Jan-Peter Klein
ba765f7a69 code cleanup 2026-01-05 13:31:45 +01:00
Jan-Peter Klein
e9d7e631c7 fix typo 2026-01-05 13:27:50 +01:00
Jan-Peter Klein
88acbc8ed7 fix typo 2026-01-05 13:22:01 +01:00
Jan-Peter Klein
e84a73b9c3 use SimpleDialog 2026-01-05 13:17:10 +01:00
Jan-Peter Klein
2324ddad8b update changelog 2026-01-05 11:53:51 +01:00
Jan-Peter Klein
04da2504de Merge branch 'develop' into feature/hub-vault-archived-dialog 2026-01-05 11:28:08 +01:00
Armin Schrenk
8105dbc236 Closes #4058 2025-12-30 12:31:45 +01:00
Armin Schrenk
ceed70c314 [skip ci] update changelog 2025-12-29 16:59:01 +01:00
Armin Schrenk
eddf586b81 Merge pull request #4078 from cryptomator/feature/files-in-use
Feature: Files in use
2025-12-29 16:00:36 +01:00
Armin Schrenk
0d7ddd3189 [skip ci] Update changelog 2025-12-29 15:55:49 +01:00
Armin Schrenk
491cd5e550 install/remove the encrypted mime type on install/uninstall 2025-12-29 15:22:26 +01:00
Armin Schrenk
93d95ecd93 make message more clear
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-21 12:42:11 +01:00
Armin Schrenk
05a41a625c reset selection index everytime window is shown
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-21 12:32:35 +01:00
Armin Schrenk
2aff02a186 cleanup
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-21 12:27:07 +01:00
Armin Schrenk
4b7de11750 dedup css
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-21 12:26:08 +01:00
Armin Schrenk
aaf921afcf Adjust notification dialog
* add scrollbar to description content
* move filename to own visual element
* move vaultName above message

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-21 11:40:02 +01:00
Armin Schrenk
7718445f7c rename method
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-21 11:00:01 +01:00
Armin Schrenk
0fa27f66c9 Use proper icon for in-use event 2025-12-19 13:54:47 +01:00
Armin Schrenk
111c5ae2de correctly convert URI path to system path 2025-12-19 13:25:42 +01:00
Armin Schrenk
8841bb1c6a add c9u as file extension 2025-12-18 17:55:43 +01:00
Armin Schrenk
8ca7e34d8f minor fixes 2025-12-18 16:47:45 +01:00
Armin Schrenk
0fa6f0d915 only close notification window on index error 2025-12-18 16:20:58 +01:00
Armin Schrenk
4ffd8f4467 add localization for file-is-in-use-event in event view 2025-12-18 16:19:42 +01:00
Armin Schrenk
bb0c1b54c1 Add log message to get used mount service 2025-12-18 15:39:56 +01:00
Armin Schrenk
8eb91f6471 Change design of files-in-use-notification
* add timestamp of event occurance
* move filename into message section
* remove translations
2025-12-18 13:28:04 +01:00
Armin Schrenk
eb20fd7278 add device name to filesystemowner and display device name in notification 2025-12-17 16:55:42 +01:00
Armin Schrenk
d836798361 only one instance of notification window 2025-12-17 16:27:10 +01:00
Jan-Peter Klein
760e260eba show archived vault dialog on unlock when Hub returns 410 2025-12-17 09:56:56 +01:00
Armin Schrenk
0cd6eb8b5e use Path api to concat (string) paths for events
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-16 12:52:35 +01:00
Armin Schrenk
0b2107309b guard the notification window from theoretical thread races
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-16 12:33:25 +01:00
dependabot[bot]
309881b339 Bump the github-actions group across 1 directory with 8 updates (#4073)
Bumps the github-actions group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `5.0.0` | `6.0.1` |
| [actions/setup-java](https://github.com/actions/setup-java) | `5.0.0` | `5.1.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4.6.2` | `5.0.0` |
| [softprops/action-gh-release](https://github.com/softprops/action-gh-release) | `2.4.1` | `2.5.0` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `5.0.0` | `6.0.0` |
| [fjogeleit/http-request-action](https://github.com/fjogeleit/http-request-action) | `1.16.5` | `1.16.6` |
| [actions/stale](https://github.com/actions/stale) | `10.1.0` | `10.1.1` |
| [skymatic/workflows](https://github.com/skymatic/workflows) | `450e322ff2214d0be0b079b63343c894f3ef735f` | `1074588008ae3326a2221ea451783280518f0366` |



Updates `actions/checkout` from 5.0.0 to 6.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](08c6903cd8...8e8c483db8)

Updates `actions/setup-java` from 5.0.0 to 5.1.0
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](dded088883...f2beeb24e1)

Updates `actions/upload-artifact` from 4.6.2 to 5.0.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](ea165f8d65...330a01c490)

Updates `softprops/action-gh-release` from 2.4.1 to 2.5.0
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](6da8fa9354...a06a81a03e)

Updates `actions/download-artifact` from 5.0.0 to 6.0.0
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](634f93cb29...018cc2cf5b)

Updates `fjogeleit/http-request-action` from 1.16.5 to 1.16.6
- [Release notes](https://github.com/fjogeleit/http-request-action/releases)
- [Commits](1297c6fc63...c0b95d02a0)

Updates `actions/stale` from 10.1.0 to 10.1.1
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](5f858e3efb...997185467f)

Updates `skymatic/workflows` from 450e322ff2214d0be0b079b63343c894f3ef735f to 1074588008ae3326a2221ea451783280518f0366
- [Release notes](https://github.com/skymatic/workflows/releases)
- [Commits](450e322ff2...1074588008)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-java
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: softprops/action-gh-release
  dependency-version: 2.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: fjogeleit/http-request-action
  dependency-version: 1.16.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/stale
  dependency-version: 10.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: skymatic/workflows
  dependency-version: 1074588008ae3326a2221ea451783280518f0366
  dependency-type: direct:production
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 09:54:39 +01:00
Armin Schrenk
532cdf30ac Fix typos 2025-12-12 17:41:29 +01:00
Armin Schrenk
d746e9e215 add translation to event 2025-12-12 16:55:15 +01:00
Armin Schrenk
408c80744a Show notification for fileIsInUseEvent 2025-12-12 13:29:46 +01:00
Armin Schrenk
bf890d2165 Merge pull request #4069 from cryptomator/feature/notify-fallback
Feature: Notification Window with JavaFX
2025-12-12 12:42:37 +01:00
Armin Schrenk
eb90a7fb33 Merge branch 'feature/files-in-use' into feature/notify-fallback
# Conflicts:
#	src/main/java/org/cryptomator/common/EventMap.java
#	src/main/java/org/cryptomator/common/vaults/Vault.java
2025-12-12 12:31:08 +01:00
Armin Schrenk
a47814f38c cleanup 2025-12-12 12:26:46 +01:00
Armin Schrenk
8b157fd935 add dark theme 2025-12-12 12:25:47 +01:00
Armin Schrenk
5e582f666c add accessibleText 2025-12-12 11:37:42 +01:00
Armin Schrenk
bebe3064ee hide paging if there is only one event 2025-12-12 11:21:39 +01:00
Armin Schrenk
bbf8e1d1e5 Ensure that keyloader is not reused between vaults 2025-12-11 14:35:23 +01:00
Armin Schrenk
98d2442deb For hub vaults set filesystem owner 2025-12-11 14:34:45 +01:00
Armin Schrenk
cac5c86dab Merge branch 'develop' into feature/files-in-use
# Conflicts:
#	pom.xml
2025-12-10 13:04:34 +01:00
Armin Schrenk
5a0145800d Redesign notifcation dialog
* add Cryptomator title
* include vault name in message
* remove bottom section and move button to current message area
2025-12-10 13:01:07 +01:00
Armin Schrenk
f4b56e5e51 rename variable 2025-12-10 12:34:33 +01:00
Armin Schrenk
7cfb63ed48 cleanup in NotificationController 2025-12-10 12:34:09 +01:00
Armin Schrenk
2521cd2bfb cleanup
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-09 18:13:04 +01:00
Armin Schrenk
1e861ac187 fix av-whitelist workflow 2025-12-09 09:34:25 +01:00
Tobias Hagemann
a4c85be61c Add accessible text to form controls and table columns (#4066) 2025-12-06 07:41:39 +01:00
Armin Schrenk
5b9e70da33 implement code suggestions
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 17:32:24 +01:00
Armin Schrenk
b83a3c27c6 [skip ci] remove unused import
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 16:52:04 +01:00
Armin Schrenk
5c2954de03 guard controller from NPE
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 16:43:39 +01:00
Armin Schrenk
8a834fe71c fix window placement
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 16:38:37 +01:00
Armin Schrenk
4ff2905723 regularly poll for notification updates
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 16:37:31 +01:00
Armin Schrenk
538e01b1e5 consider location of system bar when placing notification window
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 15:54:05 +01:00
Armin Schrenk
3135be9178 fix logic in notificationManager
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 12:14:07 +01:00
Armin Schrenk
2c456f5323 fix more issues
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 11:58:16 +01:00
Armin Schrenk
9d464e3a4f fix threading issue in notification manager
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 11:53:43 +01:00
Armin Schrenk
83199eb1de use VaultEvent for notification to also show affected vault
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 11:35:54 +01:00
Armin Schrenk
5772ffec17 activate notifications by consuming vault events
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 11:27:01 +01:00
Armin Schrenk
f3415277d7 cleanup
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 11:23:52 +01:00
Armin Schrenk
8e9a001fde clean up
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 10:50:48 +01:00
Armin Schrenk
5e229395f1 remove test code
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-05 10:50:32 +01:00
Armin Schrenk
18dd9770f2 add second fake event
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-04 17:03:47 +01:00
Armin Schrenk
185c5a4f5d implement more ui logic
and reduce css

Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-04 17:03:36 +01:00
Armin Schrenk
285ddac219 ai thoughts
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-04 16:20:49 +01:00
Armin Schrenk
9e631a78f2 Pimp notification dialog
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-12-04 15:35:05 +01:00
Tobias Hagemann
7fe9049266 Improve accessibility for icon-only controls and input labels (#4064) 2025-12-01 22:06:57 +01:00
Tobias Hagemann
39ab9ad63f Merge pull request #4055 from tamaracha/fix_a11y_buttons
Fix a11y for no-graphics buttons in main window
2025-12-01 16:13:10 +01:00
Armin Schrenk
b4528f825e [skip ci] drop interface approach and just add fx window 2025-11-29 18:03:55 +01:00
Armin Schrenk
3d679c73e7 [skip ci] impl draft
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-11-28 14:31:04 +01:00
Sebastian Stenzel
f4f093bb42 update GitHub Releases Template
[ci skip]
2025-11-24 19:57:24 +01:00
Tamara Cook
b6d0823c24 Add accessible text and tooltips to no-graphics buttons in main window 2025-11-23 22:24:31 +01:00
Tamara Cook
7f4776a995 Make buttons clickable by any input device 2025-11-23 19:26:55 +01:00
Armin Schrenk
9d2b31cd56 Merge branch 'develop' into feature/files-in-use 2025-11-21 10:15:24 +01:00
Armin Schrenk
cee22e34db adjust display text 2025-10-31 14:18:07 +01:00
Ralph Plawetzki
9a74194391 Make SecretServiceKeychainAccess the default selection 2025-10-27 05:33:35 +01:00
Armin Schrenk
18300ee67f [skip ci] adjust to changed API 2025-10-21 15:36:51 +02:00
Armin Schrenk
5797108dfe Merge branch 'develop' into feature/files-in-use 2025-10-16 12:33:14 +02:00
Armin Schrenk
e1ff552f08 [skip ci] pick a distinguishable name for fs owner 2025-10-15 17:27:12 +02:00
Armin Schrenk
f3d4494d91 [skip ci] Adjust event view to new API 2025-10-15 17:21:20 +02:00
Armin Schrenk
bdc1cc3f05 Merge branch 'develop' into feature/files-in-use
# Conflicts:
#	pom.xml
2025-10-15 17:20:37 +02:00
Armin Schrenk
f5bd23de51 test impl 2025-09-02 09:58:52 +02:00
157 changed files with 2228 additions and 647 deletions

View File

@@ -48,7 +48,7 @@ runs:
echo "client-secret=${{ inputs.client-secret }}" >> "$GITHUB_OUTPUT"
shell: bash
- name: Sign DLLs with Azure Trusted Signing
uses: azure/trusted-signing-action@fc390cf8ed0f14e248a542af1d838388a47c7a7c # v0.5.10
uses: azure/artifact-signing-action@87c2e83e6868da99d3380aa309851b32ed9a8346 # v1.1.0
with:
files-folder: ${{ inputs.base-dir }}
files-folder-filter: ${{ inputs.file-extensions }}
@@ -59,7 +59,7 @@ runs:
azure-tenant-id: ${{ steps.set-secrets.outputs.tenant-id }}
azure-client-id: ${{ steps.set-secrets.outputs.client-id }}
azure-client-secret: ${{ steps.set-secrets.outputs.client-secret }}
trusted-signing-account-name: cryptomatorSigning
signing-account-name: cryptomatorSigning
certificate-profile-name: production
endpoint: https://weu.codesigning.azure.net/
timestamp-rfc3161: http://timestamp.acs.microsoft.com

View File

@@ -19,7 +19,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.1+8.0.LTS'
JAVA_VERSION: '25.0.2+10.0.LTS'
jobs:
get-version:
@@ -44,9 +44,9 @@ jobs:
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
openjfx-sha: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -122,8 +122,8 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\""
@@ -175,7 +175,7 @@ jobs:
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: appimage-${{ matrix.appimage-suffix }}
path: |
@@ -185,7 +185,7 @@ jobs:
if-no-files-found: error
- name: Publish AppImage on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
@@ -201,7 +201,7 @@ jobs:
if: github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable'
steps:
- name: Download AppImages
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
path: downloads/
merge-multiple: true
@@ -212,7 +212,7 @@ jobs:
echo "x64-sha256sum=${X64_SHA256}" >> "$GITHUB_OUTPUT"
AARCH64_SHA256=$(sha256sum downloads/cryptomator-*-aarch64.AppImage | cut -d ' ' -f1)
echo "aarch64-sha256sum=${AARCH64_SHA256}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: 'cryptomator/aur-bin'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}

View File

@@ -48,7 +48,7 @@ jobs:
env:
AUR_PR_URL: tbd
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: 'cryptomator/aur'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}

View File

@@ -7,6 +7,16 @@ on:
description: "Url to the file to upload"
required: true
type: string
avast:
description: "Upload to Avast"
required: false
type: boolean
default: true
kaspersky:
description: "Upload to Kaspersky"
required: false
type: boolean
default: true
workflow_dispatch:
inputs:
url:
@@ -39,9 +49,9 @@ jobs:
url="${INPUT_URL}"
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
- name: Download file
run: curl --remote-name ${INPUT_URL} -L -o ${{steps.extractName.outputs.fileName}}
run: curl "${INPUT_URL}" -L -o "${{steps.extractName.outputs.fileName}}" --fail-with-body
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ${{ steps.extractName.outputs.fileName }}
path: ${{ steps.extractName.outputs.fileName }}
@@ -50,10 +60,10 @@ jobs:
name: Anti Virus Allowlisting Kaspersky
runs-on: ubuntu-latest
needs: download-file
if: github.event_name == 'workflow_call' || inputs.kaspersky
if: inputs.kaspersky
steps:
- name: Download artifact
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ${{ needs.download-file.outputs.fileName }}
path: upload
@@ -70,10 +80,10 @@ jobs:
name: Anti Virus Allowlisting Avast
runs-on: ubuntu-latest
needs: download-file
if: github.event_name == 'workflow_call' || inputs.avast
if: inputs.avast
steps:
- name: Download artifact
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ${{ needs.download-file.outputs.fileName }}
path: upload

View File

@@ -22,14 +22,14 @@ jobs:
name: Compile and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Cache SonarCloud packages
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
@@ -49,29 +49,28 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Draft a release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
generate_release_notes: true
body: |-
:construction: Work in Progress
> [!NOTE]
> 🚧 Work in Progress 🚧
>
> Please be patient, the [builds are still running](https://github.com/cryptomator/cryptomator/actions). Binary packages can be found here in a few moments.
<!--REPLACE with auto-generated release notes (see below)
### What's New 🎉
### Bugfixes 🐛
### Other Changes 📎
END REPLACE-->
Feel free to also read our [CHANGELOG.md](https://github.com/cryptomator/cryptomator/blob/develop/CHANGELOG.md).
---
### Full Changelog
See [CHANGELOG.md](https://github.com/cryptomator/cryptomator/blob/develop/CHANGELOG.md).
📜 List of closed issues is available [here](TODO)
---
⏳ Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. ⏳
<!-- Don't forget to include the
💾 SHA-256 checksums of release artifacts:
@@ -79,4 +78,9 @@ jobs:
```
-->
As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
> [!TIP]
> You can verify the GPG signature of all assets using our public key: [`5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
<!-- Auto-Generated Release Notes: -->

View File

@@ -26,7 +26,7 @@ jobs:
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,2) >> "$env:GITHUB_ENV"
shell: pwsh
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: ${{ env.JDK_MAJOR_VERSION}}
distribution: ${{ env.JDK_VENDOR }}

View File

@@ -23,7 +23,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.1+8.0.LTS'
JAVA_VERSION: '25.0.2+10.0.LTS'
DEB_BUILD_DEPENDS: 'debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
@@ -43,7 +43,7 @@ jobs:
env:
INPUT_PPAVER: ${{ inputs.ppaver }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- id: deb-version
name: Determine deb-version
run: |
@@ -60,7 +60,7 @@ jobs:
env:
DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }}
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -143,7 +143,7 @@ jobs:
run: |
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: linux-deb-package
path: |

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-dependencies:
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@1074588008ae3326a2221ea451783280518f0366 # v3.0.1
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@957d3c2c08c56855fdac41e5afb9a7aca8c30dd9 # v3.0.3
with:
runner-os: 'ubuntu-latest'
java-distribution: 'temurin'
@@ -16,4 +16,4 @@ jobs:
nvd-api-key: ${{ secrets.NVD_API_KEY }}
ossindex-username: ${{ secrets.OSSINDEX_USERNAME }}
ossindex-token: ${{ secrets.OSSINDEX_API_TOKEN }}
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}

View File

@@ -53,7 +53,7 @@ jobs:
INTERVAL: 900
JSON_DATA: ${{ steps.get-stats.outputs.result }}
- name: Upload Results
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
uses: fjogeleit/http-request-action@551353b829c3646756b2ec2b3694f819d7957495 # v2.0.0
with:
url: 'https://graphite-us-central1.grafana.net/metrics'
method: 'POST'

View File

@@ -43,7 +43,7 @@ jobs:
env:
FLATHUB_PR_URL: tbd
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: 'flathub/org.cryptomator.Cryptomator'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}

View File

@@ -35,11 +35,11 @@ jobs:
revNum: ${{ steps.versions.outputs.revNum }}
type: ${{ steps.versions.outputs.type}}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}

View File

@@ -24,7 +24,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.1+8.0.LTS'
JAVA_VERSION: '25.0.2+10.0.LTS'
jobs:
get-version:
@@ -47,9 +47,9 @@ jobs:
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-x64_bin-jmods.zip'
openjfx-sha: '0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -128,8 +128,8 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/Cryptomator/ipc.socket\""
@@ -283,7 +283,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: dmg-${{ matrix.output-suffix }}
path: |
@@ -292,7 +292,7 @@ jobs:
if-no-files-found: error
- name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}

View File

@@ -22,7 +22,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.1+8.0.LTS'
JAVA_VERSION: '25.0.2+10.0.LTS'
jobs:
get-version:
@@ -45,9 +45,9 @@ jobs:
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-aarch64_bin-jmods.zip'
openjfx-sha: '13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -126,8 +126,8 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/Cryptomator/ipc.socket\""
@@ -282,7 +282,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: dmg-${{ matrix.output-suffix }}
path: |
@@ -291,7 +291,7 @@ jobs:
if-no-files-found: error
- name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}

View File

@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with:
days-before-stale: 14
days-before-close: 0

View File

@@ -19,7 +19,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Publish asc on GitHub Releases
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}

View File

@@ -16,8 +16,8 @@ jobs:
name: Compile and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}

View File

@@ -19,9 +19,9 @@ jobs:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -49,7 +49,7 @@ jobs:
exit 1
fi
- name: Cache NVD DB
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.m2/repository/org/owasp/dependency-check-data/
key: dependency-check-${{ github.run_id }}

View File

@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with:
days-before-stale: 365
days-before-close: 90

View File

@@ -47,13 +47,13 @@ jobs:
include:
- arch: x64
os: windows-latest
java-dist: 'zulu' #cannot use temurin, see https://github.com/cryptomator/cryptomator/issues/3824#issuecomment-2829827427
java-version: '25.0.1+8'
java-dist: 'temurin' #cannot use temurin, see https://github.com/cryptomator/cryptomator/issues/3824#issuecomment-2829827427
java-version: '25.0.2+10.0.LTS'
java-package: 'jdk'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ matrix.java-dist }}
java-version: ${{ matrix.java-version }}
@@ -140,8 +140,8 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.adminConfigPath=\"C:/ProgramData/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{appdata}/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{localappdata}/Cryptomator/ipc.socket\""
@@ -185,9 +185,18 @@ jobs:
- name: Extract wixhelper.dll for Codesigning #see https://github.com/cryptomator/cryptomator/issues/3130
shell: pwsh
run: |
New-Item -Path appdir/jpackage-jmod -ItemType Directory
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
$extractDir = "appdir/jpackage-jimage"
New-Item -Path $extractDir -ItemType Directory -Force | Out-Null
& "$env:JAVA_HOME\bin\jimage.exe" extract --dir $extractDir "$env:JAVA_HOME\lib\modules"
$wixhelper = Get-ChildItem -Path $extractDir -Recurse -File -Filter "wixhelper.dll" | Select-Object -First 1
if (-not $wixhelper) {
throw "wixhelper.dll not found in $env:JAVA_HOME\lib\modules"
}
Copy-Item -Path $wixhelper.FullName -Destination "appdir/wixhelper.dll" -Force
Remove-Item -Path $extractDir -Recurse -Force
- name: Sign DLLs with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release'
uses: ./.github/actions/win-sign-action
@@ -200,7 +209,7 @@ jobs:
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign DLLs with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
uses: skymatic/workflows/.github/actions/win-sign-action@957d3c2c08c56855fdac41e5afb9a7aca8c30dd9 # no specific version
with:
base-dir: 'appdir'
file-extensions: 'dll,exe,ps1'
@@ -277,7 +286,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: msi-${{ matrix.arch }}
path: |
@@ -299,21 +308,21 @@ jobs:
java-version: '24.0.1+9'
java-package: 'jdk'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install wix and extensions
run: |
dotnet tool install --global wix --version 6.0.0
wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global
wix.exe extension add WixToolset.Util.wixext/6.0.0 --global
- name: Download .msi
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: msi-${{ matrix.arch }}
path: dist/win/bundle/resources
- name: Strip version info from msi file name
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ matrix.java-dist }}
java-version: ${{ matrix.java-version }}
@@ -376,7 +385,7 @@ jobs:
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign burn engine with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
uses: skymatic/workflows/.github/actions/win-sign-action@957d3c2c08c56855fdac41e5afb9a7aca8c30dd9 # no specific version
with:
base-dir: 'tmp'
file-extensions: 'exe'
@@ -400,7 +409,7 @@ jobs:
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign installer with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
uses: skymatic/workflows/.github/actions/win-sign-action@957d3c2c08c56855fdac41e5afb9a7aca8c30dd9 # no specific version
with:
base-dir: 'installer'
file-extensions: 'exe'
@@ -418,7 +427,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: exe-${{ matrix.executable-suffix }}
path: |
@@ -436,12 +445,12 @@ jobs:
download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }}
steps:
- name: Download installers
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
merge-multiple: true
- name: Publish installers on GitHub Releases
id: publish
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}

43
.idea/compiler.xml generated
View File

@@ -12,17 +12,15 @@
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<option name="dagger.fastInit" value="enabled" />
<option name="dagger.formatGeneratedSource" value="enabled" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.57.2/dagger-compiler-2.57.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.57.2/dagger-2.57.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.59.1/dagger-compiler-2.59.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.59.1/dagger-2.59.1.jar" />
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.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.57.2/dagger-spi-2.57.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.59.1/dagger-spi-2.59.1.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.1.21-2.0.2/symbol-processing-api-2.1.21-2.0.2.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/google/devtools/ksp/symbol-processing-api/2.2.20-2.0.3/symbol-processing-api-2.2.20-2.0.3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.33.0/google-java-format-1.33.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" />
@@ -37,8 +35,33 @@
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.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.3/checker-compat-qual-2.5.3.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-metadata-jvm/2.1.21/kotlin-metadata-jvm-2.1.21.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.21/kotlin-stdlib-2.1.21.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-metadata-jvm/2.2.20/kotlin-metadata-jvm-2.2.20.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.59.1/dagger-compiler-2.59.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.59.1/dagger-2.59.1.jar" />
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.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.59.1/dagger-spi-2.59.1.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.2.20-2.0.3/symbol-processing-api-2.2.20-2.0.3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.33.0/google-java-format-1.33.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/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$/javax/inject/javax.inject/1/javax.inject-1.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.3/checker-compat-qual-2.5.3.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-metadata-jvm/2.2.20/kotlin-metadata-jvm-2.2.20.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
</processorPath>
<module name="cryptomator" />
@@ -47,7 +70,7 @@
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled" />
<module name="cryptomator" options="-Adagger.fastInit=enabled" />
</option>
</component>
</project>

View File

@@ -10,7 +10,34 @@ Changes to prior versions can be found on the [Github release page](https://gith
## [Unreleased](https://github.com/cryptomator/cryptomator/compare/1.18.0...HEAD)
### Added
* New Self-Update Mechanism (initially for .dmg and Flatpak packages) (#3948)
* Self-Update Mechanism ([#3948](https://github.com/cryptomator/cryptomator/pull/3948))
* Implemented `.dmg` update mechanism
* Implemented Flatpak update mechanism
* App notifications ([#4069](https://github.com/cryptomator/cryptomator/pull/4069))
* Mark files in-use for Hub vaults ([#4078](https://github.com/cryptomator/cryptomator/pull/4078))
* Accessibility: Adjust app to be used with a screen reader ([#547](https://github.com/cryptomator/cryptomator/issues/547))
* Show Archived Vault Dialog on unlock when Hub returns 410 ([#4081](https://github.com/cryptomator/cryptomator/pull/4081))
* Support automatic app theme selection according to OS theme on Linux ([#4027](https://github.com/cryptomator/cryptomator/issues/4027))
* Admin configuration: Allow overwriting certain app properties by external config file ([#4105](https://github.com/cryptomator/cryptomator/pull/4105))
### Changed
* Built using JDK 25 (#4031)
* Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
* Modernized Template for GitHub Releases
* Disable user defined app start config on Windows ([#4132](https://github.com/cryptomator/cryptomator/issues/4132))
* Updated dependencies
* `ch.qos.logback:*` from 1.5.19 to 1.5.31
* `com.fasterxml.jackson.core:jackson-databind` from 2.20.0 to 2.21.0
* `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.21.0
* `com.github.ben-manes.caffeine:caffeine` from 3.2.2 to 3.2.3
* `com.google.dagger:*` from 2.57.2 to 2.59.1
* `org.apache.commons:commons-lang3` from 3.19.0 to 3.20.0
* `org.cryptomator:cryptofs` from 2.9.0 to 2.10.0-beta3
* `org.cryptomator:cryptolib` from 2.2.1 to 2.2.2
* `org.cryptomator:fuse-nio-adapter` from 5.1.0 to 6.0.0
* `org.cryptomator:integrations-api` from 1.7.0 to 1.8.0-beta1
* `org.cryptomator:integrations-linux` from 1.6.1 to 1.7.0-beta4
* `org.cryptomator:integrations-mac` from 1.4.1 to 1.5.0-beta3
* `org.cryptomator:integrations-win` from 1.5.1 to 1.6.0
* `org.cryptomator:webdav-nio-adapter` from 3.0.0 to 3.0.1

8
dist/common/cryptomator.config vendored Normal file
View File

@@ -0,0 +1,8 @@
# This is the Cryptomator administrative configuration file.
# It is a simple key-value pair file.
# Lines starting with '#' are comments and will be ignored.
# For more info, read the docs at https://docs.cryptomator.org/desktop/advanced-settings/
#
# Example:
# Sets the plugin directory and enables plugin loading
# cryptomator.pluginDir=@{userhome}/Cryptomator/Plugins

View File

@@ -88,8 +88,8 @@ ${JAVA_HOME}/bin/jpackage \
--app-version "${VERSION}.${REVISION_NO}" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
--java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \
@@ -115,6 +115,7 @@ cp ../common/org.cryptomator.Cryptomator.tray-unlocked.svg Cryptomator.AppDir/us
cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop
cp ../common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml
cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml
cp ../common/application-vnd.cryptomator.encrypted.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.encrypted.xml
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/vnd.cryptomator.encrypted">
<comment>Cryptomator Encrypted Data</comment>
<glob pattern="*.c9r"/>
<glob pattern="*.c9s"/>
<glob pattern="*.c9u"/>
</mime-type>
</mime-info>

View File

@@ -6,4 +6,5 @@ common/org.cryptomator.Cryptomator.tray-unlocked.svg usr/share/icons/hicolor/sca
common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps
common/org.cryptomator.Cryptomator512.png usr/share/icons/hicolor/512x512/apps
common/org.cryptomator.Cryptomator.metainfo.xml usr/share/metainfo
common/application-vnd.cryptomator.vault.xml usr/share/mime/packages
common/application-vnd.cryptomator.vault.xml usr/share/mime/packages
common/application-vnd.cryptomator.encrypted.xml usr/share/mime/packages

View File

@@ -25,6 +25,7 @@ case "$1" in
fi
xdg-desktop-menu install --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop
xdg-mime install /usr/share/mime/packages/application-vnd.cryptomator.vault.xml
xdg-mime install /usr/share/mime/packages/application-vnd.cryptomator.encrypted.xml
;;
abort-upgrade|abort-remove|abort-deconfigure)

View File

@@ -23,6 +23,7 @@ case "$1" in
xdg-desktop-menu uninstall --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop
xdg-mime uninstall /usr/share/mime/packages/application-vnd.cryptomator.vault.xml
xdg-mime uninstall /usr/share/mime/packages/application-vnd.cryptomator.encrypted.xml
;;
failed-upgrade)

View File

@@ -51,8 +51,8 @@ override_dh_auto_build:
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
--java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \

View File

@@ -114,9 +114,9 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dapple.awt.enableTemplateImages=true" \
--java-options "-Dsun.java2d.metal=true" \
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/${APP_NAME}\"" \
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/${APP_NAME}/Plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/${APP_NAME}/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/${APP_NAME}/ipc.socket\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/${APP_NAME}/key.p12\"" \

View File

@@ -105,6 +105,7 @@
<array>
<string>c9r</string>
<string>c9s</string>
<string>c9u</string>
</array>
<key>public.mime-type</key>
<array>

2
dist/win/build.ps1 vendored
View File

@@ -155,7 +155,7 @@ $javaOptions = @(
"--java-options", "-Djava.net.useSystemProxies=true"
"--java-options", "-Dcryptomator.logDir=`"@{localappdata}/$AppName`""
"--java-options", "-XX:ErrorFile=`"C:/cryptomator/cryptomator_crash.log`""
"--java-options", "-Dcryptomator.pluginDir=`"@{appdata}/$AppName/Plugins`""
"--java-options", "-Dcryptomator.adminConfigPath=`"C:/ProgramData/$AppName/config.properties`""
"--java-options", "-Dcryptomator.settingsPath=`"@{appdata}/$AppName/settings.json;@{userhome}/AppData/Roaming/$AppName/settings.json`""
"--java-options", "-Dcryptomator.ipcSocketPath=`"@{localappdata}/$AppName/ipc.socket`""
"--java-options", "-Dcryptomator.p12Path=`"@{appdata}/$AppName/key.p12;@{userhome}/AppData/Roaming/$AppName/key.p12`""

12
dist/win/contrib/disableUserConfig.bat vendored Normal file
View File

@@ -0,0 +1,12 @@
@echo off
:: Batch wrapper for PowerShell script to disable user configuration in Cryptomator
:: This is executed as a Custom Action during MSI installation
:: This file must be located in the INSTALLDIR
:: Change to INSTALLDIR
cd %~dp0
:: Execute the PowerShell script
powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File ".\disableUserConfig.ps1"
:: Return the exit code from PowerShell
exit /b %ERRORLEVEL%

24
dist/win/contrib/disableUserConfig.ps1 vendored Normal file
View File

@@ -0,0 +1,24 @@
# PowerShell script to disable user configuration
# This script is executed as a Custom Action during MSI installation
# It deletes the file .package, effectively disabling user specific jpackage configuration.
# NOTE: This file must be located in the same directory as set in the MSI property INSTALLDIR
try {
# Determine file path
$packageFile = Join-Path $PSScriptRoot 'app\.package'
#check if file exists
if (Test-Path -Path $packageFile) {
Write-Host "Deleting file: $packageFile"
Remove-Item -Path $packageFile -Force -ErrorAction Stop
} else {
Write-Host "File not found: $packageFile. Skipping deletion."
}
exit 0
}
catch {
Write-Error "Error deleting package file: $_"
exit 1
}

View File

@@ -87,17 +87,37 @@
<!-- Non-Opening ProgID -->
<ns0:DirectoryRef Id="INSTALLDIR">
<ns0:Component Bitness="always64" Id="nonStartingProgID" >
<ns0:Component Bitness="always64" Id="nonStartingProgID">
<ns0:File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\$(var.IconFileEncryptedData)" Name="$(var.IconFileEncryptedData)"/>
<ns0:ProgId Id="$(var.JpAppName).Encrypted.1" Description="$(var.JpAppName) Encrypted Data" Icon="IconFileForEncryptedData" IconIndex="0">
<ns0:Extension Id="c9r" Advertise="no" ContentType="$(var.ProgIdContentType)">
<ns0:MIME ContentType="$(var.ProgIdContentType)" Default="yes"/>
</ns0:Extension>
<ns0:Extension Id="c9s" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
<ns0:Extension Id="c9u" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
</ns0:ProgId>
</ns0:Component>
</ns0:DirectoryRef>
<ns0:StandardDirectory Id="CommonAppDataFolder">
<ns0:Directory Id="CryptomatorDesktopProgramData" Name="Cryptomator">
<ns0:Component Id="AdminConfigDir" Guid="c078b7da-ba6e-4069-a5ab-5c0f0f9856a0">
<ns0:CreateFolder>
<util:PermissionEx User="SYSTEM" GenericAll="yes"/>
<util:PermissionEx User="Administrators" GenericAll="yes"/>
<util:PermissionEx User="Users" GenericRead="yes" GenericExecute="yes"/>
</ns0:CreateFolder>
</ns0:Component>
<ns0:Component Id="AdminConfigFile" NeverOverwrite="yes" Permanent="yes">
<ns0:File Id="EmptyAdminConfig" Source="$(env.JP_WIXWIZARD_RESOURCES)\..\..\common\cryptomator.config" Name="cryptomator.config" KeyPath="yes">
<util:PermissionEx User="SYSTEM" GenericAll="yes"/>
<util:PermissionEx User="Administrators" GenericAll="yes"/>
<util:PermissionEx User="Users" GenericRead="yes" GenericExecute="yes"/>
</ns0:File>
</ns0:Component>
</ns0:Directory>
</ns0:StandardDirectory>
<!-- Standard required root -->
<ns0:Feature Id="DefaultFeature" Title="!(loc.MainFeatureTitle)" Level="1">
@@ -105,7 +125,9 @@
<ns0:ComponentGroupRef Id="Files"/>
<ns0:ComponentGroupRef Id="FileAssociations"/>
<!-- Ref to additional ProgIDs -->
<ns0:ComponentRef Id="nonStartingProgID" />
<ns0:ComponentRef Id="nonStartingProgID"/>
<ns0:ComponentRef Id="AdminConfigDir"/>
<ns0:ComponentRef Id="AdminConfigFile"/>
</ns0:Feature>
<ns0:CustomAction Id="JpSetARPINSTALLLOCATION" Property="ARPINSTALLLOCATION" Value="[INSTALLDIR]" />
@@ -131,6 +153,10 @@
<!-- Property for controlling update check behavior (can be set via command line) -->
<ns0:Property Id="DISABLEUPDATECHECK" Secure="yes" />
<!-- Disable user config -->
<ns0:SetProperty Id="DisableUserConfig" Value="&quot;[INSTALLDIR]disableUserConfig.bat&quot;" Sequence="execute" Before="DisableUserConfig" />
<ns0:CustomAction Id="DisableUserConfig" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- WebDAV patches -->
<ns0:SetProperty Id="PatchWebDAV" Value="&quot;[INSTALLDIR]patchWebDAV.bat&quot; &quot;$(var.LoopbackAlias)&quot;" Sequence="execute" Before="PatchWebDAV" />
<ns0:CustomAction Id="PatchWebDAV" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
@@ -187,9 +213,10 @@
<ns0:Custom Action="FailOnRunningApp" After="Wix4CloseApplications_$(sys.BUILDARCHSHORT)" Condition="FOUNDRUNNINGAPP" />
<ns0:RemoveExistingProducts After="InstallValidate"/> <!-- Moved from CostInitialize, due to Wix4CloseApplications_* -->
<ns0:Custom Action="DisableUserConfig" After="InstallFiles" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
<!-- Skip action on uninstall -->
<!-- TODO: don't skip action, but remove cryptomator alias from hosts file -->
<ns0:Custom Action="PatchWebDAV" After="InstallFiles" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
<ns0:Custom Action="PatchWebDAV" After="DisableUserConfig" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
<!-- Configure update check setting if property is provided -->
<ns0:Custom Action="PatchUpdateCheck" After="PatchWebDAV" Condition="DISABLEUPDATECHECK AND NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
</ns0:InstallExecuteSequence>

27
pom.xml
View File

@@ -33,23 +33,25 @@
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version>
<cryptomator.cryptofs.version>2.10.0-beta3</cryptomator.cryptofs.version>
<cryptomator.cryptolib.version>2.2.2</cryptomator.cryptolib.version>
<cryptomator.integrations.version>1.8.0-beta1</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.5.1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.5.0-beta1</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.7.0-beta1</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>5.1.0</cryptomator.fuse.version>
<cryptomator.webdav.version>3.0.0</cryptomator.webdav.version>
<cryptomator.integrations.win.version>1.6.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.5.0-beta3</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.7.0-beta4</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>6.0.0</cryptomator.fuse.version>
<cryptomator.webdav.version>3.0.1</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<commons-lang3.version>3.19.0</commons-lang3.version>
<dagger.version>2.57.2</dagger.version>
<caffeine.version>3.2.3</caffeine.version>
<commons-lang3.version>3.20.0</commons-lang3.version>
<dagger.version>2.59.1</dagger.version>
<easybind.version>2.2</easybind.version>
<jackson.version>2.20.0</jackson.version>
<jackson.version>2.21.0</jackson.version>
<javafx.version>25</javafx.version>
<jwt.version>4.5.0</jwt.version>
<nimbus-jose.version>10.5</nimbus-jose.version>
<logback.version>1.5.19</logback.version>
<logback.version>1.5.31</logback.version>
<slf4j.version>2.0.17</slf4j.version>
<tinyoauth2.version>0.8.1</tinyoauth2.version>
<zxcvbn.version>1.9.0</zxcvbn.version>
@@ -94,7 +96,7 @@
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptolib</artifactId>
<version>2.2.1</version>
<version>${cryptomator.cryptolib.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
@@ -228,7 +230,7 @@
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.2.2</version>
<version>${caffeine.version}</version>
</dependency>
<!-- JUnit / Mockito / Hamcrest -->
<dependency>
@@ -330,7 +332,6 @@
</annotationProcessorPaths>
<compilerArgs>
<arg>-Adagger.fastInit=enabled</arg>
<arg>-Adagger.formatGeneratedSource=enabled</arg>
</compilerArgs>
</configuration>
</plugin>

View File

@@ -1,5 +1,4 @@
import ch.qos.logback.classic.spi.Configurator;
import org.cryptomator.networking.SSLContextWithPKCS12TrustStore;
import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider;
import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider;
@@ -14,11 +13,14 @@ import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvid
import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider;
import org.cryptomator.networking.SSLContextWithMacKeychain;
import org.cryptomator.networking.SSLContextProvider;
import org.cryptomator.networking.SSLContextWithWindowsCertStore;
import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.logging.LogbackConfiguratorFactory;
import org.cryptomator.networking.SSLContextProvider;
import org.cryptomator.networking.SSLContextWithMacKeychain;
import org.cryptomator.networking.SSLContextWithPKCS12TrustStore;
import org.cryptomator.networking.SSLContextWithWindowsCertStore;
import org.cryptomator.ui.fxapp.JfxUiAppearanceProvider;
import org.cryptomator.ui.traymenu.AwtTrayMenuController;
open module org.cryptomator.desktop {
@@ -61,6 +63,7 @@ open module org.cryptomator.desktop {
uses SSLContextProvider;
uses org.cryptomator.event.NotificationHandler;
provides UiAppearanceProvider with JfxUiAppearanceProvider;
provides TrayMenuController with AwtTrayMenuController;
provides Configurator with LogbackConfiguratorFactory;
provides SSLContextProvider with SSLContextWithWindowsCertStore, SSLContextWithMacKeychain, SSLContextWithPKCS12TrustStore;

View File

@@ -13,5 +13,7 @@ public interface Constants {
String CRYPTOMATOR_FILENAME_GLOB = "*.cryptomator";
URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME);
byte[] PEPPER = new byte[0];
// Separator used to concatenate Hub username and device name in the filesystem owner identifier.
String HUB_USER_DEVICE_SEPARATOR = "&";
}

View File

@@ -1,160 +0,0 @@
package org.cryptomator.common;
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 org.cryptomator.event.VaultEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.beans.InvalidationListener;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
/**
* Map containing {@link VaultEvent}s.
* The map is keyed by the ciphertext path of the affected resource _and_ the {@link FilesystemEvent}s class in order to group same events
* <p>
* Use {@link EventMap#put(VaultEvent)} to add an element and {@link EventMap#remove(VaultEvent)} to remove it.
* <p>
* The map is size restricted to {@value MAX_SIZE} elements. If a _new_ element (i.e. not already present) is added, the least recently added is removed.
*/
@Singleton
public class EventMap implements ObservableMap<EventMap.EventKey, VaultEvent> {
private static final int MAX_SIZE = 300;
public record EventKey(Path ciphertextPath, Class<? extends FilesystemEvent> c) {}
private final ObservableMap<EventMap.EventKey, VaultEvent> delegate;
@Inject
public EventMap() {
delegate = FXCollections.observableHashMap();
}
@Override
public void addListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
delegate.addListener(mapChangeListener);
}
@Override
public void removeListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
delegate.removeListener(mapChangeListener);
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public VaultEvent get(Object key) {
return delegate.get(key);
}
@Override
public @Nullable VaultEvent put(EventKey key, VaultEvent value) {
return delegate.put(key, value);
}
@Override
public VaultEvent remove(Object key) {
return delegate.remove(key);
}
@Override
public void putAll(@NotNull Map<? extends EventKey, ? extends VaultEvent> m) {
delegate.putAll(m);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public @NotNull Set<EventKey> keySet() {
return delegate.keySet();
}
@Override
public @NotNull Collection<VaultEvent> values() {
return delegate.values();
}
@Override
public @NotNull Set<Entry<EventKey, VaultEvent>> entrySet() {
return delegate.entrySet();
}
@Override
public void addListener(InvalidationListener invalidationListener) {
delegate.addListener(invalidationListener);
}
@Override
public void removeListener(InvalidationListener invalidationListener) {
delegate.removeListener(invalidationListener);
}
public synchronized void put(VaultEvent e) {
//compute key
var key = computeKey(e.actualEvent());
//if-else
var nullOrEntry = delegate.get(key);
if (nullOrEntry == null) {
if (size() == MAX_SIZE) {
delegate.entrySet().stream() //
.min(Comparator.comparing(entry -> entry.getValue().actualEvent().getTimestamp())) //
.ifPresent(oldestEntry -> delegate.remove(oldestEntry.getKey()));
}
delegate.put(key, e);
} else {
delegate.put(key, nullOrEntry.incrementCount(e.actualEvent()));
}
}
public synchronized VaultEvent remove(VaultEvent similar) {
//compute key
var key = computeKey(similar.actualEvent());
return this.remove(key);
}
private EventKey computeKey(FilesystemEvent e) {
var p = switch (e) {
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 EventKey(p, e.getClass());
}
}

View File

@@ -0,0 +1,18 @@
package org.cryptomator.common;
import java.util.function.Supplier;
/**
* Interface marking a class to be used in {@link org.cryptomator.cryptofs.CryptoFileSystemProperties.Builder#withOwnerGetter(Supplier)}.
*/
@FunctionalInterface
public interface FilesystemOwnerSupplier {
/**
* Get the filesystem owner.
*
* @return the filesystem owner
*/
String getOwner();
}

View File

@@ -167,6 +167,7 @@ public class Mounter {
usedMountServices.add(mountService);
var builder = mountService.forFileSystem(cryptoFsRoot);
LOG.debug("Using mount service {} for mounting vault {}", mountService.getClass().getName(), vaultSettings.displayName);
var internal = new SettledMounter(mountService, builder, vaultSettings); // FIXME: no need for an inner class
var cleanup = internal.prepare();
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);

View File

@@ -61,6 +61,7 @@ public final class MasterkeyService {
Optional<Path> c9rFile = paths //
.filter(p -> p.toString().endsWith(".c9r")) //
.filter(p -> !p.endsWith("dir.c9r")) //
.filter(Files::isRegularFile) //
.findFirst();
if (c9rFile.isEmpty()) {
LOG.info("Unable to detect Crypto scheme: No *.c9r file found in {}", vaultPath);

View File

@@ -153,11 +153,6 @@ public class Settings {
@SuppressWarnings("deprecation")
private void migrateLegacySettings(SettingsJson json) {
// migrate renamed keychainAccess
if(this.keychainProvider.getValueSafe().equals("org.cryptomator.linux.keychain.SecretServiceKeychainAccess")) {
this.keychainProvider.setValue("org.cryptomator.linux.keychain.GnomeKeyringKeychainAccess");
}
// implicit migration of 1.6.x legacy setting "preferredVolumeImpl":
if (this.mountService.get() == null && json.preferredVolumeImpl != null) {
this.mountService.set(switch (json.preferredVolumeImpl) {

View File

@@ -10,14 +10,6 @@ public enum UiTheme {
DARK("preferences.interface.theme.dark"), //
AUTOMATIC("preferences.interface.theme.automatic");
public static UiTheme[] applicableValues() {
if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) {
return values();
} else {
return new UiTheme[]{LIGHT, DARK};
}
}
private final String displayName;
UiTheme(String displayName) {

View File

@@ -10,7 +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.FilesystemOwnerSupplier;
import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
@@ -23,6 +23,8 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.event.NotificationManager;
import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException;
@@ -78,6 +80,7 @@ public class Vault {
private final Mounter mounter;
private final Settings settings;
private final FileSystemEventAggregator fileSystemEventAggregator;
private final NotificationManager notificationManager;
private final BooleanProperty showingStats;
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@@ -90,7 +93,8 @@ public class Vault {
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
VaultStats stats, //
Mounter mounter, Settings settings, //
FileSystemEventAggregator fileSystemEventAggregator) {
FileSystemEventAggregator fileSystemEventAggregator, //
NotificationManager notificationManager) {
this.vaultSettings = vaultSettings;
this.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem;
@@ -109,6 +113,7 @@ public class Vault {
this.mounter = mounter;
this.settings = settings;
this.fileSystemEventAggregator = fileSystemEventAggregator;
this.notificationManager = notificationManager;
this.showingStats = new SimpleBooleanProperty(false);
this.quickAccessEntry = new AtomicReference<>(null);
}
@@ -145,14 +150,17 @@ public class Vault {
LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength.get());
}
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
var fsPropsBuilder = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withKeyLoader(keyLoader) //
.withFlags(flags) //
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
.withFilesystemEventConsumer(this::consumeVaultEvent) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
.withFilesystemEventConsumer(this::consumeVaultEvent);
if (keyLoader instanceof FilesystemOwnerSupplier oo) {
fsPropsBuilder.withOwnerGetter(oo::getOwner);
}
return CryptoFileSystemProvider.newFileSystem(getPath(), fsPropsBuilder.build());
}
private void destroyCryptoFileSystem() {
@@ -262,6 +270,7 @@ public class Vault {
private void consumeVaultEvent(FilesystemEvent e) {
fileSystemEventAggregator.put(this, e);
notificationManager.offer(this, e);
}
// ******************************************************************************

View File

@@ -6,6 +6,7 @@ import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.cryptofs.event.FileIsInUseEvent;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import javax.inject.Inject;
@@ -101,6 +102,7 @@ public class FileSystemEventAggregator {
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
case FileIsInUseEvent(_, _, Path ciphertext, _, _, _) -> ciphertext;
};
return new FSEventBucket(v, p, event.getClass());
}

View File

@@ -0,0 +1,85 @@
package org.cryptomator.event;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.FileIsInUseEvent;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Manager for notifications.
* <p>
* To add (filesystem) events, use method {@link #offer(Vault, FilesystemEvent)}. If the input event is eligible, it is added to an internal queue.
* An event is eligible, if
* <ul>
* <li>the event should trigger a notification and</li>
* <li>it is not added within the last {@value DEBOUNCE_THRESHOLD_SECONDS} seconds</li>
* </ul>
*
* @see org.cryptomator.ui.fxapp.FxNotificationManager
*/
@Singleton
public class NotificationManager {
private static final int DEBOUNCE_THRESHOLD_SECONDS = 5;
private final Cache<FSEventBucket, FilesystemEvent> debounceCache;
private final ConcurrentLinkedQueue<VaultEvent> pendingEvents;
@Inject
public NotificationManager() {
debounceCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(DEBOUNCE_THRESHOLD_SECONDS)).build();
pendingEvents = new ConcurrentLinkedQueue<>();
}
/**
* Offers the given filesystem event to the notification manager.
*
* @param v The vault where the filesystem event happened
* @param e the actual filesystem event
* @return {@code true} if the filesystem event is accepted, otherwise {@code false}.
*/
public boolean offer(Vault v, FilesystemEvent e) {
return switch (e) {
case FileIsInUseEvent fiiue -> addEvent(v, fiiue.ciphertextPath(), fiiue);
default -> false;
};
}
boolean addEvent(Vault v, Path keyPath, FilesystemEvent e) {
var key = new FSEventBucket(v, keyPath, e.getClass());
var isAdded = new AtomicBoolean(false);
debounceCache.asMap().computeIfAbsent(key, _ -> {
synchronized (this) {
pendingEvents.add(new VaultEvent(v, e));
isAdded.set(true);
}
return e;
});
return isAdded.get();
}
/**
* Adds all events to the target list and clears afterward the pending-event-queue
*
* @param target list where the filesystem events are copied to
* @return {@code true}, if elements were copied
*/
public boolean appendToAndClear(List<VaultEvent> target) {
//it is not clear, if addAll iterates thread-safe over the pendingEvents
//hence we synchronize moving (copy then clear) and adding-single-element operations
synchronized (this) {
var result = target.addAll(pendingEvents);
pendingEvents.clear();
return result;
}
}
}

View File

@@ -3,25 +3,6 @@ package org.cryptomator.event;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import java.time.Instant;
public record VaultEvent(Vault v, FilesystemEvent actualEvent) {
public record VaultEvent(Vault v, FilesystemEvent actualEvent, int count) implements Comparable<VaultEvent> {
public VaultEvent(Vault v, FilesystemEvent actualEvent) {
this(v, actualEvent, 1);
}
@Override
public int compareTo(VaultEvent other) {
var timeResult = actualEvent.getTimestamp().compareTo(other.actualEvent().getTimestamp());
if(timeResult != 0) {
return timeResult;
} else {
return this.equals(other) ? 0 : this.actualEvent.getClass().getName().compareTo(other.actualEvent.getClass().getName());
}
}
public VaultEvent incrementCount(FilesystemEvent update) {
return new VaultEvent(v, update, count+1);
}
}

View File

@@ -0,0 +1,97 @@
package org.cryptomator.launcher;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.Reader;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Properties;
import java.util.Set;
/**
* Factory to generate admin properties.
*
* <p>
* Admin properties are {@link Properties} using system properties as defaults, but allow overwriting a specific set of properties with an external config file.
* Those properties are created by calling {@link #create()}. The method first reads system property {@value #ADMIN_PROP_FILE_KEY}. If it contains a path to a valid properties file, all overridable properties from the file are loaded into the returned admin properties.
* <p>
* The overridable properties are:
* <ul>
* <li>cryptomator.logDir</li>
* <li>cryptomator.pluginDir</li>
* <li>cryptomator.p12Path</li>
* <li>cryptomator.mountPointsDir</li>
* <li>cryptomator.disableUpdateCheck</li>
* </ul>
*
* @see Properties
* @see System#getProperties()
*/
class AdminPropertiesFactory {
private static final Logger LOG = EventualLogger.INSTANCE;
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
private static final String ADMIN_PROP_FILE_KEY = "cryptomator.adminConfigPath";
private static final Set<String> ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
"cryptomator.p12Path", //
"cryptomator.mountPointsDir", //
"cryptomator.disableUpdateCheck");
/**
* Creates new {@link Properties} containing overridable properties from the admin config.
* <p>
* The returned properties object uses as default the {@link System} properties.
* For a list of overridable properties, see {@link AdminPropertiesFactory}
*
* @return {@link Properties} containing overridable properties from the admin config and defaulting to system properties.
*/
static Properties create() {
var systemProps = System.getProperties();
var adminProps = new Properties(systemProps);
final String adminCfgPath = System.getProperty(ADMIN_PROP_FILE_KEY);
if (adminCfgPath == null) {
LOG.debug("Admin config property is not defined. Skipping.");
return adminProps;
}
var propsFromFile = loadPropertiesFromFile(Path.of(adminCfgPath));
for (var key : propsFromFile.stringPropertyNames()) {
if (ALLOWED_OVERRIDES.contains(key)) {
var value = propsFromFile.getProperty(key);
LOG.info("Overwriting {} with value {} from admin config.", key, value);
adminProps.setProperty(key, value);
} else {
LOG.debug("Property {} in admin config is not supported for override.", key);
}
}
return adminProps;
}
//visible for testing
static Properties loadPropertiesFromFile(Path adminPropertiesPath) {
var adminProps = new Properties();
try (FileChannel ch = FileChannel.open(adminPropertiesPath, StandardOpenOption.READ); //
Reader reader = Channels.newReader(ch, StandardCharsets.UTF_8)) {
if (ch.size() > MAX_CONFIG_SIZE_BYTES) {
throw new IOException("Config file %s exceeds maximum size of %d".formatted(adminPropertiesPath, MAX_CONFIG_SIZE_BYTES));
}
adminProps.load(reader);
} catch (NoSuchFileException _) {
//NO-OP
LOG.debug("No admin properties found at {}.", adminPropertiesPath);
} catch (IOException | IllegalArgumentException e) {
LOG.warn("Failed to read administrative properties from {}. Returning empty properties.", adminPropertiesPath, e);
}
return adminProps;
}
}

View File

@@ -11,9 +11,9 @@ import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.common.SubstitutingProperties;
import org.cryptomator.networking.SSLContextProvider;
import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.networking.SSLContextProvider;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,7 +35,8 @@ public class Cryptomator {
private static final long STARTUP_TIME = System.currentTimeMillis();
static {
var lazyProcessedProps = new SubstitutingProperties(System.getProperties(), System.getenv());
var adminProps = AdminPropertiesFactory.create();
var lazyProcessedProps = new SubstitutingProperties(adminProps, System.getenv());
System.setProperties(lazyProcessedProps);
CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
LOG = LoggerFactory.getLogger(Cryptomator.class);
@@ -64,6 +65,7 @@ public class Cryptomator {
}
public static void main(String[] args) {
EventualLogger.INSTANCE.drainTo(LOG);
var printVersion = Optional.ofNullable(args) //
.stream() //Streams either one element (the args-array) or zero elements
.flatMap(Arrays::stream) //

View File

@@ -4,7 +4,6 @@ import dagger.Module;
import dagger.Provides;
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import javax.inject.Named;
@@ -30,11 +29,6 @@ class CryptomatorModule {
return new ArrayBlockingQueue<>(10);
}
@Provides
@Singleton
static Optional<UiAppearanceProvider> provideAppearanceProvider() {
return UiAppearanceProvider.get();
}
@Provides
@Singleton

View File

@@ -0,0 +1,106 @@
package org.cryptomator.launcher;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.event.DefaultLoggingEvent;
import org.slf4j.event.Level;
import org.slf4j.event.LoggingEvent;
import org.slf4j.helpers.AbstractLogger;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
class EventualLogger extends AbstractLogger {
static final EventualLogger INSTANCE = new EventualLogger();
private final Queue<LoggingEvent> bufferedEvents = new ArrayDeque<>();
private EventualLogger() {
}
synchronized void drainTo(Logger gutter) {
for (var event : bufferedEvents) {
var builder = gutter.atLevel(event.getLevel()) //
.setCause(event.getThrowable()) //
.setMessage(event.getMessage());
Objects.requireNonNullElse(event.getArguments(), List.of()).forEach(builder::addArgument);
Objects.requireNonNullElse(event.getMarkers(), List.<Marker>of()).forEach(builder::addMarker);
builder.log();
}
bufferedEvents.clear();
}
@Override
protected synchronized void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) {
var event = new DefaultLoggingEvent(level, this);
if (marker != null) {
event.addMarker(marker);
}
event.setMessage(messagePattern);
for (var arg : Objects.requireNonNullElse(arguments, new Object[]{})) {
event.addArgument(arg);
}
event.setThrowable(throwable);
bufferedEvents.add(event);
}
//Unclear, unused and undocumented method of slf4j, see also https://github.com/qos-ch/slf4j/discussions/348
@Override
protected String getFullyQualifiedCallerName() {
return getClass().getCanonicalName();
}
@Override
public boolean isTraceEnabled() {
return true;
}
@Override
public boolean isTraceEnabled(Marker marker) {
return true;
}
@Override
public boolean isDebugEnabled() {
return true;
}
@Override
public boolean isDebugEnabled(Marker marker) {
return true;
}
@Override
public boolean isInfoEnabled() {
return true;
}
@Override
public boolean isInfoEnabled(Marker marker) {
return true;
}
@Override
public boolean isWarnEnabled() {
return true;
}
@Override
public boolean isWarnEnabled(Marker marker) {
return true;
}
@Override
public boolean isErrorEnabled() {
return true;
}
@Override
public boolean isErrorEnabled(Marker marker) {
return true;
}
}

View File

@@ -59,7 +59,7 @@ public class ChooseExistingVaultController implements FxController {
this.vault = vault;
this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle;
this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot);
this.screenshot = applicationStyle.appliedAppThemeProperty().map(this::selectScreenshot);
}
private Image selectScreenshot(Theme theme) {

View File

@@ -38,6 +38,7 @@ public enum FxmlFile {
MIGRATION_RUN("/fxml/migration_run.fxml"), //
MIGRATION_START("/fxml/migration_start.fxml"), //
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
NOTIFICATION("/fxml/notification.fxml"), //
PREFERENCES("/fxml/preferences.fxml"), //
QUIT("/fxml/quit.fxml"), //
QUIT_FORCED("/fxml/quit_forced.fxml"), //
@@ -59,6 +60,7 @@ public enum FxmlFile {
VAULT_STATISTICS("/fxml/stats.fxml"), //
WRONGFILEALERT("/fxml/wrongfilealert.fxml");
private final String ressourcePathString;
FxmlFile(String ressourcePathString) {

View File

@@ -0,0 +1,56 @@
package org.cryptomator.ui.common;
import javafx.stage.Screen;
/**
* Utility class providing methods regarding the OS bar.
*/
public class SystemBarUtil {
public enum Placement {
/**
* OS Bar placed at the left screen edge
*/
LEFT,
/**
* OS Bar placed at the top screen edge
*/
TOP,
/**
* OS Bar placed at the right screen edge
*/
RIGHT,
/**
* OS Bar placed at the bottom screen edge
*/
BOTTOM;
}
/**
* Determines the placement of the OS bar on the given screen.
* <p>
* <b>Assuming the OS bar fills one screen edge completely</b>,
* this method determines that screen edge by comparing the actual screen bounds with the visual ones.
* <p>
* If the screen does not have a system bar, the bottom placement is returned.
* If the screen does have multiple system bars, the first in following priority is returned:
* LEFT, TOP, RIGHT, BOTTOM.
*
* @param screen a {@link Screen} where an OS bar exists
* @return {@link Placement} indicating the screen edge.
*/
public static Placement getPlacementOfSystembar(Screen screen) {
var bounds = screen.getBounds();
var vBounds = screen.getVisualBounds();
//assumption: the system bar fills a whole screen side
if (bounds.getMinX() != vBounds.getMinX()) {
return Placement.LEFT;
} else if (bounds.getMinY() != vBounds.getMinY()) {
return Placement.TOP;
} else if (bounds.getMaxX() != vBounds.getMaxX()) {
return Placement.RIGHT;
} else {
return Placement.BOTTOM;
}
}
}

View File

@@ -12,6 +12,8 @@ public enum FontAwesome5Icon {
CARET_DOWN("\uF0D7"), //
CARET_RIGHT("\uF0Da"), //
CHECK("\uF00C"), //
CHEVRON_LEFT("\uF053"), //
CHEVRON_RIGHT("\uF054"), //
CLOCK("\uF017"), //
CLIPBOARD("\uF328"), //
COG("\uF013"), //
@@ -60,6 +62,7 @@ public enum FontAwesome5Icon {
TRASH("\uF1F8"), //
UNLINK("\uf127"), //
USER_COG("\uf4fe"), //
USER_LOCK("\uf502"), //
WRENCH("\uF0AD"), //
WINDOW_MINIMIZE("\uF2D1"), //
;

View File

@@ -4,24 +4,27 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.AccessibleRole;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.ResourceBundle;
public class NotificationBar extends HBox {
public class InfoBar extends HBox {
@FXML
private Label notificationLabel;
private Label infoMessage;
private final BooleanProperty dismissable = new SimpleBooleanProperty();
private final BooleanProperty notify = new SimpleBooleanProperty();
public NotificationBar() {
public InfoBar() {
setAlignment(Pos.CENTER);
setStyle("-fx-alignment: center;");
getStyleClass().addAll("info-bar");
Region spacer = new Region();
spacer.setMinWidth(40);
@@ -36,14 +39,21 @@ public class NotificationBar extends HBox {
vbox.setAlignment(Pos.CENTER);
HBox.setHgrow(vbox, javafx.scene.layout.Priority.ALWAYS);
notificationLabel = new Label();
notificationLabel.getStyleClass().add("notification-label");
notificationLabel.setStyle("-fx-alignment: center;");
vbox.getChildren().add(notificationLabel);
infoMessage = new Label();
infoMessage.setFocusTraversable(true);
infoMessage.setAccessibleRole(AccessibleRole.BUTTON);
vbox.getChildren().add(infoMessage);
Button closeButton = new Button("X");
var closeGraphic = new FontAwesome5IconView();
closeGraphic.setGlyph(FontAwesome5Icon.TIMES);
closeGraphic.setGlyphSize(12);
closeGraphic.getStyleClass().add("glyph");
Button closeButton = new Button();
closeButton.setGraphic(closeGraphic);
closeButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
closeButton.setAccessibleText(ResourceBundle.getBundle("i18n.strings").getString("main.notification.closeButton.tooltip"));
closeButton.setMinWidth(40);
closeButton.setStyle("-fx-background-color: transparent; -fx-text-fill: white; -fx-font-weight: bold;");
closeButton.visibleProperty().bind(dismissable);
closeButton.setOnAction(_ -> {
@@ -61,11 +71,11 @@ public class NotificationBar extends HBox {
}
public String getText() {
return notificationLabel.getText();
return infoMessage.getText();
}
public void setText(String text) {
notificationLabel.setText(text);
infoMessage.setText(text);
}
public void setStyleClass(String styleClass) {

View File

@@ -61,6 +61,15 @@ public class Dialogs {
.setOkButtonKey(BUTTON_KEY_CLOSE);
}
public SimpleDialog.Builder prepareHubVaultArchived(Stage window, Vault vault) {
return createDialogBuilder().setOwner(window) //
.setTitleKey("unlock.title", vault.getDisplayName()) //
.setMessageKey("hub.archived.message") //
.setDescriptionKey("hub.archived.description") //
.setIcon(FontAwesome5Icon.BAN)//
.setOkButtonKey(BUTTON_KEY_CLOSE);
}
public SimpleDialog.Builder prepareRecoveryVaultAdded(Stage window, String displayName) {
return createDialogBuilder().setOwner(window) //
.setTitleKey("recover.existing.title") //

View File

@@ -1,5 +1,8 @@
package org.cryptomator.ui.eventview;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
import org.cryptomator.cryptofs.event.FileIsInUseEvent;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
@@ -115,7 +118,7 @@ public class EventListCellController implements FxController {
eventActionsMenu.hide();
eventActionsMenu.getItems().clear();
eventTooltip.setText(item.getKey().vault().getDisplayName());
addAction("generic.action.dismiss", () -> {
addLocalizedAction("generic.action.dismiss", () -> {
fileSystemEventAggregator.remove(item.getKey());
});
switch (item.getValue().mostRecentEvent()) {
@@ -124,20 +127,41 @@ public class EventListCellController implements FxController {
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse);
case BrokenFileNodeEvent fse -> this.adjustToBrokenFileNodeEvent(fse);
case FileIsInUseEvent fse -> this.adjustToFileInUseEvent(fse);
}
}
private void adjustToFileInUseEvent(FileIsInUseEvent fiiue) {
eventIcon.setValue(FontAwesome5Icon.USER_LOCK);
eventMessage.setValue(resourceBundle.getString("eventView.entry.inUse.message"));
var indexFileName = fiiue.cleartextPath().lastIndexOf("/");
eventDescription.setValue(fiiue.cleartextPath().substring(indexFileName + 1));
if (revealService != null) {
addLocalizedAction("eventView.entry.inUse.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(fiiue.cleartextPath())));
addLocalizedAction("eventView.entry.inUse.showEncrypted", () -> reveal(revealService, fiiue.ciphertextPath()));
} else {
addLocalizedAction("eventView.entry.inUse.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(fiiue.cleartextPath()).toString()));
addLocalizedAction("eventView.entry.inUse.copyEncrypted", () -> copyToClipboard(fiiue.ciphertextPath().toString()));
}
var userAndDevice = fiiue.owner().split(Constants.HUB_USER_DEVICE_SEPARATOR);
var user = userAndDevice[0];
var device = userAndDevice.length == 1 ? userAndDevice[0] : userAndDevice[1];
addLocalizedAction("eventView.entry.inUse.copyUserAndDevice", () -> copyToClipboard(user + ", " + device));
addLocalizedAction("eventView.entry.inUse.ignoreLock", fiiue.ignoreMethod());
}
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, bfe.ciphertextPath()));
addLocalizedAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, bfe.ciphertextPath()));
} else {
addAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(bfe.ciphertextPath().toString()));
addLocalizedAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(bfe.ciphertextPath().toString()));
}
addAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
addLocalizedAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
}
private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
@@ -145,9 +169,9 @@ public class EventListCellController implements FxController {
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())));
addLocalizedAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
} else {
addAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
addLocalizedAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
}
}
@@ -156,11 +180,11 @@ public class EventListCellController implements FxController {
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()));
addLocalizedAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
addLocalizedAction("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()));
addLocalizedAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
addLocalizedAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString()));
}
}
@@ -169,9 +193,9 @@ public class EventListCellController implements FxController {
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()));
addLocalizedAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
} else {
addAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
addLocalizedAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
}
}
@@ -180,14 +204,19 @@ public class EventListCellController implements FxController {
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()));
addLocalizedAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
} else {
addAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
addLocalizedAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
}
}
private void addAction(String localizationKey, Runnable action) {
var entry = new MenuItem(resourceBundle.getString(localizationKey));
private void addLocalizedAction(String localizationKey, Runnable action) {
var entryText = resourceBundle.getString(localizationKey);
addAction(entryText, action);
}
private void addAction(String entryText, Runnable action) {
var entry = new MenuItem(entryText);
entry.getStyleClass().addLast("dropdown-button-context-menu-item");
entry.setOnAction(_ -> action.run());
eventActionsMenu.getItems().addLast(entry);
@@ -234,18 +263,17 @@ public class EventListCellController implements FxController {
}
}
private Path convertVaultPathToSystemPath(Path p) {
if (!(p instanceof CryptoPath)) {
throw new IllegalArgumentException("Path " + p + " is not a vault path");
}
private Path convertVaultPathToSystemPath(String vaultInternalPath) {
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));
var mountPoint = v.getMountPoint().uri().getPath();
if(SystemUtils.IS_OS_WINDOWS) {
mountPoint = mountPoint.substring(1); //strip away any leading "/", otherwise there are errors
}
return Path.of(mountPoint, vaultInternalPath.substring(1)); //vaultPaths are always absolute
}
private void reveal(RevealPathService s, Path p) {

View File

@@ -30,9 +30,20 @@ public class FxApplication {
private final FxApplicationTerminator applicationTerminator;
private final AutoUnlocker autoUnlocker;
private final FxFSEventList fxFSEventList;
private final FxNotificationManager notificationManager;
@Inject
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker, FxFSEventList fxFSEventList) {
FxApplication(@Named("startupTime") long startupTime, //
Environment environment, //
Settings settings, //
AppLaunchEventHandler launchEventHandler, //
Lazy<TrayMenuComponent> trayMenu, //
FxApplicationWindows appWindows, //
FxApplicationStyle applicationStyle, //
FxApplicationTerminator applicationTerminator, //
AutoUnlocker autoUnlocker, //
FxFSEventList fxFSEventList, //
FxNotificationManager notificationManager) {
this.startupTime = startupTime;
this.environment = environment;
this.settings = settings;
@@ -43,6 +54,7 @@ public class FxApplication {
this.applicationTerminator = applicationTerminator;
this.autoUnlocker = autoUnlocker;
this.fxFSEventList = fxFSEventList;
this.notificationManager = notificationManager;
}
public void start() {
@@ -88,6 +100,7 @@ public class FxApplication {
launchEventHandler.startHandlingLaunchEvents();
fxFSEventList.schedulePollForUpdates();
notificationManager.schedulePollForUpdates();
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
}

View File

@@ -7,12 +7,14 @@ package org.cryptomator.ui.fxapp;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.decryptname.DecryptNameComponent;
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;
import org.cryptomator.ui.notification.NotificationComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
@@ -25,6 +27,7 @@ import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javafx.scene.image.Image;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
@Module(subcomponents = {TrayMenuComponent.class, //
DecryptNameComponent.class, //
@@ -39,7 +42,8 @@ import java.io.InputStream;
UpdateReminderComponent.class, //
ShareVaultComponent.class, //
EventViewComponent.class, //
RecoveryKeyComponent.class})
RecoveryKeyComponent.class, //
NotificationComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {
@@ -48,6 +52,12 @@ abstract class FxApplicationModule {
}
}
@Provides
@FxApplicationScoped
static Optional<UiAppearanceProvider> provideAppearanceProvider() {
return UiAppearanceProvider.get();
}
@Provides
@FxApplicationScoped
static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) {
@@ -78,4 +88,10 @@ abstract class FxApplicationModule {
return factory.create();
}
@Provides
@FxApplicationScoped
static NotificationComponent provideNotificationComponent(NotificationComponent.Factory factory) {
return factory.create();
}
}

View File

@@ -36,82 +36,91 @@ public class FxApplicationStyle {
}
public void initialize() {
var uiTheme = settings.theme.get();
if (uiTheme == UiTheme.AUTOMATIC) {
registerOsThemeListener();
}
applyTheme(uiTheme);
settings.theme.addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme.get());
}
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, UiTheme oldValue, UiTheme newValue) {
if (oldValue == newValue) {
// no-op
} else if (newValue == UiTheme.AUTOMATIC) {
registerOsThemeListener();
} else if (oldValue == UiTheme.AUTOMATIC) {
removeOsThemeListener();
}
applyTheme(newValue);
}
private void removeOsThemeListener() {
if (appearanceProvider.isPresent()) {
try {
appearanceProvider.get().removeListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.error("Failed to disable automatic theme switching.");
}
}
loadSelectedStyleSheet(newValue);
}
private void loadSelectedStyleSheet(UiTheme desiredTheme) {
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
switch (theme) {
case LIGHT -> applyLightTheme();
case DARK -> applyDarkTheme();
case AUTOMATIC -> {
appearanceProvider.ifPresent(provider -> {
try {
provider.addListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.error("Failed to enable automatic theme switching.");
}
});
applySystemTheme();
LOG.warn("Failed to disable automatic theme switching.", e);
}
} else {
LOG.debug("Unable to remove listener os theme changes: No supported UiAppearanceProvider present");
}
}
private void systemInterfaceThemeChanged(Theme theme) {
switch (theme) {
case LIGHT -> applyLightTheme();
case DARK -> applyDarkTheme();
}
}
private void applySystemTheme() {
private void registerOsThemeListener() {
if (appearanceProvider.isPresent()) {
systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme());
try {
appearanceProvider.get().addListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.warn("Failed to enable automatic theme switching.", e);
}
} else {
LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme...");
applyLightTheme();
LOG.warn("Unable to register for os theme changes: No supported UiAppearanceProvider present");
}
}
private void applyLightTheme() {
var stylesheet = Optional //
.ofNullable(getClass().getResource("/css/light_theme.bss")) //
.orElse(getClass().getResource("/css/light_theme.css"));
private void applyTheme(UiTheme uiTheme) {
if (!licenseHolder.isValidLicense()) {
loadAndApplyLightTheme();
} else {
switch (uiTheme) {
case AUTOMATIC -> {
var osTheme = appearanceProvider.map(UiAppearanceProvider::getSystemTheme).orElse(Theme.LIGHT);
systemInterfaceThemeChanged(osTheme);
}
case LIGHT -> loadAndApplyLightTheme();
case DARK -> loadAndApplyDarkTheme();
}
}
}
private void systemInterfaceThemeChanged(Theme osTheme) {
switch (osTheme) {
case LIGHT -> loadAndApplyLightTheme();
case DARK -> loadAndApplyDarkTheme();
}
}
private void loadAndApplyLightTheme() {
loadAndApplyTheme(Theme.LIGHT, "/css/light_theme.css");
}
private void loadAndApplyDarkTheme() {
loadAndApplyTheme(Theme.DARK, "/css/dark_theme.css");
}
private void loadAndApplyTheme(Theme appTheme, String cssFile) {
var stylesheet = getClass().getResource(cssFile);
if (stylesheet == null) {
LOG.warn("Failed to load light_theme stylesheet");
} else {
Application.setUserAgentStylesheet(stylesheet.toString());
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
appliedTheme.set(Theme.LIGHT);
throw new IllegalStateException("Cannot find resource %s".formatted(cssFile));
}
Application.setUserAgentStylesheet(stylesheet.toString());
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(appTheme));
appliedTheme.set(appTheme);
}
private void applyDarkTheme() {
var stylesheet = Optional //
.ofNullable(getClass().getResource("/css/dark_theme.bss")) //
.orElse(getClass().getResource("/css/dark_theme.css"));
if (stylesheet == null) {
LOG.warn("Failed to load dark_theme stylesheet");
} else {
Application.setUserAgentStylesheet(stylesheet.toString());
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
appliedTheme.set(Theme.DARK);
}
}
public ObjectProperty<Theme> appliedThemeProperty() {
public ObjectProperty<Theme> appliedAppThemeProperty() {
return appliedTheme;
}
}

View File

@@ -11,6 +11,7 @@ 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.notification.NotificationComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
@@ -54,6 +55,7 @@ public class FxApplicationWindows {
private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory;
private final Lazy<EventViewComponent> eventViewWindow;
private final Lazy<NotificationComponent> notificationWindow;
private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow;
@@ -73,6 +75,7 @@ public class FxApplicationWindows {
VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, //
Lazy<EventViewComponent> eventViewWindow, //
Lazy<NotificationComponent> notificationWindow,
ExecutorService executor, //
Dialogs dialogs) {
this.primaryStage = primaryStage;
@@ -85,6 +88,7 @@ public class FxApplicationWindows {
this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory;
this.eventViewWindow = eventViewWindow;
this.notificationWindow = notificationWindow;
this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow;
@@ -193,6 +197,10 @@ public class FxApplicationWindows {
return CompletableFuture.supplyAsync(() -> eventViewWindow.get().showEventViewerWindow(), Platform::runLater).whenComplete(this::reportErrors);
}
public CompletionStage<Stage> showNotification() {
return CompletableFuture.supplyAsync(() -> notificationWindow.get().showNotification(), Platform::runLater).whenComplete(this::reportErrors);
}
/**
* Displays the generic error scene in the given window.
*

View File

@@ -3,6 +3,8 @@ package org.cryptomator.ui.fxapp;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
@@ -11,6 +13,7 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -23,6 +26,8 @@ import java.util.concurrent.TimeUnit;
@FxApplicationScoped
public class FxFSEventList {
private static final Logger LOG = LoggerFactory.getLogger(FxFSEventList.class);
private final ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> events;
private final FileSystemEventAggregator eventAggregator;
private final ScheduledExecutorService scheduler;
@@ -37,7 +42,13 @@ public class FxFSEventList {
}
public void schedulePollForUpdates() {
scheduler.schedule(this::checkForEventUpdates, 1000, TimeUnit.MILLISECONDS);
try {
scheduler.schedule(this::checkForEventUpdates, 1000, TimeUnit.MILLISECONDS);
} catch ( RejectedExecutionException e) {
if(!scheduler.isShutdown()) {
LOG.warn("Failed to poll for filesystem events", e);
}
}
}
/**

View File

@@ -0,0 +1,57 @@
package org.cryptomator.ui.fxapp;
import org.cryptomator.event.NotificationManager;
import org.cryptomator.event.VaultEvent;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Notification manager inside the UI domain.
* <p>
* Polls the {@link NotificationManager} for pending events every {@value POLL_INTERVAL_SECONDS } seconds and
* triggers the notification window display when events are available.
* Returns an observable list of events requiring a user notification with {@link #getEventsRequiringNotification()}.
*
* @see NotificationManager
*/
@FxApplicationScoped
public class FxNotificationManager {
private static final int POLL_INTERVAL_SECONDS = 1;
private final NotificationManager notificationManager;
private final ScheduledExecutorService scheduler;
private final FxApplicationWindows applicationWindows;
private final ObservableList<VaultEvent> eventsRequiringNotification;
@Inject
public FxNotificationManager(NotificationManager notificationManager, ScheduledExecutorService scheduler, FxApplicationWindows applicationWindows) {
this.notificationManager = notificationManager;
this.scheduler = scheduler;
this.applicationWindows = applicationWindows;
this.eventsRequiringNotification = FXCollections.observableArrayList();
}
public void schedulePollForUpdates() {
scheduler.scheduleAtFixedRate(this::checkForPendingNotifications, 0, POLL_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
private void checkForPendingNotifications() {
Platform.runLater(() -> {
if (notificationManager.appendToAndClear(eventsRequiringNotification)) {
applicationWindows.showNotification();
}
});
}
public ObservableList<VaultEvent> getEventsRequiringNotification() {
return eventsRequiringNotification;
}
}

View File

@@ -0,0 +1,68 @@
package org.cryptomator.ui.fxapp;
import org.cryptomator.integrations.common.DisplayName;
import org.cryptomator.integrations.common.OperatingSystem;
import org.cryptomator.integrations.common.Priority;
import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.ColorScheme;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import java.util.concurrent.ConcurrentHashMap;
@DisplayName("JavaFX Color Scheme switcher")
@OperatingSystem(OperatingSystem.Value.LINUX)
@OperatingSystem(OperatingSystem.Value.WINDOWS)
@Priority(1050)
public class JfxUiAppearanceProvider implements UiAppearanceProvider {
private static final Logger LOG = LoggerFactory.getLogger(JfxUiAppearanceProvider.class);
private final ConcurrentHashMap<UiAppearanceListener, ChangeListener<ColorScheme>> uiAppearanceListeners = new ConcurrentHashMap<>();
private final Platform.Preferences preferences = Platform.getPreferences(); //Note: this service impl MUST be loaded in the fx application thread
@Override
public Theme getSystemTheme() {
return switch (preferences.getColorScheme()) {
case DARK -> Theme.DARK;
case LIGHT -> Theme.LIGHT;
};
}
@Override
public void adjustToTheme(Theme theme) {
//no-op
}
@Override
public void addListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
var fxChangeListener = (ChangeListener<ColorScheme>) (_, _, newScheme) -> {
var newTheme = switch (newScheme) {
case DARK -> Theme.DARK;
case LIGHT -> Theme.LIGHT;
};
uiAppearanceListener.systemAppearanceChanged(newTheme);
};
LOG.debug("Register listener for OS theme changes");
uiAppearanceListeners.computeIfAbsent(uiAppearanceListener, k -> {
Platform.runLater(() -> preferences.colorSchemeProperty().addListener(fxChangeListener));
return fxChangeListener;
});
}
@Override
public void removeListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
var fxChangeListener = uiAppearanceListeners.remove(uiAppearanceListener);
if (fxChangeListener != null) {
LOG.debug("Removing listener for OS theme changes");
Platform.runLater(() -> preferences.colorSchemeProperty().removeListener(fxChangeListener));
}
}
}

View File

@@ -63,8 +63,8 @@ abstract class HealthCheckModule {
@Provides
@HealthCheckWindow
@HealthCheckScoped
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) {
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) {
return compFactory.create(vault, window).keyloadingStrategy();
}
@Provides

View File

@@ -3,11 +3,8 @@ package org.cryptomator.ui.keyloading;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import javafx.stage.Stage;
import java.util.Map;
import java.util.function.Supplier;
@KeyLoadingScoped
@Subcomponent(modules = {KeyLoadingModule.class})
@@ -16,16 +13,10 @@ public interface KeyLoadingComponent {
@KeyLoading
KeyLoadingStrategy keyloadingStrategy();
@Subcomponent.Builder
interface Builder {
@Subcomponent.Factory
interface Factory {
@BindsInstance
Builder vault(@KeyLoading Vault vault);
@BindsInstance
Builder window(@KeyLoading Stage window);
KeyLoadingComponent build();
KeyLoadingComponent create(@BindsInstance @KeyLoading Vault vault, @KeyLoading @BindsInstance Stage window);
}
}

View File

@@ -66,6 +66,13 @@ public abstract class HubKeyLoadingModule {
return new AtomicReference<>();
}
@Provides
@Named("filesystemOwnerId")
@KeyLoadingScoped
static AtomicReference<String> provideFilesystemOwnerIdRef() {
return new AtomicReference<>();
}
@Provides
@KeyLoadingScoped
static CompletableFuture<ReceivedKey> provideResult() {

View File

@@ -2,6 +2,7 @@ package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import dagger.Lazy;
import org.cryptomator.common.FilesystemOwnerSupplier;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
import org.cryptomator.common.settings.DeviceKey;
@@ -23,25 +24,28 @@ import java.net.URI;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoading
public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
public class HubKeyLoadingStrategy implements KeyLoadingStrategy, FilesystemOwnerSupplier {
private static final String SCHEME_PREFIX = "hub+";
public static final String SCHEME_PREFIX = "hub+";
public static final String SCHEME_HUB_HTTP = SCHEME_PREFIX + "http";
public static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
private final Stage window;
private final KeychainManager keychainManager;
private final AtomicReference<String> fsOwnerId;
private final Lazy<Scene> authFlowScene;
private final Lazy<Scene> noKeychainScene;
private final CompletableFuture<ReceivedKey> result;
private final DeviceKey deviceKey;
@Inject
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle, @Named("filesystemOwnerId") AtomicReference<String> fsOwnerId) {
this.window = window;
this.keychainManager = keychainManager;
this.fsOwnerId = fsOwnerId;
window.setTitle(windowTitle);
window.setOnCloseRequest(_ -> result.cancel(true));
this.authFlowScene = authFlowScene;
@@ -90,4 +94,13 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
});
}
@Override
public String getOwner() {
var name = fsOwnerId.get();
if (name == null) {
throw new IllegalStateException("Owner is not yet determined");
}
return name;
}
}

View File

@@ -6,10 +6,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.common.Constants;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.slf4j.Logger;
@@ -41,7 +43,6 @@ import java.util.concurrent.atomic.AtomicReference;
public class ReceiveKeyController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ReceiveKeyController.class);
private static final String SCHEME_PREFIX = "hub+";
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10);
@@ -50,6 +51,7 @@ public class ReceiveKeyController implements FxController {
private final String vaultId;
private final String deviceId;
private final String bearerToken;
private final AtomicReference<String> fsOwnerId;
private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> registerDeviceScene;
private final Lazy<Scene> legacyRegisterDeviceScene;
@@ -57,14 +59,30 @@ public class ReceiveKeyController implements FxController {
private final Lazy<Scene> accountInitializationScene;
private final Lazy<Scene> invalidLicenseScene;
private final HttpClient httpClient;
private final Dialogs dialogs;
private final Vault vault;
@Inject
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy<Scene> accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
public ReceiveKeyController(@KeyLoading Vault vault, //
ExecutorService executor, //
@KeyLoading Stage window, //
HubConfig hubConfig, //
@Named("deviceId") String deviceId, //
@Named("bearerToken") AtomicReference<String> tokenRef, //
@Named("filesystemOwnerId") AtomicReference<String> fsOwnerId, //
CompletableFuture<ReceivedKey> result, //
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, //
@FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, //
@FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, //
@FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy<Scene> accountInitializationScene, //
@FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene, //
Dialogs dialogs) {
this.window = window;
this.hubConfig = hubConfig;
this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs)
this.deviceId = deviceId;
this.bearerToken = Objects.requireNonNull(tokenRef.get());
this.fsOwnerId = fsOwnerId;
this.result = result;
this.registerDeviceScene = registerDeviceScene;
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
@@ -73,6 +91,8 @@ public class ReceiveKeyController implements FxController {
this.invalidLicenseScene = invalidLicenseScene;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
this.dialogs = dialogs;
this.vault = vault;
}
@FXML
@@ -81,7 +101,34 @@ public class ReceiveKeyController implements FxController {
}
public void receiveKey() {
requestApiConfig();
requestUserData();
}
private void requestUserData() {
var userUri = hubConfig.URIs.API.resolve("users/me?withDevices=false");
var request = HttpRequest.newBuilder(userUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
.thenAcceptAsync(this::receivedUserData) //
.exceptionally(this::retrievalFailed);
}
private void receivedUserData(HttpResponse<String> response) {
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
try {
if (response.statusCode() == 200) {
var user = JSON.reader().readValue(response.body(), UserDto.class);
fsOwnerId.set(user.name);
requestApiConfig();
} else {
throw new IllegalStateException("Unexpected response " + response.statusCode());
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
@@ -144,6 +191,7 @@ public class ReceiveKeyController implements FxController {
switch (response.statusCode()) {
case 200 -> {
var device = JSON.reader().readValue(response.body(), DeviceDto.class);
fsOwnerId.accumulateAndGet(device.name, (s1, s2) -> s1 + Constants.HUB_USER_DEVICE_SEPARATOR + s2);
requestVaultMasterkey(device.userPrivateKey);
}
case 404 -> Platform.runLater(this::needsDeviceRegistration);
@@ -184,7 +232,8 @@ public class ReceiveKeyController implements FxController {
switch (response.statusCode()) {
case 200 -> receivedBothEncryptedKeys(response.body(), encryptedUserKey);
case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
case 403 -> accessNotGranted();
case 410 -> accessGoneVaultArchived();
case 449 -> accountInitializationRequired();
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
}
@@ -228,7 +277,8 @@ public class ReceiveKeyController implements FxController {
switch (response.statusCode()) {
case 200 -> receivedLegacyAccessTokenSuccess(response.body());
case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access
case 403 -> accessNotGranted();
case 410 -> accessGoneVaultArchived();
case 404 -> needsLegacyDeviceRegistration();
default -> throw new IOException("Unexpected response " + response.statusCode());
}
@@ -261,6 +311,11 @@ public class ReceiveKeyController implements FxController {
window.setScene(unauthorizedScene.get());
}
private void accessGoneVaultArchived() {
window.close();
dialogs.prepareHubVaultArchived((Stage)window.getOwner(), vault).build().showAndWait();
}
private void accountInitializationRequired() {
window.setScene(accountInitializationScene.get());
}
@@ -289,13 +344,16 @@ public class ReceiveKeyController implements FxController {
}
private static String extractVaultId(URI vaultKeyUri) {
assert vaultKeyUri.getScheme().startsWith(SCHEME_PREFIX);
assert vaultKeyUri.getScheme().startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX);
var path = vaultKeyUri.getPath();
return path.substring(path.lastIndexOf('/') + 1);
}
@JsonIgnoreProperties(ignoreUnknown = true)
private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
private record UserDto(@JsonProperty(value = "name", required = true) String name) {}
@JsonIgnoreProperties(ignoreUnknown = true)
private record DeviceDto(@JsonProperty(value = "name", required = true) String name, @JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
@JsonIgnoreProperties(ignoreUnknown = true)
private record ConfigDto(@JsonProperty(value = "apiLevel") int apiLevel) {}

View File

@@ -16,6 +16,7 @@ import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.layout.HBox;
import java.util.ResourceBundle;
// unscoped because each cell needs its own controller
public class VaultListCellController implements FxController {
@@ -23,9 +24,12 @@ public class VaultListCellController implements FxController {
private static final Insets COMPACT_INSETS = new Insets(6, 12, 6, 12);
private static final Insets DEFAULT_INSETS = new Insets(12);
private final ResourceBundle resourceBundle;
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private final ObservableValue<VaultState.Value> vaultState;
private final ObservableValue<FontAwesome5Icon> glyph;
private final ObservableValue<Boolean> compactMode;
private final ObservableValue<String> accessibleText;
private AutoAnimator spinAnimation;
@@ -35,17 +39,21 @@ public class VaultListCellController implements FxController {
public HBox vaultListCell;
@Inject
VaultListCellController(Settings settings) {
this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState);
VaultListCellController(Settings settings, ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
this.vaultState = vault.flatMap(Vault::stateProperty);
this.glyph = vaultState.map(this::getGlyphForVaultState);
this.accessibleText = vaultState.map(this::getAccessibleTextForVaultState);
this.compactMode = settings.compactMode;
}
public void initialize() {
this.spinAnimation = AutoAnimator.animate(Animations.createDiscrete360Rotation(vaultStateView)) //
.onCondition(vault.flatMap(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals).orElse(false)) //
.onCondition(vaultState.map(VaultState.Value.PROCESSING::equals).orElse(false)) //
.afterStop(() -> vaultStateView.setRotate(0)) //
.build();
this.vaultListCell.paddingProperty().bind(compactMode.map(c -> c ? COMPACT_INSETS : DEFAULT_INSETS));
this.vaultListCell.accessibleTextProperty().bind(accessibleText);
}
// TODO deduplicate w/ VaultDetailController
@@ -62,6 +70,25 @@ public class VaultListCellController implements FxController {
}
}
private String getAccessibleTextForVaultState(VaultState.Value state) {
var v = vault.get();
if (state != null && v != null) {
var translationKey = switch (state) {
case LOCKED -> "vault.state.locked";
case PROCESSING -> "vault.state.processing";
case UNLOCKED -> "vault.state.unlocked";
case NEEDS_MIGRATION -> "vault.state.migrationNeeded";
case MISSING -> "vault.state.missing";
case VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> "vault.state.error";
};
var localizedState = resourceBundle.getString(translationKey);
return resourceBundle.getString("main.vaultlist.listEntry").formatted(v.getDisplayName(), localizedState);
} else {
return "";
}
}
/* Getter/Setter */
public ObservableValue<FontAwesome5Icon> glyphProperty() {

View File

@@ -15,8 +15,8 @@ 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.fxapp.FxFSEventList;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.slf4j.Logger;
@@ -139,8 +139,8 @@ public class VaultListController implements FxController {
vaultList.setItems(vaults);
vaultList.setCellFactory(cellFactory);
vaultList.prefHeightProperty().bind(
vaultList.fixedCellSizeProperty().multiply(Bindings.size(vaultList.getItems()))
vaultList.prefHeightProperty().bind( //
vaultList.fixedCellSizeProperty().multiply(Bindings.size(vaultList.getItems())) //
);
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
@@ -157,11 +157,11 @@ public class VaultListController implements FxController {
//unlock vault on double click
vaultList.addEventFilter(MouseEvent.MOUSE_CLICKED, click -> {
if (click.getClickCount() >= 2) {
Optional.ofNullable(selectedVault.get())
.filter(Vault::isLocked)
Optional.ofNullable(selectedVault.get()) //
.filter(Vault::isLocked) //
.ifPresent(vault -> appWindows.startUnlockWorkflow(vault, mainWindow));
Optional.ofNullable(selectedVault.get())
.filter(Vault::isUnlocked)
Optional.ofNullable(selectedVault.get()) //
.filter(Vault::isUnlocked) //
.ifPresent(vaultService::reveal);
}
});

View File

@@ -0,0 +1,35 @@
package org.cryptomator.ui.notification;
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;
@NotificationScoped
@Subcomponent(modules = {NotificationModule.class})
public interface NotificationComponent {
@NotificationWindow
Stage window();
@FxmlScene(FxmlFile.NOTIFICATION)
Lazy<Scene> scene();
default Stage showNotification() {
var window = window();
window.setScene(scene().get());
window.sizeToScene();
window.show();
window.requestFocus();
return window;
}
@Subcomponent.Factory
interface Factory {
NotificationComponent create();
}
}

View File

@@ -0,0 +1,240 @@
package org.cryptomator.ui.notification;
import org.cryptomator.common.Constants;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.cryptofs.event.FileIsInUseEvent;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxNotificationManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
@NotificationScoped
public class NotificationController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(NotificationController.class);
private static final DateTimeFormatter LOCAL_TIME_FORMATTER_TEMPLATE = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
private static final String BUG_MSG = "If you see this message, please report it on the Cryptomator issue tracker.";
private final Stage window;
private final SimpleListProperty<VaultEvent> events;
private final ResourceBundle resourceBundle;
private final IntegerProperty selectionIndex;
private final ObservableStringValue paging;
private final ObjectProperty<VaultEvent> selectedEvent;
private final ObservableValue<Boolean> singleEvent;
private final StringProperty fileName;
private final StringProperty vaultName;
private final StringProperty eventTimestamp;
private final StringProperty message;
private final StringProperty description;
private final StringProperty actionText;
private final ExecutorService executorService;
private final DateTimeFormatter localizedTimeFormatter;
@Inject
public NotificationController(@NotificationWindow Stage window, FxNotificationManager notificationManager, ExecutorService executorService, ResourceBundle resourceBundle, Settings settings) {
this.window = window;
var preferredLanguage = settings.language.get();
this.localizedTimeFormatter = LOCAL_TIME_FORMATTER_TEMPLATE.withLocale(preferredLanguage == null ? Locale.getDefault() : Locale.forLanguageTag(preferredLanguage));
this.events = new SimpleListProperty<>(notificationManager.getEventsRequiringNotification());
this.resourceBundle = resourceBundle;
this.selectionIndex = new SimpleIntegerProperty(-1);
this.selectedEvent = new SimpleObjectProperty<>();
this.singleEvent = events.sizeProperty().map(size -> size.intValue() == 1);
this.paging = Bindings.createStringBinding(() -> selectionIndex.get() + 1 + "/" + events.size(), selectionIndex, events);
this.vaultName = new SimpleStringProperty();
this.eventTimestamp = new SimpleStringProperty();
this.message = new SimpleStringProperty();
this.fileName = new SimpleStringProperty("");
this.description = new SimpleStringProperty();
this.actionText = new SimpleStringProperty();
this.executorService = executorService;
}
@FXML
public void initialize() {
window.setOnShowing(_ -> selectionIndex.set(0));
selectionIndex.addListener((_, _, n) -> {
if (!events.isEmpty()) {
selectedEvent.setValue(events.get(n.intValue()));
}
});
selectedEvent.addListener(this::selectTexts);
}
private void selectTexts(ObservableValue<? extends VaultEvent> observable, VaultEvent oldEvent, VaultEvent newEvent) {
if (newEvent == null) {
vaultName.set("");
message.set("NO CONTENT");
description.set(BUG_MSG);
actionText.set(null);
return;
}
vaultName.set(newEvent.v().getDisplayName());
switch (newEvent.actualEvent()) {
case FileIsInUseEvent fiiue -> {
var userAndDevice = fiiue.owner().split(Constants.HUB_USER_DEVICE_SEPARATOR);
var user = userAndDevice[0];
var device = userAndDevice.length == 1 ? userAndDevice[0] : userAndDevice[1];
var cleartextFileName = fiiue.cleartextPath().substring(fiiue.cleartextPath().lastIndexOf('/') + 1);
eventTimestamp.set(localizedTimeFormatter.format(fiiue.lastUpdated()));
message.set(resourceBundle.getString("notification.inUse.message"));
fileName.set(cleartextFileName);
description.set(resourceBundle.getString("notification.inUse.description").formatted(user, device));
actionText.set(resourceBundle.getString("notification.inUse.action"));
}
default -> {
message.set("NO CONTENT");
description.set(BUG_MSG);
actionText.set(null);
}
}
}
@FXML
public void processSelectedEvent() {
try {
var ev = selectedEvent.get();
switch (ev.actualEvent()) {
case FileIsInUseEvent fiiue -> {
executorService.submit(fiiue.ignoreMethod());
}
default -> {
} //normally nothing
}
} finally {
removeSelectedEvent();
}
}
private void removeSelectedEvent() {
int i = selectionIndex.get();
var size = events.size();
if (i < 0 || i >= size) {
LOG.error("Selection index {} is out of bounds of list size {} during event removal. Closing Window.", i, size);
window.close();
return;
}
events.remove(i);
if (events.isEmpty()) {
window.close(); //no more events
} else if (events.size() == i) {
selectionIndex.set(i - 1); //triggers event update
} else {
selectedEvent.set(events.get(i));
}
}
@FXML
public void previousNotification() {
int i = selectionIndex.get();
if (i != 0) {
selectionIndex.set(i - 1);
}
}
@FXML
public void nextNotification() {
int i = selectionIndex.get();
if (i != events.size() - 1) {
selectionIndex.set(i + 1);
}
}
@FXML
public void close() {
events.clear();
window.close();
}
//FXML bindings
public ObservableValue<String> eventTimeProperty() {
return eventTimestamp;
}
public String getEventTime() {
return eventTimestamp.get();
}
public ObservableValue<String> vaultNameProperty() {
return vaultName;
}
public String getVaultName() {
return vaultName.get();
}
public ObservableValue<String> fileNameProperty() {
return fileName;
}
public String getFileName() {
return fileName.get();
}
public ObservableValue<String> messageProperty() {
return message;
}
public String getMessage() {
return message.get();
}
public ObservableValue<String> descriptionProperty() {
return description;
}
public String getDescription() {
return description.get();
}
public StringProperty actionTextProperty() {
return actionText;
}
public String getActionText() {
return Objects.requireNonNullElse(actionText.get(), "");
}
public ObservableStringValue pagingProperty() {
return paging;
}
public String getPaging() {
return paging.get();
}
public ObservableValue<Boolean> singleEventProperty() {
return singleEvent;
}
public boolean isSingleEvent() {
return singleEvent.getValue();
}
}

View File

@@ -0,0 +1,99 @@
package org.cryptomator.ui.notification;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.apache.commons.lang3.SystemUtils;
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.StageInitializer;
import org.cryptomator.ui.common.SystemBarUtil;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class NotificationModule {
@Provides
@NotificationWindow
@NotificationScoped
static Stage provideStage(StageInitializer initializer) {
Stage stage = new Stage(StageStyle.TRANSPARENT);
stage.setTitle("Filesystem notification"); //TODO: translate
stage.setResizable(false);
stage.initModality(Modality.NONE);
stage.setAlwaysOnTop(true);
initializer.accept(stage);
stage.setOnShown(_ -> placeWindow(stage));
return stage;
}
/**
* Places the notification window on the screen according to some heuristic based on operating system and system bar placement.
* <p>
* On macOS, the window is placed in the top-right corner of the primary screen, following platform conventions.
* On other operating systems, the window placement depends on the location of the system bar:
* <ul>
* <li>If the system bar is at the top, the window is centered horizontally at the top of the screen.</li>
* <li>Otherwise (e.g., system bar at the bottom or elsewhere), the window is placed in the bottom-right corner.</li>
* </ul>
* <p>
* The method uses the visual bounds of the primary screen to avoid overlapping with system UI elements.
* Assumes the window size has already been set before calling this method.
*
* @param window the Stage representing the notification window to be placed
*/
static void placeWindow(Stage window) {
var screen = Screen.getPrimary();
var vBounds = screen.getVisualBounds();
if (SystemUtils.IS_OS_MAC) { //place to right top
window.setX(vBounds.getMaxX() - window.getWidth());
window.setY(vBounds.getMinY());
} else {
switch (SystemBarUtil.getPlacementOfSystembar(screen)) {
case TOP -> { //place to middle top
window.setX(vBounds.getMinX() + (vBounds.getWidth() - window.getWidth()) / 2.0);
window.setY(vBounds.getMinY());
}
default -> { //place to right bottom
window.setX(vBounds.getMaxX() - window.getWidth());
window.setY(vBounds.getMaxY() - window.getHeight());
}
}
}
}
// javafx setup
@Provides
@FxmlScene(FxmlFile.NOTIFICATION)
@NotificationScoped
static Scene provideNotificationScene(@NotificationWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.NOTIFICATION);
}
@Provides
@NotificationScoped
@NotificationWindow
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Binds
@IntoMap
@FxControllerKey(NotificationController.class)
abstract FxController bindNotificationController(NotificationController controller);
}

View File

@@ -0,0 +1,11 @@
package org.cryptomator.ui.notification;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface NotificationScoped {}

View File

@@ -0,0 +1,12 @@
package org.cryptomator.ui.notification;
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)
public @interface NotificationWindow {}

View File

@@ -101,8 +101,9 @@ public class GeneralPreferencesController implements FxController {
}
private void migrateKeychainEntries(Observable observable, KeychainAccessProvider oldProvider, KeychainAccessProvider newProvider) {
//currently, we only migrate on macOS (touchID vs regular keychain)
if (SystemUtils.IS_OS_MAC) {
//currently, we migrate on macOS (touchID vs regular keychain)
//and on Linux (GNOME Keyring / KDE Wallet / Secret Service)
if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX) {
var idsAndNames = settings.directories.stream().collect(Collectors.toMap(vs -> vs.id, vs -> vs.displayName.getValue()));
if (!idsAndNames.isEmpty()) {
if (LOG.isDebugEnabled()) {

View File

@@ -56,7 +56,7 @@ public class InterfacePreferencesController implements FxController {
@FXML
public void initialize() {
themeChoiceBox.getItems().addAll(UiTheme.applicableValues());
themeChoiceBox.getItems().addAll(UiTheme.values());
if (!themeChoiceBox.getItems().contains(settings.theme.get())) {
settings.theme.set(UiTheme.LIGHT);
}

View File

@@ -104,7 +104,7 @@ public class RecoveryKeyCreationController implements FxController {
descriptionLabel.formatProperty().set(resourceBundle.getString("recoveryKey.recover.description"));
cancelButton.setOnAction((_) -> back());
cancelButton.setText(resourceBundle.getString("generic.button.back"));
nextButton.setOnAction((_) -> restoreWithPassword());
nextButton.setOnAction((_) -> restoreWithPasswordAsync());
}
}
@@ -137,11 +137,47 @@ public class RecoveryKeyCreationController implements FxController {
}
@FXML
public void restoreWithPassword() {
public void restoreWithPasswordAsync() {
Task<Void> task = RecoveryKeyTasks.createTask(this::restoreWithPassword);
task.setOnScheduled(_ -> {
LOG.debug("Restoring vault configuration with password for {}.", vault.getDisplayablePath());
});
task.setOnSucceeded(_ -> {
LOG.debug("Restored vault configuration for {}.", vault.getDisplayablePath());
try {
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
vaultListManager.add(vault.getPath());
}
window.close();
dialogs.prepareRecoverPasswordSuccess((Stage) window.getOwner()) //
.setTitleKey("recover.recoverVaultConfig.title") //
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
.setDescriptionKey("recoveryKey.recover.resetMasterkeyFileSuccess.description")
.build().showAndWait();
} catch (IOException e) {
LOG.error("Failed to add vault to list.", e);
appWindows.showErrorWindow(e, window, null);
}
});
task.setOnFailed(_ -> {
if (task.getException() instanceof InvalidPassphraseException e) {
LOG.info("Password invalid", e);
Animations.createShakeWindowAnimation(window).play();
} else {
LOG.error("Recovery process failed.", task.getException());
appWindows.showErrorWindow(task.getException(), window, null);
}
});
executor.submit(task);
}
void restoreWithPassword() throws IOException, CryptoException {
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
Path recoveryPath = recoveryDirectory.getRecoveryPath();
Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME);
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) {
@@ -152,23 +188,6 @@ public class RecoveryKeyCreationController implements FxController {
}
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
vaultListManager.add(vault.getPath());
}
window.close();
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
.setTitleKey("recover.recoverVaultConfig.title") //
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
.setDescriptionKey("recoveryKey.recover.resetMasterkeyFileSuccess.description")
.build().showAndWait();
} catch (InvalidPassphraseException e) {
LOG.info("Password invalid", e);
Animations.createShakeWindowAnimation(window).play();
} catch (IOException | CryptoException | IllegalStateException e) {
LOG.error("Recovery process failed", e);
appWindows.showErrorWindow(e, window, null);
}
}

View File

@@ -117,14 +117,46 @@ public class RecoveryKeyResetPasswordController implements FxController {
@FXML
public void next() {
switch (recoverType.get()) {
case RESTORE_ALL -> restorePassword();
case RESTORE_ALL -> restorePasswordAsync();
case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword();
default -> resetPassword(); // Fallback
}
}
@FXML
public void restorePassword() {
public void restorePasswordAsync() {
Task<Void> task = RecoveryKeyTasks.createTask(this::restorePassword);
task.setOnScheduled(_ -> {
LOG.debug("Restoring vault configuration for {}.", vault.getDisplayablePath());
});
task.setOnSucceeded(_ -> {
LOG.debug("Restored vault configuration for {}.", vault.getDisplayablePath());
try {
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
vaultListManager.add(vault.getPath());
}
window.close();
dialogs.prepareRecoverPasswordSuccess((Stage) window.getOwner()) //
.setTitleKey("recover.recoverVaultConfig.title") //
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
.build().showAndWait();
} catch (IOException e) {
LOG.error("Failed to add vault to list.", e);
appWindows.showErrorWindow(e, window, null);
}
});
task.setOnFailed(_ -> {
LOG.error("Recovery process failed.", task.getException());
appWindows.showErrorWindow(task.getException(), window, null);
});
executor.submit(task);
}
void restorePassword() throws IOException, CryptoException {
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
Path recoveryPath = recoveryDirectory.getRecoveryPath();
MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
@@ -135,19 +167,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME);
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
vaultListManager.add(vault.getPath());
}
window.close();
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
.setTitleKey("recover.recoverVaultConfig.title") //
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
.build().showAndWait();
} catch (IOException | CryptoException e) {
LOG.error("Recovery process failed", e);
appWindows.showErrorWindow(e, window, null);
}
}

View File

@@ -0,0 +1,25 @@
package org.cryptomator.ui.recoverykey;
import javafx.concurrent.Task;
final class RecoveryKeyTasks {
private RecoveryKeyTasks() {
}
@FunctionalInterface
interface TaskAction {
void run() throws Exception;
}
static Task<Void> createTask(TaskAction action) {
return new Task<Void>() {
@Override
protected Void call() throws Exception {
action.run();
return null;
}
};
}
}

View File

@@ -57,8 +57,8 @@ abstract class UnlockModule {
@Provides
@UnlockWindow
@UnlockScoped
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
return compFactory.create(vault, window).keyloadingStrategy();
}
@Provides

View File

@@ -9,11 +9,15 @@ import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.nio.file.Files;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@VaultOptionsScoped
public class MasterkeyOptionsController implements FxController {
@@ -25,6 +29,7 @@ public class MasterkeyOptionsController implements FxController {
private final ForgetPasswordComponent.Builder forgetPasswordWindow;
private final KeychainManager keychain;
private final ObservableValue<Boolean> passwordSaved;
private final BooleanProperty masterkeyFileAvailable;
@Inject
@@ -40,6 +45,7 @@ public class MasterkeyOptionsController implements FxController {
} else {
this.passwordSaved = new SimpleBooleanProperty(false);
}
this.masterkeyFileAvailable = new SimpleBooleanProperty(Files.exists(vault.getPath().resolve(MASTERKEY_FILENAME)));
}
@FXML
@@ -70,4 +76,12 @@ public class MasterkeyOptionsController implements FxController {
public boolean isPasswordSaved() {
return passwordSaved.getValue();
}
public BooleanProperty masterkeyFileAvailableProperty() {
return masterkeyFileAvailable;
}
public boolean isMasterkeyFileAvailable() {
return masterkeyFileAvailable.get();
}
}

View File

@@ -50,6 +50,7 @@
GREEN_5: PRIMARY;
RED_5: #E74C3C;
ORANGE_5: #E67E22;
YELLOW_4: #C19F0E;
YELLOW_5: #F1C40F;
MAIN_BG: GRAY_1;
@@ -113,6 +114,11 @@
-fx-font-size: 1.2em;
}
.label-window-title {
-fx-font-family: 'Open Sans SemiBold';
-fx-font-size: 1.0em;
}
.label-small {
-fx-font-size: 0.8em;
}
@@ -382,37 +388,62 @@
/*******************************************************************************
* *
* NotificationBar *
* InfoBar *
* *
******************************************************************************/
.info-bar {
-fx-alignment: center;
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: -bar-bg;
}
.notification-label {
-fx-text-fill: white;
.info-bar--green {
-bar-bg: GREEN_5;
-button-bg: GREEN_5;
-button-bg-pressed: GREEN_3;
}
.info-bar--yellow {
-bar-bg: YELLOW_5;
-button-bg: YELLOW_5;
-button-bg-pressed: YELLOW_4;
}
.info-bar--red {
-bar-bg: RED_5;
-button-bg: RED_5;
-button-bg-pressed: RED_5;
}
.info-bar .label {
-fx-text-fill: GRAY_9;
-fx-font-weight: bold;
}
.notification-debug {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: RED_5;
.info-bar--yellow .label {
-fx-text-fill: GRAY_9;
-fx-font-weight: bold;
-fx-effect: dropshadow( gaussian , rgba(0,0,0,0.45) , 2 , 0 , 0 , 1 );
}
.notification-update {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: YELLOW_5;
.info-bar .glyph {
-fx-fill: GRAY_9;
}
.notification-support {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: PRIMARY;
.info-bar:hover .label {
-fx-underline:true;
}
.notification-debug:hover .notification-label,
.notification-update:hover .notification-label,
.notification-support:hover .notification-label {
-fx-underline:true;
.info-bar .button {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-padding: 2 2 2 2;
}
.info-bar .button:armed {
-fx-background-color: -button-bg-pressed;
-fx-background-radius: 8;
}
/*******************************************************************************
@@ -1186,3 +1217,32 @@
-fx-background-color: MAIN_BG;
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
}
/*******************************************************************************
* *
* Notification Window
* *
******************************************************************************/
.notification-window {
-fx-background-color: MAIN_BG;
-fx-background-radius: 8px;
-fx-border-radius: 8px;
-fx-background-insets: 0;
-fx-border-color: MUTED_BG;
-fx-border-width: 1px;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 28, 0.35, 0, 6);
}
.notification-window .dialog-header {
-fx-alignment: center-left;
}
.notification-window .window-bar-button {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-padding: 6 8 6 8;
}
.notification-window .window-bar-button:armed {
-fx-background-color: CONTROL_BG_ARMED;
-fx-background-radius: 8;
}

View File

@@ -50,6 +50,7 @@
GREEN_5: PRIMARY;
RED_5: #E74C3C;
ORANGE_5: #E67E22;
YELLOW_4: #C19F0E;
YELLOW_5: #F1C40F;
MAIN_BG: GRAY_9;
@@ -113,6 +114,11 @@
-fx-font-size: 1.2em;
}
.label-window-title {
-fx-font-family: 'Open Sans SemiBold';
-fx-font-size: 1.0em;
}
.label-small {
-fx-font-size: 0.8em;
}
@@ -382,39 +388,64 @@
/*******************************************************************************
* *
* NotificationBar *
* InfoBar *
* *
******************************************************************************/
.info-bar {
-fx-alignment: center;
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: -bar-bg;
}
.notification-label {
-fx-text-fill: white;
.info-bar--green {
-bar-bg: GREEN_5;
-button-bg: GREEN_5;
-button-bg-pressed: GREEN_3;
}
.info-bar--yellow {
-bar-bg: YELLOW_5;
-button-bg: YELLOW_5;
-button-bg-pressed: YELLOW_4;
}
.info-bar--red {
-bar-bg: RED_5;
-button-bg: RED_5;
-button-bg-pressed: RED_5;
}
.info-bar .label {
-fx-text-fill: GRAY_9;
-fx-font-weight: bold;
}
.notification-debug {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: RED_5;
.info-bar--yellow .label {
-fx-text-fill: GRAY_9;
-fx-font-weight: bold;
-fx-effect: dropshadow( gaussian , rgba(0,0,0,0.45) , 2 , 0 , 0 , 1 );
}
.notification-update {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: YELLOW_5;
.info-bar .glyph {
-fx-fill: GRAY_9;
}
.notification-support {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: PRIMARY;
}
.notification-debug:hover .notification-label,
.notification-update:hover .notification-label,
.notification-support:hover .notification-label {
.info-bar:hover .label {
-fx-underline:true;
}
.info-bar .button {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-padding: 2 2 2 2;
}
.info-bar .button:armed {
-fx-background-color: -button-bg-pressed;
-fx-background-radius: 8;
}
/*******************************************************************************
* *
* ScrollBar *
@@ -1186,3 +1217,33 @@
-fx-background-color: MAIN_BG;
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
}
/*******************************************************************************
* *
* Notification Window
* *
******************************************************************************/
.notification-window {
-fx-background-color: MAIN_BG;
-fx-background-radius: 8px;
-fx-border-radius: 8px;
-fx-background-insets: 0;
-fx-border-color: MUTED_BG;
-fx-border-width: 1px;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 28, 0.35, 0, 6);
}
.notification-window .dialog-header {
-fx-alignment: center-left;
}
.notification-window .window-bar-button {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-padding: 6 8 6 8;
}
.notification-window .window-bar-button:armed {
-fx-background-color: CONTROL_BG_ARMED;
-fx-background-radius: 8;
}

View File

@@ -13,7 +13,8 @@
prefWidth="450"
prefHeight="450"
spacing="24"
alignment="CENTER">
alignment="CENTER"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>

View File

@@ -19,7 +19,8 @@
prefWidth="450"
prefHeight="450"
spacing="12"
alignment="CENTER_LEFT">
alignment="CENTER_LEFT"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>
@@ -39,9 +40,9 @@
<CheckBox fx:id="expertSettingsCheckBox" text="%addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox" onAction="#toggleUseExpertSettings"/>
<VBox spacing="6" visible="${expertSettingsCheckBox.selected}">
<HBox spacing="2" HBox.hgrow="NEVER">
<Label text="%addvaultwizard.new.expertSettings.shorteningThreshold.title"/>
<Label text="%addvaultwizard.new.expertSettings.shorteningThreshold.title" labelFor="$shorteningThresholdTextField"/>
<Region prefWidth="2"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs">
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" accessibleText="%addvaultwizard.new.expertSettings.shorteningThreshold.tooltip">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>

View File

@@ -19,7 +19,8 @@
prefWidth="450"
prefHeight="450"
spacing="12"
alignment="CENTER_LEFT">
alignment="CENTER_LEFT"
accessibleRole="DIALOG">
<fx:define>
<ToggleGroup fx:id="locationPresetsToggler"/>
<FontAwesome5IconView fx:id="badLocation" styleClass="glyph-icon-red" glyph="TIMES"/>

View File

@@ -17,7 +17,8 @@
prefWidth="450"
prefHeight="450"
spacing="12"
alignment="CENTER_LEFT">
alignment="CENTER_LEFT"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>

View File

@@ -15,7 +15,8 @@
prefWidth="450"
prefHeight="450"
spacing="12"
alignment="CENTER_LEFT">
alignment="CENTER_LEFT"
accessibleRole="DIALOG">
<fx:define>
<ToggleGroup fx:id="recoveryKeyChoice"/>
</fx:define>

View File

@@ -11,7 +11,8 @@
prefWidth="450"
prefHeight="450"
spacing="12"
alignment="CENTER_LEFT">
alignment="CENTER_LEFT"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>

View File

@@ -16,7 +16,8 @@
prefWidth="450"
prefHeight="450"
spacing="12"
alignment="TOP_CENTER">
alignment="TOP_CENTER"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>

View File

@@ -13,13 +13,14 @@
fx:controller="org.cryptomator.ui.changepassword.ChangePasswordController"
minWidth="400"
maxWidth="400"
spacing="12">
spacing="12"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<VBox spacing="6">
<FormattedLabel format="%changepassword.enterOldPassword" arg1="${controller.vault.displayName}" wrapText="true"/>
<FormattedLabel format="%changepassword.enterOldPassword" arg1="${controller.vault.displayName}" wrapText="true" labelFor="$oldPasswordField"/>
<NiceSecurePasswordField fx:id="oldPasswordField"/>
</VBox>

View File

@@ -18,7 +18,8 @@
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
alignment="TOP_CENTER"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>

View File

@@ -15,7 +15,8 @@
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
alignment="TOP_CENTER"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>

View File

@@ -19,7 +19,8 @@
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
alignment="TOP_LEFT"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>

View File

@@ -22,7 +22,7 @@
<Insets left="6"/>
</padding>
<Region HBox.hgrow="ALWAYS"/>
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#copyTableToClipboard">
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#copyTableToClipboard" accessibleText="%decryptNames.copyTable.tooltip">
<graphic>
<FontAwesome5IconView glyph="CLIPBOARD" glyphSize="16"/>
</graphic>
@@ -30,7 +30,7 @@
<Tooltip text="%decryptNames.copyTable.tooltip"/>
</tooltip>
</Button>
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#clearTable">
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#clearTable" accessibleText="%decryptNames.clearTable.tooltip">
<graphic>
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
</graphic>
@@ -48,12 +48,12 @@
</Button>
</placeholder>
<columns>
<TableColumn fx:id="ciphertextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
<TableColumn fx:id="ciphertextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}" text="%decryptNames.column.encrypted">
<graphic>
<FontAwesome5IconView glyph="LOCK"/>
</graphic>
</TableColumn>
<TableColumn fx:id="cleartextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
<TableColumn fx:id="cleartextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}" text="%decryptNames.column.decrypted">
<graphic>
<FontAwesome5IconView glyph="LOCK_OPEN"/>
</graphic>

View File

@@ -22,7 +22,8 @@
minHeight="450"
prefWidth="450"
prefHeight="450"
spacing="12">
spacing="12"
accessibleRole="DIALOG">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>
@@ -79,7 +80,7 @@
<padding>
<Insets top="6" bottom="6"/>
</padding>
<Label text="%error.technicalDetails"/>
<Label text="%error.technicalDetails" labelFor="$detailsTextArea"/>
<Region HBox.hgrow="ALWAYS"/>
<Hyperlink styleClass="hyperlink-underline" text="%generic.button.copy" onAction="#copyDetails" contentDisplay="LEFT" visible="${!controller.copiedDetails}" managed="${!controller.copiedDetails}">
<graphic>
@@ -92,7 +93,7 @@
</graphic>
</Hyperlink>
</HBox>
<TextArea VBox.vgrow="ALWAYS" text="${controller.detailText}" prefRowCount="5" editable="false"/>
<TextArea fx:id="detailsTextArea" VBox.vgrow="ALWAYS" text="${controller.detailText}" prefRowCount="5" editable="false"/>
<Region minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="B+C">

View File

@@ -20,9 +20,9 @@
<padding>
<Insets left="6" />
</padding>
<ChoiceBox fx:id="vaultFilterChoiceBox" minWidth="42"/>
<ChoiceBox fx:id="vaultFilterChoiceBox" minWidth="42" accessibleText="%eventView.filterVaults"/>
<Region HBox.hgrow="ALWAYS"/>
<Button styleClass="button-right" onAction="#clearEvents" contentDisplay="GRAPHIC_ONLY">
<Button styleClass="button-right" onAction="#clearEvents" contentDisplay="GRAPHIC_ONLY" accessibleText="%eventView.clearListButton.tooltip">
<graphic>
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
</graphic>

Some files were not shown because too many files have changed in this diff Show More