mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 07:54:19 +00:00
Compare commits
492
Commits
1.6.9
...
1.7.0-alpha2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a358f06e8 | ||
|
|
db4d845a5b | ||
|
|
c2dac30f2d | ||
|
|
63a89b5bcf | ||
|
|
12b53acd22 | ||
|
|
9105d1af16 | ||
|
|
de46b91420 | ||
|
|
67b40649bc | ||
|
|
c41af77108 | ||
|
|
ba66d4bfce | ||
|
|
c45a00377d | ||
|
|
f9d588320c | ||
|
|
c61087e697 | ||
|
|
0725a63b09 | ||
|
|
ebcd0adf78 | ||
|
|
0e7ec8eb37 | ||
|
|
0e9b115647 | ||
|
|
5ba137dda5 | ||
|
|
db928ba034 | ||
|
|
2cc01be236 | ||
|
|
ba2a4955d0 | ||
|
|
d1971e3c2a | ||
|
|
5665984a9b | ||
|
|
1f2225991a | ||
|
|
1f0fe161fd | ||
|
|
4ff8bbaa62 | ||
|
|
e188169916 | ||
|
|
1b9480293a | ||
|
|
d8b71db8e5 | ||
|
|
4bd99d2b66 | ||
|
|
61027852e1 | ||
|
|
512500aaef | ||
|
|
f9d28f20b3 | ||
|
|
7f79c554f7 | ||
|
|
3cbe4aa7eb | ||
|
|
06922717c4 | ||
|
|
32399c4a09 | ||
|
|
00a3692dcf | ||
|
|
83b198d491 | ||
|
|
8086ec1c10 | ||
|
|
b2b49f4d2f | ||
|
|
d6e14dab9a | ||
|
|
497ddcfc9c | ||
|
|
169039903d | ||
|
|
3641b34c2d | ||
|
|
42400d1f0c | ||
|
|
8f7f8ffb11 | ||
|
|
3016d35c9b | ||
|
|
f21b8a0f7a | ||
|
|
0c1bbc5bfd | ||
|
|
d932091021 | ||
|
|
216abf224e | ||
|
|
d5523f07bd | ||
|
|
e097bea1f5 | ||
|
|
920866b1a1 | ||
|
|
c41ffe2568 | ||
|
|
792fe1eafe | ||
|
|
348bc2b2ec | ||
|
|
7bac78bc5d | ||
|
|
65e1993b43 | ||
|
|
b98eb8c656 | ||
|
|
33ebc17619 | ||
|
|
ba7029968d | ||
|
|
a3fc68ab6f | ||
|
|
abf944a130 | ||
|
|
99bc4dbdc5 | ||
|
|
019292f1db | ||
|
|
2f9818aade | ||
|
|
9cec45dc1f | ||
|
|
5f55530b4a | ||
|
|
96de2556a9 | ||
|
|
db63a82d76 | ||
|
|
11aea06ecf | ||
|
|
932cfd2200 | ||
|
|
edaa2b7391 | ||
|
|
9984b2af9b | ||
|
|
34986f5919 | ||
|
|
23d9521724 | ||
|
|
4dc1d59305 | ||
|
|
74e3c28441 | ||
|
|
6e526ba796 | ||
|
|
b978044383 | ||
|
|
6325c6f450 | ||
|
|
dd79b46d72 | ||
|
|
d896fe21b5 | ||
|
|
6983d9d72d | ||
|
|
faf39c2507 | ||
|
|
083d0cfbc4 | ||
|
|
9ca8072ab6 | ||
|
|
3223ea2e5d | ||
|
|
83af5e796f | ||
|
|
9d9f118a32 | ||
|
|
1b0eb34734 | ||
|
|
79952418f4 | ||
|
|
65e26971ff | ||
|
|
13debaafbe | ||
|
|
5729e2885d | ||
|
|
043184cf53 | ||
|
|
38062f9a8d | ||
|
|
14d0d95f23 | ||
|
|
8f42c1c9ce | ||
|
|
37f89fd235 | ||
|
|
668feb73f9 | ||
|
|
ba0569deb1 | ||
|
|
a70d8e1567 | ||
|
|
96b7a8df3f | ||
|
|
759c21e5cf | ||
|
|
d8ed9f854a | ||
|
|
1414ab4681 | ||
|
|
1486826883 | ||
|
|
5143fdccbb | ||
|
|
3c5502955a | ||
|
|
2d3a035d9e | ||
|
|
dda7255d8e | ||
|
|
6caf132cfb | ||
|
|
4029f86a0d | ||
|
|
6dac00625c | ||
|
|
41b7bd54f1 | ||
|
|
f236614bd0 | ||
|
|
31c69f145e | ||
|
|
b5a00f5ebe | ||
|
|
40c4300012 | ||
|
|
5369ddb1f9 | ||
|
|
1b4117de38 | ||
|
|
3e5a7fadd0 | ||
|
|
eff2530e70 | ||
|
|
0f84d0c990 | ||
|
|
fbe33e21fb | ||
|
|
dc310c009f | ||
|
|
7091839f08 | ||
|
|
79bf1759c7 | ||
|
|
70e5f53009 | ||
|
|
c79cfe1c9d | ||
|
|
d619a0cbf4 | ||
|
|
517b5958b0 | ||
|
|
01a6475d5f | ||
|
|
445be3b668 | ||
|
|
f852ae0392 | ||
|
|
e5e606f3ae | ||
|
|
69641ed3b6 | ||
|
|
1f01923169 | ||
|
|
687bed6984 | ||
|
|
01698d1393 | ||
|
|
6367cc2dfb | ||
|
|
bcfda68bef | ||
|
|
2399e29d75 | ||
|
|
322779ee88 | ||
|
|
fd6eb2139d | ||
|
|
26ecd67a79 | ||
|
|
e06240cbb9 | ||
|
|
b10d892b12 | ||
|
|
45c2dd0358 | ||
|
|
34d6e8c419 | ||
|
|
ae6a15b391 | ||
|
|
8040b71a51 | ||
|
|
76e7a0a7b8 | ||
|
|
910efc0b7b | ||
|
|
c805a820a4 | ||
|
|
3ed8316b60 | ||
|
|
b6e381f0bf | ||
|
|
aef3f4c6e2 | ||
|
|
7bc5a336e4 | ||
|
|
672b4aee2d | ||
|
|
d4ab17b38c | ||
|
|
91f49c5642 | ||
|
|
da2f97b1c4 | ||
|
|
5d647d20a5 | ||
|
|
ce96d1c65e | ||
|
|
f02a8e14ff | ||
|
|
dd31908f3a | ||
|
|
495a1884c3 | ||
|
|
49c3b1fa43 | ||
|
|
cde67966da | ||
|
|
d19e77432e | ||
|
|
cc24a4c9ae | ||
|
|
1101a7574c | ||
|
|
30de04e291 | ||
|
|
2e3d2e86e2 | ||
|
|
78cf082096 | ||
|
|
64c90f8107 | ||
|
|
b34bf6f161 | ||
|
|
17d3d7307d | ||
|
|
e6c7fed662 | ||
|
|
23fad16742 | ||
|
|
ec794cdca2 | ||
|
|
2def9216ec | ||
|
|
4f9759af58 | ||
|
|
382f6b7851 | ||
|
|
d60eb470ea | ||
|
|
97a61ef717 | ||
|
|
08fc5e9a77 | ||
|
|
4c96c285bf | ||
|
|
0021f2d37c | ||
|
|
31d32f2018 | ||
|
|
b9c087b48e | ||
|
|
797dbc0a57 | ||
|
|
46745d030b | ||
|
|
3cdf124e54 | ||
|
|
d37147c73e | ||
|
|
67dd1548cc | ||
|
|
008bb38e30 | ||
|
|
1be6128523 | ||
|
|
b4f95c465a | ||
|
|
40fa961b9b | ||
|
|
659123fe7a | ||
|
|
2448963161 | ||
|
|
16c0431f6f | ||
|
|
2d99d1bdd4 | ||
|
|
86ed0d3c0e | ||
|
|
0501b0ef39 | ||
|
|
2c2b208de7 | ||
|
|
ec7ad0f479 | ||
|
|
5267a04491 | ||
|
|
04714cd0de | ||
|
|
46dbe15e4a | ||
|
|
7abf167ba2 | ||
|
|
44d19bdefe | ||
|
|
f84eef5639 | ||
|
|
281f6f727f | ||
|
|
bf83df3cd3 | ||
|
|
be4f6f2b9b | ||
|
|
bf84598131 | ||
|
|
943be4557f | ||
|
|
649e029d4f | ||
|
|
1cd3d1d67f | ||
|
|
8290df9ea6 | ||
|
|
47f2d423b9 | ||
|
|
80b5b6af00 | ||
|
|
414c4e20b8 | ||
|
|
11a55f382a | ||
|
|
0c5021913c | ||
|
|
8bc0b3a700 | ||
|
|
57110ff417 | ||
|
|
d5bdea4a75 | ||
|
|
72c8ca3368 | ||
|
|
2ca824696c | ||
|
|
43d8f7c47f | ||
|
|
9209e7698c | ||
|
|
6e75f1fea8 | ||
|
|
2bdb0c3e6a | ||
|
|
677634930d | ||
|
|
546da7d11f | ||
|
|
8662abae5a | ||
|
|
f98782ba4e | ||
|
|
422077ac08 | ||
|
|
10b9e75972 | ||
|
|
00e420a248 | ||
|
|
f7ddb6e714 | ||
|
|
f1c8e3d3f2 | ||
|
|
cfc8c7bed7 | ||
|
|
71b876b32d | ||
|
|
cdc60abf66 | ||
|
|
b73ab4645b | ||
|
|
c70961405c | ||
|
|
d814c1cfca | ||
|
|
50413ca9d8 | ||
|
|
2a91403f2f | ||
|
|
5ec499ff01 | ||
|
|
86cf0d4d4e | ||
|
|
f2af6f355c | ||
|
|
58d01b5bf5 | ||
|
|
5e19cd409f | ||
|
|
4dd58a664e | ||
|
|
a7fc8d6fc4 | ||
|
|
5c31a34d5f | ||
|
|
67264c0d8c | ||
|
|
153ccef8a2 | ||
|
|
0e853b25f7 | ||
|
|
12302162b1 | ||
|
|
7d14b55bcb | ||
|
|
69fe5b8c27 | ||
|
|
df099df334 | ||
|
|
6bbdb4baa0 | ||
|
|
ea6318dfc4 | ||
|
|
93500a4efd | ||
|
|
b132bb6412 | ||
|
|
cf38a10284 | ||
|
|
15781b685d | ||
|
|
73442cddc8 | ||
|
|
bce9833929 | ||
|
|
29d0e258f1 | ||
|
|
1305e45564 | ||
|
|
9e18215d10 | ||
|
|
683276a6c6 | ||
|
|
f16a30ef29 | ||
|
|
82db3b59fb | ||
|
|
c191df9ee3 | ||
|
|
a2d6db0415 | ||
|
|
077825f98a | ||
|
|
381c1cd8d3 | ||
|
|
ae2c67a88b | ||
|
|
3ce0270cde | ||
|
|
e011a98fa0 | ||
|
|
9386804216 | ||
|
|
b791df01f7 | ||
|
|
376733106b | ||
|
|
8e6d2cf45a | ||
|
|
63cd3a02de | ||
|
|
1795a7aaa4 | ||
|
|
c8e4ed1ce4 | ||
|
|
99b25d0792 | ||
|
|
e640e36744 | ||
|
|
d10d8fb208 | ||
|
|
43e6ca41aa | ||
|
|
e2b4566174 | ||
|
|
d203db8075 | ||
|
|
7c4f5b28a7 | ||
|
|
7edd1dfa40 | ||
|
|
09550b77dc | ||
|
|
3bd43908ac | ||
|
|
f69efb85a5 | ||
|
|
bc5091a0d7 | ||
|
|
a199a2a06b | ||
|
|
dffd4d9dd9 | ||
|
|
737d98775b | ||
|
|
bee9c9f452 | ||
|
|
2b19fd6852 | ||
|
|
a5e9837c99 | ||
|
|
81a1052a76 | ||
|
|
ab2d740722 | ||
|
|
bf3a62a6b4 | ||
|
|
d163d02077 | ||
|
|
677607d210 | ||
|
|
fd3c8d7f78 | ||
|
|
cce74ef5e4 | ||
|
|
74058238ec | ||
|
|
78e458f01a | ||
|
|
07cfef2e41 | ||
|
|
35c5bd03de | ||
|
|
7cc1a1728c | ||
|
|
a79bf1952d | ||
|
|
c7ad587aa2 | ||
|
|
22b842e6c2 | ||
|
|
f96c52cdb1 | ||
|
|
ca426f6535 | ||
|
|
d6f489ea98 | ||
|
|
8043a4ea10 | ||
|
|
e5cb271e28 | ||
|
|
45f038bae2 | ||
|
|
3580728a5a | ||
|
|
749c5d9f24 | ||
|
|
6ae872fa85 | ||
|
|
773fa9804a | ||
|
|
cfa9beec8b | ||
|
|
5f4d13f662 | ||
|
|
f0d69b645b | ||
|
|
797724e640 | ||
|
|
3488db8c0e | ||
|
|
d53e0b24a4 | ||
|
|
347a7b3ef7 | ||
|
|
ff80585a2f | ||
|
|
8c65bd19c2 | ||
|
|
ad7a8835b3 | ||
|
|
9f2b5b6ee3 | ||
|
|
15726c6b21 | ||
|
|
1c824a37b0 | ||
|
|
7c04ba48a8 | ||
|
|
6f33cf8df7 | ||
|
|
8f85777887 | ||
|
|
931d6f0048 | ||
|
|
692b3828b8 | ||
|
|
320be05a66 | ||
|
|
2c7b407e94 | ||
|
|
20c66a6005 | ||
|
|
e6ff7a6d88 | ||
|
|
aca55734b0 | ||
|
|
f740a93b6c | ||
|
|
e9a71827ed | ||
|
|
dc3f80ffc9 | ||
|
|
75633a10bd | ||
|
|
dda526b83e | ||
|
|
47f7c437fe | ||
|
|
15e46f8ca5 | ||
|
|
5b13553866 | ||
|
|
e14d1f4028 | ||
|
|
a7405b4fca | ||
|
|
8c52c740d3 | ||
|
|
1d70eecbb7 | ||
|
|
3d95276a7f | ||
|
|
88020fcaf4 | ||
|
|
4e89b6365a | ||
|
|
52396f68f5 | ||
|
|
5233e6b690 | ||
|
|
3c469c80be | ||
|
|
0040c8a5f8 | ||
|
|
e8751d2116 | ||
|
|
9bc439a913 | ||
|
|
4989eacaf9 | ||
|
|
c7b257eb66 | ||
|
|
2f76b724a7 | ||
|
|
cd709260ce | ||
|
|
87202453b1 | ||
|
|
4786193731 | ||
|
|
90f523bee8 | ||
|
|
78bb996072 | ||
|
|
822963e1a9 | ||
|
|
773a5d3570 | ||
|
|
acc5277258 | ||
|
|
4ecb98a5e6 | ||
|
|
75093f1c09 | ||
|
|
0351595243 | ||
|
|
5ab74affb5 | ||
|
|
8230b74c78 | ||
|
|
5832e8cd49 | ||
|
|
0220af24d0 | ||
|
|
3e6d81ef1b | ||
|
|
852daaa88e | ||
|
|
7dfc4d9ded | ||
|
|
98002f63dc | ||
|
|
80696972cb | ||
|
|
a3d4eb6048 | ||
|
|
0f88e6c2fe | ||
|
|
3524f273de | ||
|
|
fb3d64c43f | ||
|
|
90d43f28d9 | ||
|
|
a29f10a504 | ||
|
|
6702747652 | ||
|
|
a8b920698d | ||
|
|
0b2685f271 | ||
|
|
120be431ee | ||
|
|
f50eb1f047 | ||
|
|
32d6661a72 | ||
|
|
f57d8dab6d | ||
|
|
5ba0e674f2 | ||
|
|
55f67785cd | ||
|
|
6d082330e6 | ||
|
|
0fdc5b2826 | ||
|
|
31f77e62ea | ||
|
|
3435a6701a | ||
|
|
d4367b97a6 | ||
|
|
1151157dff | ||
|
|
e3c609351f | ||
|
|
01412070ad | ||
|
|
e9c58ac3d5 | ||
|
|
2833015ff1 | ||
|
|
12ef32835c | ||
|
|
eae5f4d870 | ||
|
|
30e1c989d0 | ||
|
|
d4b3eff42f | ||
|
|
9f0e269baf | ||
|
|
c6e9e33feb | ||
|
|
62c7f95f7c | ||
|
|
a304427973 | ||
|
|
c04597eca5 | ||
|
|
9d4f9c12b9 | ||
|
|
d1c4eda072 | ||
|
|
4944a634fb | ||
|
|
2108a77e12 | ||
|
|
f3bfe7b64c | ||
|
|
9f7442c1c0 | ||
|
|
64abbc8205 | ||
|
|
165f190004 | ||
|
|
b142904cc3 | ||
|
|
abc0f952e0 | ||
|
|
e6f43d1f05 | ||
|
|
25b9722019 | ||
|
|
157bef5df6 | ||
|
|
ef281f810f | ||
|
|
921dd8fe67 | ||
|
|
fb580ff79d | ||
|
|
0110e5bedd | ||
|
|
3cd99f680a | ||
|
|
34e4383c1e | ||
|
|
d4c3f02d8a | ||
|
|
839175a5e6 | ||
|
|
5924e443fb | ||
|
|
346ce67bc4 | ||
|
|
1477bf07a9 | ||
|
|
056990151a | ||
|
|
fa86d890fe | ||
|
|
5922743f19 | ||
|
|
afc853f5f5 | ||
|
|
be8243d9d1 | ||
|
|
1322b872b6 | ||
|
|
e46072c726 | ||
|
|
33ed41307e | ||
|
|
e865eaf412 | ||
|
|
e866b64352 | ||
|
|
a3a96496b6 | ||
|
|
75644a35ec | ||
|
|
ec09413575 | ||
|
|
f9c2807ce1 | ||
|
|
8075d33d39 | ||
|
|
d7dcc46988 | ||
|
|
01b2b47823 | ||
|
|
d087a5fdde | ||
|
|
43dbdb3e8f | ||
|
|
d938b1c3f7 | ||
|
|
59eda3159b | ||
|
|
7fabc6f52d | ||
|
|
b21ea61342 | ||
|
|
2952733a11 |
@@ -10,43 +10,38 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
get-version:
|
||||||
|
uses: ./.github/workflows/get-version.yml
|
||||||
|
with:
|
||||||
|
version: ${{ github.event.inputs.version }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-version]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
java-package: 'jdk+fx'
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- id: versions
|
- name: Ensure major jfx version in pom equals in jdk
|
||||||
name: Apply version information
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
exit 1
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
}
|
||||||
else
|
- name: Set version
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
fi
|
|
||||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
|
||||||
REVCOUNT=`git rev-list --count HEAD`
|
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
|
||||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
|
||||||
echo "::set-output name=revNum::${REVCOUNT}"
|
|
||||||
- name: Validate Version
|
|
||||||
uses: skymatic/semver-validation-action@v1
|
|
||||||
with:
|
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
@@ -60,7 +55,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
--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.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -69,8 +64,8 @@ jobs:
|
|||||||
- name: Prepare additional launcher
|
- name: Prepare additional launcher
|
||||||
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
||||||
env:
|
env:
|
||||||
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
|
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
|
||||||
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
|
REVISION_NUM: ${{ needs.get-version.outputs.revNum }}
|
||||||
- name: Run jpackage
|
- name: Run jpackage
|
||||||
run: >
|
run: >
|
||||||
${JAVA_HOME}/bin/jpackage
|
${JAVA_HOME}/bin/jpackage
|
||||||
@@ -84,18 +79,21 @@ jobs:
|
|||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
||||||
--app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
|
--java-options "--enable-preview"
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64"
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||||
--java-options "-Dfile.encoding=\"utf-8\""
|
--java-options "-Dfile.encoding=\"utf-8\""
|
||||||
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
|
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
|
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
|
||||||
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\""
|
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\""
|
||||||
|
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\""
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
|
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
|
||||||
--java-options "-Dcryptomator.showTrayIcon=false"
|
--java-options "-Dcryptomator.showTrayIcon=false"
|
||||||
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\""
|
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
|
||||||
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
|
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
|
||||||
--resource-dir dist/linux/resources
|
--resource-dir dist/linux/resources
|
||||||
- name: Patch Cryptomator.AppDir
|
- name: Patch Cryptomator.AppDir
|
||||||
@@ -133,7 +131,7 @@ jobs:
|
|||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
run: >
|
run: >
|
||||||
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ steps.versions.outputs.semVerStr }}-x86_64.AppImage
|
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-x86_64.AppImage
|
||||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
||||||
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
|
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
|
||||||
- name: Create detached GPG signatures
|
- name: Create detached GPG signatures
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -16,16 +16,15 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
name: Compile and Test
|
name: Compile and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-java@v2
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
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@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.sonar/cache
|
path: ~/.sonar/cache
|
||||||
key: ${{ runner.os }}-sonar
|
key: ${{ runner.os }}-sonar
|
||||||
@@ -43,15 +42,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
- name: Sign source tarball with key 615D449FE6E6A235
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
run: |
|
|
||||||
git archive --prefix="cryptomator-${{ github.ref_name }}/" -o "cryptomator-${{ github.ref_name }}.tar.gz" ${{ github.ref }}
|
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
|
||||||
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
|
|
||||||
env:
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
|
||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
|
||||||
- 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@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@@ -60,9 +50,6 @@ jobs:
|
|||||||
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
|
||||||
files: |
|
|
||||||
cryptomator-*.tar.gz.asc
|
|
||||||
fail_on_unmatched_files: true
|
|
||||||
body: |-
|
body: |-
|
||||||
:construction: Work in Progress
|
:construction: Work in Progress
|
||||||
|
|
||||||
|
|||||||
@@ -1,72 +1,98 @@
|
|||||||
name: Build Debian Package
|
name: Build Debian Package
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
|
ref:
|
||||||
|
description: 'GitHub Ref (e.g. refs/tags/1.6.16)'
|
||||||
|
required: true
|
||||||
|
semver:
|
||||||
|
description: 'SemVer String (e.g. 1.7.0-beta1)'
|
||||||
|
required: true
|
||||||
|
ppaver:
|
||||||
|
description: 'Base PPA Version String (e.g. 1.6.16+1.7.0~beta1) without -0ppa1'
|
||||||
|
required: true
|
||||||
dput:
|
dput:
|
||||||
description: 'Upload to PPA'
|
description: 'Upload to PPA'
|
||||||
required: true
|
required: true
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
version:
|
|
||||||
description: 'Version'
|
|
||||||
required: false
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/19/openjfx-19_linux-x64_bin-jmods.zip'
|
||||||
|
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/19/openjfx-19_linux-aarch64_bin-jmods.zip'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Debian Package
|
name: Build Debian Package
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install build tools
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install debhelper devscripts dput
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v2
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
|
||||||
cache: 'maven'
|
|
||||||
- id: versions
|
- id: versions
|
||||||
name: Apply version information
|
name: Get version information
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
SEM_VER_STR="${{ inputs.semver }}"
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
|
||||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
|
||||||
else
|
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
|
||||||
fi
|
|
||||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||||
REVCOUNT=`git rev-list --count HEAD`
|
REVCOUNT=`git rev-list --count HEAD`
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=revNum::${REVCOUNT}"
|
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}"
|
- name: Install build tools
|
||||||
- name: Validate Version
|
run: |
|
||||||
uses: skymatic/semver-validation-action@v1
|
sudo add-apt-repository ppa:coffeelibs/openjdk
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19 libgtk2.0-0
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
distribution: 'zulu'
|
||||||
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
cache: 'maven'
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
||||||
- name: Create orig.tar.gz with common/ libs/ mods/
|
- name: Download OpenJFX jmods
|
||||||
|
id: download-jmods
|
||||||
|
run: |
|
||||||
|
curl -L ${{ env.OPENJFX_JMODS_AMD64 }} -o openjfx-amd64.zip
|
||||||
|
mkdir -p jmods/amd64
|
||||||
|
unzip -j openjfx-amd64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/amd64
|
||||||
|
curl -L ${{ env.OPENJFX_JMODS_AARCH64 }} -o openjfx-aarch64.zip
|
||||||
|
mkdir -p jmods/aarch64
|
||||||
|
unzip -j openjfx-aarch64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/aarch64
|
||||||
|
- name: Ensure major jfx version in pom and in jmods is the same
|
||||||
|
run: |
|
||||||
|
JMOD_VERSION_AMD64=$(jmod describe ${JAVA_HOME}/jmods/amd64/javafx.base.jmod | head -1)
|
||||||
|
JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64#*@}
|
||||||
|
JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64%%.*}
|
||||||
|
JMOD_VERSION_AARCH64=$(jmod describe ${JAVA_HOME}/jmods/aarch64/javafx.base.jmod | head -1)
|
||||||
|
JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64#*@}
|
||||||
|
JMOD_VERSION_AARCH64=${JMOD_VERSION_AARCH64%%.*}
|
||||||
|
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
|
||||||
|
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
||||||
|
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
||||||
|
|
||||||
|
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then
|
||||||
|
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION_AMD64})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AARCH64 ]; then
|
||||||
|
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != aarch64 jmod version (${JMOD_VERSION_AARCH64})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Create orig.tar.gz with common/ libs/ mods/ jmods/
|
||||||
run: |
|
run: |
|
||||||
mkdir pkgdir
|
mkdir pkgdir
|
||||||
cp -r target/libs pkgdir
|
cp -r target/libs pkgdir
|
||||||
cp -r target/mods pkgdir
|
cp -r target/mods pkgdir
|
||||||
|
cp -r jmods pkgdir
|
||||||
cp -r dist/linux/common/ pkgdir
|
cp -r dist/linux/common/ pkgdir
|
||||||
cp target/cryptomator-*.jar pkgdir/mods
|
cp target/cryptomator-*.jar pkgdir/mods
|
||||||
tar -cJf cryptomator_${{ steps.versions.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir .
|
tar -cJf cryptomator_${{ inputs.ppaver }}.orig.tar.xz -C pkgdir .
|
||||||
- name: Patch and rename pkgdir
|
- name: Patch and rename pkgdir
|
||||||
run: |
|
run: |
|
||||||
cp -r dist/linux/debian/ pkgdir
|
cp -r dist/linux/debian/ pkgdir
|
||||||
@@ -74,12 +100,12 @@ jobs:
|
|||||||
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
|
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
|
||||||
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
|
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
|
||||||
find . -name "*.jar" >> pkgdir/debian/source/include-binaries
|
find . -name "*.jar" >> pkgdir/debian/source/include-binaries
|
||||||
mv pkgdir cryptomator_${{ steps.versions.outputs.ppaVerStr }}
|
mv pkgdir cryptomator_${{ inputs.ppaver }}
|
||||||
env:
|
env:
|
||||||
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
|
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
|
||||||
VERSION_NUM: ${{ steps.versions.outputs.semVerNum }}
|
VERSION_NUM: ${{ steps.versions.outputs.semVerNum }}
|
||||||
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
|
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
|
||||||
PPA_VERSION: ${{ steps.versions.outputs.ppaVerStr }}-0ppa1
|
PPA_VERSION: ${{ inputs.ppaver }}-0ppa1
|
||||||
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
|
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
@@ -94,7 +120,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback
|
DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback
|
||||||
DEBSIGN_KEYID: 615D449FE6E6A235
|
DEBSIGN_KEYID: 615D449FE6E6A235
|
||||||
working-directory: cryptomator_${{ steps.versions.outputs.ppaVerStr }}
|
working-directory: cryptomator_${{ inputs.ppaver }}
|
||||||
- name: Create detached GPG signatures
|
- name: Create detached GPG signatures
|
||||||
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
|
||||||
@@ -111,13 +137,21 @@ jobs:
|
|||||||
cryptomator_*_amd64.deb
|
cryptomator_*_amd64.deb
|
||||||
cryptomator_*.asc
|
cryptomator_*.asc
|
||||||
- name: Publish on PPA
|
- name: Publish on PPA
|
||||||
if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.dput == 'true'
|
if: inputs.dput
|
||||||
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
|
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
|
||||||
|
|
||||||
|
# If ref is a tag, also upload to GitHub Releases:
|
||||||
|
- name: Determine tag name
|
||||||
|
if: startsWith(inputs.ref, 'refs/tags/')
|
||||||
|
run: |
|
||||||
|
REF=${{ inputs.ref }}
|
||||||
|
echo "TAG_NAME=${REF##*/}" >> $GITHUB_ENV
|
||||||
- name: Publish Debian package on GitHub Releases
|
- name: Publish Debian package on GitHub Releases
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(inputs.ref, 'refs/tags/')
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
|
tag_name: ${{ github.env.TAG_NAME }}
|
||||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
files: |
|
files: |
|
||||||
cryptomator_*_amd64.deb
|
cryptomator_*_amd64.deb
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
name: Report Download Stats
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0/15 * * * *' # run every 15 min - don't forget to adjust the "interval" in the json sent to the metrics endpoint
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
report-download-stats:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get download count of latest releases
|
||||||
|
id: get-stats
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const query = `query($owner:String!, $name:String!) {
|
||||||
|
repository(owner:$owner, name:$name){
|
||||||
|
releases(first: 10, orderBy: {field: CREATED_AT, direction: DESC}) {
|
||||||
|
nodes {
|
||||||
|
isPrerelease
|
||||||
|
tagName
|
||||||
|
releaseAssets(first: 20) {
|
||||||
|
nodes {
|
||||||
|
name
|
||||||
|
downloadCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
const variables = {
|
||||||
|
owner: context.repo.owner,
|
||||||
|
name: context.repo.repo
|
||||||
|
}
|
||||||
|
return await github.graphql(query, variables)
|
||||||
|
- name: Transform Results
|
||||||
|
id: transform-stats
|
||||||
|
run: |
|
||||||
|
TIME=$(($(date +%s) / $INTERVAL * $INTERVAL))
|
||||||
|
echo ${JSON_DATA} | jq --arg TIME "$TIME" --arg INTERVAL "$INTERVAL" -c '.repository.releases.nodes[] | select(.isPrerelease == false) | .tagName as $tagName | .releaseAssets.nodes[] | {filename: .name, downloads: .downloadCount, release: $tagName, time: ($TIME|tonumber), interval: ($INTERVAL|tonumber)}' > input.json
|
||||||
|
|
||||||
|
jq -c 'select(.filename|endswith("-x86_64.AppImage")) | {name: "github.releases.downloads", tags: ["file=AppImage", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
||||||
|
jq -c 'select(.filename|endswith("_amd64.deb")) | {name: "github.releases.downloads", tags: ["file=deb", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
||||||
|
jq -c 'select(.filename|endswith("-x64.msi")) | {name: "github.releases.downloads", tags: ["file=msi", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
||||||
|
jq -c 'select(.filename|endswith("-x64.exe")) | {name: "github.releases.downloads", tags: ["file=exe", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
||||||
|
jq -c 'select(.filename|endswith("-arm64.dmg")) | {name: "github.releases.downloads", tags: ["file=dmg", "version=\(.release)", "arch=arm64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
||||||
|
jq -c 'select(.filename|endswith(".dmg")) | select(.filename|endswith("-arm64.dmg")|not) | {name: "github.releases.downloads", tags: ["file=dmg", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
||||||
|
|
||||||
|
RESULT=$(jq -s -c "." output.json)
|
||||||
|
echo "result=${RESULT}" >> $GITHUB_OUTPUT
|
||||||
|
env:
|
||||||
|
INTERVAL: 900
|
||||||
|
JSON_DATA: ${{ steps.get-stats.outputs.result }}
|
||||||
|
- name: Upload Results
|
||||||
|
uses: fjogeleit/http-request-action@v1
|
||||||
|
with:
|
||||||
|
url: 'https://graphite-us-central1.grafana.net/metrics'
|
||||||
|
method: 'POST'
|
||||||
|
contentType: 'application/json'
|
||||||
|
bearerToken: ${{ secrets.GRAFANA_GRAPHITE_TOKEN }}
|
||||||
|
data: ${{ steps.transform-stats.outputs.result }}
|
||||||
|
continue-on-error: true # currently there seems to be a problem with the metrics endpoint, failing every now and then
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
name: Update Error Database
|
||||||
|
|
||||||
|
on:
|
||||||
|
discussion:
|
||||||
|
types: [created, edited, category_changed, answered, unanswered]
|
||||||
|
discussion_comment:
|
||||||
|
types: [created, edited, deleted]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-error-db:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event.discussion.category.name == 'Errors'
|
||||||
|
steps:
|
||||||
|
- name: Query Discussion Data
|
||||||
|
id: query-data
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
|
||||||
|
repository(owner: $owner, name: $name) {
|
||||||
|
discussion(number: $discussionNumber) {
|
||||||
|
id
|
||||||
|
upvoteCount
|
||||||
|
title
|
||||||
|
url
|
||||||
|
answer {
|
||||||
|
url
|
||||||
|
upvoteCount
|
||||||
|
}
|
||||||
|
comments {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
const variables = {
|
||||||
|
owner: context.repo.owner,
|
||||||
|
name: context.repo.repo,
|
||||||
|
discussionNumber: context.payload.discussion.number
|
||||||
|
}
|
||||||
|
return await github.graphql(query, variables)
|
||||||
|
- name: Get Gist
|
||||||
|
id: get-gist
|
||||||
|
uses: andymckay/get-gist-action@master
|
||||||
|
with:
|
||||||
|
gistURL: https://gist.github.com/cryptobot/accba9fb9555e7192271b85606f97230
|
||||||
|
- name: Merge Error Code Data
|
||||||
|
run: |
|
||||||
|
jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json
|
||||||
|
echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json
|
||||||
|
jq -s '.[0] * .[1]' original.json new.json > merged.json
|
||||||
|
env:
|
||||||
|
DISCUSSION: ${{ steps.query-data.outputs.result }}
|
||||||
|
- name: Patch Gist
|
||||||
|
uses: exuanbo/actions-deploy-gist@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CRYPTOBOT_GIST_TOKEN }}
|
||||||
|
gist_id: accba9fb9555e7192271b85606f97230
|
||||||
|
gist_file_name: errorcodes.json
|
||||||
|
file_path: merged.json
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
name: Parse and Validate a version string or tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "A specific version to use"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
outputs:
|
||||||
|
semVerStr:
|
||||||
|
description: "The full version string."
|
||||||
|
value: ${{ jobs.determine-version.outputs.semVerStr}}
|
||||||
|
semVerNum:
|
||||||
|
description: "The numerical part of the version string"
|
||||||
|
value: ${{ jobs.determine-version.outputs.semVerNum}}
|
||||||
|
revNum:
|
||||||
|
description: "The revision number"
|
||||||
|
value: ${{ jobs.determine-version.outputs.revNum}}
|
||||||
|
versionType:
|
||||||
|
description: "Type of the version. Values are [stable, alpha, beta, rc, unknown]"
|
||||||
|
value: ${{ jobs.determine-version.outputs.type }}
|
||||||
|
|
||||||
|
env:
|
||||||
|
JAVA_VERSION: 19
|
||||||
|
JAVA_DIST: 'temurin'
|
||||||
|
JAVA_CACHE: 'maven'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
determine-version:
|
||||||
|
name: 'Determines the version following semver'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
semVerNum: ${{ steps.versions.outputs.semVerNum }}
|
||||||
|
semVerStr: ${{ steps.versions.outputs.semVerStr }}
|
||||||
|
revNum: ${{ steps.versions.outputs.revNum }}
|
||||||
|
type: ${{ steps.versions.outputs.type}}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
cache: ${{ env.JAVA_CACHE }}
|
||||||
|
- id: versions
|
||||||
|
name: Get version information
|
||||||
|
run: |
|
||||||
|
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
|
SEM_VER_STR=${GITHUB_REF##*/}
|
||||||
|
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
|
SEM_VER_STR="${{ github.event.inputs.version }}"
|
||||||
|
else
|
||||||
|
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||||
|
fi
|
||||||
|
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||||
|
REVCOUNT=`git rev-list --count HEAD`
|
||||||
|
TYPE="unknown"
|
||||||
|
if [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
TYPE="stable"
|
||||||
|
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-alpha[1-9]+$ ]]; then
|
||||||
|
TYPE="alpha"
|
||||||
|
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-beta[1-9]+$ ]]; then
|
||||||
|
TYPE="beta"
|
||||||
|
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-rc[1-9]$ ]]; then
|
||||||
|
TYPE="rc"
|
||||||
|
fi
|
||||||
|
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
|
||||||
|
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
|
||||||
|
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
|
||||||
|
echo "type=${TYPE}" >> $GITHUB_OUTPUT
|
||||||
|
- name: Validate Version
|
||||||
|
uses: skymatic/semver-validation-action@v2
|
||||||
|
with:
|
||||||
|
version: ${{ steps.versions.outputs.semVerStr }}
|
||||||
@@ -10,43 +10,52 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
get-version:
|
||||||
|
uses: ./.github/workflows/get-version.yml
|
||||||
|
with:
|
||||||
|
version: ${{ github.event.inputs.version }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build Cryptomator.app
|
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
|
||||||
runs-on: macos-11
|
runs-on: ${{ matrix.os }}
|
||||||
|
needs: [get-version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: macos-11
|
||||||
|
architecture: x64
|
||||||
|
output-suffix: x64
|
||||||
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
|
- os: [self-hosted, macOS, ARM64]
|
||||||
|
architecture: aarch64
|
||||||
|
output-suffix: arm64
|
||||||
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
java-package: 'jdk+fx'
|
||||||
|
architecture: ${{ matrix.architecture }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- id: versions
|
- name: Ensure major jfx version in pom equals in jdk
|
||||||
name: Apply version information
|
if: ${{ !contains(matrix.os, 'self-hosted') }}
|
||||||
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
exit 1
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
}
|
||||||
else
|
- name: Set version
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
fi
|
|
||||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
|
||||||
REVCOUNT=`git rev-list --count HEAD`
|
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
|
||||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
|
||||||
echo "::set-output name=revNum::${REVCOUNT}"
|
|
||||||
- name: Validate Version
|
|
||||||
uses: skymatic/semver-validation-action@v1
|
|
||||||
with:
|
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,mac -DskipTests
|
run: mvn -B clean package -Pdependency-check,mac -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
@@ -60,7 +69,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
--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.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -79,19 +88,23 @@ jobs:
|
|||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
||||||
--app-version "${{ steps.versions.outputs.semVerNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||||
|
--java-options "--enable-preview"
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dfile.encoding=\"utf-8\""
|
--java-options "-Dfile.encoding=\"utf-8\""
|
||||||
--java-options "-Dapple.awt.enableTemplateImages=true"
|
--java-options "-Dapple.awt.enableTemplateImages=true"
|
||||||
--java-options "-Dsun.java2d.metal=true"
|
--java-options "-Dsun.java2d.metal=true"
|
||||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||||
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
|
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
|
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
|
||||||
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
|
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
|
||||||
|
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\""
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
|
||||||
|
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.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
|
||||||
- name: Patch Cryptomator.app
|
- name: Patch Cryptomator.app
|
||||||
@@ -101,8 +114,8 @@ jobs:
|
|||||||
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
|
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||||
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
|
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||||
env:
|
env:
|
||||||
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
|
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||||
REVISION_NO: ${{ steps.versions.outputs.revNum }}
|
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
||||||
- name: Generate license for dmg
|
- name: Generate license for dmg
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party
|
mvn -B license:add-third-party
|
||||||
@@ -136,6 +149,10 @@ jobs:
|
|||||||
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
|
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
|
||||||
- name: Codesign
|
- name: Codesign
|
||||||
run: |
|
run: |
|
||||||
|
echo "Codesigning jdk files..."
|
||||||
|
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||||
|
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name 'jspawnhelper' -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
|
||||||
|
echo "Codesigning jar contents..."
|
||||||
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||||
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
|
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
|
||||||
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
||||||
@@ -184,38 +201,20 @@ jobs:
|
|||||||
--icon ".background" 128 758
|
--icon ".background" 128 758
|
||||||
--icon ".fseventsd" 320 758
|
--icon ".fseventsd" 320 758
|
||||||
--icon ".VolumeIcon.icns" 512 758
|
--icon ".VolumeIcon.icns" 512 758
|
||||||
Cryptomator-${VERSION_NO}.dmg dmg
|
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
|
||||||
env:
|
env:
|
||||||
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
|
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||||
- name: Install notarization credentials
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
run: |
|
|
||||||
# create temporary keychain
|
|
||||||
KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db
|
|
||||||
security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH}
|
|
||||||
security set-keychain-settings -lut 900 ${KEYCHAIN_PATH}
|
|
||||||
security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH}
|
|
||||||
|
|
||||||
# import credentials from secrets
|
|
||||||
sudo xcode-select -s /Applications/Xcode_13.0.app
|
|
||||||
xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}"
|
|
||||||
env:
|
|
||||||
NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }}
|
|
||||||
NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
|
||||||
NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }}
|
|
||||||
NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
|
||||||
NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }}
|
|
||||||
- name: Notarize .dmg
|
- name: Notarize .dmg
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
run: |
|
uses: cocoalibs/xcode-notarization-action@v1
|
||||||
KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db
|
with:
|
||||||
sudo xcode-select -s /Applications/Xcode_13.0.app
|
app-path: 'Cryptomator-*.dmg'
|
||||||
xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait
|
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
||||||
xcrun stapler staple Cryptomator-*.dmg
|
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
|
||||||
env:
|
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
||||||
NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }}
|
xcode-path: ${{ matrix.xcode-path }}
|
||||||
- name: Add possible alpha/beta tags to installer name
|
- name: Add possible alpha/beta tags to installer name
|
||||||
run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}.dmg
|
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
|
||||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
@@ -227,14 +226,10 @@ jobs:
|
|||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
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: Clean up notarization credentials
|
|
||||||
if: ${{ always() }}
|
|
||||||
run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db
|
|
||||||
continue-on-error: true
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: dmg
|
name: dmg-${{ matrix.output-suffix }}
|
||||||
path: Cryptomator-*.dmg
|
path: Cryptomator-*.dmg
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Publish dmg on GitHub Releases
|
- name: Publish dmg on GitHub Releases
|
||||||
@@ -246,5 +241,3 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
Cryptomator-*.dmg
|
Cryptomator-*.dmg
|
||||||
Cryptomator-*.asc
|
Cryptomator-*.asc
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: Post Release Publish Tasks
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
get-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download source tarball
|
||||||
|
run: |
|
||||||
|
curl -L -H "Accept: application/vnd.github+json" ${{ github.event.release.tarball_url }} --output cryptomator-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
- name: Sign source tarball with key 615D449FE6E6A235
|
||||||
|
run: |
|
||||||
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
|
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
|
||||||
|
env:
|
||||||
|
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||||
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
|
- name: Publish asc on GitHub Releases
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||||
|
files: |
|
||||||
|
cryptomator-*.tar.gz.asc
|
||||||
|
- name: Slack Notification
|
||||||
|
uses: rtCamp/action-slack-notify@v2
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
SLACK_USERNAME: 'Cryptobot'
|
||||||
|
SLACK_ICON: false
|
||||||
|
SLACK_ICON_EMOJI: ':bot:'
|
||||||
|
SLACK_CHANNEL: 'cryptomator-desktop'
|
||||||
|
SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
|
||||||
|
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|build deb Package>."
|
||||||
|
SLACK_FOOTER: false
|
||||||
|
MSG_MINIMAL: true
|
||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -16,10 +16,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-java@v2
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: Release Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'release/**'
|
||||||
|
- 'hotfix/**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-check-precondition:
|
||||||
|
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- id: validate-pom-version
|
||||||
|
name: Validate POM version
|
||||||
|
run: |
|
||||||
|
if [[ $GITHUB_REF =~ refs/heads/(hotfix|release)/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
|
SEM_VER_STR=${GITHUB_REF##*/}
|
||||||
|
else
|
||||||
|
echo "Failed to parse version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${SEM_VER_STR} == `mvn help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then
|
||||||
|
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "Version not set in POM"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Validate release in org.cryptomator.Cryptomator.metainfo.xml file
|
||||||
|
run: |
|
||||||
|
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\"/>" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
|
||||||
|
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -10,48 +10,46 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
WINFSP_MSI: https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi
|
JAVA_DIST: 'zulu'
|
||||||
|
JAVA_CACHE: 'maven'
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
get-version:
|
||||||
|
uses: ./.github/workflows/get-version.yml
|
||||||
|
with:
|
||||||
|
version: ${{ github.event.inputs.version }}
|
||||||
|
|
||||||
build-msi:
|
build-msi:
|
||||||
name: Build .msi Installer
|
name: Build .msi Installer
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
needs: [get-version]
|
||||||
|
env:
|
||||||
|
LOOPBACK_ALIAS: 'cryptomator-vault'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
java-package: 'jdk+fx'
|
||||||
- id: versions
|
cache: ${{ env.JAVA_CACHE }}
|
||||||
name: Apply version information
|
- name: Ensure major jfx version in pom equals in jdk
|
||||||
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||||
SEM_VER_STR=${GITHUB_REF##*/}
|
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
exit 1
|
||||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
}
|
||||||
else
|
- name: Set version
|
||||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
fi
|
|
||||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
|
||||||
REVCOUNT=`git rev-list --count HEAD`
|
|
||||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
|
||||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
|
||||||
echo "::set-output name=revNum::${REVCOUNT}"
|
|
||||||
- name: Validate Version
|
|
||||||
uses: skymatic/semver-validation-action@v1
|
|
||||||
with:
|
|
||||||
version: ${{ steps.versions.outputs.semVerStr }}
|
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,win -DskipTests
|
run: mvn -B clean package -Pdependency-check,win -DskipTests
|
||||||
- name: Patch target dir
|
- name: Patch target dir
|
||||||
@@ -65,7 +63,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
--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.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
@@ -84,33 +82,48 @@ jobs:
|
|||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
||||||
--app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
|
--java-options "--enable-preview"
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.win"
|
||||||
--java-options "-Xss5m"
|
--java-options "-Xss5m"
|
||||||
--java-options "-Xmx256m"
|
--java-options "-Xmx256m"
|
||||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||||
--java-options "-Dfile.encoding=\"utf-8\""
|
--java-options "-Dfile.encoding=\"utf-8\""
|
||||||
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
|
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
|
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
|
||||||
--java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\""
|
--java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\""
|
||||||
|
--java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\""
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
|
||||||
--java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\""
|
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
|
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
|
||||||
|
--java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\""
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||||
--java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\""
|
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
|
||||||
|
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
|
||||||
|
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"~/AppData/Roaming/Cryptomator/keychain.json\""
|
||||||
--resource-dir dist/win/resources
|
--resource-dir dist/win/resources
|
||||||
--icon dist/win/resources/Cryptomator.ico
|
--icon dist/win/resources/Cryptomator.ico
|
||||||
- name: Patch Application Directory
|
- name: Patch Application Directory
|
||||||
run: |
|
run: |
|
||||||
cp dist/win/contrib/* appdir/Cryptomator
|
cp dist/win/contrib/* appdir/Cryptomator
|
||||||
|
- name: Set LOOPBACK_ALIAS in patchWebDAV.bat
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$patchScript = "appdir\Cryptomator\patchWebDAV.bat"
|
||||||
|
try {
|
||||||
|
(Get-Content $patchScript ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"${{ env.LOOPBACK_ALIAS}}`"" | Set-Content $patchScript
|
||||||
|
} catch {
|
||||||
|
Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
- name: Fix permissions
|
- name: Fix permissions
|
||||||
run: attrib -r appdir/Cryptomator/Cryptomator.exe
|
run: attrib -r appdir/Cryptomator/Cryptomator.exe
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
- name: Codesign
|
- name: Codesign
|
||||||
uses: skymatic/code-sign-action@v1
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||||
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
|
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
|
||||||
description: Cryptomator
|
description: Cryptomator
|
||||||
timestampUrl: 'http://timestamp.digicert.com'
|
timestampUrl: 'http://timestamp.digicert.com'
|
||||||
folder: appdir/Cryptomator
|
folder: appdir/Cryptomator
|
||||||
@@ -137,7 +150,7 @@ jobs:
|
|||||||
--name Cryptomator
|
--name Cryptomator
|
||||||
--vendor "Skymatic GmbH"
|
--vendor "Skymatic GmbH"
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
||||||
--app-version "${{ steps.versions.outputs.semVerNum }}"
|
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||||
--win-menu
|
--win-menu
|
||||||
--win-dir-chooser
|
--win-dir-chooser
|
||||||
--win-shortcut-prompt
|
--win-shortcut-prompt
|
||||||
@@ -149,16 +162,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
|
||||||
- name: Codesign MSI
|
- name: Codesign MSI
|
||||||
uses: skymatic/code-sign-action@v1
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||||
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
|
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
|
||||||
description: Cryptomator Installer
|
description: Cryptomator Installer
|
||||||
timestampUrl: 'http://timestamp.digicert.com'
|
timestampUrl: 'http://timestamp.digicert.com'
|
||||||
folder: installer
|
folder: installer
|
||||||
- name: Add possible alpha/beta tags to installer name
|
- name: Add possible alpha/beta tags to installer name
|
||||||
run: mv installer/Cryptomator-*.msi Cryptomator-${{ steps.versions.outputs.semVerStr }}-x64.msi
|
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.msi
|
||||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
@@ -183,29 +196,34 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
*.msi
|
*.msi
|
||||||
*.asc
|
*.asc
|
||||||
outputs:
|
|
||||||
semVerNum: ${{ steps.versions.outputs.semVerNum }}
|
call-winget-flow:
|
||||||
semVerStr: ${{ steps.versions.outputs.semVerStr }}
|
needs: [get-version, build-msi]
|
||||||
revNum: ${{ steps.versions.outputs.revNum }}
|
if: github.event.action == 'published' && needs.get-version.outputs.versionType == 'stable'
|
||||||
|
uses: ./.github/workflows/winget.yml
|
||||||
|
with:
|
||||||
|
releaseTag: ${{ github.event.release.tag_name }}
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
|
||||||
build-exe:
|
build-exe:
|
||||||
name: Build .exe installer
|
name: Build .exe installer
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: [build-msi]
|
needs: [get-version, build-msi]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Download .msi
|
- name: Download .msi
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: msi
|
name: msi
|
||||||
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
|
||||||
- uses: actions/setup-java@v2
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: ${{ env.JAVA_CACHE }}
|
||||||
- name: Generate license for exe
|
- name: Generate license for exe
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party
|
mvn -B license:add-third-party
|
||||||
@@ -218,14 +236,16 @@ jobs:
|
|||||||
"-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges"
|
"-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges"
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
- name: Download WinFsp
|
- name: Download WinFsp
|
||||||
run:
|
run: |
|
||||||
curl --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }}
|
$winfspUrl= (Select-String -Path ".\dist\win\bundle\resources\winfsp-download.url" -Pattern 'https:.*').Matches.Value
|
||||||
|
curl --output dist/win/bundle/resources/winfsp.msi -L $winfspUrl
|
||||||
|
shell: pwsh
|
||||||
- name: Compile to wixObj file
|
- name: Compile to wixObj file
|
||||||
run: >
|
run: >
|
||||||
"${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs
|
"${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs
|
||||||
-ext WixBalExtension
|
-ext WixBalExtension
|
||||||
-out dist/win/bundle/
|
-out dist/win/bundle/
|
||||||
-dBundleVersion="${{ needs.build-msi.outputs.semVerNum }}.${{ needs.build-msi.outputs.revNum }}"
|
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||||
-dBundleVendor="Skymatic GmbH"
|
-dBundleVendor="Skymatic GmbH"
|
||||||
-dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH"
|
-dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH"
|
||||||
-dAboutUrl="https://cryptomator.org"
|
-dAboutUrl="https://cryptomator.org"
|
||||||
@@ -242,11 +262,11 @@ jobs:
|
|||||||
-ib installer/unsigned/Cryptomator-Installer.exe
|
-ib installer/unsigned/Cryptomator-Installer.exe
|
||||||
-o tmp/engine.exe
|
-o tmp/engine.exe
|
||||||
- name: Codesign burn engine
|
- name: Codesign burn engine
|
||||||
uses: skymatic/code-sign-action@v1
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||||
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
|
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
|
||||||
description: Cryptomator Installer
|
description: Cryptomator Installer
|
||||||
timestampUrl: 'http://timestamp.digicert.com'
|
timestampUrl: 'http://timestamp.digicert.com'
|
||||||
folder: tmp
|
folder: tmp
|
||||||
@@ -256,16 +276,16 @@ jobs:
|
|||||||
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
|
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
|
||||||
-o installer/Cryptomator-Installer.exe
|
-o installer/Cryptomator-Installer.exe
|
||||||
- name: Codesign EXE
|
- name: Codesign EXE
|
||||||
uses: skymatic/code-sign-action@v1
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||||
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
|
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
|
||||||
description: Cryptomator Installer
|
description: Cryptomator Installer
|
||||||
timestampUrl: 'http://timestamp.digicert.com'
|
timestampUrl: 'http://timestamp.digicert.com'
|
||||||
folder: installer
|
folder: installer
|
||||||
- name: Add possible alpha/beta tags to installer name
|
- name: Add possible alpha/beta tags to installer name
|
||||||
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe
|
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.exe
|
||||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
@@ -298,12 +318,12 @@ jobs:
|
|||||||
needs: [build-msi, build-exe]
|
needs: [build-msi, build-exe]
|
||||||
steps:
|
steps:
|
||||||
- name: Download .msi
|
- name: Download .msi
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: msi
|
name: msi
|
||||||
path: msi
|
path: msi
|
||||||
- name: Download .exe
|
- name: Download .exe
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: exe
|
name: exe
|
||||||
path: exe
|
path: exe
|
||||||
@@ -313,7 +333,7 @@ jobs:
|
|||||||
cp msi/*.msi files
|
cp msi/*.msi files
|
||||||
cp exe/*.exe files
|
cp exe/*.exe files
|
||||||
- name: Upload to Kaspersky
|
- name: Upload to Kaspersky
|
||||||
uses: SamKirkland/FTP-Deploy-Action@4.3.0
|
uses: SamKirkland/FTP-Deploy-Action@4.3.3
|
||||||
with:
|
with:
|
||||||
protocol: ftps
|
protocol: ftps
|
||||||
server: allowlist.kaspersky-labs.com
|
server: allowlist.kaspersky-labs.com
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
name: Release to Winget
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
releaseTag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
releaseTag:
|
||||||
|
description: 'Release tag name'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-winget:
|
||||||
|
name: Publish on winget repo
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Get download url for release assets
|
||||||
|
id: get-release-assets
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const query =`query($tag:String!) {
|
||||||
|
repository(owner:"cryptomator", name:"cryptomator"){
|
||||||
|
release(tagName: $tag) {
|
||||||
|
releaseAssets(first:20) {
|
||||||
|
nodes {
|
||||||
|
name
|
||||||
|
downloadUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
const variables = {
|
||||||
|
tag: "${{ inputs.releaseTag }}"
|
||||||
|
}
|
||||||
|
return await github.graphql(query, variables)
|
||||||
|
- name: Submit package to Windows Package Manager Community Repository
|
||||||
|
id: submit-winget
|
||||||
|
run: |
|
||||||
|
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||||
|
$releaseAssets = (ConvertFrom-Json '${{ steps.get-release-assets.outputs.result }}').repository.release.releaseAssets.nodes
|
||||||
|
$installerUrl = $releaseAssets | Where-Object -Property name -match '^Cryptomator-.*\.msi$' | Select -ExpandProperty downloadUrl -First 1
|
||||||
|
.\wingetcreate.exe update Cryptomator.Cryptomator -s -v "${{ inputs.releaseTag }}" -u "$installerUrl" -t ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||||
|
shell: pwsh
|
||||||
+1
-17
@@ -5,25 +5,9 @@
|
|||||||
*.war
|
*.war
|
||||||
*.ear
|
*.ear
|
||||||
|
|
||||||
# Eclipse Settings Files #
|
|
||||||
.settings
|
|
||||||
.project
|
|
||||||
.classpath
|
|
||||||
|
|
||||||
# Maven #
|
# Maven #
|
||||||
target/
|
target/
|
||||||
pom.xml.versionsBackup
|
pom.xml.versionsBackup
|
||||||
|
|
||||||
# IntelliJ Settings Files (https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems) #
|
# Java Crash Logs
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/shelf
|
|
||||||
.idea/dictionaries/**
|
|
||||||
!.idea/dictionaries/dict_*
|
|
||||||
.idea/compiler.xml
|
|
||||||
.idea/jarRepositories.xml
|
|
||||||
.idea/uiDesigner.xml
|
|
||||||
.idea/**/libraries/
|
|
||||||
*.iml
|
|
||||||
|
|
||||||
hs_err_pid*.log
|
hs_err_pid*.log
|
||||||
Generated
+13
@@ -0,0 +1,13 @@
|
|||||||
|
# see https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems
|
||||||
|
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
/usage.statistics.xml
|
||||||
|
/dictionaries/
|
||||||
|
|
||||||
|
# generated from Maven
|
||||||
|
/jarRepositories.xml
|
||||||
|
/modules.xml
|
||||||
|
/*.iml
|
||||||
|
/libraries/*.xml
|
||||||
Generated
+51
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
</profile>
|
||||||
|
<profile name="Annotation profile for Cryptomator Desktop App" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<option name="dagger.fastInit" value="enabled" />
|
||||||
|
<option name="dagger.formatGeneratedSource" value="enabled" />
|
||||||
|
<processorPath useClasspath="false">
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.44/dagger-compiler-2.44.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.44/dagger-2.44.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.44/dagger-producers-2.44.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-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/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/3.12.0/checker-qual-3.12.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.44/dagger-spi-2.44.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.7.0-1.0.6/symbol-processing-api-1.7.0-1.0.6.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.0/kotlin-stdlib-1.7.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.0/kotlin-stdlib-common-1.7.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.0/kotlin-stdlib-jdk8-1.7.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.0/kotlin-stdlib-jdk7-1.7.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$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.5.0/kotlinx-metadata-jvm-0.5.0.jar" />
|
||||||
|
</processorPath>
|
||||||
|
<module name="cryptomator" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
<component name="JavacSettings">
|
||||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
|
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled --enable-preview" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="19" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
|
||||||
<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="-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" -Dcryptomator.ipcSocketPath="~/.config/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" -Dcryptomator.pluginDir="~/.local/share/Cryptomator/plugins" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" -Dcryptomator.p12Path="~/.config/Cryptomator/key.p12" -Dcryptomator.ipcSocketPath="~/.config/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" -Dcryptomator.pluginDir="~/.local/share/Cryptomator/plugins" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
|
||||||
<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="-Dcryptomator.settingsPath="~/.config/Cryptomator-Dev/settings.json" -Dcryptomator.ipcSocketPath="~/.config/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/.local/share/Cryptomator-Dev/logs" -Dcryptomator.pluginDir="~/.local/share/Cryptomator-Dev/plugins" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator-Dev/mnt" -Dcryptomator.showTrayIcon=true -Dfuse.experimental="true" -Xss20m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="~/.config/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="~/.config/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="~/.config/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/.local/share/Cryptomator-Dev/logs" -Dcryptomator.pluginDir="~/.local/share/Cryptomator-Dev/plugins" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator-Dev/mnt" -Dcryptomator.showTrayIcon=true -Dfuse.experimental="true" -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
||||||
<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="-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcSocketPath="~/AppData/Roaming/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.pluginDir="~/AppData/Roaming/Cryptomator/Plugins" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcSocketPath="~/AppData/Roaming/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.pluginDir="~/AppData/Roaming/Cryptomator/Plugins" -Dcryptomator.integrationsWin.keychainPaths="~/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.p12Path="~/AppData/Roaming/Cryptomator/key.p12" -Dcryptomator.mountPointsDir="~/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
|
||||||
<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="-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcSocketPath="~/AppData/Roaming/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator-Dev" -Dcryptomator.pluginDir="~/AppData/Roaming/Cryptomator-Dev/Plugins" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator-Dev" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcSocketPath="~/AppData/Roaming/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator-Dev" -Dcryptomator.pluginDir="~/AppData/Roaming/Cryptomator-Dev/Plugins" -Dcryptomator.integrationsWin.keychainPaths="~/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.p12Path="~/AppData/Roaming/Cryptomator-Dev/key.p12" -Dcryptomator.mountPointsDir="~/Cryptomator-Dev" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+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="~/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.ipcSocketPath="~/Library/Application Support/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/Library/Logs/Cryptomator" -Dcryptomator.pluginDir="~/Library/Application Support/Cryptomator/Plugins" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
|
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.p12Path="~/Library/Application Support/Cryptomator/key.p12" -Dcryptomator.ipcSocketPath="~/Library/Application Support/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/Library/Logs/Cryptomator" -Dcryptomator.pluginDir="~/Library/Application Support/Cryptomator/Plugins" -Dcryptomator.mountPointsDir="~/Cryptomator" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+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="~/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.ipcSocketPath="~/Library/Application Support/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/Library/Logs/Cryptomator-Dev" -Dcryptomator.pluginDir="~/Library/Application Support/Cryptomator-Dev/Plugins" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
|
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="~/Library/Application Support/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="~/Library/Application Support/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/Library/Logs/Cryptomator-Dev" -Dcryptomator.pluginDir="~/Library/Application Support/Cryptomator-Dev/Plugins" -Dcryptomator.mountPointsDir="~/Cryptomator" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
@@ -32,11 +32,16 @@ Cryptomator is provided free of charge as an open-source project despite the hig
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="https://mowcapital.com/"><img src="https://cryptomator.org/img/sponsors/mowcapital.svg" alt="Mow Capital" height="40"></a></td>
|
<td><a href="https://mowcapital.com/"><img src="https://cryptomator.org/img/sponsors/mowcapital.svg" alt="Mow Capital" height="40"></a></td>
|
||||||
|
<td><a href="https://www.easeus.com/"><img src="https://cryptomator.org/img/sponsors/easeus.png" alt="EaseUS" height="40"></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
- [Jameson Lopp](https://www.lopp.net/)
|
### Special Shoutout
|
||||||
|
|
||||||
|
Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/opensource).
|
||||||
|
|
||||||
|
<a href="https://www.macstadium.com/opensource"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="MacStadium" height="100"></a>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -56,7 +61,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
|||||||
- File names get encrypted
|
- File names get encrypted
|
||||||
- Folder structure gets obfuscated
|
- Folder structure gets obfuscated
|
||||||
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
||||||
- Three thousand commits for the security of your data!! :tada:
|
- Four thousand commits for the security of your data!! :tada:
|
||||||
|
|
||||||
### Privacy
|
### Privacy
|
||||||
|
|
||||||
@@ -80,25 +85,20 @@ For more information on the security details visit [cryptomator.org](https://doc
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* JDK 17 (e.g. temurin)
|
* JDK 19 (e.g. temurin)
|
||||||
* Maven 3
|
* Maven 3
|
||||||
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
|
|
||||||
|
|
||||||
### Run Maven
|
### Run Maven
|
||||||
|
|
||||||
```
|
```
|
||||||
mvn clean install
|
mvn clean install
|
||||||
# or mvn clean install -Pwindows
|
# or mvn clean install -Pwin
|
||||||
# or mvn clean install -Pmac
|
# or mvn clean install -Pmac
|
||||||
# or mvn clean install -Plinux
|
# or mvn clean install -Plinux
|
||||||
```
|
```
|
||||||
|
|
||||||
This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages.
|
This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages.
|
||||||
|
|
||||||
### Start Cryptomator
|
|
||||||
|
|
||||||
If you unzip the buildkit for your OS, you will find a launcher script with some basic settings. You might want to adjust these to your needs. To start Cryptomator, simply execute the launcher script from a terminal, e.g. `launcher-linux.sh`, if you're on a Linux system.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
|
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
|
||||||
|
|||||||
Vendored
+3
-1
@@ -19,7 +19,7 @@ cp ../../../target/cryptomator-*.jar ../../../target/mods
|
|||||||
${JAVA_HOME}/bin/jlink \
|
${JAVA_HOME}/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JAVA_HOME}/jmods" \
|
--module-path "${JAVA_HOME}/jmods" \
|
||||||
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
@@ -39,6 +39,8 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--name Cryptomator \
|
--name Cryptomator \
|
||||||
--vendor "Skymatic GmbH" \
|
--vendor "Skymatic GmbH" \
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
|
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
|
||||||
|
--java-options "--enable-preview" \
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" \
|
||||||
--java-options "-Xss5m" \
|
--java-options "-Xss5m" \
|
||||||
--java-options "-Xmx256m" \
|
--java-options "-Xmx256m" \
|
||||||
--app-version "${VERSION}.${REVISION_NO}" \
|
--app-version "${VERSION}.${REVISION_NO}" \
|
||||||
|
|||||||
+2
-2
@@ -19,7 +19,7 @@ fi
|
|||||||
export LD_PRELOAD=lib/app/libjffi.so
|
export LD_PRELOAD=lib/app/libjffi.so
|
||||||
|
|
||||||
if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then
|
if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then
|
||||||
bin/Cryptomator-gtk2
|
bin/Cryptomator-gtk2 $@
|
||||||
else
|
else
|
||||||
bin/Cryptomator
|
bin/Cryptomator $@
|
||||||
fi
|
fi
|
||||||
@@ -66,6 +66,12 @@
|
|||||||
</content_rating>
|
</content_rating>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release date="2022-12-06" version="1.6.16"/>
|
||||||
|
<release date="2022-10-06" version="1.6.15"/>
|
||||||
|
<release date="2022-08-31" version="1.6.14"/>
|
||||||
|
<release date="2022-07-27" version="1.6.12"/>
|
||||||
|
<release date="2022-07-26" version="1.6.11"/>
|
||||||
|
<release date="2022-05-03" version="1.6.10"/>
|
||||||
<release date="2022-04-27" version="1.6.9"/>
|
<release date="2022-04-27" version="1.6.9"/>
|
||||||
<release date="2022-03-30" version="1.6.8"/>
|
<release date="2022-03-30" version="1.6.8"/>
|
||||||
<release date="2021-12-16" version="1.6.5"/>
|
<release date="2021-12-16" version="1.6.5"/>
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
cryptomator (${PPA_VERSION}) bionic; urgency=low
|
cryptomator (${PPA_VERSION}) focal; urgency=low
|
||||||
|
|
||||||
* Full changelog can be found on https://github.com/cryptomator/cryptomator/releases
|
* Full changelog can be found on https://github.com/cryptomator/cryptomator/releases
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -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), openjdk-17-jdk
|
Build-Depends: debhelper (>=10), coffeelibs-jdk-19, libgtk2.0-0, libgtk-3-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
|
||||||
|
|||||||
Vendored
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
# fix for https://github.com/cryptomator/cryptomator/issues/1370
|
# fix for https://github.com/cryptomator/cryptomator/issues/1370
|
||||||
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/jni/libjffi-1.2.so
|
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/jni/libjffi-1.2.so
|
||||||
|
|
||||||
/usr/lib/cryptomator/bin/cryptomator
|
/usr/lib/cryptomator/bin/cryptomator $@
|
||||||
Vendored
+15
-3
@@ -4,6 +4,14 @@
|
|||||||
# 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-19-coffeelibs
|
||||||
|
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
||||||
|
ifeq ($(DEB_BUILD_ARCH),amd64)
|
||||||
|
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
|
||||||
|
else ifeq ($(DEB_BUILD_ARCH),arm64)
|
||||||
|
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
|
||||||
|
endif
|
||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
@@ -16,15 +24,16 @@ override_dh_auto_clean:
|
|||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
mkdir resources
|
mkdir resources
|
||||||
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
||||||
jlink \
|
$(JAVA_HOME)/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
--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.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
--strip-debug \
|
--strip-debug \
|
||||||
--compress=2
|
--compress=2
|
||||||
jpackage \
|
$(JAVA_HOME)/bin/jpackage \
|
||||||
--type app-image \
|
--type app-image \
|
||||||
--runtime-image runtime \
|
--runtime-image runtime \
|
||||||
--input libs \
|
--input libs \
|
||||||
@@ -34,12 +43,15 @@ override_dh_auto_build:
|
|||||||
--name cryptomator \
|
--name cryptomator \
|
||||||
--vendor "Skymatic GmbH" \
|
--vendor "Skymatic GmbH" \
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
|
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
|
||||||
|
--java-options "--enable-preview" \
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" \
|
||||||
--java-options "-Xss5m" \
|
--java-options "-Xss5m" \
|
||||||
--java-options "-Xmx256m" \
|
--java-options "-Xmx256m" \
|
||||||
--java-options "-Dfile.encoding=\"utf-8\"" \
|
--java-options "-Dfile.encoding=\"utf-8\"" \
|
||||||
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
|
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \
|
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \
|
||||||
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
|
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
|
||||||
|
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" \
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
|
||||||
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
|
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
|
||||||
--java-options "-Dcryptomator.showTrayIcon=false" \
|
--java-options "-Dcryptomator.showTrayIcon=false" \
|
||||||
|
|||||||
Vendored
+2
@@ -1,5 +1,7 @@
|
|||||||
java-options=-Xss5m \
|
java-options=-Xss5m \
|
||||||
-Xmx256m \
|
-Xmx256m \
|
||||||
|
--enable-preview \
|
||||||
|
--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64 \
|
||||||
-Dfile.encoding=\"utf-8\" \
|
-Dfile.encoding=\"utf-8\" \
|
||||||
-Dcryptomator.appVersion=\"${SEMVER_STR}\" \
|
-Dcryptomator.appVersion=\"${SEMVER_STR}\" \
|
||||||
-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\" \
|
-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\" \
|
||||||
|
|||||||
Vendored
+2
@@ -10,4 +10,6 @@ java \
|
|||||||
-Djdk.gtk.version=2 \
|
-Djdk.gtk.version=2 \
|
||||||
-Xss2m \
|
-Xss2m \
|
||||||
-Xmx512m \
|
-Xmx512m \
|
||||||
|
--enable-preview \
|
||||||
|
--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64 \
|
||||||
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
||||||
|
|||||||
Vendored
+39
-27
@@ -14,9 +14,17 @@ while getopts ":s:" o; do
|
|||||||
done
|
done
|
||||||
shift "$((OPTIND-1))"
|
shift "$((OPTIND-1))"
|
||||||
|
|
||||||
# prepare working dir and variables
|
# prepare working dir
|
||||||
cd $(dirname $0)
|
cd $(dirname $0)
|
||||||
rm -rf runtime dmg
|
rm -rf runtime dmg *.app *.dmg
|
||||||
|
|
||||||
|
# set variables
|
||||||
|
APP_NAME="Cryptomator"
|
||||||
|
VENDOR="Skymatic GmbH"
|
||||||
|
COPYRIGHT_YEARS="2016 - 2022"
|
||||||
|
PACKAGE_IDENTIFIER="org.cryptomator"
|
||||||
|
MAIN_JAR_GLOB="cryptomator-*.jar"
|
||||||
|
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
||||||
REVISION_NO=`git rev-list --count HEAD`
|
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'`
|
||||||
|
|
||||||
@@ -31,13 +39,13 @@ fi
|
|||||||
|
|
||||||
# compile
|
# compile
|
||||||
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
|
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
|
||||||
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
|
||||||
|
|
||||||
# add runtime
|
# add runtime
|
||||||
${JAVA_HOME}/bin/jlink \
|
${JAVA_HOME}/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JAVA_HOME}/jmods" \
|
--module-path "${JAVA_HOME}/jmods" \
|
||||||
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
@@ -51,31 +59,35 @@ ${JAVA_HOME}/bin/jpackage \
|
|||||||
--runtime-image runtime \
|
--runtime-image runtime \
|
||||||
--input ../../../target/libs \
|
--input ../../../target/libs \
|
||||||
--module-path ../../../target/mods \
|
--module-path ../../../target/mods \
|
||||||
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator \
|
--module ${MODULE_AND_MAIN_CLASS} \
|
||||||
--dest . \
|
--dest . \
|
||||||
--name Cryptomator \
|
--name ${APP_NAME} \
|
||||||
--vendor "Skymatic GmbH" \
|
--vendor "${VENDOR}" \
|
||||||
--copyright "(C) 2016 - 2022 Skymatic GmbH" \
|
--copyright "(C) ${COPYRIGHT_YEARS} ${VENDOR}" \
|
||||||
--app-version "${VERSION_NO}" \
|
--app-version "${VERSION_NO}" \
|
||||||
|
--java-options "--enable-preview" \
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.mac" \
|
||||||
--java-options "-Xss5m" \
|
--java-options "-Xss5m" \
|
||||||
--java-options "-Xmx256m" \
|
--java-options "-Xmx256m" \
|
||||||
--java-options "-Dfile.encoding=\"utf-8\"" \
|
--java-options "-Dfile.encoding=\"utf-8\"" \
|
||||||
--java-options "-Dapple.awt.enableTemplateImages=true" \
|
--java-options "-Dapple.awt.enableTemplateImages=true" \
|
||||||
--java-options "-Dsun.java2d.metal=true" \
|
--java-options "-Dsun.java2d.metal=true" \
|
||||||
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
|
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
|
||||||
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\"" \
|
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/${APP_NAME}\"" \
|
||||||
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\"" \
|
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/${APP_NAME}/Plugins\"" \
|
||||||
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" \
|
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/${APP_NAME}/settings.json\"" \
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" \
|
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/${APP_NAME}/ipc.socket\"" \
|
||||||
|
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/${APP_NAME}/key.p12\"" \
|
||||||
|
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true" \
|
--java-options "-Dcryptomator.showTrayIcon=true" \
|
||||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
|
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
|
||||||
--mac-package-identifier org.cryptomator \
|
--mac-package-identifier ${PACKAGE_IDENTIFIER} \
|
||||||
--resource-dir ../resources
|
--resource-dir ../resources
|
||||||
|
|
||||||
# transform app dir
|
# transform app dir
|
||||||
cp ../resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
|
cp ../resources/${APP_NAME}-Vault.icns ${APP_NAME}.app/Contents/Resources/
|
||||||
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
|
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" ${APP_NAME}.app/Contents/Info.plist
|
||||||
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
|
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" ${APP_NAME}.app/Contents/Info.plist
|
||||||
|
|
||||||
# generate license
|
# generate license
|
||||||
mvn -B -f../../../pom.xml license:add-third-party \
|
mvn -B -f../../../pom.xml license:add-third-party \
|
||||||
@@ -89,8 +101,8 @@ mvn -B -f../../../pom.xml license:add-third-party \
|
|||||||
|
|
||||||
# codesign
|
# codesign
|
||||||
if [ -n "${CODESIGN_IDENTITY}" ]; then
|
if [ -n "${CODESIGN_IDENTITY}" ]; then
|
||||||
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
find ${APP_NAME}.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||||
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
|
for JAR_PATH in `find ${APP_NAME}.app -name "*.jar"`; do
|
||||||
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
||||||
JAR_FILENAME=$(basename ${JAR_PATH})
|
JAR_FILENAME=$(basename ${JAR_PATH})
|
||||||
OUTPUT_PATH=${JAR_PATH%.*}
|
OUTPUT_PATH=${JAR_PATH%.*}
|
||||||
@@ -105,25 +117,25 @@ if [ -n "${CODESIGN_IDENTITY}" ]; then
|
|||||||
rm -r ${OUTPUT_PATH}
|
rm -r ${OUTPUT_PATH}
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo "Codesigning Cryptomator.app..."
|
echo "Codesigning ${APP_NAME}.app..."
|
||||||
codesign --force --deep --entitlements ../Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app
|
codesign --force --deep --entitlements ../${APP_NAME}.entitlements -o runtime -s ${CODESIGN_IDENTITY} ${APP_NAME}.app
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# prepare dmg contents
|
# prepare dmg contents
|
||||||
mkdir dmg
|
mkdir dmg
|
||||||
mv Cryptomator.app dmg
|
mv ${APP_NAME}.app dmg
|
||||||
cp resources/macFUSE.webloc dmg
|
cp resources/macFUSE.webloc dmg
|
||||||
|
|
||||||
# create dmg
|
# create dmg
|
||||||
create-dmg \
|
create-dmg \
|
||||||
--volname Cryptomator \
|
--volname ${APP_NAME} \
|
||||||
--volicon "resources/Cryptomator-Volume.icns" \
|
--volicon "resources/${APP_NAME}-Volume.icns" \
|
||||||
--background "resources/Cryptomator-background.tiff" \
|
--background "resources/${APP_NAME}-background.tiff" \
|
||||||
--window-pos 400 100 \
|
--window-pos 400 100 \
|
||||||
--window-size 640 694 \
|
--window-size 640 694 \
|
||||||
--icon-size 128 \
|
--icon-size 128 \
|
||||||
--icon "Cryptomator.app" 128 245 \
|
--icon "${APP_NAME}.app" 128 245 \
|
||||||
--hide-extension "Cryptomator.app" \
|
--hide-extension "${APP_NAME}.app" \
|
||||||
--icon "macFUSE.webloc" 320 501 \
|
--icon "macFUSE.webloc" 320 501 \
|
||||||
--hide-extension "macFUSE.webloc" \
|
--hide-extension "macFUSE.webloc" \
|
||||||
--app-drop-link 512 245 \
|
--app-drop-link 512 245 \
|
||||||
@@ -131,4 +143,4 @@ create-dmg \
|
|||||||
--icon ".background" 128 758 \
|
--icon ".background" 128 758 \
|
||||||
--icon ".fseventsd" 320 758 \
|
--icon ".fseventsd" 320 758 \
|
||||||
--icon ".VolumeIcon.icns" 512 758 \
|
--icon ".VolumeIcon.icns" 512 758 \
|
||||||
Cryptomator-${VERSION_NO}.dmg dmg
|
${APP_NAME}-${VERSION_NO}.dmg dmg
|
||||||
|
|||||||
Vendored
+2
@@ -9,4 +9,6 @@ java \
|
|||||||
-Dcryptomator.mountPointsDir="/Volumes" \
|
-Dcryptomator.mountPointsDir="/Volumes" \
|
||||||
-Xss20m \
|
-Xss20m \
|
||||||
-Xmx512m \
|
-Xmx512m \
|
||||||
|
--enable-preview \
|
||||||
|
--enable-native-access=org.cryptomator.jfuse.mac \
|
||||||
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
||||||
Vendored
+24
-1
@@ -1,2 +1,25 @@
|
|||||||
@echo off
|
@echo off
|
||||||
powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1
|
:: Default values for Cryptomator builds
|
||||||
|
SET APPNAME="Cryptomator"
|
||||||
|
SET MAIN_JAR_GLOB="cryptomator-*"
|
||||||
|
SET UPGRADE_UUID="bda45523-42b1-4cae-9354-a45475ed4775"
|
||||||
|
SET VENDOR="Skymatic GmbH"
|
||||||
|
SET FIRST_COPYRIGHT_YEAR=2016
|
||||||
|
SET ABOUT_URL="https://cryptomator.org"
|
||||||
|
SET UPDATE_URL="https://cryptomator.org/downloads/"
|
||||||
|
SET HELP_URL="https://cryptomator.org/contact/"
|
||||||
|
SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
||||||
|
SET LOOPBACK_ALIAS="cryptomator-vault"
|
||||||
|
|
||||||
|
powershell -NoLogo -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
||||||
|
-AppName %APPNAME%^
|
||||||
|
-MainJarGlob "%MAIN_JAR_GLOB%"^
|
||||||
|
-ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^
|
||||||
|
-UpgradeUUID "%UPGRADE_UUID%"^
|
||||||
|
-Vendor ""%VENDOR%""^
|
||||||
|
-CopyrightStartYear %FIRST_COPYRIGHT_YEAR%^
|
||||||
|
-AboutUrl "%ABOUT_URL%"^
|
||||||
|
-HelpUrl "%HELP_URL%"^
|
||||||
|
-UpdateUrl "%UPDATE_URL%"^
|
||||||
|
-LoopbackAlias "%LOOPBACK_ALIAS%"^
|
||||||
|
-Clean 1
|
||||||
Vendored
+56
-37
@@ -1,5 +1,16 @@
|
|||||||
# check parameters
|
Param(
|
||||||
$clean = $args[0] -eq "fresh"
|
[Parameter(Mandatory, HelpMessage="Please provide a name for the app")][string] $AppName,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide the glob pattern to identify the main jar")][string] $MainJarGlob,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide the module- and main class path to start the app")][string] $ModuleAndMainClass,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide the windows upgrade uuid for the installer")][string] $UpgradeUUID,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide the name of the vendor")][string] $Vendor,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide the starting year for the copyright notice")][int] $CopyrightStartYear,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide a help url")][string] $HelpUrl,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide an update url")][string] $UpdateUrl,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide an about url")][string] $AboutUrl,
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide an alias for localhost")][string] $LoopbackAlias,
|
||||||
|
[bool] $clean
|
||||||
|
)
|
||||||
|
|
||||||
# check preconditions
|
# check preconditions
|
||||||
if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null)
|
if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null)
|
||||||
@@ -24,12 +35,11 @@ Write-Output "`$revisionNo=$revisionNo"
|
|||||||
Write-Output "`$buildDir=$buildDir"
|
Write-Output "`$buildDir=$buildDir"
|
||||||
Write-Output "`$Env:JAVA_HOME=$Env:JAVA_HOME"
|
Write-Output "`$Env:JAVA_HOME=$Env:JAVA_HOME"
|
||||||
|
|
||||||
$vendor = "Skymatic GmbH"
|
$copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor"
|
||||||
$copyright = "(C) 2016 - 2022 Skymatic GmbH"
|
|
||||||
|
|
||||||
# compile
|
# compile
|
||||||
&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin
|
&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin
|
||||||
Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\..\target\mods"
|
Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods"
|
||||||
|
|
||||||
# add runtime
|
# add runtime
|
||||||
$runtimeImagePath = '.\runtime'
|
$runtimeImagePath = '.\runtime'
|
||||||
@@ -41,14 +51,14 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
|
|||||||
--verbose `
|
--verbose `
|
||||||
--output runtime `
|
--output runtime `
|
||||||
--module-path "$Env:JAVA_HOME/jmods" `
|
--module-path "$Env:JAVA_HOME/jmods" `
|
||||||
--add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr `
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr `
|
||||||
--strip-native-commands `
|
--strip-native-commands `
|
||||||
--no-header-files `
|
--no-header-files `
|
||||||
--no-man-pages `
|
--no-man-pages `
|
||||||
--strip-debug `
|
--strip-debug `
|
||||||
--compress=1
|
--compress=1
|
||||||
|
|
||||||
$appPath = '.\Cryptomator'
|
$appPath = ".\$AppName"
|
||||||
if ($clean -and (Test-Path -Path $appPath)) {
|
if ($clean -and (Test-Path -Path $appPath)) {
|
||||||
Remove-Item -Path $appPath -Force -Recurse
|
Remove-Item -Path $appPath -Force -Recurse
|
||||||
}
|
}
|
||||||
@@ -60,26 +70,31 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
|||||||
--runtime-image runtime `
|
--runtime-image runtime `
|
||||||
--input ../../target/libs `
|
--input ../../target/libs `
|
||||||
--module-path ../../target/mods `
|
--module-path ../../target/mods `
|
||||||
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator `
|
--module $ModuleAndMainClass `
|
||||||
--dest . `
|
--dest . `
|
||||||
--name Cryptomator `
|
--name $AppName `
|
||||||
--vendor $vendor `
|
--vendor $Vendor `
|
||||||
--copyright $copyright `
|
--copyright $copyright `
|
||||||
|
--java-options "--enable-preview" `
|
||||||
|
--java-options "--enable-native-access=org.cryptomator.jfuse.win" `
|
||||||
--java-options "-Xss5m" `
|
--java-options "-Xss5m" `
|
||||||
--java-options "-Xmx256m" `
|
--java-options "-Xmx256m" `
|
||||||
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
|
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
|
||||||
--app-version "$semVerNo.$revisionNo" `
|
--app-version "$semVerNo.$revisionNo" `
|
||||||
--java-options "-Dfile.encoding=`"utf-8`"" `
|
--java-options "-Dfile.encoding=`"utf-8`"" `
|
||||||
--java-options "-Dcryptomator.logDir=`"~/AppData/Roaming/Cryptomator`"" `
|
--java-options "-Dcryptomator.logDir=`"~/AppData/Roaming/$AppName`"" `
|
||||||
--java-options "-Dcryptomator.pluginDir=`"~/AppData/Roaming/Cryptomator/Plugins`"" `
|
--java-options "-Dcryptomator.pluginDir=`"~/AppData/Roaming/$AppName/Plugins`"" `
|
||||||
--java-options "-Dcryptomator.settingsPath=`"~/AppData/Roaming/Cryptomator/settings.json`"" `
|
--java-options "-Dcryptomator.settingsPath=`"~/AppData/Roaming/$AppName/settings.json`"" `
|
||||||
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/Cryptomator/ipc.socket`"" `
|
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" `
|
||||||
--java-options "-Dcryptomator.keychainPath=`"~/AppData/Roaming/Cryptomator/keychain.json`"" `
|
--java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" `
|
||||||
--java-options "-Dcryptomator.mountPointsDir=`"~/Cryptomator`"" `
|
--java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" `
|
||||||
|
--java-options "-Dcryptomator.loopbackAlias=`"$LoopbackAlias`"" `
|
||||||
|
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" `
|
||||||
|
--java-options "-Dcryptomator.integrationsWin.keychainPaths=`"~/AppData/Roaming/$AppName/keychain.json`"" `
|
||||||
--java-options "-Dcryptomator.showTrayIcon=true" `
|
--java-options "-Dcryptomator.showTrayIcon=true" `
|
||||||
--java-options "-Dcryptomator.buildNumber=`"msi-$revisionNo`"" `
|
--java-options "-Dcryptomator.buildNumber=`"msi-$revisionNo`"" `
|
||||||
--resource-dir resources `
|
--resource-dir resources `
|
||||||
--icon resources/Cryptomator.ico
|
--icon resources/$AppName.ico
|
||||||
|
|
||||||
#Create RTF license for msi
|
#Create RTF license for msi
|
||||||
&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
|
&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
|
||||||
@@ -92,33 +107,37 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
|||||||
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges"
|
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges"
|
||||||
|
|
||||||
# patch app dir
|
# patch app dir
|
||||||
Copy-Item "contrib\*" -Destination "Cryptomator"
|
Copy-Item "contrib\*" -Destination "$AppName"
|
||||||
attrib -r "Cryptomator\Cryptomator.exe"
|
attrib -r "$AppName\$AppName.exe"
|
||||||
|
# patch batch script to set hostfile
|
||||||
$aboutUrl="https://cryptomator.org"
|
$webDAVPatcher = "$AppName\patchWebDAV.bat"
|
||||||
$updateUrl="https://cryptomator.org/downloads/"
|
try {
|
||||||
$helpUrl="https://cryptomator.org/contact/"
|
(Get-Content $webDAVPatcher ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"$LoopbackAlias`"" | Set-Content $webDAVPatcher
|
||||||
|
} catch {
|
||||||
|
Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# create .msi
|
# create .msi
|
||||||
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
||||||
& "$Env:JAVA_HOME\bin\jpackage" `
|
& "$Env:JAVA_HOME\bin\jpackage" `
|
||||||
--verbose `
|
--verbose `
|
||||||
--type msi `
|
--type msi `
|
||||||
--win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775 `
|
--win-upgrade-uuid $UpgradeUUID `
|
||||||
--app-image Cryptomator `
|
--app-image $AppName `
|
||||||
--dest installer `
|
--dest installer `
|
||||||
--name Cryptomator `
|
--name $AppName `
|
||||||
--vendor $vendor `
|
--vendor $Vendor `
|
||||||
--copyright $copyright `
|
--copyright $copyright `
|
||||||
--app-version "$semVerNo" `
|
--app-version "$semVerNo" `
|
||||||
--win-menu `
|
--win-menu `
|
||||||
--win-dir-chooser `
|
--win-dir-chooser `
|
||||||
--win-shortcut-prompt `
|
--win-shortcut-prompt `
|
||||||
--win-update-url $updateUrl `
|
--win-menu-group $AppName `
|
||||||
--win-menu-group Cryptomator `
|
|
||||||
--resource-dir resources `
|
--resource-dir resources `
|
||||||
--about-url $aboutUrl `
|
|
||||||
--license-file resources/license.rtf `
|
--license-file resources/license.rtf `
|
||||||
|
--win-update-url $UpdateUrl `
|
||||||
|
--about-url $AboutUrl `
|
||||||
--file-associations resources/FAvaultFile.properties
|
--file-associations resources/FAvaultFile.properties
|
||||||
|
|
||||||
#Create RTF license for bundle
|
#Create RTF license for bundle
|
||||||
@@ -134,19 +153,19 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
|||||||
# download Winfsp
|
# download Winfsp
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
$ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s
|
$ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s
|
||||||
$winfspMsiUrl = "https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi"
|
$winfspMsiUrl= (Select-String -Path ".\bundle\resources\winfsp-download.url" -Pattern 'https:.*').Matches.Value
|
||||||
Write-Output "Downloading ${winfspMsiUrl}..."
|
Write-Output "Downloading ${winfspMsiUrl}..."
|
||||||
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
|
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
|
||||||
|
|
||||||
# copy MSI to bundle resources
|
# copy MSI to bundle resources
|
||||||
Copy-Item ".\installer\Cryptomator-*.msi" -Destination ".\bundle\resources\Cryptomator.msi"
|
Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi"
|
||||||
|
|
||||||
# create bundle including winfsp
|
# create bundle including winfsp
|
||||||
& "$env:WIX\bin\candle.exe" .\bundle\bundleWithWinfsp.wxs -ext WixBalExtension -out bundle\ `
|
& "$env:WIX\bin\candle.exe" .\bundle\bundleWithWinfsp.wxs -ext WixBalExtension -out bundle\ `
|
||||||
-dBundleVersion="$semVerNo.$revisionNo" `
|
-dBundleVersion="$semVerNo.$revisionNo" `
|
||||||
-dBundleVendor="$vendor" `
|
-dBundleVendor="$Vendor" `
|
||||||
-dBundleCopyright="$copyright" `
|
-dBundleCopyright="$copyright" `
|
||||||
-dAboutUrl="$aboutUrl" `
|
-dAboutUrl="$AboutUrl" `
|
||||||
-dHelpUrl="$helpUrl" `
|
-dHelpUrl="$HelpUrl" `
|
||||||
-dUpdateUrl="$updateUrl"
|
-dUpdateUrl="$UpdateUrl"
|
||||||
& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\Cryptomator-Installer.exe
|
& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\$AppName-Installer.exe
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[InternetShortcut]
|
||||||
|
URL=https://github.com/winfsp/winfsp/releases/download/v1.12/winfsp-1.12.22301.msi
|
||||||
Vendored
+5
-1
@@ -1,3 +1,7 @@
|
|||||||
@echo off
|
@echo off
|
||||||
|
:: Default values for Cryptomator builds
|
||||||
|
::REPLACE ME
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1
|
powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1^
|
||||||
|
-LoopbackAlias %LOOPBACK_ALIAS%
|
||||||
Vendored
+9
-3
@@ -1,10 +1,16 @@
|
|||||||
#Requires -RunAsAdministrator
|
#Requires -RunAsAdministrator
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory, HelpMessage="Please provide an alias for 127.0.0.1")][string] $LoopbackAlias
|
||||||
|
)
|
||||||
|
|
||||||
# Adds for address 127.0.0.1 the 'cryptomator-vault' alias to the hosts file
|
# Adds an alias for 127.0.0.1 to the hosts file
|
||||||
function Add-AliasToHost {
|
function Add-AliasToHost {
|
||||||
|
param (
|
||||||
|
[string]$LoopbackAlias
|
||||||
|
)
|
||||||
$sysdir = [Environment]::SystemDirectory
|
$sysdir = [Environment]::SystemDirectory
|
||||||
$hostsFile = "$sysdir\drivers\etc\hosts"
|
$hostsFile = "$sysdir\drivers\etc\hosts"
|
||||||
$aliasLine = '127.0.0.1 cryptomator-vault'
|
$aliasLine = "127.0.0.1 $LoopbackAlias"
|
||||||
|
|
||||||
foreach ($line in Get-Content $hostsFile) {
|
foreach ($line in Get-Content $hostsFile) {
|
||||||
if ($line -eq $aliasLine){
|
if ($line -eq $aliasLine){
|
||||||
@@ -49,7 +55,7 @@ function Edit-ProviderOrder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Add-AliasToHost
|
Add-AliasToHost $LoopbackAlias
|
||||||
Write-Output 'Ensured alias exists in hosts file'
|
Write-Output 'Ensured alias exists in hosts file'
|
||||||
|
|
||||||
Set-WebDAVFileSizeLimit
|
Set-WebDAVFileSizeLimit
|
||||||
|
|||||||
Vendored
+2
@@ -9,4 +9,6 @@ java ^
|
|||||||
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
||||||
-Xss20m ^
|
-Xss20m ^
|
||||||
-Xmx512m ^
|
-Xmx512m ^
|
||||||
|
--enable-preview `
|
||||||
|
--enable-native-access=org.cryptomator.jfuse.win `
|
||||||
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
||||||
Vendored
+1
-1
@@ -91,7 +91,7 @@
|
|||||||
<Condition Action="show">FOUNDRUNNINGAPP</Condition>
|
<Condition Action="show">FOUNDRUNNINGAPP</Condition>
|
||||||
</Control>
|
</Control>
|
||||||
<Control Id="DescriptionReason2" Type="Text" X="135" Y="170" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Hidden="yes" >
|
<Control Id="DescriptionReason2" Type="Text" X="135" Y="170" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Hidden="yes" >
|
||||||
<Text>Cryptomator was still running during installation.</Text>
|
<Text>Application to update was still running during installation.</Text>
|
||||||
<Condition Action="show">FOUNDRUNNINGAPP</Condition>
|
<Condition Action="show">FOUNDRUNNINGAPP</Condition>
|
||||||
</Control>
|
</Control>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
Vendored
+23
-18
@@ -23,6 +23,11 @@
|
|||||||
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
|
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
|
||||||
<?endif?>
|
<?endif?>
|
||||||
|
|
||||||
|
<!-- Cryptomator defaults -->
|
||||||
|
<?define IconFileEncryptedData= "Cryptomator-Vault.ico" ?>
|
||||||
|
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
|
||||||
|
<?define CloseApplicationTarget= "cryptomator.exe" ?>
|
||||||
|
|
||||||
<?include $(var.JpConfigDir)/overrides.wxi ?>
|
<?include $(var.JpConfigDir)/overrides.wxi ?>
|
||||||
|
|
||||||
<Product
|
<Product
|
||||||
@@ -65,16 +70,16 @@
|
|||||||
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
||||||
<?endif?>
|
<?endif?>
|
||||||
|
|
||||||
|
<?ifndef SkipCryptomatorLegacyCheck ?>
|
||||||
<!-- Looking for legacy Cryptomator versions-->
|
<!-- Block installation if innosetup entry of Cryptomator is found -->
|
||||||
<Property Id="OLDEXEINSTALLER">
|
<Property Id="OLDEXEINSTALLER">
|
||||||
<RegistrySearch Id="InnoSetupInstallation" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Cryptomator_is1" Type="raw" Name="DisplayName" />
|
<RegistrySearch Id="InnoSetupInstallation" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Cryptomator_is1" Type="raw" Name="DisplayName" />
|
||||||
</Property>
|
</Property>
|
||||||
<!-- Block installation if innosetup entry of Cryptomator is found -->
|
<!-- TODO: localize -->
|
||||||
<!-- TODO: localize -->
|
<Condition Message="A lower version of [ProductName] is already installed. Uninstall it first and then start the setup again. Setup will now exit.">
|
||||||
<Condition Message="A lower version of [ProductName] is already installed. Uninstall it first and then start the setup again. Setup will now exit.">
|
<![CDATA[Installed OR NOT OLDEXEINSTALLER]]>
|
||||||
<![CDATA[Installed OR NOT OLDEXEINSTALLER]]>
|
</Condition>
|
||||||
</Condition>
|
<?endif?>
|
||||||
<!-- Cryptomator uses UNIX Sockets, which are supported starting with Windows 10 v1803-->
|
<!-- Cryptomator uses UNIX Sockets, which are supported starting with Windows 10 v1803-->
|
||||||
<Property Id="WINDOWSBUILDNUMBER" Secure="yes">
|
<Property Id="WINDOWSBUILDNUMBER" Secure="yes">
|
||||||
<RegistrySearch Id="BuildNumberSearch" Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion" Name="CurrentBuildNumber" Type="raw" />
|
<RegistrySearch Id="BuildNumberSearch" Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion" Name="CurrentBuildNumber" Type="raw" />
|
||||||
@@ -86,12 +91,12 @@
|
|||||||
<!-- Non-Opening ProgID -->
|
<!-- Non-Opening ProgID -->
|
||||||
<DirectoryRef Id="INSTALLDIR">
|
<DirectoryRef Id="INSTALLDIR">
|
||||||
<Component Win64="yes" Id="nonStartingProgID" >
|
<Component Win64="yes" Id="nonStartingProgID" >
|
||||||
<File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\Cryptomator-Vault.ico" Name="Cryptomator-Vault.ico"></File>
|
<File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\$(var.IconFileEncryptedData)" Name="$(var.IconFileEncryptedData)"></File>
|
||||||
<ProgId Id="Cryptomator.Encrypted.1" Description="Cryptomator Encrypted Data" Icon="IconFileForEncryptedData" IconIndex="0">
|
<ProgId Id="$(var.JpAppName).Encrypted.1" Description="$(var.JpAppName) Encrypted Data" Icon="IconFileForEncryptedData" IconIndex="0">
|
||||||
<Extension Id="c9r" Advertise="no" ContentType="application/vnd.cryptomator.encrypted">
|
<Extension Id="c9r" Advertise="no" ContentType="$(var.ProgIdContentType)">
|
||||||
<MIME ContentType="application/vnd.cryptomator.encrypted" Default="yes"></MIME>
|
<MIME ContentType="$(var.ProgIdContentType)" Default="yes"></MIME>
|
||||||
</Extension>
|
</Extension>
|
||||||
<Extension Id="c9s" Advertise="no" ContentType="application/vnd.cryptomator.encrypted"/>
|
<Extension Id="c9s" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
|
||||||
</ProgId>
|
</ProgId>
|
||||||
</Component>
|
</Component>
|
||||||
</DirectoryRef>
|
</DirectoryRef>
|
||||||
@@ -130,12 +135,12 @@
|
|||||||
<!-- Running App detection and exit -->
|
<!-- Running App detection and exit -->
|
||||||
<Property Id="FOUNDRUNNINGAPP" Admin="yes"/>
|
<Property Id="FOUNDRUNNINGAPP" Admin="yes"/>
|
||||||
<util:CloseApplication
|
<util:CloseApplication
|
||||||
Id="CloseCryptomator"
|
Target="$(var.CloseApplicationTarget)"
|
||||||
Target="cryptomator.exe"
|
Id="Close$(var.JpAppName)"
|
||||||
CloseMessage="no"
|
CloseMessage="no"
|
||||||
RebootPrompt="no"
|
RebootPrompt="no"
|
||||||
PromptToContinue="yes"
|
PromptToContinue="yes"
|
||||||
Description="A running instance of Cryptomator is found. Please close it to continue."
|
Description="A running instance of $(var.JpAppName) is found. Please close it to continue."
|
||||||
Property="FOUNDRUNNINGAPP"
|
Property="FOUNDRUNNINGAPP"
|
||||||
>
|
>
|
||||||
</util:CloseApplication>
|
</util:CloseApplication>
|
||||||
|
|||||||
Vendored
+50
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Stub by design -->
|
||||||
|
|
||||||
|
<!-- jPackage Section
|
||||||
|
overrides.wxi is a placeholder to set/alter WiX variables referenced from default
|
||||||
|
main.wxs file.
|
||||||
|
|
||||||
|
Put custom overrides.wxi in resource directory to replace this default file.
|
||||||
|
|
||||||
|
Override default overrides.wxi if configuring of msi installers through jpackage
|
||||||
|
command line is not sufficient.
|
||||||
|
|
||||||
|
WiX variables referenced from default main.wxs that can be altered in custom overrides.wxi:
|
||||||
|
|
||||||
|
- JpProductLanguage
|
||||||
|
Value of `Language` attribute of `Product` WiX element. Default value is 1033.
|
||||||
|
|
||||||
|
- JpInstallerVersion
|
||||||
|
Value of `InstallerVersion` attribute of `Package` WiX element. Default value is 200.
|
||||||
|
|
||||||
|
- JpCompressedMsi
|
||||||
|
Value of `Compressed` attribute of `Package` WiX element. Default value is `yes`.
|
||||||
|
|
||||||
|
- JpAllowDowngrades
|
||||||
|
Should be defined to enable downgrades and undefined to disable downgrades.
|
||||||
|
Default value is `yes`.
|
||||||
|
|
||||||
|
- JpAllowUpgrades
|
||||||
|
Should be defined to enable upgrades and undefined to disable upgrades.
|
||||||
|
Default value is `yes`.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Cryptomator Section
|
||||||
|
|
||||||
|
Non-opening ProgID settings:
|
||||||
|
- IconFileEncryptedData
|
||||||
|
Full file name of icon file used for encrypted data files. Default is "Cryptomator-Vault.ico"
|
||||||
|
|
||||||
|
- ProgIdContentType
|
||||||
|
Media Type of the encrypted data files. Default is "application/vnd.cryptomator.encrypted"
|
||||||
|
|
||||||
|
Close Application settings:
|
||||||
|
- CloseApplicationTarget
|
||||||
|
Full name of executable to be checkd in the close application util. Default is "cryptomator.exe"
|
||||||
|
|
||||||
|
Legacy Installation settings:
|
||||||
|
- SkipCryptomatorLegacyCheck
|
||||||
|
Should be defined to disable checking for the inno setup installation of Cryptomator and undefined, to enable it.
|
||||||
|
-->
|
||||||
|
<Include/>
|
||||||
@@ -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.6.9</version>
|
<version>1.7.0</version>
|
||||||
<name>Cryptomator Desktop App</name>
|
<name>Cryptomator Desktop App</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
@@ -21,42 +21,45 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.jdk.version>17</project.jdk.version>
|
<project.jdk.version>19</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 -->
|
||||||
<nonModularGroupIds>com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
|
<!-- Check progress on https://github.com/swiesend/secret-service/issues/31 to remove hypfvieh, swiesend and jnr -->
|
||||||
|
<nonModularGroupIds>com.github.jnr,org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
|
||||||
|
|
||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptofs.version>2.4.1</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>2.5.3</cryptomator.cryptofs.version>
|
||||||
<cryptomator.integrations.version>1.1.0</cryptomator.integrations.version>
|
<cryptomator.integrations.version>1.2.0-beta2</cryptomator.integrations.version>
|
||||||
<cryptomator.integrations.win.version>1.1.0</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.1.2</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>1.1.0</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.1.2</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
|
<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>1.3.3</cryptomator.fuse.version>
|
<cryptomator.fuse.version>2.0.0-beta2</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>1.3.3</cryptomator.dokany.version>
|
<cryptomator.dokany.version>2.0.0-beta1</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>1.2.7</cryptomator.webdav.version>
|
<cryptomator.webdav.version>2.0.0-beta1</cryptomator.webdav.version>
|
||||||
|
|
||||||
<!-- 3rd party dependencies -->
|
<!-- 3rd party dependencies -->
|
||||||
<javafx.version>18.0.1</javafx.version>
|
|
||||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||||
<jwt.version>3.19.1</jwt.version>
|
<dagger.version>2.44</dagger.version>
|
||||||
<easybind.version>2.2</easybind.version>
|
<easybind.version>2.2</easybind.version>
|
||||||
<guava.version>31.1-jre</guava.version>
|
<guava.version>31.1-jre</guava.version>
|
||||||
<dagger.version>2.41</dagger.version>
|
<gson.version>2.9.1</gson.version>
|
||||||
<gson.version>2.9.0</gson.version>
|
<javafx.version>19</javafx.version>
|
||||||
|
<jwt.version>4.2.1</jwt.version>
|
||||||
|
<nimbus-jose.version>9.25.4</nimbus-jose.version>
|
||||||
|
<logback.version>1.4.4</logback.version>
|
||||||
|
<slf4j.version>2.0.3</slf4j.version>
|
||||||
|
<tinyoauth2.version>0.5.1</tinyoauth2.version>
|
||||||
<zxcvbn.version>1.7.0</zxcvbn.version>
|
<zxcvbn.version>1.7.0</zxcvbn.version>
|
||||||
<slf4j.version>1.7.36</slf4j.version>
|
|
||||||
<logback.version>1.2.11</logback.version>
|
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
<junit.jupiter.version>5.8.1</junit.jupiter.version>
|
<junit.jupiter.version>5.9.1</junit.jupiter.version>
|
||||||
<mockito.version>4.4.0</mockito.version>
|
<mockito.version>4.8.0</mockito.version>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
|
|
||||||
<!-- build-time dependencies -->
|
<!-- build-time dependencies -->
|
||||||
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
|
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
|
||||||
<dependency-check.version>7.1.0</dependency-check.version>
|
<dependency-check.version>7.4.0</dependency-check.version>
|
||||||
<jacoco.version>0.8.7</jacoco.version>
|
<jacoco.version>0.8.8</jacoco.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -133,12 +136,22 @@
|
|||||||
<version>${commons-lang3.version}</version>
|
<version>${commons-lang3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JWT -->
|
<!-- OAuth/JWT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.coffeelibs</groupId>
|
||||||
|
<artifactId>tiny-oauth2-client</artifactId>
|
||||||
|
<version>${tinyoauth2.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.auth0</groupId>
|
<groupId>com.auth0</groupId>
|
||||||
<artifactId>java-jwt</artifactId>
|
<artifactId>java-jwt</artifactId>
|
||||||
<version>${jwt.version}</version>
|
<version>${jwt.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.nimbusds</groupId>
|
||||||
|
<artifactId>nimbus-jose-jwt</artifactId>
|
||||||
|
<version>${nimbus-jose.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- EasyBind -->
|
<!-- EasyBind -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -245,7 +258,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
<version>3.2.0</version>
|
<version>3.3.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@@ -255,7 +268,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.0.0-M5</version>
|
<version>3.0.0-M7</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
@@ -265,7 +278,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
<version>3.2.2</version>
|
<version>3.3.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
@@ -295,13 +308,14 @@
|
|||||||
<compilerArgs>
|
<compilerArgs>
|
||||||
<arg>-Adagger.fastInit=enabled</arg>
|
<arg>-Adagger.fastInit=enabled</arg>
|
||||||
<arg>-Adagger.formatGeneratedSource=enabled</arg>
|
<arg>-Adagger.formatGeneratedSource=enabled</arg>
|
||||||
|
<arg>--enable-preview</arg>
|
||||||
</compilerArgs>
|
</compilerArgs>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.1.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>compile-light-theme</id>
|
<id>compile-light-theme</id>
|
||||||
@@ -353,6 +367,7 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
<executions>
|
<executions>
|
||||||
|
<!-- sort jars into two buckets (classpath and modulepath). exclude openjfx, which gets jlinked separately -->
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-mods</id>
|
<id>copy-mods</id>
|
||||||
<phase>prepare-package</phase>
|
<phase>prepare-package</phase>
|
||||||
@@ -362,7 +377,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<includeScope>runtime</includeScope>
|
<includeScope>runtime</includeScope>
|
||||||
<outputDirectory>${project.build.directory}/mods</outputDirectory>
|
<outputDirectory>${project.build.directory}/mods</outputDirectory>
|
||||||
<excludeGroupIds>${nonModularGroupIds}</excludeGroupIds>
|
<excludeGroupIds>org.openjfx,${nonModularGroupIds}</excludeGroupIds>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
|
|||||||
@@ -1,60 +1,42 @@
|
|||||||
|
import ch.qos.logback.classic.spi.Configurator;
|
||||||
import org.cryptomator.integrations.tray.TrayMenuController;
|
import org.cryptomator.integrations.tray.TrayMenuController;
|
||||||
|
import org.cryptomator.logging.LogbackConfiguratorFactory;
|
||||||
import org.cryptomator.ui.traymenu.AwtTrayMenuController;
|
import org.cryptomator.ui.traymenu.AwtTrayMenuController;
|
||||||
|
|
||||||
module org.cryptomator.desktop {
|
open module org.cryptomator.desktop {
|
||||||
requires static org.jetbrains.annotations;
|
requires static org.jetbrains.annotations;
|
||||||
|
|
||||||
|
requires org.cryptomator.cryptolib;
|
||||||
requires org.cryptomator.cryptofs;
|
requires org.cryptomator.cryptofs;
|
||||||
requires org.cryptomator.frontend.dokany;
|
requires org.cryptomator.frontend.dokany;
|
||||||
requires org.cryptomator.frontend.fuse;
|
requires org.cryptomator.frontend.fuse;
|
||||||
requires org.cryptomator.frontend.webdav;
|
requires org.cryptomator.frontend.webdav;
|
||||||
requires org.cryptomator.integrations.api;
|
requires org.cryptomator.integrations.api;
|
||||||
|
// jdk:
|
||||||
requires java.desktop;
|
requires java.desktop;
|
||||||
requires java.net.http;
|
requires java.net.http;
|
||||||
requires javafx.base;
|
requires javafx.base;
|
||||||
requires javafx.graphics;
|
requires javafx.graphics;
|
||||||
requires javafx.controls;
|
requires javafx.controls;
|
||||||
requires javafx.fxml;
|
requires javafx.fxml;
|
||||||
requires com.tobiasdiez.easybind;
|
requires jdk.crypto.ec;
|
||||||
|
// 3rd party:
|
||||||
|
requires ch.qos.logback.classic;
|
||||||
|
requires ch.qos.logback.core;
|
||||||
|
requires com.auth0.jwt;
|
||||||
requires com.google.common;
|
requires com.google.common;
|
||||||
requires com.google.gson;
|
requires com.google.gson;
|
||||||
|
requires com.nimbusds.jose.jwt;
|
||||||
requires com.nulabinc.zxcvbn;
|
requires com.nulabinc.zxcvbn;
|
||||||
|
requires com.tobiasdiez.easybind;
|
||||||
|
requires dagger;
|
||||||
|
requires io.github.coffeelibs.tinyoauth2client;
|
||||||
requires org.slf4j;
|
requires org.slf4j;
|
||||||
requires org.apache.commons.lang3;
|
requires org.apache.commons.lang3;
|
||||||
requires dagger;
|
|
||||||
requires com.auth0.jwt;
|
|
||||||
|
|
||||||
/* TODO: filename-based modules: */
|
/* TODO: filename-based modules: */
|
||||||
requires static javax.inject; /* ugly dagger/guava crap */
|
requires static javax.inject; /* ugly dagger/guava crap */
|
||||||
requires logback.classic;
|
|
||||||
requires logback.core;
|
|
||||||
|
|
||||||
exports org.cryptomator.ui.traymenu to org.cryptomator.integrations.api;
|
|
||||||
provides TrayMenuController with AwtTrayMenuController;
|
provides TrayMenuController with AwtTrayMenuController;
|
||||||
|
provides Configurator with LogbackConfiguratorFactory;
|
||||||
opens org.cryptomator.common.settings to com.google.gson;
|
|
||||||
|
|
||||||
opens org.cryptomator.launcher to javafx.graphics;
|
|
||||||
|
|
||||||
opens org.cryptomator.common to javafx.fxml;
|
|
||||||
opens org.cryptomator.common.vaults to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.addvaultwizard to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.changepassword to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.common to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.controls to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.forgetPassword to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.fxapp to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.health to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.keyloading.masterkeyfile to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.lock to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.mainwindow to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.migration to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.preferences to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.quit to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.recoverykey to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.removevault to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.stats to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.unlock to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.vaultoptions to javafx.fxml;
|
|
||||||
opens org.cryptomator.ui.wrongfilealert to javafx.fxml;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
//Inspired by: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html#afterExecute(java.lang.Runnable,java.lang.Throwable)
|
||||||
|
public final class CatchingExecutors {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(CatchingExecutors.class);
|
||||||
|
|
||||||
|
private CatchingExecutors() { /* NO-OP */ }
|
||||||
|
|
||||||
|
public static class CatchingScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
|
||||||
|
|
||||||
|
public CatchingScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
|
||||||
|
super(corePoolSize, threadFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
|
||||||
|
Runnable oneShot = () -> this.execute(command);
|
||||||
|
return super.scheduleAtFixedRate(oneShot, initialDelay, period, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
|
||||||
|
Runnable oneShot = () -> this.execute(command);
|
||||||
|
return super.scheduleWithFixedDelay(oneShot, initialDelay, delay, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterExecute(Runnable runnable, Throwable throwable) {
|
||||||
|
super.afterExecute(runnable, throwable);
|
||||||
|
afterExecuteInternal(runnable, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CatchingThreadPoolExecutor extends ThreadPoolExecutor {
|
||||||
|
|
||||||
|
public CatchingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
|
||||||
|
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterExecute(Runnable runnable, Throwable throwable) {
|
||||||
|
super.afterExecute(runnable, throwable);
|
||||||
|
afterExecuteInternal(runnable, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void afterExecuteInternal(Runnable runnable, Throwable throwable) {
|
||||||
|
if (throwable != null) {
|
||||||
|
callHandler(Thread.currentThread(), throwable);
|
||||||
|
} else if (runnable instanceof Task<?> t) {
|
||||||
|
afterExecuteTask(t);
|
||||||
|
} else if (runnable instanceof Future<?> f) {
|
||||||
|
afterExecuteFuture(f);
|
||||||
|
}
|
||||||
|
//Errors in this method are delegated to the UncaughtExceptionHandler of the current thread
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void callHandler(Thread thread, Throwable throwable) {
|
||||||
|
Objects.requireNonNullElseGet(thread.getUncaughtExceptionHandler(), CatchingExecutors::fallbackHandler).uncaughtException(thread, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Thread.UncaughtExceptionHandler fallbackHandler() {
|
||||||
|
return (thread, throwable) -> LOG.error("FALLBACK: Uncaught exception in " + thread.getName(), throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void afterExecuteTask(Task<?> task) {
|
||||||
|
var caller = Thread.currentThread();
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (task.getOnFailed() == null) {
|
||||||
|
callHandler(caller, task.getException());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void afterExecuteFuture(Future<?> future) {
|
||||||
|
if (future instanceof ScheduledFuture<?> && !future.isDone()) {
|
||||||
|
//we assume that this must be a repeated ScheduledFutureTask, where the done-status is only set when not executed anymore
|
||||||
|
//see also https://github.com/cryptomator/cryptomator/pull/2422
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (CancellationException ce) {
|
||||||
|
//Ignore
|
||||||
|
} catch (ExecutionException ee) {
|
||||||
|
callHandler(Thread.currentThread(), ee.getCause());
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
//Ignore/Reset
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,35 +10,29 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.keychain.KeychainModule;
|
import org.cryptomator.common.keychain.KeychainModule;
|
||||||
|
import org.cryptomator.common.mount.MountModule;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.SettingsProvider;
|
import org.cryptomator.common.settings.SettingsProvider;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
|
||||||
import org.cryptomator.common.vaults.VaultComponent;
|
import org.cryptomator.common.vaults.VaultComponent;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
|
||||||
import org.cryptomator.common.vaults.VaultListModule;
|
import org.cryptomator.common.vaults.VaultListModule;
|
||||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.SynchronousQueue;
|
import java.util.concurrent.SynchronousQueue;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@Module(subcomponents = {VaultComponent.class}, includes = {VaultListModule.class, KeychainModule.class})
|
@Module(subcomponents = {VaultComponent.class}, includes = {VaultListModule.class, KeychainModule.class, MountModule.class})
|
||||||
public abstract class CommonsModule {
|
public abstract class CommonsModule {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
||||||
@@ -46,6 +40,12 @@ public abstract class CommonsModule {
|
|||||||
private static final int NUM_CORE_BG_THREADS = 6;
|
private static final int NUM_CORE_BG_THREADS = 6;
|
||||||
private static final long BG_THREAD_KEEPALIVE_SECONDS = 60l;
|
private static final long BG_THREAD_KEEPALIVE_SECONDS = 60l;
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static Environment provideEnvironment() {
|
||||||
|
return Environment.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SpellCheckingInspection")
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@@ -93,7 +93,7 @@ public abstract class CommonsModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
|
static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
|
||||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
ScheduledExecutorService executorService = new CatchingExecutors.CatchingScheduledThreadPoolExecutor(NUM_SCHEDULER_THREADS, r -> {
|
||||||
String name = String.format("App Scheduled Executor %02d", threadNumber.getAndIncrement());
|
String name = String.format("App Scheduled Executor %02d", threadNumber.getAndIncrement());
|
||||||
Thread t = new Thread(r);
|
Thread t = new Thread(r);
|
||||||
t.setName(name);
|
t.setName(name);
|
||||||
@@ -110,7 +110,7 @@ public abstract class CommonsModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
static ExecutorService provideExecutorService(ShutdownHook shutdownHook) {
|
static ExecutorService provideExecutorService(ShutdownHook shutdownHook) {
|
||||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
ExecutorService executorService = new ThreadPoolExecutor(NUM_CORE_BG_THREADS, Integer.MAX_VALUE, BG_THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, new SynchronousQueue<>(), r -> {
|
ExecutorService executorService = new CatchingExecutors.CatchingThreadPoolExecutor(NUM_CORE_BG_THREADS, Integer.MAX_VALUE, BG_THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, new SynchronousQueue<>(), r -> {
|
||||||
String name = String.format("App Background Thread %03d", threadNumber.getAndIncrement());
|
String name = String.format("App Background Thread %03d", threadNumber.getAndIncrement());
|
||||||
Thread t = new Thread(r);
|
Thread t = new Thread(r);
|
||||||
t.setName(name);
|
t.setName(name);
|
||||||
@@ -129,20 +129,11 @@ public abstract class CommonsModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
static ObservableValue<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
||||||
return Bindings.createObjectBinding(() -> {
|
return settings.port().map(port -> {
|
||||||
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
||||||
return InetSocketAddress.createUnresolved(host, settings.port().intValue());
|
return InetSocketAddress.createUnresolved(host, settings.port().intValue());
|
||||||
}, settings.port());
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
static WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
|
||||||
WebDavServer server = WebDavServer.create();
|
|
||||||
// no need to unsubscribe eventually, because server is a singleton
|
|
||||||
EasyBind.subscribe(serverSocketAddressBinding, server::bind);
|
|
||||||
return server;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ public interface Constants {
|
|||||||
String MASTERKEY_BACKUP_SUFFIX = ".bkup";
|
String MASTERKEY_BACKUP_SUFFIX = ".bkup";
|
||||||
String VAULTCONFIG_FILENAME = "vault.cryptomator";
|
String VAULTCONFIG_FILENAME = "vault.cryptomator";
|
||||||
String CRYPTOMATOR_FILENAME_EXT = ".cryptomator";
|
String CRYPTOMATOR_FILENAME_EXT = ".cryptomator";
|
||||||
|
String CRYPTOMATOR_FILENAME_GLOB = "*.cryptomator";
|
||||||
byte[] PEPPER = new byte[0];
|
byte[] PEPPER = new byte[0];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import com.google.common.base.Strings;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -17,32 +15,57 @@ import java.util.function.Predicate;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class Environment {
|
public class Environment {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
|
||||||
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
||||||
private static final char PATH_LIST_SEP = ':';
|
private static final char PATH_LIST_SEP = ':';
|
||||||
private static final int DEFAULT_MIN_PW_LENGTH = 8;
|
private static final int DEFAULT_MIN_PW_LENGTH = 8;
|
||||||
|
private static final String SETTINGS_PATH_PROP_NAME = "cryptomator.settingsPath";
|
||||||
|
private static final String IPC_SOCKET_PATH_PROP_NAME = "cryptomator.ipcSocketPath";
|
||||||
|
private static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
|
||||||
|
private static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
|
||||||
|
private static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
|
||||||
|
private static final String LOOPBACK_ALIAS_PROP_NAME = "cryptomator.loopbackAlias";
|
||||||
|
private static final String MOUNTPOINT_DIR_PROP_NAME = "cryptomator.mountPointsDir";
|
||||||
|
private static final String MIN_PW_LENGTH_PROP_NAME = "cryptomator.minPwLength";
|
||||||
|
private static final String APP_VERSION_PROP_NAME = "cryptomator.appVersion";
|
||||||
|
private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber";
|
||||||
|
private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir";
|
||||||
|
private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon";
|
||||||
|
|
||||||
@Inject
|
private Environment() {}
|
||||||
public Environment() {
|
|
||||||
LOG.debug("user.home: {}", System.getProperty("user.home"));
|
public void log() {
|
||||||
LOG.debug("java.library.path: {}", System.getProperty("java.library.path"));
|
LOG.info("user.home: {}", System.getProperty("user.home"));
|
||||||
LOG.debug("user.language: {}", System.getProperty("user.language"));
|
LOG.info("java.library.path: {}", System.getProperty("java.library.path"));
|
||||||
LOG.debug("user.region: {}", System.getProperty("user.region"));
|
LOG.info("user.language: {}", System.getProperty("user.language"));
|
||||||
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
|
LOG.info("user.region: {}", System.getProperty("user.region"));
|
||||||
LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath"));
|
LOG.info("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
|
||||||
LOG.debug("cryptomator.ipcSocketPath: {}", System.getProperty("cryptomator.ipcSocketPath"));
|
logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME);
|
||||||
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
|
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
|
||||||
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
|
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
|
||||||
LOG.debug("cryptomator.pluginDir: {}", System.getProperty("cryptomator.pluginDir"));
|
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
|
||||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
|
||||||
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
|
||||||
LOG.debug("cryptomator.appVersion: {}", System.getProperty("cryptomator.appVersion"));
|
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
|
||||||
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
|
||||||
LOG.debug("cryptomator.showTrayIcon: {}", System.getProperty("cryptomator.showTrayIcon"));
|
logCryptomatorSystemProperty(APP_VERSION_PROP_NAME);
|
||||||
LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
|
logCryptomatorSystemProperty(BUILD_NUMBER_PROP_NAME);
|
||||||
|
logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME);
|
||||||
|
logCryptomatorSystemProperty(P12_PATH_PROP_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Environment getInstance() {
|
||||||
|
final class Holder {
|
||||||
|
|
||||||
|
private static final Environment INSTANCE = new Environment();
|
||||||
|
}
|
||||||
|
return Holder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logCryptomatorSystemProperty(String propertyName) {
|
||||||
|
LOG.info("{}: {}", propertyName, System.getProperty(propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean useCustomLogbackConfig() {
|
public boolean useCustomLogbackConfig() {
|
||||||
@@ -50,52 +73,56 @@ public class Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Stream<Path> getSettingsPath() {
|
public Stream<Path> getSettingsPath() {
|
||||||
return getPaths("cryptomator.settingsPath");
|
return getPaths(SETTINGS_PATH_PROP_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<Path> getP12Path() {
|
||||||
|
return getPaths(P12_PATH_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<Path> ipcSocketPath() {
|
public Stream<Path> ipcSocketPath() {
|
||||||
return getPaths("cryptomator.ipcSocketPath");
|
return getPaths(IPC_SOCKET_PATH_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<Path> getKeychainPath() {
|
public Stream<Path> getKeychainPath() {
|
||||||
return getPaths("cryptomator.keychainPath");
|
return getPaths(KEYCHAIN_PATHS_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Path> getLogDir() {
|
public Optional<Path> getLogDir() {
|
||||||
return getPath("cryptomator.logDir").map(this::replaceHomeDir);
|
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getLoopbackAlias() {
|
||||||
|
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Path> getPluginDir() {
|
public Optional<Path> getPluginDir() {
|
||||||
return getPath("cryptomator.pluginDir").map(this::replaceHomeDir);
|
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Path> getMountPointsDir() {
|
public Optional<Path> getMountPointsDir() {
|
||||||
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
|
return getPath(MOUNTPOINT_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<String> getAppVersion() {
|
/**
|
||||||
return Optional.ofNullable(System.getProperty("cryptomator.appVersion"));
|
* Returns the app version defined in the {@value APP_VERSION_PROP_NAME} property or returns "SNAPSHOT".
|
||||||
|
*
|
||||||
|
* @return App version or "SNAPSHOT", if undefined
|
||||||
|
*/
|
||||||
|
public String getAppVersion() {
|
||||||
|
return System.getProperty(APP_VERSION_PROP_NAME, "SNAPSHOT");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<String> getBuildNumber() {
|
public Optional<String> getBuildNumber() {
|
||||||
return Optional.ofNullable(System.getProperty("cryptomator.buildNumber"));
|
return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMinPwLength() {
|
public int getMinPwLength() {
|
||||||
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean showTrayIcon() {
|
public boolean showTrayIcon() {
|
||||||
return Boolean.getBoolean("cryptomator.showTrayIcon");
|
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
|
||||||
}
|
|
||||||
|
|
||||||
private int getInt(String propertyName, int defaultValue) {
|
|
||||||
String value = System.getProperty(propertyName);
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(value);
|
|
||||||
} catch (NumberFormatException e) { // includes "null" values
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Path> getPath(String propertyName) {
|
private Optional<Path> getPath(String propertyName) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import javafx.beans.binding.BooleanBinding;
|
|||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@@ -18,7 +19,7 @@ public class LicenseHolder {
|
|||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final LicenseChecker licenseChecker;
|
private final LicenseChecker licenseChecker;
|
||||||
private final ObjectProperty<DecodedJWT> validJwtClaims;
|
private final ObjectProperty<DecodedJWT> validJwtClaims;
|
||||||
private final StringBinding licenseSubject;
|
private final ObservableValue<String> licenseSubject;
|
||||||
private final BooleanBinding validLicenseProperty;
|
private final BooleanBinding validLicenseProperty;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -26,7 +27,7 @@ public class LicenseHolder {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.licenseChecker = licenseChecker;
|
this.licenseChecker = licenseChecker;
|
||||||
this.validJwtClaims = new SimpleObjectProperty<>();
|
this.validJwtClaims = new SimpleObjectProperty<>();
|
||||||
this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
|
this.licenseSubject = validJwtClaims.map(DecodedJWT::getSubject);
|
||||||
this.validLicenseProperty = validJwtClaims.isNotNull();
|
this.validLicenseProperty = validJwtClaims.isNotNull();
|
||||||
|
|
||||||
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
|
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
|
||||||
@@ -55,17 +56,12 @@ public class LicenseHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding licenseSubjectProperty() {
|
public ObservableValue<String> licenseSubjectProperty() {
|
||||||
return licenseSubject;
|
return licenseSubject;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLicenseSubject() {
|
public String getLicenseSubject() {
|
||||||
DecodedJWT claims = validJwtClaims.get();
|
return licenseSubject.getValue();
|
||||||
if (claims != null) {
|
|
||||||
return claims.getSubject();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding validLicenseProperty() {
|
public BooleanBinding validLicenseProperty() {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum of common cloud providers and their default local storage location path.
|
||||||
|
*/
|
||||||
|
public enum LocationPreset {
|
||||||
|
|
||||||
|
DROPBOX("Dropbox", "~/Dropbox"),
|
||||||
|
ICLOUDDRIVE("iCloud Drive", "~/Library/Mobile Documents/com~apple~CloudDocs", "~/iCloudDrive"),
|
||||||
|
GDRIVE("Google Drive", "~/Google Drive/My Drive", "~/Google Drive"),
|
||||||
|
MEGA("MEGA", "~/MEGA"),
|
||||||
|
ONEDRIVE("OneDrive", "~/OneDrive"),
|
||||||
|
PCLOUD("pCloud", "~/pCloudDrive"),
|
||||||
|
|
||||||
|
LOCAL("local");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final List<Path> candidates;
|
||||||
|
|
||||||
|
LocationPreset(String name, String... candidates) {
|
||||||
|
this.name = name;
|
||||||
|
this.candidates = Arrays.stream(candidates).map(UserHome::resolve).map(Path::of).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for this LocationPreset if any of the associated paths exist.
|
||||||
|
*
|
||||||
|
* @return the first existing path or null, if none exists.
|
||||||
|
*/
|
||||||
|
public Path existingPath() {
|
||||||
|
return candidates.stream().filter(Files::isDirectory).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
//this contruct is needed, since static members are initialized after every enum member is initialized
|
||||||
|
//TODO: refactor this to normal class and use this also in different parts of the project
|
||||||
|
private static class UserHome {
|
||||||
|
|
||||||
|
private static final String USER_HOME = System.getProperty("user.home");
|
||||||
|
|
||||||
|
private static String resolve(String path) {
|
||||||
|
if (path.startsWith("~/")) {
|
||||||
|
return UserHome.USER_HOME + path.substring(1);
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -23,11 +23,14 @@ public class KeychainModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, List<KeychainAccessProvider> providers) {
|
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, List<KeychainAccessProvider> providers) {
|
||||||
return Bindings.createObjectBinding(() -> {
|
return Bindings.createObjectBinding(() -> {
|
||||||
|
if (!settings.useKeychain().get()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
var selectedProviderClass = settings.keychainProvider().get();
|
var selectedProviderClass = settings.keychainProvider().get();
|
||||||
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
|
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
|
||||||
var fallbackProvider = providers.stream().findFirst().orElse(null);
|
var fallbackProvider = providers.stream().findFirst().orElse(null);
|
||||||
return selectedProvider.orElse(fallbackProvider);
|
return selectedProvider.orElse(fallbackProvider);
|
||||||
}, settings.keychainProvider());
|
}, settings.keychainProvider(), settings.useKeychain());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class MountModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static List<MountService> provideSupportedMountServices() {
|
||||||
|
return MountService.get().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static ObservableValue<MountService> provideMountService(Settings settings, List<MountService> serviceImpls) {
|
||||||
|
return settings.mountService().map(desiredServiceImpl -> {
|
||||||
|
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
|
||||||
|
return serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny().orElse(fallbackProvider);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public final class WindowsDriveLetters {
|
||||||
|
|
||||||
|
private static final Set<Path> A_TO_Z;
|
||||||
|
|
||||||
|
static {
|
||||||
|
var sortedSet = new TreeSet<Path>();
|
||||||
|
IntStream.rangeClosed('A', 'Z').mapToObj(i -> Path.of((char) i + ":\\")).forEach(sortedSet::add);
|
||||||
|
A_TO_Z = Collections.unmodifiableSet(sortedSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WindowsDriveLetters() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Path> getAll() {
|
||||||
|
return A_TO_Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Path> getOccupied() {
|
||||||
|
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
return Set.of();
|
||||||
|
} else {
|
||||||
|
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
||||||
|
return StreamSupport.stream(rootDirs.spliterator(), false).collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Path> getAvailable() {
|
||||||
|
return Sets.difference(getAll(), getOccupied());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips A and B and only returns them if all others are occupied.
|
||||||
|
*
|
||||||
|
* @return an Optional containing either the letter of a free drive letter or empty, if none is available
|
||||||
|
*/
|
||||||
|
public Optional<Path> getFirstDesiredAvailable() {
|
||||||
|
var availableDriveLetters = getAvailable();
|
||||||
|
var optString = availableDriveLetters.stream().filter(this::notAOrB).findFirst();
|
||||||
|
return optString.or(() -> availableDriveLetters.stream().findFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean notAOrB(Path driveLetter) {
|
||||||
|
return !(Path.of("A:\\").equals(driveLetter) || Path.of("B:\\").equals(driveLetter));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.cryptomator.common.vaults.WindowsDriveLetters;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class AvailableDriveLetterChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public AvailableDriveLetterChooser(WindowsDriveLetters windowsDriveLetters) {
|
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
return SystemUtils.IS_OS_WINDOWS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
return this.windowsDriveLetters.getDesiredAvailableDriveLetterPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class CustomDriveLetterChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CustomDriveLetterChooser(VaultSettings vaultSettings) {
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
return SystemUtils.IS_OS_WINDOWS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare(Volume caller, Path driveLetter) throws InvalidMountPointException {
|
|
||||||
if (!Files.notExists(driveLetter, LinkOption.NOFOLLOW_LINKS)) {
|
|
||||||
//Drive already exists OR can't be determined
|
|
||||||
throw new InvalidMountPointException(new FileAlreadyExistsException(driveLetter.toString()));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.common.vaults.MountPointRequirement;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.nio.file.NotDirectoryException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class CustomMountPointChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private static final String HIDEAWAY_PREFIX = ".~$";
|
|
||||||
private static final String HIDEAWAY_SUFFIX = ".tmp";
|
|
||||||
private static final String WIN_HIDDEN = "dos:hidden";
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CustomMountPointChooser(VaultSettings vaultSettings) {
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
return caller.getImplementationType() != VolumeImpl.WEBDAV;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
//VaultSettings#getCustomMountPath already checks whether the saved custom mountpoint should be used
|
|
||||||
return this.vaultSettings.getCustomMountPath().map(Paths::get);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
|
||||||
return switch (caller.getMountPointRequirement()) {
|
|
||||||
case PARENT_NO_MOUNT_POINT -> {
|
|
||||||
prepareParentNoMountPoint(mountPoint);
|
|
||||||
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
|
||||||
yield true;
|
|
||||||
}
|
|
||||||
case EMPTY_MOUNT_POINT -> {
|
|
||||||
prepareEmptyMountPoint(mountPoint);
|
|
||||||
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
|
||||||
yield false;
|
|
||||||
}
|
|
||||||
case NONE, UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT -> {
|
|
||||||
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//This is case on Windows when using FUSE
|
|
||||||
//See https://github.com/billziss-gh/winfsp/issues/320
|
|
||||||
void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
|
|
||||||
Path hideaway = getHideaway(mountPoint);
|
|
||||||
var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
|
|
||||||
//TODO: possible improvement by just deleting an _empty_ hideaway
|
|
||||||
if (mpExists && hideExists) { //both resources exist (whatever type)
|
|
||||||
throw new InvalidMountPointException(new FileAlreadyExistsException(hideaway.toString()));
|
|
||||||
} else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
|
|
||||||
throw new InvalidMountPointException(new NoSuchFileException(mountPoint.toString()));
|
|
||||||
} else if (!mpExists) { //only hideaway exists
|
|
||||||
checkIsDirectory(hideaway);
|
|
||||||
LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
|
|
||||||
try {
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InvalidMountPointException(e);
|
|
||||||
}
|
|
||||||
} else { //only mountpoint exists
|
|
||||||
try {
|
|
||||||
checkIsDirectory(mountPoint);
|
|
||||||
checkIsEmpty(mountPoint);
|
|
||||||
|
|
||||||
Files.move(mountPoint, hideaway);
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InvalidMountPointException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException {
|
|
||||||
//This is the case for Windows when using Dokany and for Linux and Mac
|
|
||||||
checkIsDirectory(mountPoint);
|
|
||||||
try {
|
|
||||||
checkIsEmpty(mountPoint);
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new InvalidMountPointException("IOException while checking folder content", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanup(Volume caller, Path mountPoint) {
|
|
||||||
if (caller.getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT) {
|
|
||||||
Path hideaway = getHideaway(mountPoint);
|
|
||||||
try {
|
|
||||||
Files.move(hideaway, mountPoint);
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
Files.setAttribute(mountPoint, WIN_HIDDEN, false);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Unable to clean up mountpoint {} for Winfsp mounting.", mountPoint, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkIsDirectory(Path toCheck) throws InvalidMountPointException {
|
|
||||||
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
|
|
||||||
throw new InvalidMountPointException(new NotDirectoryException(toCheck.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkIsEmpty(Path toCheck) throws InvalidMountPointException, IOException {
|
|
||||||
try (var dirStream = Files.list(toCheck)) {
|
|
||||||
if (dirStream.findFirst().isPresent()) {
|
|
||||||
throw new InvalidMountPointException(new DirectoryNotEmptyException(toCheck.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//visible for testing
|
|
||||||
Path getHideaway(Path mountPoint) {
|
|
||||||
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
public class InvalidMountPointException extends Exception {
|
|
||||||
|
|
||||||
public InvalidMountPointException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidMountPointException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidMountPointException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class MacVolumeMountChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private static final Path VOLUME_PATH = Path.of("/Volumes");
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
private final MountPointHelper helper;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MacVolumeMountChooser(VaultSettings vaultSettings, MountPointHelper helper) {
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
this.helper = helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
return SystemUtils.IS_OS_MAC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
return Optional.of(helper.chooseTemporaryMountPoint(vaultSettings, VOLUME_PATH));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare(Volume caller, Path mountPoint) {
|
|
||||||
// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
|
|
||||||
// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
|
|
||||||
// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
|
|
||||||
// Therefore we don't need to prepare anything.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import dagger.multibindings.IntKey;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base interface for the Mountpoint-Choosing-Operation that results in the choice and
|
|
||||||
* preparation of a mountpoint or an exception otherwise.<br>
|
|
||||||
* <p>All <i>MountPointChoosers (MPCs)</i> need to implement this class and must be added to
|
|
||||||
* the pool of possible MPCs by the {@link MountPointChooserModule MountPointChooserModule.}
|
|
||||||
* The MountPointChooserModule will sort them according to their {@link IntKey IntKey priority.}
|
|
||||||
* The priority must be defined by the developer to reflect a useful execution order.<br>
|
|
||||||
* A specific priority <b>must not</b> be assigned to more than one MPC at a time;
|
|
||||||
* the result of having two MPCs with equal priority is undefined.
|
|
||||||
*
|
|
||||||
* <p>MPCs are executed by a {@link Volume} in descending order of their priority
|
|
||||||
* (higher priorities are tried first) to find and prepare a suitable mountpoint for the volume.
|
|
||||||
* The volume has access to a {@link SortedSet} of MPCs in this specific order,
|
|
||||||
* that is provided by the Module. The Set contains all available Choosers, even if they
|
|
||||||
* are not {@link #isApplicable(Volume) applicable} for the Vault/Volume. The Volume must
|
|
||||||
* check whether a MPC is applicable by invoking {@code #isApplicable(Volume)} on it
|
|
||||||
* <i>before</i> calling {@code #chooseMountPoint(Volume)}.
|
|
||||||
*
|
|
||||||
* <p>At execution of a MPC {@link #chooseMountPoint(Volume)} is called to choose a mountpoint
|
|
||||||
* according to the MPC's <i>strategy.</i> The <i>strategy</i> can involve reading configs,
|
|
||||||
* searching the filesystem, processing user-input or similar operations.
|
|
||||||
* If {@code #chooseMountPoint(Volume)} returns a non-null path (everything but
|
|
||||||
* {@linkplain Optional#empty()}) the MPC's {@link #prepare(Volume, Path)} method is called and the
|
|
||||||
* MountPoint is verified and/or prepared. In this case <i>no other MPC's will be called for
|
|
||||||
* this volume, even if {@code #prepare(Volume, Path)} fails.</i>
|
|
||||||
*
|
|
||||||
* <p>If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed
|
|
||||||
* <i>without</i> first calling the {@code #prepare(Volume, Path)} method of the current MPC.
|
|
||||||
* This is repeated until<br>
|
|
||||||
* <ul>
|
|
||||||
* <li><b>either</b> a mountpoint is returned by {@code #chooseMountPoint(Volume)}
|
|
||||||
* and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation</li>
|
|
||||||
* <li><b>or</b> no MPC remains and an {@link InvalidMountPointException} is thrown.</li>
|
|
||||||
* </ul>
|
|
||||||
* If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire
|
|
||||||
* Mountpoint-Choosing-Operation is aborted and the method should do all necessary cleanup
|
|
||||||
* before throwing the exception.
|
|
||||||
* If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
|
|
||||||
* remaining cleanup.
|
|
||||||
*/
|
|
||||||
public interface MountPointChooser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the {@link Volume} to determine whether this MountPointChooser is
|
|
||||||
* applicable for mounting the Vault/Volume, especially with regard to the
|
|
||||||
* current system configuration and particularities of the Volume type.
|
|
||||||
*
|
|
||||||
* <p>Developers should override this method to check for system configurations
|
|
||||||
* that are unsuitable for this MPC.
|
|
||||||
*
|
|
||||||
* @param caller The Volume that is calling the method to determine applicability of the MPC
|
|
||||||
* @return a boolean flag; true if applicable, else false.
|
|
||||||
* @see #chooseMountPoint(Volume)
|
|
||||||
*/
|
|
||||||
boolean isApplicable(Volume caller);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a {@link Volume} to choose a mountpoint according to the
|
|
||||||
* MountPointChoosers strategy.
|
|
||||||
*
|
|
||||||
* <p>This method must only be called for MPCs that were deemed
|
|
||||||
* {@link #isApplicable(Volume) applicable} by the {@link Volume Volume.}
|
|
||||||
* Developers should override this method to find or extract a mountpoint for
|
|
||||||
* the volume <b>without</b> preparing it. Preparation should be done by
|
|
||||||
* {@link #prepare(Volume, Path)} instead.
|
|
||||||
* Exceptions in this method should be handled gracefully and result in returning
|
|
||||||
* {@link Optional#empty()} instead of throwing an exception.
|
|
||||||
*
|
|
||||||
* @param caller The Volume that is calling the method to choose a mountpoint
|
|
||||||
* @return the chosen path or {@link Optional#empty()} if an exception occurred
|
|
||||||
* or no mountpoint could be found.
|
|
||||||
* @see #isApplicable(Volume)
|
|
||||||
* @see #prepare(Volume, Path)
|
|
||||||
*/
|
|
||||||
Optional<Path> chooseMountPoint(Volume caller);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a {@link Volume} to prepare and/or verify the chosen mountpoint.<br>
|
|
||||||
* This method is only called if the {@link #chooseMountPoint(Volume)} method
|
|
||||||
* of the same MountPointChooser returned a path.
|
|
||||||
*
|
|
||||||
* <p>Developers should override this method to prepare the mountpoint for
|
|
||||||
* the volume and check for any obstacles that could hinder the mount operation.
|
|
||||||
* The mountpoint is deemed "prepared" if it can be used to mount a volume
|
|
||||||
* without any further filesystem actions or user interaction. If this is not possible,
|
|
||||||
* this method should fail. In other words: This method should not return without
|
|
||||||
* either failing or finalizing the preparation of the mountpoint.
|
|
||||||
* Generally speaking exceptions should be wrapped as
|
|
||||||
* {@link InvalidMountPointException} to allow efficient handling by the caller.
|
|
||||||
*
|
|
||||||
* <p>Often the preparation of a mountpoint involves creating files or others
|
|
||||||
* actions that require cleaning up after the volume is unmounted.
|
|
||||||
* In this case developers should override the {@link #cleanup(Volume, Path)}
|
|
||||||
* method and return {@code true} to the volume to indicate that the
|
|
||||||
* {@code #cleanup} method of this MPC should be called after unmount.
|
|
||||||
*
|
|
||||||
* <p><b>Please note:</b> If this method fails the entire
|
|
||||||
* Mountpoint-Choosing-Operation is aborted without calling
|
|
||||||
* {@link #cleanup(Volume, Path)} or any other MPCs. Therefore this method should
|
|
||||||
* do all necessary cleanup before throwing the exception.
|
|
||||||
*
|
|
||||||
* @param caller The Volume that is calling the method to prepare a mountpoint
|
|
||||||
* @param mountPoint the mountpoint chosen by {@link #chooseMountPoint(Volume)}
|
|
||||||
* @return a boolean flag; true if cleanup is needed, false otherwise
|
|
||||||
* @throws InvalidMountPointException if the preparation fails
|
|
||||||
* @see #chooseMountPoint(Volume)
|
|
||||||
* @see #cleanup(Volume, Path)
|
|
||||||
*/
|
|
||||||
default boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
|
||||||
return false; //NO-OP
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a {@link Volume} to do any cleanup needed after unmount.
|
|
||||||
*
|
|
||||||
* <p>This method is only called if the {@link #prepare(Volume, Path)} method
|
|
||||||
* of the same MountPointChooser returned {@code true}. Typically developers want to
|
|
||||||
* delete any files created prior to mount or do similar tasks.<br>
|
|
||||||
* Exceptions in this method should be handled gracefully.
|
|
||||||
*
|
|
||||||
* @param caller The Volume that is calling the method to cleanup the prepared mountpoint
|
|
||||||
* @param mountPoint the mountpoint that was prepared by {@link #prepare(Volume, Path)}
|
|
||||||
* @see #prepare(Volume, Path)
|
|
||||||
*/
|
|
||||||
default void cleanup(Volume caller, Path mountPoint) {
|
|
||||||
//NO-OP
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import dagger.Binds;
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
import dagger.multibindings.IntKey;
|
|
||||||
import dagger.multibindings.IntoMap;
|
|
||||||
import org.cryptomator.common.vaults.PerVault;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
|
|
||||||
* See there for additional information.
|
|
||||||
*
|
|
||||||
* @see MountPointChooser
|
|
||||||
*/
|
|
||||||
@Module
|
|
||||||
public abstract class MountPointChooserModule {
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(1000)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(900)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(800)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(101)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindMacVolumeMountChooser(MacVolumeMountChooser chooser);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(100)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@PerVault
|
|
||||||
@Named("orderedMountPointChoosers")
|
|
||||||
public static Iterable<MountPointChooser> provideOrderedMountPointChoosers(Map<Integer, MountPointChooser> choosers) {
|
|
||||||
SortedMap<Integer, MountPointChooser> sortedChoosers = new TreeMap<>(Comparator.reverseOrder());
|
|
||||||
sortedChoosers.putAll(choosers);
|
|
||||||
return Iterables.unmodifiableIterable(sortedChoosers.values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class MountPointHelper {
|
|
||||||
|
|
||||||
public static Logger LOG = LoggerFactory.getLogger(MountPointHelper.class);
|
|
||||||
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
|
||||||
|
|
||||||
private final Optional<Path> tmpMountPointDir;
|
|
||||||
private volatile boolean unmountDebrisCleared = false;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MountPointHelper(Environment env) {
|
|
||||||
this.tmpMountPointDir = env.getMountPointsDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path chooseTemporaryMountPoint(VaultSettings vaultSettings, Path parentDir) {
|
|
||||||
String basename = vaultSettings.mountName().get();
|
|
||||||
//regular
|
|
||||||
Path mountPoint = parentDir.resolve(basename);
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
//with id
|
|
||||||
mountPoint = parentDir.resolve(basename + " (" + vaultSettings.getId() + ")");
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
//with id and count
|
|
||||||
for (int i = 1; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
|
|
||||||
mountPoint = parentDir.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parentDir, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void clearIrregularUnmountDebrisIfNeeded() {
|
|
||||||
if (unmountDebrisCleared || tmpMountPointDir.isEmpty()) {
|
|
||||||
return; // nothing to do
|
|
||||||
}
|
|
||||||
if (Files.exists(tmpMountPointDir.get(), LinkOption.NOFOLLOW_LINKS)) {
|
|
||||||
clearIrregularUnmountDebris(tmpMountPointDir.get());
|
|
||||||
}
|
|
||||||
unmountDebrisCleared = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearIrregularUnmountDebris(Path dirContainingMountPoints) {
|
|
||||||
IOException cleanupFailed = new IOException("Cleanup failed");
|
|
||||||
|
|
||||||
try (var ds = Files.newDirectoryStream(dirContainingMountPoints)) {
|
|
||||||
LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints);
|
|
||||||
for (Path p : ds) {
|
|
||||||
try {
|
|
||||||
var attr = Files.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
if (attr.isOther() && attr.isDirectory()) { // yes, this is possible with windows junction points -.-
|
|
||||||
Files.delete(p);
|
|
||||||
} else if (attr.isDirectory()) {
|
|
||||||
deleteEmptyDir(p);
|
|
||||||
} else if (attr.isSymbolicLink()) {
|
|
||||||
deleteDeadLink(p);
|
|
||||||
} else {
|
|
||||||
LOG.debug("Found non-directory element in mountpoint dir: {}", p);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
cleanupFailed.addSuppressed(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanupFailed.getSuppressed().length > 0) {
|
|
||||||
throw cleanupFailed;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e);
|
|
||||||
} finally {
|
|
||||||
unmountDebrisCleared = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteEmptyDir(Path dir) throws IOException {
|
|
||||||
assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
try {
|
|
||||||
ensureIsEmpty(dir);
|
|
||||||
Files.delete(dir); // attempt to delete dir non-recursively (will fail, if there are contents)
|
|
||||||
} catch (DirectoryNotEmptyException e) {
|
|
||||||
LOG.info("Found non-empty directory in mountpoint dir: {}", dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteDeadLink(Path symlink) throws IOException {
|
|
||||||
assert Files.isSymbolicLink(symlink);
|
|
||||||
if (Files.notExists(symlink)) { // following link: target does not exist
|
|
||||||
Files.delete(symlink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureIsEmpty(Path dir) throws IOException {
|
|
||||||
try (var ds = Files.newDirectoryStream(dir)) {
|
|
||||||
if (ds.iterator().hasNext()){
|
|
||||||
throw new DirectoryNotEmptyException(dir.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class TemporaryMountPointChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
private final Environment environment;
|
|
||||||
private final MountPointHelper helper;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment, MountPointHelper helper) {
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
this.environment = environment;
|
|
||||||
this.helper = helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
if (this.environment.getMountPointsDir().isEmpty()) {
|
|
||||||
LOG.warn("\"cryptomator.mountPointsDir\" is not set to a valid path!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
assert environment.getMountPointsDir().isPresent();
|
|
||||||
//clean leftovers of not-regularly unmounted vaults
|
|
||||||
//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
|
|
||||||
helper.clearIrregularUnmountDebrisIfNeeded();
|
|
||||||
return this.environment.getMountPointsDir().map(dir -> this.helper.chooseTemporaryMountPoint(this.vaultSettings, dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
|
||||||
try {
|
|
||||||
switch (caller.getMountPointRequirement()) {
|
|
||||||
case PARENT_NO_MOUNT_POINT -> {
|
|
||||||
Files.createDirectories(mountPoint.getParent());
|
|
||||||
LOG.debug("Successfully created folder for mount point: {}", mountPoint);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case EMPTY_MOUNT_POINT -> {
|
|
||||||
Files.createDirectories(mountPoint);
|
|
||||||
LOG.debug("Successfully created mount point: {}", mountPoint);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case NONE -> {
|
|
||||||
//Requirement "NONE" doesn't make any sense here.
|
|
||||||
//No need to prepare/verify a Mountpoint without requiring one...
|
|
||||||
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
//Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT"
|
|
||||||
throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new InvalidMountPointException("IOException while preparing mountpoint", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanup(Volume caller, Path mountPoint) {
|
|
||||||
try {
|
|
||||||
Files.delete(mountPoint);
|
|
||||||
LOG.debug("Successfully deleted mount point: {}", mountPoint);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Could not delete mount point: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
|
import org.cryptomator.cryptolib.common.P384KeyPair;
|
||||||
|
import org.cryptomator.cryptolib.common.Pkcs12Exception;
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class DeviceKey {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DeviceKey.class);
|
||||||
|
private static final String KEYCHAIN_KEY = "cryptomator-device-p12";
|
||||||
|
private static final String KEYCHAIN_DISPLAY_NAME = "Cryptomator Device Keypair .p12 Passphrase";
|
||||||
|
|
||||||
|
private final KeychainManager keychainManager;
|
||||||
|
private final Environment env;
|
||||||
|
private final SecureRandom csprng;
|
||||||
|
private final Supplier<P384KeyPair> keyPairSupplier;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DeviceKey(KeychainManager keychainManager, Environment env, SecureRandom csprng) {
|
||||||
|
this.keychainManager = keychainManager;
|
||||||
|
this.env = env;
|
||||||
|
this.csprng = csprng;
|
||||||
|
this.keyPairSupplier = Suppliers.memoize(this::loadOrCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public P384KeyPair get() throws DeviceKeyRetrievalException {
|
||||||
|
Preconditions.checkState(keychainManager.isSupported());
|
||||||
|
return keyPairSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private P384KeyPair loadOrCreate() throws DeviceKeyRetrievalException {
|
||||||
|
Path p12File = env.getP12Path().findFirst().orElseThrow(() -> new DeviceKeyRetrievalException("No path for .p12 file configured"));
|
||||||
|
char[] passphrase = null;
|
||||||
|
try {
|
||||||
|
passphrase = keychainManager.loadPassphrase(KEYCHAIN_KEY);
|
||||||
|
if (passphrase != null && Files.isRegularFile(p12File)) {
|
||||||
|
return loadExistingKeyPair(passphrase, p12File);
|
||||||
|
} else { // (re)generate new key pair if either file or password got lost
|
||||||
|
passphrase = randomPassword();
|
||||||
|
keychainManager.storePassphrase(KEYCHAIN_KEY, KEYCHAIN_DISPLAY_NAME, CharBuffer.wrap(passphrase));
|
||||||
|
return createAndStoreNewKeyPair(passphrase, p12File);
|
||||||
|
}
|
||||||
|
} catch (KeychainAccessException e) {
|
||||||
|
throw new DeviceKeyRetrievalException("Failed to access system keychain", e);
|
||||||
|
} catch (Pkcs12Exception | IOException e) {
|
||||||
|
throw new DeviceKeyRetrievalException("Failed to access .p12 file", e);
|
||||||
|
} finally {
|
||||||
|
if (passphrase != null) {
|
||||||
|
Arrays.fill(passphrase, '\0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private P384KeyPair loadExistingKeyPair(char[] passphrase, Path p12File) throws IOException {
|
||||||
|
LOG.debug("Loading existing device key from {}", p12File);
|
||||||
|
return P384KeyPair.load(p12File, passphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private P384KeyPair createAndStoreNewKeyPair(char[] passphrase, Path p12File) throws IOException {
|
||||||
|
var keyPair = P384KeyPair.generate();
|
||||||
|
LOG.debug("Store new device key to {}", p12File);
|
||||||
|
keyPair.store(p12File, passphrase);
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
private char[] randomPassword() {
|
||||||
|
// this is a fast & easy attempt to create a random string:
|
||||||
|
var uuid = new UUID(csprng.nextLong(), csprng.nextLong());
|
||||||
|
return uuid.toString().toCharArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DeviceKeyRetrievalException extends RuntimeException {
|
||||||
|
private DeviceKeyRetrievalException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
private DeviceKeyRetrievalException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -32,11 +32,11 @@ public class Settings {
|
|||||||
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
||||||
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
|
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
|
||||||
public static final boolean DEFAULT_START_HIDDEN = false;
|
public static final boolean DEFAULT_START_HIDDEN = false;
|
||||||
|
public static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
|
||||||
|
public static final boolean DEFAULT_USE_KEYCHAIN = true;
|
||||||
public static final int DEFAULT_PORT = 42427;
|
public static final int DEFAULT_PORT = 42427;
|
||||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||||
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
|
||||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = SystemUtils.IS_OS_WINDOWS ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
|
||||||
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||||
@Deprecated // to be changed to "whatever is available" eventually
|
@Deprecated // to be changed to "whatever is available" eventually
|
||||||
public static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
|
public static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
|
||||||
@@ -51,11 +51,11 @@ public class Settings {
|
|||||||
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
|
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
|
||||||
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
|
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
|
||||||
|
private final BooleanProperty autoCloseVaults = new SimpleBooleanProperty(DEFAULT_AUTO_CLOSE_VAULTS);
|
||||||
|
private final BooleanProperty useKeychain = new SimpleBooleanProperty(DEFAULT_USE_KEYCHAIN);
|
||||||
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
||||||
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
||||||
private final ObjectProperty<WebDavUrlScheme> preferredGvfsScheme = new SimpleObjectProperty<>(DEFAULT_GVFS_SCHEME);
|
|
||||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
|
||||||
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
||||||
private final ObjectProperty<String> keychainProvider = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_PROVIDER);
|
private final ObjectProperty<String> keychainProvider = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_PROVIDER);
|
||||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||||
@@ -70,6 +70,9 @@ public class Settings {
|
|||||||
private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE);
|
private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE);
|
||||||
|
|
||||||
|
|
||||||
|
private final StringProperty mountService = new SimpleStringProperty();
|
||||||
|
|
||||||
|
|
||||||
private Consumer<Settings> saveCmd;
|
private Consumer<Settings> saveCmd;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,11 +85,11 @@ public class Settings {
|
|||||||
askedForUpdateCheck.addListener(this::somethingChanged);
|
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||||
checkForUpdates.addListener(this::somethingChanged);
|
checkForUpdates.addListener(this::somethingChanged);
|
||||||
startHidden.addListener(this::somethingChanged);
|
startHidden.addListener(this::somethingChanged);
|
||||||
|
autoCloseVaults.addListener(this::somethingChanged);
|
||||||
|
useKeychain.addListener(this::somethingChanged);
|
||||||
port.addListener(this::somethingChanged);
|
port.addListener(this::somethingChanged);
|
||||||
numTrayNotifications.addListener(this::somethingChanged);
|
numTrayNotifications.addListener(this::somethingChanged);
|
||||||
preferredGvfsScheme.addListener(this::somethingChanged);
|
|
||||||
debugMode.addListener(this::somethingChanged);
|
debugMode.addListener(this::somethingChanged);
|
||||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
|
||||||
theme.addListener(this::somethingChanged);
|
theme.addListener(this::somethingChanged);
|
||||||
keychainProvider.addListener(this::somethingChanged);
|
keychainProvider.addListener(this::somethingChanged);
|
||||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||||
@@ -99,6 +102,7 @@ public class Settings {
|
|||||||
windowHeight.addListener(this::somethingChanged);
|
windowHeight.addListener(this::somethingChanged);
|
||||||
displayConfiguration.addListener(this::somethingChanged);
|
displayConfiguration.addListener(this::somethingChanged);
|
||||||
language.addListener(this::somethingChanged);
|
language.addListener(this::somethingChanged);
|
||||||
|
mountService.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||||
@@ -133,6 +137,12 @@ public class Settings {
|
|||||||
return startHidden;
|
return startHidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty autoCloseVaults() {
|
||||||
|
return autoCloseVaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty useKeychain() { return useKeychain; }
|
||||||
|
|
||||||
public IntegerProperty port() {
|
public IntegerProperty port() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
@@ -141,16 +151,12 @@ public class Settings {
|
|||||||
return numTrayNotifications;
|
return numTrayNotifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<WebDavUrlScheme> preferredGvfsScheme() {
|
|
||||||
return preferredGvfsScheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty debugMode() {
|
public BooleanProperty debugMode() {
|
||||||
return debugMode;
|
return debugMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<VolumeImpl> preferredVolumeImpl() {
|
public StringProperty mountService() {
|
||||||
return preferredVolumeImpl;
|
return mountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<UiTheme> theme() {
|
public ObjectProperty<UiTheme> theme() {
|
||||||
@@ -198,4 +204,5 @@ public class Settings {
|
|||||||
public StringProperty languageProperty() {
|
public StringProperty languageProperty() {
|
||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
||||||
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
||||||
out.name("startHidden").value(value.startHidden().get());
|
out.name("startHidden").value(value.startHidden().get());
|
||||||
|
out.name("autoCloseVaults").value(value.autoCloseVaults().get());
|
||||||
out.name("port").value(value.port().get());
|
out.name("port").value(value.port().get());
|
||||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get().name());
|
|
||||||
out.name("debugMode").value(value.debugMode().get());
|
out.name("debugMode").value(value.debugMode().get());
|
||||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
|
||||||
out.name("theme").value(value.theme().get().name());
|
out.name("theme").value(value.theme().get().name());
|
||||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||||
out.name("keychainProvider").value(value.keychainProvider().get());
|
out.name("keychainProvider").value(value.keychainProvider().get());
|
||||||
|
out.name("useKeychain").value(value.useKeychain().get());
|
||||||
out.name("licenseKey").value(value.licenseKey().get());
|
out.name("licenseKey").value(value.licenseKey().get());
|
||||||
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
|
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
|
||||||
out.name("showTrayIcon").value(value.showTrayIcon().get());
|
out.name("showTrayIcon").value(value.showTrayIcon().get());
|
||||||
@@ -58,7 +58,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.name("windowHeight").value((value.windowHeightProperty().get()));
|
out.name("windowHeight").value((value.windowHeightProperty().get()));
|
||||||
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
|
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
|
||||||
out.name("language").value((value.languageProperty().get()));
|
out.name("language").value((value.languageProperty().get()));
|
||||||
|
out.name("mountService").value(value.mountService().get());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +82,14 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
|
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||||
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
|
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
|
||||||
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
|
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
|
||||||
|
case "autoCloseVaults" -> settings.autoCloseVaults().set(in.nextBoolean());
|
||||||
case "port" -> settings.port().set(in.nextInt());
|
case "port" -> settings.port().set(in.nextInt());
|
||||||
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
||||||
case "preferredGvfsScheme" -> settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
|
||||||
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
|
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
|
||||||
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
|
||||||
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
||||||
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||||
case "keychainProvider" -> settings.keychainProvider().set(in.nextString());
|
case "keychainProvider" -> settings.keychainProvider().set(in.nextString());
|
||||||
|
case "useKeychain" -> settings.useKeychain().set(in.nextBoolean());
|
||||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||||
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
|
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
|
||||||
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
|
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
|
||||||
@@ -99,9 +99,15 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
|
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
|
||||||
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
|
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
|
||||||
case "language" -> settings.languageProperty().set(in.nextString());
|
case "language" -> settings.languageProperty().set(in.nextString());
|
||||||
|
case "mountService" -> {
|
||||||
|
var token = in.peek();
|
||||||
|
if (JsonToken.STRING == token) {
|
||||||
|
settings.mountService().set(in.nextString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,24 +117,6 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
|
|
||||||
try {
|
|
||||||
return VolumeImpl.valueOf(nioAdapterName.toUpperCase());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
LOG.warn("Invalid volume type {}. Defaulting to {}.", nioAdapterName, Settings.DEFAULT_PREFERRED_VOLUME_IMPL);
|
|
||||||
return Settings.DEFAULT_PREFERRED_VOLUME_IMPL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private WebDavUrlScheme parseWebDavUrlSchemePrefix(String webDavUrlSchemeName) {
|
|
||||||
try {
|
|
||||||
return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
LOG.warn("Invalid WebDAV url scheme {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME);
|
|
||||||
return Settings.DEFAULT_GVFS_SCHEME;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UiTheme parseUiTheme(String uiThemeName) {
|
private UiTheme parseUiTheme(String uiThemeName) {
|
||||||
try {
|
try {
|
||||||
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
||||||
|
|||||||
@@ -49,14 +49,12 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
|
|
||||||
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
||||||
private final Supplier<Settings> settings = Suppliers.memoize(this::load);
|
private final Supplier<Settings> settings = Suppliers.memoize(this::load);
|
||||||
private final SettingsJsonAdapter settingsJsonAdapter;
|
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SettingsProvider(SettingsJsonAdapter settingsJsonAdapter, Environment env, ScheduledExecutorService scheduler) {
|
public SettingsProvider(SettingsJsonAdapter settingsJsonAdapter, Environment env, ScheduledExecutorService scheduler) {
|
||||||
this.settingsJsonAdapter = settingsJsonAdapter;
|
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.gson = new GsonBuilder() //
|
this.gson = new GsonBuilder() //
|
||||||
@@ -118,7 +116,7 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
try {
|
try {
|
||||||
Files.createDirectories(settingsPath.getParent());
|
Files.createDirectories(settingsPath.getParent());
|
||||||
Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp");
|
Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp");
|
||||||
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE_NEW); //
|
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); //
|
||||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
|
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
|
||||||
gson.toJson(settings, writer);
|
gson.toJson(settings, writer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,11 @@
|
|||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringExpression;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -22,7 +21,6 @@ import javafx.beans.property.SimpleStringProperty;
|
|||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +30,6 @@ public class VaultSettings {
|
|||||||
|
|
||||||
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
||||||
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
|
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
|
||||||
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
|
||||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||||
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
|
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
|
||||||
@@ -45,26 +42,32 @@ public class VaultSettings {
|
|||||||
private final String id;
|
private final String id;
|
||||||
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
||||||
private final StringProperty displayName = new SimpleStringProperty();
|
private final StringProperty displayName = new SimpleStringProperty();
|
||||||
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
|
||||||
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
||||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
|
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
|
||||||
private final BooleanProperty useCustomMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
|
||||||
private final StringProperty customMountPath = new SimpleStringProperty();
|
|
||||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
||||||
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
||||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||||
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
|
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
|
||||||
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
|
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
|
||||||
private final StringBinding mountName;
|
private final StringExpression mountName;
|
||||||
|
private final ObjectProperty<Path> mountPoint = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
public VaultSettings(String id) {
|
public VaultSettings(String id) {
|
||||||
this.id = Objects.requireNonNull(id);
|
this.id = Objects.requireNonNull(id);
|
||||||
this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
|
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
|
||||||
|
final String name;
|
||||||
|
if (displayName.isEmpty().get()) {
|
||||||
|
name = path.get().getFileName().toString();
|
||||||
|
} else {
|
||||||
|
name = displayName.get();
|
||||||
|
}
|
||||||
|
return normalizeDisplayName(name);
|
||||||
|
}, displayName, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable[] observables() {
|
Observable[] observables() {
|
||||||
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, maxCleartextFilenameLength, actionAfterUnlock, autoLockWhenIdle, autoLockIdleSeconds};
|
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultSettings withRandomId() {
|
public static VaultSettings withRandomId() {
|
||||||
@@ -78,8 +81,7 @@ public class VaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
//visible for testing
|
||||||
String normalizeDisplayName() {
|
static String normalizeDisplayName(String original) {
|
||||||
var original = displayName.getValueSafe();
|
|
||||||
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
||||||
return "_";
|
return "_";
|
||||||
}
|
}
|
||||||
@@ -105,22 +107,10 @@ public class VaultSettings {
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding mountName() {
|
public StringExpression mountName() {
|
||||||
return mountName;
|
return mountName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty winDriveLetter() {
|
|
||||||
return winDriveLetter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getWinDriveLetter() {
|
|
||||||
String current = this.winDriveLetter.get();
|
|
||||||
if (!Strings.isNullOrEmpty(current)) {
|
|
||||||
return Optional.of(current);
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty unlockAfterStartup() {
|
public BooleanProperty unlockAfterStartup() {
|
||||||
return unlockAfterStartup;
|
return unlockAfterStartup;
|
||||||
}
|
}
|
||||||
@@ -129,20 +119,12 @@ public class VaultSettings {
|
|||||||
return revealAfterMount;
|
return revealAfterMount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty useCustomMountPath() {
|
public Path getMountPoint() {
|
||||||
return useCustomMountPath;
|
return mountPoint.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty customMountPath() {
|
public ObjectProperty<Path> mountPoint() {
|
||||||
return customMountPath;
|
return mountPoint;
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getCustomMountPath() {
|
|
||||||
if (useCustomMountPath.get()) {
|
|
||||||
return Optional.ofNullable(Strings.emptyToNull(customMountPath.get()));
|
|
||||||
} else {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty usesReadOnlyMode() {
|
public BooleanProperty usesReadOnlyMode() {
|
||||||
@@ -188,5 +170,4 @@ public class VaultSettings {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,14 @@
|
|||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
class VaultSettingsJsonAdapter {
|
class VaultSettingsJsonAdapter {
|
||||||
@@ -22,11 +25,10 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.name("id").value(value.getId());
|
out.name("id").value(value.getId());
|
||||||
out.name("path").value(value.path().get().toString());
|
out.name("path").value(value.path().get().toString());
|
||||||
out.name("displayName").value(value.displayName().get());
|
out.name("displayName").value(value.displayName().get());
|
||||||
out.name("winDriveLetter").value(value.winDriveLetter().get());
|
|
||||||
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
||||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||||
out.name("useCustomMountPath").value(value.useCustomMountPath().get());
|
var mountPoint = value.mountPoint().get();
|
||||||
out.name("customMountPath").value(value.customMountPath().get());
|
out.name("mountPoint").value(mountPoint != null ? mountPoint.toAbsolutePath().toString() : null);
|
||||||
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||||
out.name("mountFlags").value(value.mountFlags().get());
|
out.name("mountFlags").value(value.mountFlags().get());
|
||||||
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
|
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
|
||||||
@@ -36,18 +38,18 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: usesCustomMountPath, customMountPath and winDriveLetter removed
|
||||||
|
// -> migration required
|
||||||
public VaultSettings read(JsonReader in) throws IOException {
|
public VaultSettings read(JsonReader in) throws IOException {
|
||||||
String id = null;
|
String id = null;
|
||||||
String path = null;
|
String path = null;
|
||||||
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
|
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
String displayName = null;
|
String displayName = null;
|
||||||
String customMountPath = null;
|
|
||||||
String winDriveLetter = null;
|
|
||||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||||
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
|
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
|
||||||
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
|
||||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||||
|
Path mountPoint = null;
|
||||||
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
||||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||||
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
||||||
@@ -61,19 +63,23 @@ class VaultSettingsJsonAdapter {
|
|||||||
case "path" -> path = in.nextString();
|
case "path" -> path = in.nextString();
|
||||||
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
|
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
case "displayName" -> displayName = in.nextString();
|
case "displayName" -> displayName = in.nextString();
|
||||||
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
|
||||||
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
||||||
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
||||||
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
|
|
||||||
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
|
||||||
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
||||||
case "mountFlags" -> mountFlags = in.nextString();
|
case "mountFlags" -> mountFlags = in.nextString();
|
||||||
|
case "mountPoint" -> {
|
||||||
|
if (JsonToken.NULL == in.peek()) {
|
||||||
|
in.nextNull();
|
||||||
|
} else {
|
||||||
|
mountPoint = parseMountPoint(in.nextString());
|
||||||
|
}
|
||||||
|
}
|
||||||
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
|
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
|
||||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||||
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
|
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
|
||||||
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
|
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,20 +93,27 @@ class VaultSettingsJsonAdapter {
|
|||||||
vaultSettings.displayName().set(mountName);
|
vaultSettings.displayName().set(mountName);
|
||||||
}
|
}
|
||||||
vaultSettings.path().set(Paths.get(path));
|
vaultSettings.path().set(Paths.get(path));
|
||||||
vaultSettings.winDriveLetter().set(winDriveLetter);
|
|
||||||
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
||||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||||
vaultSettings.useCustomMountPath().set(useCustomMountPath);
|
|
||||||
vaultSettings.customMountPath().set(customMountPath);
|
|
||||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||||
vaultSettings.mountFlags().set(mountFlags);
|
vaultSettings.mountFlags().set(mountFlags);
|
||||||
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
|
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
|
||||||
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||||
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
|
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
|
||||||
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
|
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
|
||||||
|
vaultSettings.mountPoint().set(mountPoint);
|
||||||
return vaultSettings;
|
return vaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Path parseMountPoint(String mountPoint) {
|
||||||
|
try {
|
||||||
|
return Path.of(mountPoint);
|
||||||
|
} catch (InvalidPathException e) {
|
||||||
|
LOG.warn("Invalid string as mount point. Defaulting to null.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
|
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
|
||||||
try {
|
try {
|
||||||
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
|
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.cryptomator.common.settings;
|
|
||||||
|
|
||||||
public enum WebDavUrlScheme {
|
|
||||||
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
|
|
||||||
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
|
|
||||||
|
|
||||||
private final String prefix;
|
|
||||||
private final String displayName;
|
|
||||||
|
|
||||||
WebDavUrlScheme(String prefix, String displayName) {
|
|
||||||
this.prefix = prefix;
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrefix() {
|
|
||||||
return prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayName() {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public abstract class AbstractVolume implements Volume {
|
|
||||||
|
|
||||||
private final Iterable<MountPointChooser> choosers;
|
|
||||||
|
|
||||||
protected Path mountPoint;
|
|
||||||
private boolean cleanupRequired;
|
|
||||||
private MountPointChooser usedChooser;
|
|
||||||
|
|
||||||
public AbstractVolume(Iterable<MountPointChooser> choosers) {
|
|
||||||
this.choosers = choosers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Path determineMountPoint() throws InvalidMountPointException {
|
|
||||||
var applicableChoosers = Iterables.filter(choosers, c -> c.isApplicable(this));
|
|
||||||
for (var chooser : applicableChoosers) {
|
|
||||||
Optional<Path> chosenPath = chooser.chooseMountPoint(this);
|
|
||||||
if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.cleanupRequired = chooser.prepare(this, chosenPath.get());
|
|
||||||
this.usedChooser = chooser;
|
|
||||||
return chosenPath.get();
|
|
||||||
}
|
|
||||||
throw new InvalidMountPointException(String.format("No feasible MountPoint found by choosers: %s", applicableChoosers));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void cleanupMountPoint() {
|
|
||||||
if (this.cleanupRequired) {
|
|
||||||
this.usedChooser.cleanup(this, this.mountPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> getMountPoint() {
|
|
||||||
return Optional.ofNullable(mountPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
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.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -39,14 +41,13 @@ public class AutoLocker {
|
|||||||
try {
|
try {
|
||||||
vault.lock(false);
|
vault.lock(false);
|
||||||
LOG.info("Autolocked {} after idle timeout", vault.getDisplayName());
|
LOG.info("Autolocked {} after idle timeout", vault.getDisplayName());
|
||||||
} catch (Volume.VolumeException | LockNotCompletedException e) {
|
} catch (UnmountFailedException | IOException e) {
|
||||||
LOG.error("Autolocking failed.", e);
|
LOG.error("Autolocking failed.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean exceedsIdleTime(Vault vault) {
|
private boolean exceedsIdleTime(Vault vault) {
|
||||||
assert vault.isUnlocked();
|
assert vault.isUnlocked();
|
||||||
// TODO: shouldn't we read these properties from within FX Application Thread?
|
|
||||||
if (vault.getVaultSettings().autoLockWhenIdle().get()) {
|
if (vault.getVaultSettings().autoLockWhenIdle().get()) {
|
||||||
int maxIdleSeconds = vault.getVaultSettings().autoLockIdleSeconds().get();
|
int maxIdleSeconds = vault.getVaultSettings().autoLockIdleSeconds().get();
|
||||||
var deadline = vault.getStats().getLastActivity().plusSeconds(maxIdleSeconds);
|
var deadline = vault.getStats().getLastActivity().plusSeconds(maxIdleSeconds);
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import javax.inject.Qualifier;
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Documented
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
@interface DefaultMountFlags {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
import org.cryptomator.frontend.dokany.DokanyMountFailedException;
|
|
||||||
import org.cryptomator.frontend.dokany.Mount;
|
|
||||||
import org.cryptomator.frontend.dokany.MountFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class DokanyVolume extends AbstractVolume {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
|
|
||||||
|
|
||||||
private static final String FS_TYPE_NAME = "CryptomatorFS";
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
|
|
||||||
private Mount mount;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public DokanyVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
|
||||||
super(choosers);
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VolumeImpl getImplementationType() {
|
|
||||||
return VolumeImpl.DOKANY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
|
|
||||||
this.mountPoint = determineMountPoint();
|
|
||||||
try {
|
|
||||||
this.mount = MountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip(), onExitAction);
|
|
||||||
} catch (DokanyMountFailedException e) {
|
|
||||||
if (vaultSettings.getCustomMountPath().isPresent()) {
|
|
||||||
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
|
|
||||||
}
|
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reveal(Revealer revealer) throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.reveal(revealer::reveal);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unmount() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.unmount();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
throw new VolumeException("Unmount Failed.", e);
|
|
||||||
}
|
|
||||||
cleanupMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unmountForced() {
|
|
||||||
mount.unmountForced();
|
|
||||||
cleanupMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsForcedUnmount() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return DokanyVolume.isSupportedStatic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MountPointRequirement getMountPointRequirement() {
|
|
||||||
return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.EMPTY_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
|
||||||
return MountFactory.isApplicable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterators;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseMountException;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.Mount;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.Mounter;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class FuseVolume extends AbstractVolume {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
|
||||||
private static final Pattern NON_WHITESPACE_OR_QUOTED = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); // Thanks to https://stackoverflow.com/a/366532
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
|
|
||||||
private Mount mount;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public FuseVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
|
||||||
super(choosers);
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
|
|
||||||
this.mountPoint = determineMountPoint();
|
|
||||||
mount(fs.getPath("/"), mountFlags, onExitAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mount(Path root, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
|
|
||||||
try {
|
|
||||||
Mounter mounter = FuseMountFactory.getMounter();
|
|
||||||
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
|
||||||
.withFlags(splitFlags(mountFlags)) //
|
|
||||||
.withMountPoint(mountPoint) //
|
|
||||||
.withFileNameTranscoder(mounter.defaultFileNameTranscoder()) //
|
|
||||||
.build();
|
|
||||||
this.mount = mounter.mount(root, envVars, onExitAction);
|
|
||||||
} catch (FuseMountException | FuseNotSupportedException e) {
|
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] splitFlags(String str) {
|
|
||||||
List<String> flags = new ArrayList<>();
|
|
||||||
var matches = Iterators.peekingIterator(NON_WHITESPACE_OR_QUOTED.matcher(str).results().iterator());
|
|
||||||
while (matches.hasNext()) {
|
|
||||||
String flag = matches.next().group();
|
|
||||||
// check if flag is missing its argument:
|
|
||||||
if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(1) != null) { // next is "double quoted"
|
|
||||||
// next is "double quoted" and flag is missing its argument
|
|
||||||
flag += matches.next().group(1);
|
|
||||||
} else if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(2) != null) {
|
|
||||||
// next is 'single quoted' and flag is missing its argument
|
|
||||||
flag += matches.next().group(2);
|
|
||||||
}
|
|
||||||
flags.add(flag);
|
|
||||||
}
|
|
||||||
return flags.toArray(String[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reveal(Revealer revealer) throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.reveal(revealer::reveal);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsForcedUnmount() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unmountForced() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.unmountForced();
|
|
||||||
} catch (FuseMountException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
cleanupMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unmount() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.unmount();
|
|
||||||
} catch (FuseMountException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
cleanupMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return FuseVolume.isSupportedStatic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VolumeImpl getImplementationType() {
|
|
||||||
return VolumeImpl.FUSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MountPointRequirement getMountPointRequirement() {
|
|
||||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return MountPointRequirement.EMPTY_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.PARENT_NO_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
|
||||||
return FuseMountFactory.isFuseSupported();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
public class LockNotCompletedException extends Exception {
|
|
||||||
|
|
||||||
public LockNotCompletedException(String reason) {
|
|
||||||
super(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LockNotCompletedException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enumeration used to indicate the requirements for mounting a vault
|
|
||||||
* using a specific {@link Volume VolumeProvider}, e.g. {@link FuseVolume}.
|
|
||||||
*/
|
|
||||||
public enum MountPointRequirement {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Mountpoint needs to be a filesystem root and must not exist.
|
|
||||||
*/
|
|
||||||
UNUSED_ROOT_DIR,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No Mountpoint on the local filesystem required. (e.g. WebDAV)
|
|
||||||
*/
|
|
||||||
NONE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A parent folder is required, but the actual Mountpoint must not exist.
|
|
||||||
*/
|
|
||||||
PARENT_NO_MOUNT_POINT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A parent folder is required, but the actual Mountpoint may exist.
|
|
||||||
*/
|
|
||||||
PARENT_OPT_MOUNT_POINT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The actual Mountpoint must exist and must be empty.
|
|
||||||
*/
|
|
||||||
EMPTY_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,11 @@ package org.cryptomator.common.vaults;
|
|||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
import org.cryptomator.common.Constants;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.mount.WindowsDriveLetters;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.vaults.Volume.VolumeException;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||||
@@ -21,29 +23,42 @@ import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
|||||||
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.integrations.mount.Mount;
|
||||||
|
import org.cryptomator.integrations.mount.MountBuilder;
|
||||||
|
import org.cryptomator.integrations.mount.MountCapability;
|
||||||
|
import org.cryptomator.integrations.mount.MountFailedException;
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
import org.cryptomator.integrations.mount.Mountpoint;
|
||||||
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
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 javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
|
||||||
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;
|
||||||
|
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.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
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.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_WITHIN_EXISTING_PARENT;
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
public class Vault {
|
public class Vault {
|
||||||
|
|
||||||
@@ -51,15 +66,16 @@ public class Vault {
|
|||||||
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
||||||
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
|
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
private final Settings settings;
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final Provider<Volume> volumeProvider;
|
|
||||||
private final StringBinding defaultMountFlags;
|
|
||||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||||
private final VaultState state;
|
private final VaultState state;
|
||||||
private final ObjectProperty<Exception> lastKnownException;
|
private final ObjectProperty<Exception> lastKnownException;
|
||||||
|
private final ObservableValue<MountService> mountService;
|
||||||
|
private final ObservableValue<String> defaultMountFlags;
|
||||||
private final VaultConfigCache configCache;
|
private final VaultConfigCache configCache;
|
||||||
private final VaultStats stats;
|
private final VaultStats stats;
|
||||||
private final StringBinding displayName;
|
|
||||||
private final StringBinding displayablePath;
|
private final StringBinding displayablePath;
|
||||||
private final BooleanBinding locked;
|
private final BooleanBinding locked;
|
||||||
private final BooleanBinding processing;
|
private final BooleanBinding processing;
|
||||||
@@ -67,23 +83,24 @@ 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 StringBinding accessPoint;
|
private final ObjectBinding<Mountpoint> mountPoint;
|
||||||
private final BooleanBinding accessPointPresent;
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
private final BooleanProperty showingStats;
|
private final BooleanProperty showingStats;
|
||||||
|
|
||||||
private volatile Volume volume;
|
private AtomicReference<MountHandle> mountHandle = new AtomicReference<>(null);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
|
Vault(Environment env, Settings settings, VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, ObservableValue<MountService> mountService, VaultStats stats, WindowsDriveLetters windowsDriveLetters) {
|
||||||
|
this.env = env;
|
||||||
|
this.settings = settings;
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.configCache = configCache;
|
this.configCache = configCache;
|
||||||
this.volumeProvider = volumeProvider;
|
|
||||||
this.defaultMountFlags = defaultMountFlags;
|
|
||||||
this.cryptoFileSystem = cryptoFileSystem;
|
this.cryptoFileSystem = cryptoFileSystem;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.lastKnownException = lastKnownException;
|
this.lastKnownException = lastKnownException;
|
||||||
|
this.mountService = mountService;
|
||||||
|
this.defaultMountFlags = mountService.map(MountService::getDefaultMountFlags);
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
this.displayName = Bindings.createStringBinding(this::getDisplayName, vaultSettings.displayName());
|
|
||||||
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||||
@@ -91,8 +108,8 @@ public class Vault {
|
|||||||
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
|
this.missing = Bindings.createBooleanBinding(this::isMissing, 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.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
|
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||||
this.accessPointPresent = this.accessPoint.isNotEmpty();
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
this.showingStats = new SimpleBooleanProperty(false);
|
this.showingStats = new SimpleBooleanProperty(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +142,7 @@ public class Vault {
|
|||||||
.withKeyLoader(keyLoader) //
|
.withKeyLoader(keyLoader) //
|
||||||
.withFlags(flags) //
|
.withFlags(flags) //
|
||||||
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) //
|
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) //
|
||||||
|
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
|
||||||
.build();
|
.build();
|
||||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||||
}
|
}
|
||||||
@@ -141,7 +159,46 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
private MountBuilder prepareMount(Path cryptoRoot) throws IOException {
|
||||||
|
var mountProvider = mountService.getValue();
|
||||||
|
var builder = mountProvider.forFileSystem(cryptoRoot);
|
||||||
|
|
||||||
|
for (var capability : mountProvider.capabilities()) {
|
||||||
|
switch (capability) {
|
||||||
|
case FILE_SYSTEM_NAME -> builder.setFileSystemName("crypto");
|
||||||
|
case LOOPBACK_PORT -> builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings?
|
||||||
|
case LOOPBACK_HOST_NAME -> builder.setLoopbackHostName("cryptomator-vault"); //TODO: Read from system property
|
||||||
|
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
|
||||||
|
case MOUNT_FLAGS -> builder.setMountFlags(defaultMountFlags.getValue()); // TODO use custom mount flags (pre-populated with default mount flags)
|
||||||
|
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
|
||||||
|
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userChosenMountPoint = vaultSettings.getMountPoint();
|
||||||
|
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
|
||||||
|
if (userChosenMountPoint == null) {
|
||||||
|
if (mountProvider.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
|
||||||
|
// no need to set a mount point
|
||||||
|
} else if (mountProvider.hasCapability(MOUNT_AS_DRIVE_LETTER)) {
|
||||||
|
builder.setMountpoint(windowsDriveLetters.getFirstDesiredAvailable().orElseThrow());
|
||||||
|
} else if (mountProvider.hasCapability(MOUNT_WITHIN_EXISTING_PARENT)) {
|
||||||
|
Files.createDirectories(defaultMountPointBase);
|
||||||
|
builder.setMountpoint(defaultMountPointBase);
|
||||||
|
} else if (mountProvider.hasCapability(MOUNT_TO_EXISTING_DIR) ) {
|
||||||
|
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
|
||||||
|
Files.createDirectories(mountPoint);
|
||||||
|
builder.setMountpoint(mountPoint);
|
||||||
|
}
|
||||||
|
} else if (mountProvider.hasCapability(MOUNT_TO_EXISTING_DIR) || mountProvider.hasCapability(MOUNT_WITHIN_EXISTING_PARENT) || mountProvider.hasCapability(MOUNT_AS_DRIVE_LETTER)) {
|
||||||
|
// TODO: move the mount point away in case of MOUNT_WITHIN_EXISTING_PARENT?
|
||||||
|
builder.setMountpoint(userChosenMountPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, MountFailedException {
|
||||||
if (cryptoFileSystem.get() != null) {
|
if (cryptoFileSystem.get() != null) {
|
||||||
throw new IllegalStateException("Already unlocked.");
|
throw new IllegalStateException("Already unlocked.");
|
||||||
}
|
}
|
||||||
@@ -149,9 +206,10 @@ public class Vault {
|
|||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
cryptoFileSystem.set(fs);
|
cryptoFileSystem.set(fs);
|
||||||
volume = volumeProvider.get();
|
var rootPath = fs.getRootDirectories().iterator().next();
|
||||||
volume.mount(fs, getEffectiveMountFlags(), this::lockOnVolumeExit);
|
var supportsForcedUnmount = mountService.getValue().hasCapability(MountCapability.UNMOUNT_FORCED);
|
||||||
success = true;
|
var mountHandle = new MountHandle(prepareMount(rootPath).mount(), supportsForcedUnmount);
|
||||||
|
success = this.mountHandle.compareAndSet(null, mountHandle);
|
||||||
} finally {
|
} finally {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
destroyCryptoFileSystem();
|
destroyCryptoFileSystem();
|
||||||
@@ -159,37 +217,28 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lockOnVolumeExit(Throwable t) {
|
|
||||||
LOG.info("Unmounted vault '{}'", getDisplayName());
|
|
||||||
destroyCryptoFileSystem();
|
|
||||||
state.set(VaultState.Value.LOCKED);
|
|
||||||
if (t != null) {
|
|
||||||
LOG.warn("Unexpected unmount and lock of vault " + getDisplayName(), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void lock(boolean forced) throws VolumeException, LockNotCompletedException {
|
public synchronized void lock(boolean forced) throws UnmountFailedException, IOException {
|
||||||
//initiate unmount
|
var mountHandle = this.mountHandle.get();
|
||||||
if (forced && volume.supportsForcedUnmount()) {
|
if (mountHandle == null) {
|
||||||
volume.unmountForced();
|
//TODO: noop or InvalidStateException?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forced && mountHandle.supportsUnmountForced) {
|
||||||
|
mountHandle.mount.unmountForced();
|
||||||
} else {
|
} else {
|
||||||
volume.unmount();
|
mountHandle.mount.unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
//wait for lockOnVolumeExit to be executed
|
|
||||||
try {
|
try {
|
||||||
boolean locked = state.awaitState(VaultState.Value.LOCKED, 3000, TimeUnit.MILLISECONDS);
|
mountHandle.mount.close();
|
||||||
if (!locked) {
|
} finally {
|
||||||
throw new LockNotCompletedException("Locking of vault " + this.getDisplayName() + " still in progress.");
|
destroyCryptoFileSystem();
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new LockNotCompletedException(e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void reveal(Volume.Revealer vaultRevealer) throws VolumeException {
|
this.mountHandle.set(null);
|
||||||
volume.reveal(vaultRevealer);
|
LOG.info("Locked vault '{}'", getDisplayName());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
@@ -264,33 +313,21 @@ public class Vault {
|
|||||||
return state.get() == VaultState.Value.ERROR;
|
return state.get() == VaultState.Value.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding displayNameProperty() {
|
public ReadOnlyStringProperty displayNameProperty() {
|
||||||
return displayName;
|
return vaultSettings.displayName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return vaultSettings.displayName().get();
|
return vaultSettings.displayName().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding accessPointProperty() {
|
public ObjectBinding<Mountpoint> mountPointProperty() {
|
||||||
return accessPoint;
|
return mountPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessPoint() {
|
public Mountpoint getMountPoint() {
|
||||||
if (state.getValue() == VaultState.Value.UNLOCKED) {
|
var handle = mountHandle.get();
|
||||||
assert volume != null;
|
return handle == null ? null : handle.mount.getMountpoint();
|
||||||
return volume.getMountPoint().orElse(Path.of("")).toString();
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanBinding accessPointPresentProperty() {
|
|
||||||
return accessPointPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAccessPointPresent() {
|
|
||||||
return accessPointPresent.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding displayablePathProperty() {
|
public StringBinding displayablePathProperty() {
|
||||||
@@ -313,7 +350,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowingStats() {
|
public boolean isShowingStats() {
|
||||||
return accessPointPresent.get();
|
return mountHandle.get() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -342,18 +379,18 @@ public class Vault {
|
|||||||
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
|
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding defaultMountFlagsProperty() {
|
public ObservableValue<String> defaultMountFlagsProperty() {
|
||||||
return defaultMountFlags;
|
return defaultMountFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDefaultMountFlags() {
|
public String getDefaultMountFlags() {
|
||||||
return defaultMountFlags.get();
|
return defaultMountFlags.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEffectiveMountFlags() {
|
public String getEffectiveMountFlags() {
|
||||||
String mountFlags = vaultSettings.mountFlags().get();
|
String mountFlags = vaultSettings.mountFlags().get();
|
||||||
if (Strings.isNullOrEmpty(mountFlags)) {
|
if (Strings.isNullOrEmpty(mountFlags)) {
|
||||||
return getDefaultMountFlags();
|
return ""; //TODO: should the provider provide dem defaults??
|
||||||
} else {
|
} else {
|
||||||
return mountFlags;
|
return mountFlags;
|
||||||
}
|
}
|
||||||
@@ -371,10 +408,6 @@ public class Vault {
|
|||||||
return vaultSettings.getId();
|
return vaultSettings.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Volume> getVolume() {
|
|
||||||
return Optional.ofNullable(this.volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Hashcode / Equals
|
// Hashcode / Equals
|
||||||
// *******************************************************************************/
|
// *******************************************************************************/
|
||||||
@@ -393,7 +426,15 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: reactivate/ needed at all?
|
||||||
public boolean supportsForcedUnmount() {
|
public boolean supportsForcedUnmount() {
|
||||||
return volume.supportsForcedUnmount();
|
return volume.supportsForcedUnmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
private record MountHandle(Mount mount, boolean supportsUnmountForced) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,33 +8,23 @@ package org.cryptomator.common.vaults;
|
|||||||
import dagger.BindsInstance;
|
import dagger.BindsInstance;
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
import org.cryptomator.common.Nullable;
|
import org.cryptomator.common.Nullable;
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooserModule;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
@Subcomponent(modules = {VaultModule.class, MountPointChooserModule.class})
|
@Subcomponent(modules = {VaultModule.class})
|
||||||
public interface VaultComponent {
|
public interface VaultComponent {
|
||||||
|
|
||||||
Vault vault();
|
Vault vault();
|
||||||
|
|
||||||
@Subcomponent.Builder
|
@Subcomponent.Factory
|
||||||
interface Builder {
|
interface Factory {
|
||||||
|
|
||||||
@BindsInstance
|
VaultComponent create(@BindsInstance VaultSettings vaultSettings, //
|
||||||
Builder vaultSettings(VaultSettings vaultSettings);
|
@BindsInstance VaultConfigCache configCache, //
|
||||||
|
@BindsInstance VaultState.Value vaultState, //
|
||||||
|
@BindsInstance @Nullable @Named("lastKnownException") Exception initialErrorCause);
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
Builder vaultConfigCache(VaultConfigCache configCache);
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
Builder initialVaultState(VaultState.Value vaultState);
|
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
Builder initialErrorCause(@Nullable @Named("lastKnownException") Exception initialErrorCause);
|
|
||||||
|
|
||||||
VaultComponent build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -38,15 +38,15 @@ public class VaultListManager {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultListManager.class);
|
private static final Logger LOG = LoggerFactory.getLogger(VaultListManager.class);
|
||||||
|
|
||||||
private final AutoLocker autoLocker;
|
private final AutoLocker autoLocker;
|
||||||
private final VaultComponent.Builder vaultComponentBuilder;
|
private final VaultComponent.Factory vaultComponentFactory;
|
||||||
private final ObservableList<Vault> vaultList;
|
private final ObservableList<Vault> vaultList;
|
||||||
private final String defaultVaultName;
|
private final String defaultVaultName;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultListManager(ObservableList<Vault> vaultList, AutoLocker autoLocker, VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) {
|
public VaultListManager(ObservableList<Vault> vaultList, AutoLocker autoLocker, VaultComponent.Factory vaultComponentFactory, ResourceBundle resourceBundle, Settings settings) {
|
||||||
this.vaultList = vaultList;
|
this.vaultList = vaultList;
|
||||||
this.autoLocker = autoLocker;
|
this.autoLocker = autoLocker;
|
||||||
this.vaultComponentBuilder = vaultComponentBuilder;
|
this.vaultComponentFactory = vaultComponentFactory;
|
||||||
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
|
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
|
||||||
|
|
||||||
addAll(settings.getDirectories());
|
addAll(settings.getDirectories());
|
||||||
@@ -93,21 +93,17 @@ public class VaultListManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Vault create(VaultSettings vaultSettings) {
|
private Vault create(VaultSettings vaultSettings) {
|
||||||
VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings);
|
var wrapper = new VaultConfigCache(vaultSettings);
|
||||||
try {
|
try {
|
||||||
VaultState.Value vaultState = determineVaultState(vaultSettings.path().get());
|
var vaultState = determineVaultState(vaultSettings.path().get());
|
||||||
VaultConfigCache wrapper = new VaultConfigCache(vaultSettings);
|
|
||||||
compBuilder.vaultConfigCache(wrapper); //first set the wrapper in the builder, THEN try to load config
|
|
||||||
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
||||||
wrapper.reloadConfig();
|
wrapper.reloadConfig();
|
||||||
}
|
}
|
||||||
compBuilder.initialVaultState(vaultState);
|
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);
|
||||||
compBuilder.initialVaultState(ERROR);
|
return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault();
|
||||||
compBuilder.initialErrorCause(e);
|
|
||||||
}
|
}
|
||||||
return compBuilder.build().vault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultState.Value redetermineVaultState(Vault vault) {
|
public static VaultState.Value redetermineVaultState(Vault vault) {
|
||||||
|
|||||||
@@ -7,26 +7,14 @@ package org.cryptomator.common.vaults;
|
|||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.Nullable;
|
import org.cryptomator.common.Nullable;
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.StringBinding;
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -47,130 +35,4 @@ public class VaultModule {
|
|||||||
return new SimpleObjectProperty<>(initialErrorCause);
|
return new SimpleObjectProperty<>(initialErrorCause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
|
|
||||||
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
|
||||||
if (VolumeImpl.DOKANY == preferredImpl && dokanyVolume.isSupported()) {
|
|
||||||
return dokanyVolume;
|
|
||||||
} else if (VolumeImpl.FUSE == preferredImpl && fuseVolume.isSupported()) {
|
|
||||||
return fuseVolume;
|
|
||||||
} else {
|
|
||||||
if (VolumeImpl.WEBDAV != preferredImpl) {
|
|
||||||
LOG.warn("Using WebDAV, because {} is not supported.", preferredImpl.getDisplayName());
|
|
||||||
}
|
|
||||||
assert webDavVolume.isSupported();
|
|
||||||
return webDavVolume;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@PerVault
|
|
||||||
@DefaultMountFlags
|
|
||||||
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
|
||||||
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
|
||||||
StringBinding mountName = vaultSettings.mountName();
|
|
||||||
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
|
||||||
|
|
||||||
return Bindings.createStringBinding(() -> {
|
|
||||||
VolumeImpl v = preferredVolumeImpl.get();
|
|
||||||
if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_MAC) {
|
|
||||||
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
|
||||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
|
||||||
return getLinuxFuseDefaultMountFlags(readOnly);
|
|
||||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return getWindowsFuseDefaultMountFlags(mountName, readOnly);
|
|
||||||
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return getDokanyDefaultMountFlags(readOnly);
|
|
||||||
} else {
|
|
||||||
return "--flags-supported-on-FUSE-or-DOKANY-only";
|
|
||||||
}
|
|
||||||
}, mountName, readOnly, preferredVolumeImpl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
|
||||||
private String getMacFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
|
||||||
assert SystemUtils.IS_OS_MAC_OSX;
|
|
||||||
StringBuilder flags = new StringBuilder();
|
|
||||||
if (readOnly.get()) {
|
|
||||||
flags.append(" -ordonly");
|
|
||||||
}
|
|
||||||
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
|
|
||||||
flags.append(" -oatomic_o_trunc");
|
|
||||||
flags.append(" -oauto_xattr");
|
|
||||||
flags.append(" -oauto_cache");
|
|
||||||
flags.append(" -onoappledouble"); // vastly impacts performance for some reason...
|
|
||||||
flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc
|
|
||||||
|
|
||||||
try {
|
|
||||||
Path userHome = Paths.get(System.getProperty("user.home"));
|
|
||||||
int uid = (int) Files.getAttribute(userHome, "unix:uid");
|
|
||||||
int gid = (int) Files.getAttribute(userHome, "unix:gid");
|
|
||||||
flags.append(" -ouid=").append(uid);
|
|
||||||
flags.append(" -ogid=").append(gid);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Could not read uid/gid from USER_HOME", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags.toString().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://manpages.debian.org/testing/fuse/mount.fuse.8.en.html
|
|
||||||
private String getLinuxFuseDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
|
||||||
assert SystemUtils.IS_OS_LINUX;
|
|
||||||
StringBuilder flags = new StringBuilder();
|
|
||||||
if (readOnly.get()) {
|
|
||||||
flags.append(" -oro");
|
|
||||||
}
|
|
||||||
flags.append(" -oauto_unmount");
|
|
||||||
|
|
||||||
try {
|
|
||||||
Path userHome = Paths.get(System.getProperty("user.home"));
|
|
||||||
int uid = (int) Files.getAttribute(userHome, "unix:uid");
|
|
||||||
int gid = (int) Files.getAttribute(userHome, "unix:gid");
|
|
||||||
flags.append(" -ouid=").append(uid);
|
|
||||||
flags.append(" -ogid=").append(gid);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Could not read uid/gid from USER_HOME", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags.toString().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse_main.c#L53-L62 for syntax guide
|
|
||||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse.c#L295-L319 for options (-o <...>)
|
|
||||||
// see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were chosen
|
|
||||||
private String getWindowsFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
|
||||||
assert SystemUtils.IS_OS_WINDOWS;
|
|
||||||
StringBuilder flags = new StringBuilder();
|
|
||||||
|
|
||||||
//WinFSP has no explicit "readonly"-option, nut not setting the group/user-id has the same effect, tho.
|
|
||||||
//So for the time being not setting them is the way to go...
|
|
||||||
//See: https://github.com/billziss-gh/winfsp/issues/319
|
|
||||||
if (!readOnly.get()) {
|
|
||||||
flags.append(" -ouid=-1");
|
|
||||||
flags.append(" -ogid=11");
|
|
||||||
}
|
|
||||||
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
|
|
||||||
//Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
|
|
||||||
//but the option exists. Let's keep this here in case we need it.
|
|
||||||
// flags.append(" -oThreadCount=").append(5);
|
|
||||||
|
|
||||||
return flags.toString().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34
|
|
||||||
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
|
||||||
assert SystemUtils.IS_OS_WINDOWS;
|
|
||||||
StringBuilder flags = new StringBuilder();
|
|
||||||
flags.append(" --options CURRENT_SESSION");
|
|
||||||
if (readOnly.get()) {
|
|
||||||
flags.append(",WRITE_PROTECTION");
|
|
||||||
}
|
|
||||||
flags.append(" --thread-count 5");
|
|
||||||
flags.append(" --timeout 10000");
|
|
||||||
flags.append(" --allocation-unit-size 4096");
|
|
||||||
flags.append(" --sector-size 4096");
|
|
||||||
return flags.toString().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
|
|||||||
if (success) {
|
if (success) {
|
||||||
fireValueChangedEvent();
|
fireValueChangedEvent();
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("Failed transiting into state {}: Expected state was not{}.", fromState, toState);
|
LOG.debug("Failed transiting into state {}: Expected state was not {}.", fromState, toState);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ public class VaultStats {
|
|||||||
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
||||||
private final LongProperty filesRead = new SimpleLongProperty();
|
private final LongProperty filesRead = new SimpleLongProperty();
|
||||||
private final LongProperty filesWritten = new SimpleLongProperty();
|
private final LongProperty filesWritten = new SimpleLongProperty();
|
||||||
|
private final LongProperty filesAccessed = new SimpleLongProperty();
|
||||||
|
private final LongProperty totalFilesAccessed = new SimpleLongProperty();
|
||||||
private final ObjectProperty<Instant> lastActivity = new SimpleObjectProperty<>();
|
private final ObjectProperty<Instant> lastActivity = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -82,6 +84,8 @@ public class VaultStats {
|
|||||||
var oldAccessCount = filesRead.get() + filesWritten.get();
|
var oldAccessCount = filesRead.get() + filesWritten.get();
|
||||||
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
||||||
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
||||||
|
filesAccessed.set(stats.map(CryptoFileSystemStats::pollAmountOfAccesses).orElse(0L));
|
||||||
|
totalFilesAccessed.set(stats.map(CryptoFileSystemStats::pollTotalAmountOfAccesses).orElse(0L));
|
||||||
var newAccessCount = filesRead.get() + filesWritten.get();
|
var newAccessCount = filesRead.get() + filesWritten.get();
|
||||||
|
|
||||||
// check for any I/O activity
|
// check for any I/O activity
|
||||||
@@ -188,6 +192,19 @@ public class VaultStats {
|
|||||||
|
|
||||||
public long getFilesWritten() {return filesWritten.get();}
|
public long getFilesWritten() {return filesWritten.get();}
|
||||||
|
|
||||||
|
public LongProperty filesAccessed() {
|
||||||
|
return filesAccessed;}
|
||||||
|
|
||||||
|
public long getFilesAccessed() {return filesAccessed.get();}
|
||||||
|
|
||||||
|
public LongProperty totalFilesAccessed(){
|
||||||
|
return totalFilesAccessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalFilesAccessed(){
|
||||||
|
return totalFilesAccessed.get();
|
||||||
|
}
|
||||||
|
|
||||||
public ObjectProperty<Instant> lastActivityProperty() {
|
public ObjectProperty<Instant> lastActivityProperty() {
|
||||||
return lastActivity;
|
return lastActivity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a Volume and uses it to mount an unlocked vault
|
|
||||||
*/
|
|
||||||
public interface Volume {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks in constant time whether this volume type is supported on the system running Cryptomator.
|
|
||||||
*
|
|
||||||
* @return true if this volume can be mounted
|
|
||||||
*/
|
|
||||||
boolean isSupported();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the corresponding enum type of the {@link VolumeImpl volume implementation ("VolumeImpl")} that is implemented by this Volume.
|
|
||||||
*
|
|
||||||
* @return the type of implementation as defined by the {@link VolumeImpl VolumeImpl enum}
|
|
||||||
*/
|
|
||||||
VolumeImpl getImplementationType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fs
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws IOException, VolumeException, InvalidMountPointException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reveals the mounted volume.
|
|
||||||
* <p>
|
|
||||||
* The given {@code revealer} might be used to do it, but not necessarily.
|
|
||||||
*
|
|
||||||
* @param revealer An object capable of revealing the location of the mounted vault to view the content (e.g. in the default file browser).
|
|
||||||
* @throws VolumeException
|
|
||||||
*/
|
|
||||||
void reveal(Revealer revealer) throws VolumeException;
|
|
||||||
|
|
||||||
void unmount() throws VolumeException;
|
|
||||||
|
|
||||||
Optional<Path> getMountPoint();
|
|
||||||
|
|
||||||
MountPointRequirement getMountPointRequirement();
|
|
||||||
|
|
||||||
// optional forced unmounting:
|
|
||||||
|
|
||||||
default boolean supportsForcedUnmount() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
default void unmountForced() throws VolumeException {
|
|
||||||
throw new VolumeException("Operation not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static VolumeImpl[] getCurrentSupportedAdapters() {
|
|
||||||
return Stream.of(VolumeImpl.values()).filter(impl -> switch (impl) {
|
|
||||||
case WEBDAV -> WebDavVolume.isSupportedStatic();
|
|
||||||
case DOKANY -> DokanyVolume.isSupportedStatic();
|
|
||||||
case FUSE -> FuseVolume.isSupportedStatic();
|
|
||||||
}).toArray(VolumeImpl[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown when a volume-specific command such as mount/unmount/reveal failed.
|
|
||||||
*/
|
|
||||||
class VolumeException extends Exception {
|
|
||||||
|
|
||||||
public VolumeException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VolumeException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VolumeException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides and unifies the different Revealer implementations in the different nio-adapters.
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
interface Revealer {
|
|
||||||
|
|
||||||
void reveal(Path p) throws VolumeException;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
|
||||||
import org.cryptomator.frontend.webdav.mount.MountParams;
|
|
||||||
import org.cryptomator.frontend.webdav.mount.Mounter;
|
|
||||||
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Provider;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class WebDavVolume implements Volume {
|
|
||||||
|
|
||||||
private static final String LOCALHOST_ALIAS = "cryptomator-vault";
|
|
||||||
|
|
||||||
private final Provider<WebDavServer> serverProvider;
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
private final Settings settings;
|
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
|
||||||
|
|
||||||
private WebDavServer server;
|
|
||||||
private WebDavServletController servlet;
|
|
||||||
private Mounter.Mount mount;
|
|
||||||
private Consumer<Throwable> onExitAction;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
|
|
||||||
this.serverProvider = serverProvider;
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
this.settings = settings;
|
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
|
|
||||||
startServlet(fs);
|
|
||||||
mountServlet();
|
|
||||||
this.onExitAction = onExitAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startServlet(CryptoFileSystem fs) {
|
|
||||||
if (server == null) {
|
|
||||||
server = serverProvider.get();
|
|
||||||
}
|
|
||||||
if (!server.isRunning()) {
|
|
||||||
server.start();
|
|
||||||
}
|
|
||||||
CharMatcher acceptable = CharMatcher.inRange('0', '9').or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.inRange('a', 'z'));
|
|
||||||
String urlConformMountName = acceptable.negate().collapseFrom(vaultSettings.mountName().get(), '_');
|
|
||||||
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + urlConformMountName);
|
|
||||||
servlet.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mountServlet() throws VolumeException {
|
|
||||||
if (servlet == null) {
|
|
||||||
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specific one or there is no free.
|
|
||||||
Supplier<String> driveLetterSupplier;
|
|
||||||
if (System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
|
|
||||||
driveLetterSupplier = () -> windowsDriveLetters.getDesiredAvailableDriveLetter().orElse(null);
|
|
||||||
} else {
|
|
||||||
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
MountParams mountParams = MountParams.create() //
|
|
||||||
.withWindowsDriveLetter(driveLetterSupplier.get()) //
|
|
||||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
|
|
||||||
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
|
||||||
.build();
|
|
||||||
try {
|
|
||||||
this.mount = servlet.mount(mountParams); // might block this thread for a while
|
|
||||||
} catch (Mounter.CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reveal(Revealer revealer) throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.reveal(revealer::reveal);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unmount() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.unmount();
|
|
||||||
} catch (Mounter.CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
onExitAction.accept(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unmountForced() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.forced().orElseThrow(IllegalStateException::new).unmount();
|
|
||||||
} catch (Mounter.CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
onExitAction.accept(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> getMountPoint() {
|
|
||||||
return mount.getMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MountPointRequirement getMountPointRequirement() {
|
|
||||||
return MountPointRequirement.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getLocalhostAliasOrNull() {
|
|
||||||
try {
|
|
||||||
InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS);
|
|
||||||
if (alias.getHostAddress().equals("127.0.0.1")) {
|
|
||||||
return LOCALHOST_ALIAS;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanup() {
|
|
||||||
if (servlet != null) {
|
|
||||||
servlet.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return WebDavVolume.isSupportedStatic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VolumeImpl getImplementationType() {
|
|
||||||
return VolumeImpl.WEBDAV;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsForcedUnmount() {
|
|
||||||
return mount != null && mount.forced().isPresent();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public final class WindowsDriveLetters {
|
|
||||||
|
|
||||||
private static final Set<String> A_TO_Z;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
|
|
||||||
A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public WindowsDriveLetters() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getAllDriveLetters() {
|
|
||||||
return A_TO_Z;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getOccupiedDriveLetters() {
|
|
||||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return Set.of();
|
|
||||||
} else {
|
|
||||||
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
|
||||||
return StreamSupport.stream(rootDirs.spliterator(), false).map(p -> p.toString().substring(0, 1)).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getAvailableDriveLetters() {
|
|
||||||
return Sets.difference(getAllDriveLetters(), getOccupiedDriveLetters());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getAvailableDriveLetter() {
|
|
||||||
return getAvailableDriveLetters().stream().findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Path> getAvailableDriveLetterPath() {
|
|
||||||
return getAvailableDriveLetter().map(this::toPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skips A and B and only returns them if all other are occupied.
|
|
||||||
*
|
|
||||||
* @return an Optional containing either the letter of a free drive letter or empty, if none is available
|
|
||||||
*/
|
|
||||||
public Optional<String> getDesiredAvailableDriveLetter() {
|
|
||||||
var availableDriveLetters = getAvailableDriveLetters();
|
|
||||||
var optString = availableDriveLetters.stream().filter(s -> !(s.equals("A") || s.equals("B"))).findFirst();
|
|
||||||
return optString.or(() -> availableDriveLetters.stream().findFirst());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skips A and B and only returns them if all other are occupied.
|
|
||||||
*
|
|
||||||
* @return an Optional containing either the path to a free drive letter or empty, if none is available
|
|
||||||
*/
|
|
||||||
public Optional<Path> getDesiredAvailableDriveLetterPath() {
|
|
||||||
return getDesiredAvailableDriveLetter().map(this::toPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path toPath(String driveLetter) {
|
|
||||||
return Path.of(driveLetter + ":\\");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ import org.cryptomator.common.Environment;
|
|||||||
import org.cryptomator.common.ShutdownHook;
|
import org.cryptomator.common.ShutdownHook;
|
||||||
import org.cryptomator.ipc.IpcCommunicator;
|
import org.cryptomator.ipc.IpcCommunicator;
|
||||||
import org.cryptomator.logging.DebugMode;
|
import org.cryptomator.logging.DebugMode;
|
||||||
import org.cryptomator.logging.LoggerConfiguration;
|
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationComponent;
|
import org.cryptomator.ui.fxapp.FxApplicationComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -35,7 +34,6 @@ public class Cryptomator {
|
|||||||
private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
|
private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
|
||||||
|
|
||||||
private final LoggerConfiguration logConfig;
|
|
||||||
private final DebugMode debugMode;
|
private final DebugMode debugMode;
|
||||||
private final SupportedLanguages supportedLanguages;
|
private final SupportedLanguages supportedLanguages;
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
@@ -43,8 +41,7 @@ public class Cryptomator {
|
|||||||
private final ShutdownHook shutdownHook;
|
private final ShutdownHook shutdownHook;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook) {
|
Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook) {
|
||||||
this.logConfig = logConfig;
|
|
||||||
this.debugMode = debugMode;
|
this.debugMode = debugMode;
|
||||||
this.supportedLanguages = supportedLanguages;
|
this.supportedLanguages = supportedLanguages;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
@@ -79,9 +76,9 @@ public class Cryptomator {
|
|||||||
* @return Nonzero exit code in case of an error.
|
* @return Nonzero exit code in case of an error.
|
||||||
*/
|
*/
|
||||||
private int run(String[] args) {
|
private int run(String[] args) {
|
||||||
logConfig.init();
|
env.log();
|
||||||
LOG.debug("Dagger graph initialized after {}ms", System.currentTimeMillis() - STARTUP_TIME);
|
LOG.debug("Dagger graph initialized after {}ms", System.currentTimeMillis() - STARTUP_TIME);
|
||||||
LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion(), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||||
debugMode.initialize();
|
debugMode.initialize();
|
||||||
supportedLanguages.applyPreferred();
|
supportedLanguages.applyPreferred();
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ package org.cryptomator.launcher;
|
|||||||
import dagger.BindsInstance;
|
import dagger.BindsInstance;
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
import org.cryptomator.common.CommonsModule;
|
import org.cryptomator.common.CommonsModule;
|
||||||
import org.cryptomator.logging.LoggerModule;
|
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationComponent;
|
import org.cryptomator.ui.fxapp.FxApplicationComponent;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
|
@Component(modules = {CryptomatorModule.class, CommonsModule.class})
|
||||||
public interface CryptomatorComponent {
|
public interface CryptomatorComponent {
|
||||||
|
|
||||||
Cryptomator application();
|
Cryptomator application();
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public class SupportedLanguages {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
|
||||||
// these are BCP 47 language codes, not ISO. Note the "-" instead of the "_":
|
// these are BCP 47 language codes, not ISO. Note the "-" instead of the "_":
|
||||||
public static final List<String> LANGUAGAE_TAGS = List.of("en", "ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", //
|
public static final List<String> LANGUAGAE_TAGS = List.of("en", "ar", "be", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "fil", "fa", "fr", "gl", "he", //
|
||||||
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", //
|
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "si", "sk", "sr", "sr-Latn", "sv", "sw", //
|
||||||
"sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW");
|
"ta", "te", "th", "tr", "uk", "vi", "zh", "zh-HK", "zh-TW");
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String preferredLanguage;
|
private final String preferredLanguage;
|
||||||
|
|||||||
@@ -5,29 +5,24 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.logging;
|
package org.cryptomator.logging;
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
|
||||||
import ch.qos.logback.classic.Logger;
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class DebugMode {
|
public class DebugMode {
|
||||||
|
|
||||||
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DebugMode.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DebugMode.class);
|
||||||
|
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final LoggerContext context;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DebugMode(Settings settings, LoggerContext context) {
|
public DebugMode(Settings settings) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.context = context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
@@ -40,19 +35,13 @@ public class DebugMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setLogLevels(boolean debugMode) {
|
private void setLogLevels(boolean debugMode) {
|
||||||
|
var configurator = LogbackConfiguratorFactory.provider();
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
setLogLevels(LoggerModule.DEBUG_LOG_LEVELS);
|
configurator.setLogLevels(LogbackConfigurator.DEBUG_LOG_LEVELS);
|
||||||
LOG.debug("Debug mode enabled");
|
LOG.debug("Debug mode enabled");
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("Debug mode disabled");
|
LOG.debug("Debug mode disabled");
|
||||||
setLogLevels(LoggerModule.DEFAULT_LOG_LEVELS);
|
configurator.setLogLevels(LogbackConfigurator.DEFAULT_LOG_LEVELS);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setLogLevels(Map<String, Level> logLevels) {
|
|
||||||
for (Map.Entry<String, Level> loglevel : logLevels.entrySet()) {
|
|
||||||
Logger logger = context.getLogger(loglevel.getKey());
|
|
||||||
logger.setLevel(loglevel.getValue());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package org.cryptomator.logging;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.Logger;
|
||||||
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
|
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||||
|
import ch.qos.logback.classic.spi.Configurator;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.Appender;
|
||||||
|
import ch.qos.logback.core.ConsoleAppender;
|
||||||
|
import ch.qos.logback.core.FileAppender;
|
||||||
|
import ch.qos.logback.core.helpers.NOPAppender;
|
||||||
|
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
|
||||||
|
import ch.qos.logback.core.rolling.RollingFileAppender;
|
||||||
|
import ch.qos.logback.core.spi.ContextAwareBase;
|
||||||
|
import ch.qos.logback.core.util.FileSize;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class LogbackConfigurator extends ContextAwareBase implements Configurator {
|
||||||
|
|
||||||
|
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
|
||||||
|
private static final String UPGRADE_FILENAME = "upgrade.log";
|
||||||
|
private static final String LOGFILE_NAME = "cryptomator0.log";
|
||||||
|
private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log";
|
||||||
|
private static final int LOGFILE_ROLLING_MIN = 1;
|
||||||
|
private static final int LOGFILE_ROLLING_MAX = 9;
|
||||||
|
private static final String LOG_MAX_SIZE = "100mb";
|
||||||
|
|
||||||
|
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
|
||||||
|
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
||||||
|
"org.cryptomator", Level.INFO //
|
||||||
|
);
|
||||||
|
static final Map<String, Level> DEBUG_LOG_LEVELS = Map.of( //
|
||||||
|
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
||||||
|
"org.cryptomator", Level.TRACE //
|
||||||
|
);
|
||||||
|
|
||||||
|
LogbackConfigurator() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust the log levels
|
||||||
|
*
|
||||||
|
* @param logLevels new log levels to use
|
||||||
|
*/
|
||||||
|
void setLogLevels(Map<String, Level> logLevels) {
|
||||||
|
if (context instanceof LoggerContext lc) {
|
||||||
|
for (var loglevel : logLevels.entrySet()) {
|
||||||
|
Logger logger = lc.getLogger(loglevel.getKey());
|
||||||
|
logger.setLevel(loglevel.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutionStatus configure(LoggerContext context) {
|
||||||
|
var useCustomCfg = Environment.getInstance().useCustomLogbackConfig();
|
||||||
|
var logDir = Environment.getInstance().getLogDir().orElse(null);
|
||||||
|
|
||||||
|
if (useCustomCfg) {
|
||||||
|
addInfo("Using external logback configuration file.");
|
||||||
|
} else {
|
||||||
|
// configure appenders:
|
||||||
|
var stdout = stdOutAppender(context);
|
||||||
|
var noop = noopAppender(context);
|
||||||
|
var file = logDir == null ? noop : fileAppender(context, logDir);
|
||||||
|
var upgrade = logDir == null ? noop : upgradeAppender(context, logDir);
|
||||||
|
|
||||||
|
// configure loggers:
|
||||||
|
for (var loglevel : DEFAULT_LOG_LEVELS.entrySet()) {
|
||||||
|
Logger logger = context.getLogger(loglevel.getKey());
|
||||||
|
logger.setLevel(loglevel.getValue());
|
||||||
|
logger.setAdditive(false);
|
||||||
|
logger.addAppender(stdout);
|
||||||
|
logger.addAppender(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure upgrade logger:
|
||||||
|
Logger upgrades = context.getLogger("org.cryptomator.cryptofs.migration");
|
||||||
|
upgrades.setLevel(Level.DEBUG);
|
||||||
|
upgrades.addAppender(stdout);
|
||||||
|
upgrades.addAppender(upgrade);
|
||||||
|
upgrades.addAppender(file);
|
||||||
|
upgrades.setAdditive(false);
|
||||||
|
}
|
||||||
|
return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Appender<ILoggingEvent> noopAppender(LoggerContext context) {
|
||||||
|
var appender = new NOPAppender<ILoggingEvent>();
|
||||||
|
appender.setContext(context);
|
||||||
|
return appender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Appender<ILoggingEvent> stdOutAppender(LoggerContext context) {
|
||||||
|
var appender = new ConsoleAppender<ILoggingEvent>();
|
||||||
|
appender.setContext(context);
|
||||||
|
appender.setName("STDOUT");
|
||||||
|
appender.setEncoder(encoder(context));
|
||||||
|
appender.start();
|
||||||
|
return appender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Appender<ILoggingEvent> upgradeAppender(LoggerContext context, Path logDir) {
|
||||||
|
var appender = new FileAppender<ILoggingEvent>();
|
||||||
|
appender.setContext(context);
|
||||||
|
appender.setName("UPGRADE");
|
||||||
|
appender.setFile(logDir.resolve(UPGRADE_FILENAME).toString());
|
||||||
|
appender.setEncoder(encoder(context));
|
||||||
|
appender.start();
|
||||||
|
return appender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Appender<ILoggingEvent> fileAppender(LoggerContext context, Path logDir) {
|
||||||
|
var appender = new RollingFileAppender<ILoggingEvent>();
|
||||||
|
appender.setContext(context);
|
||||||
|
appender.setName("FILE");
|
||||||
|
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
|
||||||
|
appender.setEncoder(encoder(context));
|
||||||
|
var triggeringPolicy = new LaunchAndSizeBasedTriggeringPolicy<ILoggingEvent>(FileSize.valueOf(LOG_MAX_SIZE));
|
||||||
|
triggeringPolicy.setContext(context);
|
||||||
|
triggeringPolicy.start();
|
||||||
|
appender.setTriggeringPolicy(triggeringPolicy);
|
||||||
|
var rollingPolicy = new FixedWindowRollingPolicy();
|
||||||
|
rollingPolicy.setContext(context);
|
||||||
|
rollingPolicy.setFileNamePattern(logDir.resolve(LOGFILE_ROLLING_PATTERN).toString());
|
||||||
|
rollingPolicy.setMinIndex(LOGFILE_ROLLING_MIN);
|
||||||
|
rollingPolicy.setMaxIndex(LOGFILE_ROLLING_MAX);
|
||||||
|
rollingPolicy.setParent(appender);
|
||||||
|
rollingPolicy.start();
|
||||||
|
appender.setRollingPolicy(rollingPolicy);
|
||||||
|
appender.start();
|
||||||
|
return appender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PatternLayoutEncoder encoder(LoggerContext context) {
|
||||||
|
var encoder = new PatternLayoutEncoder();
|
||||||
|
encoder.setContext(context);
|
||||||
|
encoder.setPattern(LOG_PATTERN);
|
||||||
|
encoder.start();
|
||||||
|
return encoder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.cryptomator.logging;
|
||||||
|
|
||||||
|
public class LogbackConfiguratorFactory {
|
||||||
|
|
||||||
|
public static LogbackConfigurator provider() {
|
||||||
|
final class Holder {
|
||||||
|
private static final LogbackConfigurator INSTANCE = new LogbackConfigurator();
|
||||||
|
}
|
||||||
|
return Holder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package org.cryptomator.logging;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
|
||||||
import ch.qos.logback.classic.Logger;
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
||||||
import ch.qos.logback.core.Appender;
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.ShutdownHook;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class LoggerConfiguration {
|
|
||||||
|
|
||||||
private final LoggerContext context;
|
|
||||||
private final Environment environment;
|
|
||||||
private final Appender<ILoggingEvent> stdout;
|
|
||||||
private final Appender<ILoggingEvent> upgrade;
|
|
||||||
private final Appender<ILoggingEvent> file;
|
|
||||||
private final ShutdownHook shutdownHook;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
LoggerConfiguration(LoggerContext context, //
|
|
||||||
Environment environment, //
|
|
||||||
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
|
|
||||||
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
|
|
||||||
@Named("fileAppender") Appender<ILoggingEvent> file, //
|
|
||||||
ShutdownHook shutdownHook) {
|
|
||||||
this.context = context;
|
|
||||||
this.environment = environment;
|
|
||||||
this.stdout = stdout;
|
|
||||||
this.upgrade = upgrade;
|
|
||||||
this.file = file;
|
|
||||||
this.shutdownHook = shutdownHook;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
if (environment.useCustomLogbackConfig()) {
|
|
||||||
Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
|
|
||||||
root.info("Using external logback configuration file.");
|
|
||||||
} else {
|
|
||||||
context.reset();
|
|
||||||
|
|
||||||
// configure loggers:
|
|
||||||
for (Map.Entry<String, Level> loglevel : LoggerModule.DEFAULT_LOG_LEVELS.entrySet()) {
|
|
||||||
Logger logger = context.getLogger(loglevel.getKey());
|
|
||||||
logger.setLevel(loglevel.getValue());
|
|
||||||
logger.setAdditive(false);
|
|
||||||
logger.addAppender(stdout);
|
|
||||||
logger.addAppender(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure upgrade logger:
|
|
||||||
Logger upgrades = context.getLogger("org.cryptomator.cryptofs.migration");
|
|
||||||
upgrades.setLevel(Level.DEBUG);
|
|
||||||
upgrades.addAppender(stdout);
|
|
||||||
upgrades.addAppender(upgrade);
|
|
||||||
upgrades.addAppender(file);
|
|
||||||
upgrades.setAdditive(false);
|
|
||||||
|
|
||||||
// add shutdown hook
|
|
||||||
shutdownHook.runOnShutdown(ShutdownHook.PRIO_LAST, context::stop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
package org.cryptomator.logging;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
|
||||||
import ch.qos.logback.classic.Logger;
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
|
||||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
||||||
import ch.qos.logback.core.Appender;
|
|
||||||
import ch.qos.logback.core.ConsoleAppender;
|
|
||||||
import ch.qos.logback.core.FileAppender;
|
|
||||||
import ch.qos.logback.core.helpers.NOPAppender;
|
|
||||||
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
|
|
||||||
import ch.qos.logback.core.rolling.RollingFileAppender;
|
|
||||||
import ch.qos.logback.core.util.FileSize;
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.slf4j.ILoggerFactory;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Module
|
|
||||||
public class LoggerModule {
|
|
||||||
|
|
||||||
private static final String UPGRADE_FILENAME = "upgrade.log";
|
|
||||||
private static final String LOGFILE_NAME = "cryptomator0.log";
|
|
||||||
private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log";
|
|
||||||
private static final int LOGFILE_ROLLING_MIN = 1;
|
|
||||||
private static final int LOGFILE_ROLLING_MAX = 9;
|
|
||||||
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
|
|
||||||
private static final String LOG_MAX_SIZE = "100mb";
|
|
||||||
|
|
||||||
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
|
|
||||||
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
|
||||||
"org.cryptomator", Level.INFO //
|
|
||||||
);
|
|
||||||
static final Map<String, Level> DEBUG_LOG_LEVELS = Map.of( //
|
|
||||||
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
|
||||||
"org.cryptomator", Level.TRACE //
|
|
||||||
);
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
static LoggerContext provideLoggerContext() {
|
|
||||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
|
||||||
if (loggerFactory instanceof LoggerContext context) {
|
|
||||||
return context;
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("SLF4J not bound to Logback.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
static PatternLayoutEncoder provideLayoutEncoder(LoggerContext context) {
|
|
||||||
PatternLayoutEncoder ple = new PatternLayoutEncoder();
|
|
||||||
ple.setPattern(LOG_PATTERN);
|
|
||||||
ple.setContext(context);
|
|
||||||
ple.start();
|
|
||||||
return ple;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@Named("stdoutAppender")
|
|
||||||
static Appender<ILoggingEvent> provideStdoutAppender(LoggerContext context, PatternLayoutEncoder encoder) {
|
|
||||||
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
|
|
||||||
appender.setContext(context);
|
|
||||||
appender.setEncoder(encoder);
|
|
||||||
appender.start();
|
|
||||||
return appender;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@Named("fileAppender")
|
|
||||||
static Appender<ILoggingEvent> provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
|
|
||||||
var optionalLogDir = environment.getLogDir();
|
|
||||||
if (optionalLogDir.isPresent()) {
|
|
||||||
Path logDir = optionalLogDir.get();
|
|
||||||
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
|
|
||||||
appender.setContext(context);
|
|
||||||
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
|
|
||||||
appender.setEncoder(encoder);
|
|
||||||
LaunchAndSizeBasedTriggeringPolicy triggeringPolicy = new LaunchAndSizeBasedTriggeringPolicy(FileSize.valueOf(LOG_MAX_SIZE));
|
|
||||||
triggeringPolicy.setContext(context);
|
|
||||||
triggeringPolicy.start();
|
|
||||||
appender.setTriggeringPolicy(triggeringPolicy);
|
|
||||||
FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
|
|
||||||
rollingPolicy.setContext(context);
|
|
||||||
rollingPolicy.setFileNamePattern(logDir.resolve(LOGFILE_ROLLING_PATTERN).toString());
|
|
||||||
rollingPolicy.setMinIndex(LOGFILE_ROLLING_MIN);
|
|
||||||
rollingPolicy.setMaxIndex(LOGFILE_ROLLING_MAX);
|
|
||||||
rollingPolicy.setParent(appender);
|
|
||||||
rollingPolicy.start();
|
|
||||||
appender.setRollingPolicy(rollingPolicy);
|
|
||||||
appender.start();
|
|
||||||
return appender;
|
|
||||||
} else {
|
|
||||||
NOPAppender appender = new NOPAppender<>();
|
|
||||||
appender.setContext(context);
|
|
||||||
return appender;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@Named("upgradeAppender")
|
|
||||||
static Appender<ILoggingEvent> provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
|
|
||||||
var optionalLogDir = environment.getLogDir();
|
|
||||||
if (optionalLogDir.isPresent()) {
|
|
||||||
FileAppender<ILoggingEvent> appender = new FileAppender<>();
|
|
||||||
appender.setFile(optionalLogDir.get().resolve(UPGRADE_FILENAME).toString());
|
|
||||||
appender.setContext(context);
|
|
||||||
appender.setEncoder(encoder);
|
|
||||||
appender.start();
|
|
||||||
return appender;
|
|
||||||
} else {
|
|
||||||
NOPAppender appender = new NOPAppender<>();
|
|
||||||
appender.setContext(context);
|
|
||||||
return appender;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
+24
-13
@@ -2,19 +2,20 @@ package org.cryptomator.ui.addvaultwizard;
|
|||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.common.settings.UiTheme;
|
|
||||||
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.integrations.uiappearance.Theme;
|
||||||
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.fxapp.FxApplicationStyle;
|
||||||
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 javafx.beans.property.ObjectProperty;
|
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.image.Image;
|
import javafx.scene.image.Image;
|
||||||
@@ -23,8 +24,11 @@ import javafx.stage.Stage;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
||||||
|
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
public class ChooseExistingVaultController implements FxController {
|
public class ChooseExistingVaultController implements FxController {
|
||||||
|
|
||||||
@@ -38,12 +42,10 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
private final ObjectProperty<Vault> vault;
|
private final ObjectProperty<Vault> vault;
|
||||||
private final VaultListManager vaultListManager;
|
private final VaultListManager vaultListManager;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final Settings settings;
|
private final ObservableValue<Image> screenshot;
|
||||||
|
|
||||||
private Image screenshot;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, Settings settings) {
|
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, FxApplicationStyle applicationStyle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.welcomeScene = welcomeScene;
|
this.welcomeScene = welcomeScene;
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
@@ -52,16 +54,20 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.vaultListManager = vaultListManager;
|
this.vaultListManager = vaultListManager;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.settings = settings;
|
this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
private Image selectScreenshot(Theme theme) {
|
||||||
public void initialize() {
|
String imageResourcePath;
|
||||||
if (SystemUtils.IS_OS_MAC) {
|
if (SystemUtils.IS_OS_MAC) {
|
||||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-mac"+(UiTheme.LIGHT == settings.theme().get()? "":"-dark")+".png").toString());
|
imageResourcePath = switch (theme) {
|
||||||
|
case LIGHT -> "/img/select-masterkey-mac.png";
|
||||||
|
case DARK -> "/img/select-masterkey-mac-dark.png";
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-win.png").toString());
|
imageResourcePath = "/img/select-masterkey-win.png";
|
||||||
}
|
}
|
||||||
|
return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -73,7 +79,7 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
public void chooseFileAndNext() {
|
public void chooseFileAndNext() {
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle"));
|
fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle"));
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Vault", "*.cryptomator"));
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(resourceBundle.getString("addvaultwizard.existing.filePickerMimeDesc"), CRYPTOMATOR_FILENAME_GLOB));
|
||||||
File masterkeyFile = fileChooser.showOpenDialog(window);
|
File masterkeyFile = fileChooser.showOpenDialog(window);
|
||||||
if (masterkeyFile != null) {
|
if (masterkeyFile != null) {
|
||||||
vaultPath.setValue(masterkeyFile.toPath().toAbsolutePath().getParent());
|
vaultPath.setValue(masterkeyFile.toPath().toAbsolutePath().getParent());
|
||||||
@@ -90,8 +96,13 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
|
|
||||||
/* Getter */
|
/* Getter */
|
||||||
|
|
||||||
public Image getScreenshot() {
|
public ObservableValue<Image> screenshotProperty() {
|
||||||
return screenshot;
|
return screenshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Image getScreenshot() {
|
||||||
|
return screenshot.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -46,7 +46,7 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> chooseNameScene;
|
private final Lazy<Scene> chooseNameScene;
|
||||||
private final Lazy<Scene> choosePasswordScene;
|
private final Lazy<Scene> choosePasswordScene;
|
||||||
private final LocationPresets locationPresets;
|
private final ObservedLocationPresets locationPresets;
|
||||||
private final ObjectProperty<Path> vaultPath;
|
private final ObjectProperty<Path> vaultPath;
|
||||||
private final StringProperty vaultName;
|
private final StringProperty vaultName;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
@@ -71,7 +71,7 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
public FontAwesome5IconView badLocation;
|
public FontAwesome5IconView badLocation;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, ObservedLocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.chooseNameScene = chooseNameScene;
|
this.chooseNameScene = chooseNameScene;
|
||||||
this.choosePasswordScene = choosePasswordScene;
|
this.choosePasswordScene = choosePasswordScene;
|
||||||
@@ -197,7 +197,7 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
return validVaultPath.get();
|
return validVaultPath.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocationPresets getLocationPresets() {
|
public ObservedLocationPresets getObservedLocationPresets() {
|
||||||
return locationPresets;
|
return locationPresets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import javax.inject.Named;
|
|||||||
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;
|
||||||
import javafx.beans.binding.StringBinding;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@@ -33,8 +32,6 @@ public class CreateNewVaultNameController implements FxController {
|
|||||||
private final ObjectProperty<Path> vaultPath;
|
private final ObjectProperty<Path> vaultPath;
|
||||||
private final StringProperty vaultName;
|
private final StringProperty vaultName;
|
||||||
private final BooleanBinding validVaultName;
|
private final BooleanBinding validVaultName;
|
||||||
private final BooleanBinding invalidVaultName;
|
|
||||||
private final StringBinding warningText;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||||
@@ -44,20 +41,14 @@ public class CreateNewVaultNameController implements FxController {
|
|||||||
this.vaultPath = vaultPath;
|
this.vaultPath = vaultPath;
|
||||||
this.vaultName = vaultName;
|
this.vaultName = vaultName;
|
||||||
this.validVaultName = Bindings.createBooleanBinding(this::isValidVaultName, vaultName);
|
this.validVaultName = Bindings.createBooleanBinding(this::isValidVaultName, vaultName);
|
||||||
this.invalidVaultName = validVaultName.not();
|
|
||||||
this.warningText = Bindings.when(vaultName.isNotEmpty().and(invalidVaultName)).then(resourceBundle.getString("addvaultwizard.new.invalidName")).otherwise((String) null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
vaultName.bind(textField.textProperty());
|
vaultName.bindBidirectional(textField.textProperty());
|
||||||
vaultName.addListener(this::vaultNameChanged);
|
vaultName.addListener(this::vaultNameChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidVaultName() {
|
|
||||||
return vaultName.get() != null && VALID_NAME_PATTERN.matcher(vaultName.get().trim()).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vaultNameChanged(@SuppressWarnings("unused") Observable observable) {
|
private void vaultNameChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
if (isValidVaultName()) {
|
if (isValidVaultName()) {
|
||||||
if (vaultPath.get() != null) {
|
if (vaultPath.get() != null) {
|
||||||
@@ -75,32 +66,17 @@ public class CreateNewVaultNameController implements FxController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void next() {
|
public void next() {
|
||||||
window.setScene(chooseLocationScene.get());
|
window.setScene(chooseLocationScene.get());
|
||||||
|
vaultName.set(vaultName.get().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|
||||||
public BooleanBinding invalidVaultNameProperty() {
|
public BooleanBinding validVaultNameProperty() {
|
||||||
return invalidVaultName;
|
return validVaultName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInvalidVaultName() {
|
public boolean isValidVaultName() {
|
||||||
return invalidVaultName.get();
|
return vaultName.get() != null && VALID_NAME_PATTERN.matcher(vaultName.get().trim()).matches();
|
||||||
}
|
|
||||||
|
|
||||||
public StringBinding warningTextProperty() {
|
|
||||||
return warningText;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWarningText() {
|
|
||||||
return warningText.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanBinding showWarningProperty() {
|
|
||||||
return warningText.isNotEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isShowWarning() {
|
|
||||||
return showWarningProperty().get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -102,7 +102,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||||
this.processing = new SimpleBooleanProperty();
|
this.processing = new SimpleBooleanProperty();
|
||||||
this.readyToCreateVault = new SimpleBooleanProperty();
|
this.readyToCreateVault = new SimpleBooleanProperty();
|
||||||
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
this.createVaultButtonState = Bindings.when(processing).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -231,6 +231,6 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ContentDisplay getCreateVaultButtonState() {
|
public ContentDisplay getCreateVaultButtonState() {
|
||||||
return processing.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
|
return createVaultButtonState.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-36
@@ -1,23 +1,15 @@
|
|||||||
package org.cryptomator.ui.addvaultwizard;
|
package org.cryptomator.ui.addvaultwizard;
|
||||||
|
|
||||||
|
import org.cryptomator.common.LocationPreset;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
public class LocationPresets {
|
public class ObservedLocationPresets {
|
||||||
|
|
||||||
private static final String USER_HOME = System.getProperty("user.home");
|
|
||||||
private static final String[] ICLOUDDRIVE_LOCATIONS = {"~/Library/Mobile Documents/iCloud~com~setolabs~Cryptomator/Documents", "~/iCloudDrive/iCloud~com~setolabs~Cryptomator"};
|
|
||||||
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
|
|
||||||
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive/My Drive", "~/Google Drive"};
|
|
||||||
private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"};
|
|
||||||
private static final String[] MEGA_LOCATIONS = {"~/MEGA"};
|
|
||||||
private static final String[] PCLOUD_LOCATIONS = {"~/pCloudDrive"};
|
|
||||||
|
|
||||||
private final ReadOnlyObjectProperty<Path> iclouddriveLocation;
|
private final ReadOnlyObjectProperty<Path> iclouddriveLocation;
|
||||||
private final ReadOnlyObjectProperty<Path> dropboxLocation;
|
private final ReadOnlyObjectProperty<Path> dropboxLocation;
|
||||||
@@ -33,13 +25,13 @@ public class LocationPresets {
|
|||||||
private final BooleanBinding foundPcloud;
|
private final BooleanBinding foundPcloud;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LocationPresets() {
|
public ObservedLocationPresets() {
|
||||||
this.iclouddriveLocation = new SimpleObjectProperty<>(existingWritablePath(ICLOUDDRIVE_LOCATIONS));
|
this.iclouddriveLocation = new SimpleObjectProperty<>(LocationPreset.ICLOUDDRIVE.existingPath());
|
||||||
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
|
this.dropboxLocation = new SimpleObjectProperty<>(LocationPreset.DROPBOX.existingPath());
|
||||||
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
|
this.gdriveLocation = new SimpleObjectProperty<>(LocationPreset.GDRIVE.existingPath());
|
||||||
this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
|
this.onedriveLocation = new SimpleObjectProperty<>(LocationPreset.ONEDRIVE.existingPath());
|
||||||
this.megaLocation = new SimpleObjectProperty<>(existingWritablePath(MEGA_LOCATIONS));
|
this.megaLocation = new SimpleObjectProperty<>(LocationPreset.MEGA.existingPath());
|
||||||
this.pcloudLocation = new SimpleObjectProperty<>(existingWritablePath(PCLOUD_LOCATIONS));
|
this.pcloudLocation = new SimpleObjectProperty<>(LocationPreset.PCLOUD.existingPath());
|
||||||
this.foundIclouddrive = iclouddriveLocation.isNotNull();
|
this.foundIclouddrive = iclouddriveLocation.isNotNull();
|
||||||
this.foundDropbox = dropboxLocation.isNotNull();
|
this.foundDropbox = dropboxLocation.isNotNull();
|
||||||
this.foundGdrive = gdriveLocation.isNotNull();
|
this.foundGdrive = gdriveLocation.isNotNull();
|
||||||
@@ -48,24 +40,6 @@ public class LocationPresets {
|
|||||||
this.foundPcloud = pcloudLocation.isNotNull();
|
this.foundPcloud = pcloudLocation.isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path existingWritablePath(String... candidates) {
|
|
||||||
for (String candidate : candidates) {
|
|
||||||
Path path = Paths.get(resolveHomePath(candidate));
|
|
||||||
if (Files.isDirectory(path)) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String resolveHomePath(String path) {
|
|
||||||
if (path.startsWith("~/")) {
|
|
||||||
return USER_HOME + path.substring(1);
|
|
||||||
} else {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Observables */
|
/* Observables */
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<Path> iclouddriveLocationProperty() {
|
public ReadOnlyObjectProperty<Path> iclouddriveLocationProperty() {
|
||||||
@@ -12,15 +12,15 @@ import javafx.beans.value.ObservableValue;
|
|||||||
* <p>
|
* <p>
|
||||||
* During creation the consumer can optionally define actions to be executed everytime before the animation starts and after it stops.
|
* During creation the consumer can optionally define actions to be executed everytime before the animation starts and after it stops.
|
||||||
*/
|
*/
|
||||||
public class AutoAnimator<T extends Animation> {
|
public class AutoAnimator {
|
||||||
|
|
||||||
private final T animation;
|
private final Animation animation;
|
||||||
private final ObservableValue<Boolean> condition;
|
private final ObservableValue<Boolean> condition;
|
||||||
private final Runnable beforeStart;
|
private final Runnable beforeStart;
|
||||||
private final Runnable afterStop;
|
private final Runnable afterStop;
|
||||||
private final Subscription sub;
|
private final Subscription sub;
|
||||||
|
|
||||||
AutoAnimator(T animation, ObservableValue<Boolean> condition, Runnable beforeStart, Runnable afterStop) {
|
AutoAnimator(Animation animation, ObservableValue<Boolean> condition, Runnable beforeStart, Runnable afterStop) {
|
||||||
this.animation = animation;
|
this.animation = animation;
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.beforeStart = beforeStart;
|
this.beforeStart = beforeStart;
|
||||||
@@ -52,7 +52,7 @@ public class AutoAnimator<T extends Animation> {
|
|||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private Animation animation;
|
private final Animation animation;
|
||||||
private ObservableValue<Boolean> condition = new SimpleBooleanProperty(true);
|
private ObservableValue<Boolean> condition = new SimpleBooleanProperty(true);
|
||||||
private Runnable beforeStart = () -> {};
|
private Runnable beforeStart = () -> {};
|
||||||
private Runnable afterStop = () -> {};
|
private Runnable afterStop = () -> {};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user