mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-23 08:24:52 +00:00
Compare commits
186
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04bdb2d8c4 | ||
|
|
f32d24c1c0 | ||
|
|
f4f093bb42 | ||
|
|
3f06460667 | ||
|
|
617f1bf2c9 | ||
|
|
02ad38f871 | ||
|
|
7a2944cbea | ||
|
|
4c47204f92 | ||
|
|
f052395a7f | ||
|
|
57be83b38d | ||
|
|
c2cd4f5bbf | ||
|
|
d3d57312ba | ||
|
|
593a64c9bd | ||
|
|
19dc4fb6ff | ||
|
|
81f45012f3 | ||
|
|
c59554f7bb | ||
|
|
11c66e8df7 | ||
|
|
c49bf0f146 | ||
|
|
e74bd91879 | ||
|
|
89d9249a08 | ||
|
|
013fff1223 | ||
|
|
fd9d27215e | ||
|
|
f91cc2374c | ||
|
|
48298bb161 | ||
|
|
7a1cd9026c | ||
|
|
754e53d8db | ||
|
|
c36f1bc8d0 | ||
|
|
7315b59a6d | ||
|
|
8a243a01aa | ||
|
|
9e4006cc89 | ||
|
|
26b69beb87 | ||
|
|
f95bf87a4b | ||
|
|
e854c7d189 | ||
|
|
8a434dcd96 | ||
|
|
6b7324723e | ||
|
|
0bdcb2b3be | ||
|
|
c3931d9d29 | ||
|
|
307825a339 | ||
|
|
093f0e8c94 | ||
|
|
c938c42c00 | ||
|
|
884c6f6bdd | ||
|
|
c5367db971 | ||
|
|
59560193ee | ||
|
|
a6b31e19b9 | ||
|
|
8a44115234 | ||
|
|
43a1f00bea | ||
|
|
3e458060bc | ||
|
|
4bb5a3f10d | ||
|
|
8bb451261e | ||
|
|
de4c7d47e5 | ||
|
|
b4fe63c966 | ||
|
|
ed45182b6c | ||
|
|
afd2d74c90 | ||
|
|
4bcf01270a | ||
|
|
64466df6b8 | ||
|
|
db9bd49dd9 | ||
|
|
4ed4a3e3e5 | ||
|
|
ad73b2f31d | ||
|
|
227b322b8a | ||
|
|
ccf2e60390 | ||
|
|
1104c1067a | ||
|
|
83d2c39080 | ||
|
|
446caff901 | ||
|
|
90537caee7 | ||
|
|
25dea8ebe6 | ||
|
|
4a7d2907c8 | ||
|
|
7f3af0c7f6 | ||
|
|
e6e04078fa | ||
|
|
e3f6b5f812 | ||
|
|
f3461f7da4 | ||
|
|
94518ede99 | ||
|
|
0fa041da85 | ||
|
|
daa4f3a568 | ||
|
|
fc6f7d184c | ||
|
|
e50c949b62 | ||
|
|
349349e057 | ||
|
|
4b36a1219f | ||
|
|
6533ba7367 | ||
|
|
7f9c9a7df6 | ||
|
|
afda8a4981 | ||
|
|
cdcd43a805 | ||
|
|
2f4c12a89e | ||
|
|
2b79c58577 | ||
|
|
1d3c62e2c7 | ||
|
|
17166c45ac | ||
|
|
f758388063 | ||
|
|
8767564977 | ||
|
|
59f47116f0 | ||
|
|
8fac541024 | ||
|
|
1ba21b531a | ||
|
|
daad48d5af | ||
|
|
8eedc62fbe | ||
|
|
d65442f042 | ||
|
|
216513405a | ||
|
|
f4c0bc29ed | ||
|
|
d1ce0362b4 | ||
|
|
4497fb160e | ||
|
|
5bffa28cdb | ||
|
|
14c5693594 | ||
|
|
53c6045cca | ||
|
|
16d441ec44 | ||
|
|
fa053ac3df | ||
|
|
e78a064af6 | ||
|
|
d5245009f4 | ||
|
|
8f4392711e | ||
|
|
644e38f49e | ||
|
|
3b38049d71 | ||
|
|
79bb4a5215 | ||
|
|
c199d1cb3b | ||
|
|
164a4de6de | ||
|
|
2ce7fee06d | ||
|
|
99e9e92f10 | ||
|
|
8e6500d93f | ||
|
|
89ce99deaf | ||
|
|
510e134605 | ||
|
|
62b434f549 | ||
|
|
1f1e336d57 | ||
|
|
22a37699ea | ||
|
|
44f92cfb26 | ||
|
|
2067c5a33b | ||
|
|
fc5ed9ecc0 | ||
|
|
5e956c50c2 | ||
|
|
5110b35690 | ||
|
|
499960b3ec | ||
|
|
f6479ddf24 | ||
|
|
5d8b016e5f | ||
|
|
2bf2f2b616 | ||
|
|
1089e90ebf | ||
|
|
9fee4afd2a | ||
|
|
b00ee4740a | ||
|
|
02186ca17a | ||
|
|
b0ed133e05 | ||
|
|
f5ecf846f2 | ||
|
|
a9444182bb | ||
|
|
191d9473c0 | ||
|
|
8089bcd0d0 | ||
|
|
f623b1ee7e | ||
|
|
c546f3363b | ||
|
|
8a1ec1d073 | ||
|
|
81307b6d75 | ||
|
|
50fa8e0608 | ||
|
|
626a692d63 | ||
|
|
c9b9eee564 | ||
|
|
edc308ccd1 | ||
|
|
ad214691f9 | ||
|
|
acf11da202 | ||
|
|
4280112eab | ||
|
|
638e1e562d | ||
|
|
321bd2ff8c | ||
|
|
323a884718 | ||
|
|
5bdfffd151 | ||
|
|
6fcec26eea | ||
|
|
4ddb6b73bd | ||
|
|
da845c6941 | ||
|
|
c30a28aae0 | ||
|
|
5305c5a631 | ||
|
|
b0d2dcbff3 | ||
|
|
5c1364792e | ||
|
|
abf8392baa | ||
|
|
034a70d473 | ||
|
|
8c9d75fe57 | ||
|
|
d00d77b747 | ||
|
|
6d47f28634 | ||
|
|
225332bd4f | ||
|
|
8c84720e24 | ||
|
|
968e8f51e4 | ||
|
|
4de6b83e2f | ||
|
|
90cbb0c5f6 | ||
|
|
e1cc61037e | ||
|
|
1ae951126a | ||
|
|
6bb8c0d4e7 | ||
|
|
087b6162ed | ||
|
|
f9e8031acb | ||
|
|
ebd767595b | ||
|
|
6231c5b8b3 | ||
|
|
a3b8297e23 | ||
|
|
6715ff9f5f | ||
|
|
8c5325511c | ||
|
|
895614353e | ||
|
|
676c507a50 | ||
|
|
5f4fd92b1d | ||
|
|
8191a7dae6 | ||
|
|
756672258d | ||
|
|
fb6f7072d4 | ||
|
|
aa95ad40c0 | ||
|
|
ba3667ab51 |
@@ -20,6 +20,10 @@
|
|||||||
|
|
||||||
Translations are not managed directly in this repository. Instead, we use [Crowdin](https://translate.cryptomator.org/), which automatically synchronizes translations with this repository. If you want to help us with translations, please visit our translation project on Crowdin.
|
Translations are not managed directly in this repository. Instead, we use [Crowdin](https://translate.cryptomator.org/), which automatically synchronizes translations with this repository. If you want to help us with translations, please visit our translation project on Crowdin.
|
||||||
|
|
||||||
|
## Use of Generative AI
|
||||||
|
|
||||||
|
AI tools may assist your work, but every contribution must be fully understood, reviewed, and tested by you. Only submit changes you can clearly explain and justify. Unverified or low-quality AI output that wastes our time and resources will be closed without further review.
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).
|
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: 'Windows Code Signing'
|
||||||
|
description: 'Sign files on Windows with the Azure Trusted Signing'
|
||||||
|
inputs:
|
||||||
|
base-dir:
|
||||||
|
description: 'Absolute path to the base directory to search for files'
|
||||||
|
required: true
|
||||||
|
recursive:
|
||||||
|
description: 'Whether to search recursively in subdirectories'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
file-extensions:
|
||||||
|
description: 'List of file extensions to sign, separated by comma'
|
||||||
|
required: true
|
||||||
|
default: 'exe,dll,ps1'
|
||||||
|
description:
|
||||||
|
description: 'Signature description'
|
||||||
|
required: true
|
||||||
|
default: 'Cryptomator'
|
||||||
|
url:
|
||||||
|
description: 'Signature URL'
|
||||||
|
required: false
|
||||||
|
default: 'https://cryptomator.org'
|
||||||
|
append-signature:
|
||||||
|
description: 'Whether to append the signature to existing signatures'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
tenant-id:
|
||||||
|
description: 'Azure Tenant ID'
|
||||||
|
required: true
|
||||||
|
client-id:
|
||||||
|
description: 'Azure Client ID'
|
||||||
|
required: true
|
||||||
|
client-secret:
|
||||||
|
description: 'Azure Client Secret'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: 'composite'
|
||||||
|
steps:
|
||||||
|
- name: Generate, mask, and output the input secrets
|
||||||
|
id: set-secrets
|
||||||
|
run: |
|
||||||
|
echo "::add-mask::${{ inputs.tenant-id }}"
|
||||||
|
echo "::add-mask::${{ inputs.client-id }}"
|
||||||
|
echo "::add-mask::${{ inputs.client-secret }}"
|
||||||
|
echo "tenant-id=${{ inputs.tenant-id }}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "client-id=${{ inputs.client-id }}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "client-secret=${{ inputs.client-secret }}" >> "$GITHUB_OUTPUT"
|
||||||
|
shell: bash
|
||||||
|
- name: Sign DLLs with Azure Trusted Signing
|
||||||
|
uses: azure/trusted-signing-action@fc390cf8ed0f14e248a542af1d838388a47c7a7c # v0.5.10
|
||||||
|
with:
|
||||||
|
files-folder: ${{ inputs.base-dir }}
|
||||||
|
files-folder-filter: ${{ inputs.file-extensions }}
|
||||||
|
files-folder-recurse: ${{ inputs.recursive }}
|
||||||
|
append-signature: ${{ inputs.append-signature }}
|
||||||
|
description: ${{ inputs.description }}
|
||||||
|
description-url: ${{ inputs.url }}
|
||||||
|
azure-tenant-id: ${{ steps.set-secrets.outputs.tenant-id }}
|
||||||
|
azure-client-id: ${{ steps.set-secrets.outputs.client-id }}
|
||||||
|
azure-client-secret: ${{ steps.set-secrets.outputs.client-secret }}
|
||||||
|
trusted-signing-account-name: cryptomatorSigning
|
||||||
|
certificate-profile-name: production
|
||||||
|
endpoint: https://weu.codesigning.azure.net/
|
||||||
|
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||||
|
timestamp-digest: SHA256
|
||||||
|
exclude-environment-credential: false
|
||||||
|
exclude-workload-identity-credential: true
|
||||||
|
exclude-managed-identity-credential: true
|
||||||
|
exclude-shared-token-cache-credential: true
|
||||||
|
exclude-visual-studio-credential: true
|
||||||
|
exclude-visual-studio-code-credential: true
|
||||||
|
exclude-azure-cli-credential: true
|
||||||
|
exclude-azure-powershell-credential: true
|
||||||
|
exclude-azure-developer-cli-credential: true
|
||||||
|
exclude-interactive-browser-credential: true
|
||||||
@@ -19,7 +19,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'temurin'
|
||||||
JAVA_VERSION: '24.0.1+9'
|
JAVA_VERSION: '25.0.1+8.0.LTS'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get-version:
|
get-version:
|
||||||
@@ -37,16 +37,16 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
appimage-suffix: x86_64
|
appimage-suffix: x86_64
|
||||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-x64_bin-jmods.zip'
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
|
||||||
openjfx-sha: '425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c'
|
openjfx-sha: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
|
||||||
- os: ubuntu-24.04-arm
|
- os: ubuntu-24.04-arm
|
||||||
appimage-suffix: aarch64
|
appimage-suffix: aarch64
|
||||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-aarch64_bin-jmods.zip'
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
|
||||||
openjfx-sha: '7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b'
|
openjfx-sha: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
- name: Set version
|
- name: Set version
|
||||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Plinux -DskipTests -Djavafx.platform=linux
|
run: mvn -B clean package -Plinux -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
@@ -95,7 +95,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
|
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -117,7 +117,6 @@ jobs:
|
|||||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
--java-options "--enable-preview"
|
--java-options "--enable-preview"
|
||||||
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
|
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
|
||||||
--java-options "--sun-misc-unsafe-memory-access=allow"
|
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||||
@@ -176,7 +175,7 @@ jobs:
|
|||||||
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
|
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
|
||||||
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
|
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: appimage-${{ matrix.appimage-suffix }}
|
name: appimage-${{ matrix.appimage-suffix }}
|
||||||
path: |
|
path: |
|
||||||
@@ -186,7 +185,7 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Publish AppImage on GitHub Releases
|
- name: Publish AppImage on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
@@ -199,10 +198,10 @@ jobs:
|
|||||||
name: Create PR for aur-bin repo
|
name: Create PR for aur-bin repo
|
||||||
needs: [build, get-version]
|
needs: [build, get-version]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable'
|
||||||
steps:
|
steps:
|
||||||
- name: Download AppImages
|
- name: Download AppImages
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
path: downloads/
|
path: downloads/
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -213,7 +212,7 @@ jobs:
|
|||||||
echo "x64-sha256sum=${X64_SHA256}" >> "$GITHUB_OUTPUT"
|
echo "x64-sha256sum=${X64_SHA256}" >> "$GITHUB_OUTPUT"
|
||||||
AARCH64_SHA256=$(sha256sum downloads/cryptomator-*-aarch64.AppImage | cut -d ' ' -f1)
|
AARCH64_SHA256=$(sha256sum downloads/cryptomator-*-aarch64.AppImage | cut -d ' ' -f1)
|
||||||
echo "aarch64-sha256sum=${AARCH64_SHA256}" >> "$GITHUB_OUTPUT"
|
echo "aarch64-sha256sum=${AARCH64_SHA256}" >> "$GITHUB_OUTPUT"
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
repository: 'cryptomator/aur-bin'
|
repository: 'cryptomator/aur-bin'
|
||||||
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||||
@@ -248,7 +247,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||||
- name: Slack Notification
|
- name: Slack Notification
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
SLACK_USERNAME: 'Cryptobot'
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get-version]
|
needs: [get-version]
|
||||||
if: github.event_name == 'workflow_dispatch' || needs.get-version.outputs.versionType == 'stable'
|
if: github.event_name == 'workflow_dispatch' || needs.get-version.outputs.versionType == 'stable'
|
||||||
|
env:
|
||||||
|
INPUT_TAG: ${{ inputs.tag }}
|
||||||
outputs:
|
outputs:
|
||||||
url: ${{ steps.url.outputs.url}}
|
url: ${{ steps.url.outputs.url}}
|
||||||
sha256: ${{ steps.sha256.outputs.sha256}}
|
sha256: ${{ steps.sha256.outputs.sha256}}
|
||||||
@@ -27,8 +29,8 @@ jobs:
|
|||||||
id: url
|
id: url
|
||||||
run: |
|
run: |
|
||||||
URL="";
|
URL="";
|
||||||
if [[ -n "${{ inputs.tag }}" ]]; then
|
if [[ -n "${INPUT_TAG}" ]]; then
|
||||||
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ inputs.tag }}.tar.gz"
|
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${INPUT_TAG}.tar.gz"
|
||||||
else
|
else
|
||||||
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz"
|
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz"
|
||||||
fi
|
fi
|
||||||
@@ -46,7 +48,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
AUR_PR_URL: tbd
|
AUR_PR_URL: tbd
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
repository: 'cryptomator/aur'
|
repository: 'cryptomator/aur'
|
||||||
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||||
@@ -79,8 +81,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||||
- name: Slack Notification
|
- name: Slack Notification
|
||||||
uses: rtCamp/action-slack-notify@v2
|
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
|
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
SLACK_USERNAME: 'Cryptobot'
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
|
|||||||
@@ -30,16 +30,18 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
fileName: ${{ steps.extractName.outputs.fileName}}
|
fileName: ${{ steps.extractName.outputs.fileName}}
|
||||||
|
env:
|
||||||
|
INPUT_URL: ${{ inputs.url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Extract file name
|
- name: Extract file name
|
||||||
id: extractName
|
id: extractName
|
||||||
run: |
|
run: |
|
||||||
url="${{ inputs.url }}"
|
url="${INPUT_URL}"
|
||||||
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
|
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
|
||||||
- name: Download file
|
- name: Download file
|
||||||
run: curl --remote-name ${{ inputs.url }} -L -o ${{steps.extractName.outputs.fileName}}
|
run: curl --remote-name ${INPUT_URL} -L -o ${{steps.extractName.outputs.fileName}}
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.extractName.outputs.fileName }}
|
name: ${{ steps.extractName.outputs.fileName }}
|
||||||
path: ${{ steps.extractName.outputs.fileName }}
|
path: ${{ steps.extractName.outputs.fileName }}
|
||||||
@@ -51,12 +53,12 @@ jobs:
|
|||||||
if: github.event_name == 'workflow_call' || inputs.kaspersky
|
if: github.event_name == 'workflow_call' || inputs.kaspersky
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: ${{ needs.download-file.outputs.fileName }}
|
name: ${{ needs.download-file.outputs.fileName }}
|
||||||
path: upload
|
path: upload
|
||||||
- name: Upload to Kaspersky
|
- name: Upload to Kaspersky
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.6
|
uses: SamKirkland/FTP-Deploy-Action@a51268f67f6605236975928ae28b0f7e9971d50a # v4.6.3
|
||||||
with:
|
with:
|
||||||
protocol: ftps
|
protocol: ftps
|
||||||
server: allowlist.kaspersky-labs.com
|
server: allowlist.kaspersky-labs.com
|
||||||
@@ -71,12 +73,12 @@ jobs:
|
|||||||
if: github.event_name == 'workflow_call' || inputs.avast
|
if: github.event_name == 'workflow_call' || inputs.avast
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: ${{ needs.download-file.outputs.fileName }}
|
name: ${{ needs.download-file.outputs.fileName }}
|
||||||
path: upload
|
path: upload
|
||||||
- name: Upload to Avast
|
- name: Upload to Avast
|
||||||
uses: wlixcc/SFTP-Deploy-Action@v1.2.6
|
uses: wlixcc/SFTP-Deploy-Action@a5ccb9c6211a94cc59404f0fdb2a9936a6dfee64 # v1.2.6
|
||||||
with:
|
with:
|
||||||
server: whitelisting.avast.com
|
server: whitelisting.avast.com
|
||||||
port: 22
|
port: 22
|
||||||
|
|||||||
+21
-16
@@ -11,7 +11,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'temurin'
|
||||||
JAVA_VERSION: 24
|
JAVA_VERSION: 25
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -22,14 +22,14 @@ jobs:
|
|||||||
name: Compile and Test
|
name: Compile and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- uses: actions/setup-java@v5
|
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Cache SonarCloud packages
|
- name: Cache SonarCloud packages
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ~/.sonar/cache
|
path: ~/.sonar/cache
|
||||||
key: ${{ runner.os }}-sonar
|
key: ${{ runner.os }}-sonar
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
run: >
|
run: >
|
||||||
xvfb-run
|
xvfb-run
|
||||||
mvn -B verify -Djavafx.platform=linux
|
mvn -B verify
|
||||||
jacoco:report
|
jacoco:report
|
||||||
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
|
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
|
||||||
-Pcoverage
|
-Pcoverage
|
||||||
@@ -49,28 +49,28 @@ jobs:
|
|||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
- name: Draft a release
|
- name: Draft a release
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
discussion_category_name: releases
|
discussion_category_name: releases
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
body: |-
|
body: |-
|
||||||
:construction: Work in Progress
|
> [!NOTE]
|
||||||
|
> 🚧 Work in Progress 🚧
|
||||||
|
>
|
||||||
|
> Please be patient, the [builds are still running](https://github.com/cryptomator/cryptomator/actions). Binary packages can be found here in a few moments.
|
||||||
|
|
||||||
|
<!--REPLACE with auto-generated release notes (see below)
|
||||||
### What's New 🎉
|
### What's New 🎉
|
||||||
|
|
||||||
### Bugfixes 🐛
|
### Bugfixes 🐛
|
||||||
|
|
||||||
### Other Changes 📎
|
### Other Changes 📎
|
||||||
|
END REPLACE-->
|
||||||
|
|
||||||
|
Feel free to also read our [CHANGELOG.md](https://github.com/cryptomator/cryptomator/blob/develop/CHANGELOG.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
TODO FULL CHANGELOG
|
|
||||||
|
|
||||||
📜 List of closed issues is available [here](TODO)
|
|
||||||
|
|
||||||
---
|
|
||||||
⏳ Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. ⏳
|
|
||||||
|
|
||||||
<!-- Don't forget to include the
|
<!-- Don't forget to include the
|
||||||
💾 SHA-256 checksums of release artifacts:
|
💾 SHA-256 checksums of release artifacts:
|
||||||
@@ -78,4 +78,9 @@ jobs:
|
|||||||
```
|
```
|
||||||
-->
|
-->
|
||||||
|
|
||||||
As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
|
> [!TIP]
|
||||||
|
> You can verify the GPG signature of all assets using our public key: [`5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Auto-Generated Release Notes: -->
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JDK_VERSION: '24.0.1+9'
|
JDK_VERSION: '25.0.1+8.0.LTS'
|
||||||
JDK_VENDOR: temurin
|
JDK_VENDOR: temurin
|
||||||
RUNTIME_VERSION_HELPER: >
|
RUNTIME_VERSION_HELPER: >
|
||||||
public class Test {
|
public class Test {
|
||||||
@@ -23,10 +23,10 @@ jobs:
|
|||||||
JDK_MAJOR_VERSION: 'toBeFilled'
|
JDK_MAJOR_VERSION: 'toBeFilled'
|
||||||
steps:
|
steps:
|
||||||
- name: Determine current major version
|
- name: Determine current major version
|
||||||
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,20) >> "$env:GITHUB_ENV"
|
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,2) >> "$env:GITHUB_ENV"
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
|
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
java-version: ${{ env.JDK_MAJOR_VERSION}}
|
java-version: ${{ env.JDK_MAJOR_VERSION}}
|
||||||
distribution: ${{ env.JDK_VENDOR }}
|
distribution: ${{ env.JDK_VENDOR }}
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
- name: Notify
|
- name: Notify
|
||||||
if: steps.determine.outputs.UPDATE_AVAILABLE == 'true'
|
if: steps.determine.outputs.UPDATE_AVAILABLE == 'true'
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
SLACK_USERNAME: 'Cryptobot'
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
|
|||||||
@@ -23,13 +23,12 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'temurin'
|
||||||
JAVA_VERSION: '24.0.1+9'
|
JAVA_VERSION: '25.0.1+8.0.LTS'
|
||||||
COFFEELIBS_JDK: 24
|
DEB_BUILD_DEPENDS: 'debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1'
|
||||||
COFFEELIBS_JDK_VERSION: '24.0.1+9-0ppa3'
|
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
|
||||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-x64_bin-jmods.zip'
|
OPENJFX_JMODS_AMD64_HASH: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
|
||||||
OPENJFX_JMODS_AMD64_HASH: '425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c'
|
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
|
||||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-aarch64_bin-jmods.zip'
|
OPENJFX_JMODS_AARCH64_HASH: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
|
||||||
OPENJFX_JMODS_AARCH64_HASH: '7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get-version:
|
get-version:
|
||||||
@@ -41,30 +40,34 @@ jobs:
|
|||||||
name: Build Debian Package
|
name: Build Debian Package
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: [get-version]
|
needs: [get-version]
|
||||||
|
env:
|
||||||
|
INPUT_PPAVER: ${{ inputs.ppaver }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- id: deb-version
|
- id: deb-version
|
||||||
name: Determine deb-version
|
name: Determine deb-version
|
||||||
run: |
|
run: |
|
||||||
if [ -n "${{inputs.ppaver}}" ]; then
|
if [ -n "${INPUT_PPAVER}" ]; then
|
||||||
echo "debVersion=${{inputs.ppaver }}" >> "$GITHUB_OUTPUT"
|
echo "debVersion=${INPUT_PPAVER}" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "debVersion=${{needs.get-version.outputs.semVerStr}}" >> "$GITHUB_OUTPUT"
|
echo "debVersion=${{needs.get-version.outputs.semVerStr}}" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
- name: Install build tools
|
- name: Install build tools
|
||||||
run: |
|
run: |
|
||||||
sudo add-apt-repository ppa:coffeelibs/openjdk
|
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }}
|
sudo apt-get install devscripts dput
|
||||||
|
sudo apt-get satisfy "${DEB_BUILD_DEPENDS}"
|
||||||
|
env:
|
||||||
|
DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }}
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Plinux -Djavafx.platform=linux -DskipTests
|
run: mvn -B clean package -Plinux -DskipTests
|
||||||
- name: Download OpenJFX jmods
|
- name: Download OpenJFX jmods
|
||||||
id: download-jmods
|
id: download-jmods
|
||||||
run: |
|
run: |
|
||||||
@@ -140,7 +143,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
|
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: linux-deb-package
|
name: linux-deb-package
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-dependencies:
|
check-dependencies:
|
||||||
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@v1
|
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@1074588008ae3326a2221ea451783280518f0366 # v3.0.1
|
||||||
with:
|
with:
|
||||||
runner-os: 'ubuntu-latest'
|
runner-os: 'ubuntu-latest'
|
||||||
java-distribution: 'temurin'
|
java-distribution: 'temurin'
|
||||||
java-version: 24
|
java-version: 25
|
||||||
check-command: 'mvn -B validate -Pdependency-check -Djavafx.platform=linux'
|
|
||||||
secrets:
|
secrets:
|
||||||
nvd-api-key: ${{ secrets.NVD_API_KEY }}
|
nvd-api-key: ${{ secrets.NVD_API_KEY }}
|
||||||
|
ossindex-username: ${{ secrets.OSSINDEX_USERNAME }}
|
||||||
|
ossindex-token: ${{ secrets.OSSINDEX_API_TOKEN }}
|
||||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Get download count of latest releases
|
- name: Get download count of latest releases
|
||||||
id: get-stats
|
id: get-stats
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const query = `query($owner:String!, $name:String!) {
|
const query = `query($owner:String!, $name:String!) {
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
INTERVAL: 900
|
INTERVAL: 900
|
||||||
JSON_DATA: ${{ steps.get-stats.outputs.result }}
|
JSON_DATA: ${{ steps.get-stats.outputs.result }}
|
||||||
- name: Upload Results
|
- name: Upload Results
|
||||||
uses: fjogeleit/http-request-action@v1
|
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
|
||||||
with:
|
with:
|
||||||
url: 'https://graphite-us-central1.grafana.net/metrics'
|
url: 'https://graphite-us-central1.grafana.net/metrics'
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Query Discussion Data
|
- name: Query Discussion Data
|
||||||
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
|
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
|
||||||
id: query-data
|
id: query-data
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
|
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
return await github.graphql(query, variables)
|
return await github.graphql(query, variables)
|
||||||
- name: Get Gist
|
- name: Get Gist
|
||||||
id: get-gist
|
id: get-gist
|
||||||
uses: andymckay/get-gist-action@master
|
uses: andymckay/get-gist-action@cf3bc8164af24126f7e5979eb6d3dc0c12309bd1 # not_tagged
|
||||||
with:
|
with:
|
||||||
gistURL: https://gist.github.com/cryptobot/accba9fb9555e7192271b85606f97230
|
gistURL: https://gist.github.com/cryptobot/accba9fb9555e7192271b85606f97230
|
||||||
- name: Merge Error Code Data
|
- name: Merge Error Code Data
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DISCUSSION: ${{ steps.query-data.outputs.result }}
|
DISCUSSION: ${{ steps.query-data.outputs.result }}
|
||||||
- name: Patch Gist
|
- name: Patch Gist
|
||||||
uses: exuanbo/actions-deploy-gist@v1
|
uses: exuanbo/actions-deploy-gist@47697fceaeea2006a90594ee24eb9cd0a1121ef8 # v1.1.4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CRYPTOBOT_GIST_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_GIST_TOKEN }}
|
||||||
gist_id: accba9fb9555e7192271b85606f97230
|
gist_id: accba9fb9555e7192271b85606f97230
|
||||||
|
|||||||
@@ -26,13 +26,10 @@ jobs:
|
|||||||
- name: Determine tarball url
|
- name: Determine tarball url
|
||||||
id: url
|
id: url
|
||||||
run: |
|
run: |
|
||||||
URL="";
|
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${TAG}.tar.gz"
|
||||||
if [[ -n "${{ inputs.tag }}" ]]; then
|
|
||||||
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ inputs.tag }}.tar.gz"
|
|
||||||
else
|
|
||||||
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz"
|
|
||||||
fi
|
|
||||||
echo "url=${URL}" >> "$GITHUB_OUTPUT"
|
echo "url=${URL}" >> "$GITHUB_OUTPUT"
|
||||||
|
env:
|
||||||
|
TAG: ${{ inputs.tag || github.event.release.tag_name}}
|
||||||
- name: Download source tarball and compute checksum
|
- name: Download source tarball and compute checksum
|
||||||
id: sha512
|
id: sha512
|
||||||
run: |
|
run: |
|
||||||
@@ -46,7 +43,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
FLATHUB_PR_URL: tbd
|
FLATHUB_PR_URL: tbd
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
repository: 'flathub/org.cryptomator.Cryptomator'
|
repository: 'flathub/org.cryptomator.Cryptomator'
|
||||||
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||||
@@ -74,7 +71,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||||
- name: Slack Notification
|
- name: Slack Notification
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'temurin'
|
||||||
JAVA_VERSION: 24
|
JAVA_VERSION: 25
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
determine-version:
|
determine-version:
|
||||||
@@ -35,11 +35,11 @@ jobs:
|
|||||||
revNum: ${{ steps.versions.outputs.revNum }}
|
revNum: ${{ steps.versions.outputs.revNum }}
|
||||||
type: ${{ steps.versions.outputs.type}}
|
type: ${{ steps.versions.outputs.type}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -49,8 +49,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
SEM_VER_STR=${GITHUB_REF##*/}
|
||||||
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
elif [[ "${VERSION_STRING}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
SEM_VER_STR="${{ inputs.version }}"
|
SEM_VER_STR="${VERSION_STRING}"
|
||||||
else
|
else
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||||
fi
|
fi
|
||||||
@@ -70,7 +70,9 @@ jobs:
|
|||||||
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
|
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
|
||||||
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
|
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
|
||||||
echo "type=${TYPE}" >> $GITHUB_OUTPUT
|
echo "type=${TYPE}" >> $GITHUB_OUTPUT
|
||||||
|
env:
|
||||||
|
VERSION_STRING: ${{ inputs.version }}
|
||||||
- name: Validate Version
|
- name: Validate Version
|
||||||
uses: skymatic/semver-validation-action@v3
|
uses: skymatic/semver-validation-action@7a6ae1c9e121540d11c9c7e4e667c83d583aa153 # v3.0.0
|
||||||
with:
|
with:
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
version: ${{ steps.versions.outputs.semVerStr }}
|
||||||
@@ -24,7 +24,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'temurin'
|
||||||
JAVA_VERSION: '24.0.1+9'
|
JAVA_VERSION: '25.0.1+8.0.LTS'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get-version:
|
get-version:
|
||||||
@@ -44,12 +44,12 @@ jobs:
|
|||||||
architecture: x64
|
architecture: x64
|
||||||
output-suffix: x64
|
output-suffix: x64
|
||||||
fuse-lib: macFUSE
|
fuse-lib: macFUSE
|
||||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_osx-x64_bin-jmods.zip'
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-x64_bin-jmods.zip'
|
||||||
openjfx-sha: '6e62a426d43c168a488521f904a523f3dd6ee2cf103e08136f2fd465c828a105'
|
openjfx-sha: '0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
- name: Set version
|
- name: Set version
|
||||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests
|
run: mvn -B clean package -Pmac -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
|
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -121,7 +121,6 @@ jobs:
|
|||||||
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||||
--java-options "--enable-preview"
|
--java-options "--enable-preview"
|
||||||
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
|
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
|
||||||
--java-options "--sun-misc-unsafe-memory-access=allow"
|
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dfile.encoding=\"utf-8\""
|
--java-options "-Dfile.encoding=\"utf-8\""
|
||||||
@@ -137,6 +136,7 @@ jobs:
|
|||||||
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
|
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||||
|
--java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism"
|
||||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
|
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
|
||||||
--mac-package-identifier org.cryptomator
|
--mac-package-identifier org.cryptomator
|
||||||
--resource-dir dist/mac/resources
|
--resource-dir dist/mac/resources
|
||||||
@@ -151,9 +151,23 @@ jobs:
|
|||||||
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||||
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
||||||
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
|
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
|
||||||
|
- name: Build and install DockTilePlugin
|
||||||
|
env:
|
||||||
|
DERIVED_DATA_PATH: dist/mac/DockTilePlugin/build
|
||||||
|
run: |
|
||||||
|
xcodebuild -project dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj \
|
||||||
|
-scheme DockTilePlugin \
|
||||||
|
-configuration Release \
|
||||||
|
-destination "platform=macOS,arch=x86_64" \
|
||||||
|
-derivedDataPath ${DERIVED_DATA_PATH} \
|
||||||
|
-quiet \
|
||||||
|
clean build
|
||||||
|
mkdir -p Cryptomator.app/Contents/PlugIns
|
||||||
|
cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin Cryptomator.app/Contents/PlugIns/
|
||||||
|
rm -rf ${DERIVED_DATA_PATH}
|
||||||
- name: Generate license for dmg
|
- name: Generate license for dmg
|
||||||
run: >
|
run: >
|
||||||
mvn -B -Djavafx.platform=mac license:add-third-party
|
mvn -B license:add-third-party
|
||||||
-Dlicense.thirdPartyFilename=license.rtf
|
-Dlicense.thirdPartyFilename=license.rtf
|
||||||
-Dlicense.outputDirectory=dist/mac/dmg/resources
|
-Dlicense.outputDirectory=dist/mac/dmg/resources
|
||||||
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
|
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
|
||||||
@@ -248,7 +262,7 @@ jobs:
|
|||||||
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
||||||
- name: Notarize .dmg
|
- name: Notarize .dmg
|
||||||
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
|
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
|
||||||
uses: cocoalibs/xcode-notarization-action@v1
|
uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3
|
||||||
with:
|
with:
|
||||||
app-path: 'Cryptomator-*.dmg'
|
app-path: 'Cryptomator-*.dmg'
|
||||||
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
||||||
@@ -269,7 +283,7 @@ jobs:
|
|||||||
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: dmg-${{ matrix.output-suffix }}
|
name: dmg-${{ matrix.output-suffix }}
|
||||||
path: |
|
path: |
|
||||||
@@ -278,7 +292,7 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Publish dmg on GitHub Releases
|
- name: Publish dmg on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'temurin'
|
||||||
JAVA_VERSION: '24.0.1+9'
|
JAVA_VERSION: '25.0.1+8.0.LTS'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get-version:
|
get-version:
|
||||||
@@ -42,12 +42,12 @@ jobs:
|
|||||||
architecture: aarch64
|
architecture: aarch64
|
||||||
output-suffix: arm64
|
output-suffix: arm64
|
||||||
fuse-lib: FUSE-T
|
fuse-lib: FUSE-T
|
||||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_osx-aarch64_bin-jmods.zip'
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-aarch64_bin-jmods.zip'
|
||||||
openjfx-sha: 'b5a94a13077507003fa852512bfa33f4fb680bc8076d8002e4227a84c85171d4'
|
openjfx-sha: '13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
- name: Set version
|
- name: Set version
|
||||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests
|
run: mvn -B clean package -Pmac -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
@@ -97,7 +97,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
|
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -119,7 +119,6 @@ jobs:
|
|||||||
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||||
--java-options "--enable-preview"
|
--java-options "--enable-preview"
|
||||||
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
|
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
|
||||||
--java-options "--sun-misc-unsafe-memory-access=allow"
|
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dfile.encoding=\"utf-8\""
|
--java-options "-Dfile.encoding=\"utf-8\""
|
||||||
@@ -135,6 +134,7 @@ jobs:
|
|||||||
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
|
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||||
|
--java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism"
|
||||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
|
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
|
||||||
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log"
|
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log"
|
||||||
--mac-package-identifier org.cryptomator
|
--mac-package-identifier org.cryptomator
|
||||||
@@ -150,9 +150,23 @@ jobs:
|
|||||||
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||||
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
||||||
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
|
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
|
||||||
|
- name: Build and install DockTilePlugin
|
||||||
|
env:
|
||||||
|
DERIVED_DATA_PATH: dist/mac/DockTilePlugin/build
|
||||||
|
run: |
|
||||||
|
xcodebuild -project dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj \
|
||||||
|
-scheme DockTilePlugin \
|
||||||
|
-configuration Release \
|
||||||
|
-destination "platform=macOS,arch=arm64" \
|
||||||
|
-derivedDataPath ${DERIVED_DATA_PATH} \
|
||||||
|
-quiet \
|
||||||
|
clean build
|
||||||
|
mkdir -p Cryptomator.app/Contents/PlugIns
|
||||||
|
cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin Cryptomator.app/Contents/PlugIns/
|
||||||
|
rm -rf ${DERIVED_DATA_PATH}
|
||||||
- name: Generate license for dmg
|
- name: Generate license for dmg
|
||||||
run: >
|
run: >
|
||||||
mvn -B -Djavafx.platform=mac license:add-third-party
|
mvn -B license:add-third-party
|
||||||
-Dlicense.thirdPartyFilename=license.rtf
|
-Dlicense.thirdPartyFilename=license.rtf
|
||||||
-Dlicense.outputDirectory=dist/mac/dmg/resources
|
-Dlicense.outputDirectory=dist/mac/dmg/resources
|
||||||
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
|
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
|
||||||
@@ -247,7 +261,7 @@ jobs:
|
|||||||
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
||||||
- name: Notarize .dmg
|
- name: Notarize .dmg
|
||||||
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
|
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
|
||||||
uses: cocoalibs/xcode-notarization-action@v1
|
uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3
|
||||||
with:
|
with:
|
||||||
app-path: 'Cryptomator-*.dmg'
|
app-path: 'Cryptomator-*.dmg'
|
||||||
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
||||||
@@ -268,7 +282,7 @@ jobs:
|
|||||||
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: dmg-${{ matrix.output-suffix }}
|
name: dmg-${{ matrix.output-suffix }}
|
||||||
path: |
|
path: |
|
||||||
@@ -277,7 +291,7 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Publish dmg on GitHub Releases
|
- name: Publish dmg on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v10
|
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 14
|
days-before-stale: 14
|
||||||
days-before-close: 0
|
days-before-close: 0
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ jobs:
|
|||||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
- name: Publish asc on GitHub Releases
|
- name: Publish asc on GitHub Releases
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
files: |
|
files: |
|
||||||
cryptomator-*.tar.gz.asc
|
cryptomator-*.tar.gz.asc
|
||||||
- name: Slack Notification
|
- name: Slack Notification
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
SLACK_USERNAME: 'Cryptobot'
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'temurin'
|
||||||
JAVA_VERSION: 24
|
JAVA_VERSION: 25
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -16,11 +16,11 @@ jobs:
|
|||||||
name: Compile and Test
|
name: Compile and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- uses: actions/setup-java@v5
|
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
run: xvfb-run mvn -B clean install jacoco:report -Pcoverage -Djavafx.platform=linux
|
run: xvfb-run mvn -B clean install jacoco:report -Pcoverage
|
||||||
@@ -12,16 +12,16 @@ defaults:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'temurin'
|
||||||
JAVA_VERSION: 23
|
JAVA_VERSION: 25
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-preconditions:
|
check-preconditions:
|
||||||
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
- name: Cache NVD DB
|
- name: Cache NVD DB
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository/org/owasp/dependency-check-data/
|
path: ~/.m2/repository/org/owasp/dependency-check-data/
|
||||||
key: dependency-check-${{ github.run_id }}
|
key: dependency-check-${{ github.run_id }}
|
||||||
@@ -60,6 +60,6 @@ jobs:
|
|||||||
- name: Run org.owasp:dependency-check plugin
|
- name: Run org.owasp:dependency-check plugin
|
||||||
id: dependency-check
|
id: dependency-check
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: mvn -B verify -Pdependency-check -DskipTests -Djavafx.platform=linux
|
run: mvn -B verify -Pdependency-check -DskipTests
|
||||||
env:
|
env:
|
||||||
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
|
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
|
||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v10
|
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 365
|
days-before-stale: 365
|
||||||
days-before-close: 90
|
days-before-close: 90
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ on:
|
|||||||
version:
|
version:
|
||||||
description: 'Version'
|
description: 'Version'
|
||||||
required: false
|
required: false
|
||||||
isDebug:
|
|
||||||
description: 'Build debug version with console output'
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
sign:
|
sign:
|
||||||
description: 'Sign binaries'
|
description: 'Sign binaries'
|
||||||
required: false
|
required: false
|
||||||
@@ -26,8 +22,8 @@ on:
|
|||||||
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_windows-x64_bin-jmods.zip'
|
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_windows-x64_bin-jmods.zip'
|
||||||
OPENJFX_JMODS_AMD64_HASH: 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f'
|
OPENJFX_JMODS_AMD64_HASH: 'c8eb9fd039b00e0020cf6c3db8ed7876bf3ee4d27860aa697a247b83b8296ae7'
|
||||||
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
|
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
|
||||||
WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a'
|
WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a'
|
||||||
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
|
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
|
||||||
@@ -51,18 +47,13 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- arch: x64
|
- arch: x64
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
java-dist: 'zulu'
|
java-dist: 'zulu' #cannot use temurin, see https://github.com/cryptomator/cryptomator/issues/3824#issuecomment-2829827427
|
||||||
java-version: '24.0.1+9'
|
java-version: '25.0.1+8'
|
||||||
java-package: 'jdk'
|
java-package: 'jdk'
|
||||||
- arch: arm64
|
|
||||||
os: windows-11-arm
|
|
||||||
java-dist: 'liberica'
|
|
||||||
java-version: '24.0.1+11'
|
|
||||||
java-package: 'jdk+fx' #This is needed, as liberica contains JFX 24 Jmods for Windows ARM64
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ matrix.java-dist }}
|
distribution: ${{ matrix.java-dist }}
|
||||||
java-version: ${{ matrix.java-version }}
|
java-version: ${{ matrix.java-version }}
|
||||||
@@ -102,7 +93,7 @@ jobs:
|
|||||||
- name: Set version
|
- name: Set version
|
||||||
run: mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
run: mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pwin -DskipTests -Djavafx.platform=win
|
run: mvn -B clean package -Pwin -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE.txt target
|
cp LICENSE.txt target
|
||||||
@@ -122,7 +113,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
|
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -144,7 +135,6 @@ jobs:
|
|||||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
--java-options "--enable-preview"
|
--java-options "--enable-preview"
|
||||||
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.win,org.cryptomator.integrations.win"
|
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.win,org.cryptomator.integrations.win"
|
||||||
--java-options "--sun-misc-unsafe-memory-access=allow"
|
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||||
@@ -198,9 +188,19 @@ jobs:
|
|||||||
New-Item -Path appdir/jpackage-jmod -ItemType Directory
|
New-Item -Path appdir/jpackage-jmod -ItemType Directory
|
||||||
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
|
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
|
||||||
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
|
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
|
||||||
|
- name: Sign DLLs with Azure Trusted Signing
|
||||||
|
if: inputs.sign || github.event_name == 'release'
|
||||||
|
uses: ./.github/actions/win-sign-action
|
||||||
|
with:
|
||||||
|
base-dir: ${{ github.workspace }}\appdir
|
||||||
|
recursive: true
|
||||||
|
append-signature: true
|
||||||
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
- name: Sign DLLs with Actalis CodeSigner
|
- name: Sign DLLs with Actalis CodeSigner
|
||||||
if: inputs.sign || github.event_name == 'release'
|
if: inputs.sign || github.event_name == 'release'
|
||||||
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f
|
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
|
||||||
with:
|
with:
|
||||||
base-dir: 'appdir'
|
base-dir: 'appdir'
|
||||||
file-extensions: 'dll,exe,ps1'
|
file-extensions: 'dll,exe,ps1'
|
||||||
@@ -225,7 +225,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
- name: Generate license for MSI
|
- name: Generate license for MSI
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party "-Djavafx.platform=win"
|
mvn -B license:add-third-party
|
||||||
"-Dlicense.thirdPartyFilename=license.rtf"
|
"-Dlicense.thirdPartyFilename=license.rtf"
|
||||||
"-Dlicense.outputDirectory=dist/win/resources"
|
"-Dlicense.outputDirectory=dist/win/resources"
|
||||||
"-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl"
|
"-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl"
|
||||||
@@ -257,16 +257,16 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
||||||
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
|
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
|
||||||
- name: Sign msi with Actalis CodeSigner
|
- name: Sign MSI with Azure Trusted Signing
|
||||||
if: inputs.sign || github.event_name == 'release'
|
if: inputs.sign || github.event_name == 'release'
|
||||||
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f
|
uses: ./.github/actions/win-sign-action
|
||||||
with:
|
with:
|
||||||
base-dir: 'installer'
|
base-dir: ${{ github.workspace }}\installer
|
||||||
file-extensions: 'msi'
|
file-extensions: msi
|
||||||
sign-description: 'Cryptomator Installer'
|
description: 'Cryptomator Installer'
|
||||||
sign-url: 'https://cryptomator.org'
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
username: ${{ secrets.WIN_CODESIGN_USERNAME }}
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_PW }}
|
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
- name: Add possible alpha/beta tags and architecture to installer name
|
- name: Add possible alpha/beta tags and architecture to installer name
|
||||||
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.arch }}.msi
|
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.arch }}.msi
|
||||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||||
@@ -277,7 +277,7 @@ jobs:
|
|||||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: msi-${{ matrix.arch }}
|
name: msi-${{ matrix.arch }}
|
||||||
path: |
|
path: |
|
||||||
@@ -298,28 +298,22 @@ jobs:
|
|||||||
java-dist: 'zulu'
|
java-dist: 'zulu'
|
||||||
java-version: '24.0.1+9'
|
java-version: '24.0.1+9'
|
||||||
java-package: 'jdk'
|
java-package: 'jdk'
|
||||||
- arch: arm64
|
|
||||||
os: windows-11-arm
|
|
||||||
executable-suffix: arm64
|
|
||||||
java-dist: 'liberica'
|
|
||||||
java-version: '24.0.1+11'
|
|
||||||
java-package: 'jdk+fx' #This is needed, as liberica contains JFX 24 Jmods for Windows ARM64
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Install wix and extensions
|
- name: Install wix and extensions
|
||||||
run: |
|
run: |
|
||||||
dotnet tool install --global wix --version 6.0.0
|
dotnet tool install --global wix --version 6.0.0
|
||||||
wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global
|
wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global
|
||||||
wix.exe extension add WixToolset.Util.wixext/6.0.0 --global
|
wix.exe extension add WixToolset.Util.wixext/6.0.0 --global
|
||||||
- name: Download .msi
|
- name: Download .msi
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: msi-${{ matrix.arch }}
|
name: msi-${{ matrix.arch }}
|
||||||
path: dist/win/bundle/resources
|
path: dist/win/bundle/resources
|
||||||
- name: Strip version info from msi file name
|
- name: Strip version info from msi file name
|
||||||
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
|
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: ${{ matrix.java-dist }}
|
distribution: ${{ matrix.java-dist }}
|
||||||
java-version: ${{ matrix.java-version }}
|
java-version: ${{ matrix.java-version }}
|
||||||
@@ -328,7 +322,7 @@ jobs:
|
|||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Generate license for exe
|
- name: Generate license for exe
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party "-Djavafx.platform=win"
|
mvn -B license:add-third-party
|
||||||
"-Dlicense.thirdPartyFilename=license.rtf"
|
"-Dlicense.thirdPartyFilename=license.rtf"
|
||||||
"-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl"
|
"-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl"
|
||||||
"-Dlicense.outputDirectory=dist/win/bundle/resources"
|
"-Dlicense.outputDirectory=dist/win/bundle/resources"
|
||||||
@@ -369,9 +363,20 @@ jobs:
|
|||||||
- name: Detach burn engine in preparation to sign
|
- name: Detach burn engine in preparation to sign
|
||||||
run: >
|
run: >
|
||||||
wix burn detach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe
|
wix burn detach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe
|
||||||
|
- name: Sign WiX burn engine with Azure Trusted Signing
|
||||||
|
if: inputs.sign || github.event_name == 'release'
|
||||||
|
uses: ./.github/actions/win-sign-action
|
||||||
|
with:
|
||||||
|
base-dir: ${{ github.workspace }}\tmp
|
||||||
|
file-extensions: exe
|
||||||
|
append-signature: true
|
||||||
|
description: 'Cryptomator Bundle Installer'
|
||||||
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
- name: Sign burn engine with Actalis CodeSigner
|
- name: Sign burn engine with Actalis CodeSigner
|
||||||
if: inputs.sign || github.event_name == 'release'
|
if: inputs.sign || github.event_name == 'release'
|
||||||
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f
|
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
|
||||||
with:
|
with:
|
||||||
base-dir: 'tmp'
|
base-dir: 'tmp'
|
||||||
file-extensions: 'exe'
|
file-extensions: 'exe'
|
||||||
@@ -382,9 +387,20 @@ jobs:
|
|||||||
- name: Reattach signed burn engine to installer
|
- name: Reattach signed burn engine to installer
|
||||||
run: >
|
run: >
|
||||||
wix burn reattach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe
|
wix burn reattach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe
|
||||||
|
- name: Sign EXE installer with Azure Trusted Signing
|
||||||
|
if: inputs.sign || github.event_name == 'release'
|
||||||
|
uses: ./.github/actions/win-sign-action
|
||||||
|
with:
|
||||||
|
base-dir: ${{ github.workspace }}\installer
|
||||||
|
file-extensions: exe
|
||||||
|
append-signature: true
|
||||||
|
description: 'Cryptomator Bundle Installer'
|
||||||
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
- name: Sign installer with Actalis CodeSigner
|
- name: Sign installer with Actalis CodeSigner
|
||||||
if: inputs.sign || github.event_name == 'release'
|
if: inputs.sign || github.event_name == 'release'
|
||||||
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f
|
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
|
||||||
with:
|
with:
|
||||||
base-dir: 'installer'
|
base-dir: 'installer'
|
||||||
file-extensions: 'exe'
|
file-extensions: 'exe'
|
||||||
@@ -402,7 +418,7 @@ jobs:
|
|||||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: exe-${{ matrix.executable-suffix }}
|
name: exe-${{ matrix.executable-suffix }}
|
||||||
path: |
|
path: |
|
||||||
@@ -417,26 +433,22 @@ jobs:
|
|||||||
needs: [ build-msi, build-exe ]
|
needs: [ build-msi, build-exe ]
|
||||||
outputs:
|
outputs:
|
||||||
download-url-msi-x64: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }}
|
download-url-msi-x64: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }}
|
||||||
download-url-msi-arm64: ${{ fromJSON(steps.publish.outputs.assets)[1].browser_download_url }}
|
|
||||||
download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }}
|
download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }}
|
||||||
download-url-exe-arm64: ${{ fromJSON(steps.publish.outputs.assets)[3].browser_download_url }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download installers
|
- name: Download installers
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Publish installers on GitHub Releases
|
- name: Publish installers on GitHub Releases
|
||||||
id: publish
|
id: publish
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
# do not change ordering of filelist, required for correct job output
|
# do not change ordering of filelist, required for correct job output
|
||||||
files: |
|
files: |
|
||||||
*x64.msi
|
*x64.msi
|
||||||
*arm64.msi
|
|
||||||
*x64.exe
|
*x64.exe
|
||||||
*arm64.exe
|
|
||||||
*.asc
|
*.asc
|
||||||
|
|
||||||
allowlist-msi-x64:
|
allowlist-msi-x64:
|
||||||
@@ -446,13 +458,6 @@ jobs:
|
|||||||
url: ${{ needs.publish.outputs.download-url-msi-x64 }}
|
url: ${{ needs.publish.outputs.download-url-msi-x64 }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
allowlist-msi-arm64:
|
|
||||||
uses: ./.github/workflows/av-whitelist.yml
|
|
||||||
needs: [ publish ]
|
|
||||||
with:
|
|
||||||
url: ${{ needs.publish.outputs.download-url-msi-arm64 }}
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
allowlist-exe-x64:
|
allowlist-exe-x64:
|
||||||
uses: ./.github/workflows/av-whitelist.yml
|
uses: ./.github/workflows/av-whitelist.yml
|
||||||
needs: [ publish, allowlist-msi-x64 ]
|
needs: [ publish, allowlist-msi-x64 ]
|
||||||
@@ -460,13 +465,6 @@ jobs:
|
|||||||
url: ${{ needs.publish.outputs.download-url-exe-x64 }}
|
url: ${{ needs.publish.outputs.download-url-exe-x64 }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
allowlist-exe-arm64:
|
|
||||||
uses: ./.github/workflows/av-whitelist.yml
|
|
||||||
needs: [ publish, allowlist-msi-arm64 ]
|
|
||||||
with:
|
|
||||||
url: ${{ needs.publish.outputs.download-url-exe-arm64 }}
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
notify-winget:
|
notify-winget:
|
||||||
name: Notify for winget-release
|
name: Notify for winget-release
|
||||||
if: needs.get-version.outputs.versionType == 'stable'
|
if: needs.get-version.outputs.versionType == 'stable'
|
||||||
@@ -474,7 +472,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Slack Notification
|
- name: Slack Notification
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
SLACK_USERNAME: 'Cryptobot'
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
|
||||||
- name: Submit package
|
- name: Submit package
|
||||||
uses: vedantmgoyal2009/winget-releaser@main
|
uses: vedantmgoyal2009/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # no_specific_version
|
||||||
with:
|
with:
|
||||||
identifier: Cryptomator.Cryptomator
|
identifier: Cryptomator.Cryptomator
|
||||||
version: ${{ inputs.tag }}
|
version: ${{ inputs.tag }}
|
||||||
|
|||||||
Generated
+11
-10
@@ -14,16 +14,15 @@
|
|||||||
<option name="dagger.fastInit" value="enabled" />
|
<option name="dagger.fastInit" value="enabled" />
|
||||||
<option name="dagger.formatGeneratedSource" value="enabled" />
|
<option name="dagger.formatGeneratedSource" value="enabled" />
|
||||||
<processorPath useClasspath="false">
|
<processorPath useClasspath="false">
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.55/dagger-compiler-2.55.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.57.2/dagger-compiler-2.57.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.55/dagger-2.55.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.57.2/dagger-2.57.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.55/dagger-spi-2.55.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.57.2/dagger-spi-2.57.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.0.21-1.0.28/symbol-processing-api-2.0.21-1.0.28.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.1.21-2.0.2/symbol-processing-api-2.1.21-2.0.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-jre.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-jre.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
||||||
@@ -31,14 +30,16 @@
|
|||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.23.0/error_prone_annotations-2.23.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.23.0/error_prone_annotations-2.23.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-metadata-jvm/2.1.21/kotlin-metadata-jvm-2.1.21.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.21/kotlin-stdlib-2.1.21.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||||
</processorPath>
|
</processorPath>
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
</profile>
|
</profile>
|
||||||
|
|||||||
+3
-5
@@ -1,10 +1,8 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
<inspection_tool class="Deprecation" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="DEPRECATED_ATTRIBUTES" />
|
||||||
<option name="processCode" value="true" />
|
<inspection_tool class="MarkedForRemoval" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="MARKED_FOR_REMOVAL_ATTRIBUTES" />
|
||||||
<option name="processLiterals" value="true" />
|
<inspection_tool class="RedundantScheduledForRemovalAnnotation" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="MARKED_FOR_REMOVAL_ATTRIBUTES" />
|
||||||
<option name="processComments" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_24" project-jdk-name="24" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="25" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
</envs>
|
</envs>
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="@{userhome}/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/Cryptomator/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/Cryptomator/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/Cryptomator" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/Cryptomator/Plugins" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" />
|
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="@{userhome}/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/Cryptomator/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/Cryptomator/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/Cryptomator" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/Cryptomator/Plugins" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
|
||||||
|
The changelog starts with version 1.19.0.
|
||||||
|
Changes to prior versions can be found on the [Github release page](https://github.com/cryptomator/cryptomator/releases).
|
||||||
|
|
||||||
|
## [Unreleased](https://github.com/cryptomator/cryptomator/compare/1.18.0...HEAD)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* New Self-Update Mechanism (#3948)
|
||||||
|
* Implemented `.dmg` update mechanism
|
||||||
|
* Implemented Flatpak update mechanism
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Built using JDK 25 (#4031)
|
||||||
|
* Modernized Templage for GitHub Releases
|
||||||
Vendored
+5
-5
@@ -19,16 +19,16 @@ if [[ ! "${CPU_ARCH}" =~ x86_64|aarch64 ]]; then echo "Platform ${CPU_ARCH} not
|
|||||||
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
||||||
|
|
||||||
# compile
|
# compile
|
||||||
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests -Djavafx.platform=linux
|
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
|
||||||
cp ../../../LICENSE.txt ../../../target
|
cp ../../../LICENSE.txt ../../../target
|
||||||
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
||||||
|
|
||||||
JAVAFX_VERSION=24.0.1
|
JAVAFX_VERSION=25
|
||||||
JAVAFX_ARCH="x64"
|
JAVAFX_ARCH="x64"
|
||||||
JAVAFX_JMODS_SHA256='425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c'
|
JAVAFX_JMODS_SHA256='96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
|
||||||
if [ "${CPU_ARCH}" = "aarch64" ]; then
|
if [ "${CPU_ARCH}" = "aarch64" ]; then
|
||||||
JAVAFX_ARCH="aarch64"
|
JAVAFX_ARCH="aarch64"
|
||||||
JAVAFX_JMODS_SHA256='7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b'
|
JAVAFX_JMODS_SHA256='951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# download javaFX jmods
|
# download javaFX jmods
|
||||||
@@ -62,7 +62,7 @@ ${JAVA_HOME}/bin/jlink \
|
|||||||
--verbose \
|
--verbose \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JMOD_PATHS}" \
|
--module-path "${JMOD_PATHS}" \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
|
|||||||
@@ -83,6 +83,9 @@
|
|||||||
</content_rating>
|
</content_rating>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release date="2025-11-12" version="1.18.0">
|
||||||
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.18.0</url>
|
||||||
|
</release>
|
||||||
<release date="2025-07-08" version="1.17.1">
|
<release date="2025-07-08" version="1.17.1">
|
||||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.17.1</url>
|
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.17.1</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
Vendored
+2
-2
@@ -2,7 +2,7 @@ Source: cryptomator
|
|||||||
Maintainer: Cryptobot <releases@cryptomator.org>
|
Maintainer: Cryptobot <releases@cryptomator.org>
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-24 (>= 24.0.1+9-0ppa3), libgtk-3-0, libxxf86vm1, libgl1
|
Build-Depends: debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1
|
||||||
Standards-Version: 4.5.0
|
Standards-Version: 4.5.0
|
||||||
Homepage: https://cryptomator.org
|
Homepage: https://cryptomator.org
|
||||||
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
||||||
@@ -12,7 +12,7 @@ Package: cryptomator
|
|||||||
Architecture: any
|
Architecture: any
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3
|
Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3, libgtk-3-0 (>= 3.20.0)
|
||||||
Recommends: gvfs-backends, gvfs-fuse, gnome-keyring
|
Recommends: gvfs-backends, gvfs-fuse, gnome-keyring
|
||||||
XB-AppName: Cryptomator
|
XB-AppName: Cryptomator
|
||||||
XB-Category: Utility;Security;FileTools;
|
XB-Category: Utility;Security;FileTools;
|
||||||
|
|||||||
Vendored
+3
-3
@@ -4,11 +4,12 @@
|
|||||||
# Uncomment this to turn on verbose mode.
|
# Uncomment this to turn on verbose mode.
|
||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
JAVA_HOME = /usr/lib/jvm/java-24-coffeelibs
|
|
||||||
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
||||||
ifeq ($(DEB_BUILD_ARCH),amd64)
|
ifeq ($(DEB_BUILD_ARCH),amd64)
|
||||||
|
JAVA_HOME = /usr/lib/jvm/java-25-openjdk-amd64
|
||||||
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
|
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
|
||||||
else ifeq ($(DEB_BUILD_ARCH),arm64)
|
else ifeq ($(DEB_BUILD_ARCH),arm64)
|
||||||
|
JAVA_HOME = /usr/lib/jvm/java-25-openjdk-arm64
|
||||||
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
|
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ override_dh_auto_build:
|
|||||||
$(JAVA_HOME)/bin/jlink \
|
$(JAVA_HOME)/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JMODS_PATH}" \
|
--module-path "${JMODS_PATH}" \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
@@ -45,7 +46,6 @@ override_dh_auto_build:
|
|||||||
--vendor "Skymatic GmbH" \
|
--vendor "Skymatic GmbH" \
|
||||||
--java-options "--enable-preview" \
|
--java-options "--enable-preview" \
|
||||||
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
|
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
|
||||||
--java-options "--sun-misc-unsafe-memory-access=allow" \
|
|
||||||
--copyright "(C) 2016 - 2025 Skymatic GmbH" \
|
--copyright "(C) 2016 - 2025 Skymatic GmbH" \
|
||||||
--java-options "-Xss5m" \
|
--java-options "-Xss5m" \
|
||||||
--java-options "-Xmx256m" \
|
--java-options "-Xmx256m" \
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
embedded.provisionprofile
|
embedded.provisionprofile
|
||||||
|
xcuserdata/
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// CryptomatorDockTilePlugin.swift
|
||||||
|
// Integrations
|
||||||
|
//
|
||||||
|
// Created by Tobias Hagemann on 22.09.25.
|
||||||
|
// Copyright © 2025 Cryptomator. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
class CryptomatorDockTilePlugin: NSObject, NSDockTilePlugIn {
|
||||||
|
func setDockTile(_ dockTile: NSDockTile?) {
|
||||||
|
guard let dockTile = dockTile, let image = Bundle(for: Self.self).image(forResource: "Cryptomator") else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dockTile.contentView = NSImageView(image: image)
|
||||||
|
dockTile.display()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 77;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
74E08DE12E8584DE007E665C /* CryptomatorDockTilePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */; };
|
||||||
|
74E08DED2E858532007E665C /* Cryptomator.icns in Resources */ = {isa = PBXBuildFile; fileRef = 74E08DEC2E858532007E665C /* Cryptomator.icns */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
74E08DD92E858467007E665C /* Cryptomator.docktileplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Cryptomator.docktileplugin; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptomatorDockTilePlugin.swift; sourceTree = "<group>"; };
|
||||||
|
74E08DEC2E858532007E665C /* Cryptomator.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = Cryptomator.icns; path = ../resources/Cryptomator.icns; sourceTree = SOURCE_ROOT; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
74E08DD62E858467007E665C /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
74E08DD02E858467007E665C = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */,
|
||||||
|
74E08DEC2E858532007E665C /* Cryptomator.icns */,
|
||||||
|
74E08DDA2E858467007E665C /* Products */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
74E08DDA2E858467007E665C /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
74E08DD92E858467007E665C /* Cryptomator.docktileplugin */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
74E08DD82E858467007E665C /* DockTilePlugin */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 74E08DDD2E858467007E665C /* Build configuration list for PBXNativeTarget "DockTilePlugin" */;
|
||||||
|
buildPhases = (
|
||||||
|
74E08DD52E858467007E665C /* Sources */,
|
||||||
|
74E08DD62E858467007E665C /* Frameworks */,
|
||||||
|
74E08DD72E858467007E665C /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = DockTilePlugin;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = DockTilePlugin;
|
||||||
|
productReference = 74E08DD92E858467007E665C /* Cryptomator.docktileplugin */;
|
||||||
|
productType = "com.apple.product-type.bundle";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
74E08DD12E858467007E665C /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = 1;
|
||||||
|
LastUpgradeCheck = 2600;
|
||||||
|
ORGANIZATIONNAME = Cryptomator;
|
||||||
|
TargetAttributes = {
|
||||||
|
74E08DD82E858467007E665C = {
|
||||||
|
CreatedOnToolsVersion = 26.0.1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 74E08DD42E858467007E665C /* Build configuration list for PBXProject "DockTilePlugin" */;
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 74E08DD02E858467007E665C;
|
||||||
|
minimizedProjectReferenceProxies = 1;
|
||||||
|
preferredProjectObjectVersion = 77;
|
||||||
|
productRefGroup = 74E08DDA2E858467007E665C /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
74E08DD82E858467007E665C /* DockTilePlugin */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
74E08DD72E858467007E665C /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74E08DED2E858532007E665C /* Cryptomator.icns in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
74E08DD52E858467007E665C /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74E08DE12E8584DE007E665C /* CryptomatorDockTilePlugin.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
74E08DDB2E858467007E665C /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
DEVELOPMENT_TEAM = YZQJQUHA3L;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
74E08DDC2E858467007E665C /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEVELOPMENT_TEAM = YZQJQUHA3L;
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
74E08DDE2E858467007E665C /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Cryptomator. All rights reserved.";
|
||||||
|
INFOPLIST_KEY_NSPrincipalClass = CryptomatorDockTilePlugin;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.DockTilePlugin;
|
||||||
|
PRODUCT_NAME = Cryptomator;
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
WRAPPER_EXTENSION = docktileplugin;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
74E08DDF2E858467007E665C /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Cryptomator. All rights reserved.";
|
||||||
|
INFOPLIST_KEY_NSPrincipalClass = CryptomatorDockTilePlugin;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.DockTilePlugin;
|
||||||
|
PRODUCT_NAME = Cryptomator;
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
WRAPPER_EXTENSION = docktileplugin;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
74E08DD42E858467007E665C /* Build configuration list for PBXProject "DockTilePlugin" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
74E08DDB2E858467007E665C /* Debug */,
|
||||||
|
74E08DDC2E858467007E665C /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
74E08DDD2E858467007E665C /* Build configuration list for PBXNativeTarget "DockTilePlugin" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
74E08DDE2E858467007E665C /* Debug */,
|
||||||
|
74E08DDF2E858467007E665C /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 74E08DD12E858467007E665C /* Project object */;
|
||||||
|
}
|
||||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
Vendored
+67
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "74E08DD82E858467007E665C"
|
||||||
|
BuildableName = "Cryptomator.docktileplugin"
|
||||||
|
BlueprintName = "DockTilePlugin"
|
||||||
|
ReferencedContainer = "container:DockTilePlugin.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "74E08DD82E858467007E665C"
|
||||||
|
BuildableName = "Cryptomator.docktileplugin"
|
||||||
|
BlueprintName = "DockTilePlugin"
|
||||||
|
ReferencedContainer = "container:DockTilePlugin.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
Vendored
+20
-6
@@ -32,15 +32,15 @@ REVISION_NO=`git rev-list --count HEAD`
|
|||||||
VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'`
|
VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'`
|
||||||
FUSE_LIB="FUSE-T"
|
FUSE_LIB="FUSE-T"
|
||||||
|
|
||||||
JAVAFX_VERSION=24.0.1
|
JAVAFX_VERSION=25
|
||||||
JAVAFX_ARCH="undefined"
|
JAVAFX_ARCH="undefined"
|
||||||
JAVAFX_JMODS_SHA256="undefined"
|
JAVAFX_JMODS_SHA256="undefined"
|
||||||
if [ "$(machine)" = "arm64e" ]; then
|
if [ "$(machine)" = "arm64e" ]; then
|
||||||
JAVAFX_ARCH="aarch64"
|
JAVAFX_ARCH="aarch64"
|
||||||
JAVAFX_JMODS_SHA256="b5a94a13077507003fa852512bfa33f4fb680bc8076d8002e4227a84c85171d4"
|
JAVAFX_JMODS_SHA256="13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba"
|
||||||
else
|
else
|
||||||
JAVAFX_ARCH="x64"
|
JAVAFX_ARCH="x64"
|
||||||
JAVAFX_JMODS_SHA256="6e62a426d43c168a488521f904a523f3dd6ee2cf103e08136f2fd465c828a105"
|
JAVAFX_JMODS_SHA256="0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52"
|
||||||
fi
|
fi
|
||||||
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip"
|
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip"
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# compile
|
# compile
|
||||||
mvn -B -Djavafx.platform=mac -f../../../pom.xml clean package -DskipTests -Pmac
|
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
|
||||||
cp ../../../LICENSE.txt ../../../target
|
cp ../../../LICENSE.txt ../../../target
|
||||||
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
|
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ fi
|
|||||||
${JAVA_HOME}/bin/jlink \
|
${JAVA_HOME}/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JMOD_PATHS}" \
|
--module-path "${JMOD_PATHS}" \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,java.compiler \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,java.compiler \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
@@ -123,6 +123,7 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \
|
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support${APP_NAME}/mnt\"" \
|
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support${APP_NAME}/mnt\"" \
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true" \
|
--java-options "-Dcryptomator.showTrayIcon=true" \
|
||||||
|
--java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism" \
|
||||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
|
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
|
||||||
--mac-package-identifier ${PACKAGE_IDENTIFIER} \
|
--mac-package-identifier ${PACKAGE_IDENTIFIER} \
|
||||||
--resource-dir ../resources
|
--resource-dir ../resources
|
||||||
@@ -133,8 +134,21 @@ sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" ${APP_NAME}.app/
|
|||||||
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" ${APP_NAME}.app/Contents/Info.plist
|
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" ${APP_NAME}.app/Contents/Info.plist
|
||||||
cp ../embedded.provisionprofile ${APP_NAME}.app/Contents/
|
cp ../embedded.provisionprofile ${APP_NAME}.app/Contents/
|
||||||
|
|
||||||
|
# build and install dock tile plugin
|
||||||
|
echo "Building and installing Cryptomator.docktileplugin..."
|
||||||
|
DERIVED_DATA_PATH=../DockTilePlugin/build
|
||||||
|
xcodebuild -project ../DockTilePlugin/DockTilePlugin.xcodeproj \
|
||||||
|
-scheme DockTilePlugin \
|
||||||
|
-configuration Release \
|
||||||
|
-derivedDataPath ${DERIVED_DATA_PATH} \
|
||||||
|
-quiet \
|
||||||
|
clean build
|
||||||
|
mkdir -p ${APP_NAME}.app/Contents/PlugIns
|
||||||
|
cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin ${APP_NAME}.app/Contents/PlugIns/
|
||||||
|
rm -rf ${DERIVED_DATA_PATH}
|
||||||
|
|
||||||
# generate license
|
# generate license
|
||||||
mvn -B -Djavafx.platform=mac -f../../../pom.xml license:add-third-party \
|
mvn -B -f../../../pom.xml license:add-third-party \
|
||||||
-Dlicense.thirdPartyFilename=license.rtf \
|
-Dlicense.thirdPartyFilename=license.rtf \
|
||||||
-Dlicense.outputDirectory=dist/mac/dmg/resources \
|
-Dlicense.outputDirectory=dist/mac/dmg/resources \
|
||||||
-Dlicense.fileTemplate=resources/licenseTemplate.ftl \
|
-Dlicense.fileTemplate=resources/licenseTemplate.ftl \
|
||||||
|
|||||||
Vendored
+3
@@ -116,5 +116,8 @@
|
|||||||
<!-- allow utilization of integrated GPU, see https://developer.apple.com/library/mac/qa/qa1734/_index.html -->
|
<!-- allow utilization of integrated GPU, see https://developer.apple.com/library/mac/qa/qa1734/_index.html -->
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<!-- register dock tile plugin -->
|
||||||
|
<key>NSDockTilePlugIn</key>
|
||||||
|
<string>Cryptomator.docktileplugin</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Vendored
+7
-7
@@ -65,7 +65,7 @@ Write-Host "`$Env:JAVA_HOME=$Env:JAVA_HOME"
|
|||||||
$copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor"
|
$copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor"
|
||||||
|
|
||||||
# compile
|
# compile
|
||||||
&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin "-Djavafx.platform=win"
|
&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin
|
||||||
Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods"
|
Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods"
|
||||||
|
|
||||||
# add runtime
|
# add runtime
|
||||||
@@ -86,16 +86,16 @@ switch ($archName) {
|
|||||||
'ARM64' {
|
'ARM64' {
|
||||||
$javafxBaseJmod = Join-Path $Env:JAVA_HOME "jmods\javafx.base.jmod"
|
$javafxBaseJmod = Join-Path $Env:JAVA_HOME "jmods\javafx.base.jmod"
|
||||||
if (!(Test-Path $javafxBaseJmod)) {
|
if (!(Test-Path $javafxBaseJmod)) {
|
||||||
Write-Error "JavaFX module not found in JDK. Please ensure full JDK (including jmods) is installed."
|
Write-Error "JavaFX module not found in JDK. Please ensure a JDK with JavaFX (including jmods) is installed."
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$jmodPaths = "$Env:JAVA_HOME/jmods"
|
$jmodPaths = "$Env:JAVA_HOME/jmods"
|
||||||
}
|
}
|
||||||
'x64' {
|
'x64' {
|
||||||
$javaFxVersion='24.0.1'
|
$javaFxVersion='25'
|
||||||
$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip"
|
$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip"
|
||||||
$javaFxJmodsSHA256 = 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f'
|
$javaFxJmodsSHA256 = 'c8eb9fd039b00e0020cf6c3db8ed7876bf3ee4d27860aa697a247b83b8296ae7'
|
||||||
$javaFxJmods = '.\resources\jfxJmods.zip'
|
$javaFxJmods = '.\resources\jfxJmods.zip'
|
||||||
|
|
||||||
if( !(Test-Path -Path $javaFxJmods) ) {
|
if( !(Test-Path -Path $javaFxJmods) ) {
|
||||||
@@ -133,7 +133,7 @@ if ((& "$Env:JAVA_HOME\bin\jlink" --help | Select-String -Pattern "Linking from
|
|||||||
--verbose `
|
--verbose `
|
||||||
--output runtime `
|
--output runtime `
|
||||||
--module-path $jmodPaths `
|
--module-path $jmodPaths `
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml `
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml `
|
||||||
--strip-native-commands `
|
--strip-native-commands `
|
||||||
--no-header-files `
|
--no-header-files `
|
||||||
--no-man-pages `
|
--no-man-pages `
|
||||||
@@ -194,7 +194,7 @@ if ($LASTEXITCODE -ne 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Create RTF license for msi
|
#Create RTF license for msi
|
||||||
&mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" `
|
&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
|
||||||
"-Dlicense.thirdPartyFilename=license.rtf" `
|
"-Dlicense.thirdPartyFilename=license.rtf" `
|
||||||
"-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" `
|
"-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" `
|
||||||
"-Dlicense.outputDirectory=$buildDir\resources\" `
|
"-Dlicense.outputDirectory=$buildDir\resources\" `
|
||||||
@@ -237,7 +237,7 @@ if ($LASTEXITCODE -ne 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Create RTF license for bundle
|
#Create RTF license for bundle
|
||||||
&mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" `
|
&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
|
||||||
"-Dlicense.thirdPartyFilename=license.rtf" `
|
"-Dlicense.thirdPartyFilename=license.rtf" `
|
||||||
"-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" `
|
"-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" `
|
||||||
"-Dlicense.outputDirectory=$buildDir\bundle\resources\" `
|
"-Dlicense.outputDirectory=$buildDir\bundle\resources\" `
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
Apache License v2.0|Apache License, Version 2.0|The Apache Software License, Version 2.0|Apache 2.0|Apache Software License - Version 2.0|Apache-2.0
|
Apache License v2.0|Apache License, Version 2.0|The Apache License, Version 2.0|The Apache Software License, Version 2.0|Apache 2.0|Apache Software License - Version 2.0|Apache-2.0
|
||||||
MIT License|The MIT License (MIT)|The MIT License|MIT license
|
MIT License|MIT|The MIT License (MIT)|The MIT License|MIT license
|
||||||
LGPL 2.1|LGPL, version 2.1|GNU Lesser/Library General Public License version 2|GNU Lesser General Public License Version 2.1
|
LGPL 2.1|LGPL, version 2.1|GNU Lesser/Library General Public License version 2|GNU Lesser General Public License Version 2.1|GNU Lesser General Public License
|
||||||
GPLv2|GNU General Public License Version 2
|
GPLv2|GNU General Public License Version 2
|
||||||
GPLv2+CE|CDDL + GPLv2 with classpath exception
|
GPLv2+CE|CDDL + GPLv2 with classpath exception
|
||||||
Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0
|
Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptomator</artifactId>
|
<artifactId>cryptomator</artifactId>
|
||||||
<version>1.18.0</version>
|
<version>1.19.0</version>
|
||||||
<name>Cryptomator Desktop App</name>
|
<name>Cryptomator Desktop App</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.jdk.version>24</project.jdk.version>
|
<project.jdk.version>25</project.jdk.version>
|
||||||
|
|
||||||
<!-- Group IDs of jars that need to stay on the class path for now -->
|
<!-- Group IDs of jars that need to stay on the class path for now -->
|
||||||
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
|
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
|
||||||
@@ -34,22 +34,22 @@
|
|||||||
|
|
||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version>
|
||||||
<cryptomator.integrations.version>1.7.0</cryptomator.integrations.version>
|
<cryptomator.integrations.version>1.8.0-beta1</cryptomator.integrations.version>
|
||||||
<cryptomator.integrations.win.version>1.5.1</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.5.1</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>1.4.1</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.5.0-beta2</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>1.6.1</cryptomator.integrations.linux.version>
|
<cryptomator.integrations.linux.version>1.7.0-beta2</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>5.1.0</cryptomator.fuse.version>
|
<cryptomator.fuse.version>5.1.0</cryptomator.fuse.version>
|
||||||
<cryptomator.webdav.version>3.0.0</cryptomator.webdav.version>
|
<cryptomator.webdav.version>3.0.0</cryptomator.webdav.version>
|
||||||
|
|
||||||
<!-- 3rd party dependencies -->
|
<!-- 3rd party dependencies -->
|
||||||
<commons-lang3.version>3.18.0</commons-lang3.version>
|
<commons-lang3.version>3.19.0</commons-lang3.version>
|
||||||
<dagger.version>2.57.1</dagger.version>
|
<dagger.version>2.57.2</dagger.version>
|
||||||
<easybind.version>2.2</easybind.version>
|
<easybind.version>2.2</easybind.version>
|
||||||
<jackson.version>2.20.0</jackson.version>
|
<jackson.version>2.20.0</jackson.version>
|
||||||
<javafx.version>24.0.1</javafx.version>
|
<javafx.version>25</javafx.version>
|
||||||
<jwt.version>4.5.0</jwt.version>
|
<jwt.version>4.5.0</jwt.version>
|
||||||
<nimbus-jose.version>9.37.3</nimbus-jose.version>
|
<nimbus-jose.version>10.5</nimbus-jose.version>
|
||||||
<logback.version>1.5.18</logback.version>
|
<logback.version>1.5.19</logback.version>
|
||||||
<slf4j.version>2.0.17</slf4j.version>
|
<slf4j.version>2.0.17</slf4j.version>
|
||||||
<tinyoauth2.version>0.8.1</tinyoauth2.version>
|
<tinyoauth2.version>0.8.1</tinyoauth2.version>
|
||||||
<zxcvbn.version>1.9.0</zxcvbn.version>
|
<zxcvbn.version>1.9.0</zxcvbn.version>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
<!-- build-time dependencies -->
|
<!-- build-time dependencies -->
|
||||||
<jetbrains.annotations.version>26.0.2-1</jetbrains.annotations.version>
|
<jetbrains.annotations.version>26.0.2-1</jetbrains.annotations.version>
|
||||||
<dependency-check.version>12.1.5</dependency-check.version>
|
<dependency-check.version>12.1.5</dependency-check.version>
|
||||||
<jacoco.version>0.8.13</jacoco.version>
|
<jacoco.version>0.8.14</jacoco.version>
|
||||||
<license-generator.version>2.7.0</license-generator.version>
|
<license-generator.version>2.7.0</license-generator.version>
|
||||||
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
|
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
|
||||||
<mvn-compiler.version>3.14.1</mvn-compiler.version>
|
<mvn-compiler.version>3.14.1</mvn-compiler.version>
|
||||||
@@ -75,6 +75,20 @@
|
|||||||
<surefire.jacoco.args></surefire.jacoco.args>
|
<surefire.jacoco.args></surefire.jacoco.args>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<name>Central Portal Snapshots</name>
|
||||||
|
<id>central-portal-snapshots</id>
|
||||||
|
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Cryptomator Libs -->
|
<!-- Cryptomator Libs -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ open module org.cryptomator.desktop {
|
|||||||
requires io.github.coffeelibs.tinyoauth2client;
|
requires io.github.coffeelibs.tinyoauth2client;
|
||||||
requires org.slf4j;
|
requires org.slf4j;
|
||||||
requires org.apache.commons.lang3;
|
requires org.apache.commons.lang3;
|
||||||
|
requires com.github.benmanes.caffeine;
|
||||||
|
|
||||||
/* dagger bs */
|
/* dagger bs */
|
||||||
requires jakarta.inject;
|
requires jakarta.inject;
|
||||||
requires static javax.inject;
|
requires static javax.inject;
|
||||||
requires java.compiler;
|
requires java.compiler;
|
||||||
requires com.github.benmanes.caffeine;
|
|
||||||
|
|
||||||
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
|
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
|
||||||
uses SSLContextProvider;
|
uses SSLContextProvider;
|
||||||
|
|||||||
@@ -74,13 +74,6 @@ public abstract class CommonsModule {
|
|||||||
return new MasterkeyFileAccess(Constants.PEPPER, csprng);
|
return new MasterkeyFileAccess(Constants.PEPPER, csprng);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@Named("SemVer")
|
|
||||||
static Comparator<String> providesSemVerComparator() {
|
|
||||||
return new SemVerComparator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static Optional<RevealPathService> provideRevealPathService() {
|
static Optional<RevealPathService> provideRevealPathService() {
|
||||||
|
|||||||
@@ -124,6 +124,15 @@ public class Environment {
|
|||||||
return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME));
|
return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the app version concatenated with the build number (if defined).
|
||||||
|
*
|
||||||
|
* @return version string formatted like {@code 1.2.3-4567} or {@code 1.2.3} if no build number is defined.
|
||||||
|
*/
|
||||||
|
public String getAppVersionWithBuildNumber() {
|
||||||
|
return getAppVersion() + getBuildNumber().map("-"::concat).orElse("");
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<Path> getPluginDir() {
|
public Optional<Path> getPluginDir() {
|
||||||
return getPath(PLUGIN_DIR_PROP_NAME);
|
return getPath(PLUGIN_DIR_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
|
|
||||||
* All rights reserved.
|
|
||||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*
|
|
||||||
* Contributors:
|
|
||||||
* Sebastian Stenzel - initial API and implementation
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.common;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares version strings according to <a href="http://semver.org/spec/v2.0.0.html">SemVer 2.0.0</a>.
|
|
||||||
*/
|
|
||||||
public class SemVerComparator implements Comparator<String> {
|
|
||||||
|
|
||||||
private static final char VERSION_SEP = '.'; // http://semver.org/spec/v2.0.0.html#spec-item-2
|
|
||||||
private static final String PRE_RELEASE_SEP = "-"; // http://semver.org/spec/v2.0.0.html#spec-item-9
|
|
||||||
private static final String BUILD_SEP = "+"; // http://semver.org/spec/v2.0.0.html#spec-item-10
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(String version1, String version2) {
|
|
||||||
// "Build metadata SHOULD be ignored when determining version precedence.
|
|
||||||
// Thus two versions that differ only in the build metadata, have the same precedence."
|
|
||||||
String v1WithoutBuildMetadata = StringUtils.substringBefore(version1, BUILD_SEP);
|
|
||||||
String v2WithoutBuildMetadata = StringUtils.substringBefore(version2, BUILD_SEP);
|
|
||||||
|
|
||||||
if (v1WithoutBuildMetadata.equals(v2WithoutBuildMetadata)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
String v1MajorMinorPatch = StringUtils.substringBefore(v1WithoutBuildMetadata, PRE_RELEASE_SEP);
|
|
||||||
String v2MajorMinorPatch = StringUtils.substringBefore(v2WithoutBuildMetadata, PRE_RELEASE_SEP);
|
|
||||||
String v1PreReleaseVersion = StringUtils.substringAfter(v1WithoutBuildMetadata, PRE_RELEASE_SEP);
|
|
||||||
String v2PreReleaseVersion = StringUtils.substringAfter(v2WithoutBuildMetadata, PRE_RELEASE_SEP);
|
|
||||||
return compare(v1MajorMinorPatch, v1PreReleaseVersion, v2MajorMinorPatch, v2PreReleaseVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int compare(String v1MajorMinorPatch, String v1PreReleaseVersion, String v2MajorMinorPatch, String v2PreReleaseVersion) {
|
|
||||||
int comparisonResult = compareNumericallyThenLexicographically(v1MajorMinorPatch, v2MajorMinorPatch);
|
|
||||||
if (comparisonResult == 0) {
|
|
||||||
if (v1PreReleaseVersion.isEmpty()) {
|
|
||||||
return 1; // 1.0.0 > 1.0.0-BETA
|
|
||||||
} else if (v2PreReleaseVersion.isEmpty()) {
|
|
||||||
return -1; // 1.0.0-BETA < 1.0.0
|
|
||||||
} else {
|
|
||||||
return compareNumericallyThenLexicographically(v1PreReleaseVersion, v2PreReleaseVersion);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return comparisonResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int compareNumericallyThenLexicographically(String version1, String version2) {
|
|
||||||
final String[] vComps1 = StringUtils.split(version1, VERSION_SEP);
|
|
||||||
final String[] vComps2 = StringUtils.split(version2, VERSION_SEP);
|
|
||||||
final int commonCompCount = Math.min(vComps1.length, vComps2.length);
|
|
||||||
|
|
||||||
for (int i = 0; i < commonCompCount; i++) {
|
|
||||||
int subversionComparisonResult = 0;
|
|
||||||
try {
|
|
||||||
final int v1 = Integer.parseInt(vComps1[i]);
|
|
||||||
final int v2 = Integer.parseInt(vComps2[i]);
|
|
||||||
subversionComparisonResult = v1 - v2;
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
// ok, lets compare this fragment lexicographically
|
|
||||||
subversionComparisonResult = vComps1[i].compareTo(vComps2[i]);
|
|
||||||
}
|
|
||||||
if (subversionComparisonResult != 0) {
|
|
||||||
return subversionComparisonResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// all in common so far? longest version string is considered the higher version:
|
|
||||||
return vComps1.length - vComps2.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.cryptomator.common.recovery;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.nio.file.attribute.FileTime;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
|
||||||
|
|
||||||
|
public final class BackupRestorer {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(BackupRestorer.class);
|
||||||
|
|
||||||
|
private BackupRestorer() {}
|
||||||
|
|
||||||
|
public static void restoreIfBackupPresent(Path vaultPath, String filePrefix) {
|
||||||
|
Path targetFile = vaultPath.resolve(filePrefix);
|
||||||
|
|
||||||
|
try (Stream<Path> files = Files.list(vaultPath)) {
|
||||||
|
files.filter(file -> isFileMatchingPattern(file.getFileName().toString(), filePrefix))
|
||||||
|
.max((f1, f2) -> {
|
||||||
|
try {
|
||||||
|
FileTime time1 = Files.getLastModifiedTime(f1);
|
||||||
|
FileTime time2 = Files.getLastModifiedTime(f2);
|
||||||
|
return time1.compareTo(time2);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ifPresent(backupFile -> copyBackupFile(backupFile, targetFile));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.info("Unable to restore backup files in '{}'", vaultPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isFileMatchingPattern(String fileName, String filePrefix) {
|
||||||
|
return fileName.startsWith(filePrefix) && fileName.endsWith(MASTERKEY_BACKUP_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copyBackupFile(Path backupFile, Path configPath) {
|
||||||
|
try {
|
||||||
|
Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
LOG.debug("Backup restored - file: '{}' path: '{}'", backupFile, configPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Unable to copy backup file from '{}' to '{}'", backupFile, configPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.cryptomator.common.recovery;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
|
||||||
|
|
||||||
|
public final class CryptoFsInitializer {
|
||||||
|
|
||||||
|
private CryptoFsInitializer() {}
|
||||||
|
|
||||||
|
public static void init(Path recoveryPath,
|
||||||
|
Masterkey masterkey,
|
||||||
|
int shorteningThreshold,
|
||||||
|
CryptorProvider.Scheme scheme) throws IOException, CryptoException {
|
||||||
|
|
||||||
|
MasterkeyLoader loader = ignored -> masterkey.copy();
|
||||||
|
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties //
|
||||||
|
.cryptoFileSystemProperties() //
|
||||||
|
.withCipherCombo(scheme) //
|
||||||
|
.withKeyLoader(loader) //
|
||||||
|
.withShorteningThreshold(shorteningThreshold) //
|
||||||
|
.build();
|
||||||
|
CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package org.cryptomator.common.recovery;
|
||||||
|
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
|
import org.cryptomator.cryptolib.api.Cryptor;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
|
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME;
|
||||||
|
|
||||||
|
public final class MasterkeyService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyService.class);
|
||||||
|
|
||||||
|
private MasterkeyService() {}
|
||||||
|
|
||||||
|
public static void recoverFromRecoveryKey(String recoveryKey, RecoveryKeyFactory recoveryKeyFactory, Path recoveryPath, CharSequence newPassword) throws IOException {
|
||||||
|
recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Masterkey load(MasterkeyFileAccess masterkeyFileAccess, Path masterkeyFilePath, CharSequence password) throws IOException {
|
||||||
|
return masterkeyFileAccess.load(masterkeyFilePath, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CryptorProvider.Scheme validateRecoveryKeyAndDetectCombo(RecoveryKeyFactory recoveryKeyFactory, //
|
||||||
|
Vault vault, String recoveryKey, //
|
||||||
|
MasterkeyFileAccess masterkeyFileAccess) throws IOException, CryptoException, NoSuchElementException {
|
||||||
|
String tmpPass = UUID.randomUUID().toString();
|
||||||
|
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||||
|
Path tempRecoveryPath = recoveryDirectory.getRecoveryPath();
|
||||||
|
recoverFromRecoveryKey(recoveryKey, recoveryKeyFactory, tempRecoveryPath, tmpPass);
|
||||||
|
Path masterkeyFilePath = tempRecoveryPath.resolve(MASTERKEY_FILENAME);
|
||||||
|
|
||||||
|
try (Masterkey mk = load(masterkeyFileAccess, masterkeyFilePath, tmpPass)) {
|
||||||
|
return detect(mk, vault.getPath()).orElseThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<CryptorProvider.Scheme> detect(Masterkey masterkey, Path vaultPath) {
|
||||||
|
try (Stream<Path> paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) {
|
||||||
|
Optional<Path> c9rFile = paths //
|
||||||
|
.filter(p -> p.toString().endsWith(".c9r")) //
|
||||||
|
.filter(p -> !p.endsWith("dir.c9r")) //
|
||||||
|
.findFirst();
|
||||||
|
if (c9rFile.isEmpty()) {
|
||||||
|
LOG.info("Unable to detect Crypto scheme: No *.c9r file found in {}", vaultPath);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return determineScheme(c9rFile.get(), masterkey);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.info("Unable to detect Crypto scheme: Failed to inspect vault", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Optional<CryptorProvider.Scheme> determineScheme(Path c9rFile, Masterkey masterkey) {
|
||||||
|
return Arrays.stream(CryptorProvider.Scheme.values()).filter(scheme -> {
|
||||||
|
try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey.copy(), SecureRandom.getInstanceStrong())) {
|
||||||
|
int headerSize = cryptor.fileHeaderCryptor().headerSize();
|
||||||
|
|
||||||
|
ByteBuffer headerBuf = ByteBuffer.allocate(headerSize);
|
||||||
|
|
||||||
|
try (FileChannel channel = FileChannel.open(c9rFile, StandardOpenOption.READ)) {
|
||||||
|
channel.read(headerBuf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBuf.flip();
|
||||||
|
cryptor.fileHeaderCryptor().decryptHeader(headerBuf.duplicate());
|
||||||
|
LOG.debug("Detected Crypto scheme: {}", scheme);
|
||||||
|
return true;
|
||||||
|
} catch (IllegalArgumentException | CryptoException e) {
|
||||||
|
LOG.debug("Could not decrypt with scheme: {}", scheme);
|
||||||
|
return false;
|
||||||
|
} catch (IOException | NoSuchAlgorithmException e) {
|
||||||
|
LOG.warn("Unable to detect Crypto scheme: Failed to decrypt .c9r file", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.cryptomator.common.recovery;
|
||||||
|
|
||||||
|
public enum RecoveryActionType {
|
||||||
|
RESTORE_ALL,
|
||||||
|
RESTORE_MASTERKEY,
|
||||||
|
RESTORE_VAULT_CONFIG,
|
||||||
|
RESET_PASSWORD,
|
||||||
|
SHOW_KEY,
|
||||||
|
CONVERT_VAULT
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.cryptomator.common.recovery;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public final class RecoveryDirectory implements AutoCloseable {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RecoveryDirectory.class);
|
||||||
|
|
||||||
|
private final Path recoveryPath;
|
||||||
|
private final Path vaultPath;
|
||||||
|
|
||||||
|
private RecoveryDirectory(Path vaultPath, Path recoveryPath) {
|
||||||
|
this.vaultPath = vaultPath;
|
||||||
|
this.recoveryPath = recoveryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RecoveryDirectory create(Path vaultPath) throws IOException {
|
||||||
|
Path tempDir = Files.createTempDirectory("cryptomator");
|
||||||
|
return new RecoveryDirectory(vaultPath, tempDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveRecoveredFile(String file) throws IOException {
|
||||||
|
Files.move(recoveryPath.resolve(file), vaultPath.resolve(file), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteRecoveryDirectory() {
|
||||||
|
try (var paths = Files.walk(recoveryPath)) {
|
||||||
|
paths.sorted(Comparator.reverseOrder()).forEach(p -> {
|
||||||
|
try {
|
||||||
|
Files.delete(p);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.info("Unable to delete {}. Please delete it manually.", p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to clean up recovery directory", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
deleteRecoveryDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getRecoveryPath() {
|
||||||
|
return recoveryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.cryptomator.common.recovery;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultComponent;
|
||||||
|
import org.cryptomator.common.vaults.VaultConfigCache;
|
||||||
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||||
|
|
||||||
|
public final class VaultPreparator {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VaultPreparator.class);
|
||||||
|
|
||||||
|
private VaultPreparator() {}
|
||||||
|
|
||||||
|
public static Vault prepareVault(Path selectedDirectory, //
|
||||||
|
VaultComponent.Factory vaultComponentFactory, //
|
||||||
|
List<MountService> mountServices, //
|
||||||
|
ResourceBundle resourceBundle) {
|
||||||
|
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||||
|
vaultSettings.path.set(selectedDirectory);
|
||||||
|
if (selectedDirectory.getFileName() != null) {
|
||||||
|
vaultSettings.displayName.set(selectedDirectory.getFileName().toString());
|
||||||
|
} else {
|
||||||
|
vaultSettings.displayName.set(resourceBundle.getString("defaults.vault.vaultName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrapper = new VaultConfigCache(vaultSettings);
|
||||||
|
Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, LOCKED, null).vault();
|
||||||
|
try {
|
||||||
|
VaultListManager.determineVaultState(vault.getPath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Failed to determine vault state for {}", vaultSettings.path.get(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//due to https://github.com/cryptomator/cryptomator/issues/2880#issuecomment-1680313498
|
||||||
|
var nameOfWinfspLocalMounter = "org.cryptomator.frontend.fuse.mount.WinFspMountProvider";
|
||||||
|
if (SystemUtils.IS_OS_WINDOWS && vaultSettings.path.get().toString().contains("Dropbox") && mountServices.stream().anyMatch(s -> s.getClass().getName().equals(nameOfWinfspLocalMounter))) {
|
||||||
|
vaultSettings.mountService.setValue(nameOfWinfspLocalMounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vault;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,10 +25,8 @@ import javafx.beans.property.StringProperty;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.NodeOrientation;
|
import javafx.geometry.NodeOrientation;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
|
|
||||||
@@ -53,6 +51,7 @@ public class Settings {
|
|||||||
static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
|
static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
|
||||||
public static final Instant DEFAULT_TIMESTAMP = Instant.parse("2000-01-01T00:00:00Z");
|
public static final Instant DEFAULT_TIMESTAMP = Instant.parse("2000-01-01T00:00:00Z");
|
||||||
|
|
||||||
|
private final SettingsProvider provider;
|
||||||
public final ObservableList<VaultSettings> directories;
|
public final ObservableList<VaultSettings> directories;
|
||||||
public final BooleanProperty startHidden;
|
public final BooleanProperty startHidden;
|
||||||
public final BooleanProperty autoCloseVaults;
|
public final BooleanProperty autoCloseVaults;
|
||||||
@@ -78,13 +77,12 @@ public class Settings {
|
|||||||
public final ObjectProperty<Instant> lastUpdateCheckReminder;
|
public final ObjectProperty<Instant> lastUpdateCheckReminder;
|
||||||
public final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
|
public final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
|
||||||
public final ObjectProperty<Path> previouslyUsedVaultDirectory;
|
public final ObjectProperty<Path> previouslyUsedVaultDirectory;
|
||||||
|
public final StringProperty lastUpdateAttemptedByVersion;
|
||||||
|
|
||||||
private Consumer<Settings> saveCmd;
|
public static Settings create(SettingsProvider provider, Environment env) {
|
||||||
|
|
||||||
public static Settings create(Environment env) {
|
|
||||||
var defaults = new SettingsJson();
|
var defaults = new SettingsJson();
|
||||||
defaults.showTrayIcon = env.showTrayIcon();
|
defaults.showTrayIcon = env.showTrayIcon();
|
||||||
return new Settings(defaults);
|
return new Settings(provider, defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,7 +90,8 @@ public class Settings {
|
|||||||
*
|
*
|
||||||
* @param json The parsed settings.json
|
* @param json The parsed settings.json
|
||||||
*/
|
*/
|
||||||
Settings(SettingsJson json) {
|
Settings(SettingsProvider provider, SettingsJson json) {
|
||||||
|
this.provider = provider;
|
||||||
this.directories = FXCollections.observableArrayList(VaultSettings::observables);
|
this.directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||||
this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden);
|
this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden);
|
||||||
this.autoCloseVaults = new SimpleBooleanProperty(this, "autoCloseVaults", json.autoCloseVaults);
|
this.autoCloseVaults = new SimpleBooleanProperty(this, "autoCloseVaults", json.autoCloseVaults);
|
||||||
@@ -118,6 +117,7 @@ public class Settings {
|
|||||||
this.lastUpdateCheckReminder = new SimpleObjectProperty<>(this, "lastUpdateCheckReminder", json.lastReminderForUpdateCheck);
|
this.lastUpdateCheckReminder = new SimpleObjectProperty<>(this, "lastUpdateCheckReminder", json.lastReminderForUpdateCheck);
|
||||||
this.lastSuccessfulUpdateCheck = new SimpleObjectProperty<>(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck);
|
this.lastSuccessfulUpdateCheck = new SimpleObjectProperty<>(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck);
|
||||||
this.previouslyUsedVaultDirectory = new SimpleObjectProperty<>(this, "previouslyUsedVaultDirectory", json.previouslyUsedVaultDirectory);
|
this.previouslyUsedVaultDirectory = new SimpleObjectProperty<>(this, "previouslyUsedVaultDirectory", json.previouslyUsedVaultDirectory);
|
||||||
|
this.lastUpdateAttemptedByVersion = new SimpleStringProperty(this, "lastUpdateAttemptedByVersion", json.lastUpdateAttemptedByVersion);
|
||||||
|
|
||||||
this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
|
this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
|
||||||
|
|
||||||
@@ -148,6 +148,7 @@ public class Settings {
|
|||||||
lastUpdateCheckReminder.addListener(this::somethingChanged);
|
lastUpdateCheckReminder.addListener(this::somethingChanged);
|
||||||
lastSuccessfulUpdateCheck.addListener(this::somethingChanged);
|
lastSuccessfulUpdateCheck.addListener(this::somethingChanged);
|
||||||
previouslyUsedVaultDirectory.addListener(this::somethingChanged);
|
previouslyUsedVaultDirectory.addListener(this::somethingChanged);
|
||||||
|
lastUpdateAttemptedByVersion.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@@ -210,6 +211,7 @@ public class Settings {
|
|||||||
json.lastReminderForUpdateCheck = lastUpdateCheckReminder.get();
|
json.lastReminderForUpdateCheck = lastUpdateCheckReminder.get();
|
||||||
json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.get();
|
json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.get();
|
||||||
json.previouslyUsedVaultDirectory = previouslyUsedVaultDirectory.get();
|
json.previouslyUsedVaultDirectory = previouslyUsedVaultDirectory.get();
|
||||||
|
json.lastUpdateAttemptedByVersion = lastUpdateAttemptedByVersion.get();
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,20 +224,12 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO rename to setChangeListener
|
|
||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
|
||||||
this.saveCmd = saveCmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
this.save();
|
provider.scheduleSave(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void save() {
|
public void saveNow() {
|
||||||
if (saveCmd != null) {
|
provider.saveNow(this);
|
||||||
saveCmd.accept(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,4 +96,7 @@ class SettingsJson {
|
|||||||
|
|
||||||
@JsonProperty("previouslyUsedVaultDirectory")
|
@JsonProperty("previouslyUsedVaultDirectory")
|
||||||
Path previouslyUsedVaultDirectory;
|
Path previouslyUsedVaultDirectory;
|
||||||
|
|
||||||
|
@JsonProperty("lastUpdateAttemptedByVersion")
|
||||||
|
String lastUpdateAttemptedByVersion;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ import java.nio.file.NoSuchFileException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.Optional;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -61,8 +63,7 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
Settings settings = env.getSettingsPath() //
|
Settings settings = env.getSettingsPath() //
|
||||||
.flatMap(this::tryLoad) //
|
.flatMap(this::tryLoad) //
|
||||||
.findFirst() //
|
.findFirst() //
|
||||||
.orElseGet(() -> Settings.create(env));
|
.orElseGet(() -> Settings.create(this, env));
|
||||||
settings.setSaveCmd(this::scheduleSave);
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ)) {
|
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ)) {
|
||||||
var json = JSON.reader().readValue(in, SettingsJson.class);
|
var json = JSON.reader().readValue(in, SettingsJson.class);
|
||||||
LOG.info("Settings loaded from {}", path);
|
LOG.info("Settings loaded from {}", path);
|
||||||
var settings = new Settings(json);
|
var settings = new Settings(this, json);
|
||||||
return Stream.of(settings);
|
return Stream.of(settings);
|
||||||
} catch (JacksonException e) {
|
} catch (JacksonException e) {
|
||||||
LOG.warn("Failed to parse json file {}", path, e);
|
LOG.warn("Failed to parse json file {}", path, e);
|
||||||
@@ -84,19 +85,33 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleSave(Settings settings) {
|
void saveNow(Settings settings) {
|
||||||
if (settings == null) {
|
try {
|
||||||
return;
|
scheduleSave(settings, 0L).get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
LOG.error("Saving settings was interrupted.", e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
LOG.error("Unexpected exception while saving.", e);
|
||||||
}
|
}
|
||||||
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // always save to preferred (first) path
|
}
|
||||||
settingsPath.ifPresent(path -> {
|
|
||||||
Runnable saveCommand = () -> this.save(settings, path);
|
void scheduleSave(Settings settings) {
|
||||||
ScheduledFuture<?> scheduledTask = scheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
scheduleSave(settings, SAVE_DELAY_MS);
|
||||||
ScheduledFuture<?> previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask);
|
}
|
||||||
if (previouslyScheduledTask != null) {
|
|
||||||
previouslyScheduledTask.cancel(false);
|
private Future<?> scheduleSave(Settings settings, long delayMillis) {
|
||||||
}
|
if (settings == null) {
|
||||||
});
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
final Path settingsPath = env.getSettingsPath().findFirst().orElseThrow(); // always save to preferred (first) path
|
||||||
|
Runnable saveCommand = () -> this.save(settings, settingsPath);
|
||||||
|
ScheduledFuture<?> scheduledTask = scheduler.schedule(saveCommand, delayMillis, TimeUnit.MILLISECONDS);
|
||||||
|
ScheduledFuture<?> previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask);
|
||||||
|
if (previouslyScheduledTask != null) {
|
||||||
|
previouslyScheduledTask.cancel(false);
|
||||||
|
}
|
||||||
|
return scheduledTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save(Settings settings, Path settingsPath) {
|
private void save(Settings settings, Path settingsPath) {
|
||||||
@@ -107,7 +122,7 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp");
|
Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp");
|
||||||
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
|
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
|
||||||
var jsonObj = settings.serialized();
|
var jsonObj = settings.serialized();
|
||||||
jsonObj.writtenByVersion = env.getAppVersion() + env.getBuildNumber().map("-"::concat).orElse("");
|
jsonObj.writtenByVersion = env.getAppVersionWithBuildNumber();
|
||||||
JSON.writerWithDefaultPrettyPrinter().writeValue(out, jsonObj);
|
JSON.writerWithDefaultPrettyPrinter().writeValue(out, jsonObj);
|
||||||
}
|
}
|
||||||
Files.move(tmpPath, settingsPath, StandardCopyOption.REPLACE_EXISTING);
|
Files.move(tmpPath, settingsPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
|
|||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
import org.cryptomator.event.VaultEvent;
|
|
||||||
import org.cryptomator.integrations.mount.MountFailedException;
|
import org.cryptomator.integrations.mount.MountFailedException;
|
||||||
import org.cryptomator.integrations.mount.Mountpoint;
|
import org.cryptomator.integrations.mount.Mountpoint;
|
||||||
import org.cryptomator.integrations.mount.UnmountFailedException;
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
@@ -35,7 +34,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
@@ -75,6 +73,7 @@ public class Vault {
|
|||||||
private final BooleanBinding missing;
|
private final BooleanBinding missing;
|
||||||
private final BooleanBinding needsMigration;
|
private final BooleanBinding needsMigration;
|
||||||
private final BooleanBinding unknownError;
|
private final BooleanBinding unknownError;
|
||||||
|
private final BooleanBinding missingVaultConfig;
|
||||||
private final ObjectBinding<Mountpoint> mountPoint;
|
private final ObjectBinding<Mountpoint> mountPoint;
|
||||||
private final Mounter mounter;
|
private final Mounter mounter;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
@@ -103,6 +102,7 @@ public class Vault {
|
|||||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||||
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
|
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
|
||||||
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
|
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
|
||||||
|
this.missingVaultConfig = Bindings.createBooleanBinding(this::isMissingVaultConfig, state);
|
||||||
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
||||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||||
@@ -336,6 +336,14 @@ public class Vault {
|
|||||||
return state.get() == VaultState.Value.ERROR;
|
return state.get() == VaultState.Value.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanBinding missingVaultConfigProperty() {
|
||||||
|
return missingVaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMissingVaultConfig() {
|
||||||
|
return state.get() == VaultState.Value.VAULT_CONFIG_MISSING || state.get() == VaultState.Value.ALL_MISSING;
|
||||||
|
}
|
||||||
|
|
||||||
public ReadOnlyStringProperty displayNameProperty() {
|
public ReadOnlyStringProperty displayNameProperty() {
|
||||||
return vaultSettings.displayName;
|
return vaultSettings.displayName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class VaultConfigCache {
|
|||||||
private final VaultSettings settings;
|
private final VaultSettings settings;
|
||||||
private final AtomicReference<VaultConfig.UnverifiedVaultConfig> config;
|
private final AtomicReference<VaultConfig.UnverifiedVaultConfig> config;
|
||||||
|
|
||||||
VaultConfigCache(VaultSettings settings) {
|
public VaultConfigCache(VaultSettings settings) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.config = new AtomicReference<>(null);
|
this.config = new AtomicReference<>(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.recovery.BackupRestorer;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
@@ -34,9 +35,7 @@ import java.util.ResourceBundle;
|
|||||||
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class VaultListManager {
|
public class VaultListManager {
|
||||||
@@ -67,6 +66,12 @@ public class VaultListManager {
|
|||||||
autoLocker.init();
|
autoLocker.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAlreadyAdded(Path vaultPath) {
|
||||||
|
assert vaultPath.isAbsolute();
|
||||||
|
assert vaultPath.normalize().equals(vaultPath);
|
||||||
|
return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
public Vault add(Path pathToVault) throws IOException {
|
public Vault add(Path pathToVault) throws IOException {
|
||||||
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
|
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
|
||||||
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
|
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
|
||||||
@@ -114,59 +119,122 @@ public class VaultListManager {
|
|||||||
.findAny();
|
.findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addVault(Vault vault) {
|
||||||
|
Path path = vault.getPath().normalize().toAbsolutePath();
|
||||||
|
if (!isAlreadyAdded(path)) {
|
||||||
|
vaultList.add(vault);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Vault create(VaultSettings vaultSettings) {
|
private Vault create(VaultSettings vaultSettings) {
|
||||||
var wrapper = new VaultConfigCache(vaultSettings);
|
var wrapper = new VaultConfigCache(vaultSettings);
|
||||||
try {
|
try {
|
||||||
var vaultState = determineVaultState(vaultSettings.path.get());
|
var vaultState = determineVaultState(vaultSettings.path.get());
|
||||||
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
initializeLastKnownKeyLoaderIfPossible(vaultSettings, vaultState, wrapper);
|
||||||
wrapper.reloadConfig();
|
|
||||||
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
|
|
||||||
var keyIdScheme = wrapper.get().getKeyId().getScheme();
|
|
||||||
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
|
|
||||||
}
|
|
||||||
} else if (vaultState == NEEDS_MIGRATION) {
|
|
||||||
vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
|
|
||||||
}
|
|
||||||
return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault();
|
return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Failed to determine vault state for " + vaultSettings.path.get(), e);
|
LOG.warn("Failed to determine vault state for {}", vaultSettings.path.get(), e);
|
||||||
return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault();
|
return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeLastKnownKeyLoaderIfPossible(VaultSettings vaultSettings, VaultState.Value vaultState, VaultConfigCache wrapper) throws IOException {
|
||||||
|
if (vaultSettings.lastKnownKeyLoader.get() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (vaultState) {
|
||||||
|
case LOCKED -> {
|
||||||
|
wrapper.reloadConfig();
|
||||||
|
vaultSettings.lastKnownKeyLoader.set(wrapper.get().getKeyId().getScheme());
|
||||||
|
}
|
||||||
|
case NEEDS_MIGRATION -> {
|
||||||
|
//for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
||||||
|
vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
|
||||||
|
}
|
||||||
|
case VAULT_CONFIG_MISSING -> {
|
||||||
|
//Nothing to do here, since there is no config to read
|
||||||
|
}
|
||||||
|
case MISSING, ALL_MISSING, ERROR, PROCESSING -> {
|
||||||
|
// no config available or not safe to load
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
if (Files.exists(vaultSettings.path.get().resolve(VAULTCONFIG_FILENAME))) {
|
||||||
|
try {
|
||||||
|
wrapper.reloadConfig();
|
||||||
|
vaultSettings.lastKnownKeyLoader.set(wrapper.get().getKeyId().getScheme());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.debug("Unable to load config for {}", vaultSettings.path.get(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static VaultState.Value redetermineVaultState(Vault vault) {
|
public static VaultState.Value redetermineVaultState(Vault vault) {
|
||||||
VaultState state = vault.stateProperty();
|
VaultState state = vault.stateProperty();
|
||||||
VaultState.Value previousState = state.getValue();
|
VaultState.Value previous = state.getValue();
|
||||||
return switch (previousState) {
|
|
||||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
if (previous.equals(UNLOCKED) || previous.equals(PROCESSING)) {
|
||||||
try {
|
return previous;
|
||||||
var determinedState = determineVaultState(vault.getPath());
|
}
|
||||||
if (determinedState == LOCKED) {
|
|
||||||
vault.getVaultConfigCache().reloadConfig();
|
try {
|
||||||
}
|
VaultState.Value determined = determineVaultState(vault.getPath());
|
||||||
state.set(determinedState);
|
|
||||||
yield determinedState;
|
if (determined == LOCKED) {
|
||||||
} catch (IOException e) {
|
vault.getVaultConfigCache().reloadConfig();
|
||||||
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
|
||||||
state.set(ERROR);
|
|
||||||
vault.setLastKnownException(e);
|
|
||||||
yield ERROR;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
|
||||||
};
|
state.set(determined);
|
||||||
|
return determined;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Failed to (re)determine vault state for {}", vault.getPath(), e);
|
||||||
|
vault.setLastKnownException(e);
|
||||||
|
state.set(ERROR);
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
|
public static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
|
||||||
if (!Files.exists(pathToVault)) {
|
if (!Files.exists(pathToVault)) {
|
||||||
return VaultState.Value.MISSING;
|
return MISSING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VaultState.Value structureResult = checkDirStructure(pathToVault);
|
||||||
|
|
||||||
|
if (structureResult == LOCKED || structureResult == NEEDS_MIGRATION) {
|
||||||
|
return structureResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path pathToVaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME);
|
||||||
|
Path pathToMasterkey = pathToVault.resolve(MASTERKEY_FILENAME);
|
||||||
|
|
||||||
|
if (!Files.exists(pathToVaultConfig)) {
|
||||||
|
BackupRestorer.restoreIfBackupPresent(pathToVault, VAULTCONFIG_FILENAME);
|
||||||
|
}
|
||||||
|
if (!Files.exists(pathToMasterkey)) {
|
||||||
|
BackupRestorer.restoreIfBackupPresent(pathToVault, MASTERKEY_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasConfig = Files.exists(pathToVaultConfig);
|
||||||
|
|
||||||
|
if (!hasConfig && !Files.exists(pathToMasterkey)) {
|
||||||
|
return ALL_MISSING;
|
||||||
|
}
|
||||||
|
if (!hasConfig) {
|
||||||
|
return VAULT_CONFIG_MISSING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkDirStructure(pathToVault);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VaultState.Value checkDirStructure(Path pathToVault) throws IOException {
|
||||||
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
|
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
|
||||||
case VAULT -> VaultState.Value.LOCKED;
|
case VAULT -> LOCKED;
|
||||||
case UNRELATED -> VaultState.Value.MISSING;
|
case UNRELATED -> MISSING;
|
||||||
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? //
|
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? NEEDS_MIGRATION : MISSING;
|
||||||
VaultState.Value.NEEDS_MIGRATION //
|
|
||||||
: VaultState.Value.MISSING;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,16 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
|
|||||||
*/
|
*/
|
||||||
MISSING,
|
MISSING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No vault config found at the provided path
|
||||||
|
*/
|
||||||
|
VAULT_CONFIG_MISSING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No vault config and masterkey found at the provided path
|
||||||
|
*/
|
||||||
|
ALL_MISSING,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vault requires migration to a newer vault format
|
* Vault requires migration to a newer vault format
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import java.util.ResourceBundle;
|
|||||||
public class PasswordStrengthUtil {
|
public class PasswordStrengthUtil {
|
||||||
|
|
||||||
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
|
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
|
||||||
private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel.";
|
|
||||||
private static final List<String> SANITIZED_INPUTS = List.of("cryptomator");
|
private static final List<String> SANITIZED_INPUTS = List.of("cryptomator");
|
||||||
|
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
@@ -48,13 +47,15 @@ public class PasswordStrengthUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getStrengthDescription(Number score) {
|
public String getStrengthDescription(Number score) {
|
||||||
if (score.intValue() == -1) {
|
return switch (score.intValue()) {
|
||||||
return String.format(resourceBundle.getString(RESSOURCE_PREFIX + "tooShort"), minPwLength);
|
case -1 -> String.format(resourceBundle.getString("passwordStrength.messageLabel.tooShort"), minPwLength);
|
||||||
} else if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
|
case 0 -> resourceBundle.getString("passwordStrength.messageLabel.0");
|
||||||
return resourceBundle.getString(RESSOURCE_PREFIX + score.intValue());
|
case 1 -> resourceBundle.getString("passwordStrength.messageLabel.1");
|
||||||
} else {
|
case 2 -> resourceBundle.getString("passwordStrength.messageLabel.2");
|
||||||
return "";
|
case 3 -> resourceBundle.getString("passwordStrength.messageLabel.3");
|
||||||
}
|
case 4 -> resourceBundle.getString("passwordStrength.messageLabel.4");
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ public enum FxmlFile {
|
|||||||
QUIT("/fxml/quit.fxml"), //
|
QUIT("/fxml/quit.fxml"), //
|
||||||
QUIT_FORCED("/fxml/quit_forced.fxml"), //
|
QUIT_FORCED("/fxml/quit_forced.fxml"), //
|
||||||
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
||||||
|
RECOVERYKEY_EXPERT_SETTINGS("/fxml/recoverykey_expert_settings.fxml"), //
|
||||||
|
RECOVERYKEY_ONBOARDING("/fxml/recoverykey_onboarding.fxml"), //
|
||||||
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
|
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
|
||||||
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
|
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
|
||||||
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
|
|
||||||
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
||||||
SHARE_VAULT("/fxml/share_vault.fxml"), //
|
SHARE_VAULT("/fxml/share_vault.fxml"), //
|
||||||
SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), //
|
SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), //
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import dagger.Binds;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.cryptofs.VaultConfig;
|
import org.cryptomator.cryptofs.VaultConfig;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||||
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
|
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
@@ -20,6 +22,7 @@ import org.cryptomator.ui.recoverykey.RecoveryKeyValidateController;
|
|||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
@@ -119,8 +122,8 @@ abstract class ConvertVaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(RecoveryKeyValidateController.class)
|
@FxControllerKey(RecoveryKeyValidateController.class)
|
||||||
static FxController bindRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
static FxController provideRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, MasterkeyFileAccess masterkeyFileAccess) {
|
||||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
|
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, new SimpleObjectProperty<>(RecoveryActionType.CONVERT_VAULT), new SimpleObjectProperty<>(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public class DecryptFileNamesViewController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//obvservable getter
|
//observable getter
|
||||||
|
|
||||||
public ObservableValue<String> dropZoneTextProperty() {
|
public ObservableValue<String> dropZoneTextProperty() {
|
||||||
return dropZoneText;
|
return dropZoneText;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.cryptomator.ui.dialogs;
|
|||||||
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.StageFactory;
|
import org.cryptomator.ui.common.StageFactory;
|
||||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||||
@@ -19,17 +20,21 @@ public class Dialogs {
|
|||||||
|
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final StageFactory stageFactory;
|
private final StageFactory stageFactory;
|
||||||
|
private final DefaultSceneFactory sceneFactory;
|
||||||
|
|
||||||
|
private static final String BUTTON_KEY_CLOSE = "generic.button.close";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory) {
|
public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory, DefaultSceneFactory sceneFactory) {
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.stageFactory = stageFactory;
|
this.stageFactory = stageFactory;
|
||||||
|
this.sceneFactory = sceneFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Dialogs.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Dialogs.class);
|
||||||
|
|
||||||
private SimpleDialog.Builder createDialogBuilder() {
|
private SimpleDialog.Builder createDialogBuilder() {
|
||||||
return new SimpleDialog.Builder(resourceBundle, stageFactory);
|
return new SimpleDialog.Builder(resourceBundle, stageFactory, sceneFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleDialog.Builder prepareRemoveVaultDialog(Stage window, Vault vault, ObservableList<Vault> vaults) {
|
public SimpleDialog.Builder prepareRemoveVaultDialog(Stage window, Vault vault, ObservableList<Vault> vaults) {
|
||||||
@@ -47,6 +52,43 @@ public class Dialogs {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SimpleDialog.Builder prepareContactHubVaultOwner(Stage window) {
|
||||||
|
return createDialogBuilder().setOwner(window) //
|
||||||
|
.setTitleKey("contactHubVaultOwner.title") //
|
||||||
|
.setMessageKey("contactHubVaultOwner.message") //
|
||||||
|
.setDescriptionKey("contactHubVaultOwner.description") //
|
||||||
|
.setIcon(FontAwesome5Icon.EXCLAMATION)//
|
||||||
|
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDialog.Builder prepareRecoveryVaultAdded(Stage window, String displayName) {
|
||||||
|
return createDialogBuilder().setOwner(window) //
|
||||||
|
.setTitleKey("recover.existing.title") //
|
||||||
|
.setMessageKey("recover.existing.message") //
|
||||||
|
.setDescriptionKey("recover.existing.description", displayName) //
|
||||||
|
.setIcon(FontAwesome5Icon.CHECK)//
|
||||||
|
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||||
|
}
|
||||||
|
public SimpleDialog.Builder prepareRecoveryVaultAlreadyExists(Stage window, String displayName) {
|
||||||
|
return createDialogBuilder().setOwner(window) //
|
||||||
|
.setTitleKey("recover.alreadyExists.title") //
|
||||||
|
.setMessageKey("recover.alreadyExists.message") //
|
||||||
|
.setDescriptionKey("recover.alreadyExists.description", displayName) //
|
||||||
|
.setIcon(FontAwesome5Icon.EXCLAMATION)//
|
||||||
|
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDialog.Builder prepareRecoverPasswordSuccess(Stage window) {
|
||||||
|
return createDialogBuilder()
|
||||||
|
.setOwner(window) //
|
||||||
|
.setTitleKey("recoveryKey.recover.title") //
|
||||||
|
.setMessageKey("recoveryKey.recover.resetSuccess.message") //
|
||||||
|
.setDescriptionKey("recoveryKey.recover.resetSuccess.description") //
|
||||||
|
.setIcon(FontAwesome5Icon.CHECK)
|
||||||
|
.setOkAction(Stage::close)
|
||||||
|
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) {
|
public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) {
|
||||||
return createDialogBuilder() //
|
return createDialogBuilder() //
|
||||||
.setOwner(window) //
|
.setOwner(window) //
|
||||||
@@ -69,7 +111,7 @@ public class Dialogs {
|
|||||||
.setMessageKey("dokanySupportEnd.message") //
|
.setMessageKey("dokanySupportEnd.message") //
|
||||||
.setDescriptionKey("dokanySupportEnd.description") //
|
.setDescriptionKey("dokanySupportEnd.description") //
|
||||||
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||||
.setOkButtonKey("generic.button.close") //
|
.setOkButtonKey(BUTTON_KEY_CLOSE) //
|
||||||
.setCancelButtonKey("dokanySupportEnd.preferencesBtn") //
|
.setCancelButtonKey("dokanySupportEnd.preferencesBtn") //
|
||||||
.setOkAction(Stage::close) //
|
.setOkAction(Stage::close) //
|
||||||
.setCancelAction(cancelAction);
|
.setCancelAction(cancelAction);
|
||||||
@@ -83,8 +125,20 @@ public class Dialogs {
|
|||||||
.setDescriptionKey("retryIfReadonly.description") //
|
.setDescriptionKey("retryIfReadonly.description") //
|
||||||
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||||
.setOkButtonKey("retryIfReadonly.retry") //
|
.setOkButtonKey("retryIfReadonly.retry") //
|
||||||
.setCancelButtonKey("generic.button.close") //
|
.setCancelButtonKey(BUTTON_KEY_CLOSE) //
|
||||||
.setOkAction(okAction) //
|
.setOkAction(okAction) //
|
||||||
.setCancelAction(Stage::close);
|
.setCancelAction(Stage::close);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) {
|
||||||
|
return createDialogBuilder() //
|
||||||
|
.setOwner(window) //
|
||||||
|
.setTitleKey("recover.invalidSelection.title") //
|
||||||
|
.setMessageKey("recover.invalidSelection.message") //
|
||||||
|
.setDescriptionKey("recover.invalidSelection.description") //
|
||||||
|
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||||
|
.setOkButtonKey("generic.button.change") //
|
||||||
|
.setOkAction(Stage::close);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.dialogs;
|
package org.cryptomator.ui.dialogs;
|
||||||
|
|
||||||
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.StageFactory;
|
import org.cryptomator.ui.common.StageFactory;
|
||||||
@@ -30,15 +31,15 @@ public class SimpleDialog {
|
|||||||
|
|
||||||
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
|
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
|
||||||
new SimpleDialogController(resolveText(builder.messageKey, null), //
|
new SimpleDialogController(resolveText(builder.messageKey, null), //
|
||||||
resolveText(builder.descriptionKey, null), //
|
resolveText(builder.descriptionKey, builder.descriptionArgs), //
|
||||||
builder.icon, //
|
builder.icon, //
|
||||||
resolveText(builder.okButtonKey, null), //
|
resolveText(builder.okButtonKey, null), //
|
||||||
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
|
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
|
||||||
() -> builder.okAction.accept(dialogStage), //
|
() -> builder.okAction.accept(dialogStage), //
|
||||||
() -> builder.cancelAction.accept(dialogStage)), //
|
() -> builder.cancelAction.accept(dialogStage)), //
|
||||||
Scene::new, builder.resourceBundle);
|
builder.sceneFactory, builder.resourceBundle);
|
||||||
|
|
||||||
dialogStage.setScene(new Scene(loaderFactory.load(FxmlFile.SIMPLE_DIALOG.getRessourcePathString()).getRoot()));
|
dialogStage.setScene(loaderFactory.createScene(FxmlFile.SIMPLE_DIALOG));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showAndWait() {
|
public void showAndWait() {
|
||||||
@@ -62,19 +63,22 @@ public class SimpleDialog {
|
|||||||
private Stage owner;
|
private Stage owner;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final StageFactory stageFactory;
|
private final StageFactory stageFactory;
|
||||||
|
private final DefaultSceneFactory sceneFactory;
|
||||||
private String titleKey;
|
private String titleKey;
|
||||||
private String[] titleArgs;
|
private String[] titleArgs;
|
||||||
private String messageKey;
|
private String messageKey;
|
||||||
private String descriptionKey;
|
private String descriptionKey;
|
||||||
|
private String[] descriptionArgs;
|
||||||
private String okButtonKey;
|
private String okButtonKey;
|
||||||
private String cancelButtonKey;
|
private String cancelButtonKey;
|
||||||
private FontAwesome5Icon icon;
|
private FontAwesome5Icon icon;
|
||||||
private Consumer<Stage> okAction = Stage::close;
|
private Consumer<Stage> okAction = Stage::close;
|
||||||
private Consumer<Stage> cancelAction = Stage::close;
|
private Consumer<Stage> cancelAction = Stage::close;
|
||||||
|
|
||||||
public Builder(ResourceBundle resourceBundle, StageFactory stageFactory) {
|
public Builder(ResourceBundle resourceBundle, StageFactory stageFactory, DefaultSceneFactory sceneFactory) {
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.stageFactory = stageFactory;
|
this.stageFactory = stageFactory;
|
||||||
|
this.sceneFactory = sceneFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setOwner(Stage owner) {
|
public Builder setOwner(Stage owner) {
|
||||||
@@ -93,8 +97,9 @@ public class SimpleDialog {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setDescriptionKey(String descriptionKey) {
|
public Builder setDescriptionKey(String descriptionKey, String... args) {
|
||||||
this.descriptionKey = descriptionKey;
|
this.descriptionKey = descriptionKey;
|
||||||
|
this.descriptionArgs = args;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,20 +15,18 @@ import org.cryptomator.ui.lock.LockComponent;
|
|||||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||||
import org.cryptomator.ui.quit.QuitComponent;
|
import org.cryptomator.ui.quit.QuitComponent;
|
||||||
|
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||||
import org.cryptomator.ui.sharevault.ShareVaultComponent;
|
import org.cryptomator.ui.sharevault.ShareVaultComponent;
|
||||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||||
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
||||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, //
|
@Module(subcomponents = {TrayMenuComponent.class, //
|
||||||
DecryptNameComponent.class, //
|
DecryptNameComponent.class, //
|
||||||
MainWindowComponent.class, //
|
MainWindowComponent.class, //
|
||||||
PreferencesComponent.class, //
|
PreferencesComponent.class, //
|
||||||
@@ -40,7 +38,8 @@ import java.io.InputStream;
|
|||||||
HealthCheckComponent.class, //
|
HealthCheckComponent.class, //
|
||||||
UpdateReminderComponent.class, //
|
UpdateReminderComponent.class, //
|
||||||
ShareVaultComponent.class, //
|
ShareVaultComponent.class, //
|
||||||
EventViewComponent.class})
|
EventViewComponent.class, //
|
||||||
|
RecoveryKeyComponent.class})
|
||||||
abstract class FxApplicationModule {
|
abstract class FxApplicationModule {
|
||||||
|
|
||||||
private static Image createImageFromResource(String resourceName) throws IOException {
|
private static Image createImageFromResource(String resourceName) throws IOException {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.*;
|
|||||||
@FxApplicationScoped
|
@FxApplicationScoped
|
||||||
public class FxApplicationTerminator {
|
public class FxApplicationTerminator {
|
||||||
|
|
||||||
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
|
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR, VAULT_CONFIG_MISSING, ALL_MISSING);
|
||||||
private static final Set<VaultState.Value> STATES_PREVENT_TERMINATION = EnumSet.of(PROCESSING);
|
private static final Set<VaultState.Value> STATES_PREVENT_TERMINATION = EnumSet.of(PROCESSING);
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
|
||||||
|
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
package org.cryptomator.ui.fxapp;
|
|
||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.SemVerComparator;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.concurrent.ScheduledService;
|
|
||||||
import javafx.concurrent.Worker;
|
|
||||||
import javafx.concurrent.WorkerStateEvent;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
@FxApplicationScoped
|
|
||||||
public class UpdateChecker {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class);
|
|
||||||
private static final Duration AUTO_CHECK_DELAY = Duration.seconds(5);
|
|
||||||
|
|
||||||
private final Environment env;
|
|
||||||
private final Settings settings;
|
|
||||||
private final StringProperty latestVersion = new SimpleStringProperty();
|
|
||||||
private final ScheduledService<String> updateCheckerService;
|
|
||||||
private final ObjectProperty<UpdateCheckState> state = new SimpleObjectProperty<>(UpdateCheckState.NOT_CHECKED);
|
|
||||||
private final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
|
|
||||||
private final Comparator<String> versionComparator = new SemVerComparator();
|
|
||||||
private final BooleanBinding updateAvailable;
|
|
||||||
private final BooleanBinding checkFailed;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
UpdateChecker(Settings settings, //
|
|
||||||
Environment env, //
|
|
||||||
ScheduledService<String> updateCheckerService) {
|
|
||||||
this.env = env;
|
|
||||||
this.settings = settings;
|
|
||||||
this.updateCheckerService = updateCheckerService;
|
|
||||||
this.lastSuccessfulUpdateCheck = settings.lastSuccessfulUpdateCheck;
|
|
||||||
this.updateAvailable = Bindings.createBooleanBinding(this::isUpdateAvailable, latestVersion);
|
|
||||||
this.checkFailed = Bindings.equal(UpdateCheckState.CHECK_FAILED, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void automaticallyCheckForUpdatesIfEnabled() {
|
|
||||||
if (!env.disableUpdateCheck() && settings.checkForUpdates.get()) {
|
|
||||||
startCheckingForUpdates(AUTO_CHECK_DELAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkForUpdatesNow() {
|
|
||||||
startCheckingForUpdates(Duration.ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startCheckingForUpdates(Duration initialDelay) {
|
|
||||||
updateCheckerService.cancel();
|
|
||||||
updateCheckerService.reset();
|
|
||||||
updateCheckerService.setDelay(initialDelay);
|
|
||||||
updateCheckerService.setOnRunning(this::checkStarted);
|
|
||||||
updateCheckerService.setOnSucceeded(this::checkSucceeded);
|
|
||||||
updateCheckerService.setOnFailed(this::checkFailed);
|
|
||||||
updateCheckerService.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkStarted(WorkerStateEvent event) {
|
|
||||||
LOG.debug("Checking for updates...");
|
|
||||||
state.set(UpdateCheckState.IS_CHECKING);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkSucceeded(WorkerStateEvent event) {
|
|
||||||
var latestVersionString = updateCheckerService.getValue();
|
|
||||||
LOG.info("Current version: {}, latest version: {}", getCurrentVersion(), latestVersionString);
|
|
||||||
lastSuccessfulUpdateCheck.set(Instant.now());
|
|
||||||
latestVersion.set(latestVersionString);
|
|
||||||
state.set(UpdateCheckState.CHECK_SUCCESSFUL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkFailed(WorkerStateEvent event) {
|
|
||||||
state.set(UpdateCheckState.CHECK_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum UpdateCheckState {
|
|
||||||
NOT_CHECKED,
|
|
||||||
IS_CHECKING,
|
|
||||||
CHECK_SUCCESSFUL,
|
|
||||||
CHECK_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Observable Properties */
|
|
||||||
public BooleanBinding checkingForUpdatesProperty() {
|
|
||||||
return updateCheckerService.stateProperty().isEqualTo(Worker.State.RUNNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyStringProperty latestVersionProperty() {
|
|
||||||
return latestVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanBinding updateAvailableProperty() {
|
|
||||||
return updateAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanBinding checkFailedProperty() {
|
|
||||||
return checkFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUpdateAvailable() {
|
|
||||||
String currentVersion = getCurrentVersion();
|
|
||||||
String latestVersionString = latestVersion.get();
|
|
||||||
|
|
||||||
if (currentVersion == null || latestVersionString == null) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return versionComparator.compare(currentVersion, latestVersionString) < 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<Instant> lastSuccessfulUpdateCheckProperty() {
|
|
||||||
return lastSuccessfulUpdateCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<UpdateCheckState> updateCheckStateProperty() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrentVersion() {
|
|
||||||
return env.getAppVersion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package org.cryptomator.ui.fxapp;
|
|
||||||
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.ObjectBinding;
|
|
||||||
import javafx.concurrent.ScheduledService;
|
|
||||||
import javafx.concurrent.Task;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
|
|
||||||
@Module
|
|
||||||
public abstract class UpdateCheckerModule {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UpdateCheckerModule.class);
|
|
||||||
|
|
||||||
private static final URI LATEST_VERSION_URI = URI.create("https://api.cryptomator.org/desktop/latest-version.json");
|
|
||||||
private static final Duration UPDATE_CHECK_INTERVAL = Duration.hours(3);
|
|
||||||
private static final Duration DISABLED_UPDATE_CHECK_INTERVAL = Duration.hours(100000); // Duration.INDEFINITE leads to overflows...
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@FxApplicationScoped
|
|
||||||
static Optional<HttpClient> provideHttpClient() {
|
|
||||||
try {
|
|
||||||
return Optional.of(HttpClient.newBuilder() //
|
|
||||||
.followRedirects(HttpClient.Redirect.NORMAL) // from version 1.6.11 onwards, Cryptomator can follow redirects, in case this URL ever changes
|
|
||||||
.build());
|
|
||||||
} catch (UncheckedIOException e) {
|
|
||||||
LOG.error("HttpClient for update check cannot be created.", e);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@FxApplicationScoped
|
|
||||||
static HttpRequest provideCheckForUpdatesRequest(Environment env) {
|
|
||||||
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", //
|
|
||||||
env.getAppVersion(), //
|
|
||||||
SystemUtils.OS_NAME, //
|
|
||||||
SystemUtils.OS_VERSION, //
|
|
||||||
SystemUtils.OS_ARCH); //
|
|
||||||
return HttpRequest.newBuilder() //
|
|
||||||
.uri(LATEST_VERSION_URI) //
|
|
||||||
.header("User-Agent", userAgent) //
|
|
||||||
.timeout(java.time.Duration.ofSeconds(10))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Named("checkForUpdatesInterval")
|
|
||||||
@FxApplicationScoped
|
|
||||||
static ObjectBinding<Duration> provideCheckForUpdateInterval(Settings settings) {
|
|
||||||
return Bindings.when(settings.checkForUpdates).then(UPDATE_CHECK_INTERVAL).otherwise(DISABLED_UPDATE_CHECK_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@FxApplicationScoped
|
|
||||||
static ScheduledService<String> provideCheckForUpdatesService(ExecutorService executor, Optional<HttpClient> httpClient, HttpRequest checkForUpdatesRequest, @Named("checkForUpdatesInterval") ObjectBinding<Duration> period) {
|
|
||||||
ScheduledService<String> service = new ScheduledService<>() {
|
|
||||||
@Override
|
|
||||||
protected Task<String> createTask() {
|
|
||||||
if (httpClient.isPresent()) {
|
|
||||||
return new UpdateCheckerTask(httpClient.get(), checkForUpdatesRequest);
|
|
||||||
} else {
|
|
||||||
return new Task<>() {
|
|
||||||
@Override
|
|
||||||
protected String call() {
|
|
||||||
throw new NullPointerException("No HttpClient present.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
service.setOnFailed(event -> LOG.error("Failed to execute update service", service.getException()));
|
|
||||||
service.setExecutor(executor);
|
|
||||||
service.periodProperty().bind(period);
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package org.cryptomator.ui.fxapp;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.google.common.io.ByteStreams;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javafx.concurrent.Task;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
|
|
||||||
public class UpdateCheckerTask extends Task<String> {
|
|
||||||
|
|
||||||
private static final ObjectMapper JSON = new ObjectMapper();
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UpdateCheckerTask.class);
|
|
||||||
|
|
||||||
private static final long MAX_RESPONSE_SIZE = 10L * 1024; // 10kb should be sufficient. protect against flooding
|
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
|
||||||
private final HttpRequest checkForUpdatesRequest;
|
|
||||||
|
|
||||||
UpdateCheckerTask(HttpClient httpClient, HttpRequest checkForUpdatesRequest) {
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
this.checkForUpdatesRequest = checkForUpdatesRequest;
|
|
||||||
|
|
||||||
setOnFailed(event -> LOG.error("Failed to check for updates", getException()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String call() throws IOException, InterruptedException {
|
|
||||||
HttpResponse<InputStream> response = httpClient.send(checkForUpdatesRequest, HttpResponse.BodyHandlers.ofInputStream());
|
|
||||||
if (response.statusCode() == 200) {
|
|
||||||
return processBody(response);
|
|
||||||
} else {
|
|
||||||
throw new IOException("Unexpected HTTP response code " + response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String processBody(HttpResponse<InputStream> response) throws IOException {
|
|
||||||
try (InputStream in = response.body(); //
|
|
||||||
InputStream limitedIn = ByteStreams.limit(in, MAX_RESPONSE_SIZE)) {
|
|
||||||
var json = JSON.reader().readTree(limitedIn);
|
|
||||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
|
||||||
return json.get("mac").asText();
|
|
||||||
} else if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return json.get("win").asText();
|
|
||||||
} else if (SystemUtils.IS_OS_LINUX) {
|
|
||||||
return json.get("linux").asText();
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Unsupported operating system");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+38
-3
@@ -1,14 +1,18 @@
|
|||||||
package org.cryptomator.ui.keyloading.masterkeyfile;
|
package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||||
|
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.WindowEvent;
|
import javafx.stage.WindowEvent;
|
||||||
@@ -27,17 +31,41 @@ public class ChooseMasterkeyFileController implements FxController {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final CompletableFuture<Path> result;
|
private final CompletableFuture<Path> result;
|
||||||
|
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox restoreInsteadCheckBox;
|
||||||
|
@FXML
|
||||||
|
private Button forwardButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ChooseMasterkeyFileController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<Path> result, ResourceBundle resourceBundle) {
|
public ChooseMasterkeyFileController(@KeyLoading Stage window, //
|
||||||
|
@KeyLoading Vault vault, //
|
||||||
|
CompletableFuture<Path> result, //
|
||||||
|
RecoveryKeyComponent.Factory recoveryKeyWindow, //
|
||||||
|
ResourceBundle resourceBundle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
|
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.window.setOnHiding(this::windowClosed);
|
this.window.setOnHiding(this::windowClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
restoreInsteadCheckBox.selectedProperty().addListener((_, _, newVal) -> {
|
||||||
|
if (newVal) {
|
||||||
|
forwardButton.setText(resourceBundle.getString("addvaultwizard.existing.restore"));
|
||||||
|
forwardButton.setOnAction(_ -> restoreMasterkey());
|
||||||
|
} else {
|
||||||
|
forwardButton.setText(resourceBundle.getString("generic.button.choose"));
|
||||||
|
forwardButton.setOnAction(_ -> proceed());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
window.close();
|
window.close();
|
||||||
@@ -47,6 +75,13 @@ public class ChooseMasterkeyFileController implements FxController {
|
|||||||
result.cancel(true);
|
result.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void restoreMasterkey() {
|
||||||
|
Stage ownerStage = (Stage) window.getOwner();
|
||||||
|
window.close();
|
||||||
|
recoveryKeyWindow.create(vault, ownerStage, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_MASTERKEY)).showOnboardingDialogWindow();
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void proceed() {
|
public void proceed() {
|
||||||
LOG.trace("proceed()");
|
LOG.trace("proceed()");
|
||||||
@@ -62,7 +97,7 @@ public class ChooseMasterkeyFileController implements FxController {
|
|||||||
|
|
||||||
//--- Setter & Getter ---
|
//--- Setter & Getter ---
|
||||||
|
|
||||||
public String getDisplayName(){
|
public String getDisplayName() {
|
||||||
return vault.getDisplayName();
|
return vault.getDisplayName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.cryptomator.common.vaults.Vault;
|
|||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
import org.cryptomator.updater.UpdateChecker;
|
||||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.cryptomator.ui.error.ErrorComponent;
|
|||||||
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
|
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
|
||||||
import org.cryptomator.ui.fxapp.PrimaryStage;
|
import org.cryptomator.ui.fxapp.PrimaryStage;
|
||||||
import org.cryptomator.ui.migration.MigrationComponent;
|
import org.cryptomator.ui.migration.MigrationComponent;
|
||||||
|
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||||
@@ -32,7 +33,7 @@ import javafx.stage.Stage;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
|
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class, RecoveryKeyComponent.class})
|
||||||
abstract class MainWindowModule {
|
abstract class MainWindowModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class VaultDetailController implements FxController {
|
|||||||
case LOCKED -> FontAwesome5Icon.LOCK;
|
case LOCKED -> FontAwesome5Icon.LOCK;
|
||||||
case PROCESSING -> FontAwesome5Icon.SPINNER;
|
case PROCESSING -> FontAwesome5Icon.SPINNER;
|
||||||
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
|
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
|
||||||
case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.dialogs.Dialogs;
|
import org.cryptomator.ui.dialogs.Dialogs;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||||
|
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
||||||
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultDetailMissingVaultController implements FxController {
|
public class VaultDetailMissingVaultController implements FxController {
|
||||||
@@ -23,6 +29,7 @@ public class VaultDetailMissingVaultController implements FxController {
|
|||||||
private final ObservableList<Vault> vaults;
|
private final ObservableList<Vault> vaults;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
|
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||||
private final Dialogs dialogs;
|
private final Dialogs dialogs;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -30,11 +37,13 @@ public class VaultDetailMissingVaultController implements FxController {
|
|||||||
ObservableList<Vault> vaults, //
|
ObservableList<Vault> vaults, //
|
||||||
ResourceBundle resourceBundle, //
|
ResourceBundle resourceBundle, //
|
||||||
@MainWindow Stage window, //
|
@MainWindow Stage window, //
|
||||||
Dialogs dialogs) {
|
Dialogs dialogs, //
|
||||||
|
RecoveryKeyComponent.Factory recoveryKeyWindow) {
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||||
this.dialogs = dialogs;
|
this.dialogs = dialogs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +57,19 @@ public class VaultDetailMissingVaultController implements FxController {
|
|||||||
dialogs.prepareRemoveVaultDialog(window, vault.get(), vaults).build().showAndWait();
|
dialogs.prepareRemoveVaultDialog(window, vault.get(), vaults).build().showAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void restoreVaultConfig() {
|
||||||
|
if(KeyLoadingStrategy.isHubVault(vault.get().getVaultSettings().lastKnownKeyLoader.get())){
|
||||||
|
dialogs.prepareContactHubVaultOwner(window).build().showAndWait();
|
||||||
|
}
|
||||||
|
else if(Files.exists(vault.get().getPath().resolve(MASTERKEY_FILENAME))){
|
||||||
|
recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void changeLocation() {
|
void changeLocation() {
|
||||||
// copied from ChooseExistingVaultController class
|
// copied from ChooseExistingVaultController class
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class VaultListCellController implements FxController {
|
|||||||
case LOCKED -> FontAwesome5Icon.LOCK;
|
case LOCKED -> FontAwesome5Icon.LOCK;
|
||||||
case PROCESSING -> FontAwesome5Icon.SPINNER;
|
case PROCESSING -> FontAwesome5Icon.SPINNER;
|
||||||
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
|
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
|
||||||
case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ import javafx.stage.Stage;
|
|||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
|
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultListContextMenuController implements FxController {
|
public class VaultListContextMenuController implements FxController {
|
||||||
@@ -63,7 +65,7 @@ public class VaultListContextMenuController implements FxController {
|
|||||||
|
|
||||||
this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null);
|
this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null);
|
||||||
this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false);
|
this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false);
|
||||||
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
|
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING)::contains).orElse(false);
|
||||||
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
|
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
|
||||||
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
|
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
|
||||||
}
|
}
|
||||||
@@ -102,6 +104,12 @@ public class VaultListContextMenuController implements FxController {
|
|||||||
vaultService.reveal(vault);
|
vaultService.reveal(vault);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void didClickShareVault() {
|
||||||
|
var vault = Objects.requireNonNull(selectedVault.get());
|
||||||
|
appWindows.showShareVaultWindow(vault);
|
||||||
|
}
|
||||||
|
|
||||||
// Getter and Setter
|
// Getter and Setter
|
||||||
|
|
||||||
public ObservableValue<Boolean> selectedVaultUnlockableProperty() {
|
public ObservableValue<Boolean> selectedVaultUnlockableProperty() {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
|
import org.cryptomator.common.recovery.VaultPreparator;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultComponent;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
import org.cryptomator.cryptofs.DirStructure;
|
import org.cryptomator.cryptofs.DirStructure;
|
||||||
|
import org.cryptomator.cryptofs.common.Constants;
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
@@ -13,6 +18,7 @@ import org.cryptomator.ui.dialogs.Dialogs;
|
|||||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
|
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -22,6 +28,7 @@ import javafx.beans.binding.BooleanBinding;
|
|||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
@@ -37,11 +44,14 @@ import javafx.scene.input.KeyEvent;
|
|||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.input.TransferMode;
|
import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.stage.DirectoryChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -50,10 +60,12 @@ import java.util.stream.Collectors;
|
|||||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultListController implements FxController {
|
public class VaultListController implements FxController {
|
||||||
@@ -75,6 +87,10 @@ public class VaultListController implements FxController {
|
|||||||
private final ObservableValue<Double> cellSize;
|
private final ObservableValue<Double> cellSize;
|
||||||
private final Dialogs dialogs;
|
private final Dialogs dialogs;
|
||||||
|
|
||||||
|
private final VaultComponent.Factory vaultComponentFactory;
|
||||||
|
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||||
|
private final List<MountService> mountServices;
|
||||||
|
|
||||||
public ListView<Vault> vaultList;
|
public ListView<Vault> vaultList;
|
||||||
public StackPane root;
|
public StackPane root;
|
||||||
@FXML
|
@FXML
|
||||||
@@ -94,6 +110,9 @@ public class VaultListController implements FxController {
|
|||||||
FxApplicationWindows appWindows, //
|
FxApplicationWindows appWindows, //
|
||||||
Settings settings, //
|
Settings settings, //
|
||||||
Dialogs dialogs, //
|
Dialogs dialogs, //
|
||||||
|
RecoveryKeyComponent.Factory recoveryKeyWindow, //
|
||||||
|
VaultComponent.Factory vaultComponentFactory, //
|
||||||
|
List<MountService> mountServices, //
|
||||||
FxFSEventList fxFSEventList) {
|
FxFSEventList fxFSEventList) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
@@ -105,6 +124,9 @@ public class VaultListController implements FxController {
|
|||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.dialogs = dialogs;
|
this.dialogs = dialogs;
|
||||||
|
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||||
|
this.vaultComponentFactory = vaultComponentFactory;
|
||||||
|
this.mountServices = mountServices;
|
||||||
|
|
||||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||||
this.unreadEvents = fxFSEventList.unreadEventsProperty();
|
this.unreadEvents = fxFSEventList.unreadEventsProperty();
|
||||||
@@ -204,6 +226,26 @@ public class VaultListController implements FxController {
|
|||||||
VaultListManager.redetermineVaultState(newValue);
|
VaultListManager.redetermineVaultState(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<Path> chooseValidVaultDirectory() {
|
||||||
|
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||||
|
File selectedDirectory;
|
||||||
|
|
||||||
|
do {
|
||||||
|
selectedDirectory = directoryChooser.showDialog(mainWindow);
|
||||||
|
if (selectedDirectory == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Path selectedPath = selectedDirectory.toPath();
|
||||||
|
if (!Files.isDirectory(selectedPath.resolve(Constants.DATA_DIR_NAME))) {
|
||||||
|
dialogs.prepareNoDDirectorySelectedDialog(mainWindow).build().showAndWait();
|
||||||
|
selectedDirectory = null;
|
||||||
|
}
|
||||||
|
} while (selectedDirectory == null);
|
||||||
|
|
||||||
|
return Optional.of(selectedDirectory.toPath());
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void didClickAddNewVault() {
|
public void didClickAddNewVault() {
|
||||||
addVaultWizard.build().showAddNewVaultWizard(resourceBundle);
|
addVaultWizard.build().showAddNewVaultWizard(resourceBundle);
|
||||||
@@ -214,9 +256,40 @@ public class VaultListController implements FxController {
|
|||||||
addVaultWizard.build().showAddExistingVaultWizard(resourceBundle);
|
addVaultWizard.build().showAddExistingVaultWizard(resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void didClickRecoverExistingVault() {
|
||||||
|
Optional<Path> selectedDirectory = chooseValidVaultDirectory();
|
||||||
|
if (selectedDirectory.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path path = selectedDirectory.get();
|
||||||
|
Optional<Vault> matchingVaultListEntry = vaultListManager.get(path);
|
||||||
|
if (matchingVaultListEntry.isPresent()) {
|
||||||
|
dialogs.prepareRecoveryVaultAlreadyExists(mainWindow, matchingVaultListEntry.get().getDisplayName()) //
|
||||||
|
.setOkAction(Stage::close) //
|
||||||
|
.build().showAndWait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vault preparedVault = VaultPreparator.prepareVault(path, vaultComponentFactory, mountServices, resourceBundle);
|
||||||
|
VaultListManager.redetermineVaultState(preparedVault);
|
||||||
|
|
||||||
|
switch (preparedVault.getState()) {
|
||||||
|
case VAULT_CONFIG_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
|
||||||
|
case ALL_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
|
||||||
|
case LOCKED, NEEDS_MIGRATION -> {
|
||||||
|
vaultListManager.addVault(preparedVault);
|
||||||
|
dialogs.prepareRecoveryVaultAdded(mainWindow, preparedVault.getDisplayName()).setOkAction(Stage::close).build().showAndWait();
|
||||||
|
}
|
||||||
|
default -> LOG.warn("Unhandled vault state during recovery: {}", preparedVault.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void pressedShortcutToRemoveVault() {
|
private void pressedShortcutToRemoveVault() {
|
||||||
final var vault = selectedVault.get();
|
final var vault = selectedVault.get();
|
||||||
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) {
|
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING).contains(vault.getState())) {
|
||||||
dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait();
|
dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.cryptomator.ui.preferences;
|
|||||||
import com.google.common.io.CharStreams;
|
import com.google.common.io.CharStreams;
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
import org.cryptomator.updater.UpdateChecker;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.cryptomator.ui.preferences;
|
|||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
import org.cryptomator.updater.UpdateChecker;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,37 @@ package org.cryptomator.ui.preferences;
|
|||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.integrations.update.UpdateStep;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
|
import org.cryptomator.updater.UpdateChecker;
|
||||||
|
import org.cryptomator.updater.FallbackUpdateInfo;
|
||||||
|
import org.cryptomator.updater.UpdateService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.animation.PauseTransition;
|
import javafx.animation.PauseTransition;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.binding.BooleanExpression;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.concurrent.Worker;
|
||||||
|
import javafx.concurrent.WorkerStateEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ContentDisplay;
|
import javafx.scene.control.ContentDisplay;
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -34,73 +46,64 @@ import java.util.ResourceBundle;
|
|||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
public class UpdatesPreferencesController implements FxController {
|
public class UpdatesPreferencesController implements FxController {
|
||||||
|
|
||||||
private static final String DOWNLOADS_URI_TEMPLATE = "https://cryptomator.org/downloads/" //
|
private static final Logger LOG = LoggerFactory.getLogger(UpdatesPreferencesController.class);
|
||||||
+ "?utm_source=cryptomator-desktop" //
|
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
|
||||||
+ "&utm_medium=update-notification&" //
|
|
||||||
+ "utm_campaign=app-update-%s";
|
|
||||||
|
|
||||||
private final Application application;
|
private final Application application;
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final UpdateChecker updateChecker;
|
private final UpdateChecker updateChecker;
|
||||||
private final ObjectBinding<ContentDisplay> checkForUpdatesButtonState;
|
private final UpdateService updateService;
|
||||||
private final ReadOnlyStringProperty latestVersion;
|
private final ObservableList<Vault> unlockedVaults;
|
||||||
private final ObservableValue<Instant> lastSuccessfulUpdateCheck;
|
private final VaultService vaultService;
|
||||||
private final StringBinding lastUpdateCheckMessage;
|
private final ObjectBinding<Worker<?>> worker;
|
||||||
|
private final BooleanExpression running;
|
||||||
|
private final StringBinding updateButtonTitle;
|
||||||
|
private final ObjectBinding<ContentDisplay> updateButtonState;
|
||||||
private final ObservableValue<String> timeDifferenceMessage;
|
private final ObservableValue<String> timeDifferenceMessage;
|
||||||
private final String currentVersion;
|
private final StringBinding lastUpdateCheckMessage;
|
||||||
private final BooleanBinding updateAvailable;
|
private final BooleanBinding prohibitUpdateWhileUnlocked;
|
||||||
private final BooleanBinding checkFailed;
|
private final BooleanBinding updateButtonDisabled;
|
||||||
|
private final StringProperty errorMessage = new SimpleStringProperty("");
|
||||||
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
|
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
|
||||||
private final DateTimeFormatter formatter;
|
|
||||||
private final BooleanBinding upToDate;
|
|
||||||
private final String downloadsUri;
|
|
||||||
|
|
||||||
/* FXML */
|
/* FXML */
|
||||||
public CheckBox checkForUpdatesCheckbox;
|
public CheckBox checkForUpdatesCheckbox;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UpdatesPreferencesController(Application application, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker) {
|
UpdatesPreferencesController(Application application, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker, ObservableList<Vault> vaults, VaultService vaultService) {
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.updateChecker = updateChecker;
|
this.updateChecker = updateChecker;
|
||||||
this.checkForUpdatesButtonState = Bindings.when(updateChecker.checkingForUpdatesProperty()).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
this.updateService = new UpdateService(updateChecker.updateProperty());
|
||||||
this.latestVersion = updateChecker.latestVersionProperty();
|
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
||||||
this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty();
|
this.vaultService = vaultService;
|
||||||
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck);
|
this.worker = Bindings.when(updateChecker.updateAvailableProperty()).<Worker<?>>then(this.updateService).otherwise(this.updateChecker);
|
||||||
this.currentVersion = environment.getAppVersion();
|
this.running = Bindings.createBooleanBinding(this::isRunning, updateService.stateProperty(), updateChecker.stateProperty());
|
||||||
this.updateAvailable = updateChecker.updateAvailableProperty();
|
this.updateButtonTitle = Bindings.createStringBinding(this::getUpdateButtonTitle, worker, updateService.stateProperty(), updateService.messageProperty());
|
||||||
this.formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
|
this.updateButtonState = Bindings.createObjectBinding(this::getUpdateButtonState, updateChecker.stateProperty(), updateService.stateProperty());
|
||||||
this.upToDate = updateChecker.updateCheckStateProperty().isEqualTo(UpdateChecker.UpdateCheckState.CHECK_SUCCESSFUL).and(latestVersion.isEqualTo(currentVersion));
|
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, updateChecker.lastSuccessfulUpdateCheckProperty());
|
||||||
this.checkFailed = updateChecker.checkFailedProperty();
|
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, updateChecker.lastSuccessfulUpdateCheckProperty());
|
||||||
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, lastSuccessfulUpdateCheck);
|
this.prohibitUpdateWhileUnlocked = Bindings.createBooleanBinding(this::isProhibitUpdateWhileUnlocked, unlockedVaults, updateChecker.updateProperty());
|
||||||
this.downloadsUri = DOWNLOADS_URI_TEMPLATE.formatted(URLEncoder.encode(currentVersion, StandardCharsets.US_ASCII));
|
this.updateButtonDisabled = Bindings.when(worker.isEqualTo(updateChecker)).then(running).otherwise(prohibitUpdateWhileUnlocked.or(running));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates);
|
checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates);
|
||||||
|
updateChecker.updateAvailableProperty().addListener((_, _, hasUpdate) -> {
|
||||||
upToDate.addListener((_, _, newVal) -> {
|
if (!hasUpdate) {
|
||||||
if (newVal) {
|
|
||||||
upToDateLabelVisible.set(true);
|
upToDateLabelVisible.set(true);
|
||||||
PauseTransition delay = new PauseTransition(javafx.util.Duration.seconds(5));
|
PauseTransition delay = new PauseTransition(javafx.util.Duration.seconds(5));
|
||||||
delay.setOnFinished(_ -> upToDateLabelVisible.set(false));
|
delay.setOnFinished(_ -> upToDateLabelVisible.set(false));
|
||||||
delay.play();
|
delay.play();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
updateChecker.setOnFailed(this::checkFailed);
|
||||||
|
updateService.setOnSucceeded(this::updateSucceeded);
|
||||||
@FXML
|
updateService.setOnFailed(this::updateFailed);
|
||||||
public void checkNow() {
|
|
||||||
updateChecker.checkForUpdatesNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void visitDownloadsPage() {
|
|
||||||
application.getHostServices().showDocument(downloadsUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -108,38 +111,104 @@ public class UpdatesPreferencesController implements FxController {
|
|||||||
environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString()));
|
environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void startWork() {
|
||||||
|
if (worker.get().equals(updateChecker)) {
|
||||||
|
updateChecker.checkForUpdatesNow();
|
||||||
|
} else if (!unlockedVaults.isEmpty()) {
|
||||||
|
LOG.warn("Cannot start update due to unlocked vaults.");
|
||||||
|
} else if (worker.get().equals(updateService)) {
|
||||||
|
LOG.info("User started update to version {}", updateChecker.getUpdate().version());
|
||||||
|
updateService.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkFailed(WorkerStateEvent workerStateEvent) {
|
||||||
|
assert workerStateEvent.getSource() == updateChecker;
|
||||||
|
LOG.error("Update check failed.", updateChecker.getException());
|
||||||
|
errorMessage.set(resourceBundle.getString("preferences.updates.checkFailed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSucceeded(WorkerStateEvent workerStateEvent) {
|
||||||
|
assert workerStateEvent.getSource() == updateService;
|
||||||
|
var lastStep = updateService.getValue();
|
||||||
|
if (lastStep == UpdateStep.EXIT) {
|
||||||
|
// Record that this version attempted an update, so next launch can choose fallback if needed
|
||||||
|
settings.lastUpdateAttemptedByVersion.set(environment.getAppVersionWithBuildNumber());
|
||||||
|
settings.saveNow();
|
||||||
|
LOG.info("Exiting app to update...");
|
||||||
|
Platform.exit();
|
||||||
|
} else if (lastStep == UpdateStep.RETRY) {
|
||||||
|
updateService.reset();
|
||||||
|
} else {
|
||||||
|
LOG.info("Update succeeded.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFailed(WorkerStateEvent workerStateEvent) {
|
||||||
|
assert workerStateEvent.getSource() == updateService;
|
||||||
|
LOG.error("Update failed.", updateService.getException());
|
||||||
|
updateService.reset();
|
||||||
|
errorMessage.set(resourceBundle.getString("preferences.updates.updateFailed"));
|
||||||
|
// try fallback mechanism:
|
||||||
|
updateChecker.recheckWithFallbackMechanism();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void lockAllGracefully() {
|
||||||
|
vaultService.lockAll(unlockedVaults, false);
|
||||||
|
}
|
||||||
|
|
||||||
/* Observable Properties */
|
/* Observable Properties */
|
||||||
|
|
||||||
public ObjectBinding<ContentDisplay> checkForUpdatesButtonStateProperty() {
|
public UpdateChecker getUpdateChecker() {
|
||||||
return checkForUpdatesButtonState;
|
return updateChecker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentDisplay getCheckForUpdatesButtonState() {
|
public ObjectBinding<Worker<?>> workerProperty() {
|
||||||
return checkForUpdatesButtonState.get();
|
return worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyStringProperty latestVersionProperty() {
|
public Worker<?> getWorker() {
|
||||||
return latestVersion;
|
return worker.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLatestVersion() {
|
public BooleanExpression runningProperty() {
|
||||||
return latestVersion.get();
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCurrentVersion() {
|
public boolean isRunning() {
|
||||||
return currentVersion;
|
return updateChecker.getState() == Worker.State.RUNNING || updateService.getState() == Worker.State.RUNNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding lastUpdateCheckMessageProperty() {
|
public StringBinding updateButtonTitleProperty() {
|
||||||
return lastUpdateCheckMessage;
|
return updateButtonTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLastUpdateCheckMessage() {
|
public String getUpdateButtonTitle() {
|
||||||
Instant lastCheck = lastSuccessfulUpdateCheck.getValue();
|
if (worker.get() == updateChecker) {
|
||||||
if (lastCheck != null && !lastCheck.equals(Settings.DEFAULT_TIMESTAMP)) {
|
return resourceBundle.getString("preferences.updates.checkNowBtn");
|
||||||
return formatter.format(LocalDateTime.ofInstant(lastCheck, ZoneId.systemDefault()));
|
|
||||||
} else {
|
} else {
|
||||||
return "-";
|
return switch (updateService.getState()) {
|
||||||
|
case READY -> updateChecker.getUpdate().updateMechanism().getName();
|
||||||
|
case SCHEDULED, RUNNING -> updateService.getMessage();
|
||||||
|
case SUCCEEDED -> resourceBundle.getString("generic.button.done");
|
||||||
|
case FAILED, CANCELLED -> "failed"; // should never be visible
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectBinding<ContentDisplay> updateButtonStateProperty() {
|
||||||
|
return updateButtonState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDisplay getUpdateButtonState() {
|
||||||
|
if (updateService.isRunning()) { // isRunning() covers RUNNING and SCHEDULED states
|
||||||
|
return ContentDisplay.BOTTOM;
|
||||||
|
} else if (updateChecker.getState() == Worker.State.RUNNING) {
|
||||||
|
return ContentDisplay.LEFT;
|
||||||
|
} else {
|
||||||
|
return ContentDisplay.TEXT_ONLY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +217,7 @@ public class UpdatesPreferencesController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getTimeDifferenceMessage() {
|
public String getTimeDifferenceMessage() {
|
||||||
var lastSuccessCheck = lastSuccessfulUpdateCheck.getValue();
|
var lastSuccessCheck = updateChecker.getLastSuccessfulUpdateCheck();
|
||||||
var duration = Duration.between(lastSuccessCheck, Instant.now());
|
var duration = Duration.between(lastSuccessCheck, Instant.now());
|
||||||
var hours = duration.toHours();
|
var hours = duration.toHours();
|
||||||
if (lastSuccessCheck.equals(Settings.DEFAULT_TIMESTAMP)) {
|
if (lastSuccessCheck.equals(Settings.DEFAULT_TIMESTAMP)) {
|
||||||
@@ -162,6 +231,44 @@ public class UpdatesPreferencesController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StringBinding lastUpdateCheckMessageProperty() {
|
||||||
|
return lastUpdateCheckMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastUpdateCheckMessage() {
|
||||||
|
Instant lastCheck = updateChecker.getLastSuccessfulUpdateCheck();
|
||||||
|
if (lastCheck != null && !lastCheck.equals(Settings.DEFAULT_TIMESTAMP)) {
|
||||||
|
return FORMATTER.format(LocalDateTime.ofInstant(lastCheck, ZoneId.systemDefault()));
|
||||||
|
} else {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyStringProperty errorMessageProperty() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProhibitUpdateWhileUnlocked() {
|
||||||
|
// If the result of the last update check was from the fallback mechanism, we don't need to show the warning
|
||||||
|
return !unlockedVaults.isEmpty() && !FallbackUpdateInfo.class.isInstance(updateChecker.getUpdate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding prohibitUpdateWhileUnlockedProperty() {
|
||||||
|
return prohibitUpdateWhileUnlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdateButtonDisabled() {
|
||||||
|
return updateButtonDisabled.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding updateButtonDisabledProperty() {
|
||||||
|
return updateButtonDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
public BooleanProperty upToDateLabelVisibleProperty() {
|
public BooleanProperty upToDateLabelVisibleProperty() {
|
||||||
return upToDateLabelVisible;
|
return upToDateLabelVisible;
|
||||||
}
|
}
|
||||||
@@ -170,20 +277,4 @@ public class UpdatesPreferencesController implements FxController {
|
|||||||
return upToDateLabelVisible.get();
|
return upToDateLabelVisible.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding updateAvailableProperty() {
|
|
||||||
return updateAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUpdateAvailable() {
|
|
||||||
return updateAvailable.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanBinding checkFailedProperty() {
|
|
||||||
return checkFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCheckFailed() {
|
|
||||||
return checkFailed.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package org.cryptomator.ui.recoverykey;
|
|||||||
import dagger.BindsInstance;
|
import dagger.BindsInstance;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
@@ -24,6 +26,9 @@ public interface RecoveryKeyComponent {
|
|||||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
|
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
|
||||||
Lazy<Scene> recoverScene();
|
Lazy<Scene> recoverScene();
|
||||||
|
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING)
|
||||||
|
Lazy<Scene> recoverOnboardingScene();
|
||||||
|
|
||||||
default void showRecoveryKeyCreationWindow() {
|
default void showRecoveryKeyCreationWindow() {
|
||||||
Stage stage = window();
|
Stage stage = window();
|
||||||
stage.setScene(creationScene().get());
|
stage.setScene(creationScene().get());
|
||||||
@@ -38,11 +43,19 @@ public interface RecoveryKeyComponent {
|
|||||||
stage.show();
|
stage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void showOnboardingDialogWindow() {
|
||||||
|
Stage stage = window();
|
||||||
|
stage.setScene(recoverOnboardingScene().get());
|
||||||
|
stage.sizeToScene();
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
@Subcomponent.Factory
|
@Subcomponent.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
||||||
RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, @BindsInstance @Named("keyRecoveryOwner") Stage owner);
|
RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, //
|
||||||
|
@BindsInstance @Named("keyRecoveryOwner") Stage owner, //
|
||||||
|
@BindsInstance @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,45 @@
|
|||||||
package org.cryptomator.ui.recoverykey;
|
package org.cryptomator.ui.recoverykey;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.recovery.CryptoFsInitializer;
|
||||||
|
import org.cryptomator.common.recovery.MasterkeyService;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryDirectory;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.ui.common.Animations;
|
import org.cryptomator.ui.common.Animations;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.controls.FormattedLabel;
|
||||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||||
|
import org.cryptomator.ui.dialogs.Dialogs;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
|
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
public class RecoveryKeyCreationController implements FxController {
|
public class RecoveryKeyCreationController implements FxController {
|
||||||
|
|
||||||
@@ -30,23 +47,71 @@ public class RecoveryKeyCreationController implements FxController {
|
|||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> successScene;
|
private final Lazy<Scene> successScene;
|
||||||
|
private final Lazy<Scene> recoverykeyExpertSettingsScene;
|
||||||
|
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||||
private final StringProperty recoveryKeyProperty;
|
private final StringProperty recoveryKeyProperty;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
public NiceSecurePasswordField passwordField;
|
public NiceSecurePasswordField passwordField;
|
||||||
|
private final IntegerProperty shorteningThreshold;
|
||||||
|
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
public FormattedLabel descriptionLabel;
|
||||||
|
public Button cancelButton;
|
||||||
|
public Button nextButton;
|
||||||
|
private final VaultListManager vaultListManager;
|
||||||
|
private final Dialogs dialogs;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
|
public RecoveryKeyCreationController(FxApplicationWindows appWindows, //
|
||||||
|
@RecoveryKeyWindow Stage window, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverykeyExpertSettingsScene, //
|
||||||
|
@RecoveryKeyWindow Vault vault, //
|
||||||
|
RecoveryKeyFactory recoveryKeyFactory, //
|
||||||
|
MasterkeyFileAccess masterkeyFileAccess, //
|
||||||
|
ExecutorService executor, //
|
||||||
|
@RecoveryKeyWindow StringProperty recoveryKey, //
|
||||||
|
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
|
||||||
|
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||||
|
VaultListManager vaultListManager, //
|
||||||
|
ResourceBundle resourceBundle, //
|
||||||
|
Dialogs dialogs) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
|
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
|
this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||||
this.recoveryKeyProperty = recoveryKey;
|
this.recoveryKeyProperty = recoveryKey;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
|
this.recoverType = recoverType;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||||
|
this.shorteningThreshold = shorteningThreshold;
|
||||||
|
this.vaultListManager = vaultListManager;
|
||||||
|
this.dialogs = dialogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
if (recoverType.get() == RecoveryActionType.SHOW_KEY) {
|
||||||
|
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
|
||||||
|
} else if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
|
||||||
|
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
|
||||||
|
descriptionLabel.formatProperty().set(resourceBundle.getString("recoveryKey.recover.description"));
|
||||||
|
cancelButton.setOnAction((_) -> back());
|
||||||
|
cancelButton.setText(resourceBundle.getString("generic.button.back"));
|
||||||
|
nextButton.setOnAction((_) -> restoreWithPassword());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void back() {
|
||||||
|
window.setScene(recoverykeyExpertSettingsScene.get());
|
||||||
|
window.centerOnScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -71,6 +136,42 @@ public class RecoveryKeyCreationController implements FxController {
|
|||||||
executor.submit(task);
|
executor.submit(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void restoreWithPassword() {
|
||||||
|
|
||||||
|
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||||
|
Path recoveryPath = recoveryDirectory.getRecoveryPath();
|
||||||
|
|
||||||
|
Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME);
|
||||||
|
|
||||||
|
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) {
|
||||||
|
var combo = MasterkeyService.detect(masterkey, vault.getPath())
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Could not detect combo for vault path: " + vault.getPath()));
|
||||||
|
|
||||||
|
CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), combo);
|
||||||
|
}
|
||||||
|
|
||||||
|
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
|
||||||
|
|
||||||
|
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
|
||||||
|
vaultListManager.add(vault.getPath());
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
|
||||||
|
.setTitleKey("recover.recoverVaultConfig.title") //
|
||||||
|
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
|
||||||
|
.setDescriptionKey("recoveryKey.recover.resetMasterkeyFileSuccess.description")
|
||||||
|
.build().showAndWait();
|
||||||
|
|
||||||
|
} catch (InvalidPassphraseException e) {
|
||||||
|
LOG.info("Password invalid", e);
|
||||||
|
Animations.createShakeWindowAnimation(window).play();
|
||||||
|
} catch (IOException | CryptoException | IllegalStateException e) {
|
||||||
|
LOG.error("Recovery process failed", e);
|
||||||
|
appWindows.showErrorWindow(e, window, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void close() {
|
public void close() {
|
||||||
window.close();
|
window.close();
|
||||||
|
|||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
package org.cryptomator.ui.recoverykey;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
|
import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.controls.NumericTextField;
|
||||||
|
|
||||||
|
@RecoveryKeyScoped
|
||||||
|
public class RecoveryKeyExpertSettingsController implements FxController {
|
||||||
|
|
||||||
|
public static final int MAX_SHORTENING_THRESHOLD = 220;
|
||||||
|
public static final int MIN_SHORTENING_THRESHOLD = 36;
|
||||||
|
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/vault/#name-shortening";
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final Lazy<Application> application;
|
||||||
|
private final Vault vault;
|
||||||
|
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||||
|
private final IntegerProperty shorteningThreshold;
|
||||||
|
private final Lazy<Scene> resetPasswordScene;
|
||||||
|
private final Lazy<Scene> createScene;
|
||||||
|
private final Lazy<Scene> onBoardingScene;
|
||||||
|
private final Lazy<Scene> recoverScene;
|
||||||
|
private final BooleanBinding validShorteningThreshold;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public CheckBox expertSettingsCheckBox;
|
||||||
|
@FXML
|
||||||
|
public NumericTextField shorteningThresholdTextField;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, //
|
||||||
|
Lazy<Application> application, //
|
||||||
|
@RecoveryKeyWindow Vault vault, //
|
||||||
|
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||||
|
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE) Lazy<Scene> createScene, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy<Scene> onBoardingScene, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene) {
|
||||||
|
this.window = window;
|
||||||
|
this.application = application;
|
||||||
|
this.vault = vault;
|
||||||
|
this.recoverType = recoverType;
|
||||||
|
this.shorteningThreshold = shorteningThreshold;
|
||||||
|
this.resetPasswordScene = resetPasswordScene;
|
||||||
|
this.createScene = createScene;
|
||||||
|
this.onBoardingScene = onBoardingScene;
|
||||||
|
this.recoverScene = recoverScene;
|
||||||
|
this.validShorteningThreshold = Bindings.createBooleanBinding(this::isValidShorteningThreshold, shorteningThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
shorteningThresholdTextField.setPromptText(MIN_SHORTENING_THRESHOLD + "-" + MAX_SHORTENING_THRESHOLD);
|
||||||
|
shorteningThresholdTextField.setText(Integer.toString(MAX_SHORTENING_THRESHOLD));
|
||||||
|
shorteningThresholdTextField.textProperty().addListener((_, _, newValue) -> {
|
||||||
|
try {
|
||||||
|
int intValue = Integer.parseInt(newValue);
|
||||||
|
shorteningThreshold.set(intValue);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
shorteningThreshold.set(0); //the value is set to 0 to ensure that an invalid value assignment is detected during a NumberFormatException
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void toggleUseExpertSettings() {
|
||||||
|
if (!expertSettingsCheckBox.isSelected()) {
|
||||||
|
shorteningThresholdTextField.setText(Integer.toString(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openDocs() {
|
||||||
|
application.get().getHostServices().showDocument(DOCS_NAME_SHORTENING_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding validShorteningThresholdProperty() {
|
||||||
|
return validShorteningThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidShorteningThreshold() {
|
||||||
|
var value = shorteningThreshold.get();
|
||||||
|
return value >= MIN_SHORTENING_THRESHOLD && value <= MAX_SHORTENING_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void back() {
|
||||||
|
if (recoverType.get() == RecoveryActionType.RESTORE_ALL && vault.getState() == VaultState.Value.VAULT_CONFIG_MISSING) {
|
||||||
|
window.setScene(recoverScene.get());
|
||||||
|
} else if (recoverType.get() == RecoveryActionType.RESTORE_ALL && vault.getState() == VaultState.Value.ALL_MISSING) {
|
||||||
|
window.setScene(recoverScene.get());
|
||||||
|
} else if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
|
||||||
|
window.setScene(onBoardingScene.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void next() {
|
||||||
|
if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
|
||||||
|
window.setScene(createScene.get());
|
||||||
|
} else {
|
||||||
|
window.setScene(resetPasswordScene.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,12 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.Nullable;
|
import org.cryptomator.common.Nullable;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.cryptofs.VaultConfig;
|
import org.cryptomator.cryptofs.VaultConfig;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
|
import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
@@ -19,6 +23,10 @@ import org.cryptomator.ui.common.StageFactory;
|
|||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
@@ -99,12 +107,18 @@ abstract class RecoveryKeyModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS)
|
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING)
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
static Scene provideRecoveryKeyResetPasswordSuccessScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
static Scene provideRecoveryKeyOnboardingScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS);
|
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_ONBOARDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS)
|
||||||
|
@RecoveryKeyScoped
|
||||||
|
static Scene provideRecoveryKeyExpertSettingsScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
@@ -120,6 +134,25 @@ abstract class RecoveryKeyModule {
|
|||||||
return new RecoveryKeyDisplayController(window, vault.getDisplayName(), recoveryKey.get(), localization);
|
return new RecoveryKeyDisplayController(window, vault.getDisplayName(), recoveryKey.get(), localization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Named("shorteningThreshold")
|
||||||
|
@RecoveryKeyScoped
|
||||||
|
static IntegerProperty provideShorteningThreshold() {
|
||||||
|
return new SimpleIntegerProperty(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Named("cipherCombo")
|
||||||
|
@RecoveryKeyScoped
|
||||||
|
static ObjectProperty<CryptorProvider.Scheme> provideCipherCombo() {
|
||||||
|
return new SimpleObjectProperty<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(RecoveryKeyExpertSettingsController.class)
|
||||||
|
abstract FxController provideRecoveryKeyExpertSettingsController(RecoveryKeyExpertSettingsController controller);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(RecoveryKeyRecoverController.class)
|
@FxControllerKey(RecoveryKeyRecoverController.class)
|
||||||
@@ -137,14 +170,14 @@ abstract class RecoveryKeyModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(RecoveryKeyResetPasswordSuccessController.class)
|
@FxControllerKey(RecoveryKeyOnboardingController.class)
|
||||||
abstract FxController bindRecoveryKeyResetPasswordSuccessController(RecoveryKeyResetPasswordSuccessController controller);
|
abstract FxController bindRecoveryKeyOnboardingController(RecoveryKeyOnboardingController controller);
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(RecoveryKeyValidateController.class)
|
@FxControllerKey(RecoveryKeyValidateController.class)
|
||||||
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, @Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) {
|
||||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
|
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, recoverType, cipherCombo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package org.cryptomator.ui.recoverykey;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.RadioButton;
|
||||||
|
import javafx.scene.control.Toggle;
|
||||||
|
import javafx.scene.control.ToggleGroup;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_ALL;
|
||||||
|
import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_VAULT_CONFIG;
|
||||||
|
|
||||||
|
@RecoveryKeyScoped
|
||||||
|
public class RecoveryKeyOnboardingController implements FxController {
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final Vault vault;
|
||||||
|
private final Lazy<Scene> recoverykeyRecoverScene;
|
||||||
|
private final Lazy<Scene> recoverykeyExpertSettingsScene;
|
||||||
|
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
|
public Label titleLabel;
|
||||||
|
public Label messageLabel;
|
||||||
|
public Label pleaseConfirm;
|
||||||
|
public Label secondTextDesc;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox affirmationBox;
|
||||||
|
@FXML
|
||||||
|
private RadioButton recoveryKeyRadio;
|
||||||
|
@FXML
|
||||||
|
private RadioButton passwordRadio;
|
||||||
|
@FXML
|
||||||
|
private Button nextButton;
|
||||||
|
@FXML
|
||||||
|
private VBox chooseMethodeBox;
|
||||||
|
@FXML
|
||||||
|
private ToggleGroup methodToggleGroup = new ToggleGroup();
|
||||||
|
@FXML
|
||||||
|
private HBox hBox;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RecoveryKeyOnboardingController(@RecoveryKeyWindow Stage window, //
|
||||||
|
@RecoveryKeyWindow Vault vault, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverykeyExpertSettingsScene, //
|
||||||
|
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||||
|
ResourceBundle resourceBundle) {
|
||||||
|
this.window = window;
|
||||||
|
this.vault = vault;
|
||||||
|
this.recoverykeyRecoverScene = recoverykeyRecoverScene;
|
||||||
|
this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene;
|
||||||
|
this.recoverType = recoverType;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
recoveryKeyRadio.setToggleGroup(methodToggleGroup);
|
||||||
|
passwordRadio.setToggleGroup(methodToggleGroup);
|
||||||
|
|
||||||
|
BooleanBinding showMethodSelection = Bindings.createBooleanBinding(
|
||||||
|
() -> recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG, recoverType);
|
||||||
|
chooseMethodeBox.visibleProperty().bind(showMethodSelection);
|
||||||
|
chooseMethodeBox.managedProperty().bind(showMethodSelection);
|
||||||
|
|
||||||
|
nextButton.disableProperty().bind(
|
||||||
|
affirmationBox.selectedProperty().not()
|
||||||
|
.or(methodToggleGroup.selectedToggleProperty().isNull().and(showMethodSelection))
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (recoverType.get()) {
|
||||||
|
case RESTORE_MASTERKEY -> {
|
||||||
|
window.setTitle(resourceBundle.getString("recover.recoverMasterkey.title"));
|
||||||
|
messageLabel.setVisible(false);
|
||||||
|
messageLabel.setManaged(false);
|
||||||
|
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.pleaseConfirm"));
|
||||||
|
}
|
||||||
|
case RESTORE_ALL -> {
|
||||||
|
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
|
||||||
|
messageLabel.setVisible(true);
|
||||||
|
messageLabel.setManaged(true);
|
||||||
|
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.otherwisePleaseConfirm"));
|
||||||
|
}
|
||||||
|
case RESTORE_VAULT_CONFIG -> {
|
||||||
|
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
|
||||||
|
messageLabel.setVisible(false);
|
||||||
|
messageLabel.setManaged(false);
|
||||||
|
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.pleaseConfirm"));
|
||||||
|
}
|
||||||
|
default -> window.setTitle("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vault.getState() == VaultState.Value.ALL_MISSING) {
|
||||||
|
messageLabel.setText(resourceBundle.getString("recover.onBoarding.allMissing.intro"));
|
||||||
|
} else {
|
||||||
|
messageLabel.setText(resourceBundle.getString("recover.onBoarding.intro"));
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.textProperty().bind(Bindings.createStringBinding(() ->
|
||||||
|
recoverType.get() == RecoveryActionType.RESTORE_MASTERKEY
|
||||||
|
? resourceBundle.getString("recover.recoverMasterkey.title")
|
||||||
|
: resourceBundle.getString("recover.recoverVaultConfig.title"), recoverType));
|
||||||
|
|
||||||
|
BooleanBinding isRestoreMasterkey = Bindings.createBooleanBinding(
|
||||||
|
() -> recoverType.get() == RecoveryActionType.RESTORE_MASTERKEY, recoverType);
|
||||||
|
hBox.minHeightProperty().bind(Bindings.when(isRestoreMasterkey).then(206.0).otherwise(Region.USE_COMPUTED_SIZE));
|
||||||
|
|
||||||
|
secondTextDesc.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||||
|
RecoveryActionType type = recoverType.get();
|
||||||
|
Toggle sel = methodToggleGroup.getSelectedToggle();
|
||||||
|
return switch (type) {
|
||||||
|
case RESTORE_VAULT_CONFIG -> resourceBundle.getString(sel == passwordRadio
|
||||||
|
? "recover.onBoarding.intro.password"
|
||||||
|
: "recover.onBoarding.intro.recoveryKey");
|
||||||
|
case RESTORE_MASTERKEY -> resourceBundle.getString("recover.onBoarding.intro.masterkey.recoveryKey");
|
||||||
|
case RESTORE_ALL -> resourceBundle.getString("recover.onBoarding.intro.recoveryKey");
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
|
}, recoverType, methodToggleGroup.selectedToggleProperty()));
|
||||||
|
|
||||||
|
showMethodSelection.addListener((_, _, nowShown) -> {
|
||||||
|
if (nowShown && methodToggleGroup.getSelectedToggle() == null) {
|
||||||
|
methodToggleGroup.selectToggle(recoveryKeyRadio);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void next() {
|
||||||
|
switch (recoverType.get()) {
|
||||||
|
case RESTORE_VAULT_CONFIG, RESTORE_ALL -> {
|
||||||
|
Object selectedToggle = methodToggleGroup.getSelectedToggle();
|
||||||
|
if (selectedToggle == recoveryKeyRadio) {
|
||||||
|
recoverType.set(RESTORE_ALL);
|
||||||
|
window.setScene(recoverykeyRecoverScene.get());
|
||||||
|
} else if (selectedToggle == passwordRadio) {
|
||||||
|
recoverType.set(RESTORE_VAULT_CONFIG);
|
||||||
|
window.setScene(recoverykeyExpertSettingsScene.get());
|
||||||
|
} else {
|
||||||
|
window.setScene(recoverykeyRecoverScene.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case RESTORE_MASTERKEY -> window.setScene(recoverykeyRecoverScene.get());
|
||||||
|
default -> window.setScene(recoverykeyRecoverScene.get()); // Fallback
|
||||||
|
}
|
||||||
|
window.centerOnScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,54 +1,105 @@
|
|||||||
package org.cryptomator.ui.recoverykey;
|
package org.cryptomator.ui.recoverykey;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.Nullable;
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.cryptofs.VaultConfig;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.Observable;
|
import javax.inject.Named;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
public class RecoveryKeyRecoverController implements FxController {
|
public class RecoveryKeyRecoverController implements FxController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> resetPasswordScene;
|
private final Vault vault;
|
||||||
|
private final Lazy<Scene> nextScene;
|
||||||
|
private final Lazy<Scene> onBoardingScene;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
public ObjectProperty<RecoveryActionType> recoverType;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button cancelButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
RecoveryKeyValidateController recoveryKeyValidateController;
|
RecoveryKeyValidateController recoveryKeyValidateController;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, ResourceBundle resourceBundle) {
|
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, //
|
||||||
|
@RecoveryKeyWindow Vault vault, //
|
||||||
|
ResourceBundle resourceBundle, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> expertSettingsScene, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy<Scene> onBoardingScene, //
|
||||||
|
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
|
this.vault = vault;
|
||||||
this.resetPasswordScene = resetPasswordScene;
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.onBoardingScene = onBoardingScene;
|
||||||
|
this.recoverType = recoverType;
|
||||||
|
this.nextScene = switch (recoverType.get()) {
|
||||||
|
case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
|
||||||
|
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
|
||||||
|
yield expertSettingsScene;
|
||||||
|
}
|
||||||
|
case RESTORE_MASTERKEY -> {
|
||||||
|
window.setTitle(resourceBundle.getString("recover.recoverMasterkey.title"));
|
||||||
|
yield resetPasswordScene;
|
||||||
|
}
|
||||||
|
case RESET_PASSWORD -> {
|
||||||
|
window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
|
||||||
|
yield resetPasswordScene;
|
||||||
|
}
|
||||||
|
case SHOW_KEY -> {
|
||||||
|
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
|
||||||
|
yield resetPasswordScene;
|
||||||
|
}
|
||||||
|
default -> throw new IllegalArgumentException("Unexpected recovery action type: " + recoverType.get());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
if (recoverType.get() == RecoveryActionType.RESET_PASSWORD) {
|
||||||
|
cancelButton.setText(resourceBundle.getString("generic.button.cancel"));
|
||||||
|
} else {
|
||||||
|
cancelButton.setText(resourceBundle.getString("generic.button.back"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void close() {
|
public void closeOrReturn() {
|
||||||
window.close();
|
switch (recoverType.get()) {
|
||||||
|
case RESET_PASSWORD -> window.close();
|
||||||
|
case RESTORE_MASTERKEY -> {
|
||||||
|
window.setScene(onBoardingScene.get());
|
||||||
|
window.centerOnScreen();
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
if(vault.getState().equals(VaultState.Value.ALL_MISSING)){
|
||||||
|
recoverType.set(RecoveryActionType.RESTORE_ALL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recoverType.set(RecoveryActionType.RESTORE_VAULT_CONFIG);
|
||||||
|
}
|
||||||
|
window.setScene(onBoardingScene.get());
|
||||||
|
window.centerOnScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void recover() {
|
public void recover() {
|
||||||
window.setScene(resetPasswordScene.get());
|
window.setScene(nextScene.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|||||||
+123
-13
@@ -1,25 +1,44 @@
|
|||||||
package org.cryptomator.ui.recoverykey;
|
package org.cryptomator.ui.recoverykey;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.recovery.CryptoFsInitializer;
|
||||||
|
import org.cryptomator.common.recovery.MasterkeyService;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryDirectory;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
|
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
import org.cryptomator.ui.dialogs.Dialogs;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
|
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
public class RecoveryKeyResetPasswordController implements FxController {
|
public class RecoveryKeyResetPasswordController implements FxController {
|
||||||
|
|
||||||
@@ -30,48 +49,140 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
|||||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
private final StringProperty recoveryKey;
|
private final StringProperty recoveryKey;
|
||||||
private final Lazy<Scene> recoverResetPasswordSuccessScene;
|
private final Lazy<Scene> recoverExpertSettingsScene;
|
||||||
|
private final Lazy<Scene> recoverykeyRecoverScene;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
|
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||||
|
private final VaultListManager vaultListManager;
|
||||||
|
private final IntegerProperty shorteningThreshold;
|
||||||
|
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||||
|
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
private final Dialogs dialogs;
|
||||||
|
|
||||||
public NewPasswordController newPasswordController;
|
public NewPasswordController newPasswordController;
|
||||||
|
public Button nextButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy<Scene> recoverResetPasswordSuccessScene, FxApplicationWindows appWindows) {
|
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, //
|
||||||
|
@RecoveryKeyWindow Vault vault, //
|
||||||
|
RecoveryKeyFactory recoveryKeyFactory, //
|
||||||
|
ExecutorService executor, //
|
||||||
|
@RecoveryKeyWindow StringProperty recoveryKey, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverExpertSettingsScene, //
|
||||||
|
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene, //
|
||||||
|
FxApplicationWindows appWindows, //
|
||||||
|
MasterkeyFileAccess masterkeyFileAccess, //
|
||||||
|
VaultListManager vaultListManager, //
|
||||||
|
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
|
||||||
|
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||||
|
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, //
|
||||||
|
ResourceBundle resourceBundle, //
|
||||||
|
Dialogs dialogs) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.recoveryKey = recoveryKey;
|
this.recoveryKey = recoveryKey;
|
||||||
this.recoverResetPasswordSuccessScene = recoverResetPasswordSuccessScene;
|
this.recoverExpertSettingsScene = recoverExpertSettingsScene;
|
||||||
|
this.recoverykeyRecoverScene = recoverykeyRecoverScene;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
|
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||||
|
this.vaultListManager = vaultListManager;
|
||||||
|
this.shorteningThreshold = shorteningThreshold;
|
||||||
|
this.cipherCombo = cipherCombo;
|
||||||
|
this.recoverType = recoverType;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.dialogs = dialogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
switch (recoverType.get()) {
|
||||||
|
case RESTORE_MASTERKEY, RESTORE_ALL -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn"));
|
||||||
|
case RESET_PASSWORD -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn"));
|
||||||
|
default -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); // Fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void close() {
|
public void close() {
|
||||||
window.close();
|
switch (recoverType.get()) {
|
||||||
|
case RESTORE_ALL -> window.setScene(recoverExpertSettingsScene.get());
|
||||||
|
case RESTORE_MASTERKEY, RESET_PASSWORD -> window.setScene(recoverykeyRecoverScene.get());
|
||||||
|
default -> window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void next() {
|
||||||
|
switch (recoverType.get()) {
|
||||||
|
case RESTORE_ALL -> restorePassword();
|
||||||
|
case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword();
|
||||||
|
default -> resetPassword(); // Fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void restorePassword() {
|
||||||
|
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||||
|
Path recoveryPath = recoveryDirectory.getRecoveryPath();
|
||||||
|
MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
|
||||||
|
|
||||||
|
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, recoveryPath.resolve(MASTERKEY_FILENAME), newPasswordController.passwordField.getCharacters())) {
|
||||||
|
CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME);
|
||||||
|
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
|
||||||
|
|
||||||
|
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
|
||||||
|
vaultListManager.add(vault.getPath());
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
|
||||||
|
.setTitleKey("recover.recoverVaultConfig.title") //
|
||||||
|
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
|
||||||
|
.build().showAndWait();
|
||||||
|
|
||||||
|
} catch (IOException | CryptoException e) {
|
||||||
|
LOG.error("Recovery process failed", e);
|
||||||
|
appWindows.showErrorWindow(e, window, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void resetPassword() {
|
public void resetPassword() {
|
||||||
Task<Void> task = new ResetPasswordTask();
|
Task<Void> task = new ResetPasswordTask();
|
||||||
task.setOnScheduled(event -> {
|
|
||||||
|
task.setOnScheduled(_ -> {
|
||||||
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
|
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||||
});
|
});
|
||||||
task.setOnSucceeded(event -> {
|
|
||||||
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
task.setOnSucceeded(_ -> {
|
||||||
window.setScene(recoverResetPasswordSuccessScene.get());
|
LOG.debug("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||||
|
window.close();
|
||||||
|
switch (recoverType.get()){
|
||||||
|
case RESET_PASSWORD -> dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()).build().showAndWait();
|
||||||
|
case RESTORE_MASTERKEY -> dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()).setTitleKey("recover.recoverMasterkey.title").setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message").build().showAndWait();
|
||||||
|
default -> dialogs.prepareRecoverPasswordSuccess(window).build().showAndWait(); // Fallback
|
||||||
|
}
|
||||||
});
|
});
|
||||||
task.setOnFailed(event -> {
|
|
||||||
|
task.setOnFailed(_ -> {
|
||||||
LOG.error("Resetting password failed.", task.getException());
|
LOG.error("Resetting password failed.", task.getException());
|
||||||
appWindows.showErrorWindow(task.getException(), window, null);
|
appWindows.showErrorWindow(task.getException(), window, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
executor.submit(task);
|
executor.submit(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ResetPasswordTask extends Task<Void> {
|
private class ResetPasswordTask extends Task<Void> {
|
||||||
|
|
||||||
private ResetPasswordTask() {
|
private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class);
|
||||||
setOnFailed(event -> LOG.error("Failed to reset password", getException()));
|
|
||||||
|
public ResetPasswordTask() {
|
||||||
|
setOnFailed(_ -> LOG.error("Failed to reset password", getException()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -79,7 +190,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
|||||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
package org.cryptomator.ui.recoverykey;
|
|
||||||
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
@RecoveryKeyScoped
|
|
||||||
public class RecoveryKeyResetPasswordSuccessController implements FxController {
|
|
||||||
|
|
||||||
private final Stage window;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public RecoveryKeyResetPasswordSuccessController(@RecoveryKeyWindow Stage window) {
|
|
||||||
this.window = window;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void close() {
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
package org.cryptomator.ui.recoverykey;
|
package org.cryptomator.ui.recoverykey;
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.cryptomator.common.Nullable;
|
import org.cryptomator.common.Nullable;
|
||||||
import org.cryptomator.common.ObservableUtil;
|
import org.cryptomator.common.ObservableUtil;
|
||||||
|
import org.cryptomator.common.recovery.MasterkeyService;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.cryptofs.VaultConfig;
|
import org.cryptomator.cryptofs.VaultConfig;
|
||||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||||
import org.cryptomator.cryptofs.VaultKeyInvalidException;
|
import org.cryptomator.cryptofs.VaultKeyInvalidException;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
@@ -22,10 +27,12 @@ import javafx.scene.control.TextArea;
|
|||||||
import javafx.scene.control.TextFormatter;
|
import javafx.scene.control.TextFormatter;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
public class RecoveryKeyValidateController implements FxController {
|
public class RecoveryKeyValidateController implements FxController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyValidateController.class);
|
||||||
private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
|
private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
|
||||||
|
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
@@ -36,13 +43,23 @@ public class RecoveryKeyValidateController implements FxController {
|
|||||||
private final ObservableValue<Boolean> recoveryKeyInvalid;
|
private final ObservableValue<Boolean> recoveryKeyInvalid;
|
||||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||||
private final ObjectProperty<RecoveryKeyState> recoveryKeyState;
|
private final ObjectProperty<RecoveryKeyState> recoveryKeyState;
|
||||||
|
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
|
||||||
private final AutoCompleter autoCompleter;
|
private final AutoCompleter autoCompleter;
|
||||||
|
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||||
|
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||||
|
|
||||||
private volatile boolean isWrongKey;
|
private volatile boolean isWrongKey;
|
||||||
|
|
||||||
public TextArea textarea;
|
public TextArea textarea;
|
||||||
|
|
||||||
public RecoveryKeyValidateController(Vault vault, @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
public RecoveryKeyValidateController(Vault vault, //
|
||||||
|
@Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, //
|
||||||
|
StringProperty recoveryKey, //
|
||||||
|
RecoveryKeyFactory recoveryKeyFactory, //
|
||||||
|
MasterkeyFileAccess masterkeyFileAccess, //
|
||||||
|
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||||
|
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo
|
||||||
|
) {
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.unverifiedVaultConfig = vaultConfig;
|
this.unverifiedVaultConfig = vaultConfig;
|
||||||
this.recoveryKey = recoveryKey;
|
this.recoveryKey = recoveryKey;
|
||||||
@@ -52,6 +69,9 @@ public class RecoveryKeyValidateController implements FxController {
|
|||||||
this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false);
|
this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false);
|
||||||
this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false);
|
this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false);
|
||||||
this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false);
|
this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false);
|
||||||
|
this.recoverType = recoverType;
|
||||||
|
this.cipherCombo = cipherCombo;
|
||||||
|
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -117,14 +137,37 @@ public class RecoveryKeyValidateController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateRecoveryKey() {
|
private void validateRecoveryKey() {
|
||||||
isWrongKey = false;
|
switch (recoverType.get()) {
|
||||||
var valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
|
case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
|
||||||
if (valid) {
|
try {
|
||||||
recoveryKeyState.set(RecoveryKeyState.CORRECT);
|
var scheme = MasterkeyService.validateRecoveryKeyAndDetectCombo(recoveryKeyFactory, vault, recoveryKey.get(), masterkeyFileAccess);
|
||||||
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
|
cipherCombo.set(scheme);
|
||||||
recoveryKeyState.set(RecoveryKeyState.WRONG);
|
recoveryKeyState.set(RecoveryKeyState.CORRECT);
|
||||||
} else {
|
} catch (CryptoException e) {
|
||||||
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
LOG.info("Recovery key is valid but crypto scheme couldn't be determined", e);
|
||||||
|
recoveryKeyState.set(RecoveryKeyState.WRONG);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOG.info("Recovery key is syntactically invalid", e);
|
||||||
|
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("IO error while validating recovery key", e);
|
||||||
|
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
LOG.warn("Could not determine scheme from masterkey during recovery key validation, because no valid *.c9r file is present in vault", e);
|
||||||
|
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case RESTORE_MASTERKEY, RESET_PASSWORD, SHOW_KEY, CONVERT_VAULT -> {
|
||||||
|
isWrongKey = false;
|
||||||
|
boolean valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
|
||||||
|
if (valid) {
|
||||||
|
recoveryKeyState.set(RecoveryKeyState.CORRECT);
|
||||||
|
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
|
||||||
|
recoveryKeyState.set(RecoveryKeyState.WRONG);
|
||||||
|
} else {
|
||||||
|
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,9 +58,15 @@ public class ShareVaultController implements FxController {
|
|||||||
|
|
||||||
private static URI getHubUri(Vault vault) {
|
private static URI getHubUri(Vault vault) {
|
||||||
try {
|
try {
|
||||||
var keyID = new URI(vault.getVaultConfigCache().get().getKeyId().toString());
|
var keyId = new URI(vault.getVaultConfigCache().get().getKeyId().toString());
|
||||||
assert keyID.getScheme().startsWith(SCHEME_PREFIX);
|
assert keyId.getScheme().startsWith(SCHEME_PREFIX);
|
||||||
return new URI(keyID.getScheme().substring(SCHEME_PREFIX.length()) + "://" + keyID.getHost() + "/app/vaults");
|
var path = keyId.getPath();
|
||||||
|
var apiIdx = path.indexOf("/api/");
|
||||||
|
if (apiIdx < 0) {
|
||||||
|
throw new IllegalArgumentException("Path does not contain /api/: " + path);
|
||||||
|
}
|
||||||
|
var appPath = path.substring(0, apiIdx) + "/app/vaults";
|
||||||
|
return new URI(keyId.getScheme().substring(SCHEME_PREFIX.length()), keyId.getAuthority(), appPath, null, null);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxmlScene;
|
|||||||
import org.cryptomator.ui.common.StageFactory;
|
import org.cryptomator.ui.common.StageFactory;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
|
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||||
|
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@@ -27,7 +28,7 @@ import javafx.stage.Stage;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@Module(subcomponents = {KeyLoadingComponent.class})
|
@Module(subcomponents = {KeyLoadingComponent.class, RecoveryKeyComponent.class})
|
||||||
abstract class UnlockModule {
|
abstract class UnlockModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.cryptomator.ui.updatereminder;
|
|||||||
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
import org.cryptomator.updater.UpdateChecker;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package org.cryptomator.ui.vaultoptions;
|
package org.cryptomator.ui.vaultoptions;
|
||||||
|
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
|
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
|
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
|
||||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
@@ -18,8 +18,6 @@ import javafx.stage.Stage;
|
|||||||
@VaultOptionsScoped
|
@VaultOptionsScoped
|
||||||
public class MasterkeyOptionsController implements FxController {
|
public class MasterkeyOptionsController implements FxController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyOptionsController.class);
|
|
||||||
|
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final ChangePasswordComponent.Builder changePasswordWindow;
|
private final ChangePasswordComponent.Builder changePasswordWindow;
|
||||||
@@ -51,12 +49,12 @@ public class MasterkeyOptionsController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void showRecoveryKey() {
|
public void showRecoveryKey() {
|
||||||
recoveryKeyWindow.create(vault, window).showRecoveryKeyCreationWindow();
|
recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.SHOW_KEY)).showRecoveryKeyCreationWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void showRecoverVaultDialog() {
|
public void showRecoverVaultDialog() {
|
||||||
recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow();
|
recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.RESET_PASSWORD)).showRecoveryKeyRecoverWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package org.cryptomator.updater;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Authenticator;
|
||||||
|
import java.net.CookieHandler;
|
||||||
|
import java.net.ProxySelector;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
abstract class DelegatingHttpClient extends HttpClient {
|
||||||
|
|
||||||
|
private final HttpClient delegate;
|
||||||
|
|
||||||
|
public DelegatingHttpClient(HttpClient delegate) {
|
||||||
|
this.delegate = Objects.requireNonNull(delegate, "delegate must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<CookieHandler> cookieHandler() {
|
||||||
|
return delegate.cookieHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Duration> connectTimeout() {
|
||||||
|
return delegate.connectTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Redirect followRedirects() {
|
||||||
|
return delegate.followRedirects();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ProxySelector> proxy() {
|
||||||
|
return delegate.proxy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLContext sslContext() {
|
||||||
|
return delegate.sslContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLParameters sslParameters() {
|
||||||
|
return delegate.sslParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Authenticator> authenticator() {
|
||||||
|
return delegate.authenticator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Version version() {
|
||||||
|
return delegate.version();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Executor> executor() {
|
||||||
|
return delegate.executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException {
|
||||||
|
return delegate.send(request, responseBodyHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) {
|
||||||
|
return delegate.sendAsync(request, responseBodyHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, HttpResponse.PushPromiseHandler<T> pushPromiseHandler) {
|
||||||
|
return delegate.sendAsync(request, responseBodyHandler, pushPromiseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.cryptomator.updater;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.update.UpdateInfo;
|
||||||
|
import org.cryptomator.integrations.update.UpdateMechanism;
|
||||||
|
|
||||||
|
public record FallbackUpdateInfo(String version, UpdateMechanism<FallbackUpdateInfo> updateMechanism) implements UpdateInfo<FallbackUpdateInfo> {}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package org.cryptomator.updater;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.integrations.common.LocalizedDisplayName;
|
||||||
|
import org.cryptomator.integrations.update.UpdateMechanism;
|
||||||
|
import org.cryptomator.integrations.update.UpdateStep;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@FxApplicationScoped
|
||||||
|
@LocalizedDisplayName(bundle = "i18n.strings", key = "preferences.updates.visitDownloadPage")
|
||||||
|
public class FallbackUpdateMechanism implements UpdateMechanism<FallbackUpdateInfo> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FallbackUpdateMechanism.class);
|
||||||
|
private static final String LATEST_VERSION_API_URL = "https://api.cryptomator.org/connect/apps/desktop/latest-version";
|
||||||
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
private static final String DOWNLOADS_URI_TEMPLATE = "https://cryptomator.org/downloads/" //
|
||||||
|
+ "?utm_source=cryptomator-desktop" //
|
||||||
|
+ "&utm_medium=update-notification&" //
|
||||||
|
+ "utm_campaign=app-update-%s";
|
||||||
|
|
||||||
|
private final Application app;
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public FallbackUpdateMechanism(Application app, Environment env) {
|
||||||
|
this.app = app;
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FallbackUpdateInfo checkForUpdate(String currentVersion, HttpClient httpClient) {
|
||||||
|
try {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(LATEST_VERSION_API_URL)).build();
|
||||||
|
HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new RuntimeException("Failed to fetch release: " + response.statusCode());
|
||||||
|
}
|
||||||
|
var release = MAPPER.readValue(response.body(), LatestVersion.class);
|
||||||
|
var updateVersion = release.versionForCurrentOS();
|
||||||
|
if (UpdateMechanism.isUpdateAvailable(updateVersion, currentVersion)) {
|
||||||
|
return new FallbackUpdateInfo(updateVersion, this);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
LOG.warn("Update check interrupted", e);
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Update check failed", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UpdateStep firstStep(FallbackUpdateInfo updateInfo) {
|
||||||
|
return UpdateStep.of("Go to download page", this::openDownloadPage); // TODO localize
|
||||||
|
}
|
||||||
|
|
||||||
|
private UpdateStep openDownloadPage() {
|
||||||
|
var downloadUrl = DOWNLOADS_URI_TEMPLATE.formatted(URLEncoder.encode(env.getAppVersion(), StandardCharsets.US_ASCII));
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
app.getHostServices().showDocument(downloadUrl);
|
||||||
|
});
|
||||||
|
return UpdateStep.RETRY; // allow running this "update mechanism" as many times as the user wants
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public record LatestVersion(
|
||||||
|
@JsonProperty("mac") String macVersion,
|
||||||
|
@JsonProperty("win") String winVersion,
|
||||||
|
@JsonProperty("linux") String linuxVersion
|
||||||
|
) {
|
||||||
|
public String versionForCurrentOS() {
|
||||||
|
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||||
|
return macVersion;
|
||||||
|
} else if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
return winVersion;
|
||||||
|
} else if (SystemUtils.IS_OS_LINUX) {
|
||||||
|
return linuxVersion;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unsupported operating system");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package org.cryptomator.updater;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.integrations.update.UpdateFailedException;
|
||||||
|
import org.cryptomator.integrations.update.UpdateInfo;
|
||||||
|
import org.cryptomator.integrations.update.UpdateMechanism;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.binding.ObjectBinding;
|
||||||
|
import javafx.beans.binding.StringExpression;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.concurrent.ScheduledService;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
@FxApplicationScoped
|
||||||
|
public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class);
|
||||||
|
private static final Duration AUTO_CHECK_DELAY = Duration.seconds(5);
|
||||||
|
private static final Duration UPDATE_CHECK_INTERVAL = Duration.hours(3);
|
||||||
|
private static final Duration DISABLED_UPDATE_CHECK_INTERVAL = Duration.hours(100000); // Duration.INDEFINITE leads to overflows...
|
||||||
|
|
||||||
|
public enum UpdateCheckState {
|
||||||
|
NOT_CHECKED,
|
||||||
|
IS_CHECKING,
|
||||||
|
CHECK_SUCCESSFUL,
|
||||||
|
CHECK_FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
private final Settings settings;
|
||||||
|
private final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
|
||||||
|
private final ObjectProperty<UpdateInfo<?>> update = new SimpleObjectProperty<>();
|
||||||
|
private final StringExpression latestVersion = StringExpression.stringExpression(update.map(UpdateInfo::version));
|
||||||
|
private final BooleanBinding updateAvailable = update.isNotNull();
|
||||||
|
private final ObjectBinding<UpdateCheckState> updateState = Bindings.createObjectBinding(this::getUpdateCheckState, stateProperty());
|
||||||
|
private final BooleanBinding checkFailed = Bindings.equal(UpdateCheckState.CHECK_FAILED, updateState);
|
||||||
|
private final UpdateMechanism<?> fallbackUpdateMechanism;
|
||||||
|
private UpdateMechanism<?> updateMechanism;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UpdateChecker(Settings settings, //
|
||||||
|
Environment env,
|
||||||
|
FallbackUpdateMechanism fallbackUpdateMechanism) {
|
||||||
|
this.env = env;
|
||||||
|
this.settings = settings;
|
||||||
|
this.lastSuccessfulUpdateCheck = settings.lastSuccessfulUpdateCheck;
|
||||||
|
this.fallbackUpdateMechanism = fallbackUpdateMechanism;
|
||||||
|
|
||||||
|
// Prefer the safer fallback mechanism if the last update attempt was already made by this app version
|
||||||
|
var currentVersion = env.getAppVersionWithBuildNumber();
|
||||||
|
var lastAttemptedBy = settings.lastUpdateAttemptedByVersion.get();
|
||||||
|
if (currentVersion != null && currentVersion.equals(lastAttemptedBy)) {
|
||||||
|
this.updateMechanism = fallbackUpdateMechanism; // immediately use fallback mechanism
|
||||||
|
} else {
|
||||||
|
this.updateMechanism = UpdateMechanism.get().orElse(fallbackUpdateMechanism);
|
||||||
|
}
|
||||||
|
|
||||||
|
setExecutor(Executors.newVirtualThreadPerTaskExecutor());
|
||||||
|
periodProperty().bind(Bindings.when(settings.checkForUpdates).then(UPDATE_CHECK_INTERVAL).otherwise(DISABLED_UPDATE_CHECK_INTERVAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void automaticallyCheckForUpdatesIfEnabled() {
|
||||||
|
if (!env.disableUpdateCheck() && settings.checkForUpdates.get()) {
|
||||||
|
startCheckingForUpdates(AUTO_CHECK_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recheckWithFallbackMechanism() {
|
||||||
|
if (updateMechanism == fallbackUpdateMechanism) {
|
||||||
|
return; // already using fallback mechanism
|
||||||
|
}
|
||||||
|
updateMechanism = fallbackUpdateMechanism;
|
||||||
|
checkForUpdatesNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkForUpdatesNow() {
|
||||||
|
startCheckingForUpdates(Duration.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startCheckingForUpdates(Duration initialDelay) {
|
||||||
|
cancel();
|
||||||
|
reset();
|
||||||
|
setDelay(initialDelay);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void succeeded() {
|
||||||
|
var updateInfo = getValue();
|
||||||
|
super.succeeded(); // this will nil the value property!
|
||||||
|
lastSuccessfulUpdateCheck.set(Instant.now());
|
||||||
|
if (updateInfo != null) {
|
||||||
|
LOG.info("Current version: {}, latest version: {}", getCurrentVersion(), updateInfo.version());
|
||||||
|
update.set(updateInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<UpdateInfo<?>> createTask() {
|
||||||
|
return new UpdateCheckTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Observable Properties */
|
||||||
|
|
||||||
|
public UpdateInfo<?> getUpdate() {
|
||||||
|
return update.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<UpdateInfo<?>> updateProperty() {
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLatestVersion() {
|
||||||
|
return latestVersion.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringExpression latestVersionProperty() {
|
||||||
|
return latestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdateAvailable() {
|
||||||
|
return updateAvailable.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding updateAvailableProperty() {
|
||||||
|
return updateAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCheckFailed() {
|
||||||
|
return checkFailed.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding checkFailedProperty() {
|
||||||
|
return checkFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getLastSuccessfulUpdateCheck() {
|
||||||
|
return lastSuccessfulUpdateCheck.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Instant> lastSuccessfulUpdateCheckProperty() {
|
||||||
|
return lastSuccessfulUpdateCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectBinding<UpdateCheckState> updateCheckStateProperty() {
|
||||||
|
return updateState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UpdateCheckState getUpdateCheckState() {
|
||||||
|
return switch (getState()) {
|
||||||
|
case READY -> UpdateCheckState.NOT_CHECKED;
|
||||||
|
case SCHEDULED, RUNNING -> UpdateCheckState.IS_CHECKING;
|
||||||
|
case SUCCEEDED -> UpdateCheckState.CHECK_SUCCESSFUL;
|
||||||
|
case FAILED, CANCELLED -> UpdateCheckState.CHECK_FAILED;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentVersion() {
|
||||||
|
return env.getAppVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpdateCheckTask extends Task<UpdateInfo<?>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UpdateInfo<?> call() {
|
||||||
|
try (var httpClient = new UpdateCheckerHttpClient(env)) {
|
||||||
|
var result = updateMechanism.checkForUpdate(env.getAppVersion(), httpClient);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch (UpdateFailedException e) {
|
||||||
|
LOG.error("Update check using {} failed.", updateMechanism.getClass(), e);
|
||||||
|
}
|
||||||
|
if (updateMechanism == fallbackUpdateMechanism) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LOG.debug("Trying fallback update check...");
|
||||||
|
try (var httpClient = new UpdateCheckerHttpClient(env)) {
|
||||||
|
return fallbackUpdateMechanism.checkForUpdate(env.getAppVersion(), httpClient);
|
||||||
|
} catch (UpdateFailedException e) {
|
||||||
|
LOG.error("Fallback update check failed.", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.cryptomator.updater;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ProxySelector;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class UpdateCheckerHttpClient extends DelegatingHttpClient {
|
||||||
|
|
||||||
|
private final String userAgent;
|
||||||
|
|
||||||
|
public UpdateCheckerHttpClient(Environment env) {
|
||||||
|
var delegate = HttpClient.newBuilder() //
|
||||||
|
.followRedirects(HttpClient.Redirect.NORMAL) // from version 1.6.11 onwards, Cryptomator can follow redirects, in case this URL ever changes
|
||||||
|
.proxy(ProxySelector.getDefault()).build();
|
||||||
|
super(delegate);
|
||||||
|
this.userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", env.getAppVersion(), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException {
|
||||||
|
return super.send(decorateRequest(request), responseBodyHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) {
|
||||||
|
return super.sendAsync(decorateRequest(request), responseBodyHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, HttpResponse.PushPromiseHandler<T> pushPromiseHandler) {
|
||||||
|
return super.sendAsync(decorateRequest(request), responseBodyHandler, pushPromiseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequest decorateRequest(HttpRequest request) {
|
||||||
|
return HttpRequest.newBuilder(request, (_, _) -> true) //
|
||||||
|
.header("User-Agent", this.userAgent) //
|
||||||
|
.timeout(Duration.ofSeconds(10)) //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package org.cryptomator.updater;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.update.UpdateInfo;
|
||||||
|
import org.cryptomator.integrations.update.UpdateMechanism;
|
||||||
|
import org.cryptomator.integrations.update.UpdateStep;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.concurrent.Service;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service that performs all update steps provided by the given {@link UpdateMechanism} in sequence.
|
||||||
|
*/
|
||||||
|
public class UpdateService extends Service<UpdateStep> {
|
||||||
|
|
||||||
|
private final BooleanBinding updateFailed = Bindings.equal(State.FAILED, stateProperty());
|
||||||
|
|
||||||
|
private ObservableValue<UpdateInfo<?>> updateInfo;
|
||||||
|
|
||||||
|
public UpdateService(ObservableValue<UpdateInfo<?>> updateInfo) {
|
||||||
|
setExecutor(Executors.newVirtualThreadPerTaskExecutor());
|
||||||
|
this.updateInfo = updateInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<UpdateStep> createTask() {
|
||||||
|
return new RunAllStepsTask(updateInfo.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RunAllStepsTask extends Task<UpdateStep> {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private final UpdateInfo updateInfo;
|
||||||
|
|
||||||
|
public RunAllStepsTask(UpdateInfo<?> updateInfo) {
|
||||||
|
this.updateInfo = Objects.requireNonNull(updateInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UpdateStep call() throws IOException {
|
||||||
|
try {
|
||||||
|
UpdateStep step = updateInfo.useToPrepareFirstStep();
|
||||||
|
UpdateStep lastStep;
|
||||||
|
do {
|
||||||
|
step.start();
|
||||||
|
observeAndWaitFor(step);
|
||||||
|
lastStep = step;
|
||||||
|
step = step.nextStep();
|
||||||
|
} while (step != null);
|
||||||
|
return lastStep;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new InterruptedIOException("Update interrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void observeAndWaitFor(UpdateStep step) throws InterruptedException {
|
||||||
|
do {
|
||||||
|
updateProgress(step.preparationProgress(), 1.0);
|
||||||
|
updateMessage(step.description());
|
||||||
|
} while (!step.await(100, TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Observable Properties */
|
||||||
|
|
||||||
|
public boolean isUpdateFailed() {
|
||||||
|
return updateFailed.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding updateFailedProperty() {
|
||||||
|
return updateFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,12 @@
|
|||||||
<?import javafx.scene.control.ButtonBar?>
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
<?import javafx.scene.layout.Region?>
|
<?import javafx.scene.layout.Region?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import javafx.scene.Group?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordConvertController"
|
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordConvertController"
|
||||||
minWidth="400"
|
minWidth="400"
|
||||||
@@ -17,7 +22,16 @@
|
|||||||
<padding>
|
<padding>
|
||||||
<Insets topRightBottomLeft="12"/>
|
<Insets topRightBottomLeft="12"/>
|
||||||
</padding>
|
</padding>
|
||||||
<children>
|
<Group>
|
||||||
|
<StackPane>
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="6"/>
|
||||||
|
</padding>
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="KEY" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
</Group>
|
||||||
|
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
|
||||||
<fx:include fx:id="newPassword" source="new_password.fxml"/>
|
<fx:include fx:id="newPassword" source="new_password.fxml"/>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
@@ -26,7 +40,7 @@
|
|||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||||
<Button fx:id="convertBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#convert"> <!-- for button logic, see controller -->
|
<Button fx:id="convertBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#convert">
|
||||||
<graphic>
|
<graphic>
|
||||||
<FontAwesome5Spinner glyphSize="12"/>
|
<FontAwesome5Spinner glyphSize="12"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
@@ -34,5 +48,5 @@
|
|||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
</VBox>
|
</VBox>
|
||||||
</children>
|
</VBox>
|
||||||
</VBox>
|
</HBox>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user