mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
Merge branch 'develop' into feature/hub-vault-archived-dialog
This commit is contained in:
12
.github/workflows/appimage.yml
vendored
12
.github/workflows/appimage.yml
vendored
@@ -44,9 +44,9 @@ jobs:
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
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.zsync
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: appimage-${{ matrix.appimage-suffix }}
|
||||
path: |
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
- name: Publish AppImage on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
if: github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable'
|
||||
steps:
|
||||
- name: Download AppImages
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
path: downloads/
|
||||
merge-multiple: true
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
echo "x64-sha256sum=${X64_SHA256}" >> "$GITHUB_OUTPUT"
|
||||
AARCH64_SHA256=$(sha256sum downloads/cryptomator-*-aarch64.AppImage | cut -d ' ' -f1)
|
||||
echo "aarch64-sha256sum=${AARCH64_SHA256}" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
repository: 'cryptomator/aur-bin'
|
||||
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||
|
||||
2
.github/workflows/aur.yml
vendored
2
.github/workflows/aur.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
env:
|
||||
AUR_PR_URL: tbd
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
repository: 'cryptomator/aur'
|
||||
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||
|
||||
6
.github/workflows/av-whitelist.yml
vendored
6
.github/workflows/av-whitelist.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
- name: Download file
|
||||
run: curl "${INPUT_URL}" -L -o "${{steps.extractName.outputs.fileName}}" --fail-with-body
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: ${{ steps.extractName.outputs.fileName }}
|
||||
path: ${{ steps.extractName.outputs.fileName }}
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
if: inputs.kaspersky
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: ${{ needs.download-file.outputs.fileName }}
|
||||
path: upload
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
if: inputs.avast
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: ${{ needs.download-file.outputs.fileName }}
|
||||
path: upload
|
||||
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -22,8 +22,8 @@ jobs:
|
||||
name: Compile and Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
- name: Draft a release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
draft: true
|
||||
discussion_category_name: releases
|
||||
|
||||
2
.github/workflows/check-jdk-updates.yml
vendored
2
.github/workflows/check-jdk-updates.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,2) >> "$env:GITHUB_ENV"
|
||||
shell: pwsh
|
||||
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
java-version: ${{ env.JDK_MAJOR_VERSION}}
|
||||
distribution: ${{ env.JDK_VENDOR }}
|
||||
|
||||
6
.github/workflows/debian.yml
vendored
6
.github/workflows/debian.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
env:
|
||||
INPUT_PPAVER: ${{ inputs.ppaver }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- id: deb-version
|
||||
name: Determine deb-version
|
||||
run: |
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
env:
|
||||
DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }}
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
run: |
|
||||
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: linux-deb-package
|
||||
path: |
|
||||
|
||||
2
.github/workflows/dl-stats.yml
vendored
2
.github/workflows/dl-stats.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
INTERVAL: 900
|
||||
JSON_DATA: ${{ steps.get-stats.outputs.result }}
|
||||
- name: Upload Results
|
||||
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
|
||||
uses: fjogeleit/http-request-action@c0b95d02a088b47c1f2f4db04fd8af8bd19eee54 # v1.16.6
|
||||
with:
|
||||
url: 'https://graphite-us-central1.grafana.net/metrics'
|
||||
method: 'POST'
|
||||
|
||||
2
.github/workflows/flathub.yml
vendored
2
.github/workflows/flathub.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
env:
|
||||
FLATHUB_PR_URL: tbd
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
repository: 'flathub/org.cryptomator.Cryptomator'
|
||||
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||
|
||||
4
.github/workflows/get-version.yml
vendored
4
.github/workflows/get-version.yml
vendored
@@ -35,11 +35,11 @@ jobs:
|
||||
revNum: ${{ steps.versions.outputs.revNum }}
|
||||
type: ${{ steps.versions.outputs.type}}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
8
.github/workflows/mac-dmg-x64.yml
vendored
8
.github/workflows/mac-dmg-x64.yml
vendored
@@ -47,9 +47,9 @@ jobs:
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-x64_bin-jmods.zip'
|
||||
openjfx-sha: '0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52'
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
@@ -283,7 +283,7 @@ jobs:
|
||||
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
||||
continue-on-error: true
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: dmg-${{ matrix.output-suffix }}
|
||||
path: |
|
||||
@@ -292,7 +292,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
- name: Publish dmg on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
|
||||
8
.github/workflows/mac-dmg.yml
vendored
8
.github/workflows/mac-dmg.yml
vendored
@@ -45,9 +45,9 @@ jobs:
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: '13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba'
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
@@ -282,7 +282,7 @@ jobs:
|
||||
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
||||
continue-on-error: true
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: dmg-${{ matrix.output-suffix }}
|
||||
path: |
|
||||
@@ -291,7 +291,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
- name: Publish dmg on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
|
||||
2
.github/workflows/no-response.yml
vendored
2
.github/workflows/no-response.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
with:
|
||||
days-before-stale: 14
|
||||
days-before-close: 0
|
||||
|
||||
2
.github/workflows/post-publish.yml
vendored
2
.github/workflows/post-publish.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||
- name: Publish asc on GitHub Releases
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
|
||||
4
.github/workflows/pullrequest.yml
vendored
4
.github/workflows/pullrequest.yml
vendored
@@ -16,8 +16,8 @@ jobs:
|
||||
name: Compile and Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
4
.github/workflows/release-check.yml
vendored
4
.github/workflows/release-check.yml
vendored
@@ -19,9 +19,9 @@ jobs:
|
||||
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
with:
|
||||
days-before-stale: 365
|
||||
days-before-close: 90
|
||||
|
||||
24
.github/workflows/win-exe.yml
vendored
24
.github/workflows/win-exe.yml
vendored
@@ -51,9 +51,9 @@ jobs:
|
||||
java-version: '25.0.1+8'
|
||||
java-package: 'jdk'
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ matrix.java-dist }}
|
||||
java-version: ${{ matrix.java-version }}
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
- name: Sign DLLs with Actalis CodeSigner
|
||||
if: inputs.sign || github.event_name == 'release'
|
||||
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
|
||||
uses: skymatic/workflows/.github/actions/win-sign-action@1074588008ae3326a2221ea451783280518f0366 # no specific version
|
||||
with:
|
||||
base-dir: 'appdir'
|
||||
file-extensions: 'dll,exe,ps1'
|
||||
@@ -277,7 +277,7 @@ jobs:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: msi-${{ matrix.arch }}
|
||||
path: |
|
||||
@@ -299,21 +299,21 @@ jobs:
|
||||
java-version: '24.0.1+9'
|
||||
java-package: 'jdk'
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Install wix and extensions
|
||||
run: |
|
||||
dotnet tool install --global wix --version 6.0.0
|
||||
wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global
|
||||
wix.exe extension add WixToolset.Util.wixext/6.0.0 --global
|
||||
- name: Download .msi
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: msi-${{ matrix.arch }}
|
||||
path: dist/win/bundle/resources
|
||||
- name: Strip version info from msi file name
|
||||
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
||||
with:
|
||||
distribution: ${{ matrix.java-dist }}
|
||||
java-version: ${{ matrix.java-version }}
|
||||
@@ -376,7 +376,7 @@ jobs:
|
||||
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
- name: Sign burn engine with Actalis CodeSigner
|
||||
if: inputs.sign || github.event_name == 'release'
|
||||
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
|
||||
uses: skymatic/workflows/.github/actions/win-sign-action@1074588008ae3326a2221ea451783280518f0366 # no specific version
|
||||
with:
|
||||
base-dir: 'tmp'
|
||||
file-extensions: 'exe'
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
- name: Sign installer with Actalis CodeSigner
|
||||
if: inputs.sign || github.event_name == 'release'
|
||||
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
|
||||
uses: skymatic/workflows/.github/actions/win-sign-action@1074588008ae3326a2221ea451783280518f0366 # no specific version
|
||||
with:
|
||||
base-dir: 'installer'
|
||||
file-extensions: 'exe'
|
||||
@@ -418,7 +418,7 @@ jobs:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: exe-${{ matrix.executable-suffix }}
|
||||
path: |
|
||||
@@ -436,12 +436,12 @@ jobs:
|
||||
download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }}
|
||||
steps:
|
||||
- name: Download installers
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
merge-multiple: true
|
||||
- name: Publish installers on GitHub Releases
|
||||
id: publish
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
|
||||
@@ -10,10 +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)
|
||||
|
||||
### Added
|
||||
* New Self-Update Mechanism (#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
|
||||
* Built using JDK 25 (#4031)
|
||||
* Modernized Templage for GitHub Releases
|
||||
* Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
|
||||
* Modernized Template for GitHub Releases
|
||||
1
dist/linux/appimage/build.sh
vendored
1
dist/linux/appimage/build.sh
vendored
@@ -115,6 +115,7 @@ cp ../common/org.cryptomator.Cryptomator.tray-unlocked.svg Cryptomator.AppDir/us
|
||||
cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop
|
||||
cp ../common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml
|
||||
cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml
|
||||
cp ../common/application-vnd.cryptomator.encrypted.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.encrypted.xml
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon
|
||||
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop
|
||||
|
||||
9
dist/linux/common/application-vnd.cryptomator.encrypted.xml
vendored
Normal file
9
dist/linux/common/application-vnd.cryptomator.encrypted.xml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type type="application/vnd.cryptomator.encrypted">
|
||||
<comment>Cryptomator Encrypted Data</comment>
|
||||
<glob pattern="*.c9r"/>
|
||||
<glob pattern="*.c9s"/>
|
||||
<glob pattern="*.c9u"/>
|
||||
</mime-type>
|
||||
</mime-info>
|
||||
3
dist/linux/debian/cryptomator.install
vendored
3
dist/linux/debian/cryptomator.install
vendored
@@ -6,4 +6,5 @@ common/org.cryptomator.Cryptomator.tray-unlocked.svg usr/share/icons/hicolor/sca
|
||||
common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps
|
||||
common/org.cryptomator.Cryptomator512.png usr/share/icons/hicolor/512x512/apps
|
||||
common/org.cryptomator.Cryptomator.metainfo.xml usr/share/metainfo
|
||||
common/application-vnd.cryptomator.vault.xml usr/share/mime/packages
|
||||
common/application-vnd.cryptomator.vault.xml usr/share/mime/packages
|
||||
common/application-vnd.cryptomator.encrypted.xml usr/share/mime/packages
|
||||
1
dist/linux/debian/postinst
vendored
1
dist/linux/debian/postinst
vendored
@@ -25,6 +25,7 @@ case "$1" in
|
||||
fi
|
||||
xdg-desktop-menu install --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop
|
||||
xdg-mime install /usr/share/mime/packages/application-vnd.cryptomator.vault.xml
|
||||
xdg-mime install /usr/share/mime/packages/application-vnd.cryptomator.encrypted.xml
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
|
||||
1
dist/linux/debian/prerm
vendored
1
dist/linux/debian/prerm
vendored
@@ -23,6 +23,7 @@ case "$1" in
|
||||
|
||||
xdg-desktop-menu uninstall --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop
|
||||
xdg-mime uninstall /usr/share/mime/packages/application-vnd.cryptomator.vault.xml
|
||||
xdg-mime uninstall /usr/share/mime/packages/application-vnd.cryptomator.encrypted.xml
|
||||
;;
|
||||
|
||||
failed-upgrade)
|
||||
|
||||
1
dist/mac/resources/Info.plist
vendored
1
dist/mac/resources/Info.plist
vendored
@@ -105,6 +105,7 @@
|
||||
<array>
|
||||
<string>c9r</string>
|
||||
<string>c9s</string>
|
||||
<string>c9u</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
|
||||
1
dist/win/resources/main.wxs
vendored
1
dist/win/resources/main.wxs
vendored
@@ -94,6 +94,7 @@
|
||||
<ns0:MIME ContentType="$(var.ProgIdContentType)" Default="yes"/>
|
||||
</ns0:Extension>
|
||||
<ns0:Extension Id="c9s" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
|
||||
<ns0:Extension Id="c9u" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
|
||||
</ns0:ProgId>
|
||||
</ns0:Component>
|
||||
</ns0:DirectoryRef>
|
||||
|
||||
6
pom.xml
6
pom.xml
@@ -33,11 +33,11 @@
|
||||
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.cryptofs.version>2.10.0-beta2</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.8.0-beta1</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.5.1</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.5.0-beta1</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.7.0-beta1</cryptomator.integrations.linux.version>
|
||||
<cryptomator.integrations.mac.version>1.5.0-beta3</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.7.0-beta3</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>5.1.0</cryptomator.fuse.version>
|
||||
<cryptomator.webdav.version>3.0.0</cryptomator.webdav.version>
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ch.qos.logback.classic.spi.Configurator;
|
||||
import org.cryptomator.networking.SSLContextWithPKCS12TrustStore;
|
||||
import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider;
|
||||
@@ -14,11 +13,12 @@ import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvid
|
||||
import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider;
|
||||
import org.cryptomator.networking.SSLContextWithMacKeychain;
|
||||
import org.cryptomator.networking.SSLContextProvider;
|
||||
import org.cryptomator.networking.SSLContextWithWindowsCertStore;
|
||||
import org.cryptomator.integrations.tray.TrayMenuController;
|
||||
import org.cryptomator.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;
|
||||
|
||||
open module org.cryptomator.desktop {
|
||||
|
||||
@@ -13,5 +13,7 @@ public interface Constants {
|
||||
String CRYPTOMATOR_FILENAME_GLOB = "*.cryptomator";
|
||||
URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME);
|
||||
byte[] PEPPER = new byte[0];
|
||||
// Separator used to concatenate Hub username and device name in the filesystem owner identifier.
|
||||
String HUB_USER_DEVICE_SEPARATOR = "&";
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
var builder = mountService.forFileSystem(cryptoFsRoot);
|
||||
LOG.debug("Using mount service {} for mounting vault {}", mountService.getClass().getName(), vaultSettings.displayName);
|
||||
var internal = new SettledMounter(mountService, builder, vaultSettings); // FIXME: no need for an inner class
|
||||
var cleanup = internal.prepare();
|
||||
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);
|
||||
|
||||
@@ -10,7 +10,7 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.common.FilesystemOwnerSupplier;
|
||||
import org.cryptomator.common.mount.Mounter;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
@@ -23,6 +23,8 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.event.NotificationManager;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.integrations.mount.Mountpoint;
|
||||
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||
@@ -78,6 +80,7 @@ public class Vault {
|
||||
private final Mounter mounter;
|
||||
private final Settings settings;
|
||||
private final FileSystemEventAggregator fileSystemEventAggregator;
|
||||
private final NotificationManager notificationManager;
|
||||
private final BooleanProperty showingStats;
|
||||
|
||||
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
||||
@@ -90,7 +93,8 @@ public class Vault {
|
||||
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
|
||||
VaultStats stats, //
|
||||
Mounter mounter, Settings settings, //
|
||||
FileSystemEventAggregator fileSystemEventAggregator) {
|
||||
FileSystemEventAggregator fileSystemEventAggregator, //
|
||||
NotificationManager notificationManager) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.configCache = configCache;
|
||||
this.cryptoFileSystem = cryptoFileSystem;
|
||||
@@ -109,6 +113,7 @@ public class Vault {
|
||||
this.mounter = mounter;
|
||||
this.settings = settings;
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.notificationManager = notificationManager;
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
this.quickAccessEntry = new AtomicReference<>(null);
|
||||
}
|
||||
@@ -145,14 +150,17 @@ public class Vault {
|
||||
LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength.get());
|
||||
}
|
||||
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||
var fsPropsBuilder = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||
.withKeyLoader(keyLoader) //
|
||||
.withFlags(flags) //
|
||||
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
|
||||
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
|
||||
.withFilesystemEventConsumer(this::consumeVaultEvent) //
|
||||
.build();
|
||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||
.withFilesystemEventConsumer(this::consumeVaultEvent);
|
||||
if (keyLoader instanceof FilesystemOwnerSupplier oo) {
|
||||
fsPropsBuilder.withOwnerGetter(oo::getOwner);
|
||||
}
|
||||
|
||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsPropsBuilder.build());
|
||||
}
|
||||
|
||||
private void destroyCryptoFileSystem() {
|
||||
@@ -262,6 +270,7 @@ public class Vault {
|
||||
|
||||
private void consumeVaultEvent(FilesystemEvent e) {
|
||||
fileSystemEventAggregator.put(this, e);
|
||||
notificationManager.offer(this, e);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
|
||||
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.FileIsInUseEvent;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -101,6 +102,7 @@ public class FileSystemEventAggregator {
|
||||
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
|
||||
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
|
||||
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
|
||||
case FileIsInUseEvent(_, _, Path ciphertext, _, _, _) -> ciphertext;
|
||||
};
|
||||
return new FSEventBucket(v, p, event.getClass());
|
||||
}
|
||||
|
||||
85
src/main/java/org/cryptomator/event/NotificationManager.java
Normal file
85
src/main/java/org/cryptomator/event/NotificationManager.java
Normal file
@@ -0,0 +1,85 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FileIsInUseEvent;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Manager for notifications.
|
||||
* <p>
|
||||
* To add (filesystem) events, use method {@link #offer(Vault, FilesystemEvent)}. If the input event is eligible, it is added to an internal queue.
|
||||
* An event is eligible, if
|
||||
* <ul>
|
||||
* <li>the event should trigger a notification and</li>
|
||||
* <li>it is not added within the last {@value DEBOUNCE_THRESHOLD_SECONDS} seconds</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see org.cryptomator.ui.fxapp.FxNotificationManager
|
||||
*/
|
||||
@Singleton
|
||||
public class NotificationManager {
|
||||
|
||||
private static final int DEBOUNCE_THRESHOLD_SECONDS = 5;
|
||||
|
||||
private final Cache<FSEventBucket, FilesystemEvent> debounceCache;
|
||||
private final ConcurrentLinkedQueue<VaultEvent> pendingEvents;
|
||||
|
||||
@Inject
|
||||
public NotificationManager() {
|
||||
debounceCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(DEBOUNCE_THRESHOLD_SECONDS)).build();
|
||||
pendingEvents = new ConcurrentLinkedQueue<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers the given filesystem event to the notification manager.
|
||||
*
|
||||
* @param v The vault where the filesystem event happened
|
||||
* @param e the actual filesystem event
|
||||
* @return {@code true} if the filesystem event is accepted, otherwise {@code false}.
|
||||
*/
|
||||
public boolean offer(Vault v, FilesystemEvent e) {
|
||||
return switch (e) {
|
||||
case FileIsInUseEvent fiiue -> addEvent(v, fiiue.ciphertextPath(), fiiue);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
boolean addEvent(Vault v, Path keyPath, FilesystemEvent e) {
|
||||
var key = new FSEventBucket(v, keyPath, e.getClass());
|
||||
var isAdded = new AtomicBoolean(false);
|
||||
debounceCache.asMap().computeIfAbsent(key, _ -> {
|
||||
synchronized (this) {
|
||||
pendingEvents.add(new VaultEvent(v, e));
|
||||
isAdded.set(true);
|
||||
}
|
||||
return e;
|
||||
});
|
||||
return isAdded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all events to the target list and clears afterward the pending-event-queue
|
||||
*
|
||||
* @param target list where the filesystem events are copied to
|
||||
* @return {@code true}, if elements were copied
|
||||
*/
|
||||
public boolean appendToAndClear(List<VaultEvent> target) {
|
||||
//it is not clear, if addAll iterates thread-safe over the pendingEvents
|
||||
//hence we synchronize moving (copy then clear) and adding-single-element operations
|
||||
synchronized (this) {
|
||||
var result = target.addAll(pendingEvents);
|
||||
pendingEvents.clear();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,25 +3,6 @@ package org.cryptomator.event;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import java.time.Instant;
|
||||
public record VaultEvent(Vault v, FilesystemEvent actualEvent) {
|
||||
|
||||
public record VaultEvent(Vault v, FilesystemEvent actualEvent, int count) implements Comparable<VaultEvent> {
|
||||
|
||||
public VaultEvent(Vault v, FilesystemEvent actualEvent) {
|
||||
this(v, actualEvent, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(VaultEvent other) {
|
||||
var timeResult = actualEvent.getTimestamp().compareTo(other.actualEvent().getTimestamp());
|
||||
if(timeResult != 0) {
|
||||
return timeResult;
|
||||
} else {
|
||||
return this.equals(other) ? 0 : this.actualEvent.getClass().getName().compareTo(other.actualEvent.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public VaultEvent incrementCount(FilesystemEvent update) {
|
||||
return new VaultEvent(v, update, count+1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public enum FxmlFile {
|
||||
MIGRATION_RUN("/fxml/migration_run.fxml"), //
|
||||
MIGRATION_START("/fxml/migration_start.fxml"), //
|
||||
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
|
||||
NOTIFICATION("/fxml/notification.fxml"), //
|
||||
PREFERENCES("/fxml/preferences.fxml"), //
|
||||
QUIT("/fxml/quit.fxml"), //
|
||||
QUIT_FORCED("/fxml/quit_forced.fxml"), //
|
||||
@@ -60,6 +61,7 @@ public enum FxmlFile {
|
||||
VAULT_STATISTICS("/fxml/stats.fxml"), //
|
||||
WRONGFILEALERT("/fxml/wrongfilealert.fxml");
|
||||
|
||||
|
||||
private final String ressourcePathString;
|
||||
|
||||
FxmlFile(String ressourcePathString) {
|
||||
|
||||
56
src/main/java/org/cryptomator/ui/common/SystemBarUtil.java
Normal file
56
src/main/java/org/cryptomator/ui/common/SystemBarUtil.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import javafx.stage.Screen;
|
||||
|
||||
/**
|
||||
* Utility class providing methods regarding the OS bar.
|
||||
*/
|
||||
public class SystemBarUtil {
|
||||
|
||||
public enum Placement {
|
||||
/**
|
||||
* OS Bar placed at the left screen edge
|
||||
*/
|
||||
LEFT,
|
||||
/**
|
||||
* OS Bar placed at the top screen edge
|
||||
*/
|
||||
TOP,
|
||||
/**
|
||||
* OS Bar placed at the right screen edge
|
||||
*/
|
||||
RIGHT,
|
||||
/**
|
||||
* OS Bar placed at the bottom screen edge
|
||||
*/
|
||||
BOTTOM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the placement of the OS bar on the given screen.
|
||||
* <p>
|
||||
* <b>Assuming the OS bar fills one screen edge completely</b>,
|
||||
* this method determines that screen edge by comparing the actual screen bounds with the visual ones.
|
||||
* <p>
|
||||
* If the screen does not have a system bar, the bottom placement is returned.
|
||||
* If the screen does have multiple system bars, the first in following priority is returned:
|
||||
* LEFT, TOP, RIGHT, BOTTOM.
|
||||
*
|
||||
* @param screen a {@link Screen} where an OS bar exists
|
||||
* @return {@link Placement} indicating the screen edge.
|
||||
*/
|
||||
public static Placement getPlacementOfSystembar(Screen screen) {
|
||||
var bounds = screen.getBounds();
|
||||
var vBounds = screen.getVisualBounds();
|
||||
//assumption: the system bar fills a whole screen side
|
||||
if (bounds.getMinX() != vBounds.getMinX()) {
|
||||
return Placement.LEFT;
|
||||
} else if (bounds.getMinY() != vBounds.getMinY()) {
|
||||
return Placement.TOP;
|
||||
} else if (bounds.getMaxX() != vBounds.getMaxX()) {
|
||||
return Placement.RIGHT;
|
||||
} else {
|
||||
return Placement.BOTTOM;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ public enum FontAwesome5Icon {
|
||||
CARET_DOWN("\uF0D7"), //
|
||||
CARET_RIGHT("\uF0Da"), //
|
||||
CHECK("\uF00C"), //
|
||||
CHEVRON_LEFT("\uF053"), //
|
||||
CHEVRON_RIGHT("\uF054"), //
|
||||
CLOCK("\uF017"), //
|
||||
CLIPBOARD("\uF328"), //
|
||||
COG("\uF013"), //
|
||||
@@ -60,6 +62,7 @@ public enum FontAwesome5Icon {
|
||||
TRASH("\uF1F8"), //
|
||||
UNLINK("\uf127"), //
|
||||
USER_COG("\uf4fe"), //
|
||||
USER_LOCK("\uf502"), //
|
||||
WRENCH("\uF0AD"), //
|
||||
WINDOW_MINIMIZE("\uF2D1"), //
|
||||
;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.cryptofs.event.FileIsInUseEvent;
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
@@ -115,7 +118,7 @@ public class EventListCellController implements FxController {
|
||||
eventActionsMenu.hide();
|
||||
eventActionsMenu.getItems().clear();
|
||||
eventTooltip.setText(item.getKey().vault().getDisplayName());
|
||||
addAction("generic.action.dismiss", () -> {
|
||||
addLocalizedAction("generic.action.dismiss", () -> {
|
||||
fileSystemEventAggregator.remove(item.getKey());
|
||||
});
|
||||
switch (item.getValue().mostRecentEvent()) {
|
||||
@@ -124,20 +127,41 @@ public class EventListCellController implements FxController {
|
||||
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
|
||||
case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse);
|
||||
case BrokenFileNodeEvent fse -> this.adjustToBrokenFileNodeEvent(fse);
|
||||
case FileIsInUseEvent fse -> this.adjustToFileInUseEvent(fse);
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToFileInUseEvent(FileIsInUseEvent fiiue) {
|
||||
eventIcon.setValue(FontAwesome5Icon.USER_LOCK);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.inUse.message"));
|
||||
var indexFileName = fiiue.cleartextPath().lastIndexOf("/");
|
||||
eventDescription.setValue(fiiue.cleartextPath().substring(indexFileName + 1));
|
||||
if (revealService != null) {
|
||||
addLocalizedAction("eventView.entry.inUse.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(fiiue.cleartextPath())));
|
||||
addLocalizedAction("eventView.entry.inUse.showEncrypted", () -> reveal(revealService, fiiue.ciphertextPath()));
|
||||
} else {
|
||||
addLocalizedAction("eventView.entry.inUse.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(fiiue.cleartextPath()).toString()));
|
||||
addLocalizedAction("eventView.entry.inUse.copyEncrypted", () -> copyToClipboard(fiiue.ciphertextPath().toString()));
|
||||
}
|
||||
|
||||
var userAndDevice = fiiue.owner().split(Constants.HUB_USER_DEVICE_SEPARATOR);
|
||||
var user = userAndDevice[0];
|
||||
var device = userAndDevice.length == 1 ? userAndDevice[0] : userAndDevice[1];
|
||||
addLocalizedAction("eventView.entry.inUse.copyUserAndDevice", () -> copyToClipboard(user + ", " + device));
|
||||
addLocalizedAction("eventView.entry.inUse.ignoreLock", fiiue.ignoreMethod());
|
||||
}
|
||||
|
||||
|
||||
private void adjustToBrokenFileNodeEvent(BrokenFileNodeEvent bfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message"));
|
||||
eventDescription.setValue(bfe.ciphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, bfe.ciphertextPath()));
|
||||
addLocalizedAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, bfe.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(bfe.ciphertextPath().toString()));
|
||||
addLocalizedAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(bfe.ciphertextPath().toString()));
|
||||
}
|
||||
addAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
|
||||
addLocalizedAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
|
||||
}
|
||||
|
||||
private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
|
||||
@@ -145,9 +169,9 @@ public class EventListCellController implements FxController {
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message"));
|
||||
eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
|
||||
addLocalizedAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
|
||||
} else {
|
||||
addAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
|
||||
addLocalizedAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,11 +180,11 @@ public class EventListCellController implements FxController {
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflict.message"));
|
||||
eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
|
||||
addAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath()));
|
||||
addLocalizedAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
|
||||
addLocalizedAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
|
||||
addAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString()));
|
||||
addLocalizedAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
|
||||
addLocalizedAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,9 +193,9 @@ public class EventListCellController implements FxController {
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.decryptionFailed.message"));
|
||||
eventDescription.setValue(dfe.ciphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
|
||||
addLocalizedAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
|
||||
addLocalizedAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,14 +204,19 @@ public class EventListCellController implements FxController {
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenDirFile.message"));
|
||||
eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
|
||||
addLocalizedAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
|
||||
addLocalizedAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void addAction(String localizationKey, Runnable action) {
|
||||
var entry = new MenuItem(resourceBundle.getString(localizationKey));
|
||||
private void addLocalizedAction(String localizationKey, Runnable action) {
|
||||
var entryText = resourceBundle.getString(localizationKey);
|
||||
addAction(entryText, action);
|
||||
}
|
||||
|
||||
private void addAction(String entryText, Runnable action) {
|
||||
var entry = new MenuItem(entryText);
|
||||
entry.getStyleClass().addLast("dropdown-button-context-menu-item");
|
||||
entry.setOnAction(_ -> action.run());
|
||||
eventActionsMenu.getItems().addLast(entry);
|
||||
@@ -234,18 +263,17 @@ public class EventListCellController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private Path convertVaultPathToSystemPath(Path p) {
|
||||
if (!(p instanceof CryptoPath)) {
|
||||
throw new IllegalArgumentException("Path " + p + " is not a vault path");
|
||||
}
|
||||
private Path convertVaultPathToSystemPath(String vaultInternalPath) {
|
||||
var v = eventEntry.getValue().getKey().vault();
|
||||
if (!v.isUnlocked()) {
|
||||
return Path.of(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
var mountUri = v.getMountPoint().uri();
|
||||
var internalPath = p.toString().substring(1);
|
||||
return Path.of(mountUri.getPath().concat(internalPath).substring(1));
|
||||
var mountPoint = v.getMountPoint().uri().getPath();
|
||||
if(SystemUtils.IS_OS_WINDOWS) {
|
||||
mountPoint = mountPoint.substring(1); //strip away any leading "/", otherwise there are errors
|
||||
}
|
||||
return Path.of(mountPoint, vaultInternalPath.substring(1)); //vaultPaths are always absolute
|
||||
}
|
||||
|
||||
private void reveal(RevealPathService s, Path p) {
|
||||
|
||||
@@ -30,9 +30,20 @@ public class FxApplication {
|
||||
private final FxApplicationTerminator applicationTerminator;
|
||||
private final AutoUnlocker autoUnlocker;
|
||||
private final FxFSEventList fxFSEventList;
|
||||
private final FxNotificationManager notificationManager;
|
||||
|
||||
@Inject
|
||||
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker, FxFSEventList fxFSEventList) {
|
||||
FxApplication(@Named("startupTime") long startupTime, //
|
||||
Environment environment, //
|
||||
Settings settings, //
|
||||
AppLaunchEventHandler launchEventHandler, //
|
||||
Lazy<TrayMenuComponent> trayMenu, //
|
||||
FxApplicationWindows appWindows, //
|
||||
FxApplicationStyle applicationStyle, //
|
||||
FxApplicationTerminator applicationTerminator, //
|
||||
AutoUnlocker autoUnlocker, //
|
||||
FxFSEventList fxFSEventList, //
|
||||
FxNotificationManager notificationManager) {
|
||||
this.startupTime = startupTime;
|
||||
this.environment = environment;
|
||||
this.settings = settings;
|
||||
@@ -43,6 +54,7 @@ public class FxApplication {
|
||||
this.applicationTerminator = applicationTerminator;
|
||||
this.autoUnlocker = autoUnlocker;
|
||||
this.fxFSEventList = fxFSEventList;
|
||||
this.notificationManager = notificationManager;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -88,6 +100,7 @@ public class FxApplication {
|
||||
|
||||
launchEventHandler.startHandlingLaunchEvents();
|
||||
fxFSEventList.schedulePollForUpdates();
|
||||
notificationManager.schedulePollForUpdates();
|
||||
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.cryptomator.ui.eventview.EventViewComponent;
|
||||
import org.cryptomator.ui.health.HealthCheckComponent;
|
||||
import org.cryptomator.ui.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.notification.NotificationComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
import org.cryptomator.ui.quit.QuitComponent;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
@@ -39,7 +40,8 @@ import java.io.InputStream;
|
||||
UpdateReminderComponent.class, //
|
||||
ShareVaultComponent.class, //
|
||||
EventViewComponent.class, //
|
||||
RecoveryKeyComponent.class})
|
||||
RecoveryKeyComponent.class, //
|
||||
NotificationComponent.class })
|
||||
abstract class FxApplicationModule {
|
||||
|
||||
private static Image createImageFromResource(String resourceName) throws IOException {
|
||||
@@ -78,4 +80,10 @@ abstract class FxApplicationModule {
|
||||
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.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.notification.NotificationComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.cryptomator.ui.quit.QuitComponent;
|
||||
@@ -54,6 +55,7 @@ public class FxApplicationWindows {
|
||||
private final LockComponent.Factory lockWorkflowFactory;
|
||||
private final ErrorComponent.Factory errorWindowFactory;
|
||||
private final Lazy<EventViewComponent> eventViewWindow;
|
||||
private final Lazy<NotificationComponent> notificationWindow;
|
||||
private final ExecutorService executor;
|
||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||
private final ShareVaultComponent.Factory shareVaultWindow;
|
||||
@@ -73,6 +75,7 @@ public class FxApplicationWindows {
|
||||
VaultOptionsComponent.Factory vaultOptionsWindow, //
|
||||
ShareVaultComponent.Factory shareVaultWindow, //
|
||||
Lazy<EventViewComponent> eventViewWindow, //
|
||||
Lazy<NotificationComponent> notificationWindow,
|
||||
ExecutorService executor, //
|
||||
Dialogs dialogs) {
|
||||
this.primaryStage = primaryStage;
|
||||
@@ -85,6 +88,7 @@ public class FxApplicationWindows {
|
||||
this.lockWorkflowFactory = lockWorkflowFactory;
|
||||
this.errorWindowFactory = errorWindowFactory;
|
||||
this.eventViewWindow = eventViewWindow;
|
||||
this.notificationWindow = notificationWindow;
|
||||
this.executor = executor;
|
||||
this.vaultOptionsWindow = vaultOptionsWindow;
|
||||
this.shareVaultWindow = shareVaultWindow;
|
||||
@@ -193,6 +197,10 @@ public class FxApplicationWindows {
|
||||
return CompletableFuture.supplyAsync(() -> eventViewWindow.get().showEventViewerWindow(), Platform::runLater).whenComplete(this::reportErrors);
|
||||
}
|
||||
|
||||
public CompletionStage<Stage> showNotification() {
|
||||
return CompletableFuture.supplyAsync(() -> notificationWindow.get().showNotification(), Platform::runLater).whenComplete(this::reportErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the generic error scene in the given window.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
@HealthCheckWindow
|
||||
@HealthCheckScoped
|
||||
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) {
|
||||
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
|
||||
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) {
|
||||
return compFactory.create(vault, window).keyloadingStrategy();
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -3,11 +3,8 @@ package org.cryptomator.ui.keyloading;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
|
||||
import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@KeyLoadingScoped
|
||||
@Subcomponent(modules = {KeyLoadingModule.class})
|
||||
@@ -16,16 +13,10 @@ public interface KeyLoadingComponent {
|
||||
@KeyLoading
|
||||
KeyLoadingStrategy keyloadingStrategy();
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
@BindsInstance
|
||||
Builder vault(@KeyLoading Vault vault);
|
||||
|
||||
@BindsInstance
|
||||
Builder window(@KeyLoading Stage window);
|
||||
|
||||
KeyLoadingComponent build();
|
||||
KeyLoadingComponent create(@BindsInstance @KeyLoading Vault vault, @KeyLoading @BindsInstance Stage window);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,6 +66,13 @@ public abstract class HubKeyLoadingModule {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("filesystemOwnerId")
|
||||
@KeyLoadingScoped
|
||||
static AtomicReference<String> provideFilesystemOwnerIdRef() {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static CompletableFuture<ReceivedKey> provideResult() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.FilesystemOwnerSupplier;
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
|
||||
import org.cryptomator.common.settings.DeviceKey;
|
||||
@@ -23,25 +24,28 @@ import java.net.URI;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@KeyLoading
|
||||
public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
||||
public class HubKeyLoadingStrategy implements KeyLoadingStrategy, FilesystemOwnerSupplier {
|
||||
|
||||
private static final String SCHEME_PREFIX = "hub+";
|
||||
public static final String SCHEME_PREFIX = "hub+";
|
||||
public static final String SCHEME_HUB_HTTP = SCHEME_PREFIX + "http";
|
||||
public static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
|
||||
|
||||
private final Stage window;
|
||||
private final KeychainManager keychainManager;
|
||||
private final AtomicReference<String> fsOwnerId;
|
||||
private final Lazy<Scene> authFlowScene;
|
||||
private final Lazy<Scene> noKeychainScene;
|
||||
private final CompletableFuture<ReceivedKey> result;
|
||||
private final DeviceKey deviceKey;
|
||||
|
||||
@Inject
|
||||
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
|
||||
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle, @Named("filesystemOwnerId") AtomicReference<String> fsOwnerId) {
|
||||
this.window = window;
|
||||
this.keychainManager = keychainManager;
|
||||
this.fsOwnerId = fsOwnerId;
|
||||
window.setTitle(windowTitle);
|
||||
window.setOnCloseRequest(_ -> result.cancel(true));
|
||||
this.authFlowScene = authFlowScene;
|
||||
@@ -90,4 +94,13 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwner() {
|
||||
var name = fsOwnerId.get();
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("Owner is not yet determined");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.nimbusds.jose.JWEObject;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
@@ -41,7 +42,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
public class ReceiveKeyController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReceiveKeyController.class);
|
||||
private static final String SCHEME_PREFIX = "hub+";
|
||||
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
|
||||
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10);
|
||||
|
||||
@@ -50,6 +50,7 @@ public class ReceiveKeyController implements FxController {
|
||||
private final String vaultId;
|
||||
private final String deviceId;
|
||||
private final String bearerToken;
|
||||
private final AtomicReference<String> fsOwnerId;
|
||||
private final CompletableFuture<ReceivedKey> result;
|
||||
private final Lazy<Scene> registerDeviceScene;
|
||||
private final Lazy<Scene> legacyRegisterDeviceScene;
|
||||
@@ -66,6 +67,7 @@ public class ReceiveKeyController implements FxController {
|
||||
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,
|
||||
@@ -78,6 +80,7 @@ public class ReceiveKeyController implements FxController {
|
||||
this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs)
|
||||
this.deviceId = deviceId;
|
||||
this.bearerToken = Objects.requireNonNull(tokenRef.get());
|
||||
this.fsOwnerId = fsOwnerId;
|
||||
this.result = result;
|
||||
this.registerDeviceScene = registerDeviceScene;
|
||||
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
|
||||
@@ -95,7 +98,34 @@ public class ReceiveKeyController implements FxController {
|
||||
}
|
||||
|
||||
public void receiveKey() {
|
||||
requestApiConfig();
|
||||
requestUserData();
|
||||
}
|
||||
|
||||
private void requestUserData() {
|
||||
var userUri = hubConfig.URIs.API.resolve("users/me?withDevices=false");
|
||||
var request = HttpRequest.newBuilder(userUri) //
|
||||
.header("Authorization", "Bearer " + bearerToken) //
|
||||
.GET() //
|
||||
.timeout(REQ_TIMEOUT) //
|
||||
.build();
|
||||
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
|
||||
.thenAcceptAsync(this::receivedUserData) //
|
||||
.exceptionally(this::retrievalFailed);
|
||||
}
|
||||
|
||||
private void receivedUserData(HttpResponse<String> response) {
|
||||
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
|
||||
try {
|
||||
if (response.statusCode() == 200) {
|
||||
var user = JSON.reader().readValue(response.body(), UserDto.class);
|
||||
fsOwnerId.set(user.name);
|
||||
requestApiConfig();
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected response " + response.statusCode());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,6 +188,7 @@ public class ReceiveKeyController implements FxController {
|
||||
switch (response.statusCode()) {
|
||||
case 200 -> {
|
||||
var device = JSON.reader().readValue(response.body(), DeviceDto.class);
|
||||
fsOwnerId.accumulateAndGet(device.name, (s1, s2) -> s1 + Constants.HUB_USER_DEVICE_SEPARATOR + s2);
|
||||
requestVaultMasterkey(device.userPrivateKey);
|
||||
}
|
||||
case 404 -> Platform.runLater(this::needsDeviceRegistration);
|
||||
@@ -309,13 +340,16 @@ public class ReceiveKeyController implements FxController {
|
||||
}
|
||||
|
||||
private static String extractVaultId(URI vaultKeyUri) {
|
||||
assert vaultKeyUri.getScheme().startsWith(SCHEME_PREFIX);
|
||||
assert vaultKeyUri.getScheme().startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX);
|
||||
var path = vaultKeyUri.getPath();
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
|
||||
private record UserDto(@JsonProperty(value = "name", required = true) String name) {}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private record DeviceDto(@JsonProperty(value = "name", required = true) String name, @JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private record ConfigDto(@JsonProperty(value = "apiLevel") int apiLevel) {}
|
||||
|
||||
@@ -15,8 +15,8 @@ import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -139,8 +139,8 @@ public class VaultListController implements FxController {
|
||||
vaultList.setItems(vaults);
|
||||
vaultList.setCellFactory(cellFactory);
|
||||
|
||||
vaultList.prefHeightProperty().bind(
|
||||
vaultList.fixedCellSizeProperty().multiply(Bindings.size(vaultList.getItems()))
|
||||
vaultList.prefHeightProperty().bind( //
|
||||
vaultList.fixedCellSizeProperty().multiply(Bindings.size(vaultList.getItems())) //
|
||||
);
|
||||
|
||||
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
|
||||
@@ -157,11 +157,11 @@ public class VaultListController implements FxController {
|
||||
//unlock vault on double click
|
||||
vaultList.addEventFilter(MouseEvent.MOUSE_CLICKED, click -> {
|
||||
if (click.getClickCount() >= 2) {
|
||||
Optional.ofNullable(selectedVault.get())
|
||||
.filter(Vault::isLocked)
|
||||
Optional.ofNullable(selectedVault.get()) //
|
||||
.filter(Vault::isLocked) //
|
||||
.ifPresent(vault -> appWindows.startUnlockWorkflow(vault, mainWindow));
|
||||
Optional.ofNullable(selectedVault.get())
|
||||
.filter(Vault::isUnlocked)
|
||||
Optional.ofNullable(selectedVault.get()) //
|
||||
.filter(Vault::isUnlocked) //
|
||||
.ifPresent(vaultService::reveal);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
|
||||
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
|
||||
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
|
||||
return compFactory.create(vault, window).keyloadingStrategy();
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -113,6 +113,11 @@
|
||||
-fx-font-size: 1.2em;
|
||||
}
|
||||
|
||||
.label-window-title {
|
||||
-fx-font-family: 'Open Sans SemiBold';
|
||||
-fx-font-size: 1.0em;
|
||||
}
|
||||
|
||||
.label-small {
|
||||
-fx-font-size: 0.8em;
|
||||
}
|
||||
@@ -1186,3 +1191,32 @@
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
|
||||
}
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Notification Window
|
||||
* *
|
||||
******************************************************************************/
|
||||
.notification-window {
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 8px;
|
||||
-fx-border-radius: 8px;
|
||||
-fx-background-insets: 0;
|
||||
-fx-border-color: MUTED_BG;
|
||||
-fx-border-width: 1px;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 28, 0.35, 0, 6);
|
||||
}
|
||||
|
||||
.notification-window .dialog-header {
|
||||
-fx-alignment: center-left;
|
||||
}
|
||||
|
||||
.notification-window .window-bar-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 6 8 6 8;
|
||||
}
|
||||
|
||||
.notification-window .window-bar-button:armed {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
-fx-background-radius: 8;
|
||||
}
|
||||
@@ -113,6 +113,11 @@
|
||||
-fx-font-size: 1.2em;
|
||||
}
|
||||
|
||||
.label-window-title {
|
||||
-fx-font-family: 'Open Sans SemiBold';
|
||||
-fx-font-size: 1.0em;
|
||||
}
|
||||
|
||||
.label-small {
|
||||
-fx-font-size: 0.8em;
|
||||
}
|
||||
@@ -1186,3 +1191,33 @@
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Notification Window
|
||||
* *
|
||||
******************************************************************************/
|
||||
.notification-window {
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 8px;
|
||||
-fx-border-radius: 8px;
|
||||
-fx-background-insets: 0;
|
||||
-fx-border-color: MUTED_BG;
|
||||
-fx-border-width: 1px;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 28, 0.35, 0, 6);
|
||||
}
|
||||
|
||||
.notification-window .dialog-header {
|
||||
-fx-alignment: center-left;
|
||||
}
|
||||
|
||||
.notification-window .window-bar-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 6 8 6 8;
|
||||
}
|
||||
|
||||
.notification-window .window-bar-button:armed {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
-fx-background-radius: 8;
|
||||
}
|
||||
75
src/main/resources/fxml/notification.fxml
Normal file
75
src/main/resources/fxml/notification.fxml
Normal 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>
|
||||
@@ -13,6 +13,7 @@ generic.button.close=Close
|
||||
generic.button.copy=Copy
|
||||
generic.button.copied=Copied!
|
||||
generic.button.done=Done
|
||||
generic.button.previous=Previous
|
||||
generic.button.next=Next
|
||||
generic.button.print=Print
|
||||
generic.button.remove=Remove
|
||||
@@ -698,3 +699,17 @@ eventView.entry.brokenFileNode.message=Broken filesystem node
|
||||
eventView.entry.brokenFileNode.showEncrypted=Show broken, encrypted node
|
||||
eventView.entry.brokenFileNode.copyEncrypted=Copy path of broken, encrypted node
|
||||
eventView.entry.brokenFileNode.copyDecrypted=Copy decrypted path
|
||||
eventView.entry.inUse.message=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
|
||||
@@ -82,13 +82,22 @@
|
||||
|
||||
<!-- Vulnerable, but unused class in jetty -->
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
<notes><![CDATA[
|
||||
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.
|
||||
]]></notes>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user