Compare commits

...
Author SHA1 Message Date
Armin Schrenk ad240eb18b Merge branch 'develop' into release/1.19.0
# Conflicts:
#	pom.xml
2025-12-30 12:40:06 +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 SchrenkandGitHub 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
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]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...8e8c483db84b4bee98b60c0593521ed34d9990e8)

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

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

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](https://github.com/softprops/action-gh-release/compare/6da8fa9354ddfdc4aeace5fc48d7f679b5214090...a06a81a03ee405af7f2048a818ed3f03bbf83c7b)

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

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

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](https://github.com/actions/stale/compare/5f858e3efba33a5ca4407a664cc011ad407f2008...997185467fa4f803885201cee163a9f38240193d)

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

---
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 SchrenkandGitHub 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 HagemannandGitHub 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 HagemannandGitHub 7fe9049266 Improve accessibility for icon-only controls and input labels (#4064) 2025-12-01 22:06:57 +01:00
Tobias HagemannandGitHub 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 04bdb2d8c4 fix mocked update check 2025-11-24 20:58:57 +01:00
Sebastian Stenzel f32d24c1c0 Merge branch 'develop' into release/1.19.0 2025-11-24 20:58:25 +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
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
84 changed files with 1170 additions and 365 deletions
+6 -6
View File
@@ -44,9 +44,9 @@ jobs:
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
openjfx-sha: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4' openjfx-sha: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup Java - name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -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
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: appimage-${{ matrix.appimage-suffix }} name: appimage-${{ matrix.appimage-suffix }}
path: | path: |
@@ -185,7 +185,7 @@ jobs:
if-no-files-found: error if-no-files-found: error
- name: Publish AppImage on GitHub Releases - name: Publish AppImage on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' 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: with:
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
@@ -201,7 +201,7 @@ jobs:
if: github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable' if: github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable'
steps: steps:
- name: Download AppImages - name: Download AppImages
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
path: downloads/ path: downloads/
merge-multiple: true merge-multiple: true
@@ -212,7 +212,7 @@ jobs:
echo "x64-sha256sum=${X64_SHA256}" >> "$GITHUB_OUTPUT" echo "x64-sha256sum=${X64_SHA256}" >> "$GITHUB_OUTPUT"
AARCH64_SHA256=$(sha256sum downloads/cryptomator-*-aarch64.AppImage | cut -d ' ' -f1) AARCH64_SHA256=$(sha256sum downloads/cryptomator-*-aarch64.AppImage | cut -d ' ' -f1)
echo "aarch64-sha256sum=${AARCH64_SHA256}" >> "$GITHUB_OUTPUT" echo "aarch64-sha256sum=${AARCH64_SHA256}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: with:
repository: 'cryptomator/aur-bin' repository: 'cryptomator/aur-bin'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }} token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
+1 -1
View File
@@ -48,7 +48,7 @@ jobs:
env: env:
AUR_PR_URL: tbd AUR_PR_URL: tbd
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: with:
repository: 'cryptomator/aur' repository: 'cryptomator/aur'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }} token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
+16 -6
View File
@@ -7,6 +7,16 @@ on:
description: "Url to the file to upload" description: "Url to the file to upload"
required: true required: true
type: string 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: workflow_dispatch:
inputs: inputs:
url: url:
@@ -39,9 +49,9 @@ jobs:
url="${INPUT_URL}" url="${INPUT_URL}"
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
- name: Download file - 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 - name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: ${{ steps.extractName.outputs.fileName }} name: ${{ steps.extractName.outputs.fileName }}
path: ${{ steps.extractName.outputs.fileName }} path: ${{ steps.extractName.outputs.fileName }}
@@ -50,10 +60,10 @@ jobs:
name: Anti Virus Allowlisting Kaspersky name: Anti Virus Allowlisting Kaspersky
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: download-file needs: download-file
if: github.event_name == 'workflow_call' || inputs.kaspersky if: inputs.kaspersky
steps: steps:
- name: Download artifact - name: Download artifact
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
name: ${{ needs.download-file.outputs.fileName }} name: ${{ needs.download-file.outputs.fileName }}
path: upload path: upload
@@ -70,10 +80,10 @@ jobs:
name: Anti Virus Allowlisting Avast name: Anti Virus Allowlisting Avast
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: download-file needs: download-file
if: github.event_name == 'workflow_call' || inputs.avast if: inputs.avast
steps: steps:
- name: Download artifact - name: Download artifact
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
name: ${{ needs.download-file.outputs.fileName }} name: ${{ needs.download-file.outputs.fileName }}
path: upload path: upload
+18 -14
View File
@@ -22,8 +22,8 @@ jobs:
name: Compile and Test name: Compile and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -49,29 +49,28 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Draft a release - name: Draft a release
if: startsWith(github.ref, 'refs/tags/') 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: with:
draft: true draft: true
discussion_category_name: releases discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
generate_release_notes: true generate_release_notes: true
body: |- 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 🎉 ### What's New 🎉
### Bugfixes 🐛 ### Bugfixes 🐛
### Other Changes 📎 ### 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 <!-- Don't forget to include the
💾 SHA-256 checksums of release artifacts: 💾 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: -->
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,2) >> "$env:GITHUB_ENV" run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,2) >> "$env:GITHUB_ENV"
shell: pwsh shell: pwsh
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }} - name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
java-version: ${{ env.JDK_MAJOR_VERSION}} java-version: ${{ env.JDK_MAJOR_VERSION}}
distribution: ${{ env.JDK_VENDOR }} distribution: ${{ env.JDK_VENDOR }}
+3 -3
View File
@@ -43,7 +43,7 @@ jobs:
env: env:
INPUT_PPAVER: ${{ inputs.ppaver }} INPUT_PPAVER: ${{ inputs.ppaver }}
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- id: deb-version - id: deb-version
name: Determine deb-version name: Determine deb-version
run: | run: |
@@ -60,7 +60,7 @@ jobs:
env: env:
DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }} DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }}
- name: Setup Java - name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -143,7 +143,7 @@ jobs:
run: | run: |
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: linux-deb-package name: linux-deb-package
path: | path: |
+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
INTERVAL: 900 INTERVAL: 900
JSON_DATA: ${{ steps.get-stats.outputs.result }} JSON_DATA: ${{ steps.get-stats.outputs.result }}
- name: Upload Results - name: Upload Results
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5 uses: fjogeleit/http-request-action@c0b95d02a088b47c1f2f4db04fd8af8bd19eee54 # v1.16.6
with: with:
url: 'https://graphite-us-central1.grafana.net/metrics' url: 'https://graphite-us-central1.grafana.net/metrics'
method: 'POST' method: 'POST'
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
env: env:
FLATHUB_PR_URL: tbd FLATHUB_PR_URL: tbd
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: with:
repository: 'flathub/org.cryptomator.Cryptomator' repository: 'flathub/org.cryptomator.Cryptomator'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }} token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
+2 -2
View File
@@ -35,11 +35,11 @@ jobs:
revNum: ${{ steps.versions.outputs.revNum }} revNum: ${{ steps.versions.outputs.revNum }}
type: ${{ steps.versions.outputs.type}} type: ${{ steps.versions.outputs.type}}
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
+4 -4
View File
@@ -47,9 +47,9 @@ jobs:
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-x64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-x64_bin-jmods.zip'
openjfx-sha: '0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52' openjfx-sha: '0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52'
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup Java - name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -283,7 +283,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true continue-on-error: true
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: dmg-${{ matrix.output-suffix }} name: dmg-${{ matrix.output-suffix }}
path: | path: |
@@ -292,7 +292,7 @@ jobs:
if-no-files-found: error if-no-files-found: error
- name: Publish dmg on GitHub Releases - name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' 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: with:
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+4 -4
View File
@@ -45,9 +45,9 @@ jobs:
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-aarch64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-aarch64_bin-jmods.zip'
openjfx-sha: '13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba' openjfx-sha: '13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba'
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup Java - name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@@ -282,7 +282,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true continue-on-error: true
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: dmg-${{ matrix.output-suffix }} name: dmg-${{ matrix.output-suffix }}
path: | path: |
@@ -291,7 +291,7 @@ jobs:
if-no-files-found: error if-no-files-found: error
- name: Publish dmg on GitHub Releases - name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' 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: with:
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with: with:
days-before-stale: 14 days-before-stale: 14
days-before-close: 0 days-before-close: 0
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Publish asc on GitHub Releases - 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: with:
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+2 -2
View File
@@ -16,8 +16,8 @@ jobs:
name: Compile and Test name: Compile and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
+2 -2
View File
@@ -19,9 +19,9 @@ jobs:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup Java - name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with: with:
days-before-stale: 365 days-before-stale: 365
days-before-close: 90 days-before-close: 90
+12 -12
View File
@@ -51,9 +51,9 @@ jobs:
java-version: '25.0.1+8' java-version: '25.0.1+8'
java-package: 'jdk' java-package: 'jdk'
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup Java - name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ matrix.java-dist }} distribution: ${{ matrix.java-dist }}
java-version: ${{ matrix.java-version }} java-version: ${{ matrix.java-version }}
@@ -200,7 +200,7 @@ jobs:
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign DLLs with Actalis CodeSigner - name: Sign DLLs with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release' 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@1074588008ae3326a2221ea451783280518f0366 # no specific version
with: with:
base-dir: 'appdir' base-dir: 'appdir'
file-extensions: 'dll,exe,ps1' file-extensions: 'dll,exe,ps1'
@@ -277,7 +277,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: msi-${{ matrix.arch }} name: msi-${{ matrix.arch }}
path: | path: |
@@ -299,21 +299,21 @@ jobs:
java-version: '24.0.1+9' java-version: '24.0.1+9'
java-package: 'jdk' java-package: 'jdk'
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install wix and extensions - name: Install wix and extensions
run: | run: |
dotnet tool install --global wix --version 6.0.0 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.BootstrapperApplications.wixext/6.0.0 --global
wix.exe extension add WixToolset.Util.wixext/6.0.0 --global wix.exe extension add WixToolset.Util.wixext/6.0.0 --global
- name: Download .msi - name: Download .msi
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
name: msi-${{ matrix.arch }} name: msi-${{ matrix.arch }}
path: dist/win/bundle/resources path: dist/win/bundle/resources
- name: Strip version info from msi file name - name: Strip version info from msi file name
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
- name: Setup Java - name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with: with:
distribution: ${{ matrix.java-dist }} distribution: ${{ matrix.java-dist }}
java-version: ${{ matrix.java-version }} java-version: ${{ matrix.java-version }}
@@ -376,7 +376,7 @@ jobs:
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign burn engine with Actalis CodeSigner - name: Sign burn engine with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release' 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@1074588008ae3326a2221ea451783280518f0366 # no specific version
with: with:
base-dir: 'tmp' base-dir: 'tmp'
file-extensions: 'exe' file-extensions: 'exe'
@@ -400,7 +400,7 @@ jobs:
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign installer with Actalis CodeSigner - name: Sign installer with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release' 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@1074588008ae3326a2221ea451783280518f0366 # no specific version
with: with:
base-dir: 'installer' base-dir: 'installer'
file-extensions: 'exe' file-extensions: 'exe'
@@ -418,7 +418,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: exe-${{ matrix.executable-suffix }} name: exe-${{ matrix.executable-suffix }}
path: | path: |
@@ -436,12 +436,12 @@ jobs:
download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }} download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }}
steps: steps:
- name: Download installers - name: Download installers
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
merge-multiple: true merge-multiple: true
- name: Publish installers on GitHub Releases - name: Publish installers on GitHub Releases
id: publish id: publish
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with: with:
fail_on_unmatched_files: true fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
+8 -2
View File
@@ -10,7 +10,13 @@ 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) ## [Unreleased](https://github.com/cryptomator/cryptomator/compare/1.18.0...HEAD)
### Added ### 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 labels for GUI elements ([#4064](https://github.com/cryptomator/cryptomator/issues/4064), [#4066](https://github.com/cryptomator/cryptomator/pull/4066), [#4055](https://github.com/cryptomator/cryptomator/issues/4055))
### Changed ### Changed
* Built using JDK 25 (#4031) * Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
* Modernized Template for GitHub Releases
+1
View File
@@ -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.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/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.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/org.cryptomator.Cryptomator.svg
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon 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 ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop
@@ -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>
+2 -1
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.Cryptomator256.png usr/share/icons/hicolor/256x256/apps
common/org.cryptomator.Cryptomator512.png usr/share/icons/hicolor/512x512/apps common/org.cryptomator.Cryptomator512.png usr/share/icons/hicolor/512x512/apps
common/org.cryptomator.Cryptomator.metainfo.xml usr/share/metainfo 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
+1
View File
@@ -25,6 +25,7 @@ case "$1" in
fi fi
xdg-desktop-menu install --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop 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.vault.xml
xdg-mime install /usr/share/mime/packages/application-vnd.cryptomator.encrypted.xml
;; ;;
abort-upgrade|abort-remove|abort-deconfigure) abort-upgrade|abort-remove|abort-deconfigure)
+1
View File
@@ -23,6 +23,7 @@ case "$1" in
xdg-desktop-menu uninstall --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop 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.vault.xml
xdg-mime uninstall /usr/share/mime/packages/application-vnd.cryptomator.encrypted.xml
;; ;;
failed-upgrade) failed-upgrade)
+1
View File
@@ -105,6 +105,7 @@
<array> <array>
<string>c9r</string> <string>c9r</string>
<string>c9s</string> <string>c9s</string>
<string>c9u</string>
</array> </array>
<key>public.mime-type</key> <key>public.mime-type</key>
<array> <array>
+1
View File
@@ -94,6 +94,7 @@
<ns0:MIME ContentType="$(var.ProgIdContentType)" Default="yes"/> <ns0:MIME ContentType="$(var.ProgIdContentType)" Default="yes"/>
</ns0:Extension> </ns0:Extension>
<ns0:Extension Id="c9s" Advertise="no" ContentType="$(var.ProgIdContentType)"/> <ns0:Extension Id="c9s" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
<ns0:Extension Id="c9u" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
</ns0:ProgId> </ns0:ProgId>
</ns0:Component> </ns0:Component>
</ns0:DirectoryRef> </ns0:DirectoryRef>
+3 -3
View File
@@ -33,11 +33,11 @@
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds> <nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
<!-- cryptomator dependencies --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>2.10.0-beta2</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.8.0-beta1</cryptomator.integrations.version> <cryptomator.integrations.version>1.8.0-beta1</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.5.1</cryptomator.integrations.win.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.mac.version>1.5.0-beta3</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.7.0-beta1</cryptomator.integrations.linux.version> <cryptomator.integrations.linux.version>1.7.0-beta3</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>5.1.0</cryptomator.fuse.version> <cryptomator.fuse.version>5.1.0</cryptomator.fuse.version>
<cryptomator.webdav.version>3.0.0</cryptomator.webdav.version> <cryptomator.webdav.version>3.0.0</cryptomator.webdav.version>
+4 -4
View File
@@ -1,5 +1,4 @@
import ch.qos.logback.classic.spi.Configurator; import ch.qos.logback.classic.spi.Configurator;
import org.cryptomator.networking.SSLContextWithPKCS12TrustStore;
import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider;
import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider;
@@ -14,11 +13,12 @@ import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvid
import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider; 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.tray.TrayMenuController;
import org.cryptomator.logging.LogbackConfiguratorFactory; 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.traymenu.AwtTrayMenuController; import org.cryptomator.ui.traymenu.AwtTrayMenuController;
open module org.cryptomator.desktop { open module org.cryptomator.desktop {
@@ -13,5 +13,7 @@ public interface Constants {
String CRYPTOMATOR_FILENAME_GLOB = "*.cryptomator"; String CRYPTOMATOR_FILENAME_GLOB = "*.cryptomator";
URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME); URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME);
byte[] PEPPER = new byte[0]; byte[] PEPPER = new byte[0];
// Separator used to concatenate Hub username and device name in the filesystem owner identifier.
String HUB_USER_DEVICE_SEPARATOR = "&";
} }
@@ -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());
}
}
@@ -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();
}
@@ -167,6 +167,7 @@ public class Mounter {
usedMountServices.add(mountService); usedMountServices.add(mountService);
var builder = mountService.forFileSystem(cryptoFsRoot); 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 internal = new SettledMounter(mountService, builder, vaultSettings); // FIXME: no need for an inner class
var cleanup = internal.prepare(); var cleanup = internal.prepare();
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup); return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);
@@ -10,7 +10,7 @@ package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants; 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.mount.Mounter;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings; 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.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; 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.MountFailedException;
import org.cryptomator.integrations.mount.Mountpoint; import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException; import org.cryptomator.integrations.mount.UnmountFailedException;
@@ -78,6 +80,7 @@ public class Vault {
private final Mounter mounter; private final Mounter mounter;
private final Settings settings; private final Settings settings;
private final FileSystemEventAggregator fileSystemEventAggregator; private final FileSystemEventAggregator fileSystemEventAggregator;
private final NotificationManager notificationManager;
private final BooleanProperty showingStats; private final BooleanProperty showingStats;
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null); private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@@ -90,7 +93,8 @@ public class Vault {
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, // @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
VaultStats stats, // VaultStats stats, //
Mounter mounter, Settings settings, // Mounter mounter, Settings settings, //
FileSystemEventAggregator fileSystemEventAggregator) { FileSystemEventAggregator fileSystemEventAggregator, //
NotificationManager notificationManager) {
this.vaultSettings = vaultSettings; this.vaultSettings = vaultSettings;
this.configCache = configCache; this.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem; this.cryptoFileSystem = cryptoFileSystem;
@@ -109,6 +113,7 @@ public class Vault {
this.mounter = mounter; this.mounter = mounter;
this.settings = settings; this.settings = settings;
this.fileSystemEventAggregator = fileSystemEventAggregator; this.fileSystemEventAggregator = fileSystemEventAggregator;
this.notificationManager = notificationManager;
this.showingStats = new SimpleBooleanProperty(false); this.showingStats = new SimpleBooleanProperty(false);
this.quickAccessEntry = new AtomicReference<>(null); 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()); LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength.get());
} }
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() // var fsPropsBuilder = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withKeyLoader(keyLoader) // .withKeyLoader(keyLoader) //
.withFlags(flags) // .withFlags(flags) //
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) // .withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) // .withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
.withFilesystemEventConsumer(this::consumeVaultEvent) // .withFilesystemEventConsumer(this::consumeVaultEvent);
.build(); if (keyLoader instanceof FilesystemOwnerSupplier oo) {
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps); fsPropsBuilder.withOwnerGetter(oo::getOwner);
}
return CryptoFileSystemProvider.newFileSystem(getPath(), fsPropsBuilder.build());
} }
private void destroyCryptoFileSystem() { private void destroyCryptoFileSystem() {
@@ -262,6 +270,7 @@ public class Vault {
private void consumeVaultEvent(FilesystemEvent e) { private void consumeVaultEvent(FilesystemEvent e) {
fileSystemEventAggregator.put(this, e); fileSystemEventAggregator.put(this, e);
notificationManager.offer(this, e);
} }
// ****************************************************************************** // ******************************************************************************
@@ -6,6 +6,7 @@ import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent; import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent; import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent; import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.cryptofs.event.FileIsInUseEvent;
import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptofs.event.FilesystemEvent;
import javax.inject.Inject; import javax.inject.Inject;
@@ -101,6 +102,7 @@ public class FileSystemEventAggregator {
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext; case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext; case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext; case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
case FileIsInUseEvent(_, _, Path ciphertext, _, _, _) -> ciphertext;
}; };
return new FSEventBucket(v, p, event.getClass()); return new FSEventBucket(v, p, event.getClass());
} }
@@ -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;
}
}
}
@@ -3,25 +3,6 @@ package org.cryptomator.event;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.FilesystemEvent; 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);
}
} }
@@ -38,6 +38,7 @@ public enum FxmlFile {
MIGRATION_RUN("/fxml/migration_run.fxml"), // MIGRATION_RUN("/fxml/migration_run.fxml"), //
MIGRATION_START("/fxml/migration_start.fxml"), // MIGRATION_START("/fxml/migration_start.fxml"), //
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), // MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
NOTIFICATION("/fxml/notification.fxml"), //
PREFERENCES("/fxml/preferences.fxml"), // PREFERENCES("/fxml/preferences.fxml"), //
QUIT("/fxml/quit.fxml"), // QUIT("/fxml/quit.fxml"), //
QUIT_FORCED("/fxml/quit_forced.fxml"), // QUIT_FORCED("/fxml/quit_forced.fxml"), //
@@ -59,6 +60,7 @@ public enum FxmlFile {
VAULT_STATISTICS("/fxml/stats.fxml"), // VAULT_STATISTICS("/fxml/stats.fxml"), //
WRONGFILEALERT("/fxml/wrongfilealert.fxml"); WRONGFILEALERT("/fxml/wrongfilealert.fxml");
private final String ressourcePathString; private final String ressourcePathString;
FxmlFile(String ressourcePathString) { FxmlFile(String ressourcePathString) {
@@ -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;
}
}
}
@@ -12,6 +12,8 @@ public enum FontAwesome5Icon {
CARET_DOWN("\uF0D7"), // CARET_DOWN("\uF0D7"), //
CARET_RIGHT("\uF0Da"), // CARET_RIGHT("\uF0Da"), //
CHECK("\uF00C"), // CHECK("\uF00C"), //
CHEVRON_LEFT("\uF053"), //
CHEVRON_RIGHT("\uF054"), //
CLOCK("\uF017"), // CLOCK("\uF017"), //
CLIPBOARD("\uF328"), // CLIPBOARD("\uF328"), //
COG("\uF013"), // COG("\uF013"), //
@@ -60,6 +62,7 @@ public enum FontAwesome5Icon {
TRASH("\uF1F8"), // TRASH("\uF1F8"), //
UNLINK("\uf127"), // UNLINK("\uf127"), //
USER_COG("\uf4fe"), // USER_COG("\uf4fe"), //
USER_LOCK("\uf502"), //
WRENCH("\uF0AD"), // WRENCH("\uF0AD"), //
WINDOW_MINIMIZE("\uF2D1"), // WINDOW_MINIMIZE("\uF2D1"), //
; ;
@@ -1,5 +1,8 @@
package org.cryptomator.ui.eventview; 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.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent; import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator; import org.cryptomator.event.FileSystemEventAggregator;
@@ -115,7 +118,7 @@ public class EventListCellController implements FxController {
eventActionsMenu.hide(); eventActionsMenu.hide();
eventActionsMenu.getItems().clear(); eventActionsMenu.getItems().clear();
eventTooltip.setText(item.getKey().vault().getDisplayName()); eventTooltip.setText(item.getKey().vault().getDisplayName());
addAction("generic.action.dismiss", () -> { addLocalizedAction("generic.action.dismiss", () -> {
fileSystemEventAggregator.remove(item.getKey()); fileSystemEventAggregator.remove(item.getKey());
}); });
switch (item.getValue().mostRecentEvent()) { switch (item.getValue().mostRecentEvent()) {
@@ -124,20 +127,41 @@ public class EventListCellController implements FxController {
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse); case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse); case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse);
case BrokenFileNodeEvent fse -> this.adjustToBrokenFileNodeEvent(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) { private void adjustToBrokenFileNodeEvent(BrokenFileNodeEvent bfe) {
eventIcon.setValue(FontAwesome5Icon.TIMES); eventIcon.setValue(FontAwesome5Icon.TIMES);
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message")); eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message"));
eventDescription.setValue(bfe.ciphertextPath().getFileName().toString()); eventDescription.setValue(bfe.ciphertextPath().getFileName().toString());
if (revealService != null) { if (revealService != null) {
addAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, bfe.ciphertextPath())); addLocalizedAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, bfe.ciphertextPath()));
} else { } 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) { private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
@@ -145,9 +169,9 @@ public class EventListCellController implements FxController {
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message")); eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message"));
eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString()); eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString());
if (revealService != null) { if (revealService != null) {
addAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath()))); addLocalizedAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
} else { } 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")); eventMessage.setValue(resourceBundle.getString("eventView.entry.conflict.message"));
eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString()); eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString());
if (revealService != null) { if (revealService != null) {
addAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath()))); addLocalizedAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
addAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath())); addLocalizedAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath()));
} else { } else {
addAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString())); addLocalizedAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
addAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().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")); eventMessage.setValue(resourceBundle.getString("eventView.entry.decryptionFailed.message"));
eventDescription.setValue(dfe.ciphertextPath().getFileName().toString()); eventDescription.setValue(dfe.ciphertextPath().getFileName().toString());
if (revealService != null) { if (revealService != null) {
addAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath())); addLocalizedAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
} else { } 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")); eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenDirFile.message"));
eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString()); eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString());
if (revealService != null) { if (revealService != null) {
addAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath())); addLocalizedAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
} else { } 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) { private void addLocalizedAction(String localizationKey, Runnable action) {
var entry = new MenuItem(resourceBundle.getString(localizationKey)); 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.getStyleClass().addLast("dropdown-button-context-menu-item");
entry.setOnAction(_ -> action.run()); entry.setOnAction(_ -> action.run());
eventActionsMenu.getItems().addLast(entry); eventActionsMenu.getItems().addLast(entry);
@@ -234,18 +263,17 @@ public class EventListCellController implements FxController {
} }
} }
private Path convertVaultPathToSystemPath(Path p) { private Path convertVaultPathToSystemPath(String vaultInternalPath) {
if (!(p instanceof CryptoPath)) {
throw new IllegalArgumentException("Path " + p + " is not a vault path");
}
var v = eventEntry.getValue().getKey().vault(); var v = eventEntry.getValue().getKey().vault();
if (!v.isUnlocked()) { if (!v.isUnlocked()) {
return Path.of(System.getProperty("user.home")); return Path.of(System.getProperty("user.home"));
} }
var mountUri = v.getMountPoint().uri(); var mountPoint = v.getMountPoint().uri().getPath();
var internalPath = p.toString().substring(1); if(SystemUtils.IS_OS_WINDOWS) {
return Path.of(mountUri.getPath().concat(internalPath).substring(1)); 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) { private void reveal(RevealPathService s, Path p) {
@@ -30,9 +30,20 @@ public class FxApplication {
private final FxApplicationTerminator applicationTerminator; private final FxApplicationTerminator applicationTerminator;
private final AutoUnlocker autoUnlocker; private final AutoUnlocker autoUnlocker;
private final FxFSEventList fxFSEventList; private final FxFSEventList fxFSEventList;
private final FxNotificationManager notificationManager;
@Inject @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.startupTime = startupTime;
this.environment = environment; this.environment = environment;
this.settings = settings; this.settings = settings;
@@ -43,6 +54,7 @@ public class FxApplication {
this.applicationTerminator = applicationTerminator; this.applicationTerminator = applicationTerminator;
this.autoUnlocker = autoUnlocker; this.autoUnlocker = autoUnlocker;
this.fxFSEventList = fxFSEventList; this.fxFSEventList = fxFSEventList;
this.notificationManager = notificationManager;
} }
public void start() { public void start() {
@@ -88,6 +100,7 @@ public class FxApplication {
launchEventHandler.startHandlingLaunchEvents(); launchEventHandler.startHandlingLaunchEvents();
fxFSEventList.schedulePollForUpdates(); fxFSEventList.schedulePollForUpdates();
notificationManager.schedulePollForUpdates();
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES); autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
} }
@@ -13,6 +13,7 @@ import org.cryptomator.ui.eventview.EventViewComponent;
import org.cryptomator.ui.health.HealthCheckComponent; import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.notification.NotificationComponent;
import org.cryptomator.ui.preferences.PreferencesComponent; import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
@@ -39,7 +40,8 @@ import java.io.InputStream;
UpdateReminderComponent.class, // UpdateReminderComponent.class, //
ShareVaultComponent.class, // ShareVaultComponent.class, //
EventViewComponent.class, // EventViewComponent.class, //
RecoveryKeyComponent.class}) RecoveryKeyComponent.class, //
NotificationComponent.class })
abstract class FxApplicationModule { abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException { private static Image createImageFromResource(String resourceName) throws IOException {
@@ -78,4 +80,10 @@ abstract class FxApplicationModule {
return factory.create(); return factory.create();
} }
@Provides
@FxApplicationScoped
static NotificationComponent provideNotificationComponent(NotificationComponent.Factory factory) {
return factory.create();
}
} }
@@ -11,6 +11,7 @@ import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.eventview.EventViewComponent; import org.cryptomator.ui.eventview.EventViewComponent;
import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.notification.NotificationComponent;
import org.cryptomator.ui.preferences.PreferencesComponent; import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.quit.QuitComponent;
@@ -54,6 +55,7 @@ public class FxApplicationWindows {
private final LockComponent.Factory lockWorkflowFactory; private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory; private final ErrorComponent.Factory errorWindowFactory;
private final Lazy<EventViewComponent> eventViewWindow; private final Lazy<EventViewComponent> eventViewWindow;
private final Lazy<NotificationComponent> notificationWindow;
private final ExecutorService executor; private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow; private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow; private final ShareVaultComponent.Factory shareVaultWindow;
@@ -73,6 +75,7 @@ public class FxApplicationWindows {
VaultOptionsComponent.Factory vaultOptionsWindow, // VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, // ShareVaultComponent.Factory shareVaultWindow, //
Lazy<EventViewComponent> eventViewWindow, // Lazy<EventViewComponent> eventViewWindow, //
Lazy<NotificationComponent> notificationWindow,
ExecutorService executor, // ExecutorService executor, //
Dialogs dialogs) { Dialogs dialogs) {
this.primaryStage = primaryStage; this.primaryStage = primaryStage;
@@ -85,6 +88,7 @@ public class FxApplicationWindows {
this.lockWorkflowFactory = lockWorkflowFactory; this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory; this.errorWindowFactory = errorWindowFactory;
this.eventViewWindow = eventViewWindow; this.eventViewWindow = eventViewWindow;
this.notificationWindow = notificationWindow;
this.executor = executor; this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow; this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow; this.shareVaultWindow = shareVaultWindow;
@@ -193,6 +197,10 @@ public class FxApplicationWindows {
return CompletableFuture.supplyAsync(() -> eventViewWindow.get().showEventViewerWindow(), Platform::runLater).whenComplete(this::reportErrors); 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. * Displays the generic error scene in the given window.
* *
@@ -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;
}
}
@@ -63,8 +63,8 @@ abstract class HealthCheckModule {
@Provides @Provides
@HealthCheckWindow @HealthCheckWindow
@HealthCheckScoped @HealthCheckScoped
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) { static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) {
return compBuilder.vault(vault).window(window).build().keyloadingStrategy(); return compFactory.create(vault, window).keyloadingStrategy();
} }
@Provides @Provides
@@ -3,11 +3,8 @@ package org.cryptomator.ui.keyloading;
import dagger.BindsInstance; import dagger.BindsInstance;
import dagger.Subcomponent; import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.util.Map;
import java.util.function.Supplier;
@KeyLoadingScoped @KeyLoadingScoped
@Subcomponent(modules = {KeyLoadingModule.class}) @Subcomponent(modules = {KeyLoadingModule.class})
@@ -16,16 +13,10 @@ public interface KeyLoadingComponent {
@KeyLoading @KeyLoading
KeyLoadingStrategy keyloadingStrategy(); KeyLoadingStrategy keyloadingStrategy();
@Subcomponent.Builder @Subcomponent.Factory
interface Builder { interface Factory {
@BindsInstance KeyLoadingComponent create(@BindsInstance @KeyLoading Vault vault, @KeyLoading @BindsInstance Stage window);
Builder vault(@KeyLoading Vault vault);
@BindsInstance
Builder window(@KeyLoading Stage window);
KeyLoadingComponent build();
} }
} }
@@ -66,6 +66,13 @@ public abstract class HubKeyLoadingModule {
return new AtomicReference<>(); return new AtomicReference<>();
} }
@Provides
@Named("filesystemOwnerId")
@KeyLoadingScoped
static AtomicReference<String> provideFilesystemOwnerIdRef() {
return new AtomicReference<>();
}
@Provides @Provides
@KeyLoadingScoped @KeyLoadingScoped
static CompletableFuture<ReceivedKey> provideResult() { static CompletableFuture<ReceivedKey> provideResult() {
@@ -2,6 +2,7 @@ package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.FilesystemOwnerSupplier;
import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.keychain.NoKeychainAccessProviderException; import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
import org.cryptomator.common.settings.DeviceKey; import org.cryptomator.common.settings.DeviceKey;
@@ -23,25 +24,28 @@ import java.net.URI;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoading @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_HTTP = SCHEME_PREFIX + "http";
public static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https"; public static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
private final Stage window; private final Stage window;
private final KeychainManager keychainManager; private final KeychainManager keychainManager;
private final AtomicReference<String> fsOwnerId;
private final Lazy<Scene> authFlowScene; private final Lazy<Scene> authFlowScene;
private final Lazy<Scene> noKeychainScene; private final Lazy<Scene> noKeychainScene;
private final CompletableFuture<ReceivedKey> result; private final CompletableFuture<ReceivedKey> result;
private final DeviceKey deviceKey; private final DeviceKey deviceKey;
@Inject @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.window = window;
this.keychainManager = keychainManager; this.keychainManager = keychainManager;
this.fsOwnerId = fsOwnerId;
window.setTitle(windowTitle); window.setTitle(windowTitle);
window.setOnCloseRequest(_ -> result.cancel(true)); window.setOnCloseRequest(_ -> result.cancel(true));
this.authFlowScene = authFlowScene; 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;
}
} }
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWEObject;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.Constants;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
@@ -41,7 +42,6 @@ import java.util.concurrent.atomic.AtomicReference;
public class ReceiveKeyController implements FxController { public class ReceiveKeyController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ReceiveKeyController.class); 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 ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10); private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10);
@@ -50,6 +50,7 @@ public class ReceiveKeyController implements FxController {
private final String vaultId; private final String vaultId;
private final String deviceId; private final String deviceId;
private final String bearerToken; private final String bearerToken;
private final AtomicReference<String> fsOwnerId;
private final CompletableFuture<ReceivedKey> result; private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> registerDeviceScene; private final Lazy<Scene> registerDeviceScene;
private final Lazy<Scene> legacyRegisterDeviceScene; private final Lazy<Scene> legacyRegisterDeviceScene;
@@ -59,12 +60,25 @@ public class ReceiveKeyController implements FxController {
private final HttpClient httpClient; private final HttpClient httpClient;
@Inject @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) {
this.window = window; this.window = window;
this.hubConfig = hubConfig; this.hubConfig = hubConfig;
this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs) this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs)
this.deviceId = deviceId; this.deviceId = deviceId;
this.bearerToken = Objects.requireNonNull(tokenRef.get()); this.bearerToken = Objects.requireNonNull(tokenRef.get());
this.fsOwnerId = fsOwnerId;
this.result = result; this.result = result;
this.registerDeviceScene = registerDeviceScene; this.registerDeviceScene = registerDeviceScene;
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene; this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
@@ -81,7 +95,34 @@ public class ReceiveKeyController implements FxController {
} }
public void receiveKey() { 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 +185,7 @@ public class ReceiveKeyController implements FxController {
switch (response.statusCode()) { switch (response.statusCode()) {
case 200 -> { case 200 -> {
var device = JSON.reader().readValue(response.body(), DeviceDto.class); 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); requestVaultMasterkey(device.userPrivateKey);
} }
case 404 -> Platform.runLater(this::needsDeviceRegistration); case 404 -> Platform.runLater(this::needsDeviceRegistration);
@@ -289,13 +331,16 @@ public class ReceiveKeyController implements FxController {
} }
private static String extractVaultId(URI vaultKeyUri) { private static String extractVaultId(URI vaultKeyUri) {
assert vaultKeyUri.getScheme().startsWith(SCHEME_PREFIX); assert vaultKeyUri.getScheme().startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX);
var path = vaultKeyUri.getPath(); var path = vaultKeyUri.getPath();
return path.substring(path.lastIndexOf('/') + 1); return path.substring(path.lastIndexOf('/') + 1);
} }
@JsonIgnoreProperties(ignoreUnknown = true) @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) @JsonIgnoreProperties(ignoreUnknown = true)
private record ConfigDto(@JsonProperty(value = "apiLevel") int apiLevel) {} private record ConfigDto(@JsonProperty(value = "apiLevel") int apiLevel) {}
@@ -15,8 +15,8 @@ import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService; import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxFSEventList;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.FxFSEventList;
import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -139,8 +139,8 @@ public class VaultListController implements FxController {
vaultList.setItems(vaults); vaultList.setItems(vaults);
vaultList.setCellFactory(cellFactory); vaultList.setCellFactory(cellFactory);
vaultList.prefHeightProperty().bind( vaultList.prefHeightProperty().bind( //
vaultList.fixedCellSizeProperty().multiply(Bindings.size(vaultList.getItems())) vaultList.fixedCellSizeProperty().multiply(Bindings.size(vaultList.getItems())) //
); );
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty()); selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
@@ -157,11 +157,11 @@ public class VaultListController implements FxController {
//unlock vault on double click //unlock vault on double click
vaultList.addEventFilter(MouseEvent.MOUSE_CLICKED, click -> { vaultList.addEventFilter(MouseEvent.MOUSE_CLICKED, click -> {
if (click.getClickCount() >= 2) { if (click.getClickCount() >= 2) {
Optional.ofNullable(selectedVault.get()) Optional.ofNullable(selectedVault.get()) //
.filter(Vault::isLocked) .filter(Vault::isLocked) //
.ifPresent(vault -> appWindows.startUnlockWorkflow(vault, mainWindow)); .ifPresent(vault -> appWindows.startUnlockWorkflow(vault, mainWindow));
Optional.ofNullable(selectedVault.get()) Optional.ofNullable(selectedVault.get()) //
.filter(Vault::isUnlocked) .filter(Vault::isUnlocked) //
.ifPresent(vaultService::reveal); .ifPresent(vaultService::reveal);
} }
}); });
@@ -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();
}
}
@@ -0,0 +1,245 @@
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("File is locked by another device");
fileName.set(cleartextFileName);
description.set("The file is opened by %s on device %s. Ask the user to close the file and sync again. Otherwise, you can ignore the lock and open it anyway.".formatted(user, device));
actionText.set("Ignore Lock");
/* TODO: Once feature is out of beta, activate translations
message.set(resourceBundle.getString("notification.inUse.message"));
description.set(resourceBundle.getString("notification.inUse.description").formatted(fiiue.cleartextPath(), 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();
}
}
@@ -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);
}
@@ -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 {}
@@ -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 {}
@@ -57,8 +57,8 @@ abstract class UnlockModule {
@Provides @Provides
@UnlockWindow @UnlockWindow
@UnlockScoped @UnlockScoped
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @UnlockWindow Vault vault, @UnlockWindow Stage window) { static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
return compBuilder.vault(vault).window(window).build().keyloadingStrategy(); return compFactory.create(vault, window).keyloadingStrategy();
} }
@Provides @Provides
+34
View File
@@ -113,6 +113,11 @@
-fx-font-size: 1.2em; -fx-font-size: 1.2em;
} }
.label-window-title {
-fx-font-family: 'Open Sans SemiBold';
-fx-font-size: 1.0em;
}
.label-small { .label-small {
-fx-font-size: 0.8em; -fx-font-size: 0.8em;
} }
@@ -1186,3 +1191,32 @@
-fx-background-color: MAIN_BG; -fx-background-color: MAIN_BG;
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */ -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;
}
+35
View File
@@ -113,6 +113,11 @@
-fx-font-size: 1.2em; -fx-font-size: 1.2em;
} }
.label-window-title {
-fx-font-family: 'Open Sans SemiBold';
-fx-font-size: 1.0em;
}
.label-small { .label-small {
-fx-font-size: 0.8em; -fx-font-size: 0.8em;
} }
@@ -1186,3 +1191,33 @@
-fx-background-color: MAIN_BG; -fx-background-color: MAIN_BG;
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */ -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;
}
@@ -39,9 +39,9 @@
<CheckBox fx:id="expertSettingsCheckBox" text="%addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox" onAction="#toggleUseExpertSettings"/> <CheckBox fx:id="expertSettingsCheckBox" text="%addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox" onAction="#toggleUseExpertSettings"/>
<VBox spacing="6" visible="${expertSettingsCheckBox.selected}"> <VBox spacing="6" visible="${expertSettingsCheckBox.selected}">
<HBox spacing="2" HBox.hgrow="NEVER"> <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"/> <Region prefWidth="2"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs"> <Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" accessibleText="%addvaultwizard.new.expertSettings.shorteningThreshold.tooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/> <FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic> </graphic>
+1 -1
View File
@@ -19,7 +19,7 @@
</padding> </padding>
<children> <children>
<VBox spacing="6"> <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"/> <NiceSecurePasswordField fx:id="oldPasswordField"/>
</VBox> </VBox>
+4 -4
View File
@@ -22,7 +22,7 @@
<Insets left="6"/> <Insets left="6"/>
</padding> </padding>
<Region HBox.hgrow="ALWAYS"/> <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> <graphic>
<FontAwesome5IconView glyph="CLIPBOARD" glyphSize="16"/> <FontAwesome5IconView glyph="CLIPBOARD" glyphSize="16"/>
</graphic> </graphic>
@@ -30,7 +30,7 @@
<Tooltip text="%decryptNames.copyTable.tooltip"/> <Tooltip text="%decryptNames.copyTable.tooltip"/>
</tooltip> </tooltip>
</Button> </Button>
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#clearTable"> <Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#clearTable" accessibleText="%decryptNames.clearTable.tooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/> <FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
</graphic> </graphic>
@@ -48,12 +48,12 @@
</Button> </Button>
</placeholder> </placeholder>
<columns> <columns>
<TableColumn fx:id="ciphertextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}"> <TableColumn fx:id="ciphertextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}" text="%decryptNames.column.encrypted">
<graphic> <graphic>
<FontAwesome5IconView glyph="LOCK"/> <FontAwesome5IconView glyph="LOCK"/>
</graphic> </graphic>
</TableColumn> </TableColumn>
<TableColumn fx:id="cleartextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}"> <TableColumn fx:id="cleartextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}" text="%decryptNames.column.decrypted">
<graphic> <graphic>
<FontAwesome5IconView glyph="LOCK_OPEN"/> <FontAwesome5IconView glyph="LOCK_OPEN"/>
</graphic> </graphic>
+2 -2
View File
@@ -79,7 +79,7 @@
<padding> <padding>
<Insets top="6" bottom="6"/> <Insets top="6" bottom="6"/>
</padding> </padding>
<Label text="%error.technicalDetails"/> <Label text="%error.technicalDetails" labelFor="$detailsTextArea"/>
<Region HBox.hgrow="ALWAYS"/> <Region HBox.hgrow="ALWAYS"/>
<Hyperlink styleClass="hyperlink-underline" text="%generic.button.copy" onAction="#copyDetails" contentDisplay="LEFT" visible="${!controller.copiedDetails}" managed="${!controller.copiedDetails}"> <Hyperlink styleClass="hyperlink-underline" text="%generic.button.copy" onAction="#copyDetails" contentDisplay="LEFT" visible="${!controller.copiedDetails}" managed="${!controller.copiedDetails}">
<graphic> <graphic>
@@ -92,7 +92,7 @@
</graphic> </graphic>
</Hyperlink> </Hyperlink>
</HBox> </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"/> <Region minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="B+C"> <ButtonBar buttonMinWidth="120" buttonOrder="B+C">
+2 -2
View File
@@ -20,9 +20,9 @@
<padding> <padding>
<Insets left="6" /> <Insets left="6" />
</padding> </padding>
<ChoiceBox fx:id="vaultFilterChoiceBox" minWidth="42"/> <ChoiceBox fx:id="vaultFilterChoiceBox" minWidth="42" accessibleText="%eventView.filterVaults"/>
<Region HBox.hgrow="ALWAYS"/> <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> <graphic>
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/> <FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
</graphic> </graphic>
+5 -1
View File
@@ -5,6 +5,7 @@
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.ContextMenu?> <?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<HBox xmlns:fx="http://javafx.com/fxml" <HBox xmlns:fx="http://javafx.com/fxml"
@@ -29,10 +30,13 @@
</HBox> </HBox>
<Label text="${controller.description}"/> <Label text="${controller.description}"/>
</VBox> </VBox>
<Button fx:id="eventActionsButton" contentDisplay="GRAPHIC_ONLY" onAction="#toggleEventActionsMenu" managed="${controller.actionsButtonVisible}" visible="${controller.actionsButtonVisible}"> <Button fx:id="eventActionsButton" contentDisplay="GRAPHIC_ONLY" onAction="#toggleEventActionsMenu" managed="${controller.actionsButtonVisible}" visible="${controller.actionsButtonVisible}" accessibleText="%eventView.cell.actionsButton.tooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="ELLIPSIS_V" glyphSize="16"/> <FontAwesome5IconView glyph="ELLIPSIS_V" glyphSize="16"/>
</graphic> </graphic>
<tooltip>
<Tooltip text="%eventView.cell.actionsButton.tooltip"/>
</tooltip>
</Button> </Button>
<VBox alignment="CENTER" maxWidth="64" minWidth="64" visible="${!controller.actionsButtonVisible}" managed="${!controller.actionsButtonVisible}"> <VBox alignment="CENTER" maxWidth="64" minWidth="64" visible="${!controller.actionsButtonVisible}" managed="${!controller.actionsButtonVisible}">
<Label text="${controller.eventLocalTime}" /> <Label text="${controller.eventLocalTime}" />
@@ -47,8 +47,8 @@
<FontAwesome5IconView glyph="FUNNEL" glyphSize="${filterLbl.height}" styleClass="glyph-icon-muted"/> <FontAwesome5IconView glyph="FUNNEL" glyphSize="${filterLbl.height}" styleClass="glyph-icon-muted"/>
</graphic> </graphic>
</Label> </Label>
<ChoiceBox fx:id="severityChoiceBox" /> <ChoiceBox fx:id="severityChoiceBox" accessibleText="%health.check.detail.filterSeverity"/>
<ChoiceBox fx:id="fixStateChoiceBox" /> <ChoiceBox fx:id="fixStateChoiceBox" accessibleText="%health.check.detail.filterFixState"/>
</HBox> </HBox>
<ListView fx:id="resultsListView" VBox.vgrow="ALWAYS" visible="${!controller.checkSkipped}" fixedCellSize="25"> <ListView fx:id="resultsListView" VBox.vgrow="ALWAYS" visible="${!controller.checkSkipped}" fixedCellSize="25">
<contextMenu> <contextMenu>
@@ -21,6 +21,6 @@
<CheckBox fx:id="checkbox" visible="${controller.checkRunnable}"/> <CheckBox fx:id="checkbox" visible="${controller.checkRunnable}"/>
<CheckStateIconView check="${controller.check}" glyphSize="20" visible="${!controller.checkRunnable}"/> <CheckStateIconView check="${controller.check}" glyphSize="20" visible="${!controller.checkRunnable}"/>
</StackPane> </StackPane>
<Label text="${controller.checkName}"/> <Label text="${controller.checkName}" labelFor="$checkbox"/>
</HBox> </HBox>
+1 -1
View File
@@ -21,7 +21,7 @@
</padding> </padding>
<children> <children>
<VBox spacing="6" visible="${!controller.vault.processing}" managed="${!controller.vault.processing}"> <VBox spacing="6" visible="${!controller.vault.processing}" managed="${!controller.vault.processing}">
<FormattedLabel format="%migration.run.enterPassword" arg1="${controller.vault.displayName}" wrapText="true"/> <FormattedLabel format="%migration.run.enterPassword" arg1="${controller.vault.displayName}" wrapText="true" labelFor="$passwordField"/>
<NiceSecurePasswordField fx:id="passwordField"/> <NiceSecurePasswordField fx:id="passwordField"/>
</VBox> </VBox>
+75
View File
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ScrollPane?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.notification.NotificationController"
prefHeight="200.0" prefWidth="400.0" maxHeight="200.0" maxWidth="400.0"
styleClass="notification-window">
<padding>
<Insets top="12" right="12" bottom="12" left="12"/>
</padding>
<top>
<VBox >
<HBox spacing="6" styleClass="dialog-header" alignment="CENTER_LEFT">
<ImageView fitHeight="12" preserveRatio="true" cache="true">
<Image url="@../img/logo64.png"/>
</ImageView>
<Label text="Cryptomator" styleClass="label-window-title"/>
<Region HBox.hgrow="ALWAYS"/>
<HBox styleClass="dialog-header" alignment="CENTER_LEFT" visible="${!controller.singleEvent}">
<Button contentDisplay="GRAPHIC_ONLY" styleClass="window-bar-button" onAction="#previousNotification" accessibleText="%generic.button.previous">
<graphic>
<FontAwesome5IconView glyph="CHEVRON_LEFT" glyphSize="12"/>
</graphic>
</Button>
<Label text="${controller.paging}" styleClass="label-window-title"/>
<Button contentDisplay="GRAPHIC_ONLY" styleClass="window-bar-button" onAction="#nextNotification" accessibleText="%generic.button.next">
<graphic>
<FontAwesome5IconView glyph="CHEVRON_RIGHT" glyphSize="12"/>
</graphic>
</Button>
</HBox>
<Button contentDisplay="GRAPHIC_ONLY" onAction="#close" styleClass="window-bar-button" accessibleText="%generic.button.close">
<graphic>
<FontAwesome5IconView glyph="TIMES" glyphSize="12"/>
</graphic>
</Button>
</HBox>
<Separator orientation="HORIZONTAL"/>
</VBox>
</top>
<center>
<VBox>
<padding>
<Insets top="6"/>
</padding>
<HBox>
<Label text="${controller.vaultName}" styleClass="label-small" wrapText="true"/>
<Region minWidth="12" HBox.hgrow="ALWAYS"/>
<Label text="${controller.eventTime}" styleClass="label-small" />
</HBox>
<Label text="${controller.message}" styleClass="label-large" wrapText="true"/>
<Label text="${controller.fileName}" styleClass="label" textOverrun="CENTER_ELLIPSIS" visible="${!controller.fileName.empty}" managed="${!controller.fileName.empty}"/>
<Region minHeight="6"/>
<ScrollPane minViewportWidth="370" minViewportHeight="50">
<Label text="${controller.description}" styleClass="label" wrapText="true" maxWidth="370"/>
</ScrollPane>
<Region VBox.vgrow="ALWAYS"/>
<Button text="${controller.actionText}" onAction="#processSelectedEvent"
visible="${!controller.actionText.empty}" managed="${!controller.actionText.empty}"/>
</VBox>
</center>
</BorderPane>
@@ -26,6 +26,6 @@
</VBox> </VBox>
</HBox> </HBox>
<TextArea text="${controller.thirdPartyLicenseText}" editable="false"/> <TextArea text="${controller.thirdPartyLicenseText}" editable="false" accessibleText="%preferences.about.thirdPartyLicenses"/>
</children> </children>
</VBox> </VBox>
@@ -7,6 +7,7 @@
<?import javafx.scene.control.Hyperlink?> <?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?> <?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.image.Image?> <?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?> <?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
@@ -32,10 +33,13 @@
<FormattedLabel format="%preferences.contribute.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/> <FormattedLabel format="%preferences.contribute.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
<Region minHeight="12"/> <Region minHeight="12"/>
<HBox alignment="BOTTOM_CENTER" spacing="6"> <HBox alignment="BOTTOM_CENTER" spacing="6">
<Button onAction="#didClickRemoveCert"> <Button onAction="#didClickRemoveCert" contentDisplay="GRAPHIC_ONLY" accessibleText="%preferences.contribute.removeCert.tooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="TRASH"/> <FontAwesome5IconView glyph="TRASH"/>
</graphic> </graphic>
<tooltip>
<Tooltip text="%preferences.contribute.removeCert.tooltip"/>
</tooltip>
</Button> </Button>
<Button text="%preferences.contribute.donate" minWidth="100" onAction="#showDonate"> <Button text="%preferences.contribute.donate" minWidth="100" onAction="#showDonate">
<graphic> <graphic>
@@ -56,7 +60,7 @@
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/> <FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>
</StackPane> </StackPane>
<VBox HBox.hgrow="ALWAYS" spacing="6"> <VBox HBox.hgrow="ALWAYS" spacing="6">
<Label text="%preferences.contribute.noCertificate" wrapText="true" VBox.vgrow="ALWAYS"/> <Label text="%preferences.contribute.noCertificate" wrapText="true" VBox.vgrow="ALWAYS" labelFor="$supporterCertificateField"/>
<Hyperlink text="%preferences.contribute.getCertificate" onAction="#getSupporterCertificate"> <Hyperlink text="%preferences.contribute.getCertificate" onAction="#getSupporterCertificate">
<graphic> <graphic>
<FontAwesome5IconView glyph="LINK"/> <FontAwesome5IconView glyph="LINK"/>
@@ -27,12 +27,12 @@
<HBox spacing="12" alignment="CENTER_LEFT"> <HBox spacing="12" alignment="CENTER_LEFT">
<CheckBox fx:id="useKeychainCheckbox" text="%preferences.general.keychainBackend"/> <CheckBox fx:id="useKeychainCheckbox" text="%preferences.general.keychainBackend"/>
<ChoiceBox fx:id="keychainBackendChoiceBox"/> <ChoiceBox fx:id="keychainBackendChoiceBox" accessibleText="%preferences.general.keychainBackend"/>
</HBox> </HBox>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.someQuickAccessServiceAvailable}" managed="${controller.someQuickAccessServiceAvailable}"> <HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.someQuickAccessServiceAvailable}" managed="${controller.someQuickAccessServiceAvailable}">
<CheckBox fx:id="useQuickAccessCheckbox" text="%preferences.general.quickAccessService"/> <CheckBox fx:id="useQuickAccessCheckbox" text="%preferences.general.quickAccessService"/>
<ChoiceBox fx:id="quickAccessServiceChoiceBox"/> <ChoiceBox fx:id="quickAccessServiceChoiceBox" accessibleText="%preferences.general.quickAccessService"/>
</HBox> </HBox>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
@@ -21,13 +21,13 @@
</padding> </padding>
<children> <children>
<HBox spacing="12" alignment="CENTER_LEFT"> <HBox spacing="12" alignment="CENTER_LEFT">
<Label text="%preferences.interface.theme"/> <Label text="%preferences.interface.theme" labelFor="$themeChoiceBox"/>
<ChoiceBox fx:id="themeChoiceBox" disable="${!controller.licenseHolder.validLicense}"/> <ChoiceBox fx:id="themeChoiceBox" disable="${!controller.licenseHolder.validLicense}"/>
<Hyperlink styleClass="hyperlink-underline,hyperlink-muted" text="%preferences.interface.unlockThemes" onAction="#showContributeTab" visible="${!controller.licenseHolder.validLicense}" managed="${!controller.licenseHolder.validLicense}"/> <Hyperlink styleClass="hyperlink-underline,hyperlink-muted" text="%preferences.interface.unlockThemes" onAction="#showContributeTab" visible="${!controller.licenseHolder.validLicense}" managed="${!controller.licenseHolder.validLicense}"/>
</HBox> </HBox>
<HBox spacing="12" alignment="CENTER_LEFT"> <HBox spacing="12" alignment="CENTER_LEFT">
<Label text="%preferences.interface.language"/> <Label text="%preferences.interface.language" labelFor="$preferredLanguageChoiceBox"/>
<ChoiceBox fx:id="preferredLanguageChoiceBox"/> <ChoiceBox fx:id="preferredLanguageChoiceBox"/>
</HBox> </HBox>
@@ -20,9 +20,9 @@
</padding> </padding>
<children> <children>
<HBox spacing="12" alignment="CENTER_LEFT"> <HBox spacing="12" alignment="CENTER_LEFT">
<Label text="%preferences.volume.type"/> <Label text="%preferences.volume.type" labelFor="$volumeTypeChoiceBox"/>
<ChoiceBox fx:id="volumeTypeChoiceBox"/> <ChoiceBox fx:id="volumeTypeChoiceBox"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs"> <Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" accessibleText="%preferences.volume.docsTooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/> <FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic> </graphic>
@@ -33,7 +33,7 @@
</HBox> </HBox>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}" managed="${controller.loopbackPortSupported}"> <HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}" managed="${controller.loopbackPortSupported}">
<Label text="%preferences.volume.tcp.port"/> <Label text="%preferences.volume.tcp.port" labelFor="$loopbackPortField"/>
<NumericTextField fx:id="loopbackPortField"/> <NumericTextField fx:id="loopbackPortField"/>
<Button text="%generic.button.apply" fx:id="loopbackPortApplyButton" onAction="#doChangeLoopbackPort"/> <Button text="%generic.button.apply" fx:id="loopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox> </HBox>
@@ -40,7 +40,7 @@
<Insets bottom="6" top="6"/> <Insets bottom="6" top="6"/>
</padding> </padding>
</Label> </Label>
<FormattedLabel fx:id="descriptionLabel" format="%recoveryKey.create.description" arg1="${controller.vault.displayName}" wrapText="true"> <FormattedLabel fx:id="descriptionLabel" format="%recoveryKey.create.description" arg1="${controller.vault.displayName}" wrapText="true" labelFor="$passwordField">
<padding> <padding>
<Insets bottom="6"/> <Insets bottom="6"/>
</padding> </padding>
@@ -16,7 +16,7 @@
spacing="12" spacing="12"
alignment="TOP_LEFT"> alignment="TOP_LEFT">
<children> <children>
<FormattedLabel format="%recoveryKey.display.description" arg1="${controller.vaultName}" wrapText="true"/> <FormattedLabel format="%recoveryKey.display.description" arg1="${controller.vaultName}" wrapText="true" labelFor="$textarea"/>
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true" prefRowCount="4" fx:id="textarea"/> <TextArea editable="false" text="${controller.recoveryKey}" wrapText="true" prefRowCount="4" fx:id="textarea"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+R"> <ButtonBar buttonMinWidth="120" buttonOrder="+R">
@@ -48,7 +48,7 @@
<HBox spacing="2" HBox.hgrow="NEVER"> <HBox spacing="2" HBox.hgrow="NEVER">
<Label text="%addvaultwizard.new.expertSettings.shorteningThreshold.title"/> <Label text="%addvaultwizard.new.expertSettings.shorteningThreshold.title"/>
<Region prefWidth="2"/> <Region prefWidth="2"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs"> <Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" accessibleText="%addvaultwizard.new.expertSettings.shorteningThreshold.tooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/> <FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic> </graphic>
@@ -57,7 +57,7 @@
</tooltip> </tooltip>
</Hyperlink> </Hyperlink>
</HBox> </HBox>
<Label text="%recover.expertSettings.shorteningThreshold.title" wrapText="true"/> <Label text="%recover.expertSettings.shorteningThreshold.title" wrapText="true" labelFor="$shorteningThresholdTextField"/>
<NumericTextField fx:id="shorteningThresholdTextField"/> <NumericTextField fx:id="shorteningThresholdTextField"/>
<HBox alignment="TOP_RIGHT"> <HBox alignment="TOP_RIGHT">
<Region minWidth="4" prefWidth="4" HBox.hgrow="NEVER"/> <Region minWidth="4" prefWidth="4" HBox.hgrow="NEVER"/>
@@ -12,7 +12,7 @@
minHeight="145" minHeight="145"
spacing="12"> spacing="12">
<children> <children>
<FormattedLabel format="%recoveryKey.recover.prompt" arg1="${controller.vault.displayName}" wrapText="true"/> <FormattedLabel format="%recoveryKey.recover.prompt" arg1="${controller.vault.displayName}" wrapText="true" labelFor="$textarea"/>
<TextArea wrapText="true" prefRowCount="4" fx:id="textarea" textFormatter="${controller.recoveryKeyTextFormatter}" onKeyPressed="#onKeyPressed"/> <TextArea wrapText="true" prefRowCount="4" fx:id="textarea" textFormatter="${controller.recoveryKeyTextFormatter}" onKeyPressed="#onKeyPressed"/>
<VBox> <VBox>
+1 -1
View File
@@ -68,7 +68,7 @@
<TextFlow styleClass="text-flow"> <TextFlow styleClass="text-flow">
<Text text="%shareVault.remarkBestPractices"/> <Text text="%shareVault.remarkBestPractices"/>
<Text text=" "/> <Text text=" "/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#visitBestPractices"> <Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#visitBestPractices" accessibleText="%shareVault.docsTooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/> <FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic> </graphic>
@@ -46,7 +46,7 @@
</ImageView> </ImageView>
</StackPane> </StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS"> <VBox spacing="6" HBox.hgrow="ALWAYS">
<FormattedLabel format="%unlock.passwordPrompt" arg1="${controller.vaultName}" wrapText="true"/> <FormattedLabel format="%unlock.passwordPrompt" arg1="${controller.vaultName}" wrapText="true" labelFor="$passwordField"/>
<NiceSecurePasswordField fx:id="passwordField" disable="${controller.userInteractionDisabled}"/> <NiceSecurePasswordField fx:id="passwordField" disable="${controller.userInteractionDisabled}"/>
<CheckBox fx:id="savePasswordCheckbox" text="%unlock.savePassword" onAction="#didClickSavePasswordCheckbox" disable="${controller.userInteractionDisabled}" visible="${controller.keychainAccessAvailable}"/> <CheckBox fx:id="savePasswordCheckbox" text="%unlock.savePassword" onAction="#didClickSavePasswordCheckbox" disable="${controller.userInteractionDisabled}" visible="${controller.keychainAccessAvailable}"/>
</VBox> </VBox>
@@ -15,7 +15,7 @@
alignment="TOP_CENTER" alignment="TOP_CENTER"
spacing="9"> spacing="9">
<Label text="%main.vaultDetail.accessLocation"/> <Label text="%main.vaultDetail.accessLocation"/>
<Button styleClass="button-large" contentDisplay="GRAPHIC_ONLY" minWidth="120" onAction="#revealAccessLocation" defaultButton="${controller.accessibleViaPath}" visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}"> <Button styleClass="button-large" contentDisplay="GRAPHIC_ONLY" minWidth="120" onAction="#revealAccessLocation" defaultButton="${controller.accessibleViaPath}" visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}" accessibleText="%main.vaultDetail.revealBtn">
<graphic> <graphic>
<HBox spacing="12" alignment="CENTER"> <HBox spacing="12" alignment="CENTER">
<FontAwesome5IconView glyph="HDD" glyphSize="24"/> <FontAwesome5IconView glyph="HDD" glyphSize="24"/>
@@ -25,8 +25,11 @@
</VBox> </VBox>
</HBox> </HBox>
</graphic> </graphic>
<tooltip>
<Tooltip text="%main.vaultDetail.revealBtn"/>
</tooltip>
</Button> </Button>
<Button styleClass="button-large" contentDisplay="GRAPHIC_ONLY" minWidth="120" onAction="#copyMountUri" defaultButton="${controller.accessibleViaUri}" visible="${controller.accessibleViaUri}" managed="${controller.accessibleViaUri}"> <Button styleClass="button-large" contentDisplay="GRAPHIC_ONLY" minWidth="120" onAction="#copyMountUri" defaultButton="${controller.accessibleViaUri}" visible="${controller.accessibleViaUri}" managed="${controller.accessibleViaUri}" accessibleText="%main.vaultDetail.copyUri">
<graphic> <graphic>
<HBox spacing="12" alignment="CENTER"> <HBox spacing="12" alignment="CENTER">
<FontAwesome5IconView glyph="LINK" glyphSize="24"/> <FontAwesome5IconView glyph="LINK" glyphSize="24"/>
@@ -36,6 +39,9 @@
</VBox> </VBox>
</HBox> </HBox>
</graphic> </graphic>
<tooltip>
<Tooltip text="%main.vaultDetail.copyUri"/>
</tooltip>
</Button> </Button>
<Button text="%main.vaultDetail.lockBtn" minWidth="120" onAction="#lock"> <Button text="%main.vaultDetail.lockBtn" minWidth="120" onAction="#lock">
<graphic> <graphic>
+9 -3
View File
@@ -38,14 +38,17 @@
</VBox> </VBox>
</StackPane> </StackPane>
<HBox styleClass="button-bar"> <HBox styleClass="button-bar">
<Button fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY"> <Button fx:id="addVaultButton" onAction="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY" accessibleText="%main.vaultlist.addVaultButton.tooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="PLUS" glyphSize="16"/> <FontAwesome5IconView glyph="PLUS" glyphSize="16"/>
</graphic> </graphic>
<tooltip>
<Tooltip text="%main.vaultlist.addVaultButton.tooltip"/>
</tooltip>
</Button> </Button>
<Region HBox.hgrow="ALWAYS"/> <Region HBox.hgrow="ALWAYS"/>
<StackPane> <StackPane>
<Button onMouseClicked="#showEventViewer" styleClass="button-right" minWidth="20" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false"> <Button onAction="#showEventViewer" styleClass="button-right" minWidth="20" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" accessibleText="%main.vaultlist.showEventsButton.tooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="BELL" glyphSize="16"/> <FontAwesome5IconView glyph="BELL" glyphSize="16"/>
</graphic> </graphic>
@@ -57,10 +60,13 @@
<Circle radius="4" styleClass="icon-update-indicator" AnchorPane.topAnchor="-8" AnchorPane.rightAnchor="-6" visible="${controller.unreadEventsPresent}" /> <Circle radius="4" styleClass="icon-update-indicator" AnchorPane.topAnchor="-8" AnchorPane.rightAnchor="-6" visible="${controller.unreadEventsPresent}" />
</AnchorPane> </AnchorPane>
</StackPane> </StackPane>
<Button onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY"> <Button onAction="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY" accessibleText="%main.vaultlist.showPreferencesButton.tooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="COG" glyphSize="16"/> <FontAwesome5IconView glyph="COG" glyphSize="16"/>
</graphic> </graphic>
<tooltip>
<Tooltip text="%main.vaultlist.showPreferencesButton.tooltip"/>
</tooltip>
</Button> </Button>
</HBox> </HBox>
</VBox> </VBox>
@@ -22,14 +22,14 @@
</padding> </padding>
<children> <children>
<HBox spacing="6" alignment="CENTER_LEFT"> <HBox spacing="6" alignment="CENTER_LEFT">
<Label text="%vaultOptions.general.vaultName"/> <Label text="%vaultOptions.general.vaultName" labelFor="$vaultName"/>
<TextField fx:id="vaultName"/> <TextField fx:id="vaultName"/>
</HBox> </HBox>
<TextFlow styleClass="text-flow" prefWidth="-Infinity"> <TextFlow styleClass="text-flow" prefWidth="-Infinity">
<CheckBox text="%vaultOptions.general.autoLock.lockAfterTimePart1" fx:id="lockAfterTimeCheckbox"/> <CheckBox text="%vaultOptions.general.autoLock.lockAfterTimePart1" fx:id="lockAfterTimeCheckbox"/>
<Text text=" "/> <Text text=" "/>
<NumericTextField fx:id="lockTimeInMinutesTextField" prefWidth="50"/> <NumericTextField fx:id="lockTimeInMinutesTextField" prefWidth="50" accessibleText="%vaultOptions.general.autoLock.accessibleText"/>
<Text text=" "/> <Text text=" "/>
<FormattedLabel format="%vaultOptions.general.autoLock.lockAfterTimePart2"/> <FormattedLabel format="%vaultOptions.general.autoLock.lockAfterTimePart2"/>
</TextFlow> </TextFlow>
@@ -37,7 +37,7 @@
<CheckBox text="%vaultOptions.general.unlockAfterStartup" fx:id="unlockOnStartupCheckbox"/> <CheckBox text="%vaultOptions.general.unlockAfterStartup" fx:id="unlockOnStartupCheckbox"/>
<HBox spacing="6" alignment="CENTER_LEFT"> <HBox spacing="6" alignment="CENTER_LEFT">
<Label text="%vaultOptions.general.actionAfterUnlock"/> <Label text="%vaultOptions.general.actionAfterUnlock" labelFor="$actionAfterUnlockChoiceBox"/>
<ChoiceBox fx:id="actionAfterUnlockChoiceBox"/> <ChoiceBox fx:id="actionAfterUnlockChoiceBox"/>
</HBox> </HBox>
@@ -26,9 +26,9 @@
</padding> </padding>
<children> <children>
<HBox spacing="12" alignment="CENTER_LEFT"> <HBox spacing="12" alignment="CENTER_LEFT">
<Label text="%vaultOptions.mount.volume.type"/> <Label text="%vaultOptions.mount.volume.type" labelFor="$vaultVolumeTypeChoiceBox"/>
<ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/> <ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}"> <Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}" accessibleText="%vaultOptions.mount.info">
<graphic> <graphic>
<FontAwesome5IconView glyph="COGS" styleClass="glyph-icon-muted"/> <FontAwesome5IconView glyph="COGS" styleClass="glyph-icon-muted"/>
</graphic> </graphic>
@@ -36,7 +36,7 @@
<Tooltip text="%vaultOptions.mount.info" showDelay="100ms"/> <Tooltip text="%vaultOptions.mount.info" showDelay="100ms"/>
</tooltip> </tooltip>
</Hyperlink> </Hyperlink>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" visible="${!controller.defaultMountServiceSelected}" managed="${!controller.defaultMountServiceSelected}"> <Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" visible="${!controller.defaultMountServiceSelected}" managed="${!controller.defaultMountServiceSelected}" accessibleText="%preferences.volume.docsTooltip">
<graphic> <graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/> <FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic> </graphic>
@@ -49,7 +49,7 @@
<Label styleClass="label-red" text="%vaultOptions.mount.volumeType.restartRequired" visible="${controller.selectedMountServiceRequiresRestart}" managed="${controller.selectedMountServiceRequiresRestart}"/> <Label styleClass="label-red" text="%vaultOptions.mount.volumeType.restartRequired" visible="${controller.selectedMountServiceRequiresRestart}" managed="${controller.selectedMountServiceRequiresRestart}"/>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortChangeable}" managed="${controller.loopbackPortChangeable}"> <HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortChangeable}" managed="${controller.loopbackPortChangeable}">
<Label text="%vaultOptions.mount.volume.tcp.port"/> <Label text="%vaultOptions.mount.volume.tcp.port" labelFor="$vaultLoopbackPortField"/>
<NumericTextField fx:id="vaultLoopbackPortField"/> <NumericTextField fx:id="vaultLoopbackPortField"/>
<Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/> <Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox> </HBox>
@@ -58,7 +58,7 @@
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}"> <VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/> <CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
<TextField fx:id="mountFlagsField" HBox.hgrow="ALWAYS" maxWidth="Infinity"> <TextField fx:id="mountFlagsField" HBox.hgrow="ALWAYS" maxWidth="Infinity" accessibleText="%vaultOptions.mount.customMountFlags">
<VBox.margin> <VBox.margin>
<Insets left="24"/> <Insets left="24"/>
</VBox.margin> </VBox.margin>
@@ -75,7 +75,7 @@
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}"> <HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/> <RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}"/> <ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}" accessibleText="%vaultOptions.mount.mountPoint.driveLetter"/>
</HBox> </HBox>
<VBox spacing="6" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}"> <VBox spacing="6" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
@@ -87,7 +87,7 @@
</graphic> </graphic>
</Button> </Button>
</HBox> </HBox>
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false"> <TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false" accessibleText="%vaultOptions.mount.mountPoint.custom">
<VBox.margin> <VBox.margin>
<Insets left="24"/> <Insets left="24"/>
</VBox.margin> </VBox.margin>
+27 -1
View File
@@ -13,6 +13,7 @@ generic.button.close=Close
generic.button.copy=Copy generic.button.copy=Copy
generic.button.copied=Copied! generic.button.copied=Copied!
generic.button.done=Done generic.button.done=Done
generic.button.previous=Previous
generic.button.next=Next generic.button.next=Next
generic.button.print=Print generic.button.print=Print
generic.button.remove=Remove generic.button.remove=Remove
@@ -258,6 +259,8 @@ health.check.detail.checkFinishedAndFound=The check finished running. Please rev
health.check.detail.checkFailed=The check exited due to an error. health.check.detail.checkFailed=The check exited due to an error.
health.check.detail.checkCancelled=The check was cancelled. health.check.detail.checkCancelled=The check was cancelled.
health.check.detail.listFilters.label=Filter health.check.detail.listFilters.label=Filter
health.check.detail.filterSeverity=Filter by severity
health.check.detail.filterFixState=Filter by fix state
health.check.detail.fixAllSpecificBtn=Fix all of type health.check.detail.fixAllSpecificBtn=Fix all of type
health.check.exportBtn=Export Report health.check.exportBtn=Export Report
## Result view ## Result view
@@ -347,6 +350,7 @@ preferences.contribute.promptText=Paste supporter certificate code here
preferences.contribute.thankYou=Thank you for supporting Cryptomator's open-source development! preferences.contribute.thankYou=Thank you for supporting Cryptomator's open-source development!
preferences.contribute.donate=Donate preferences.contribute.donate=Donate
preferences.contribute.sponsor=Sponsor preferences.contribute.sponsor=Sponsor
preferences.contribute.removeCert.tooltip=Remove certificate
### Remove License Key Dialog ### Remove License Key Dialog
removeCert.title=Remove Certificate removeCert.title=Remove Certificate
@@ -356,6 +360,7 @@ removeCert.description=Cryptomator's core features are not affected by this. Nei
## About ## About
preferences.about=About preferences.about=About
preferences.about.thirdPartyLicenses=Third-party licenses
# Vault Statistics # Vault Statistics
stats.title=Statistics for %s stats.title=Statistics for %s
@@ -405,7 +410,9 @@ main.vaultlist.contextMenu.share=Share…
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault… main.vaultlist.addVaultBtn.menuItemNew=Create New Vault…
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault… main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault…
main.vaultlist.addVaultBtn.menuItemRecover=Recover Existing Vault… main.vaultlist.addVaultBtn.menuItemRecover=Recover Existing Vault…
main.vaultlist.showEventsButton.tooltip=Open event view main.vaultlist.addVaultButton.tooltip=Add Vault
main.vaultlist.showEventsButton.tooltip=Open Event View
main.vaultlist.showPreferencesButton.tooltip=Show Preferences
##Notificaition ##Notificaition
main.notification.updateAvailable=Update is available. main.notification.updateAvailable=Update is available.
main.notification.support=Support Cryptomator. main.notification.support=Support Cryptomator.
@@ -469,6 +476,7 @@ vaultOptions.general=General
vaultOptions.general.vaultName=Vault Name vaultOptions.general.vaultName=Vault Name
vaultOptions.general.autoLock.lockAfterTimePart1=Lock when idle for vaultOptions.general.autoLock.lockAfterTimePart1=Lock when idle for
vaultOptions.general.autoLock.lockAfterTimePart2=minutes vaultOptions.general.autoLock.lockAfterTimePart2=minutes
vaultOptions.general.autoLock.accessibleText=Lock timeout in minutes
vaultOptions.general.unlockAfterStartup=Unlock vault when starting Cryptomator vaultOptions.general.unlockAfterStartup=Unlock vault when starting Cryptomator
vaultOptions.general.actionAfterUnlock=After successful unlock vaultOptions.general.actionAfterUnlock=After successful unlock
vaultOptions.general.actionAfterUnlock.ignore=Do nothing vaultOptions.general.actionAfterUnlock.ignore=Do nothing
@@ -652,6 +660,8 @@ decryptNames.filePicker.title=Select encrypted file
decryptNames.filePicker.extensionDescription=Cryptomator encrypted file decryptNames.filePicker.extensionDescription=Cryptomator encrypted file
decryptNames.copyTable.tooltip=Copy table decryptNames.copyTable.tooltip=Copy table
decryptNames.clearTable.tooltip=Clear table decryptNames.clearTable.tooltip=Clear table
decryptNames.column.encrypted=Encrypted
decryptNames.column.decrypted=Decrypted
decryptNames.copyHint=Copy cell content with %s decryptNames.copyHint=Copy cell content with %s
decryptNames.dropZone.message=Drop files or click to select decryptNames.dropZone.message=Drop files or click to select
decryptNames.dropZone.error.vaultInternalFiles=Vault internal files with no decrypt-able name selected decryptNames.dropZone.error.vaultInternalFiles=Vault internal files with no decrypt-able name selected
@@ -664,6 +674,8 @@ decryptNames.dropZone.error.generic=Failed to decrypt file names
eventView.title=Events eventView.title=Events
eventView.filter.allVaults=All eventView.filter.allVaults=All
eventView.clearListButton.tooltip=Clear list eventView.clearListButton.tooltip=Clear list
eventView.filterVaults=Filter by vault
eventView.cell.actionsButton.tooltip=Event actions
## event list entries ## event list entries
eventView.entry.vaultLocked.description=Unlock "%s" for details eventView.entry.vaultLocked.description=Unlock "%s" for details
eventView.entry.conflictResolved.message=Resolved conflict eventView.entry.conflictResolved.message=Resolved conflict
@@ -684,3 +696,17 @@ eventView.entry.brokenFileNode.message=Broken filesystem node
eventView.entry.brokenFileNode.showEncrypted=Show broken, encrypted node eventView.entry.brokenFileNode.showEncrypted=Show broken, encrypted node
eventView.entry.brokenFileNode.copyEncrypted=Copy path of broken, encrypted node eventView.entry.brokenFileNode.copyEncrypted=Copy path of broken, encrypted node
eventView.entry.brokenFileNode.copyDecrypted=Copy decrypted path eventView.entry.brokenFileNode.copyDecrypted=Copy decrypted path
eventView.entry.inUse.message=Locked File
eventView.entry.inUse.showDecrypted=Show decrypted file
eventView.entry.inUse.copyDecrypted=Copy decrypted path
eventView.entry.inUse.showEncrypted=Show encrypted file
eventView.entry.inUse.copyEncrypted=Copy encrypted path
eventView.entry.inUse.copyUserAndDevice=Copy locking user and device name
eventView.entry.inUse.ignoreLock=Ignore Lock
# Notifications
## FileIsInUse Notification
#notification.inUse.message=File is locked
#notification.inUse.description=File %s is opened by user %s (%s). Ask the user to close the file. Otherwise, you can ignore the lock and open it anyway, but be aware of the data loss risk.
#notification.inUse.action=Ignore Lock
+11 -2
View File
@@ -82,13 +82,22 @@
<!-- Vulnerable, but unused class in jetty --> <!-- Vulnerable, but unused class in jetty -->
<suppress> <suppress>
<notes><![CDATA[ <notes><![CDATA[
The project does not use the HttpURI class at all, so no decoded user data is passed to it. The project does not use the HttpURI class at all, so no decoded user data is passed to it.
See also https://github.com/jetty/jetty.project/security/advisories/GHSA-qh8g-58pp-2wxh. See also https://github.com/jetty/jetty.project/security/advisories/GHSA-qh8g-58pp-2wxh.
]]></notes> ]]></notes>
<packageUrl regex="true">^pkg:maven/org\.eclipse\.jetty/jetty-.*$</packageUrl> <packageUrl regex="true">^pkg:maven/org\.eclipse\.jetty/jetty-.*$</packageUrl>
<vulnerabilityName>CVE-2024-6763</vulnerabilityName> <vulnerabilityName>CVE-2024-6763</vulnerabilityName>
<cve>CVE-2024-6763</cve> <cve>CVE-2024-6763</cve>
</suppress> </suppress>
<!-- False positives of the flatpak sandbox -->
<suppress>
<notes><![CDATA[
file name: flatpak-update-portal-1.1.0.jar
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.purejava/flatpak-update-portal@.*$</packageUrl>
<cpe>cpe:/a:flatpak:flatpak</cpe>
</suppress>
</suppressions> </suppressions>