mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 07:54:19 +00:00
Compare commits
256
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
845189c638 | ||
|
|
61b7a39aad | ||
|
|
16bd84ee62 | ||
|
|
3d0a97fcdf | ||
|
|
b199b65e38 | ||
|
|
999abf3c13 | ||
|
|
31e938de6a | ||
|
|
4c10ab764a | ||
|
|
4e075ab0ca | ||
|
|
f73ae9759f | ||
|
|
f6283b2f7e | ||
|
|
b23d2e4def | ||
|
|
91a2943599 | ||
|
|
e083fd0bcc | ||
|
|
ff687f1bae | ||
|
|
84d2644f19 | ||
|
|
f9a9d7b870 | ||
|
|
d75a8c4bba | ||
|
|
d326c7c990 | ||
|
|
e8e63ca4fc | ||
|
|
6878f2e94b | ||
|
|
a17b416262 | ||
|
|
fed572694f | ||
|
|
fe3d67d937 | ||
|
|
3dcdeb1033 | ||
|
|
0898158c5a | ||
|
|
81091d4bdf | ||
|
|
c9b1b1baa5 | ||
|
|
c81f3bd972 | ||
|
|
9f86b74320 | ||
|
|
2d8390a0f3 | ||
|
|
8d3671175f | ||
|
|
366011a222 | ||
|
|
c0c2e21055 | ||
|
|
63be96a151 | ||
|
|
fc1df4bd99 | ||
|
|
7657330931 | ||
|
|
4e44de3c3b | ||
|
|
8d902a72ca | ||
|
|
7378031a72 | ||
|
|
f898c07c27 | ||
|
|
c8f02d1045 | ||
|
|
4cc13b986b | ||
|
|
af7b6780f3 | ||
|
|
4a17d01004 | ||
|
|
1d68408e21 | ||
|
|
821b1ee660 | ||
|
|
c6616bd467 | ||
|
|
4bb2589f16 | ||
|
|
896c3b3565 | ||
|
|
6ee717a693 | ||
|
|
07ec858e1e | ||
|
|
a7c1e43ebc | ||
|
|
ac1cd3b073 | ||
|
|
7474519faf | ||
|
|
340e474c33 | ||
|
|
c90ab75427 | ||
|
|
848b3f77d3 | ||
|
|
fb8cdb31ca | ||
|
|
ba5f8053fb | ||
|
|
38165becca | ||
|
|
f731ac5c90 | ||
|
|
0291702736 | ||
|
|
ab63929861 | ||
|
|
ca36fb8af3 | ||
|
|
8cb29ff83b | ||
|
|
304fa67dd7 | ||
|
|
a80ec32d2c | ||
|
|
dc4f21162e | ||
|
|
3b8d7e0241 | ||
|
|
6891c68cf1 | ||
|
|
18b26fbc58 | ||
|
|
1db8da6b6b | ||
|
|
14bce7e91d | ||
|
|
2674bc6f23 | ||
|
|
74e1d6f991 | ||
|
|
25dba028f8 | ||
|
|
bf51cc13d1 | ||
|
|
24be36cccb | ||
|
|
797e15c775 | ||
|
|
2395dbcae9 | ||
|
|
efacbbe2b6 | ||
|
|
2518086e46 | ||
|
|
c733247611 | ||
|
|
4f44e71e13 | ||
|
|
02aa46ece8 | ||
|
|
635f64c847 | ||
|
|
0a8516ae9a | ||
|
|
c1edb30472 | ||
|
|
93c78b63ca | ||
|
|
3ccae93757 | ||
|
|
450dec2baf | ||
|
|
36121527ca | ||
|
|
f30451f7e1 | ||
|
|
74d7f63f64 | ||
|
|
350a4ab762 | ||
|
|
0e0e75b89c | ||
|
|
e76440d928 | ||
|
|
0cbace67a5 | ||
|
|
84a3082589 | ||
|
|
cdfcca06d1 | ||
|
|
630bfa4cee | ||
|
|
90cb7beb1f | ||
|
|
d37fa33278 | ||
|
|
eed9c906cc | ||
|
|
2b7ed5f4dd | ||
|
|
6a7a309a2b | ||
|
|
0382069995 | ||
|
|
7c5f40d7d1 | ||
|
|
09e9530660 | ||
|
|
947d25b335 | ||
|
|
80cbc6699a | ||
|
|
da0933fa92 | ||
|
|
adc9f32fb3 | ||
|
|
65369bdbff | ||
|
|
d18e618ef1 | ||
|
|
970216dd59 | ||
|
|
6840a649c7 | ||
|
|
e4709ed6fe | ||
|
|
19ebc7e562 | ||
|
|
0fa052d2d1 | ||
|
|
402861b9c0 | ||
|
|
aca1666dea | ||
|
|
70f6a4877c | ||
|
|
85c5dc8dfb | ||
|
|
23c113948f | ||
|
|
d86ea20c31 | ||
|
|
01929c2288 | ||
|
|
2634433b2c | ||
|
|
c1f44f76b9 | ||
|
|
b2f27c0a3d | ||
|
|
b1d7cfc81b | ||
|
|
7cd5c66836 | ||
|
|
95cef34234 | ||
|
|
8977440697 | ||
|
|
d5eb84a000 | ||
|
|
81fb6b8794 | ||
|
|
92b390d5bb | ||
|
|
8867532210 | ||
|
|
77db435b4f | ||
|
|
cb2bc17283 | ||
|
|
aaa93239d0 | ||
|
|
93445e22d4 | ||
|
|
64897cfa21 | ||
|
|
7349a29831 | ||
|
|
73554b4759 | ||
|
|
0312f045aa | ||
|
|
1134c1b2ff | ||
|
|
8bb925fd93 | ||
|
|
ca5d9eba36 | ||
|
|
1a49e24d8a | ||
|
|
24b0ed2502 | ||
|
|
a226d5403a | ||
|
|
0f3085cc73 | ||
|
|
f0a040cf29 | ||
|
|
0676748dde | ||
|
|
589b8384eb | ||
|
|
9e3947f337 | ||
|
|
790cc4e772 | ||
|
|
72f4988632 | ||
|
|
b3fa6bbf32 | ||
|
|
c2dc487c79 | ||
|
|
e74dd3be9e | ||
|
|
1eeee61572 | ||
|
|
984b7a2d0e | ||
|
|
45c714a123 | ||
|
|
96bb97d50a | ||
|
|
046acb9bbf | ||
|
|
ce4ec16c62 | ||
|
|
96c8c9a9a9 | ||
|
|
44db97327d | ||
|
|
f441700c25 | ||
|
|
76b023bdbd | ||
|
|
241d9781bb | ||
|
|
ff94532560 | ||
|
|
e949023321 | ||
|
|
da31407d63 | ||
|
|
8c7c5da89a | ||
|
|
e3a0e1ec5a | ||
|
|
4cee27d8dd | ||
|
|
b0fce66d77 | ||
|
|
bf47ba0145 | ||
|
|
fc7b484c9a | ||
|
|
4402c19d90 | ||
|
|
4e89de9e47 | ||
|
|
a27c9d63d9 | ||
|
|
da6aa0f002 | ||
|
|
0bf3533f45 | ||
|
|
295cb0ef6d | ||
|
|
6fac84e9c2 | ||
|
|
77b2e67c49 | ||
|
|
c48c9b1568 | ||
|
|
6da7eae2f8 | ||
|
|
aec56c48c5 | ||
|
|
3b73544766 | ||
|
|
fbc471635a | ||
|
|
3c5b9ed3a6 | ||
|
|
8479122561 | ||
|
|
91c883d5cc | ||
|
|
1cd4da0796 | ||
|
|
935aaccdae | ||
|
|
ab3dd779d2 | ||
|
|
1fbcf6d517 | ||
|
|
82538091c9 | ||
|
|
62ade6113b | ||
|
|
4779bbf415 | ||
|
|
c6b786a771 | ||
|
|
6381227897 | ||
|
|
e82167b5e6 | ||
|
|
dc4fd482b5 | ||
|
|
a71f8b350e | ||
|
|
c9ec8fecef | ||
|
|
e72d32f2d1 | ||
|
|
2545aa2a7e | ||
|
|
51aac15622 | ||
|
|
afa0cfeafb | ||
|
|
19e24ba12c | ||
|
|
c47ce2c730 | ||
|
|
5d00b3dd76 | ||
|
|
4db57cc0dc | ||
|
|
432a9a27f1 | ||
|
|
57bfa3276d | ||
|
|
02fc9b263a | ||
|
|
4c66f81736 | ||
|
|
2de151aebe | ||
|
|
c44911dcac | ||
|
|
bcf2a3d20c | ||
|
|
422ce4a387 | ||
|
|
21387bd76c | ||
|
|
52cd560cb2 | ||
|
|
6c440dfbbb | ||
|
|
a074450452 | ||
|
|
b781cf6f25 | ||
|
|
9b653f488b | ||
|
|
17c580267f | ||
|
|
490d1b8f87 | ||
|
|
b0ad86f16b | ||
|
|
df2b4ac086 | ||
|
|
aee4ececba | ||
|
|
b2992aa6ae | ||
|
|
4c546d281a | ||
|
|
c90e445a67 | ||
|
|
d3c2b0509e | ||
|
|
c6d1c2ca6b | ||
|
|
86dec80726 | ||
|
|
9083976989 | ||
|
|
d9c5d76417 | ||
|
|
c19c3754c6 | ||
|
|
947e0e2369 | ||
|
|
6eca8f2e0c | ||
|
|
9f61ad1941 | ||
|
|
41492a951a | ||
|
|
cbbfec5531 | ||
|
|
d7a4dff080 | ||
|
|
8e56218fbb | ||
|
|
8ad20c27da |
@@ -5,18 +5,12 @@ labels: type:bug
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
⚠️⚠️⚠️ READ CAREFULLY ⚠️⚠️⚠️
|
Please make sure to:
|
||||||
|
- Comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
||||||
|
- Search for existing similar issues first: https://github.com/cryptomator/cryptomator/issues?q=
|
||||||
|
|
||||||
Do you want to ask a QUESTION? Are you looking for SUPPORT?
|
⚠️ IMPORTANT: If you don't stick to this template, the issue will get closed.
|
||||||
We're happy to help you via our support channels! Please read: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md
|
|
||||||
|
|
||||||
By filing an issue, you are expected to comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
|
||||||
|
|
||||||
Of course, we also expect you to search for existing similar issues first! ;) https://github.com/cryptomator/cryptomator/issues?q=
|
|
||||||
|
|
||||||
⚠️ IMPORTANT: If you don't stick to this template, the issue will get closed. To proof that you read this, please remove the X from the following line:
|
|
||||||
-->
|
-->
|
||||||
<!-- oooXooo -->
|
|
||||||
|
|
||||||
### Description
|
### Description
|
||||||
|
|
||||||
@@ -24,7 +18,7 @@ Of course, we also expect you to search for existing similar issues first! ;) ht
|
|||||||
|
|
||||||
### System Setup
|
### System Setup
|
||||||
|
|
||||||
* Operating system and version: [Windows/macOS/Linux + Version]
|
* Operating system and version: [Windows/macOS/Linux + Version ( + Desktop Environment, if Linux)]
|
||||||
* Cryptomator version: [Shown in the settings]
|
* Cryptomator version: [Shown in the settings]
|
||||||
* Volume type: [Dokany/FUSE/WebDAV, shown in the settings]
|
* Volume type: [Dokany/FUSE/WebDAV, shown in the settings]
|
||||||
|
|
||||||
@@ -51,7 +45,6 @@ Of course, we also expect you to search for existing similar issues first! ;) ht
|
|||||||
[Any additional information, log files, screenshots, configuration, or data that might be necessary to reproduce the issue.]
|
[Any additional information, log files, screenshots, configuration, or data that might be necessary to reproduce the issue.]
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
If you want to add the log file or screenshots, please add them as attachments. If your log file seems empty and doesn't show any errors, you may enable the debug mode first. Here is how to do that: https://community.cryptomator.org/t/how-do-i-enable-debug-mode/36
|
If you want to add the log file or screenshots, please add them as attachments. If your log file seems empty and doesn't show any errors, you may enable the debug mode first. Here is how to do that: https://community.cryptomator.org/t/how-do-i-enable-debug-mode/36
|
||||||
|
|
||||||
Then reproduce the problem to ensure all important information is contained in there. You may use test data or redact sensitive information from the log file.
|
Then reproduce the problem to ensure all important information is contained in there. You may use test data or redact sensitive information from the log file.
|
||||||
@@ -60,5 +53,4 @@ Log file location:
|
|||||||
- Windows: %appdata%/Cryptomator
|
- Windows: %appdata%/Cryptomator
|
||||||
- macOS: ~/Library/Logs/Cryptomator
|
- macOS: ~/Library/Logs/Cryptomator
|
||||||
- Linux: ~/.local/share/Cryptomator/logs
|
- Linux: ~/.local/share/Cryptomator/logs
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Help & Support
|
||||||
|
url: https://community.cryptomator.org/
|
||||||
|
about: You will find answers in our community forum
|
||||||
|
- name: User Manual
|
||||||
|
url: https://docs.cryptomator.org/
|
||||||
|
about: Read the Cryptomator documentation here
|
||||||
@@ -5,14 +5,9 @@ labels: type:feature-request
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
Please make sure to:
|
||||||
Do you want to ask a QUESTION? Are you looking for SUPPORT?
|
- Comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
||||||
We're happy to help you via our support channels! Please read: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md
|
- Search for existing similar issues first: https://github.com/cryptomator/cryptomator/issues?q=
|
||||||
|
|
||||||
By filing a feature request, you are expected to comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
|
||||||
|
|
||||||
Of course, we also expect you to search for existing similar feature requests first! ;)
|
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
@@ -29,4 +24,4 @@ Of course, we also expect you to search for existing similar feature requests fi
|
|||||||
|
|
||||||
### Additional Context
|
### Additional Context
|
||||||
|
|
||||||
[Add any other context or screenshots about the feature request here.]
|
[Add any other context or screenshots about the feature request here.]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 14
|
java-version: 16
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
path: ~/.m2/repository
|
||||||
@@ -25,12 +25,13 @@ jobs:
|
|||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
run: mvn -B install --file main/pom.xml -Pcoverage
|
run: mvn -B install --file main/pom.xml -Pcoverage
|
||||||
- name: Run Codacy Coverage Reporter
|
- name: Run Codacy Coverage Reporter
|
||||||
|
if: github.repository == 'cryptomator/cryptomator'
|
||||||
run: |
|
run: |
|
||||||
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
|
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
|
$JAVA_HOME/bin/java --illegal-access=permit -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
|
$JAVA_HOME/bin/java --illegal-access=permit -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
$JAVA_HOME/bin/java --illegal-access=permit -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
$JAVA_HOME/bin/java --illegal-access=permit -jar ~/codacy-coverage-reporter.jar final
|
||||||
env:
|
env:
|
||||||
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||||
- name: Assemble buildkit-linux.zip
|
- name: Assemble buildkit-linux.zip
|
||||||
@@ -59,7 +60,7 @@ jobs:
|
|||||||
name: Draft a Release on GitHub Releases
|
name: Draft a Release on GitHub Releases
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator'
|
||||||
steps:
|
steps:
|
||||||
- name: Download buildkit-linux.zip
|
- name: Download buildkit-linux.zip
|
||||||
uses: actions/download-artifact@v1
|
uses: actions/download-artifact@v1
|
||||||
@@ -86,6 +87,11 @@ jobs:
|
|||||||
release_name: ${{ github.ref }}
|
release_name: ${{ github.ref }}
|
||||||
body: |
|
body: |
|
||||||
:construction: Work in Progress
|
:construction: Work in Progress
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
* [ ] add Linux appimage, zsync file and signature file
|
||||||
|
* [ ] add Windows installer and signature file
|
||||||
|
* [ ] add MacOs disk image and signature file
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
- name: Upload buildkit-linux.zip to GitHub Releases
|
- name: Upload buildkit-linux.zip to GitHub Releases
|
||||||
|
|||||||
@@ -6,20 +6,32 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
closeTemplateViolation:
|
closeTemplateViolation:
|
||||||
name: Close bug reports that violate the issue template
|
name: Validate bug report against issue template
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: contains(github.event.issue.labels.*.name, 'type:bug')
|
||||||
steps:
|
steps:
|
||||||
- if: |
|
- name: Check "Description"
|
||||||
contains(github.event.issue.labels.*.name, 'type:bug')
|
if: |
|
||||||
&& (
|
!contains(github.event.issue.body, env.MUST_CONTAIN)
|
||||||
!contains(github.event.issue.body, '<!-- oooooo -->')
|
|| contains(toJson(github.event.issue.body), env.MUST_NOT_CONTAIN)
|
||||||
|| !contains(github.event.issue.body, '### Description')
|
run: exit 1
|
||||||
)
|
env:
|
||||||
name: Close Issue
|
MUST_CONTAIN: '### Description'
|
||||||
|
MUST_NOT_CONTAIN: '### Description\r\n\r\n[Summarize your problem.]\r\n\r\n### System Setup'
|
||||||
|
- name: Check "Steps to Reproduce"
|
||||||
|
if: |
|
||||||
|
!contains(github.event.issue.body, env.MUST_CONTAIN)
|
||||||
|
|| contains(toJson(github.event.issue.body), env.MUST_NOT_CONTAIN)
|
||||||
|
run: exit 1
|
||||||
|
env:
|
||||||
|
MUST_CONTAIN: '### Steps to Reproduce'
|
||||||
|
MUST_NOT_CONTAIN: '### Steps to Reproduce\r\n\r\n1. [First step]\r\n2. [Second step]\r\n3. [and so on…]\r\n\r\n#### Expected Behavior'
|
||||||
|
- name: Close issue if one of the checks failed
|
||||||
|
if: ${{ failure() }}
|
||||||
uses: peter-evans/close-issue@v1
|
uses: peter-evans/close-issue@v1
|
||||||
with:
|
with:
|
||||||
comment: |
|
comment: |
|
||||||
This bug report did ignore our issue template. 😞
|
This bug report did ignore our issue template. 😞
|
||||||
Auto-closing this issue, since it is most likely not useful.
|
Auto-closing this issue, since it is most likely not useful.
|
||||||
|
|
||||||
_This decision was made by a bot. If you think the bot is wrong, let us know and we'll reopen this issue._
|
_This decision was made by a bot. If you think the bot is wrong, let us know and we'll reopen this issue._
|
||||||
|
|||||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="false" project-jdk-name="14" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" project-jdk-name="16" 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="launcher" />
|
<module name="launcher" />
|
||||||
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" -Xss20m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m" />
|
||||||
<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="launcher" />
|
<module name="launcher" />
|
||||||
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator-Dev/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator-Dev/mnt" -Dfuse.experimental="true" -Xss20m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator-Dev/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator-Dev/mnt" -Dcryptomator.showTrayIcon=true -Dfuse.experimental="true" -Xss20m -Xmx512m" />
|
||||||
<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="launcher" />
|
<module name="launcher" />
|
||||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator" -Xss2m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
|
||||||
<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="launcher" />
|
<module name="launcher" />
|
||||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator-Dev" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator-Dev" -Dfuse.experimental="true" -Xss2m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator-Dev" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator-Dev" -Dfuse.experimental="true" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-2
@@ -5,8 +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="launcher" />
|
<module name="launcher" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/Library/Logs/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
|
||||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/Library/Logs/Cryptomator" -Dcryptomator.mountPointsDir="/Volumes/" -Xss2m -Xmx512m -ea" />
|
|
||||||
<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="launcher" />
|
<module name="launcher" />
|
||||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/Library/Logs/Cryptomator-Dev" -Dcryptomator.mountPointsDir="/Volumes/" -Dfuse.experimental="true" -Xss2m -Xmx512m -ea" />
|
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/Library/Logs/Cryptomator-Dev" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
||||||
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
|
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
|
||||||
[](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade)
|
[](https://www.codacy.com/gh/cryptomator/cryptomator/dashboard)
|
||||||
[](http://twitter.com/Cryptomator)
|
[](http://twitter.com/Cryptomator)
|
||||||
[](https://translate.cryptomator.org/)
|
[](https://translate.cryptomator.org/)
|
||||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||||
@@ -17,11 +17,26 @@ Cryptomator is provided free of charge as an open-source project despite the hig
|
|||||||
|
|
||||||
### Gold Sponsors
|
### Gold Sponsors
|
||||||
|
|
||||||
[<img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="96">](https://www.gee-whiz.de/)
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://www.gee-whiz.de/"><img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="80"></a></td>
|
||||||
|
<td><a href="https://proxy-hub.com/"><img src="https://cryptomator.org/img/sponsors/proxyhub.svg" alt="Proxy-Hub" height="80"></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
### Silver Sponsors
|
### Silver Sponsors
|
||||||
|
|
||||||
[](https://thebestvpn.com/)
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://thebestvpn.com/"><img src="https://cryptomator.org/img/sponsors/thebestvpn@2x.png" alt="TheBestVPN" height="64"></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
- [Jameson Lopp](https://www.lopp.net/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -65,7 +80,7 @@ For more information on the security details visit [cryptomator.org](https://doc
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* JDK 14 (e.g. adoptopenjdk)
|
* JDK 16 (e.g. adoptopenjdk)
|
||||||
* 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))
|
* 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))
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.9</version>
|
<version>1.6.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>buildkit</artifactId>
|
<artifactId>buildkit</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ java \
|
|||||||
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
|
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
|
||||||
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
|
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
|
||||||
-Djdk.gtk.version=2 \
|
-Djdk.gtk.version=2 \
|
||||||
-Xss20m \
|
-Xss2m \
|
||||||
-Xmx512m \
|
-Xmx512m \
|
||||||
org.cryptomator.launcher.Cryptomator
|
org.cryptomator.launcher.Cryptomator
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.9</version>
|
<version>1.6.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<name>Cryptomator Commons</name>
|
<name>Cryptomator Commons</name>
|
||||||
|
|||||||
@@ -47,10 +47,12 @@ public abstract class CommonsModule {
|
|||||||
@Named("licensePublicKey")
|
@Named("licensePublicKey")
|
||||||
static String provideLicensePublicKey() {
|
static String provideLicensePublicKey() {
|
||||||
// in PEM format without the dash-escaped begin/end lines
|
// in PEM format without the dash-escaped begin/end lines
|
||||||
return "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW" //
|
return """
|
||||||
+ "fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV" //
|
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW\
|
||||||
+ "gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE" //
|
fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV\
|
||||||
+ "r0DzRyj4ixPIt38CQB8=";
|
gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE\
|
||||||
|
r0DzRyj4ixPIt38CQB8=\
|
||||||
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -21,14 +21,13 @@ import java.util.stream.StreamSupport;
|
|||||||
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 String USER_HOME = System.getProperty("user.home");
|
|
||||||
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
||||||
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
|
|
||||||
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;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Environment() {
|
public Environment() {
|
||||||
|
LOG.debug("user.home: {}", System.getProperty("user.home"));
|
||||||
LOG.debug("java.library.path: {}", System.getProperty("java.library.path"));
|
LOG.debug("java.library.path: {}", System.getProperty("java.library.path"));
|
||||||
LOG.debug("user.language: {}", System.getProperty("user.language"));
|
LOG.debug("user.language: {}", System.getProperty("user.language"));
|
||||||
LOG.debug("user.region: {}", System.getProperty("user.region"));
|
LOG.debug("user.region: {}", System.getProperty("user.region"));
|
||||||
@@ -40,6 +39,7 @@ public class Environment {
|
|||||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||||
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
||||||
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
||||||
|
LOG.debug("cryptomator.showTrayIcon: {}", System.getProperty("cryptomator.showTrayIcon"));
|
||||||
LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
|
LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +75,11 @@ public class Environment {
|
|||||||
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean showTrayIcon() {
|
||||||
|
return Boolean.getBoolean("cryptomator.showTrayIcon");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated // TODO: remove as soon as custom mount path works properly on Win+Fuse
|
||||||
public boolean useExperimentalFuse() {
|
public boolean useExperimentalFuse() {
|
||||||
return Boolean.getBoolean("fuse.experimental");
|
return Boolean.getBoolean("fuse.experimental");
|
||||||
}
|
}
|
||||||
@@ -92,8 +97,13 @@ public class Environment {
|
|||||||
String value = System.getProperty(propertyName);
|
String value = System.getProperty(propertyName);
|
||||||
return Optional.ofNullable(value).map(Paths::get);
|
return Optional.ofNullable(value).map(Paths::get);
|
||||||
}
|
}
|
||||||
// visible for testing
|
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
Path getHomeDir() {
|
||||||
|
return getPath("user.home").orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
Stream<Path> getPaths(String propertyName) {
|
Stream<Path> getPaths(String propertyName) {
|
||||||
Stream<String> rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP);
|
Stream<String> rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP);
|
||||||
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir);
|
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir);
|
||||||
@@ -101,7 +111,7 @@ public class Environment {
|
|||||||
|
|
||||||
private Path replaceHomeDir(Path path) {
|
private Path replaceHomeDir(Path path) {
|
||||||
if (path.startsWith(RELATIVE_HOME_DIR)) {
|
if (path.startsWith(RELATIVE_HOME_DIR)) {
|
||||||
return ABSOLUTE_HOME_DIR.resolve(RELATIVE_HOME_DIR.relativize(path));
|
return getHomeDir().resolve(RELATIVE_HOME_DIR.relativize(path));
|
||||||
} else {
|
} else {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +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;
|
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.function.UnaryOperator;
|
|
||||||
|
|
||||||
public final class LazyInitializer {
|
|
||||||
|
|
||||||
private LazyInitializer() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as {@link #initializeLazily(AtomicReference, SupplierThrowingException, Class)} except that no checked exception may be thrown by the factory function.
|
|
||||||
*
|
|
||||||
* @param <T> Type of the value
|
|
||||||
* @param reference A reference to a maybe not yet initialized value.
|
|
||||||
* @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
|
|
||||||
* @return The initialized value
|
|
||||||
*/
|
|
||||||
public static <T> T initializeLazily(AtomicReference<T> reference, Supplier<T> factory) {
|
|
||||||
SupplierThrowingException<T, RuntimeException> factoryThrowingRuntimeExceptions = () -> factory.get();
|
|
||||||
return initializeLazily(reference, factoryThrowingRuntimeExceptions, RuntimeException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
|
|
||||||
*
|
|
||||||
* @param <T> Type of the value
|
|
||||||
* @param <E> Type of the any expected exception that may occur during initialization
|
|
||||||
* @param reference A reference to a maybe not yet initialized value.
|
|
||||||
* @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
|
|
||||||
* @param exceptionType Expected exception type.
|
|
||||||
* @return The initialized value
|
|
||||||
* @throws E Exception thrown by the factory function.
|
|
||||||
*/
|
|
||||||
public static <T, E extends Exception> T initializeLazily(AtomicReference<T> reference, SupplierThrowingException<T, E> factory, Class<E> exceptionType) throws E {
|
|
||||||
final T existing = reference.get();
|
|
||||||
if (existing != null) {
|
|
||||||
return existing;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return reference.updateAndGet(invokeFactoryIfNull(factory));
|
|
||||||
} catch (InitializationException e) {
|
|
||||||
Throwables.throwIfUnchecked(e.getCause());
|
|
||||||
Throwables.throwIfInstanceOf(e.getCause(), exceptionType);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T, E extends Exception> UnaryOperator<T> invokeFactoryIfNull(SupplierThrowingException<T, E> factory) throws InitializationException {
|
|
||||||
return currentValue -> {
|
|
||||||
if (currentValue == null) {
|
|
||||||
try {
|
|
||||||
return factory.get();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new InitializationException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return currentValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class InitializationException extends RuntimeException {
|
|
||||||
|
|
||||||
public InitializationException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,8 +33,8 @@ class LicenseChecker {
|
|||||||
try {
|
try {
|
||||||
byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
|
byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
|
||||||
PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
|
PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
|
||||||
if (key instanceof ECPublicKey) {
|
if (key instanceof ECPublicKey k) {
|
||||||
return (ECPublicKey) key;
|
return k;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Key not an EC public key.");
|
throw new IllegalStateException("Key not an EC public key.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ public class KeychainManager implements KeychainAccessProvider {
|
|||||||
return keychain.getValue() != null;
|
return keychain.getValue() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return keychain.getValue() == null || keychain.get().isLocked();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the keychain knows a passphrase for the given key.
|
* Checks if the keychain knows a passphrase for the given key.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
+1
-8
@@ -8,9 +8,7 @@ import javax.inject.Inject;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class AvailableDriveLetterChooser implements MountPointChooser {
|
class AvailableDriveLetterChooser implements MountPointChooser {
|
||||||
|
|
||||||
public static final int PRIORITY = 200;
|
|
||||||
|
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
|
|
||||||
@@ -28,9 +26,4 @@ public class AvailableDriveLetterChooser implements MountPointChooser {
|
|||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||||
return this.windowsDriveLetters.getAvailableDriveLetterPath();
|
return this.windowsDriveLetters.getAvailableDriveLetterPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return PRIORITY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-8
@@ -9,9 +9,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class CustomDriveLetterChooser implements MountPointChooser {
|
class CustomDriveLetterChooser implements MountPointChooser {
|
||||||
|
|
||||||
public static final int PRIORITY = 100;
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
|
|
||||||
@@ -29,9 +27,4 @@ public class CustomDriveLetterChooser implements MountPointChooser {
|
|||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||||
return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
|
return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return PRIORITY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-7
@@ -20,9 +20,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class CustomMountPointChooser implements MountPointChooser {
|
class CustomMountPointChooser implements MountPointChooser {
|
||||||
|
|
||||||
public static final int PRIORITY = 0;
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
|
||||||
|
|
||||||
@@ -94,8 +92,4 @@ public class CustomMountPointChooser implements MountPointChooser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return PRIORITY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
-64
@@ -1,64 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
public class IrregularUnmountCleaner {
|
|
||||||
|
|
||||||
public static Logger LOG = LoggerFactory.getLogger(IrregularUnmountCleaner.class);
|
|
||||||
|
|
||||||
public static void removeIrregularUnmountDebris(Path dirContainingMountPoints) {
|
|
||||||
IOException cleanupFailed = new IOException("Cleanup failed");
|
|
||||||
|
|
||||||
try {
|
|
||||||
LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints);
|
|
||||||
for (Path p : Files.newDirectoryStream(dirContainingMountPoints)) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void deleteEmptyDir(Path dir) throws IOException {
|
|
||||||
assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
try {
|
|
||||||
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 static void deleteDeadLink(Path symlink) throws IOException {
|
|
||||||
assert Files.isSymbolicLink(symlink);
|
|
||||||
if (Files.notExists(symlink)) { // following link: target does not exist
|
|
||||||
Files.delete(symlink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
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
-32
@@ -47,7 +47,7 @@ import java.util.SortedSet;
|
|||||||
* If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
|
* If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
|
||||||
* remaining cleanup.
|
* remaining cleanup.
|
||||||
*/
|
*/
|
||||||
public interface MountPointChooser extends Comparable<MountPointChooser> {
|
public interface MountPointChooser {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the {@link Volume} to determine whether this MountPointChooser is
|
* Called by the {@link Volume} to determine whether this MountPointChooser is
|
||||||
@@ -135,35 +135,4 @@ public interface MountPointChooser extends Comparable<MountPointChooser> {
|
|||||||
//NO-OP
|
//NO-OP
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the {@link MountPointChooserModule} to sort the available MPCs
|
|
||||||
* and determine their execution order.
|
|
||||||
* The priority must be defined by the developer to reflect a useful execution order.
|
|
||||||
* MPCs with lower priorities will be placed at lower indices in the resulting
|
|
||||||
* {@link SortedSet} and will be executed with higher probability.<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.
|
|
||||||
*
|
|
||||||
* @return the priority of this MPC.
|
|
||||||
*/
|
|
||||||
int getPriority();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the {@link Volume} to determine the execution order of the registered MPCs.
|
|
||||||
* <b>Implementations usually may not override this method.</b> This default implementation
|
|
||||||
* sorts the MPCs in ascending order of their {@link #getPriority() priority.}<br>
|
|
||||||
* <br>
|
|
||||||
* <b>Original description:</b>
|
|
||||||
* <p>{@inheritDoc}
|
|
||||||
*
|
|
||||||
* @implNote This default implementation sorts the MPCs in ascending order
|
|
||||||
* of their {@link #getPriority() priority.}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default int compareTo(MountPointChooser other) {
|
|
||||||
Preconditions.checkNotNull(other, "Other must not be null!");
|
|
||||||
|
|
||||||
//Sort by priority (ascending order)
|
|
||||||
return Integer.compare(this.getPriority(), other.getPriority());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-11
@@ -1,15 +1,17 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSortedSet;
|
import com.google.common.collect.Iterables;
|
||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoSet;
|
import dagger.multibindings.IntKey;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.vaults.PerVault;
|
import org.cryptomator.common.vaults.PerVault;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import java.util.Set;
|
import java.util.Map;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
|
* Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
|
||||||
@@ -21,30 +23,40 @@ import java.util.SortedSet;
|
|||||||
public abstract class MountPointChooserModule {
|
public abstract class MountPointChooserModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoMap
|
||||||
|
@IntKey(0)
|
||||||
@PerVault
|
@PerVault
|
||||||
public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
|
public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoMap
|
||||||
|
@IntKey(100)
|
||||||
@PerVault
|
@PerVault
|
||||||
public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
|
public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoMap
|
||||||
|
@IntKey(101)
|
||||||
|
@PerVault
|
||||||
|
public abstract MountPointChooser bindMacVolumeMountChooser(MacVolumeMountChooser chooser);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@IntKey(200)
|
||||||
@PerVault
|
@PerVault
|
||||||
public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
|
public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoMap
|
||||||
|
@IntKey(999)
|
||||||
@PerVault
|
@PerVault
|
||||||
public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
|
public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@PerVault
|
@PerVault
|
||||||
@Named("orderedMountPointChoosers")
|
@Named("orderedMountPointChoosers")
|
||||||
public static SortedSet<MountPointChooser> provideOrderedMountPointChoosers(Set<MountPointChooser> choosers) {
|
public static Iterable<MountPointChooser> provideOrderedMountPointChoosers(Map<Integer, MountPointChooser> choosers) {
|
||||||
//Sort by natural order. The natural order is defined by MountPointChooser#compareTo
|
SortedMap<Integer, MountPointChooser> sortedChoosers = new TreeMap<>(choosers);
|
||||||
return ImmutableSortedSet.copyOf(choosers);
|
return Iterables.unmodifiableIterable(sortedChoosers.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
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 {
|
||||||
|
LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints);
|
||||||
|
for (Path p : Files.newDirectoryStream(dirContainingMountPoints)) {
|
||||||
|
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 {
|
||||||
|
if (Files.newDirectoryStream(dir).iterator().hasNext()) {
|
||||||
|
throw new DirectoryNotEmptyException(dir.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-43
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.vaults.Volume;
|
import org.cryptomator.common.vaults.Volume;
|
||||||
@@ -8,27 +7,24 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
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.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class TemporaryMountPointChooser implements MountPointChooser {
|
class TemporaryMountPointChooser implements MountPointChooser {
|
||||||
|
|
||||||
public static final int PRIORITY = 300;
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
|
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
|
||||||
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
|
private final MountPointHelper helper;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment) {
|
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment, MountPointHelper helper) {
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
|
this.helper = helper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,41 +38,15 @@ public class TemporaryMountPointChooser implements MountPointChooser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||||
return this.environment.getMountPointsDir().map(this::choose);
|
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
|
||||||
private Path choose(Path parent) {
|
helper.clearIrregularUnmountDebrisIfNeeded();
|
||||||
String basename = this.vaultSettings.mountName().get();
|
return this.environment.getMountPointsDir().map(dir -> this.helper.chooseTemporaryMountPoint(this.vaultSettings, dir));
|
||||||
//regular
|
|
||||||
Path mountPoint = parent.resolve(basename);
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
//with id
|
|
||||||
mountPoint = parent.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 = parent.resolve(basename + "_(" +vaultSettings.getId() + ")_"+i);
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parent, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
||||||
// 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.
|
|
||||||
if (SystemUtils.IS_OS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (caller.getMountPointRequirement()) {
|
switch (caller.getMountPointRequirement()) {
|
||||||
case PARENT_NO_MOUNT_POINT -> {
|
case PARENT_NO_MOUNT_POINT -> {
|
||||||
@@ -114,8 +84,4 @@ public class TemporaryMountPointChooser implements MountPointChooser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return PRIORITY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
@@ -39,7 +40,8 @@ public class Settings {
|
|||||||
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||||
public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = SystemUtils.IS_OS_WINDOWS ? KeychainBackend.WIN_SYSTEM_KEYCHAIN : SystemUtils.IS_OS_MAC ? KeychainBackend.MAC_SYSTEM_KEYCHAIN : KeychainBackend.GNOME;
|
public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = SystemUtils.IS_OS_WINDOWS ? KeychainBackend.WIN_SYSTEM_KEYCHAIN : SystemUtils.IS_OS_MAC ? KeychainBackend.MAC_SYSTEM_KEYCHAIN : KeychainBackend.GNOME;
|
||||||
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
||||||
private static final String DEFAULT_LICENSE_KEY = "";
|
public static final String DEFAULT_LICENSE_KEY = "";
|
||||||
|
public static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
|
||||||
|
|
||||||
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||||
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||||
@@ -54,13 +56,17 @@ public class Settings {
|
|||||||
private final ObjectProperty<KeychainBackend> keychainBackend = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_BACKEND);
|
private final ObjectProperty<KeychainBackend> keychainBackend = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_BACKEND);
|
||||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||||
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
||||||
|
private final BooleanProperty showMinimizeButton = new SimpleBooleanProperty(DEFAULT_SHOW_MINIMIZE_BUTTON);
|
||||||
|
private final BooleanProperty showTrayIcon;
|
||||||
|
|
||||||
private Consumer<Settings> saveCmd;
|
private Consumer<Settings> saveCmd;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Package-private constructor; use {@link SettingsProvider}.
|
* Package-private constructor; use {@link SettingsProvider}.
|
||||||
*/
|
*/
|
||||||
Settings() {
|
Settings(Environment env) {
|
||||||
|
this.showTrayIcon = new SimpleBooleanProperty(env.showTrayIcon());
|
||||||
|
|
||||||
directories.addListener(this::somethingChanged);
|
directories.addListener(this::somethingChanged);
|
||||||
askedForUpdateCheck.addListener(this::somethingChanged);
|
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||||
checkForUpdates.addListener(this::somethingChanged);
|
checkForUpdates.addListener(this::somethingChanged);
|
||||||
@@ -74,6 +80,8 @@ public class Settings {
|
|||||||
keychainBackend.addListener(this::somethingChanged);
|
keychainBackend.addListener(this::somethingChanged);
|
||||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||||
licenseKey.addListener(this::somethingChanged);
|
licenseKey.addListener(this::somethingChanged);
|
||||||
|
showMinimizeButton.addListener(this::somethingChanged);
|
||||||
|
showTrayIcon.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||||
@@ -141,4 +149,12 @@ public class Settings {
|
|||||||
public StringProperty licenseKey() {
|
public StringProperty licenseKey() {
|
||||||
return licenseKey;
|
return licenseKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty showMinimizeButton() {
|
||||||
|
return showMinimizeButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty showTrayIcon() {
|
||||||
|
return showTrayIcon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-2
@@ -9,19 +9,29 @@ import com.google.gson.TypeAdapter;
|
|||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonToken;
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
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 javafx.geometry.NodeOrientation;
|
import javafx.geometry.NodeOrientation;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
|
||||||
|
|
||||||
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
|
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SettingsJsonAdapter(Environment env) {
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JsonWriter out, Settings value) throws IOException {
|
public void write(JsonWriter out, Settings value) throws IOException {
|
||||||
@@ -40,6 +50,8 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||||
out.name("keychainBackend").value(value.keychainBackend().get().name());
|
out.name("keychainBackend").value(value.keychainBackend().get().name());
|
||||||
out.name("licenseKey").value(value.licenseKey().get());
|
out.name("licenseKey").value(value.licenseKey().get());
|
||||||
|
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
|
||||||
|
out.name("showTrayIcon").value(value.showTrayIcon().get());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +65,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Settings read(JsonReader in) throws IOException {
|
public Settings read(JsonReader in) throws IOException {
|
||||||
Settings settings = new Settings();
|
Settings settings = new Settings(env);
|
||||||
|
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
@@ -72,6 +84,8 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||||
case "keychainBackend" -> settings.keychainBackend().set(parseKeychainBackend(in.nextString()));
|
case "keychainBackend" -> settings.keychainBackend().set(parseKeychainBackend(in.nextString()));
|
||||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||||
|
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
|
||||||
|
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
@@ -137,5 +151,4 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
in.endArray();
|
in.endArray();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,13 +8,13 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.common.LazyInitializer;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -48,14 +48,15 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
private static final long SAVE_DELAY_MS = 1000;
|
private static final long SAVE_DELAY_MS = 1000;
|
||||||
|
|
||||||
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
||||||
private final AtomicReference<Settings> settings = new AtomicReference<>();
|
private final Supplier<Settings> settings = Suppliers.memoize(this::load);
|
||||||
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
|
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(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() //
|
||||||
@@ -66,11 +67,11 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Settings get() {
|
public Settings get() {
|
||||||
return LazyInitializer.initializeLazily(settings, this::load);
|
return settings.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Settings load() {
|
private Settings load() {
|
||||||
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings());
|
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings(env));
|
||||||
settings.setSaveCmd(this::scheduleSave);
|
settings.setSaveCmd(this::scheduleSave);
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
@@ -90,7 +91,7 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
}
|
}
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
} catch (IOException e) {
|
} catch (IOException | JsonParseException e) {
|
||||||
LOG.warn("Exception while loading settings from " + path, e);
|
LOG.warn("Exception while loading settings from " + path, e);
|
||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public enum UiTheme {
|
|||||||
AUTOMATIC("preferences.general.theme.automatic");
|
AUTOMATIC("preferences.general.theme.automatic");
|
||||||
|
|
||||||
public static UiTheme[] applicableValues() {
|
public static UiTheme[] applicableValues() {
|
||||||
if (SystemUtils.IS_OS_MAC) {
|
if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) {
|
||||||
return values();
|
return values();
|
||||||
} else {
|
} else {
|
||||||
return new UiTheme[]{LIGHT, DARK};
|
return new UiTheme[]{LIGHT, DARK};
|
||||||
|
|||||||
@@ -173,8 +173,7 @@ public class VaultSettings {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof VaultSettings && obj.getClass().equals(this.getClass())) {
|
if (obj instanceof VaultSettings other && obj.getClass().equals(this.getClass())) {
|
||||||
VaultSettings other = (VaultSettings) obj;
|
|
||||||
return Objects.equals(this.id, other.id);
|
return Objects.equals(this.id, other.id);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,50 +1,36 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
public abstract class AbstractVolume implements Volume {
|
public abstract class AbstractVolume implements Volume {
|
||||||
|
|
||||||
private final SortedSet<MountPointChooser> choosers;
|
private final Iterable<MountPointChooser> choosers;
|
||||||
|
|
||||||
protected Path mountPoint;
|
protected Path mountPoint;
|
||||||
|
|
||||||
//Cleanup
|
|
||||||
private boolean cleanupRequired;
|
private boolean cleanupRequired;
|
||||||
private MountPointChooser usedChooser;
|
private MountPointChooser usedChooser;
|
||||||
|
|
||||||
public AbstractVolume(SortedSet<MountPointChooser> choosers) {
|
public AbstractVolume(Iterable<MountPointChooser> choosers) {
|
||||||
this.choosers = choosers;
|
this.choosers = choosers;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Path determineMountPoint() throws InvalidMountPointException {
|
protected Path determineMountPoint() throws InvalidMountPointException {
|
||||||
SortedSet<MountPointChooser> checkedChoosers = new TreeSet<>(); //Natural order
|
var applicableChoosers = Iterables.filter(choosers, c -> c.isApplicable(this));
|
||||||
for (MountPointChooser chooser : this.choosers) {
|
for (var chooser : applicableChoosers) {
|
||||||
if (!chooser.isApplicable(this)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<Path> chosenPath = chooser.chooseMountPoint(this);
|
Optional<Path> chosenPath = chooser.chooseMountPoint(this);
|
||||||
checkedChoosers.add(chooser); //Consider a chooser checked if it's #chooseMountPoint() method was called
|
if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint
|
||||||
if (chosenPath.isEmpty()) {
|
|
||||||
//Chooser was applicable, but couldn't find a feasible mountpoint
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.cleanupRequired = chooser.prepare(this, chosenPath.get()); //Fail entirely if an Exception occurs
|
this.cleanupRequired = chooser.prepare(this, chosenPath.get());
|
||||||
this.usedChooser = chooser;
|
this.usedChooser = chooser;
|
||||||
return chosenPath.get();
|
return chosenPath.get();
|
||||||
}
|
}
|
||||||
//SortedSet#stream() should return a sorted stream (that's what it's docs and the docs of #spliterator() say, even if they are not 100% clear for me.)
|
throw new InvalidMountPointException(String.format("No feasible MountPoint found by choosers: %s", applicableChoosers));
|
||||||
//We want to keep that order, that's why we use ImmutableSet#toImmutableSet() to collect (even if it doesn't implement SortedSet, it's docs promise use encounter ordering.)
|
|
||||||
String checked = Joiner.on(", ").join(checkedChoosers.stream().map((mpc) -> mpc.getClass().getTypeName()).collect(ImmutableSet.toImmutableSet()));
|
|
||||||
throw new InvalidMountPointException(String.format("No feasible MountPoint found! Checked %s", checked.isBlank() ? "<No applicable MPC>" : checked));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void cleanupMountPoint() {
|
protected void cleanupMountPoint() {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
public class DokanyVolume extends AbstractVolume {
|
public class DokanyVolume extends AbstractVolume {
|
||||||
@@ -28,7 +27,7 @@ public class DokanyVolume extends AbstractVolume {
|
|||||||
private Mount mount;
|
private Mount mount;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
|
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
||||||
super(choosers);
|
super(choosers);
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.mountFactory = new MountFactory(executorService);
|
this.mountFactory = new MountFactory(executorService);
|
||||||
@@ -42,7 +41,6 @@ public class DokanyVolume extends AbstractVolume {
|
|||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
|
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
|
||||||
this.mountPoint = determineMountPoint();
|
this.mountPoint = determineMountPoint();
|
||||||
String mountName = vaultSettings.mountName().get();
|
|
||||||
try {
|
try {
|
||||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
|
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
|
||||||
} catch (MountFailedException e) {
|
} catch (MountFailedException e) {
|
||||||
@@ -54,19 +52,35 @@ public class DokanyVolume extends AbstractVolume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reveal() throws VolumeException {
|
public void reveal(Revealer revealer) throws VolumeException {
|
||||||
boolean success = mount.reveal();
|
try {
|
||||||
if (!success) {
|
mount.reveal(revealer::reveal);
|
||||||
throw new VolumeException("Reveal failed.");
|
} catch (Exception e) {
|
||||||
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unmount() {
|
public void unmount() throws VolumeException {
|
||||||
mount.close();
|
try {
|
||||||
|
mount.unmount();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
throw new VolumeException("Unmount Failed.", e);
|
||||||
|
}
|
||||||
cleanupMountPoint();
|
cleanupMountPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unmountForced() {
|
||||||
|
mount.unmountForced();
|
||||||
|
cleanupMountPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsForcedUnmount() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
return DokanyVolume.isSupportedStatic();
|
return DokanyVolume.isSupportedStatic();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.collect.Iterators;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||||
@@ -18,16 +18,19 @@ import org.slf4j.LoggerFactory;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.SortedSet;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class FuseVolume extends AbstractVolume {
|
public class FuseVolume extends AbstractVolume {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
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 Mount mount;
|
private Mount mount;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FuseVolume(@Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
|
public FuseVolume(@Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
||||||
super(choosers);
|
super(choosers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,15 +54,28 @@ public class FuseVolume extends AbstractVolume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String[] splitFlags(String str) {
|
private String[] splitFlags(String str) {
|
||||||
return Splitter.on(' ').splitToList(str).toArray(String[]::new);
|
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
|
@Override
|
||||||
public void reveal() throws VolumeException {
|
public void reveal(Revealer revealer) throws VolumeException {
|
||||||
try {
|
try {
|
||||||
mount.revealInFileManager();
|
mount.reveal(revealer::reveal);
|
||||||
} catch (CommandFailedException e) {
|
} catch (Exception e) {
|
||||||
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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.LazyInitializer;
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.vaults.Volume.VolumeException;
|
import org.cryptomator.common.vaults.Volume.VolumeException;
|
||||||
@@ -100,16 +99,12 @@ public class Vault {
|
|||||||
// Commands
|
// Commands
|
||||||
// ********************************************************************************/
|
// ********************************************************************************/
|
||||||
|
|
||||||
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
private CryptoFileSystem createCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
||||||
return LazyInitializer.initializeLazily(cryptoFileSystem, () -> unlockCryptoFileSystem(passphrase), IOException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
|
||||||
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
||||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||||
flags.add(FileSystemFlags.READONLY);
|
flags.add(FileSystemFlags.READONLY);
|
||||||
}
|
}
|
||||||
if (vaultSettings.filenameLengthLimit().get() == -1) {
|
if (!flags.contains(FileSystemFlags.READONLY) && vaultSettings.filenameLengthLimit().get() == -1) {
|
||||||
LOG.debug("Determining file name length limitations...");
|
LOG.debug("Determining file name length limitations...");
|
||||||
int limit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(getPath());
|
int limit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(getPath());
|
||||||
vaultSettings.filenameLengthLimit().set(limit);
|
vaultSettings.filenameLengthLimit().set(limit);
|
||||||
@@ -126,18 +121,8 @@ public class Vault {
|
|||||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
private void destroyCryptoFileSystem() {
|
||||||
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
LOG.trace("Trying to close associated CryptoFS...");
|
||||||
volume = volumeProvider.get();
|
|
||||||
volume.mount(fs, getEffectiveMountFlags());
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void lock(boolean forced) throws VolumeException {
|
|
||||||
if (forced && volume.supportsForcedUnmount()) {
|
|
||||||
volume.unmountForced();
|
|
||||||
} else {
|
|
||||||
volume.unmount();
|
|
||||||
}
|
|
||||||
CryptoFileSystem fs = cryptoFileSystem.getAndSet(null);
|
CryptoFileSystem fs = cryptoFileSystem.getAndSet(null);
|
||||||
if (fs != null) {
|
if (fs != null) {
|
||||||
try {
|
try {
|
||||||
@@ -148,8 +133,33 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reveal() throws VolumeException {
|
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
||||||
volume.reveal();
|
if (cryptoFileSystem.get() == null) {
|
||||||
|
CryptoFileSystem fs = createCryptoFileSystem(passphrase);
|
||||||
|
cryptoFileSystem.set(fs);
|
||||||
|
try {
|
||||||
|
volume = volumeProvider.get();
|
||||||
|
volume.mount(fs, getEffectiveMountFlags());
|
||||||
|
} catch (Exception e) {
|
||||||
|
destroyCryptoFileSystem();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Already unlocked.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void lock(boolean forced) throws VolumeException {
|
||||||
|
if (forced && volume.supportsForcedUnmount()) {
|
||||||
|
volume.unmountForced();
|
||||||
|
} else {
|
||||||
|
volume.unmount();
|
||||||
|
}
|
||||||
|
destroyCryptoFileSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reveal(Volume.Revealer vaultRevealer) throws VolumeException {
|
||||||
|
volume.reveal(vaultRevealer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
@@ -345,8 +355,7 @@ public class Vault {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof Vault && obj.getClass().equals(this.getClass())) {
|
if (obj instanceof Vault other && obj.getClass().equals(this.getClass())) {
|
||||||
final Vault other = (Vault) obj;
|
|
||||||
return Objects.equals(this.vaultSettings, other.vaultSettings);
|
return Objects.equals(this.vaultSettings, other.vaultSettings);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+2
-2
@@ -22,10 +22,10 @@ class VaultListChangeListener implements ListChangeListener<Vault> {
|
|||||||
public void onChanged(Change<? extends Vault> c) {
|
public void onChanged(Change<? extends Vault> c) {
|
||||||
while (c.next()) {
|
while (c.next()) {
|
||||||
if (c.wasAdded()) {
|
if (c.wasAdded()) {
|
||||||
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).toList();
|
||||||
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
||||||
} else if (c.wasRemoved()) {
|
} else if (c.wasRemoved()) {
|
||||||
List<VaultSettings> removedSettings = c.getRemoved().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
List<VaultSettings> removedSettings = c.getRemoved().stream().map(Vault::getVaultSettings).toList();
|
||||||
vaultSettingsList.removeAll(removedSettings);
|
vaultSettingsList.removeAll(removedSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class VaultListManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addAll(Collection<VaultSettings> vaultSettings) {
|
private void addAll(Collection<VaultSettings> vaultSettings) {
|
||||||
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
|
Collection<Vault> vaults = vaultSettings.stream().map(this::create).toList();
|
||||||
vaultList.addAll(vaults);
|
vaultList.addAll(vaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class VaultModule {
|
|||||||
if (readOnly.get()) {
|
if (readOnly.get()) {
|
||||||
flags.append(" -ordonly");
|
flags.append(" -ordonly");
|
||||||
}
|
}
|
||||||
flags.append(" -ovolname=").append(mountName.get());
|
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
|
||||||
flags.append(" -oatomic_o_trunc");
|
flags.append(" -oatomic_o_trunc");
|
||||||
flags.append(" -oauto_xattr");
|
flags.append(" -oauto_xattr");
|
||||||
flags.append(" -oauto_cache");
|
flags.append(" -oauto_cache");
|
||||||
@@ -156,9 +156,9 @@ public class VaultModule {
|
|||||||
//See: https://github.com/billziss-gh/winfsp/issues/319
|
//See: https://github.com/billziss-gh/winfsp/issues/319
|
||||||
if (!readOnly.get()) {
|
if (!readOnly.get()) {
|
||||||
flags.append(" -ouid=-1");
|
flags.append(" -ouid=-1");
|
||||||
flags.append(" -ogid=-1");
|
flags.append(" -ogid=11");
|
||||||
}
|
}
|
||||||
flags.append(" -ovolname=").append(mountName.get());
|
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
|
||||||
//Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
|
//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.
|
//but the option exists. Let's keep this here in case we need it.
|
||||||
// flags.append(" -oThreadCount=").append(5);
|
// flags.append(" -oThreadCount=").append(5);
|
||||||
|
|||||||
@@ -34,7 +34,15 @@ public interface Volume {
|
|||||||
*/
|
*/
|
||||||
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException, InvalidMountPointException;
|
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException, InvalidMountPointException;
|
||||||
|
|
||||||
void reveal() throws VolumeException;
|
/**
|
||||||
|
* 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;
|
void unmount() throws VolumeException;
|
||||||
|
|
||||||
@@ -79,4 +87,14 @@ public interface Volume {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides and unifies the different Revealer implementations in the different nio-adapters.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
interface Revealer {
|
||||||
|
|
||||||
|
void reveal(Path p) throws VolumeException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import java.net.InetAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class WebDavVolume implements Volume {
|
public class WebDavVolume implements Volume {
|
||||||
|
|
||||||
@@ -25,21 +26,27 @@ public class WebDavVolume implements Volume {
|
|||||||
private final Provider<WebDavServer> serverProvider;
|
private final Provider<WebDavServer> serverProvider;
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
|
|
||||||
private WebDavServer server;
|
private WebDavServer server;
|
||||||
private WebDavServletController servlet;
|
private WebDavServletController servlet;
|
||||||
private Mounter.Mount mount;
|
private Mounter.Mount mount;
|
||||||
private Path mountPoint;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings) {
|
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
|
||||||
this.serverProvider = serverProvider;
|
this.serverProvider = serverProvider;
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException {
|
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException {
|
||||||
|
startServlet(fs);
|
||||||
|
mountServlet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServlet(CryptoFileSystem fs){
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
server = serverProvider.get();
|
server = serverProvider.get();
|
||||||
}
|
}
|
||||||
@@ -50,32 +57,38 @@ public class WebDavVolume implements Volume {
|
|||||||
String urlConformMountName = acceptable.negate().collapseFrom(vaultSettings.mountName().get(), '_');
|
String urlConformMountName = acceptable.negate().collapseFrom(vaultSettings.mountName().get(), '_');
|
||||||
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + urlConformMountName);
|
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + urlConformMountName);
|
||||||
servlet.start();
|
servlet.start();
|
||||||
mount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mount() throws VolumeException {
|
private void mountServlet() throws VolumeException {
|
||||||
if (servlet == null) {
|
if (servlet == null) {
|
||||||
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
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 specifc one or there is no free.
|
||||||
|
Supplier<String> driveLetterSupplier;
|
||||||
|
if(System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
|
||||||
|
driveLetterSupplier = () -> windowsDriveLetters.getAvailableDriveLetter().orElse(null);
|
||||||
|
} else {
|
||||||
|
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
|
||||||
|
}
|
||||||
|
|
||||||
MountParams mountParams = MountParams.create() //
|
MountParams mountParams = MountParams.create() //
|
||||||
.withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) //
|
.withWindowsDriveLetter(driveLetterSupplier.get()) //
|
||||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
|
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
|
||||||
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
this.mount = servlet.mount(mountParams); // might block this thread for a while
|
this.mount = servlet.mount(mountParams); // might block this thread for a while
|
||||||
} catch (Mounter.CommandFailedException e) {
|
} catch (Mounter.CommandFailedException e) {
|
||||||
e.printStackTrace();
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reveal() throws VolumeException {
|
public void reveal(Revealer revealer) throws VolumeException {
|
||||||
try {
|
try {
|
||||||
mount.reveal();
|
mount.reveal(revealer::reveal);
|
||||||
} catch (Mounter.CommandFailedException e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +115,7 @@ public class WebDavVolume implements Volume {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Path> getMountPoint() {
|
public Optional<Path> getMountPoint() {
|
||||||
return Optional.ofNullable(mountPoint); //TODO
|
return mount.getMountPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -20,14 +21,10 @@ class EnvironmentTest {
|
|||||||
|
|
||||||
private Environment env;
|
private Environment env;
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
static void init() {
|
|
||||||
System.setProperty("user.home", "/home/testuser");
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void initEach() {
|
void init() {
|
||||||
env = new Environment();
|
env = Mockito.spy(new Environment());
|
||||||
|
Mockito.when(env.getHomeDir()).thenReturn(Path.of("/home/testuser"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -35,7 +32,7 @@ class EnvironmentTest {
|
|||||||
public void testSettingsPath() {
|
public void testSettingsPath() {
|
||||||
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
|
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
|
||||||
|
|
||||||
List<Path> result = env.getSettingsPath().collect(Collectors.toList());
|
List<Path> result = env.getSettingsPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
|
||||||
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
||||||
@@ -46,7 +43,7 @@ class EnvironmentTest {
|
|||||||
public void testIpcPortPath() {
|
public void testIpcPortPath() {
|
||||||
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
|
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
|
||||||
|
|
||||||
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
|
List<Path> result = env.getIpcPortPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
|
||||||
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
||||||
@@ -57,7 +54,7 @@ class EnvironmentTest {
|
|||||||
public void testKeychainPath() {
|
public void testKeychainPath() {
|
||||||
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
|
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
|
||||||
|
|
||||||
List<Path> result = env.getKeychainPath().collect(Collectors.toList());
|
List<Path> result = env.getKeychainPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
|
||||||
}
|
}
|
||||||
@@ -91,7 +88,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=")
|
@DisplayName("test.path.property=")
|
||||||
public void testEmptyList() {
|
public void testEmptyList() {
|
||||||
System.setProperty("test.path.property", "");
|
System.setProperty("test.path.property", "");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(0));
|
MatcherAssert.assertThat(result, Matchers.hasSize(0));
|
||||||
}
|
}
|
||||||
@@ -100,7 +97,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=/foo/bar/test")
|
@DisplayName("test.path.property=/foo/bar/test")
|
||||||
public void testSingleAbsolutePath() {
|
public void testSingleAbsolutePath() {
|
||||||
System.setProperty("test.path.property", "/foo/bar/test");
|
System.setProperty("test.path.property", "/foo/bar/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
||||||
@@ -110,7 +107,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=~/test")
|
@DisplayName("test.path.property=~/test")
|
||||||
public void testSingleHomeRelativePath() {
|
public void testSingleHomeRelativePath() {
|
||||||
System.setProperty("test.path.property", "~/test");
|
System.setProperty("test.path.property", "~/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
|
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
|
||||||
@@ -120,7 +117,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
|
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
|
||||||
public void testMultiplePaths() {
|
public void testMultiplePaths() {
|
||||||
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
|
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import java.util.Optional;
|
|||||||
|
|
||||||
class LicenseCheckerTest {
|
class LicenseCheckerTest {
|
||||||
|
|
||||||
private static final String PUBLIC_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ" //
|
private static final String PUBLIC_KEY = """
|
||||||
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
|
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\
|
||||||
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
|
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\
|
||||||
+ "Al8G7CqwoJOsW7Kddns=";
|
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\
|
||||||
|
Al8G7CqwoJOsW7Kddns=\
|
||||||
|
""";
|
||||||
|
|
||||||
private LicenseChecker licenseChecker;
|
private LicenseChecker licenseChecker;
|
||||||
|
|
||||||
|
|||||||
@@ -44,4 +44,9 @@ class MapKeychainAccess implements KeychainAccessProvider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-8
@@ -5,26 +5,34 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class SettingsJsonAdapterTest {
|
public class SettingsJsonAdapterTest {
|
||||||
|
|
||||||
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter();
|
private final Environment env = Mockito.mock(Environment.class);
|
||||||
|
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter(env);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeserialize() throws IOException {
|
public void testDeserialize() throws IOException {
|
||||||
String vault1Json = "{\"id\": \"1\", \"path\": \"/vault1\", \"mountName\": \"vault1\", \"winDriveLetter\": \"X\"}";
|
String json = """
|
||||||
String vault2Json = "{\"id\": \"2\", \"path\": \"/vault2\", \"mountName\": \"vault2\", \"winDriveLetter\": \"Y\"}";
|
{
|
||||||
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
|
"directories": [
|
||||||
+ "\"checkForUpdatesEnabled\": true,"//
|
{"id": "1", "path": "/vault1", "mountName": "vault1", "winDriveLetter": "X"},
|
||||||
+ "\"port\": 8080,"//
|
{"id": "2", "path": "/vault2", "mountName": "vault2", "winDriveLetter": "Y"}
|
||||||
+ "\"numTrayNotifications\": 42,"//
|
],
|
||||||
+ "\"preferredVolumeImpl\": \"FUSE\"}";
|
"checkForUpdatesEnabled": true,
|
||||||
|
"port": 8080,
|
||||||
|
"numTrayNotifications": 42,
|
||||||
|
"preferredVolumeImpl": "FUSE"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
Settings settings = adapter.fromJson(json);
|
Settings settings = adapter.fromJson(json);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
@@ -14,8 +15,10 @@ public class SettingsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAutoSave() {
|
public void testAutoSave() {
|
||||||
|
Environment env = Mockito.mock(Environment.class);
|
||||||
@SuppressWarnings("unchecked") Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
|
@SuppressWarnings("unchecked") Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
|
||||||
Settings settings = new Settings();
|
|
||||||
|
Settings settings = new Settings(env);
|
||||||
settings.setSaveCmd(changeListener);
|
settings.setSaveCmd(changeListener);
|
||||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||||
Mockito.verify(changeListener, Mockito.times(0)).accept(settings);
|
Mockito.verify(changeListener, Mockito.times(0)).accept(settings);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class VaultModuleTest {
|
|||||||
|
|
||||||
StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings);
|
StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings);
|
||||||
|
|
||||||
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ovolname=TEST"));
|
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ovolname=\"TEST\""));
|
||||||
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ordonly"));
|
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ordonly"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.9</version>
|
<version>1.6.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>launcher</artifactId>
|
<artifactId>launcher</artifactId>
|
||||||
<name>Cryptomator Launcher</name>
|
<name>Cryptomator Launcher</name>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class FileOpenRequestHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openFiles(OpenFilesEvent evt) {
|
private void openFiles(OpenFilesEvent evt) {
|
||||||
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).collect(Collectors.toList());
|
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).toList();
|
||||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||||
tryToEnqueueFileOpenRequest(launchEvent);
|
tryToEnqueueFileOpenRequest(launchEvent);
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ class FileOpenRequestHandler {
|
|||||||
LOG.trace("Argument not a valid path: {}", str);
|
LOG.trace("Argument not a valid path: {}", str);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
}).filter(Objects::nonNull).toList();
|
||||||
if (!pathsToOpen.isEmpty()) {
|
if (!pathsToOpen.isEmpty()) {
|
||||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||||
tryToEnqueueFileOpenRequest(launchEvent);
|
tryToEnqueueFileOpenRequest(launchEvent);
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ public class LoggerModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
static LoggerContext provideLoggerContext() {
|
static LoggerContext provideLoggerContext() {
|
||||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||||
if (loggerFactory instanceof LoggerContext) {
|
if (loggerFactory instanceof LoggerContext context) {
|
||||||
return (LoggerContext) loggerFactory;
|
return context;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("SLF4J not bound to Logback.");
|
throw new IllegalStateException("SLF4J not bound to Logback.");
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-19
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.9</version>
|
<version>1.6.0-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Cryptomator</name>
|
<name>Cryptomator</name>
|
||||||
|
|
||||||
@@ -22,24 +22,25 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.jdk.version>16</project.jdk.version>
|
||||||
|
|
||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptofs.version>1.9.13</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>1.9.14</cryptomator.cryptofs.version>
|
||||||
<cryptomator.integrations.version>0.1.6</cryptomator.integrations.version>
|
<cryptomator.integrations.version>1.0.0-beta2</cryptomator.integrations.version>
|
||||||
<cryptomator.integrations.win.version>0.1.0-beta1</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.0.0-beta2</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>0.1.0-beta3</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.0.0-beta2</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>0.1.0-beta2</cryptomator.integrations.linux.version>
|
<cryptomator.integrations.linux.version>1.0.0-beta1</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>1.2.5</cryptomator.fuse.version>
|
<cryptomator.fuse.version>1.2.9</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>1.2.0</cryptomator.dokany.version>
|
<cryptomator.dokany.version>1.2.4</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>1.0.13</cryptomator.webdav.version>
|
<cryptomator.webdav.version>1.1.4</cryptomator.webdav.version>
|
||||||
|
|
||||||
<!-- 3rd party dependencies -->
|
<!-- 3rd party dependencies -->
|
||||||
<javafx.version>15</javafx.version>
|
<javafx.version>15</javafx.version>
|
||||||
<commons-lang3.version>3.11</commons-lang3.version>
|
<commons-lang3.version>3.11</commons-lang3.version>
|
||||||
<jwt.version>3.11.0</jwt.version>
|
<jwt.version>3.12.0</jwt.version>
|
||||||
<easybind.version>2.1.0</easybind.version>
|
<easybind.version>2.1.0</easybind.version>
|
||||||
<guava.version>30.0-jre</guava.version>
|
<guava.version>30.0-jre</guava.version>
|
||||||
<dagger.version>2.29.1</dagger.version>
|
<dagger.version>2.32</dagger.version>
|
||||||
<gson.version>2.8.6</gson.version>
|
<gson.version>2.8.6</gson.version>
|
||||||
<slf4j.version>1.7.30</slf4j.version>
|
<slf4j.version>1.7.30</slf4j.version>
|
||||||
<logback.version>1.2.3</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
@@ -50,13 +51,6 @@
|
|||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>jcenter</id>
|
|
||||||
<url>https://jcenter.bintray.com</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- modules -->
|
<!-- modules -->
|
||||||
@@ -223,6 +217,13 @@
|
|||||||
<version>${javafx.version}</version>
|
<version>${javafx.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- TODO: temporary fix for XXE attack, can be removed once java-jwt is updated -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.10.5.1</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -325,6 +326,32 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</profile>
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>dependency-check</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.owasp</groupId>
|
||||||
|
<artifactId>dependency-check-maven</artifactId>
|
||||||
|
<version>6.0.3</version>
|
||||||
|
<configuration>
|
||||||
|
<cveValidForHours>24</cveValidForHours>
|
||||||
|
<failBuildOnCVSS>0</failBuildOnCVSS>
|
||||||
|
<skipTestScope>true</skipTestScope>
|
||||||
|
<detail>true</detail>
|
||||||
|
<suppressionFile>suppression.xml</suppressionFile>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -404,7 +431,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>14</release>
|
<release>${project.jdk.version}</release>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
<path>
|
<path>
|
||||||
<groupId>com.google.dagger</groupId>
|
<groupId>com.google.dagger</groupId>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- This file lists false positives found by org.owasp:dependency-check-maven build plugin -->
|
||||||
|
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.2.xsd">
|
||||||
|
<suppress>
|
||||||
|
<notes><![CDATA[ Upstream fix backported from 2.11.0 to 2.10.5.1, see https://github.com/FasterXML/jackson-databind/issues/2589#issuecomment-714833837. ]]></notes>
|
||||||
|
<gav>com.fasterxml.jackson.core:jackson-databind:2.10.5.1</gav>
|
||||||
|
<cve>CVE-2020-25649</cve>
|
||||||
|
</suppress>
|
||||||
|
<suppress>
|
||||||
|
<notes><![CDATA[ Suppress known vulnerabilities in FUSE libraries for fuse-nio-adapter. For more info, see suppression.xml of https://github.com/cryptomator/fuse-nio-adapter ]]></notes>
|
||||||
|
<gav regex="true">^org\.cryptomator:fuse-nio-adapter:.*$</gav>
|
||||||
|
<cvssBelow>9</cvssBelow>
|
||||||
|
</suppress>
|
||||||
|
<suppress>
|
||||||
|
<notes><![CDATA[ Suppress known vulnerabilities in FUSE libraries for jnr-fuse (dependency of fuse-nio-adapter). ]]></notes>
|
||||||
|
<gav regex="true">^com\.github\.serceman:jnr-fuse:.*$</gav>
|
||||||
|
<cvssBelow>9</cvssBelow>
|
||||||
|
</suppress>
|
||||||
|
</suppressions>
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.9</version>
|
<version>1.6.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>ui</artifactId>
|
<artifactId>ui</artifactId>
|
||||||
<name>Cryptomator GUI</name>
|
<name>Cryptomator GUI</name>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
|||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -43,8 +43,8 @@ public abstract class AddVaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@AddVaultWizardWindow
|
@AddVaultWizardWindow
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -91,50 +91,50 @@ public abstract class AddVaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideWelcomeScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideWelcomeScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_NAME.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_LOCATION.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_LOCATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_PASSWORD.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultRecoveryKeyScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultRecoveryKeyScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_SUCCESS)
|
@FxmlScene(FxmlFile.ADDVAULT_SUCCESS)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
+3
-4
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.addvaultwizard;
|
package org.cryptomator.ui.addvaultwizard;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.ui.common.ErrorComponent;
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
@@ -84,11 +83,11 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
public void initialize() {
|
public void initialize() {
|
||||||
predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
||||||
usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
||||||
EasyBind.subscribe(vaultPath, this::vaultPathDidChange);
|
vaultPath.addListener(this::vaultPathDidChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void vaultPathDidChange(Path newValue) {
|
private void vaultPathDidChange(@SuppressWarnings("unused") ObservableValue<? extends Path> observable, @SuppressWarnings("unused") Path oldValue, Path newValue) {
|
||||||
if (newValue != null && !Files.notExists(newValue)) {
|
if (!Files.notExists(newValue)) {
|
||||||
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
|
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
|
||||||
} else {
|
} else {
|
||||||
warningText.set(null);
|
warningText.set(null);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -37,8 +37,8 @@ abstract class ChangePasswordModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@ChangePasswordWindow
|
@ChangePasswordWindow
|
||||||
@ChangePasswordScoped
|
@ChangePasswordScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -56,8 +56,8 @@ abstract class ChangePasswordModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.CHANGEPASSWORD)
|
@FxmlScene(FxmlFile.CHANGEPASSWORD)
|
||||||
@ChangePasswordScoped
|
@ChangePasswordScoped
|
||||||
static Scene provideUnlockScene(@ChangePasswordWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideUnlockScene(@ChangePasswordWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/changepassword.fxml");
|
return fxmlLoaders.createScene(FxmlFile.CHANGEPASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ public class DefaultSceneFactory implements Function<Parent, Scene> {
|
|||||||
protected void configureScene(Scene scene) {
|
protected void configureScene(Scene scene) {
|
||||||
scene.windowProperty().addListener(observable -> {
|
scene.windowProperty().addListener(observable -> {
|
||||||
Window window = scene.getWindow();
|
Window window = scene.getWindow();
|
||||||
if (window instanceof Stage) {
|
if (window instanceof Stage s) {
|
||||||
setupDefaultAccelerators(scene, (Stage) window);
|
setupDefaultAccelerators(scene, s);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import java.util.ResourceBundle;
|
|||||||
abstract class ErrorModule {
|
abstract class ErrorModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -38,8 +38,8 @@ abstract class ErrorModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ERROR)
|
@FxmlScene(FxmlFile.ERROR)
|
||||||
static Scene provideErrorScene(FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideErrorScene(FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ERROR.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ public enum FxmlFile {
|
|||||||
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
|
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
|
||||||
ERROR("/fxml/error.fxml"), //
|
ERROR("/fxml/error.fxml"), //
|
||||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||||
|
LOCK_FORCED("/fxml/lock_forced.fxml"), //
|
||||||
|
LOCK_FAILED("/fxml/lock_failed.fxml"), //
|
||||||
MAIN_WINDOW("/fxml/main_window.fxml"), //
|
MAIN_WINDOW("/fxml/main_window.fxml"), //
|
||||||
MIGRATION_CAPABILITY_ERROR("/fxml/migration_capability_error.fxml"), //
|
MIGRATION_CAPABILITY_ERROR("/fxml/migration_capability_error.fxml"), //
|
||||||
MIGRATION_IMPOSSIBLE("/fxml/migration_impossible.fxml"),
|
MIGRATION_IMPOSSIBLE("/fxml/migration_impossible.fxml"),
|
||||||
@@ -37,7 +39,7 @@ public enum FxmlFile {
|
|||||||
this.ressourcePathString = ressourcePathString;
|
this.ressourcePathString = ressourcePathString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRessourcePathString() {
|
String getRessourcePathString() {
|
||||||
return ressourcePathString;
|
return ressourcePathString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -11,13 +11,13 @@ import java.util.Map;
|
|||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class FXMLLoaderFactory {
|
public class FxmlLoaderFactory {
|
||||||
|
|
||||||
private final Map<Class<? extends FxController>, Provider<FxController>> controllerFactories;
|
private final Map<Class<? extends FxController>, Provider<FxController>> controllerFactories;
|
||||||
private final Function<Parent, Scene> sceneFactory;
|
private final Function<Parent, Scene> sceneFactory;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
public FXMLLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> controllerFactories, Function<Parent, Scene> sceneFactory, ResourceBundle resourceBundle) {
|
public FxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> controllerFactories, Function<Parent, Scene> sceneFactory, ResourceBundle resourceBundle) {
|
||||||
this.controllerFactories = controllerFactories;
|
this.controllerFactories = controllerFactories;
|
||||||
this.sceneFactory = sceneFactory;
|
this.sceneFactory = sceneFactory;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
@@ -48,13 +48,17 @@ public class FXMLLoaderFactory {
|
|||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Scene createScene(FxmlFile fxmlFile) {
|
||||||
|
return createScene(fxmlFile.getRessourcePathString());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link #load(String) Loads} the FXML file and creates a new Scene containing the loaded ui.
|
* {@link #load(String) Loads} the FXML file and creates a new Scene containing the loaded ui.
|
||||||
*
|
*
|
||||||
* @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}).
|
* @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}).
|
||||||
* @throws UncheckedIOException wrapping any IOException thrown by {@link #load(String)).
|
* @throws UncheckedIOException wrapping any IOException thrown by {@link #load(String)).
|
||||||
*/
|
*/
|
||||||
public Scene createScene(String fxmlResourceName) {
|
private Scene createScene(String fxmlResourceName) {
|
||||||
final FXMLLoader loader;
|
final FXMLLoader loader;
|
||||||
try {
|
try {
|
||||||
loader = load(fxmlResourceName);
|
loader = load(fxmlResourceName);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@FxApplicationScoped
|
||||||
|
public class HostServiceRevealer implements Volume.Revealer {
|
||||||
|
|
||||||
|
private final Lazy<Application> application;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HostServiceRevealer(Lazy<Application> application) {
|
||||||
|
this.application = application;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reveal(Path p) throws Volume.VolumeException {
|
||||||
|
application.get().getHostServices().showDocument(p.toUri().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,10 +23,12 @@ public class VaultService {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
|
||||||
|
|
||||||
private final ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
|
private final HostServiceRevealer vaultRevealer;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultService(ExecutorService executorService) {
|
public VaultService(ExecutorService executorService, HostServiceRevealer vaultRevealer) {
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
|
this.vaultRevealer = vaultRevealer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reveal(Vault vault) {
|
public void reveal(Vault vault) {
|
||||||
@@ -39,7 +41,7 @@ public class VaultService {
|
|||||||
* @param vault The vault to reveal
|
* @param vault The vault to reveal
|
||||||
*/
|
*/
|
||||||
public Task<Vault> createRevealTask(Vault vault) {
|
public Task<Vault> createRevealTask(Vault vault) {
|
||||||
Task<Vault> task = new RevealVaultTask(vault);
|
Task<Vault> task = new RevealVaultTask(vault, vaultRevealer);
|
||||||
task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayName()));
|
task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayName()));
|
||||||
task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), evt.getSource().getException()));
|
task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), evt.getSource().getException()));
|
||||||
return task;
|
return task;
|
||||||
@@ -65,6 +67,7 @@ public class VaultService {
|
|||||||
public Task<Vault> createLockTask(Vault vault, boolean forced) {
|
public Task<Vault> createLockTask(Vault vault, boolean forced) {
|
||||||
Task<Vault> task = new LockVaultTask(vault, forced);
|
Task<Vault> task = new LockVaultTask(vault, forced);
|
||||||
task.setOnSucceeded(evt -> LOG.info("Locked {}", vault.getDisplayName()));
|
task.setOnSucceeded(evt -> LOG.info("Locked {}", vault.getDisplayName()));
|
||||||
|
task.setOnFailed(evt -> LOG.info("Failed to lock {}.", vault.getDisplayName(), evt.getSource().getException()));
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,19 +101,22 @@ public class VaultService {
|
|||||||
private static class RevealVaultTask extends Task<Vault> {
|
private static class RevealVaultTask extends Task<Vault> {
|
||||||
|
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
|
private final Volume.Revealer revealer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param vault The vault to lock
|
* @param vault The vault to lock
|
||||||
|
* @param revealer The object to use to show the vault content to the user.
|
||||||
*/
|
*/
|
||||||
public RevealVaultTask(Vault vault) {
|
public RevealVaultTask(Vault vault, Volume.Revealer revealer) {
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
|
this.revealer = revealer;
|
||||||
|
|
||||||
setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), getException()));
|
setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), getException()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Vault call() throws Volume.VolumeException {
|
protected Vault call() throws Volume.VolumeException {
|
||||||
vault.reveal();
|
vault.reveal(revealer);
|
||||||
return vault;
|
return vault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -29,8 +29,8 @@ abstract class ForgetPasswordModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@ForgetPasswordWindow
|
@ForgetPasswordWindow
|
||||||
@ForgetPasswordScoped
|
@ForgetPasswordScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -62,8 +62,8 @@ abstract class ForgetPasswordModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.FORGET_PASSWORD)
|
@FxmlScene(FxmlFile.FORGET_PASSWORD)
|
||||||
@ForgetPasswordScoped
|
@ForgetPasswordScoped
|
||||||
static Scene provideForgetPasswordScene(@ForgetPasswordWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideForgetPasswordScene(@ForgetPasswordWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/forget_password.fxml");
|
return fxmlLoaders.createScene(FxmlFile.FORGET_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.fxapp;
|
package org.cryptomator.ui.fxapp;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.LicenseHolder;
|
import org.cryptomator.common.LicenseHolder;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
@@ -12,6 +11,7 @@ import org.cryptomator.integrations.uiappearance.UiAppearanceException;
|
|||||||
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
|
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
|
||||||
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
|
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
|
import org.cryptomator.ui.lock.LockComponent;
|
||||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
@@ -27,10 +27,13 @@ import javafx.application.Platform;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ObservableSet;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
import java.awt.desktop.QuitResponse;
|
import java.awt.desktop.QuitResponse;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
|
||||||
@FxApplicationScoped
|
@FxApplicationScoped
|
||||||
public class FxApplication extends Application {
|
public class FxApplication extends Application {
|
||||||
@@ -40,34 +43,38 @@ public class FxApplication extends Application {
|
|||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final Lazy<MainWindowComponent> mainWindow;
|
private final Lazy<MainWindowComponent> mainWindow;
|
||||||
private final Lazy<PreferencesComponent> preferencesWindow;
|
private final Lazy<PreferencesComponent> preferencesWindow;
|
||||||
|
private final Lazy<QuitComponent> quitWindow;
|
||||||
private final Provider<UnlockComponent.Builder> unlockWindowBuilderProvider;
|
private final Provider<UnlockComponent.Builder> unlockWindowBuilderProvider;
|
||||||
private final Provider<QuitComponent.Builder> quitWindowBuilderProvider;
|
private final Provider<LockComponent.Builder> lockWindowBuilderProvider;
|
||||||
private final Optional<TrayIntegrationProvider> trayIntegration;
|
private final Optional<TrayIntegrationProvider> trayIntegration;
|
||||||
private final Optional<UiAppearanceProvider> appearanceProvider;
|
private final Optional<UiAppearanceProvider> appearanceProvider;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
private final LicenseHolder licenseHolder;
|
private final LicenseHolder licenseHolder;
|
||||||
private final BooleanBinding hasVisibleStages;
|
private final ObservableList<Window> visibleWindows;
|
||||||
|
private final BooleanBinding hasVisibleWindows;
|
||||||
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
|
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<QuitComponent.Builder> quitWindowBuilderProvider, Optional<TrayIntegrationProvider> trayIntegration, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet<Stage> visibleStages) {
|
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<LockComponent.Builder> lockWindowBuilderProvider, Lazy<QuitComponent> quitWindow, Optional<TrayIntegrationProvider> trayIntegration, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.preferencesWindow = preferencesWindow;
|
this.preferencesWindow = preferencesWindow;
|
||||||
this.unlockWindowBuilderProvider = unlockWindowBuilderProvider;
|
this.unlockWindowBuilderProvider = unlockWindowBuilderProvider;
|
||||||
this.quitWindowBuilderProvider = quitWindowBuilderProvider;
|
this.lockWindowBuilderProvider = lockWindowBuilderProvider;
|
||||||
|
this.quitWindow = quitWindow;
|
||||||
this.trayIntegration = trayIntegration;
|
this.trayIntegration = trayIntegration;
|
||||||
this.appearanceProvider = appearanceProvider;
|
this.appearanceProvider = appearanceProvider;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
this.licenseHolder = licenseHolder;
|
this.licenseHolder = licenseHolder;
|
||||||
this.hasVisibleStages = Bindings.isNotEmpty(visibleStages);
|
this.visibleWindows = Stage.getWindows().filtered(Window::isShowing);
|
||||||
|
this.hasVisibleWindows = Bindings.isNotEmpty(visibleWindows);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
LOG.trace("FxApplication.start()");
|
LOG.trace("FxApplication.start()");
|
||||||
Platform.setImplicitExit(false);
|
Platform.setImplicitExit(false);
|
||||||
|
|
||||||
EasyBind.subscribe(hasVisibleStages, this::hasVisibleStagesChanged);
|
hasVisibleWindows.addListener(this::hasVisibleStagesChanged);
|
||||||
|
|
||||||
settings.theme().addListener(this::appThemeChanged);
|
settings.theme().addListener(this::appThemeChanged);
|
||||||
loadSelectedStyleSheet(settings.theme().get());
|
loadSelectedStyleSheet(settings.theme().get());
|
||||||
@@ -78,7 +85,8 @@ public class FxApplication extends Application {
|
|||||||
throw new UnsupportedOperationException("Use start() instead.");
|
throw new UnsupportedOperationException("Use start() instead.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hasVisibleStagesChanged(boolean newValue) {
|
private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
|
||||||
|
LOG.debug("has visible stages: {}", newValue);
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
|
trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
|
||||||
} else {
|
} else {
|
||||||
@@ -93,11 +101,14 @@ public class FxApplication extends Application {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showMainWindow() {
|
public CompletionStage<Stage> showMainWindow() {
|
||||||
|
CompletableFuture<Stage> future = new CompletableFuture<>();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
mainWindow.get().showMainWindow();
|
var win = mainWindow.get().showMainWindow();
|
||||||
LOG.debug("Showing MainWindow");
|
LOG.debug("Showing MainWindow");
|
||||||
|
future.complete(win);
|
||||||
});
|
});
|
||||||
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startUnlockWorkflow(Vault vault, Optional<Stage> owner) {
|
public void startUnlockWorkflow(Vault vault, Optional<Stage> owner) {
|
||||||
@@ -107,9 +118,16 @@ public class FxApplication extends Application {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void startLockWorkflow(Vault vault, Optional<Stage> owner) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
lockWindowBuilderProvider.get().vault(vault).owner(owner).build().startLockWorkflow();
|
||||||
|
LOG.debug("Start lock workflow for {}", vault.getDisplayName());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void showQuitWindow(QuitResponse response) {
|
public void showQuitWindow(QuitResponse response) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
quitWindowBuilderProvider.get().quitResponse(response).build().showQuitWindow();
|
quitWindow.get().showQuitWindow(response);
|
||||||
LOG.debug("Showing QuitWindow");
|
LOG.debug("Showing QuitWindow");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -119,13 +137,13 @@ public class FxApplication extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
|
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
|
||||||
appearanceProvider.ifPresent(appearanceProvider -> {
|
if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
|
||||||
try {
|
try {
|
||||||
appearanceProvider.removeListener(systemInterfaceThemeListener);
|
appearanceProvider.get().removeListener(systemInterfaceThemeListener);
|
||||||
} catch (UiAppearanceException e) {
|
} catch (UiAppearanceException e) {
|
||||||
LOG.error("Failed to disable automatic theme switching.");
|
LOG.error("Failed to disable automatic theme switching.");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
loadSelectedStyleSheet(newValue);
|
loadSelectedStyleSheet(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,8 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.fxapp;
|
package org.cryptomator.ui.fxapp;
|
||||||
|
|
||||||
import dagger.BindsInstance;
|
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
@FxApplicationScoped
|
@FxApplicationScoped
|
||||||
@Subcomponent(modules = FxApplicationModule.class)
|
@Subcomponent(modules = FxApplicationModule.class)
|
||||||
public interface FxApplicationComponent {
|
public interface FxApplicationComponent {
|
||||||
@@ -19,9 +16,6 @@ public interface FxApplicationComponent {
|
|||||||
@Subcomponent.Builder
|
@Subcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
Builder trayMenuSupported(@Named("trayMenuSupported") boolean trayMenuSupported);
|
|
||||||
|
|
||||||
FxApplicationComponent build();
|
FxApplicationComponent build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import dagger.Provides;
|
|||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.ui.common.ErrorComponent;
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
import org.cryptomator.ui.common.StageFactory;
|
import org.cryptomator.ui.common.StageFactory;
|
||||||
|
import org.cryptomator.ui.lock.LockComponent;
|
||||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||||
import org.cryptomator.ui.quit.QuitComponent;
|
import org.cryptomator.ui.quit.QuitComponent;
|
||||||
@@ -18,7 +19,6 @@ import org.cryptomator.ui.unlock.UnlockComponent;
|
|||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableSet;
|
import javafx.collections.ObservableSet;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
@@ -28,15 +28,9 @@ import java.io.UncheckedIOException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, QuitComponent.class, ErrorComponent.class})
|
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class})
|
||||||
abstract class FxApplicationModule {
|
abstract class FxApplicationModule {
|
||||||
|
|
||||||
@Provides
|
|
||||||
@FxApplicationScoped
|
|
||||||
static ObservableSet<Stage> provideVisibleStages() {
|
|
||||||
return FXCollections.observableSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Named("windowIcons")
|
@Named("windowIcons")
|
||||||
@FxApplicationScoped
|
@FxApplicationScoped
|
||||||
@@ -56,16 +50,9 @@ abstract class FxApplicationModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxApplicationScoped
|
@FxApplicationScoped
|
||||||
static StageFactory provideStageFactory(@Named("windowIcons") List<Image> windowIcons, ObservableSet<Stage> visibleStages) {
|
static StageFactory provideStageFactory(@Named("windowIcons") List<Image> windowIcons) {
|
||||||
return new StageFactory(stage -> {
|
return new StageFactory(stage -> {
|
||||||
stage.getIcons().addAll(windowIcons);
|
stage.getIcons().addAll(windowIcons);
|
||||||
stage.showingProperty().addListener((observableValue, wasShowing, isShowing) -> {
|
|
||||||
if (isShowing) {
|
|
||||||
visibleStages.add(stage);
|
|
||||||
} else {
|
|
||||||
visibleStages.remove(stage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,4 +75,8 @@ abstract class FxApplicationModule {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.launcher;
|
package org.cryptomator.ui.launcher;
|
||||||
|
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.ui.fxapp.FxApplication;
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -34,15 +35,15 @@ class AppLaunchEventHandler {
|
|||||||
this.vaultListManager = vaultListManager;
|
this.vaultListManager = vaultListManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startHandlingLaunchEvents(boolean hasTrayIcon) {
|
public void startHandlingLaunchEvents() {
|
||||||
executorService.submit(() -> handleLaunchEvents(hasTrayIcon));
|
executorService.submit(this::handleLaunchEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleLaunchEvents(boolean hasTrayIcon) {
|
private void handleLaunchEvents() {
|
||||||
try {
|
try {
|
||||||
while (!Thread.interrupted()) {
|
while (!Thread.interrupted()) {
|
||||||
AppLaunchEvent event = launchEventQueue.take();
|
AppLaunchEvent event = launchEventQueue.take();
|
||||||
handleLaunchEvent(hasTrayIcon, event);
|
handleLaunchEvent(event);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.warn("Interrupted launch event handler.");
|
LOG.warn("Interrupted launch event handler.");
|
||||||
@@ -50,12 +51,12 @@ class AppLaunchEventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleLaunchEvent(boolean hasTrayIcon, AppLaunchEvent event) {
|
private void handleLaunchEvent(AppLaunchEvent event) {
|
||||||
switch (event.getType()) {
|
switch (event.getType()) {
|
||||||
case REVEAL_APP -> fxApplicationStarter.get(hasTrayIcon).thenAccept(FxApplication::showMainWindow);
|
case REVEAL_APP -> fxApplicationStarter.get().thenAccept(FxApplication::showMainWindow);
|
||||||
case OPEN_FILE -> fxApplicationStarter.get(hasTrayIcon).thenRun(() -> {
|
case OPEN_FILE -> fxApplicationStarter.get().thenRun(() -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
event.getPathsToOpen().forEach(this::addVault);
|
event.getPathsToOpen().forEach(this::addOrRevealVault);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
default -> LOG.warn("Unsupported event type: {}", event.getType());
|
default -> LOG.warn("Unsupported event type: {}", event.getType());
|
||||||
@@ -63,13 +64,18 @@ class AppLaunchEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO dedup MainWindowController...
|
// TODO dedup MainWindowController...
|
||||||
private void addVault(Path potentialVaultPath) {
|
private void addOrRevealVault(Path potentialVaultPath) {
|
||||||
assert Platform.isFxApplicationThread();
|
assert Platform.isFxApplicationThread();
|
||||||
try {
|
try {
|
||||||
|
final Vault v;
|
||||||
if (potentialVaultPath.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
if (potentialVaultPath.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
||||||
vaultListManager.add(potentialVaultPath.getParent());
|
v = vaultListManager.add(potentialVaultPath.getParent());
|
||||||
} else {
|
} else {
|
||||||
vaultListManager.add(potentialVaultPath);
|
v = vaultListManager.add(potentialVaultPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.isUnlocked()) {
|
||||||
|
fxApplicationStarter.get().thenAccept(app -> app.getVaultService().reveal(v));
|
||||||
}
|
}
|
||||||
LOG.debug("Added vault {}", potentialVaultPath);
|
LOG.debug("Added vault {}", potentialVaultPath);
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class AppLifecycleListener {
|
|||||||
if (allowQuitWithoutPrompt.get()) {
|
if (allowQuitWithoutPrompt.get()) {
|
||||||
decoratedQuitResponse.performQuit();
|
decoratedQuitResponse.performQuit();
|
||||||
} else {
|
} else {
|
||||||
fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
|
fxApplicationStarter.get().thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,11 +113,11 @@ public class AppLifecycleListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
|
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
|
||||||
fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
|
fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showAboutWindow(@SuppressWarnings("unused") AboutEvent aboutEvent) {
|
private void showAboutWindow(@SuppressWarnings("unused") AboutEvent aboutEvent) {
|
||||||
fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ABOUT));
|
fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ABOUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forceUnmountRemainingVaults() {
|
private void forceUnmountRemainingVaults() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.launcher;
|
package org.cryptomator.ui.launcher;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
import org.cryptomator.ui.fxapp.FxApplication;
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationComponent;
|
import org.cryptomator.ui.fxapp.FxApplicationComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -18,37 +19,36 @@ public class FxApplicationStarter {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStarter.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStarter.class);
|
||||||
|
|
||||||
private final FxApplicationComponent.Builder fxAppComponent;
|
private final Lazy<FxApplicationComponent> fxAppComponent;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
private final AtomicBoolean started;
|
private final AtomicBoolean started;
|
||||||
private final CompletableFuture<FxApplication> future;
|
private final CompletableFuture<FxApplication> future;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FxApplicationStarter(FxApplicationComponent.Builder fxAppComponent, ExecutorService executor) {
|
public FxApplicationStarter(Lazy<FxApplicationComponent> fxAppComponent, ExecutorService executor) {
|
||||||
this.fxAppComponent = fxAppComponent;
|
this.fxAppComponent = fxAppComponent;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.started = new AtomicBoolean();
|
this.started = new AtomicBoolean();
|
||||||
this.future = new CompletableFuture<>();
|
this.future = new CompletableFuture<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletionStage<FxApplication> get(boolean hasTrayIcon) {
|
public CompletionStage<FxApplication> get() {
|
||||||
if (!started.getAndSet(true)) {
|
if (!started.getAndSet(true)) {
|
||||||
start(hasTrayIcon);
|
start();
|
||||||
}
|
}
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start(boolean hasTrayIcon) {
|
private void start() {
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
LOG.debug("Starting JavaFX runtime...");
|
LOG.debug("Starting JavaFX runtime...");
|
||||||
Platform.startup(() -> {
|
Platform.startup(() -> {
|
||||||
assert Platform.isFxApplicationThread();
|
assert Platform.isFxApplicationThread();
|
||||||
LOG.info("JavaFX Runtime started.");
|
LOG.info("JavaFX Runtime started.");
|
||||||
FxApplication app = fxAppComponent.trayMenuSupported(hasTrayIcon).build().application();
|
FxApplication app = fxAppComponent.get().application();
|
||||||
app.start();
|
app.start();
|
||||||
future.complete(app);
|
future.complete(app);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.ui.launcher;
|
package org.cryptomator.ui.launcher;
|
||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.mountpoint.IrregularUnmountCleaner;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
||||||
@@ -16,8 +15,6 @@ import javafx.collections.ObservableList;
|
|||||||
import java.awt.Desktop;
|
import java.awt.Desktop;
|
||||||
import java.awt.SystemTray;
|
import java.awt.SystemTray;
|
||||||
import java.awt.desktop.AppReopenedListener;
|
import java.awt.desktop.AppReopenedListener;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -28,66 +25,66 @@ public class UiLauncher {
|
|||||||
|
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final ObservableList<Vault> vaults;
|
private final ObservableList<Vault> vaults;
|
||||||
private final TrayMenuComponent.Builder trayComponent;
|
private final Lazy<TrayMenuComponent> trayMenu;
|
||||||
private final FxApplicationStarter fxApplicationStarter;
|
private final FxApplicationStarter fxApplicationStarter;
|
||||||
private final AppLaunchEventHandler launchEventHandler;
|
private final AppLaunchEventHandler launchEventHandler;
|
||||||
private final Optional<TrayIntegrationProvider> trayIntegration;
|
private final Optional<TrayIntegrationProvider> trayIntegration;
|
||||||
private final Environment env;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UiLauncher(Settings settings, ObservableList<Vault> vaults, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<TrayIntegrationProvider> trayIntegration, Environment env) {
|
public UiLauncher(Settings settings, ObservableList<Vault> vaults, Lazy<TrayMenuComponent> trayMenu, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<TrayIntegrationProvider> trayIntegration) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
this.trayComponent = trayComponent;
|
this.trayMenu = trayMenu;
|
||||||
this.fxApplicationStarter = fxApplicationStarter;
|
this.fxApplicationStarter = fxApplicationStarter;
|
||||||
this.launchEventHandler = launchEventHandler;
|
this.launchEventHandler = launchEventHandler;
|
||||||
this.trayIntegration = trayIntegration;
|
this.trayIntegration = trayIntegration;
|
||||||
this.env = env;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void launch() {
|
public void launch() {
|
||||||
final boolean hasTrayIcon;
|
boolean hidden = settings.startHidden().get();
|
||||||
if (SystemTray.isSupported()) {
|
if (SystemTray.isSupported() && settings.showTrayIcon().get()) {
|
||||||
trayComponent.build().addIconToSystemTray();
|
trayMenu.get().initializeTrayIcon();
|
||||||
hasTrayIcon = true;
|
launch(true, hidden);
|
||||||
} else {
|
} else {
|
||||||
hasTrayIcon = false;
|
launch(false, hidden);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// show window on start?
|
private void launch(boolean withTrayIcon, boolean hidden) {
|
||||||
if (hasTrayIcon && settings.startHidden().get()) {
|
// start hidden, minimized or normal?
|
||||||
|
if (withTrayIcon && hidden) {
|
||||||
LOG.debug("Hiding application...");
|
LOG.debug("Hiding application...");
|
||||||
trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray);
|
trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray);
|
||||||
|
} else if (!withTrayIcon && hidden) {
|
||||||
|
LOG.debug("Minimizing application...");
|
||||||
|
showMainWindowAsync(true);
|
||||||
} else {
|
} else {
|
||||||
showMainWindowAsync(hasTrayIcon);
|
LOG.debug("Showing application...");
|
||||||
|
showMainWindowAsync(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// register app reopen listener
|
// register app reopen listener
|
||||||
Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(hasTrayIcon));
|
Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(false));
|
||||||
|
|
||||||
//clean leftovers of not-regularly unmounted vaults
|
|
||||||
//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
|
|
||||||
env.getMountPointsDir().filter(path -> Files.exists(path, LinkOption.NOFOLLOW_LINKS)).ifPresent(IrregularUnmountCleaner::removeIrregularUnmountDebris);
|
|
||||||
|
|
||||||
// auto unlock
|
// auto unlock
|
||||||
Collection<Vault> vaultsToAutoUnlock = vaults.filtered(this::shouldAttemptAutoUnlock);
|
Collection<Vault> vaultsToAutoUnlock = vaults.filtered(this::shouldAttemptAutoUnlock);
|
||||||
if (!vaultsToAutoUnlock.isEmpty()) {
|
if (!vaultsToAutoUnlock.isEmpty()) {
|
||||||
fxApplicationStarter.get(hasTrayIcon).thenAccept(app -> {
|
fxApplicationStarter.get().thenAccept(app -> {
|
||||||
for (Vault vault : vaultsToAutoUnlock) {
|
for (Vault vault : vaultsToAutoUnlock) {
|
||||||
app.startUnlockWorkflow(vault, Optional.empty());
|
app.startUnlockWorkflow(vault, Optional.empty());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
launchEventHandler.startHandlingLaunchEvents(hasTrayIcon);
|
launchEventHandler.startHandlingLaunchEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldAttemptAutoUnlock(Vault vault) {
|
private boolean shouldAttemptAutoUnlock(Vault vault) {
|
||||||
return vault.isLocked() && vault.getVaultSettings().unlockAfterStartup().get();
|
return vault.isLocked() && vault.getVaultSettings().unlockAfterStartup().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showMainWindowAsync(boolean hasTrayIcon) {
|
private void showMainWindowAsync(boolean minimize) {
|
||||||
fxApplicationStarter.get(hasTrayIcon).thenAccept(FxApplication::showMainWindow);
|
fxApplicationStarter.get().thenCompose(FxApplication::showMainWindow).thenAccept(win -> win.setIconified(minimize));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ import java.util.concurrent.BlockingQueue;
|
|||||||
@Module(subcomponents = {TrayMenuComponent.class, FxApplicationComponent.class})
|
@Module(subcomponents = {TrayMenuComponent.class, FxApplicationComponent.class})
|
||||||
public abstract class UiLauncherModule {
|
public abstract class UiLauncherModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static FxApplicationComponent provideFxApplicationComponent(FxApplicationComponent.Builder builder) {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static Optional<UiAppearanceProvider> provideAppearanceProvider() {
|
static Optional<UiAppearanceProvider> provideAppearanceProvider() {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
|
import dagger.BindsInstance;
|
||||||
|
import dagger.Subcomponent;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
|
||||||
|
@LockScoped
|
||||||
|
@Subcomponent(modules = {LockModule.class})
|
||||||
|
public interface LockComponent {
|
||||||
|
|
||||||
|
ExecutorService defaultExecutorService();
|
||||||
|
|
||||||
|
LockWorkflow lockWorkflow();
|
||||||
|
|
||||||
|
default Future<Void> startLockWorkflow() {
|
||||||
|
LockWorkflow workflow = lockWorkflow();
|
||||||
|
defaultExecutorService().submit(workflow);
|
||||||
|
return workflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subcomponent.Builder
|
||||||
|
interface Builder {
|
||||||
|
|
||||||
|
@BindsInstance
|
||||||
|
LockComponent.Builder vault(@LockWindow Vault vault);
|
||||||
|
|
||||||
|
@BindsInstance
|
||||||
|
LockComponent.Builder owner(@Named("lockWindowOwner") Optional<Stage> owner);
|
||||||
|
|
||||||
|
LockComponent build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
@LockScoped
|
||||||
|
public class LockFailedController implements FxController {
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final Vault vault;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LockFailedController(@LockWindow Stage window, @LockWindow Vault vault) {
|
||||||
|
this.window = window;
|
||||||
|
this.vault = vault;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Getter & Setter -----
|
||||||
|
public String getVaultName() {
|
||||||
|
return vault.getDisplayName();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.UserInteractionLock;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
|
||||||
|
@LockScoped
|
||||||
|
public class LockForcedController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LockForcedController.class);
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final Vault vault;
|
||||||
|
private final UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LockForcedController(@LockWindow Stage window, @LockWindow Vault vault, UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock) {
|
||||||
|
this.window = window;
|
||||||
|
this.vault = vault;
|
||||||
|
this.forceLockDecisionLock = forceLockDecisionLock;
|
||||||
|
this.window.setOnHiding(this::windowClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void cancel() {
|
||||||
|
forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL);
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void confirmForcedLock() {
|
||||||
|
forceLockDecisionLock.interacted(LockModule.ForceLockDecision.FORCE);
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void windowClosed(WindowEvent windowEvent) {
|
||||||
|
// if not already interacted, set the decision to CANCEL
|
||||||
|
if (forceLockDecisionLock.awaitingInteraction().get()) {
|
||||||
|
LOG.debug("Lock canceled in force-lock-phase by user.");
|
||||||
|
forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Getter & Setter -----
|
||||||
|
|
||||||
|
public String getVaultName() {
|
||||||
|
return vault.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.common.StageFactory;
|
||||||
|
import org.cryptomator.ui.common.UserInteractionLock;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
abstract class LockModule {
|
||||||
|
|
||||||
|
enum ForceLockDecision {
|
||||||
|
CANCEL,
|
||||||
|
FORCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@LockScoped
|
||||||
|
static UserInteractionLock<LockModule.ForceLockDecision> provideForceLockDecisionLock() {
|
||||||
|
return new UserInteractionLock<>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@LockWindow
|
||||||
|
@LockScoped
|
||||||
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@LockWindow
|
||||||
|
@LockScoped
|
||||||
|
static Stage provideWindow(StageFactory factory, @LockWindow Vault vault, @Named("lockWindowOwner") Optional<Stage> owner) {
|
||||||
|
Stage stage = factory.create();
|
||||||
|
stage.setTitle(vault.getDisplayName());
|
||||||
|
stage.setResizable(false);
|
||||||
|
if (owner.isPresent()) {
|
||||||
|
stage.initOwner(owner.get());
|
||||||
|
stage.initModality(Modality.WINDOW_MODAL);
|
||||||
|
} else {
|
||||||
|
stage.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
}
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.LOCK_FORCED)
|
||||||
|
@LockScoped
|
||||||
|
static Scene provideForceLockScene(@LockWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.LOCK_FORCED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.LOCK_FAILED)
|
||||||
|
@LockScoped
|
||||||
|
static Scene provideLockFailedScene(@LockWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.LOCK_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(LockForcedController.class)
|
||||||
|
abstract FxController bindLockForcedController(LockForcedController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(LockFailedController.class)
|
||||||
|
abstract FxController bindLockFailedController(LockFailedController controller);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
|
import javax.inject.Scope;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@Scope
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@interface LockScoped {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
|
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 LockWindow {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.common.UserInteractionLock;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sequence of actions performed and checked during lock of a vault.
|
||||||
|
* <p>
|
||||||
|
* This class implements the Task interface, sucht that it can run in the background with some possible forground operations/requests to the ui, without blocking the main app.
|
||||||
|
* If the task state is
|
||||||
|
* <li>succeeded, the vault was successfully locked;</li>
|
||||||
|
* <li>canceled, the lock was canceled;</li>
|
||||||
|
* <li>failed, the lock failed due to an exception.</li>
|
||||||
|
*/
|
||||||
|
public class LockWorkflow extends Task<Void> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LockWorkflow.class);
|
||||||
|
|
||||||
|
private final Stage lockWindow;
|
||||||
|
private final Vault vault;
|
||||||
|
private final UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock;
|
||||||
|
private final Lazy<Scene> lockForcedScene;
|
||||||
|
private final Lazy<Scene> lockFailedScene;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy<Scene> lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy<Scene> lockFailedScene) {
|
||||||
|
this.lockWindow = lockWindow;
|
||||||
|
this.vault = vault;
|
||||||
|
this.forceLockDecisionLock = forceLockDecisionLock;
|
||||||
|
this.lockForcedScene = lockForcedScene;
|
||||||
|
this.lockFailedScene = lockFailedScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void call() throws Volume.VolumeException, InterruptedException {
|
||||||
|
try {
|
||||||
|
vault.lock(false);
|
||||||
|
} catch (Volume.VolumeException e) {
|
||||||
|
LOG.debug("Regular lock of {} failed.", vault.getDisplayName(), e);
|
||||||
|
var decision = askUserForAction();
|
||||||
|
switch (decision) {
|
||||||
|
case FORCE -> vault.lock(true);
|
||||||
|
case CANCEL -> cancel(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LockModule.ForceLockDecision askUserForAction() throws InterruptedException {
|
||||||
|
// show forcedLock dialogue ...
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
lockWindow.setScene(lockForcedScene.get());
|
||||||
|
lockWindow.show();
|
||||||
|
Window owner = lockWindow.getOwner();
|
||||||
|
if (owner != null) {
|
||||||
|
lockWindow.setX(owner.getX() + (owner.getWidth() - lockWindow.getWidth()) / 2);
|
||||||
|
lockWindow.setY(owner.getY() + (owner.getHeight() - lockWindow.getHeight()) / 2);
|
||||||
|
} else {
|
||||||
|
lockWindow.centerOnScreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// ... and wait for answer
|
||||||
|
return forceLockDecisionLock.awaitInteraction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void scheduled() {
|
||||||
|
vault.setState(VaultState.PROCESSING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void succeeded() {
|
||||||
|
LOG.info("Lock of {} succeeded.", vault.getDisplayName());
|
||||||
|
vault.setState(VaultState.LOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void failed() {
|
||||||
|
LOG.warn("Failed to lock {}.", vault.getDisplayName());
|
||||||
|
vault.setState(VaultState.UNLOCKED);
|
||||||
|
lockWindow.setScene(lockFailedScene.get());
|
||||||
|
lockWindow.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cancelled() {
|
||||||
|
LOG.debug("Lock of {} canceled.", vault.getDisplayName());
|
||||||
|
vault.setState(VaultState.UNLOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,7 +26,6 @@ public interface MainWindowComponent {
|
|||||||
default Stage showMainWindow() {
|
default Stage showMainWindow() {
|
||||||
Stage stage = window();
|
Stage stage = window();
|
||||||
stage.setScene(scene().get());
|
stage.setScene(scene().get());
|
||||||
stage.setIconified(false);
|
|
||||||
stage.show();
|
stage.show();
|
||||||
stage.toFront();
|
stage.toFront();
|
||||||
stage.requestFocus();
|
stage.requestFocus();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
|||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -39,8 +39,8 @@ abstract class MainWindowModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@MainWindow
|
@MainWindow
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, MainWindowSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, MainWindowSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -60,8 +60,8 @@ abstract class MainWindowModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MAIN_WINDOW)
|
@FxmlScene(FxmlFile.MAIN_WINDOW)
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
static Scene provideMainScene(@MainWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMainScene(@MainWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/main_window.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MAIN_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
+23
-14
@@ -7,13 +7,14 @@ import org.cryptomator.ui.fxapp.FxApplication;
|
|||||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
import org.cryptomator.ui.fxapp.UpdateChecker;
|
||||||
import org.cryptomator.ui.launcher.AppLifecycleListener;
|
import org.cryptomator.ui.launcher.AppLifecycleListener;
|
||||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
|
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
@@ -23,32 +24,31 @@ public class MainWindowTitleController implements FxController {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
|
||||||
|
|
||||||
public HBox titleBar;
|
|
||||||
|
|
||||||
private final AppLifecycleListener appLifecycle;
|
private final AppLifecycleListener appLifecycle;
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final FxApplication application;
|
private final FxApplication application;
|
||||||
private final boolean minimizeToSysTray;
|
private final boolean trayMenuInitialized;
|
||||||
private final UpdateChecker updateChecker;
|
private final UpdateChecker updateChecker;
|
||||||
private final BooleanBinding updateAvailable;
|
private final BooleanBinding updateAvailable;
|
||||||
private final LicenseHolder licenseHolder;
|
private final LicenseHolder licenseHolder;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final BooleanBinding debugModeEnabled;
|
private final BooleanBinding showMinimizeButton;
|
||||||
|
|
||||||
|
public HBox titleBar;
|
||||||
private double xOffset;
|
private double xOffset;
|
||||||
private double yOffset;
|
private double yOffset;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
|
MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
|
||||||
this.appLifecycle = appLifecycle;
|
this.appLifecycle = appLifecycle;
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.minimizeToSysTray = minimizeToSysTray;
|
this.trayMenuInitialized = trayMenu.isInitialized();
|
||||||
this.updateChecker = updateChecker;
|
this.updateChecker = updateChecker;
|
||||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||||
this.licenseHolder = licenseHolder;
|
this.licenseHolder = licenseHolder;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.debugModeEnabled = Bindings.createBooleanBinding(this::isDebugModeEnabled, settings.debugMode());
|
this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton(), settings.showTrayIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -71,7 +71,7 @@ public class MainWindowTitleController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void close() {
|
public void close() {
|
||||||
if (minimizeToSysTray) {
|
if (trayMenuInitialized) {
|
||||||
window.close();
|
window.close();
|
||||||
} else {
|
} else {
|
||||||
appLifecycle.quit();
|
appLifecycle.quit();
|
||||||
@@ -112,15 +112,24 @@ public class MainWindowTitleController implements FxController {
|
|||||||
return updateAvailable.get();
|
return updateAvailable.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMinimizeToSysTray() {
|
public boolean isTrayIconPresent() {
|
||||||
return minimizeToSysTray;
|
return trayMenuInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding debugModeEnabledProperty() {
|
public ReadOnlyBooleanProperty debugModeEnabledProperty() {
|
||||||
return debugModeEnabled;
|
return settings.debugMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDebugModeEnabled() {
|
public boolean isDebugModeEnabled() {
|
||||||
return settings.debugMode().get();
|
return debugModeEnabledProperty().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding showMinimizeButtonProperty() {
|
||||||
|
return showMinimizeButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShowMinimizeButton() {
|
||||||
|
// always show the minimize button if no tray icon is present OR it is explicitily enabled
|
||||||
|
return !trayMenuInitialized || settings.showMinimizeButton().get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -5,6 +5,7 @@ import org.cryptomator.common.keychain.KeychainManager;
|
|||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.FxApplication;
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
|
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
||||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -47,7 +48,12 @@ public class VaultDetailLockedController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void showVaultOptions() {
|
public void showVaultOptions() {
|
||||||
vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow();
|
vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void showKeyVaultOptions() {
|
||||||
|
vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow(SelectedVaultOptionsTab.KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|||||||
+9
-3
@@ -6,25 +6,32 @@ import com.google.common.cache.LoadingCache;
|
|||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultDetailUnlockedController implements FxController {
|
public class VaultDetailUnlockedController implements FxController {
|
||||||
|
|
||||||
private final ReadOnlyObjectProperty<Vault> vault;
|
private final ReadOnlyObjectProperty<Vault> vault;
|
||||||
|
private final FxApplication application;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
|
private final Stage mainWindow;
|
||||||
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
||||||
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder) {
|
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplication application, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
|
this.application = application;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
||||||
this.vaultStatsBuilder = vaultStatsBuilder;
|
this.vaultStatsBuilder = vaultStatsBuilder;
|
||||||
}
|
}
|
||||||
@@ -40,8 +47,7 @@ public class VaultDetailUnlockedController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void lock() {
|
public void lock() {
|
||||||
vaultService.lock(vault.get(), false);
|
application.startLockWorkflow(vault.get(), Optional.of(mainWindow));
|
||||||
// TODO count lock attempts, and allow forced lock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.controls.DraggableListCell;
|
import org.cryptomator.ui.controls.DraggableListCell;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -17,10 +17,10 @@ import java.io.UncheckedIOException;
|
|||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultListCellFactory implements Callback<ListView<Vault>, ListCell<Vault>> {
|
public class VaultListCellFactory implements Callback<ListView<Vault>, ListCell<Vault>> {
|
||||||
|
|
||||||
private final FXMLLoaderFactory fxmlLoaders;
|
private final FxmlLoaderFactory fxmlLoaders;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultListCellFactory(@MainWindow FXMLLoaderFactory fxmlLoaders) {
|
VaultListCellFactory(@MainWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
this.fxmlLoaders = fxmlLoaders;
|
this.fxmlLoaders = fxmlLoaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||||
@@ -13,6 +12,7 @@ import javax.inject.Inject;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@@ -41,7 +41,7 @@ public class VaultListController implements FxController {
|
|||||||
this.removeVault = removeVault;
|
this.removeVault = removeVault;
|
||||||
this.noVaultSelected = selectedVault.isNull();
|
this.noVaultSelected = selectedVault.isNull();
|
||||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||||
EasyBind.subscribe(selectedVault, this::selectedVaultDidChange);
|
selectedVault.addListener(this::selectedVaultDidChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
@@ -58,10 +58,11 @@ public class VaultListController implements FxController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectedVaultDidChange(Vault newValue) {
|
private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue<? extends Vault> observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) {
|
||||||
if (newValue != null) {
|
if (newValue == null) {
|
||||||
VaultListManager.redetermineVaultState(newValue);
|
return;
|
||||||
}
|
}
|
||||||
|
VaultListManager.redetermineVaultState(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
|||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -30,8 +30,8 @@ abstract class MigrationModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@MigrationWindow
|
@MigrationWindow
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -56,36 +56,36 @@ abstract class MigrationModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_START)
|
@FxmlScene(FxmlFile.MIGRATION_START)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationStartScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationStartScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_start.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_START);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_RUN)
|
@FxmlScene(FxmlFile.MIGRATION_RUN)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationRunScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationRunScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_run.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_RUN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_SUCCESS)
|
@FxmlScene(FxmlFile.MIGRATION_SUCCESS)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationSuccessScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationSuccessScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_success.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR)
|
@FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationCapabilityErrorScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationCapabilityErrorScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_capability_error.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_CAPABILITY_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE)
|
@FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationImpossibleScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationImpossibleScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_impossible.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_IMPOSSIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
|
||||||
|
|
||||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
|
||||||
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
class AutoStartMacStrategy implements AutoStartStrategy {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AutoStartMacStrategy.class);
|
|
||||||
|
|
||||||
private final AutoStartProvider autoStartProvider;
|
|
||||||
|
|
||||||
public AutoStartMacStrategy(AutoStartProvider autoStartProvider) {
|
|
||||||
this.autoStartProvider = autoStartProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletionStage<Boolean> isAutoStartEnabled() {
|
|
||||||
return CompletableFuture.completedFuture(autoStartProvider.isEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enableAutoStart() throws TogglingAutoStartFailedException {
|
|
||||||
try {
|
|
||||||
autoStartProvider.enable();
|
|
||||||
LOG.debug("Added login item.");
|
|
||||||
} catch (ToggleAutoStartFailedException e) {
|
|
||||||
throw new TogglingAutoStartFailedException("Failed to add login item.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disableAutoStart() throws TogglingAutoStartFailedException {
|
|
||||||
try {
|
|
||||||
autoStartProvider.disable();
|
|
||||||
LOG.debug("Removed login item.");
|
|
||||||
} catch (ToggleAutoStartFailedException e) {
|
|
||||||
throw new TogglingAutoStartFailedException("Failed to remove login item.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
|
||||||
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@Module
|
|
||||||
abstract class AutoStartModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@PreferencesScoped
|
|
||||||
public static Optional<AutoStartStrategy> provideAutoStartStrategy(Optional<AutoStartProvider> autoStartProvider) {
|
|
||||||
if (SystemUtils.IS_OS_MAC_OSX && autoStartProvider.isPresent()) {
|
|
||||||
return Optional.of(new AutoStartMacStrategy(autoStartProvider.get()));
|
|
||||||
} else if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
Optional<String> exeName = ProcessHandle.current().info().command();
|
|
||||||
return exeName.map(AutoStartWinStrategy::new);
|
|
||||||
} else {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public interface AutoStartStrategy {
|
|
||||||
|
|
||||||
CompletionStage<Boolean> isAutoStartEnabled();
|
|
||||||
|
|
||||||
void enableAutoStart() throws TogglingAutoStartFailedException;
|
|
||||||
|
|
||||||
void disableAutoStart() throws TogglingAutoStartFailedException;
|
|
||||||
|
|
||||||
class TogglingAutoStartFailedException extends Exception {
|
|
||||||
|
|
||||||
public TogglingAutoStartFailedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TogglingAutoStartFailedException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OS specific class to check, en- and disable the auto start on Windows.
|
|
||||||
* <p>
|
|
||||||
* Two strategies are implemented for this feature, the first uses the registry and the second one the autostart folder.
|
|
||||||
* <p>
|
|
||||||
* The registry strategy checks/add/removes at the registry key {@value HKCU_AUTOSTART_KEY} an entry for Cryptomator.
|
|
||||||
* The folder strategy checks/add/removes at the location {@value WINDOWS_START_MENU_ENTRY}.
|
|
||||||
* <p>
|
|
||||||
* To check if the feature is active, both strategies are applied.
|
|
||||||
* To enable the feature, first the registry is tried and only on failure the autostart folder is used.
|
|
||||||
* To disable it, first it is determined by an internal state, which strategies must be used and in the second step those are executed.
|
|
||||||
*
|
|
||||||
* @apiNote This class is not thread safe, hence it should be avoided to call its methods simultaniously by different threads.
|
|
||||||
* @deprecated To be moved to integration-win project
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
class AutoStartWinStrategy implements AutoStartStrategy {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AutoStartWinStrategy.class);
|
|
||||||
private static final String HKCU_AUTOSTART_KEY = "\"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\"";
|
|
||||||
private static final String AUTOSTART_VALUE = "Cryptomator";
|
|
||||||
private static final String WINDOWS_START_MENU_ENTRY = "\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Cryptomator.lnk";
|
|
||||||
|
|
||||||
private final String exePath;
|
|
||||||
|
|
||||||
private boolean activatedUsingFolder;
|
|
||||||
private boolean activatedUsingRegistry;
|
|
||||||
|
|
||||||
public AutoStartWinStrategy(String exePath) {
|
|
||||||
this.exePath = exePath;
|
|
||||||
this.activatedUsingFolder = false;
|
|
||||||
this.activatedUsingRegistry = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletionStage<Boolean> isAutoStartEnabled() {
|
|
||||||
return isAutoStartEnabledUsingRegistry().thenCombine(isAutoStartEnabledUsingFolder(), (bReg, bFolder) -> bReg || bFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Boolean> isAutoStartEnabledUsingFolder() {
|
|
||||||
Path autoStartEntry = Path.of(System.getProperty("user.home") + WINDOWS_START_MENU_ENTRY);
|
|
||||||
this.activatedUsingFolder = Files.exists(autoStartEntry);
|
|
||||||
return CompletableFuture.completedFuture(activatedUsingFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Boolean> isAutoStartEnabledUsingRegistry() {
|
|
||||||
ProcessBuilder regQuery = new ProcessBuilder("reg", "query", HKCU_AUTOSTART_KEY, //
|
|
||||||
"/v", AUTOSTART_VALUE);
|
|
||||||
try {
|
|
||||||
Process proc = regQuery.start();
|
|
||||||
return proc.onExit().thenApply(p -> {
|
|
||||||
this.activatedUsingRegistry = p.exitValue() == 0;
|
|
||||||
return activatedUsingRegistry;
|
|
||||||
});
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug("Failed to query {} from registry key {}", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
|
|
||||||
return CompletableFuture.completedFuture(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enableAutoStart() throws TogglingAutoStartFailedException {
|
|
||||||
try {
|
|
||||||
enableAutoStartUsingRegistry().thenAccept((Void v) -> this.activatedUsingRegistry = true).exceptionallyCompose(e -> {
|
|
||||||
LOG.debug("Falling back to using autostart folder.");
|
|
||||||
return this.enableAutoStartUsingFolder();
|
|
||||||
}).thenAccept((Void v) -> this.activatedUsingFolder = true).get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new TogglingAutoStartFailedException("Execution of enabling auto start setting was interrupted.");
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
throw new TogglingAutoStartFailedException("Enabling auto start failed both using registry and auto start folder.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Void> enableAutoStartUsingRegistry() {
|
|
||||||
ProcessBuilder regAdd = new ProcessBuilder("reg", "add", HKCU_AUTOSTART_KEY, //
|
|
||||||
"/v", AUTOSTART_VALUE, //
|
|
||||||
"/t", "REG_SZ", //
|
|
||||||
"/d", "\"" + exePath + "\"", //
|
|
||||||
"/f");
|
|
||||||
try {
|
|
||||||
Process proc = regAdd.start();
|
|
||||||
boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
|
|
||||||
if (finishedInTime && proc.exitValue() == 0) {
|
|
||||||
LOG.debug("Added {} to registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
} else {
|
|
||||||
throw new IOException("Process exited with error code " + proc.exitValue());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug("Registry could not be edited to set auto start.", e);
|
|
||||||
return CompletableFuture.failedFuture(new SystemCommandException("Adding registry value failed."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Void> enableAutoStartUsingFolder() {
|
|
||||||
String autoStartFolderEntry = System.getProperty("user.home") + WINDOWS_START_MENU_ENTRY;
|
|
||||||
String createShortcutCommand = "$s=(New-Object -COM WScript.Shell).CreateShortcut('" + autoStartFolderEntry + "');$s.TargetPath='" + exePath + "';$s.Save();";
|
|
||||||
ProcessBuilder shortcutAdd = new ProcessBuilder("cmd", "/c", "Start powershell " + createShortcutCommand);
|
|
||||||
try {
|
|
||||||
Process proc = shortcutAdd.start();
|
|
||||||
boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
|
|
||||||
if (finishedInTime && proc.exitValue() == 0) {
|
|
||||||
LOG.debug("Created file {} for auto start.", autoStartFolderEntry);
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
} else {
|
|
||||||
throw new IOException("Process exited with error code " + proc.exitValue());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug("Adding entry to auto start folder failed.", e);
|
|
||||||
return CompletableFuture.failedFuture(new SystemCommandException("Adding entry to auto start folder failed."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disableAutoStart() throws TogglingAutoStartFailedException {
|
|
||||||
if (activatedUsingRegistry) {
|
|
||||||
disableAutoStartUsingRegistry().whenComplete((voit, ex) -> {
|
|
||||||
if (ex == null) {
|
|
||||||
this.activatedUsingRegistry = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activatedUsingFolder) {
|
|
||||||
disableAutoStartUsingFolder().whenComplete((voit, ex) -> {
|
|
||||||
if (ex == null) {
|
|
||||||
this.activatedUsingFolder = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activatedUsingRegistry || activatedUsingFolder) {
|
|
||||||
throw new TogglingAutoStartFailedException("Disabling auto start failed using registry and/or auto start folder.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<Void> disableAutoStartUsingRegistry() {
|
|
||||||
ProcessBuilder regRemove = new ProcessBuilder("reg", "delete", HKCU_AUTOSTART_KEY, //
|
|
||||||
"/v", AUTOSTART_VALUE, //
|
|
||||||
"/f");
|
|
||||||
try {
|
|
||||||
Process proc = regRemove.start();
|
|
||||||
boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
|
|
||||||
if (finishedInTime && proc.exitValue() == 0) {
|
|
||||||
LOG.debug("Removed {} from registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
} else {
|
|
||||||
throw new IOException("Process exited with error code " + proc.exitValue());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug("Registry could not be edited to remove auto start.", e);
|
|
||||||
return CompletableFuture.failedFuture(new SystemCommandException("Removing registry value failed."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Void> disableAutoStartUsingFolder() {
|
|
||||||
try {
|
|
||||||
Files.delete(Path.of(WINDOWS_START_MENU_ENTRY));
|
|
||||||
LOG.debug("Successfully deleted {}.", WINDOWS_START_MENU_ENTRY);
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
} catch (NoSuchFileException e) {
|
|
||||||
//that is also okay
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug("Failed to delete entry from auto start folder.", e);
|
|
||||||
return CompletableFuture.failedFuture(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean waitForProcessOrCancel(Process proc, int timeout, TimeUnit timeUnit) {
|
|
||||||
boolean finishedInTime = false;
|
|
||||||
try {
|
|
||||||
finishedInTime = proc.waitFor(timeout, timeUnit);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.error("Timeout while reading registry", e);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
} finally {
|
|
||||||
if (!finishedInTime) {
|
|
||||||
proc.destroyForcibly();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return finishedInTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SystemCommandException extends RuntimeException {
|
|
||||||
|
|
||||||
public SystemCommandException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+11
@@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
import org.cryptomator.common.LicenseHolder;
|
import org.cryptomator.common.LicenseHolder;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.UiTheme;
|
import org.cryptomator.common.settings.UiTheme;
|
||||||
@@ -10,6 +11,7 @@ import javafx.application.Application;
|
|||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.TextArea;
|
import javafx.scene.control.TextArea;
|
||||||
|
import javafx.scene.control.TextFormatter;
|
||||||
|
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
public class DonationKeyPreferencesController implements FxController {
|
public class DonationKeyPreferencesController implements FxController {
|
||||||
@@ -32,6 +34,15 @@ public class DonationKeyPreferencesController implements FxController {
|
|||||||
public void initialize() {
|
public void initialize() {
|
||||||
donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
|
donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
|
||||||
donationKeyField.textProperty().addListener(this::registrationKeyChanged);
|
donationKeyField.textProperty().addListener(this::registrationKeyChanged);
|
||||||
|
donationKeyField.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) {
|
||||||
|
if (change.isContentChange()) {
|
||||||
|
var strippedText = CharMatcher.whitespace().removeFrom(change.getText());
|
||||||
|
change.setText(strippedText);
|
||||||
|
}
|
||||||
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
|
private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
|
||||||
|
|||||||
+36
-48
@@ -5,19 +5,19 @@ import org.cryptomator.common.LicenseHolder;
|
|||||||
import org.cryptomator.common.settings.KeychainBackend;
|
import org.cryptomator.common.settings.KeychainBackend;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.UiTheme;
|
import org.cryptomator.common.settings.UiTheme;
|
||||||
|
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
||||||
|
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
|
||||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||||
import org.cryptomator.ui.common.ErrorComponent;
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.concurrent.Task;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.NodeOrientation;
|
import javafx.geometry.NodeOrientation;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
@@ -31,7 +31,6 @@ import java.util.Arrays;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
@@ -41,11 +40,11 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
|
private final boolean trayMenuInitialized;
|
||||||
private final boolean trayMenuSupported;
|
private final boolean trayMenuSupported;
|
||||||
private final Optional<AutoStartStrategy> autoStartStrategy;
|
private final Optional<AutoStartProvider> autoStartProvider;
|
||||||
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
||||||
private final LicenseHolder licenseHolder;
|
private final LicenseHolder licenseHolder;
|
||||||
private final ExecutorService executor;
|
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final Application application;
|
private final Application application;
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
@@ -53,6 +52,8 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
private final ErrorComponent.Builder errorComponent;
|
private final ErrorComponent.Builder errorComponent;
|
||||||
public ChoiceBox<UiTheme> themeChoiceBox;
|
public ChoiceBox<UiTheme> themeChoiceBox;
|
||||||
public ChoiceBox<KeychainBackend> keychainBackendChoiceBox;
|
public ChoiceBox<KeychainBackend> keychainBackendChoiceBox;
|
||||||
|
public CheckBox showMinimizeButtonCheckbox;
|
||||||
|
public CheckBox showTrayIconCheckbox;
|
||||||
public CheckBox startHiddenCheckbox;
|
public CheckBox startHiddenCheckbox;
|
||||||
public CheckBox debugModeCheckbox;
|
public CheckBox debugModeCheckbox;
|
||||||
public CheckBox autoStartCheckbox;
|
public CheckBox autoStartCheckbox;
|
||||||
@@ -60,16 +61,17 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
public RadioButton nodeOrientationLtr;
|
public RadioButton nodeOrientationLtr;
|
||||||
public RadioButton nodeOrientationRtl;
|
public RadioButton nodeOrientationRtl;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, Set<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
|
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional<AutoStartProvider> autoStartProvider, Set<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.trayMenuSupported = trayMenuSupported;
|
this.trayMenuInitialized = trayMenu.isInitialized();
|
||||||
this.autoStartStrategy = autoStartStrategy;
|
this.trayMenuSupported = trayMenu.isSupported();
|
||||||
|
this.autoStartProvider = autoStartProvider;
|
||||||
this.keychainAccessProviders = keychainAccessProviders;
|
this.keychainAccessProviders = keychainAccessProviders;
|
||||||
this.selectedTabProperty = selectedTabProperty;
|
this.selectedTabProperty = selectedTabProperty;
|
||||||
this.licenseHolder = licenseHolder;
|
this.licenseHolder = licenseHolder;
|
||||||
this.executor = executor;
|
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
@@ -85,15 +87,15 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
|
themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
|
||||||
themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle));
|
themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle));
|
||||||
|
|
||||||
|
showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton());
|
||||||
|
|
||||||
|
showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon());
|
||||||
|
|
||||||
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden());
|
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden());
|
||||||
|
|
||||||
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
|
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
|
||||||
|
|
||||||
autoStartStrategy.ifPresent(autoStart -> {
|
autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled()));
|
||||||
autoStart.isAutoStartEnabled().thenAccept(enabled -> {
|
|
||||||
Platform.runLater(() -> autoStartCheckbox.setSelected(enabled));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
|
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
|
||||||
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
|
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
|
||||||
@@ -109,12 +111,16 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
return Arrays.stream(KeychainBackend.values()).filter(value -> namesOfAvailableProviders.contains(value.getProviderClass())).toArray(KeychainBackend[]::new);
|
return Arrays.stream(KeychainBackend.values()).filter(value -> namesOfAvailableProviders.contains(value.getProviderClass())).toArray(KeychainBackend[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTrayMenuInitialized() {
|
||||||
|
return trayMenuInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isTrayMenuSupported() {
|
public boolean isTrayMenuSupported() {
|
||||||
return this.trayMenuSupported;
|
return trayMenuSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAutoStartSupported() {
|
public boolean isAutoStartSupported() {
|
||||||
return autoStartStrategy.isPresent();
|
return autoStartProvider.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||||
@@ -129,15 +135,19 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void toggleAutoStart() {
|
public void toggleAutoStart() {
|
||||||
autoStartStrategy.ifPresent(autoStart -> {
|
autoStartProvider.ifPresent(autoStart -> {
|
||||||
boolean enableAutoStart = autoStartCheckbox.isSelected();
|
boolean enableAutoStart = autoStartCheckbox.isSelected();
|
||||||
Task<Void> toggleTask = new ToggleAutoStartTask(autoStart, enableAutoStart);
|
try {
|
||||||
toggleTask.setOnFailed(event -> {
|
if (enableAutoStart) {
|
||||||
|
autoStart.enable();
|
||||||
|
} else {
|
||||||
|
autoStart.disable();
|
||||||
|
}
|
||||||
|
} catch (ToggleAutoStartFailedException e) {
|
||||||
autoStartCheckbox.setSelected(!enableAutoStart); // restore previous state
|
autoStartCheckbox.setSelected(!enableAutoStart); // restore previous state
|
||||||
LOG.error("Failed to toggle autostart.", event.getSource().getException());
|
LOG.error("Failed to toggle autostart.", e);
|
||||||
errorComponent.cause(event.getSource().getException()).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||||
});
|
}
|
||||||
executor.execute(toggleTask);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +185,7 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
public UiTheme fromString(String string) {
|
public UiTheme fromString(String string) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class KeychainBackendConverter extends StringConverter<KeychainBackend> {
|
private static class KeychainBackendConverter extends StringConverter<KeychainBackend> {
|
||||||
@@ -194,29 +205,6 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
public KeychainBackend fromString(String string) {
|
public KeychainBackend fromString(String string) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ToggleAutoStartTask extends Task<Void> {
|
|
||||||
|
|
||||||
private final AutoStartStrategy autoStart;
|
|
||||||
private final boolean enable;
|
|
||||||
|
|
||||||
public ToggleAutoStartTask(AutoStartStrategy autoStart, boolean enable) {
|
|
||||||
this.autoStart = autoStart;
|
|
||||||
this.enable = enable;
|
|
||||||
|
|
||||||
setOnFailed(event -> LOG.error("Failed to toggle Autostart", getException()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void call() throws Exception {
|
|
||||||
if (enable) {
|
|
||||||
autoStart.enableAutoStart();
|
|
||||||
} else {
|
|
||||||
autoStart.disableAutoStart();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -20,7 +20,7 @@ import javafx.stage.Stage;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@Module(includes = {AutoStartModule.class})
|
@Module
|
||||||
abstract class PreferencesModule {
|
abstract class PreferencesModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -32,8 +32,8 @@ abstract class PreferencesModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@PreferencesWindow
|
@PreferencesWindow
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -49,8 +49,8 @@ abstract class PreferencesModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.PREFERENCES)
|
@FxmlScene(FxmlFile.PREFERENCES)
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
static Scene providePreferencesScene(@PreferencesWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene providePreferencesScene(@PreferencesWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/preferences.fxml");
|
return fxmlLoaders.createScene(FxmlFile.PREFERENCES);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.quit;
|
package org.cryptomator.ui.quit;
|
||||||
|
|
||||||
import dagger.BindsInstance;
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -25,7 +24,10 @@ public interface QuitComponent {
|
|||||||
@FxmlScene(FxmlFile.QUIT)
|
@FxmlScene(FxmlFile.QUIT)
|
||||||
Lazy<Scene> scene();
|
Lazy<Scene> scene();
|
||||||
|
|
||||||
default Stage showQuitWindow() {
|
QuitController controller();
|
||||||
|
|
||||||
|
default Stage showQuitWindow(QuitResponse response) {
|
||||||
|
controller().updateQuitRequest(response);
|
||||||
Stage stage = window();
|
Stage stage = window();
|
||||||
stage.setScene(scene().get());
|
stage.setScene(scene().get());
|
||||||
stage.show();
|
stage.show();
|
||||||
@@ -36,9 +38,6 @@ public interface QuitComponent {
|
|||||||
@Subcomponent.Builder
|
@Subcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
|
|
||||||
@BindsInstance
|
|
||||||
Builder quitResponse(QuitResponse response);
|
|
||||||
|
|
||||||
QuitComponent build();
|
QuitComponent build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import javafx.stage.Stage;
|
|||||||
import java.awt.desktop.QuitResponse;
|
import java.awt.desktop.QuitResponse;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@QuitScoped
|
@QuitScoped
|
||||||
@@ -24,26 +26,40 @@ public class QuitController implements FxController {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(QuitController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(QuitController.class);
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final QuitResponse response;
|
|
||||||
private final ObservableList<Vault> unlockedVaults;
|
private final ObservableList<Vault> unlockedVaults;
|
||||||
private final ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
|
private final AtomicReference<QuitResponse> quitResponse = new AtomicReference<>();
|
||||||
public Button lockAndQuitButton;
|
public Button lockAndQuitButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService) {
|
QuitController(@QuitWindow Stage window, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.response = response;
|
|
||||||
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
|
window.setOnCloseRequest(windowEvent -> cancel());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateQuitRequest(QuitResponse newResponse) {
|
||||||
|
var oldResponse = quitResponse.getAndSet(newResponse);
|
||||||
|
if (oldResponse != null) {
|
||||||
|
oldResponse.cancelQuit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void respondToQuitRequest(Consumer<QuitResponse> action) {
|
||||||
|
var response = quitResponse.getAndSet(null);
|
||||||
|
if (response != null) {
|
||||||
|
action.accept(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
LOG.info("Quitting application canceled by user.");
|
LOG.info("Quitting application canceled by user.");
|
||||||
window.close();
|
window.close();
|
||||||
response.cancelQuit();
|
respondToQuitRequest(QuitResponse::cancelQuit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -56,16 +72,16 @@ public class QuitController implements FxController {
|
|||||||
LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayName).collect(Collectors.joining(", ")));
|
LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayName).collect(Collectors.joining(", ")));
|
||||||
if (unlockedVaults.isEmpty()) {
|
if (unlockedVaults.isEmpty()) {
|
||||||
window.close();
|
window.close();
|
||||||
response.performQuit();
|
respondToQuitRequest(QuitResponse::performQuit);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
lockAllTask.setOnFailed(evt -> {
|
lockAllTask.setOnFailed(evt -> {
|
||||||
LOG.warn("Locking failed", lockAllTask.getException());
|
LOG.warn("Locking failed", lockAllTask.getException());
|
||||||
lockAndQuitButton.setDisable(false);
|
lockAndQuitButton.setDisable(false);
|
||||||
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
||||||
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
|
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!) (see https://github.com/cryptomator/cryptomator/pull/1416)
|
||||||
// see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
|
window.close();
|
||||||
response.cancelQuit();
|
respondToQuitRequest(QuitResponse::cancelQuit);
|
||||||
});
|
});
|
||||||
executorService.execute(lockAllTask);
|
executorService.execute(lockAllTask);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user