mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-16 01:31:28 +00:00
Compare commits
301 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
281cfb74d9 | ||
|
|
0d03eeb5bd | ||
|
|
36e669d729 | ||
|
|
b1f55d7cfe | ||
|
|
9aa8c46560 | ||
|
|
2ed00ed02d | ||
|
|
5d56f5beb6 | ||
|
|
1b937dfb06 | ||
|
|
42be5330fe | ||
|
|
be8949157f | ||
|
|
93b2a4e07a | ||
|
|
ada1195a26 | ||
|
|
93563f68e0 | ||
|
|
3faa0e83cc | ||
|
|
1985e2af72 | ||
|
|
3f03d36ad6 | ||
|
|
8359deb8eb | ||
|
|
e1f2330f95 | ||
|
|
87014c4db2 | ||
|
|
c9eb9b1938 | ||
|
|
28cb2e1301 | ||
|
|
277999112e | ||
|
|
a7fdf3d325 | ||
|
|
54ae332364 | ||
|
|
76075ffec4 | ||
|
|
ed448146f7 | ||
|
|
638e9c1a8b | ||
|
|
1624cffb2a | ||
|
|
f391a6521d | ||
|
|
02ae2e7ca0 | ||
|
|
40bd84a09c | ||
|
|
0c0fb1c4c5 | ||
|
|
ab39bd1667 | ||
|
|
b41636a208 | ||
|
|
0902de821a | ||
|
|
8a6265658e | ||
|
|
7750a49e65 | ||
|
|
b75b9781c1 | ||
|
|
2687c02e31 | ||
|
|
71b65e03d6 | ||
|
|
e09ee27219 | ||
|
|
5a3428d9b0 | ||
|
|
ed109977f8 | ||
|
|
d6c6f177e8 | ||
|
|
97f2cee1ae | ||
|
|
4fb8a27a78 | ||
|
|
79b825aaad | ||
|
|
28fedafb59 | ||
|
|
d9bff68555 | ||
|
|
cef3a5fc77 | ||
|
|
9956f43fd9 | ||
|
|
2b84593bde | ||
|
|
4e728fd387 | ||
|
|
438ade1106 | ||
|
|
fe54f4ec66 | ||
|
|
fe86b4c593 | ||
|
|
a583afeb60 | ||
|
|
a585d3cf16 | ||
|
|
3db757193e | ||
|
|
bac1d6fd83 | ||
|
|
39ee8a9cde | ||
|
|
1263b3af81 | ||
|
|
dafa29d8a3 | ||
|
|
2bc6fe89ad | ||
|
|
8439216233 | ||
|
|
aab616d184 | ||
|
|
70c3a38c49 | ||
|
|
c64294ac3e | ||
|
|
82330db871 | ||
|
|
c54a721f9a | ||
|
|
355bbb5459 | ||
|
|
63daa0f121 | ||
|
|
50885d5c7c | ||
|
|
4d68818ec5 | ||
|
|
6fb20dd509 | ||
|
|
2bb87dfa96 | ||
|
|
3e374a927c | ||
|
|
84ac6d88f5 | ||
|
|
72f6ee6477 | ||
|
|
a3cfcb1131 | ||
|
|
d7d8d21ba4 | ||
|
|
ef0425e2b1 | ||
|
|
df1fd6d0b3 | ||
|
|
2fa04d7b7c | ||
|
|
a15acd64c8 | ||
|
|
5b18eff01a | ||
|
|
47133c6f31 | ||
|
|
09ba4f5129 | ||
|
|
20d4047bed | ||
|
|
56b71ef7d9 | ||
|
|
091e62057d | ||
|
|
824bd9ea64 | ||
|
|
697a791593 | ||
|
|
7462a887b3 | ||
|
|
3535e83d7d | ||
|
|
cf0b4accb3 | ||
|
|
a63bcfbaa2 | ||
|
|
5c4bf2a207 | ||
|
|
c1611a12ed | ||
|
|
0983120712 | ||
|
|
ce12af8495 | ||
|
|
dc117c8415 | ||
|
|
06e526a961 | ||
|
|
2e343a951f | ||
|
|
141ffcf656 | ||
|
|
d61e5c5a08 | ||
|
|
6a15fa132a | ||
|
|
902b29ee0a | ||
|
|
995bba616f | ||
|
|
f39b7b047f | ||
|
|
72e52df4e0 | ||
|
|
8018e9485e | ||
|
|
e0ae50378f | ||
|
|
a9c2b0fc57 | ||
|
|
dc58ba434a | ||
|
|
34af306309 | ||
|
|
21d70b5ae4 | ||
|
|
e90880ac9a | ||
|
|
66faa13f40 | ||
|
|
8a4a29b4d1 | ||
|
|
8c8db84a4a | ||
|
|
a499a3c80b | ||
|
|
6a3ccf2b48 | ||
|
|
fcfcffe9cb | ||
|
|
363ed4ac4b | ||
|
|
1f73a08e09 | ||
|
|
fe0a34907f | ||
|
|
461b11700f | ||
|
|
24bfbb59a4 | ||
|
|
4476558e9c | ||
|
|
560171832c | ||
|
|
6e93d40e51 | ||
|
|
79b819bca6 | ||
|
|
a18c406cf0 | ||
|
|
6730a83cac | ||
|
|
3b3ebd2196 | ||
|
|
505b6542c7 | ||
|
|
31368f0cba | ||
|
|
5b5dd756b1 | ||
|
|
f6ebbb23d1 | ||
|
|
3f0373b08f | ||
|
|
4c3c60060d | ||
|
|
28f275c22d | ||
|
|
24df3c3809 | ||
|
|
034a667e07 | ||
|
|
008e3e3b05 | ||
|
|
94a5bf7596 | ||
|
|
e8db836eff | ||
|
|
429b26f3d8 | ||
|
|
3ae8327300 | ||
|
|
df7e9a0af1 | ||
|
|
93d3eca0ab | ||
|
|
7753d1f0e7 | ||
|
|
d7c6c24932 | ||
|
|
1a75f23081 | ||
|
|
f071efe1b9 | ||
|
|
a8ad335aed | ||
|
|
7022a80c95 | ||
|
|
9a2f602d6c | ||
|
|
c78a4aa241 | ||
|
|
975ce4d973 | ||
|
|
1e6ff0d969 | ||
|
|
69e133d561 | ||
|
|
20e55eddf8 | ||
|
|
0fdcdc816a | ||
|
|
b7506d97a9 | ||
|
|
4ad7481dc7 | ||
|
|
bc815405d2 | ||
|
|
9c06e762c3 | ||
|
|
1ac87dd32f | ||
|
|
e0ce7ce2ec | ||
|
|
3d951a9d7b | ||
|
|
cec3d984b0 | ||
|
|
392e474cfa | ||
|
|
41fb0d51a4 | ||
|
|
aa9fef2967 | ||
|
|
adc9c02564 | ||
|
|
ace64117a2 | ||
|
|
fb4db2506b | ||
|
|
1076d971ae | ||
|
|
eed1b1cff0 | ||
|
|
f5cb82e21e | ||
|
|
67661f114b | ||
|
|
8a3e09764a | ||
|
|
eb3cfd6e6a | ||
|
|
4d1727d0e9 | ||
|
|
a51d853d1c | ||
|
|
d0039466f7 | ||
|
|
5c959989a2 | ||
|
|
6283d2df3d | ||
|
|
a9e0dfdaf8 | ||
|
|
45ca7e9e47 | ||
|
|
034b5c2718 | ||
|
|
e188649c79 | ||
|
|
1468c6ec90 | ||
|
|
07ba4eb537 | ||
|
|
414bbef1a7 | ||
|
|
e2b94ff6ef | ||
|
|
41f8a9faca | ||
|
|
1d9252e974 | ||
|
|
80780eef3c | ||
|
|
87ff33956b | ||
|
|
1804b98f05 | ||
|
|
847c6813cc | ||
|
|
1dde5ff6e7 | ||
|
|
76c9a19428 | ||
|
|
25ee0519e1 | ||
|
|
c184089c35 | ||
|
|
d2bcc47857 | ||
|
|
34629a69ea | ||
|
|
92c87f7b84 | ||
|
|
0dd96635ac | ||
|
|
048c44a6e4 | ||
|
|
06910ad1f4 | ||
|
|
02a0f3acc6 | ||
|
|
851f9240b7 | ||
|
|
99fce8d0b7 | ||
|
|
bf05c59c3b | ||
|
|
3dcebb1e1f | ||
|
|
fe3efdf610 | ||
|
|
5f4ae46f82 | ||
|
|
deef325319 | ||
|
|
fbe00a8fe3 | ||
|
|
dc87dade43 | ||
|
|
ba1625b5ad | ||
|
|
f6b126415e | ||
|
|
9147e1c08b | ||
|
|
6c18103662 | ||
|
|
6fc343ea12 | ||
|
|
d304d66cdd | ||
|
|
2ce9143b85 | ||
|
|
1c54e4f4ad | ||
|
|
9fd6f2ecae | ||
|
|
0d9f8eefc0 | ||
|
|
40a1530f19 | ||
|
|
0477a0a2e3 | ||
|
|
b77d4b5ae2 | ||
|
|
7b6c5318c5 | ||
|
|
6006d65ce0 | ||
|
|
2b01b76926 | ||
|
|
dcea9e21f0 | ||
|
|
78645ecdf6 | ||
|
|
91646dd93d | ||
|
|
fca146e939 | ||
|
|
62aa3ccc7f | ||
|
|
c0f4a2b0d3 | ||
|
|
68ee89af98 | ||
|
|
ad2c9116b9 | ||
|
|
8e24745b3e | ||
|
|
08f664e3df | ||
|
|
b6d1d1dc22 | ||
|
|
a0ef02b95c | ||
|
|
a6cefe67c4 | ||
|
|
be2b63ab2a | ||
|
|
78f11b4a5e | ||
|
|
0f20c7c3c9 | ||
|
|
d4235174f7 | ||
|
|
f16be84aa3 | ||
|
|
833f2d8566 | ||
|
|
c02a63878e | ||
|
|
6deb30307e | ||
|
|
7357829741 | ||
|
|
4bd04150c1 | ||
|
|
ac9fe28967 | ||
|
|
515755d84a | ||
|
|
cf35772c18 | ||
|
|
b0fd226c4c | ||
|
|
0d188d1c0c | ||
|
|
c6016ec7b2 | ||
|
|
e8719a1f9b | ||
|
|
27baf78029 | ||
|
|
bf5ce9a3a5 | ||
|
|
fef19fe6b3 | ||
|
|
5f56dacc4e | ||
|
|
aa249dabb5 | ||
|
|
06a5bed6e3 | ||
|
|
02f1ffc6bf | ||
|
|
bcfe040784 | ||
|
|
de9af9e303 | ||
|
|
d9b88ad1b7 | ||
|
|
e66e5b1d96 | ||
|
|
588166dce9 | ||
|
|
e2bc71a0bc | ||
|
|
e528f6827c | ||
|
|
2882ae8ef8 | ||
|
|
e37f7cea1a | ||
|
|
9b4ee10155 | ||
|
|
c9d970955c | ||
|
|
9e0afd36c4 | ||
|
|
0e523599a3 | ||
|
|
1df6589dd7 | ||
|
|
fb60c97fd3 | ||
|
|
90cd149be8 | ||
|
|
89c04ad83b | ||
|
|
f2d383a211 | ||
|
|
73fde5d020 | ||
|
|
5c0857e98e | ||
|
|
3e87b9c0c6 | ||
|
|
a1d0b6b1d3 | ||
|
|
b0d4b2e403 | ||
|
|
6996d36ea2 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -11,3 +11,9 @@
|
||||
.classpath
|
||||
target/
|
||||
test-output/
|
||||
|
||||
# IntelliJ Settings Files #
|
||||
.idea/
|
||||
out/
|
||||
.idea_modules/
|
||||
*.iws
|
||||
|
||||
60
.travis.yml
60
.travis.yml
@@ -1,19 +1,30 @@
|
||||
language: java
|
||||
|
||||
sudo: required
|
||||
dist: trusty
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
env:
|
||||
global:
|
||||
- secure: "Lgj042RD0X3rB8VZVZLWP1GetLhjd3PqI5JbJMlzgHJpDI6RkFIBLN9SWAGmkLPCehIp2zA5tu9+UVy0NNMxm9xz6SyjMCaxS28/fnYEXaNmwwDSF6O6gLUbdxyzoYIFPYOPmFxpzhebqnNIsxaM29oZpgRgUGqosCczQxiB+Ng=" #coveralls
|
||||
- secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" #coverity
|
||||
|
||||
before_install: "curl -L --cookie 'oraclelicense=accept-securebackup-cookie;' http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip -o /tmp/policy.zip && sudo unzip -j -o /tmp/policy.zip *.jar -d `jdk_switcher home oraclejdk8`/jre/lib/security && rm /tmp/policy.zip"
|
||||
|
||||
script: mvn -fmain/pom.xml clean test
|
||||
|
||||
after_success: mvn -fmain/pom.xml clean test jacoco:report coveralls:report
|
||||
|
||||
- secure: "lV9OwUbHMrMpLUH1CY+Z4puLDdFXytudyPlG1eGRsesdpuG6KM3uQVz6uAtf6lrU8DRbMM/T7ML+PmvQ4UoPPYLdLxESLLBat2qUPOIVBOhTSlCc7I0DmGy04CSvkeMy8dPaQC0ukgNiR7zwoNzfcpGRN/U9S8tziDruuHoZSrg=" #bintray
|
||||
addons:
|
||||
coverity_scan:
|
||||
project:
|
||||
name: "cryptomator/cryptomator"
|
||||
notification_email: sebastian.stenzel@cryptomator.org
|
||||
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
|
||||
branch_pattern: release.*
|
||||
install:
|
||||
# "clean" needed until https://bugs.openjdk.java.net/browse/JDK-8067747 is resolved.
|
||||
- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Ptest-coverage
|
||||
- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Prelease
|
||||
script:
|
||||
- mvn --update-snapshots -fmain/pom.xml -Ptest-coverage clean test jacoco:report-aggregate
|
||||
after_success:
|
||||
- "bash <(curl -s https://codecov.io/bash)"
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
@@ -26,24 +37,27 @@ notifications:
|
||||
secure: "lngJ/HEAFBbD5AdiO9avMqptKpZHdmEwOzS9FabZjkdFh7yAYueTk5RniPUvShjsKtThYm7cJ8AtDMDwc07NvPrzbMBRtUJGwuDT+7c7YFALGFJ1NYi+emkC9x1oafvmPgEYSE+tMKzNcwrHi3ytGgKdIotsKwaF35QNXYA9aMs="
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
before_deploy: mvn -fmain/pom.xml -Puber-jar clean package -DskipTests
|
||||
|
||||
addons:
|
||||
coverity_scan:
|
||||
project:
|
||||
name: "cryptomator/cryptomator"
|
||||
notification_email: sebastian.stenzel@cryptomator.org
|
||||
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
|
||||
branch_pattern: coverity_scan
|
||||
|
||||
before_deploy:
|
||||
- mvn -fmain/pom.xml -Prelease clean package -DskipTests
|
||||
deploy:
|
||||
provider: releases
|
||||
- provider: releases
|
||||
prerelease: false
|
||||
api_key:
|
||||
secure: "ZjE1j93v3qbPIe2YbmhS319aCbMdLQw0HuymmluTurxXsZtn9D4t2+eTr99vBVxGRuB5lzzGezPR5zjk5W7iHF7xhwrawXrFzr2rPJWzWFt0aM+Ry2njU1ROTGGXGTbv4anWeBlgMxLEInTAy/9ytOGNJlec83yc0THpOY2wxnk="
|
||||
file: main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar
|
||||
file:
|
||||
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
|
||||
- "main/ant-kit/target/antkit.tar.gz"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
tags: true
|
||||
- provider: script
|
||||
script: "curl -X POST -u cryptobot:${BINTRAY_API_KEY} -H 'Content-Type: application/json' -d '{\"name\": \"${TRAVIS_TAG}\", \"vcs_tag\": \"${TRAVIS_TAG}\"}' https://api.bintray.com/packages/cryptomator/cryptomator/cryptomator-win/versions"
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
tags: true
|
||||
- provider: script
|
||||
script: "curl -X POST -u cryptobot:${BINTRAY_API_KEY} -H 'Content-Type: application/json' -d '{\"name\": \"${TRAVIS_TAG}\", \"vcs_tag\": \"${TRAVIS_TAG}\"}' https://api.bintray.com/packages/cryptomator/cryptomator/cryptomator-osx/versions"
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
tags: true
|
||||
|
||||
74
CODE_OF_CONDUCT.md
Normal file
74
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at support@cryptomator.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
38
CONTRIBUTING.md
Normal file
38
CONTRIBUTING.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Contributing to Cryptomator
|
||||
|
||||
## Did you find a bug?
|
||||
|
||||
- Ensure you're running the latest version of Cryptomator.
|
||||
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively.
|
||||
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [knowledge base](https://cryptomator.freshdesk.com/support/solutions) and our [Wiki](https://github.com/cryptomator/cryptomator/wiki).
|
||||
- If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new).
|
||||
|
||||
## Do you have questions?
|
||||
|
||||
- Ask questions by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new).
|
||||
- [Contact us](https://cryptomator.org/contact/) directly by writing an email. Wir sprechen auch Deutsch!
|
||||
- Have a chat with us on [Gitter](https://gitter.im/cryptomator/cryptomator).
|
||||
|
||||
## Do you miss a feature?
|
||||
|
||||
- Ensure the feature was not [already requested](https://github.com/cryptomator/cryptomator/issues).
|
||||
- You're welcome to suggest a feature by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new).
|
||||
|
||||
## Did you write a patch that fixes a bug?
|
||||
|
||||
- Open a new pull request with the patch.
|
||||
- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
|
||||
|
||||
## Do you intend to add a new feature or change an existing one?
|
||||
|
||||
- Suggest your change by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new) and start writing code.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Above all, thank you for your contributions
|
||||
|
||||
Thank you for taking the time to contribute to the project! :+1:
|
||||
|
||||
Cryptomator Team
|
||||
38
ISSUE_TEMPLATE.md
Normal file
38
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,38 @@
|
||||
To tick a checkbox replace [ ] with [x]. Make sure to replace placeholders (…) accordingly.
|
||||
|
||||
## Issue Checklist
|
||||
|
||||
Before creating a new issue make sure that you
|
||||
- [ ] searched [existing (and closed) issues](https://github.com/cryptomator/cryptomator/issues).
|
||||
- [ ] searched the [knowledge base](https://cryptomator.freshdesk.com/support/solutions).
|
||||
- [ ] have read the [contribution guide](https://github.com/cryptomator/cryptomator/blob/master/CONTRIBUTING.md).
|
||||
- [ ] have read the [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Basic Info
|
||||
|
||||
This is a
|
||||
- [ ] bug report.
|
||||
- [ ] feature request.
|
||||
- [ ] question or something else.
|
||||
|
||||
I'm using
|
||||
- [ ] Windows in version: …
|
||||
- [ ] macOS in version: …
|
||||
- [ ] Linux in version: …
|
||||
|
||||
I'm running Cryptomator in version: …
|
||||
(You can check the version in the Cryptomator settings.)
|
||||
|
||||
## Description
|
||||
|
||||
…
|
||||
(Please describe in detail what you did, what you expected, and what really happened.)
|
||||
|
||||
## Attachments (optional)
|
||||
|
||||
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](https://cryptomator.freshdesk.com/support/solutions/articles/16000046480) first and 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.
|
||||
|
||||
You can find the log file
|
||||
- on Windows: %appdata%/Cryptomator/cryptomator.log
|
||||
- on macOS: ~/Library/Logs/Cryptomator/cryptomator.log
|
||||
- on Linux: ~/.Cryptomator/cryptomator.log
|
||||
72
README.md
72
README.md
@@ -1,44 +1,72 @@
|
||||
Cryptomator
|
||||
====================
|
||||

|
||||
|
||||
[](https://travis-ci.org/cryptomator/cryptomator)
|
||||
[](https://scan.coverity.com/projects/cryptomator-cryptomator)
|
||||
[](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade)
|
||||
[](https://coveralls.io/github/cryptomator/cryptomator?branch=master)
|
||||
[](https://gitter.im/cryptomator/cryptomator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://twitter.com/Cryptomator)
|
||||
[](https://poeditor.com/join/project/bHwbvJmx0E)
|
||||
|
||||
Multiplatform transparent client-side encryption of your files in the cloud.
|
||||
Multi-platform transparent client-side encryption of your files in the cloud.
|
||||
|
||||
If you want to take a look at the current beta version, go ahead and get your copy of cryptomator on [Cryptomator.org](https://cryptomator.org) or clone and build Cryptomator using Maven (instructions below).
|
||||
Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator.org/) or clone and build Cryptomator using Maven (instructions below).
|
||||
|
||||
## Features
|
||||
- Totally transparent: Just work on the encrypted volume, as if it was an USB flash drive
|
||||
- Works with Dropbox, OneDrive (Skydrive), Google Drive and any other cloud storage, that syncs with a local directory.
|
||||
- In fact it works with any directory. You can use it to encrypt as many folders as you like
|
||||
|
||||
- Works with Dropbox, Google Drive, OneDrive, Nextcloud and any other cloud storage service which synchronizes with a local directory
|
||||
- Open Source means: No backdoors, control is better than trust
|
||||
- Client-side: No accounts, no data shared with any online service
|
||||
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
|
||||
- AES encryption with 256-bit key length
|
||||
- Client-side. No accounts, no data shared with any online service
|
||||
- Filenames get encrypted too
|
||||
- No need to provide credentials for any 3rd party service
|
||||
- Open Source means: No backdoors. Control is better than trust
|
||||
- Use as many encrypted folders in your Dropbox as you want. Each having individual passwords
|
||||
- No commercial interest, no government agency, no wasted taxpayers' money ;-)
|
||||
- File names get encrypted
|
||||
- Folder structure gets obfuscated
|
||||
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
||||
|
||||
### Privacy
|
||||
- 256 bit keys (unlimited strength policy bundled with native binaries - 128-bit elsewhere)
|
||||
|
||||
- 256-bit keys (unlimited strength policy bundled with native binaries)
|
||||
- Scrypt key derivation
|
||||
- Cryptographically secure random numbers for salts, IVs and the master key of course
|
||||
- Sensitive data is swiped from the heap asap
|
||||
- Cryptographically secure random numbers for salts, IVs and the masterkey of course
|
||||
- Sensitive data is wiped from the heap asap
|
||||
- Lightweight: [Complexity kills security](https://www.schneier.com/essays/archives/1999/11/a_plea_for_simplicit.html)
|
||||
|
||||
### Consistency
|
||||
|
||||
- HMAC over file contents to recognize changed ciphertext before decryption
|
||||
- I/O operations are transactional and atomic, if the file systems support it
|
||||
- Each file contains all information needed for decryption (except for the key of course). No common metadata means no [SPOF](http://en.wikipedia.org/wiki/Single_point_of_failure)
|
||||
- I/O operations are transactional and atomic, if the filesystems support it
|
||||
- Each file contains all information needed for decryption (except for the key of course), no common metadata means no [SPOF](http://en.wikipedia.org/wiki/Single_point_of_failure)
|
||||
|
||||
### Security Architecture
|
||||
|
||||
For more information on the security details visit [cryptomator.org](https://cryptomator.org/architecture/).
|
||||
|
||||
## Building
|
||||
|
||||
#### Dependencies
|
||||
* Java 8 + JCE unlimited strength policy files (needed for 256-bit keys)
|
||||
### Dependencies
|
||||
|
||||
* Java 8 (min. 8u51, we recommend to use the current version)
|
||||
* [JCE unlimited strength policy files](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html) (needed for 256-bit keys)
|
||||
* 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), [Debian](https://github.com/cryptomator/cryptomator-deb))
|
||||
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
|
||||
|
||||
### Run Maven
|
||||
|
||||
```
|
||||
cd main
|
||||
mvn clean install -Prelease
|
||||
```
|
||||
|
||||
An executable jar file will be created inside `main/uber-jar/target`.
|
||||
|
||||
## Contributing to Cryptomator
|
||||
|
||||
Please read our [contribution guide](https://github.com/cryptomator/cryptomator/blob/master/CONTRIBUTING.md), if you would like to report a bug, ask a question or help us with coding.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the MIT X Consortium license. See the LICENSE file for more info.
|
||||
Distributed under the MIT X Consortium license. See the `LICENSES/MIT-X-Consortium-License.txt` file for more info.
|
||||
|
||||
BIN
cryptomator.png
Normal file
BIN
cryptomator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
37
main/ant-kit/assembly.xml
Normal file
37
main/ant-kit/assembly.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
<id>tarball</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/fixed-binaries</directory>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>fixed-binaries</outputDirectory>
|
||||
<fileMode>755</fileMode>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/package</directory>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>package</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<includes>
|
||||
<include>build.xml</include>
|
||||
</includes>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
103
main/ant-kit/pom.xml
Normal file
103
main/ant-kit/pom.xml
Normal file
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2016 Sebastian Stenzel
|
||||
This file is licensed under the terms of the MIT license.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.3.0-rc1</version>
|
||||
</parent>
|
||||
<artifactId>ant-kit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator Ant Build Kit</name>
|
||||
<description>Builds a package that can be built with Ant locally</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>launcher</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- copy libraries to target/libs/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- copy resources to target/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<escapeString>\</escapeString>
|
||||
<encoding>UTF-8</encoding>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>fixed-binaries/**</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<includes>
|
||||
<include>fixed-binaries/**</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- create antkit.tar.gz: -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>antkit</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
72
main/ant-kit/src/main/resources/build.xml
Normal file
72
main/ant-kit/src/main/resources/build.xml
Normal file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="Cryptomator" default="create-jar" basedir="." xmlns:fx="javafx:com.sun.javafx.tools.ant">
|
||||
<taskdef uri="javafx:com.sun.javafx.tools.ant" resource="com/sun/javafx/tools/ant/antlib.xml" classpath="\${java.class.path}:\${java.home}/../lib/ant-javafx.jar:." />
|
||||
|
||||
<!-- Define application to build -->
|
||||
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.launcher.Cryptomator" />
|
||||
|
||||
<!-- Create main application jar -->
|
||||
<target name="create-jar">
|
||||
<fx:jar destfile="antbuild/Cryptomator-${project.version}.jar">
|
||||
<fx:application refid="Cryptomator" />
|
||||
<fx:fileset dir="libs" includes="launcher-${project.version}.jar" />
|
||||
<fx:resources>
|
||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar" />
|
||||
</fx:resources>
|
||||
<fx:manifest>
|
||||
<fx:attribute name="Implementation-Vendor" value="cryptomator.org" />
|
||||
<fx:attribute name="Implementation-Title" value="Cryptomator"/>
|
||||
<fx:attribute name="Implementation-Version" value="${project.version}" />
|
||||
</fx:manifest>
|
||||
</fx:jar>
|
||||
</target>
|
||||
|
||||
<!-- Create Debian package -->
|
||||
<target name="deb" depends="create-jar">
|
||||
<fx:deploy nativeBundles="deb" outdir="antbuild" outfile="Cryptomator-${project.version}" verbose="true">
|
||||
<fx:application refid="Cryptomator" />
|
||||
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="MIT" category="Utility">
|
||||
<fx:association mimetype="application/x-vnd.cryptomator-vault-metadata" extension="cryptomator" description="Cryptomator Vault Metadata" />
|
||||
</fx:info>
|
||||
<fx:platform j2se="8.0">
|
||||
<fx:property name="cryptomator.logPath" value="~/.Cryptomator/cryptomator.log" />
|
||||
<fx:property name="cryptomator.upgradeLogPath" value="~/.Cryptomator/upgrade.log" />
|
||||
<fx:property name="cryptomator.settingsPath" value="~/.Cryptomator/settings.json" />
|
||||
<fx:property name="cryptomator.ipcPortPath" value="~/.Cryptomator/ipcPort.bin" />
|
||||
<fx:jvmarg value="-Xmx512m"/>
|
||||
</fx:platform>
|
||||
<fx:resources>
|
||||
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
|
||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
|
||||
<fx:fileset dir="fixed-binaries" type="data" includes="linux-launcher-*" arch=""/>
|
||||
</fx:resources>
|
||||
<fx:permissions elevated="false" />
|
||||
<fx:preferences install="true" />
|
||||
</fx:deploy>
|
||||
</target>
|
||||
|
||||
<!-- Create Red Hat package -->
|
||||
<target name="rpm" depends="create-jar">
|
||||
<fx:deploy nativeBundles="rpm" outdir="antbuild" outfile="Cryptomator-${project.version}" verbose="true">
|
||||
<fx:application refid="Cryptomator" />
|
||||
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="MIT" category="Utility">
|
||||
<fx:association mimetype="application/x-vnd.cryptomator-vault-metadata" extension="cryptomator" description="Cryptomator Vault Metadata" />
|
||||
</fx:info>
|
||||
<fx:platform j2se="8.0">
|
||||
<fx:property name="cryptomator.logPath" value="~/.Cryptomator/cryptomator.log" />
|
||||
<fx:property name="cryptomator.upgradeLogPath" value="~/.Cryptomator/upgrade.log" />
|
||||
<fx:property name="cryptomator.settingsPath" value="~/.Cryptomator/settings.json" />
|
||||
<fx:property name="cryptomator.ipcPortPath" value="~/.Cryptomator/ipcPort.bin" />
|
||||
<fx:jvmarg value="-Xmx512m"/>
|
||||
</fx:platform>
|
||||
<fx:resources>
|
||||
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
|
||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
|
||||
<fx:fileset dir="fixed-binaries" type="data" includes="linux-launcher-*" arch=""/>
|
||||
</fx:resources>
|
||||
<fx:permissions elevated="false" />
|
||||
<fx:preferences install="true" />
|
||||
</fx:deploy>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
BIN
main/ant-kit/src/main/resources/package/linux/Cryptomator.png
Normal file
BIN
main/ant-kit/src/main/resources/package/linux/Cryptomator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
16
main/ant-kit/src/main/resources/package/linux/control
Normal file
16
main/ant-kit/src/main/resources/package/linux/control
Normal file
@@ -0,0 +1,16 @@
|
||||
Package: APPLICATION_PACKAGE
|
||||
Version: APPLICATION_VERSION
|
||||
Section: contrib/utils
|
||||
Maintainer: Sebastian Stenzel <sebastian.stenzel@gmail.com>
|
||||
Homepage: https://cryptomator.org
|
||||
Vcs-Git: https://github.com/totalvoidness/cryptomator.git
|
||||
Vcs-Browser: https://github.com/totalvoidness/cryptomator
|
||||
Priority: optional
|
||||
Architecture: APPLICATION_ARCH
|
||||
Provides: APPLICATION_PACKAGE
|
||||
Installed-Size: APPLICATION_INSTALLED_SIZE
|
||||
Depends: gvfs-bin, gvfs-backends, gvfs-fuse
|
||||
Description: Multi-platform client-side encryption of your cloud files.
|
||||
Cryptomator provides free client-side AES encryption for your cloud files.
|
||||
Create encrypted vaults, which get mounted as virtual volumes. Whatever
|
||||
you save on one of these volumes will end up encrypted inside your vault.
|
||||
23
main/ant-kit/src/main/resources/package/linux/copyright
Normal file
23
main/ant-kit/src/main/resources/package/linux/copyright
Normal file
@@ -0,0 +1,23 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: cryptomator
|
||||
Source: <https://github.com/totalvoidness/cryptomator>
|
||||
|
||||
Copyright: 2015 Sebastian Stenzel <sebastian.stenzel@gmail.com> and contributors.
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
50
main/ant-kit/src/main/resources/package/linux/postinst
Normal file
50
main/ant-kit/src/main/resources/package/linux/postinst
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/bin/sh
|
||||
# postinst script for APPLICATION_NAME
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postinst> `configure' <most-recently-configured-version>
|
||||
# * <old-postinst> `abort-upgrade' <new version>
|
||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||
# <new-version>
|
||||
# * <postinst> `abort-remove'
|
||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||
# <failed-install-package> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
echo Adding shortcut to the menu
|
||||
SECONDARY_LAUNCHERS_INSTALL
|
||||
APP_CDS_CACHE
|
||||
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
||||
FILE_ASSOCIATION_INSTALL
|
||||
|
||||
rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
if [ $(uname -m) = "x86_64" ]; then
|
||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
else
|
||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
fi
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
54
main/ant-kit/src/main/resources/package/linux/spec
Normal file
54
main/ant-kit/src/main/resources/package/linux/spec
Normal file
@@ -0,0 +1,54 @@
|
||||
Summary: APPLICATION_SUMMARY
|
||||
Name: APPLICATION_PACKAGE
|
||||
Version: APPLICATION_VERSION
|
||||
Release: 1
|
||||
License: APPLICATION_LICENSE_TYPE
|
||||
Vendor: APPLICATION_VENDOR
|
||||
Prefix: /opt
|
||||
Provides: APPLICATION_PACKAGE
|
||||
Requires: ld-linux.so.2 libX11.so.6 libXext.so.6 libXi.so.6 libXrender.so.1 libXtst.so.6 libasound.so.2 libc.so.6 libdl.so.2 libgcc_s.so.1 libm.so.6 libpthread.so.0 libthread_db.so.1
|
||||
Autoprov: 0
|
||||
Autoreq: 0
|
||||
|
||||
#avoid ARCH subfolder
|
||||
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
|
||||
|
||||
#comment line below to enable effective jar compression
|
||||
#it could easily get your package size from 40 to 15Mb but
|
||||
#build time will substantially increase and it may require unpack200/system java to install
|
||||
%define __jar_repack %{nil}
|
||||
|
||||
%description
|
||||
APPLICATION_DESCRIPTION
|
||||
|
||||
%prep
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
rm -rf %{buildroot}
|
||||
mkdir -p %{buildroot}/opt
|
||||
cp -r %{_sourcedir}/APPLICATION_FS_NAME %{buildroot}/opt
|
||||
|
||||
%files
|
||||
APPLICATION_LICENSE_FILE
|
||||
/opt/APPLICATION_FS_NAME
|
||||
|
||||
%post
|
||||
SECONDARY_LAUNCHERS_INSTALL
|
||||
APP_CDS_CACHE
|
||||
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
||||
FILE_ASSOCIATION_INSTALL
|
||||
rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
if [ $(uname -m) = "x86_64" ]; then
|
||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
else
|
||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
fi
|
||||
|
||||
%preun
|
||||
SECONDARY_LAUNCHERS_REMOVE
|
||||
xdg-desktop-menu uninstall --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
||||
FILE_ASSOCIATION_REMOVE
|
||||
|
||||
%clean
|
||||
@@ -10,17 +10,26 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.3</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>commons-test</artifactId>
|
||||
<name>Cryptomator common test dependencies</name>
|
||||
<description>Shared utilities for tests</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.bechte.junit</groupId>
|
||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
||||
@@ -29,11 +38,6 @@
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package org.cryptomator.common.test;
|
||||
|
||||
import static java.nio.file.Files.walkFileTree;
|
||||
import static java.util.Collections.synchronizedSet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class TempFilesRemovedOnShutdown {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TempFilesRemovedOnShutdown.class);
|
||||
|
||||
private static final Set<Path> PATHS_TO_REMOVE_ON_SHUTDOWN = synchronizedSet(new HashSet<>());
|
||||
private static final Thread ON_SHUTDOWN_DELETER = new Thread(TempFilesRemovedOnShutdown::removeAll);
|
||||
|
||||
static {
|
||||
Runtime.getRuntime().addShutdownHook(ON_SHUTDOWN_DELETER);
|
||||
}
|
||||
|
||||
public static Path createTempDirectory(String prefix) throws IOException {
|
||||
Path path = Files.createTempDirectory(prefix);
|
||||
PATHS_TO_REMOVE_ON_SHUTDOWN.add(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void removeAll() {
|
||||
PATHS_TO_REMOVE_ON_SHUTDOWN.forEach(TempFilesRemovedOnShutdown::remove);
|
||||
}
|
||||
|
||||
private static void remove(Path path) {
|
||||
try {
|
||||
tryRemove(path);
|
||||
} catch (Throwable e) {
|
||||
LOG.debug("Failed to remove " + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryRemove(Path path) throws IOException {
|
||||
walkFileTree(path, new FileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.cryptomator.common.test.matcher;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
/**
|
||||
* Wraps hamcrest contains and containsInAnyOrder matcher factory methods to
|
||||
* avoid problems due to incorrect / inconsistent handling of generics by
|
||||
* several java compilers.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
public class ContainsMatcher {
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@SafeVarargs
|
||||
public static <T> Matcher<Iterable<? super T>> containsInAnyOrder(Matcher<? extends T>... matchers) {
|
||||
return Matchers.containsInAnyOrder((Matcher[]) matchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@SafeVarargs
|
||||
public static <T> Matcher<Iterable<? super T>> contains(Matcher<? extends T>... matchers) {
|
||||
return Matchers.contains((Matcher[]) matchers);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package org.cryptomator.common.test.matcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher;
|
||||
|
||||
public class ExceptionMatcher<T extends Throwable> extends TypeSafeDiagnosingMatcher<T> {
|
||||
|
||||
public static <T extends Throwable> ExceptionMatcher<T> ofType(Class<T> exceptionType) {
|
||||
return new ExceptionMatcher<>(exceptionType);
|
||||
}
|
||||
|
||||
private final Class<T> exceptionType;
|
||||
private final Optional<Matcher<T>> subMatcher;
|
||||
|
||||
private ExceptionMatcher(Class<T> exceptionType) {
|
||||
super(exceptionType);
|
||||
this.exceptionType = exceptionType;
|
||||
this.subMatcher = Optional.empty();
|
||||
}
|
||||
|
||||
private ExceptionMatcher(Class<T> exceptionType, Matcher<T> subMatcher) {
|
||||
super(exceptionType);
|
||||
this.exceptionType = exceptionType;
|
||||
this.subMatcher = Optional.of(subMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
subMatcher.ifPresent(description::appendDescriptionOf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(T item, Description mismatchDescription) {
|
||||
if (subMatcher.map(matcher -> !matcher.matches(item)).orElse(false)) {
|
||||
subMatcher.get().describeMismatch(item, mismatchDescription);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Matcher<T> withCauseThat(Matcher<? super Throwable> matcher) {
|
||||
return new ExceptionMatcher<T>(exceptionType, new PropertyMatcher<>(exceptionType, Throwable::getCause, "cause", matcher));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.cryptomator.common.test.matcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher;
|
||||
|
||||
public class OptionalMatcher {
|
||||
|
||||
public static <T> Matcher<Optional<T>> presentOptionalWithValueThat(Matcher<? super T> valueMatcher) {
|
||||
return new TypeSafeDiagnosingMatcher<Optional<T>>(Optional.class) {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description //
|
||||
.appendText("a present Optional with a value that ") //
|
||||
.appendDescriptionOf(valueMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(Optional<T> item, Description mismatchDescription) {
|
||||
if (item.isPresent()) {
|
||||
if (valueMatcher.matches(item.get())) {
|
||||
return true;
|
||||
} else {
|
||||
mismatchDescription.appendText("a present Optional with value that ");
|
||||
valueMatcher.describeMismatch(item, mismatchDescription);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
mismatchDescription.appendText("an empty Optional");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Matcher<Optional<T>> emptyOptional() {
|
||||
return new TypeSafeDiagnosingMatcher<Optional<T>>(Optional.class) {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("an empty Optional");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(Optional<T> item, Description mismatchDescription) {
|
||||
if (item.isPresent()) {
|
||||
mismatchDescription.appendText("a present Optional of ").appendValue(item.get());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.common.test.matcher;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher;
|
||||
|
||||
public class PropertyMatcher<T, P> extends TypeSafeDiagnosingMatcher<T> {
|
||||
|
||||
private final Class<T> expectedType;
|
||||
private final Function<? super T, P> getter;
|
||||
private final String name;
|
||||
private final Matcher<? super P> subMatcher;
|
||||
|
||||
public PropertyMatcher(Class<T> type, Function<? super T, P> getter, String name, Matcher<? super P> subMatcher) {
|
||||
super(type);
|
||||
this.expectedType = type;
|
||||
this.getter = getter;
|
||||
this.name = name;
|
||||
this.subMatcher = subMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("a ") //
|
||||
.appendText(expectedType.getSimpleName()) //
|
||||
.appendText(" with a ") //
|
||||
.appendText(name) //
|
||||
.appendText(" that ") //
|
||||
.appendDescriptionOf(subMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(T item, Description mismatchDescription) {
|
||||
P propertyValue = getter.apply(item);
|
||||
if (subMatcher.matches(propertyValue)) {
|
||||
return true;
|
||||
} else {
|
||||
mismatchDescription.appendText("a ") //
|
||||
.appendText(expectedType.getSimpleName()) //
|
||||
.appendText(" with a ") //
|
||||
.appendText(name) //
|
||||
.appendText(" that ");
|
||||
subMatcher.describeMismatch(propertyValue, mismatchDescription);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.cryptomator.common.test.mockito;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
public class Answers {
|
||||
|
||||
public static <T> Answer<T> collectParameters(Answer<T> answer, Consumer<?>... parameterConsumers) {
|
||||
return new Answer<T>() {
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
public T answer(InvocationOnMock invocation) throws Throwable {
|
||||
for (int i = 0; i < invocation.getArguments().length; i++) {
|
||||
if (parameterConsumers.length > i) {
|
||||
((Consumer) parameterConsumers[i]).accept(invocation.getArguments()[i]);
|
||||
}
|
||||
}
|
||||
return answer.answer(invocation);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Answer<T> consecutiveAnswers(Answer<T>... answers) {
|
||||
if (answers == null || answers.length == 0) {
|
||||
throw new IllegalArgumentException("Required at least one answer");
|
||||
}
|
||||
if (asList(answers).contains(null)) {
|
||||
throw new IllegalArgumentException("No answers must be null");
|
||||
}
|
||||
return new Answer<T>() {
|
||||
private int nextIndex = 0;
|
||||
|
||||
@Override
|
||||
public T answer(InvocationOnMock invocation) throws Throwable {
|
||||
try {
|
||||
return answers[nextIndex].answer(invocation);
|
||||
} finally {
|
||||
nextIndex = (nextIndex + 1) % answers.length;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Answer<T> value(T value) {
|
||||
return new Answer<T>() {
|
||||
@Override
|
||||
public T answer(InvocationOnMock invocation) throws Throwable {
|
||||
return value;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,32 +10,56 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.3</version>
|
||||
<version>1.3.0-rc1</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator common</name>
|
||||
<name>Cryptomator Commons</name>
|
||||
<description>Shared utilities</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Libs -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.bechte.junit</groupId>
|
||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
||||
<scope>test</scope>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DI -->
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger-compiler</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class CommonsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("SemVer")
|
||||
Comparator<String> providesSemVerComparator() {
|
||||
return new SemVerComparator();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ConsumerThrowingException<T, E extends Exception> {
|
||||
public interface ConsumerThrowingException<T, E extends Throwable> {
|
||||
|
||||
void accept(T t) throws E;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.cryptomator.common;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
public final class LazyInitializer {
|
||||
|
||||
@@ -9,7 +10,7 @@ public final class LazyInitializer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
|
||||
* 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.
|
||||
@@ -17,17 +18,59 @@ public final class LazyInitializer {
|
||||
* @return The initialized value
|
||||
*/
|
||||
public static <T> T initializeLazily(AtomicReference<T> reference, Supplier<T> factory) {
|
||||
final T existingInstance = reference.get();
|
||||
if (existingInstance != null) {
|
||||
return existingInstance;
|
||||
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 {
|
||||
final T newInstance = factory.get();
|
||||
if (reference.compareAndSet(null, newInstance)) {
|
||||
return newInstance;
|
||||
} else {
|
||||
return reference.get();
|
||||
try {
|
||||
return reference.updateAndGet(invokeFactoryIfNull(factory));
|
||||
} catch (InitializationException e) {
|
||||
if (exceptionType.isInstance(e.getCause())) {
|
||||
throw exceptionType.cast(e.getCause());
|
||||
} else {
|
||||
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 (RuntimeException e) {
|
||||
throw e; // don't catch unchecked exceptions
|
||||
} catch (Exception e) {
|
||||
throw new InitializationException(e);
|
||||
}
|
||||
} else {
|
||||
return currentValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class InitializationException extends RuntimeException {
|
||||
|
||||
public InitializationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RunnableThrowingException<T extends Exception> {
|
||||
public interface RunnableThrowingException<T extends Throwable> {
|
||||
|
||||
void run() throws T;
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Compares version strings according to <a href="http://semver.org/spec/v2.0.0.html">SemVer 2.0.0</a>.
|
||||
*/
|
||||
public class SemVerComparator implements Comparator<String> {
|
||||
|
||||
private static final char VERSION_SEP = '.'; // http://semver.org/spec/v2.0.0.html#spec-item-2
|
||||
private static final String PRE_RELEASE_SEP = "-"; // http://semver.org/spec/v2.0.0.html#spec-item-9
|
||||
private static final String BUILD_SEP = "+"; // http://semver.org/spec/v2.0.0.html#spec-item-10
|
||||
|
||||
@Override
|
||||
public int compare(String version1, String version2) {
|
||||
// "Build metadata SHOULD be ignored when determining version precedence.
|
||||
// Thus two versions that differ only in the build metadata, have the same precedence."
|
||||
String v1WithoutBuildMetadata = StringUtils.substringBefore(version1, BUILD_SEP);
|
||||
String v2WithoutBuildMetadata = StringUtils.substringBefore(version2, BUILD_SEP);
|
||||
|
||||
if (v1WithoutBuildMetadata.equals(v2WithoutBuildMetadata)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String v1MajorMinorPatch = StringUtils.substringBefore(v1WithoutBuildMetadata, PRE_RELEASE_SEP);
|
||||
String v2MajorMinorPatch = StringUtils.substringBefore(v2WithoutBuildMetadata, PRE_RELEASE_SEP);
|
||||
String v1PreReleaseVersion = StringUtils.substringAfter(v1WithoutBuildMetadata, PRE_RELEASE_SEP);
|
||||
String v2PreReleaseVersion = StringUtils.substringAfter(v2WithoutBuildMetadata, PRE_RELEASE_SEP);
|
||||
return compare(v1MajorMinorPatch, v1PreReleaseVersion, v2MajorMinorPatch, v2PreReleaseVersion);
|
||||
}
|
||||
|
||||
private int compare(String v1MajorMinorPatch, String v1PreReleaseVersion, String v2MajorMinorPatch, String v2PreReleaseVersion) {
|
||||
int comparisonResult = compareNumericallyThenLexicographically(v1MajorMinorPatch, v2MajorMinorPatch);
|
||||
if (comparisonResult == 0) {
|
||||
if (v1PreReleaseVersion.isEmpty()) {
|
||||
return 1; // 1.0.0 > 1.0.0-BETA
|
||||
} else if (v2PreReleaseVersion.isEmpty()) {
|
||||
return -1; // 1.0.0-BETA < 1.0.0
|
||||
} else {
|
||||
return compareNumericallyThenLexicographically(v1PreReleaseVersion, v2PreReleaseVersion);
|
||||
}
|
||||
} else {
|
||||
return comparisonResult;
|
||||
}
|
||||
}
|
||||
|
||||
private int compareNumericallyThenLexicographically(String version1, String version2) {
|
||||
final String[] vComps1 = StringUtils.split(version1, VERSION_SEP);
|
||||
final String[] vComps2 = StringUtils.split(version2, VERSION_SEP);
|
||||
final int commonCompCount = Math.min(vComps1.length, vComps2.length);
|
||||
|
||||
for (int i = 0; i < commonCompCount; i++) {
|
||||
int subversionComparisionResult = 0;
|
||||
try {
|
||||
final int v1 = Integer.parseInt(vComps1[i]);
|
||||
final int v2 = Integer.parseInt(vComps2[i]);
|
||||
subversionComparisionResult = v1 - v2;
|
||||
} catch (NumberFormatException ex) {
|
||||
// ok, lets compare this fragment lexicographically
|
||||
subversionComparisionResult = vComps1[i].compareTo(vComps2[i]);
|
||||
}
|
||||
if (subversionComparisionResult != 0) {
|
||||
return subversionComparisionResult;
|
||||
}
|
||||
}
|
||||
|
||||
// all in common so far? longest version string is considered the higher version:
|
||||
return vComps1.length - vComps2.length;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Markus Kreusch and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Kreusch - initial implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Utility to print stack traces while analyzing issues.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
public class StackTrace {
|
||||
|
||||
public static void print(String message) {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.err.println(stackTraceFor(message, thread));
|
||||
}
|
||||
|
||||
private static String stackTraceFor(String message, Thread thread) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
appendMessageAndThreadName(result, message, thread);
|
||||
appendStackTrace(thread, result);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static void appendStackTrace(Thread thread, StringBuilder result) {
|
||||
Stream.of(thread.getStackTrace()) //
|
||||
.skip(4) //
|
||||
.forEach(stackTraceElement -> append(stackTraceElement, result));
|
||||
}
|
||||
|
||||
private static void appendMessageAndThreadName(StringBuilder result, String message, Thread thread) {
|
||||
result //
|
||||
.append('[') //
|
||||
.append(thread.getName()) //
|
||||
.append("] ") //
|
||||
.append(message);
|
||||
}
|
||||
|
||||
private static void append(StackTraceElement stackTraceElement, StringBuilder result) {
|
||||
String className = stackTraceElement.getClassName();
|
||||
String methodName = stackTraceElement.getMethodName();
|
||||
String fileName = stackTraceElement.getFileName();
|
||||
int lineNumber = stackTraceElement.getLineNumber();
|
||||
result.append('\n') //
|
||||
.append(className).append(':').append(methodName) //
|
||||
.append(" (").append(fileName).append(':').append(lineNumber).append(')');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SupplierThrowingException<T, E extends Throwable> {
|
||||
|
||||
T get() throws E;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014, 2016 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
public class Settings {
|
||||
|
||||
public static final int MIN_PORT = 1024;
|
||||
public static final int MAX_PORT = 65535;
|
||||
public static final boolean DEFAULT_CHECK_FOR_UDPATES = true;
|
||||
public static final int DEFAULT_PORT = 42427;
|
||||
public static final boolean DEFAULT_USE_IPV6 = false;
|
||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
public static final String DEFAULT_GVFS_SCHEME = "dav";
|
||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||
|
||||
private final Consumer<Settings> saveCmd;
|
||||
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList();
|
||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
|
||||
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
||||
private final BooleanProperty useIpv6 = new SimpleBooleanProperty(DEFAULT_USE_IPV6);
|
||||
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
||||
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
|
||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||
|
||||
/**
|
||||
* Package-private constructor; use {@link SettingsProvider}.
|
||||
*/
|
||||
Settings(Consumer<Settings> saveCmd) {
|
||||
this.saveCmd = saveCmd;
|
||||
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
|
||||
checkForUpdates.addListener(this::somethingChanged);
|
||||
port.addListener(this::somethingChanged);
|
||||
useIpv6.addListener(this::somethingChanged);
|
||||
numTrayNotifications.addListener(this::somethingChanged);
|
||||
preferredGvfsScheme.addListener(this::somethingChanged);
|
||||
debugMode.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
|
||||
this.save();
|
||||
}
|
||||
|
||||
void save() {
|
||||
if (saveCmd != null) {
|
||||
saveCmd.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ObservableList<VaultSettings> getDirectories() {
|
||||
return directories;
|
||||
}
|
||||
|
||||
public BooleanProperty checkForUpdates() {
|
||||
return checkForUpdates;
|
||||
}
|
||||
|
||||
public IntegerProperty port() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public BooleanProperty useIpv6() {
|
||||
return useIpv6;
|
||||
}
|
||||
|
||||
public IntegerProperty numTrayNotifications() {
|
||||
return numTrayNotifications;
|
||||
}
|
||||
|
||||
public StringProperty preferredGvfsScheme() {
|
||||
return preferredGvfsScheme;
|
||||
}
|
||||
|
||||
public BooleanProperty debugMode() {
|
||||
return debugMode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
* 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.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
|
||||
|
||||
private final Consumer<Settings> saveCmd;
|
||||
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
|
||||
|
||||
public SettingsJsonAdapter(Consumer<Settings> saveCmd) {
|
||||
this.saveCmd = saveCmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Settings value) throws IOException {
|
||||
out.beginObject();
|
||||
out.name("directories");
|
||||
writeVaultSettingsArray(out, value.getDirectories());
|
||||
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
||||
out.name("port").value(value.port().get());
|
||||
out.name("useIpv6").value(value.useIpv6().get());
|
||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
|
||||
out.name("debugMode").value(value.debugMode().get());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
private void writeVaultSettingsArray(JsonWriter out, Iterable<VaultSettings> vaultSettings) throws IOException {
|
||||
out.beginArray();
|
||||
for (VaultSettings value : vaultSettings) {
|
||||
vaultSettingsJsonAdapter.write(out, value);
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings read(JsonReader in) throws IOException {
|
||||
Settings settings = new Settings(saveCmd);
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "directories":
|
||||
settings.getDirectories().addAll(readVaultSettingsArray(in, settings));
|
||||
break;
|
||||
case "checkForUpdatesEnabled":
|
||||
settings.checkForUpdates().set(in.nextBoolean());
|
||||
break;
|
||||
case "port":
|
||||
settings.port().set(in.nextInt());
|
||||
break;
|
||||
case "useIpv6":
|
||||
settings.useIpv6().set(in.nextBoolean());
|
||||
break;
|
||||
case "numTrayNotifications":
|
||||
settings.numTrayNotifications().set(in.nextInt());
|
||||
break;
|
||||
case "preferredGvfsScheme":
|
||||
settings.preferredGvfsScheme().set(in.nextString());
|
||||
break;
|
||||
case "debugMode":
|
||||
settings.debugMode().set(in.nextBoolean());
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private List<VaultSettings> readVaultSettingsArray(JsonReader in, Settings settings) throws IOException {
|
||||
List<VaultSettings> result = new ArrayList<>();
|
||||
in.beginArray();
|
||||
while (!JsonToken.END_ARRAY.equals(in.peek())) {
|
||||
result.add(vaultSettingsJsonAdapter.read(in, settings));
|
||||
}
|
||||
in.endArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
@Singleton
|
||||
public class SettingsProvider implements Provider<Settings> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
||||
private static final Path DEFAULT_SETTINGS_PATH;
|
||||
private static final long SAVE_DELAY_MS = 1000;
|
||||
|
||||
static {
|
||||
final FileSystem fs = FileSystems.getDefault();
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "AppData/Roaming/Cryptomator/settings.json");
|
||||
} else if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "Library/Application Support/Cryptomator/settings.json");
|
||||
} else {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, ".Cryptomator/settings.json");
|
||||
}
|
||||
}
|
||||
|
||||
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
||||
private final AtomicReference<Settings> settings = new AtomicReference<>();
|
||||
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter(this::scheduleSave);
|
||||
private final Gson gson;
|
||||
|
||||
@Inject
|
||||
public SettingsProvider() {
|
||||
this.gson = new GsonBuilder() //
|
||||
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
|
||||
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
|
||||
.create();
|
||||
}
|
||||
|
||||
private Path getSettingsPath() {
|
||||
final String settingsPathProperty = System.getProperty("cryptomator.settingsPath");
|
||||
return Optional.ofNullable(settingsPathProperty).filter(StringUtils::isNotBlank).map(this::replaceHomeDir).map(FileSystems.getDefault()::getPath).orElse(DEFAULT_SETTINGS_PATH);
|
||||
}
|
||||
|
||||
private String replaceHomeDir(String path) {
|
||||
if (path.startsWith("~/")) {
|
||||
return SystemUtils.USER_HOME + path.substring(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings get() {
|
||||
return LazyInitializer.initializeLazily(settings, this::load);
|
||||
}
|
||||
|
||||
private Settings load() {
|
||||
Settings settings;
|
||||
final Path settingsPath = getSettingsPath();
|
||||
try (InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
settings = gson.fromJson(reader, Settings.class);
|
||||
LOG.info("Settings loaded from " + settingsPath);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Failed to load settings, creating new one.");
|
||||
settings = new Settings(this::scheduleSave);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private void scheduleSave(Settings settings) {
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
ScheduledFuture<?> saveCmd = saveScheduler.schedule(() -> {
|
||||
this.save(settings);
|
||||
}, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
ScheduledFuture<?> previousSaveCmd = scheduledSaveCmd.getAndSet(saveCmd);
|
||||
if (previousSaveCmd != null) {
|
||||
previousSaveCmd.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void save(Settings settings) {
|
||||
assert settings != null : "method should only be invoked by #scheduleSave, which checks for null";
|
||||
final Path settingsPath = getSettingsPath();
|
||||
try {
|
||||
Files.createDirectories(settingsPath.getParent());
|
||||
try (OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
|
||||
gson.toJson(settings, writer);
|
||||
LOG.info("Settings saved to " + settingsPath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to save settings.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*******************************************************************************
|
||||
* 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.settings;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
public class VaultSettings {
|
||||
|
||||
private final Settings settings;
|
||||
private final String id;
|
||||
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
||||
private final StringProperty mountName = new SimpleStringProperty();
|
||||
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
||||
private final BooleanProperty mountAfterUnlock = new SimpleBooleanProperty();
|
||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty();
|
||||
|
||||
public VaultSettings(Settings settings, String id) {
|
||||
this.settings = settings;
|
||||
this.id = Objects.requireNonNull(id);
|
||||
|
||||
EasyBind.subscribe(path, this::deriveMountNameFromPath);
|
||||
path.addListener(this::somethingChanged);
|
||||
mountName.addListener(this::somethingChanged);
|
||||
winDriveLetter.addListener(this::somethingChanged);
|
||||
mountAfterUnlock.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
|
||||
settings.save();
|
||||
}
|
||||
|
||||
private void deriveMountNameFromPath(Path path) {
|
||||
if (path != null && StringUtils.isBlank(mountName.get())) {
|
||||
mountName.set(normalizeMountName(path.getFileName().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public static VaultSettings withRandomId(Settings settings) {
|
||||
return new VaultSettings(settings, generateId());
|
||||
}
|
||||
|
||||
private static String generateId() {
|
||||
return asBase64String(nineBytesFrom(UUID.randomUUID()));
|
||||
}
|
||||
|
||||
private static String asBase64String(byte[] bytes) {
|
||||
byte[] base64Bytes = Base64.getUrlEncoder().encode(bytes);
|
||||
return new String(base64Bytes, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private static byte[] nineBytesFrom(UUID uuid) {
|
||||
ByteBuffer uuidBuffer = ByteBuffer.allocate(9);
|
||||
uuidBuffer.putLong(uuid.getMostSignificantBits());
|
||||
uuidBuffer.put((byte) (uuid.getLeastSignificantBits() & 0xFF));
|
||||
uuidBuffer.flip();
|
||||
return uuidBuffer.array();
|
||||
}
|
||||
|
||||
public static String normalizeMountName(String mountName) {
|
||||
String normalizedMountName = StringUtils.stripAccents(mountName);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (char c : normalizedMountName.toCharArray()) {
|
||||
if (Character.isWhitespace(c)) {
|
||||
if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') {
|
||||
builder.append('_');
|
||||
}
|
||||
} else if (c < 127 && Character.isLetterOrDigit(c)) {
|
||||
builder.append(c);
|
||||
} else {
|
||||
if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') {
|
||||
builder.append('_');
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ObjectProperty<Path> path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public StringProperty mountName() {
|
||||
return mountName;
|
||||
}
|
||||
|
||||
public StringProperty winDriveLetter() {
|
||||
return winDriveLetter;
|
||||
}
|
||||
|
||||
public BooleanProperty mountAfterUnlock() {
|
||||
return mountAfterUnlock;
|
||||
}
|
||||
|
||||
public BooleanProperty revealAfterMount() {
|
||||
return revealAfterMount;
|
||||
}
|
||||
|
||||
/* Hashcode/Equals */
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof VaultSettings && obj.getClass().equals(this.getClass())) {
|
||||
VaultSettings other = (VaultSettings) obj;
|
||||
return Objects.equals(this.id, other.id);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*******************************************************************************
|
||||
* 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.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
class VaultSettingsJsonAdapter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultSettingsJsonAdapter.class);
|
||||
|
||||
public void write(JsonWriter out, VaultSettings value) throws IOException {
|
||||
out.beginObject();
|
||||
out.name("id").value(value.getId());
|
||||
out.name("path").value(value.path().get().toString());
|
||||
out.name("mountName").value(value.mountName().get());
|
||||
out.name("winDriveLetter").value(value.winDriveLetter().get());
|
||||
out.name("mountAfterUnlock").value(value.mountAfterUnlock().get());
|
||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
public VaultSettings read(JsonReader in, Settings settings) throws IOException {
|
||||
String id = null;
|
||||
String path = null;
|
||||
String mountName = null;
|
||||
String winDriveLetter = null;
|
||||
boolean mountAfterUnlock = true;
|
||||
boolean revealAfterMount = true;
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "id":
|
||||
id = in.nextString();
|
||||
break;
|
||||
case "path":
|
||||
path = in.nextString();
|
||||
break;
|
||||
case "mountName":
|
||||
mountName = in.nextString();
|
||||
break;
|
||||
case "winDriveLetter":
|
||||
winDriveLetter = in.nextString();
|
||||
break;
|
||||
case "mountAfterUnlock":
|
||||
mountAfterUnlock = in.nextBoolean();
|
||||
break;
|
||||
case "revealAfterMount":
|
||||
revealAfterMount = in.nextBoolean();
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId(settings) : new VaultSettings(settings, id);
|
||||
vaultSettings.mountName().set(mountName);
|
||||
vaultSettings.path().set(Paths.get(path));
|
||||
vaultSettings.winDriveLetter().set(winDriveLetter);
|
||||
vaultSettings.mountAfterUnlock().set(mountAfterUnlock);
|
||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.util;
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -21,8 +21,10 @@ public class SemVerComparatorTest {
|
||||
|
||||
@Test
|
||||
public void compareEqualVersions() {
|
||||
final int comparisonResult = semVerComparator.compare("1.23.4", "1.23.4");
|
||||
Assert.assertEquals(0, Integer.signum(comparisonResult));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231")));
|
||||
}
|
||||
|
||||
// newer versions in first argument
|
||||
@@ -32,7 +34,11 @@ public class SemVerComparatorTest {
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4a", "1.23.4")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78")));
|
||||
}
|
||||
|
||||
// newer versions in second argument
|
||||
@@ -42,7 +48,11 @@ public class SemVerComparatorTest {
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4a")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.cryptomator.common.WeakValuedCache;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
@@ -83,6 +83,7 @@ public class WeakValuedCacheTest {
|
||||
assertThat(result, is(sameInstance(theValue)));
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testCacheDoesNotPreventGarbageCollectionOfValues() {
|
||||
when(loader.apply(A_KEY)).thenAnswer(this::createValueUsingMoreThanHalfTheJvmMemory);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*******************************************************************************
|
||||
* 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.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SettingsJsonAdapterTest {
|
||||
|
||||
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter(this::noop);
|
||||
|
||||
private void noop(Settings settings) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeserialize() throws IOException {
|
||||
String vault1Json = "{\"id\": \"1\", \"path\": \"/vault1\", \"mountName\": \"vault1\", \"winDriveLetter\": \"X\"}";
|
||||
String vault2Json = "{\"id\": \"2\", \"path\": \"/vault2\", \"mountName\": \"vault2\", \"winDriveLetter\": \"Y\"}";
|
||||
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
|
||||
+ "\"checkForUpdatesEnabled\": true,"//
|
||||
+ "\"port\": 8080,"//
|
||||
+ "\"useIpv6\": true,"//
|
||||
+ "\"numTrayNotifications\": 42}";
|
||||
|
||||
Settings settings = adapter.fromJson(json);
|
||||
|
||||
Assert.assertTrue(settings.checkForUpdates().get());
|
||||
Assert.assertEquals(2, settings.getDirectories().size());
|
||||
Assert.assertEquals(8080, settings.port().get());
|
||||
Assert.assertTrue(settings.useIpv6().get());
|
||||
Assert.assertEquals(42, settings.numTrayNotifications().get());
|
||||
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* 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.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
|
||||
public class VaultSettingsJsonAdapterTest {
|
||||
|
||||
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
|
||||
|
||||
@Test
|
||||
public void testDeserialize() throws IOException {
|
||||
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true}";
|
||||
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
||||
Settings settings = Mockito.mock(Settings.class);
|
||||
|
||||
VaultSettings vaultSettings = adapter.read(jsonReader, settings);
|
||||
Assert.assertEquals("foo", vaultSettings.getId());
|
||||
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||
Assert.assertEquals("test", vaultSettings.mountName().get());
|
||||
Assert.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,21 +6,21 @@
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class VaultTest {
|
||||
public class VaultSettingsTest {
|
||||
|
||||
@Test
|
||||
public void testNormalize() throws Exception {
|
||||
assertEquals("_", Vault.normalize(" "));
|
||||
assertEquals("a", Vault.normalize("ä"));
|
||||
assertEquals("C", Vault.normalize("Ĉ"));
|
||||
assertEquals("_", Vault.normalize(":"));
|
||||
assertEquals("", Vault.normalize("汉语"));
|
||||
assertEquals("_", VaultSettings.normalizeMountName(" "));
|
||||
assertEquals("a", VaultSettings.normalizeMountName("ä"));
|
||||
assertEquals("C", VaultSettings.normalizeMountName("Ĉ"));
|
||||
assertEquals("_", VaultSettings.normalizeMountName(":"));
|
||||
assertEquals("_", VaultSettings.normalizeMountName("汉语"));
|
||||
}
|
||||
|
||||
}
|
||||
2
main/filesystem-api/.gitignore
vendored
2
main/filesystem-api/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/target/
|
||||
@@ -1,55 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
class Copier {
|
||||
|
||||
public static void copy(Folder source, Folder destination) {
|
||||
assertFoldersAreNotNested(source, destination);
|
||||
|
||||
destination.delete();
|
||||
destination.create();
|
||||
|
||||
source.files().forEach(sourceFile -> {
|
||||
File destinationFile = destination.file(sourceFile.name());
|
||||
sourceFile.copyTo(destinationFile);
|
||||
});
|
||||
|
||||
source.folders().forEach(sourceFolder -> {
|
||||
Folder destinationFolder = destination.folder(sourceFolder.name());
|
||||
sourceFolder.copyTo(destinationFolder);
|
||||
});
|
||||
}
|
||||
|
||||
public static void copy(File source, File destination) {
|
||||
try (OpenFiles openFiles = DeadlockSafeFileOpener.withReadable(source).andWritable(destination).open()) {
|
||||
ReadableFile readable = openFiles.readable(source);
|
||||
WritableFile writable = openFiles.writable(destination);
|
||||
writable.truncate();
|
||||
ByteStreams.copy(readable, writable);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertFoldersAreNotNested(Folder source, Folder destination) {
|
||||
if (source.isAncestorOf(destination)) {
|
||||
throw new IllegalArgumentException("Can not copy parent to child directory (src: " + source + ", dst: " + destination + ")");
|
||||
}
|
||||
if (destination.isAncestorOf(source)) {
|
||||
throw new IllegalArgumentException("Can not copy child to parent directory (src: " + source + ", dst: " + destination + ")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class DeadlockSafeFileOpener {
|
||||
|
||||
public static DeadlockSafeFileOpener withReadable(File file) {
|
||||
return new DeadlockSafeFileOpener().andReadable(file);
|
||||
}
|
||||
|
||||
public static DeadlockSafeFileOpener withWritable(File file) {
|
||||
return new DeadlockSafeFileOpener().andWritable(file);
|
||||
}
|
||||
|
||||
private final SortedMap<File, Consumer<File>> filesWithOperation = new TreeMap<>();
|
||||
|
||||
private final Map<File, ReadableFile> readableFiles = new HashMap<>();
|
||||
private final Map<File, WritableFile> writableFiles = new HashMap<>();
|
||||
|
||||
private DeadlockSafeFileOpener() {
|
||||
}
|
||||
|
||||
public DeadlockSafeFileOpener andReadable(File file) {
|
||||
if (filesWithOperation.put(file, this::openReadable) != null) {
|
||||
throw new IllegalArgumentException(format("File %s already marked for opening", file));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DeadlockSafeFileOpener andWritable(File file) {
|
||||
if (filesWithOperation.put(file, this::openWritable) != null) {
|
||||
throw new IllegalArgumentException(format("File %s already marked for opening", file));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void openReadable(File file) {
|
||||
readableFiles.put(file, file.openReadable());
|
||||
}
|
||||
|
||||
private void openWritable(File file) {
|
||||
writableFiles.put(file, file.openWritable());
|
||||
}
|
||||
|
||||
public OpenFiles open() {
|
||||
try {
|
||||
filesWithOperation.forEach((file, openAction) -> openAction.accept(file));
|
||||
} catch (RuntimeException e) {
|
||||
OpenFiles.cleanup(readableFiles.values(), writableFiles.values());
|
||||
throw e;
|
||||
}
|
||||
return new OpenFiles(readableFiles, writableFiles);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
public class Deleter {
|
||||
|
||||
/**
|
||||
* Deletes all and only the content of a given {@link Folder} but <b>not</b> the folder itself.
|
||||
*/
|
||||
public static void deleteContent(Folder folder) {
|
||||
if (folder.exists()) {
|
||||
folder.folders().forEach(Folder::delete);
|
||||
folder.files().forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
/**
|
||||
* A {@link File} in a {@link FileSystem}.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
public interface File extends Node, Comparable<File> {
|
||||
|
||||
static final int EOF = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Opens this file for reading.
|
||||
* <p>
|
||||
* An implementation guarantees, that per {@link FileSystem} and
|
||||
* {@code File} while a {@code ReadableFile} is open no {@link WritableFile}
|
||||
* can be open and vice versa. A {@link ReadableFile} is open when returned
|
||||
* from this method and not yet closed using {@link ReadableFile#close()}.
|
||||
* <br>
|
||||
* A limitation to the number of {@code ReadableFiles} is in general not
|
||||
* required but may be set by a specific implementation.
|
||||
* <p>
|
||||
* If a {@link WritableFile} for this {@code File} is open the invocation of
|
||||
* this method will block regarding the specified timeout.<br>
|
||||
* In addition implementations may block to lock the required IO resources
|
||||
* to read the file.
|
||||
*
|
||||
* @return a {@link ReadableFile} to work with
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs while opening the file, the
|
||||
* file does not exist or is a directory
|
||||
*/
|
||||
|
||||
ReadableFile openReadable() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Opens this file for writing.
|
||||
* <p>
|
||||
* If the file does not exist a new empty file is created.
|
||||
* <p>
|
||||
* An implementation guarantees, that per {@link FileSystem} and
|
||||
* {@code File} only one {@link WritableFile} is open at a time. A
|
||||
* {@code WritableFile} is open when returned from this method and not yet
|
||||
* closed using {@link WritableFile#close()} or
|
||||
* {@link WritableFile#delete()}.<br>
|
||||
* In addition while a {@code WritableFile} is open no {@link ReadableFile}
|
||||
* can be open and vice versa.
|
||||
* <p>
|
||||
* If a {@code Readable-} or {@code WritableFile} for this {@code File} is
|
||||
* open the invocation of this method will block regarding the specified
|
||||
* timeout.<br>
|
||||
* In addition implementations may block to lock the required IO resources
|
||||
* to read the file.
|
||||
*
|
||||
* @return a {@link WritableFile} to work with
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs while opening the file or
|
||||
* the file is a directory
|
||||
*/
|
||||
WritableFile openWritable() throws UncheckedIOException;
|
||||
|
||||
default void copyTo(File destination) {
|
||||
Copier.copy(this, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves this file including content to a new location specified by <code>destination</code>.
|
||||
*/
|
||||
void moveTo(File destination) throws UncheckedIOException;
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* The root folder of a file system.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
public interface FileSystem extends Folder {
|
||||
|
||||
/**
|
||||
* @return an empty {@link Optional} because a {@link FileSystem} represents
|
||||
* the root {@link Folder} and thus does not have a parent
|
||||
*/
|
||||
@Override
|
||||
default Optional<? extends Folder> parent() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Long> quotaUsedBytes();
|
||||
|
||||
Optional<Long> quotaAvailableBytes();
|
||||
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A {@link Folder} in a {@link FileSystem}.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
public interface Folder extends Node {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates a {@link Stream} over all child nodes of this {@code Folder}.
|
||||
* <p>
|
||||
* <b>Note:</b> The {@link Stream} may be lazily populated and thus
|
||||
* {@link IOException IOExceptions} may occurs after this method returned.
|
||||
* In this case implementors should throw a {@link UncheckedIOException}
|
||||
* from any method that produces an {@link IOException}. Thus users should
|
||||
* expect {@link UncheckedIOException UncheckedIOExceptions} when invoking
|
||||
* methods on the returned {@code Stream}.
|
||||
*
|
||||
* @return the created {@code Stream}
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs while initializing the
|
||||
* stream or the {@code Folder} does not exist
|
||||
*/
|
||||
Stream<? extends Node> children() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the child {@link Node} in this directory of type {@link File}
|
||||
* with the specified name.
|
||||
* <p>
|
||||
* This operation always returns a {@link File} without checking if the file
|
||||
* exists or is a {@link Folder} instead.
|
||||
*/
|
||||
File file(String name) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* Returns a file by resolving a path relative to this folder.
|
||||
*
|
||||
* @param path A unix-style path, which is always relative to this folder, no matter if it starts with a slash or not. Path must not be empty.
|
||||
* @return File with the given path relative to this folder
|
||||
* @throws IllegalArgumentException
|
||||
* if relativePath is empty
|
||||
*/
|
||||
default File resolveFile(String relativePath) throws UncheckedIOException, IllegalArgumentException {
|
||||
return PathResolver.resolveFile(this, relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the child {@link Node} in this directory of type {@link Folder}
|
||||
* with the specified name.
|
||||
* <p>
|
||||
* This operation always returns a {@link Folder} without checking if the
|
||||
* folder exists or is a {@link File} instead.
|
||||
*/
|
||||
Folder folder(String name) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* Returns a folder by resolving a path relative to this folder.
|
||||
*
|
||||
* @param path A unix-style path, which is always relative to this folder, no matter if it starts with a slash or not. Path may be empty.
|
||||
* @return Folder with the given path relative to this folder. Returns <code>this</code> if path is empty.
|
||||
*/
|
||||
default Folder resolveFolder(String relativePath) throws UncheckedIOException {
|
||||
return PathResolver.resolveFolder(this, relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the directory including all parent directories, if it doesn't
|
||||
* exist yet. No effect, if folder already exists.
|
||||
*
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs while creating the folder or
|
||||
* one of its parents
|
||||
*/
|
||||
void create() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* Recusively copies this directory and all its contents to (not into) the
|
||||
* given destination, creating nonexisting parent directories. If the target
|
||||
* exists it is deleted before performing the copy.
|
||||
*
|
||||
* @param target
|
||||
* Destination folder. Must not be a descendant of this folder.
|
||||
*/
|
||||
default void copyTo(Folder target) throws UncheckedIOException {
|
||||
Copier.copy(this, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves this directory and its contents to the given destination. If the
|
||||
* target exists it is deleted before performing the move.
|
||||
*/
|
||||
void moveTo(Folder target);
|
||||
|
||||
/**
|
||||
* @return the result of {@link #children()} filtered to contain only
|
||||
* {@link File Files}
|
||||
*/
|
||||
default Stream<? extends File> files() throws UncheckedIOException {
|
||||
return children() //
|
||||
.filter(File.class::isInstance) //
|
||||
.map(File.class::cast);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the result of {@link #children()} filtered to contain only
|
||||
* {@link Folder Folders}
|
||||
*/
|
||||
default Stream<? extends Folder> folders() throws UncheckedIOException {
|
||||
return children() //
|
||||
.filter(Folder.class::isInstance) //
|
||||
.map(Folder.class::cast);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively checks whether this folder or any subfolder contains the
|
||||
* given node.
|
||||
*
|
||||
* @param node
|
||||
* Potential child, grandchild, ...
|
||||
* @return <code>true</code> if this folder is an ancestor of the node.
|
||||
*/
|
||||
default boolean isAncestorOf(Node node) {
|
||||
if (!node.parent().isPresent()) {
|
||||
return false;
|
||||
} else if (node.parent().get().equals(this)) {
|
||||
return true;
|
||||
} else {
|
||||
return this.isAncestorOf(node.parent().get());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FolderVisitor {
|
||||
|
||||
private final Consumer<Folder> beforeFolderVisitor;
|
||||
private final Consumer<Folder> afterFolderVisitor;
|
||||
private final Consumer<File> fileVisitor;
|
||||
private final Consumer<Node> nodeVisitor;
|
||||
private final int maxDepth;
|
||||
|
||||
public FolderVisitor(FolderVisitorBuilder builder) {
|
||||
this.beforeFolderVisitor = builder.beforeFolderVisitor;
|
||||
this.afterFolderVisitor = builder.afterFolderVisitor;
|
||||
this.fileVisitor = builder.fileVisitor;
|
||||
this.nodeVisitor = builder.nodeVisitor;
|
||||
this.maxDepth = builder.maxDepth;
|
||||
}
|
||||
|
||||
public static FolderVisitorBuilder folderVisitor() {
|
||||
return new FolderVisitorBuilder();
|
||||
}
|
||||
|
||||
public FolderVisitor visit(Folder folder) {
|
||||
return visit(folder, 0);
|
||||
}
|
||||
|
||||
public FolderVisitor visit(File file) {
|
||||
return visit(file, 0);
|
||||
}
|
||||
|
||||
private FolderVisitor visit(Folder folder, int depth) {
|
||||
beforeFolderVisitor.accept(folder);
|
||||
nodeVisitor.accept(folder);
|
||||
final int childDepth = depth + 1;
|
||||
if (childDepth <= maxDepth) {
|
||||
folder.folders().forEach(childFolder -> visit(childFolder, childDepth));
|
||||
folder.files().forEach(childFile -> visit(childFile, childDepth));
|
||||
}
|
||||
afterFolderVisitor.accept(folder);
|
||||
return this;
|
||||
}
|
||||
|
||||
private FolderVisitor visit(File file, int depth) {
|
||||
nodeVisitor.accept(file);
|
||||
fileVisitor.accept(file);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class FolderVisitorBuilder {
|
||||
|
||||
private Consumer<Folder> beforeFolderVisitor = noOp();
|
||||
private Consumer<Folder> afterFolderVisitor = noOp();
|
||||
private Consumer<File> fileVisitor = noOp();
|
||||
private Consumer<Node> nodeVisitor = noOp();
|
||||
private int maxDepth = Integer.MAX_VALUE;
|
||||
|
||||
private FolderVisitorBuilder() {
|
||||
}
|
||||
|
||||
public FolderVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
|
||||
if (beforeFolderVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
this.beforeFolderVisitor = beforeFolderVisitor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FolderVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
|
||||
if (afterFolderVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
this.afterFolderVisitor = afterFolderVisitor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FolderVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
|
||||
if (fileVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
this.fileVisitor = fileVisitor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FolderVisitorBuilder forEachNode(Consumer<Node> nodeVisitor) {
|
||||
if (nodeVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
this.nodeVisitor = nodeVisitor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FolderVisitorBuilder withMaxDepth(int maxDepth) {
|
||||
if (maxDepth < 0) {
|
||||
throw new IllegalArgumentException(format("maxDepth must not be smaller 0 but was %d", maxDepth));
|
||||
}
|
||||
this.maxDepth = maxDepth;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FolderVisitor visit(Folder folder) {
|
||||
return build().visit(folder);
|
||||
}
|
||||
|
||||
public FolderVisitor visit(File file) {
|
||||
return build().visit(file);
|
||||
}
|
||||
|
||||
public FolderVisitor build() {
|
||||
return new FolderVisitor(this);
|
||||
}
|
||||
|
||||
private static <T> Consumer<T> noOp() {
|
||||
return ignoredParameter -> {
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a node, namely a {@link File} or {@link Folder}, in a
|
||||
* {@link FileSystem}.
|
||||
* <p>
|
||||
* A node's identity (i.e. {@link #hashCode()} and {@link #equals(Object)}) depends on its parent node and its name (forming the node's path).
|
||||
* These properties are meant to be immutable. This means that e.g. moving a node doesn't modify the node's identity but rather transfers properties to the destination node.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
* @see Folder
|
||||
* @see File
|
||||
*/
|
||||
public interface Node {
|
||||
|
||||
String name() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* @return Optional parent folder. No parent is present for the root node (see {@link FileSystem#parent()}).
|
||||
*/
|
||||
Optional<? extends Folder> parent() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the node exists.
|
||||
*/
|
||||
boolean exists() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Deletes the node if it exists.
|
||||
* <p>
|
||||
* Does nothing if the node does not exist.
|
||||
*/
|
||||
void delete() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Determines the last modified date of this node.
|
||||
*
|
||||
* @returns the last modified date of the file
|
||||
*/
|
||||
Instant lastModified() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the last modified date of the file.
|
||||
*
|
||||
* @param lastModified the time to set as creation time
|
||||
*/
|
||||
void setLastModified(Instant lastModified) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Determines the creation time of this node.
|
||||
* <p>
|
||||
* Note: Getting the creation time may not be supported by all {@link FileSystem FileSystems}.
|
||||
*
|
||||
* @returns the creation time of the file or {@link Optional#empty()} if not supported
|
||||
*/
|
||||
default Optional<Instant> creationTime() throws UncheckedIOException {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the creation time of this node.
|
||||
* <p>
|
||||
* Setting the creation time may not be supported by all {@link FileSystem FileSystems}. If the {@code FileSystem} this {@code Node} belongs to does not support the
|
||||
* setting the creation time the behavior of this method is unspecified.
|
||||
*
|
||||
* @param creationTime the time to set as creation time
|
||||
*/
|
||||
default void setCreationTime(Instant creationTime) throws UncheckedIOException {
|
||||
throw new UncheckedIOException(new IOException("CreationTime not supported"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link FileSystem} this Node belongs to
|
||||
*/
|
||||
default FileSystem fileSystem() {
|
||||
return parent() //
|
||||
.map(Node::fileSystem) //
|
||||
.orElseGet(() -> (FileSystem) this);
|
||||
}
|
||||
|
||||
default boolean belongsToSameFilesystem(Node other) {
|
||||
return fileSystem() == other.fileSystem();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class OpenFiles implements AutoCloseable {
|
||||
|
||||
private final static Logger LOG = LoggerFactory.getLogger(OpenFiles.class);
|
||||
|
||||
private final Map<File, ReadableFile> readableFiles;
|
||||
private final Map<File, WritableFile> writableFiles;
|
||||
|
||||
public OpenFiles(Map<File, ReadableFile> readableFiles, Map<File, WritableFile> writableFiles) {
|
||||
this.readableFiles = readableFiles;
|
||||
this.writableFiles = writableFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
OpenFiles.cleanup(readableFiles.values(), writableFiles.values());
|
||||
}
|
||||
|
||||
public ReadableFile readable(File file) {
|
||||
return readableFiles.computeIfAbsent(file, fileNotOpenForReading -> {
|
||||
throw new IllegalArgumentException(String.format("File %s is not open for reading", fileNotOpenForReading));
|
||||
});
|
||||
}
|
||||
|
||||
public WritableFile writable(File file) {
|
||||
return writableFiles.computeIfAbsent(file, fileNotOpenForWriting -> {
|
||||
throw new IllegalArgumentException(String.format("File %s is not open for writing", fileNotOpenForWriting));
|
||||
});
|
||||
}
|
||||
|
||||
static void cleanup(Collection<ReadableFile> readableFiles, Collection<WritableFile> writableFiles) {
|
||||
Iterator<? extends AutoCloseable> iterator = Stream.concat(readableFiles.stream(), writableFiles.stream()).iterator();
|
||||
UncheckedIOException firstException = null;
|
||||
while (iterator.hasNext()) {
|
||||
AutoCloseable openFile = iterator.next();
|
||||
try {
|
||||
openFile.close();
|
||||
} catch (UncheckedIOException e) {
|
||||
if (firstException == null) {
|
||||
firstException = e;
|
||||
} else {
|
||||
firstException.addSuppressed(e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Unexpected exception during close on " + openFile.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
if (firstException != null) {
|
||||
throw firstException;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
final class PathResolver {
|
||||
|
||||
private static final String DOT = ".";
|
||||
private static final String DOTDOT = "..";
|
||||
|
||||
private PathResolver() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a relative path (separated by '/') to a folder, e.g.
|
||||
* <!-- @formatter:off -->
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th>dir</th>
|
||||
* <th>path</th>
|
||||
* <th>result</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <td>/foo/bar</td>
|
||||
* <td>foo/bar</td>
|
||||
* <td>/foo/bar/foo/bar</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>/foo/bar</td>
|
||||
* <td>../baz</td>
|
||||
* <td>/foo/baz</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>/foo/bar</td>
|
||||
* <td>./foo/..</td>
|
||||
* <td>/foo/bar</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>/foo/bar</td>
|
||||
* <td>/</td>
|
||||
* <td>/foo/bar</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>/foo/bar</td>
|
||||
* <td></td>
|
||||
* <td>/foo/bar</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>/foo/bar</td>
|
||||
* <td>../../..</td>
|
||||
* <td>Exception</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
* @param dir The directory from which to resolve the path.
|
||||
* @param relativePath The path relative to a given directory.
|
||||
* @return The folder with the given path relative to the given dir.
|
||||
*/
|
||||
public static Folder resolveFolder(Folder dir, String relativePath) {
|
||||
final String[] fragments = StringUtils.split(relativePath, '/');
|
||||
if (ArrayUtils.isEmpty(fragments)) {
|
||||
return dir;
|
||||
}
|
||||
return resolveFolder(dir, Arrays.stream(fragments).iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a relative path (separated by '/') to a file. Besides returning a File, this method is identical to {@link #resolveFile(Folder, String)}.
|
||||
*
|
||||
* @param dir The directory from which to resolve the path.
|
||||
* @param relativePath The path relative to a given directory.
|
||||
* @return The file with the given path relative to the given dir.
|
||||
* @throws IllegalArgumentException
|
||||
* if relativePath is empty, as this path would resolve to the directory itself, which obviously can't be a file.
|
||||
*/
|
||||
public static File resolveFile(Folder dir, String relativePath) {
|
||||
final String[] fragments = StringUtils.split(relativePath, '/');
|
||||
if (ArrayUtils.isEmpty(fragments)) {
|
||||
throw new IllegalArgumentException("Empty relativePath");
|
||||
}
|
||||
final Folder folder = resolveFolder(dir, Arrays.stream(fragments).limit(fragments.length - 1).iterator());
|
||||
final String filename = fragments[fragments.length - 1];
|
||||
return folder.file(filename);
|
||||
}
|
||||
|
||||
private static Folder resolveFolder(Folder dir, Iterator<String> remainingPathFragments) {
|
||||
if (!remainingPathFragments.hasNext()) {
|
||||
return dir;
|
||||
}
|
||||
final String fragment = remainingPathFragments.next();
|
||||
assert fragment.length() > 0 : "iterator must not contain empty fragments";
|
||||
if (DOT.equals(fragment)) {
|
||||
return resolveFolder(dir, remainingPathFragments);
|
||||
} else if (DOTDOT.equals(fragment) && dir.parent().isPresent()) {
|
||||
return resolveFolder(dir.parent().get(), remainingPathFragments);
|
||||
} else if (DOTDOT.equals(fragment) && !dir.parent().isPresent()) {
|
||||
throw new UncheckedIOException(new FileNotFoundException("Unresolvable path"));
|
||||
} else {
|
||||
return resolveFolder(dir.folder(fragment), remainingPathFragments);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
public interface ReadableFile extends ReadableByteChannel {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Tries to fill the remaining space in the given byte buffer with data from
|
||||
* this readable bytes from the current position.
|
||||
* <p>
|
||||
* May read less bytes if the end of this readable bytes has been reached.
|
||||
*
|
||||
* @param target
|
||||
* the byte buffer to fill
|
||||
* @return the number of bytes actually read, or {@code -1} if the end of
|
||||
* file has been reached
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs while reading from this
|
||||
* {@code ReadableBytes}
|
||||
*/
|
||||
@Override
|
||||
int read(ByteBuffer target) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* @return The current size of the file. This value is a snapshot and might have been changed by concurrent modifications.
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs
|
||||
*/
|
||||
long size() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Fast-forwards or rewinds the file to the specified position.
|
||||
* <p>
|
||||
* Consecutive reads on the file will begin at the new position.
|
||||
*
|
||||
* @param position
|
||||
* the position to set the file to
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs
|
||||
*
|
||||
*/
|
||||
void position(long position) throws UncheckedIOException;
|
||||
|
||||
@Override
|
||||
void close() throws UncheckedIOException;
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
public interface WritableFile extends WritableByteChannel {
|
||||
|
||||
void truncate() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* Writes the data in the given byte buffer to this readable bytes at the
|
||||
* current position.
|
||||
*
|
||||
* @param source
|
||||
* the byte buffer to use
|
||||
* @return the number of bytes written, always equal to
|
||||
* {@code source.remaining()}
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs while writing
|
||||
*/
|
||||
@Override
|
||||
int write(ByteBuffer source) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Fast-forwards or rewinds the file to the specified position.
|
||||
* <p>
|
||||
* Consecutive writes on the file will begin at the new position.
|
||||
* <p>
|
||||
* If the position is set to a value greater than the current end of file
|
||||
* consecutive writes will write data to the given position. The value of
|
||||
* all bytes between this position and the previous end of file will be
|
||||
* unspecified.
|
||||
*
|
||||
* @param position
|
||||
* the position to set the file to
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs
|
||||
*/
|
||||
void position(long position) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Closes this {@code WritableFile} which finally commits all operations
|
||||
* performed on it to the underlying file system.
|
||||
* <p>
|
||||
* After a {@code WritableFile} has been closed all other operations will
|
||||
* throw an {@link UncheckedIOException}.
|
||||
* <p>
|
||||
* Invoking this method on a {@link WritableFile} which has already been
|
||||
* closed does nothing.
|
||||
*/
|
||||
@Override
|
||||
void close() throws UncheckedIOException;
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
|
||||
public abstract class DelegatingFile<D extends DelegatingFolder<D, ?>> extends DelegatingNode<File>implements File {
|
||||
|
||||
private final D parent;
|
||||
|
||||
public DelegatingFile(D parent, File delegate) {
|
||||
super(delegate);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<D> parent() throws UncheckedIOException {
|
||||
return Optional.of(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableFile openReadable() throws UncheckedIOException {
|
||||
return delegate.openReadable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritableFile openWritable() throws UncheckedIOException {
|
||||
return delegate.openWritable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(File destination) {
|
||||
if (getClass().equals(destination.getClass())) {
|
||||
final File delegateDest = ((DelegatingFile<?>) destination).delegate;
|
||||
delegate.copyTo(delegateDest);
|
||||
} else {
|
||||
delegate.copyTo(destination);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(File destination) {
|
||||
if (getClass().equals(destination.getClass())) {
|
||||
final File delegateDest = ((DelegatingFile<?>) destination).delegate;
|
||||
delegate.moveTo(delegateDest);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Can only move DelegatingFile to other DelegatingFile.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws UncheckedIOException {
|
||||
delegate.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(File o) {
|
||||
if (getClass().equals(o.getClass())) {
|
||||
final File delegateOther = ((DelegatingFile<?>) o).delegate;
|
||||
return delegate.compareTo(delegateOther);
|
||||
} else {
|
||||
return delegate.compareTo(o);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
|
||||
public interface DelegatingFileSystem extends FileSystem {
|
||||
|
||||
Folder getDelegate();
|
||||
|
||||
@Override
|
||||
default Optional<Long> quotaUsedBytes() {
|
||||
return getDelegate().fileSystem().quotaUsedBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Optional<Long> quotaAvailableBytes() {
|
||||
return getDelegate().fileSystem().quotaAvailableBytes();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.cryptomator.common.WeakValuedCache;
|
||||
import org.cryptomator.common.streams.AutoClosingStream;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
|
||||
public abstract class DelegatingFolder<D extends DelegatingFolder<D, F>, F extends DelegatingFile<D>> extends DelegatingNode<Folder>implements Folder {
|
||||
|
||||
private final D parent;
|
||||
private final WeakValuedCache<Folder, D> folders = WeakValuedCache.usingLoader(this::newFolder);
|
||||
private final WeakValuedCache<File, F> files = WeakValuedCache.usingLoader(this::newFile);
|
||||
|
||||
public DelegatingFolder(D parent, Folder delegate) {
|
||||
super(delegate);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<D> parent() throws UncheckedIOException {
|
||||
return Optional.ofNullable(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends Node> children() throws UncheckedIOException {
|
||||
return AutoClosingStream.from(Stream.concat(folders(), files()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<D> folders() {
|
||||
return delegate.folders().map(folders::get);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<F> files() throws UncheckedIOException {
|
||||
return delegate.files().map(files::get);
|
||||
}
|
||||
|
||||
@Override
|
||||
public F file(String name) throws UncheckedIOException {
|
||||
return files.get(delegate.file(name));
|
||||
}
|
||||
|
||||
protected abstract F newFile(File delegate);
|
||||
|
||||
@Override
|
||||
public D folder(String name) throws UncheckedIOException {
|
||||
return folders.get(delegate.folder(name));
|
||||
}
|
||||
|
||||
protected abstract D newFolder(Folder delegate);
|
||||
|
||||
@Override
|
||||
public void create() throws UncheckedIOException {
|
||||
if (exists()) {
|
||||
return;
|
||||
}
|
||||
parent().ifPresent(p -> p.create());
|
||||
delegate.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
delegate.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(Folder destination) throws UncheckedIOException {
|
||||
if (destination instanceof DelegatingFolder) {
|
||||
final Folder delegateDest = ((DelegatingFolder<?, ?>) destination).delegate;
|
||||
delegate.copyTo(delegateDest);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Can only copy DelegatingFolder to other DelegatingFolder.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(Folder destination) {
|
||||
if (getClass().equals(destination.getClass())) {
|
||||
final Folder delegateDest = ((DelegatingFolder<?, ?>) destination).delegate;
|
||||
delegate.moveTo(delegateDest);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Can only move DelegatingFolder to other DelegatingFolder.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.filesystem.Node;
|
||||
|
||||
public abstract class DelegatingNode<T extends Node> implements Node {
|
||||
|
||||
protected final T delegate;
|
||||
|
||||
public DelegatingNode(T delegate) {
|
||||
if (delegate == null) {
|
||||
throw new IllegalArgumentException("Delegate must not be null");
|
||||
}
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() throws UncheckedIOException {
|
||||
return delegate.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws UncheckedIOException {
|
||||
return delegate.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant lastModified() throws UncheckedIOException {
|
||||
return delegate.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastModified(Instant instant) throws UncheckedIOException {
|
||||
delegate.setLastModified(instant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Instant> creationTime() throws UncheckedIOException {
|
||||
return delegate.creationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(Instant instant) throws UncheckedIOException {
|
||||
delegate.setCreationTime(instant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof DelegatingNode) {
|
||||
DelegatingNode<?> other = (DelegatingNode<?>) obj;
|
||||
return this.delegate.equals(other.delegate);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Delegate[" + delegate + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
|
||||
public class DelegatingReadableFile implements ReadableFile {
|
||||
|
||||
private final ReadableFile delegate;
|
||||
|
||||
public DelegatingReadableFile(ReadableFile delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return delegate.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer target) throws UncheckedIOException {
|
||||
return delegate.read(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
delegate.position(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
|
||||
public class DelegatingWritableFile implements WritableFile {
|
||||
|
||||
final WritableFile delegate;
|
||||
|
||||
public DelegatingWritableFile(WritableFile delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return delegate.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void truncate() throws UncheckedIOException {
|
||||
delegate.truncate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer source) throws UncheckedIOException {
|
||||
return delegate.write(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
delegate.position(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
/**
|
||||
* Defines a file system abstraction to allow access to real and virtual file
|
||||
* systems through a common API.
|
||||
*/
|
||||
package org.cryptomator.filesystem;
|
||||
@@ -1,35 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class ByteBuffers {
|
||||
|
||||
private ByteBuffers() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies as many bytes as possible from the given source to the destination buffer.
|
||||
* The position of both buffers will be incremented by as many bytes as have been copied.
|
||||
*
|
||||
* @param source ByteBuffer from which bytes are read
|
||||
* @param destination ByteBuffer into which bytes are written
|
||||
* @return number of bytes copied, i.e. {@link ByteBuffer#remaining() source.remaining()} or {@link ByteBuffer#remaining() destination.remaining()}, whatever is less.
|
||||
*/
|
||||
public static int copy(ByteBuffer source, ByteBuffer destination) {
|
||||
final int numBytes = Math.min(source.remaining(), destination.remaining());
|
||||
final ByteBuffer tmp = source.asReadOnlyBuffer();
|
||||
tmp.limit(tmp.position() + numBytes);
|
||||
destination.put(tmp);
|
||||
source.position(tmp.position()); // until now only tmp pos has been incremented, so we need to adjust the position
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.cryptomator.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
|
||||
public final class FileContents {
|
||||
|
||||
public static final FileContents UTF_8 = FileContents.withCharset(StandardCharsets.UTF_8);
|
||||
|
||||
private final Charset charset;
|
||||
|
||||
private FileContents(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the whole content from the given file.
|
||||
*
|
||||
* @param file File whose content should be read.
|
||||
* @return The file's content interpreted in this FileContents' charset.
|
||||
*/
|
||||
public String readContents(File file) {
|
||||
try (Reader reader = Channels.newReader(file.openReadable(), charset.newDecoder(), -1)) {
|
||||
return IOUtils.toString(reader);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the string into the file encoded with this FileContents' charset.
|
||||
* This methods replaces any previously existing content, i.e. the string will be the sole content.
|
||||
*
|
||||
* @param file File whose content should be written.
|
||||
* @param content The new content.
|
||||
*/
|
||||
public void writeContents(File file, String content) {
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
writable.truncate();
|
||||
writable.write(charset.encode(content));
|
||||
}
|
||||
}
|
||||
|
||||
public static FileContents withCharset(Charset charset) {
|
||||
return new FileContents(charset);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher;
|
||||
|
||||
public class ByteBufferMatcher {
|
||||
|
||||
public static Matcher<ByteBuffer> byteBufferFilledWith(int value) {
|
||||
if (((byte) value) != value) {
|
||||
throw new IllegalArgumentException("Invalid byte value");
|
||||
}
|
||||
return new TypeSafeDiagnosingMatcher<ByteBuffer>(ByteBuffer.class) {
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("a byte buffer filled with " + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(ByteBuffer item, Description mismatchDescription) {
|
||||
while (item.hasRemaining()) {
|
||||
byte currentValue = item.get();
|
||||
if (currentValue != value) {
|
||||
mismatchDescription.appendText("a byte buffer containing also " + currentValue);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.cryptomator.common.test.matcher.ContainsMatcher.contains;
|
||||
import static org.cryptomator.common.test.mockito.Answers.collectParameters;
|
||||
import static org.cryptomator.common.test.mockito.Answers.consecutiveAnswers;
|
||||
import static org.cryptomator.common.test.mockito.Answers.value;
|
||||
import static org.cryptomator.filesystem.File.EOF;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import de.bechte.junit.runners.context.HierarchicalContextRunner;
|
||||
|
||||
@RunWith(HierarchicalContextRunner.class)
|
||||
public class CopierTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||
|
||||
public class CopyFiles {
|
||||
|
||||
@Mock
|
||||
private File source;
|
||||
|
||||
@Mock
|
||||
private File destination;
|
||||
|
||||
@Mock
|
||||
private ReadableFile readable;
|
||||
|
||||
@Mock
|
||||
private WritableFile writable;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
when(source.openReadable()).thenReturn(readable);
|
||||
when(destination.openWritable()).thenReturn(writable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyFileOpensFilesInSortedOrderIfSourceIsSmallerDestination() {
|
||||
mockCompareToWithOrder(source, destination);
|
||||
when(readable.read(any())).thenReturn(EOF);
|
||||
|
||||
Copier.copy(source, destination);
|
||||
|
||||
InOrder inOrder = inOrder(source, destination);
|
||||
inOrder.verify(source).openReadable();
|
||||
inOrder.verify(destination).openWritable();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyFileOpensFilesInSortedOrderIfDestinationIsSmallerSource() {
|
||||
mockCompareToWithOrder(destination, source);
|
||||
when(readable.read(any())).thenReturn(EOF);
|
||||
|
||||
Copier.copy(source, destination);
|
||||
|
||||
InOrder inOrder = inOrder(source, destination);
|
||||
inOrder.verify(destination).openWritable();
|
||||
inOrder.verify(source).openReadable();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyFileReadsAndWritesReadableSourceAndWritableDestintationUntilEof() {
|
||||
int irrelevantValue = 0;
|
||||
Collection<byte[]> written = new ArrayList<>();
|
||||
mockCompareToWithOrder(source, destination);
|
||||
byte[] read1 = {1, 48, 32, 33, 22};
|
||||
byte[] read2 = {4, 3, 1, -2, -8};
|
||||
when(readable.read(any())).then(consecutiveAnswers(fillBufferWith(read1), fillBufferWith(read2), value(EOF)));
|
||||
when(writable.write(any())).then(collectParameters(value(irrelevantValue), (ByteBuffer buffer) -> {
|
||||
byte[] data = new byte[buffer.remaining()];
|
||||
buffer.get(data);
|
||||
written.add(data);
|
||||
}));
|
||||
|
||||
Copier.copy(source, destination);
|
||||
|
||||
InOrder inOrder = inOrder(readable, writable);
|
||||
inOrder.verify(writable).truncate();
|
||||
inOrder.verify(readable).read(any());
|
||||
inOrder.verify(writable).write(any());
|
||||
inOrder.verify(readable).read(any());
|
||||
inOrder.verify(writable).write(any());
|
||||
inOrder.verify(readable).read(any());
|
||||
inOrder.verify(readable).close();
|
||||
inOrder.verify(writable).close();
|
||||
|
||||
assertThat(written, contains(is(read1), is(read2)));
|
||||
}
|
||||
|
||||
private Answer<Integer> fillBufferWith(byte[] data) {
|
||||
return new Answer<Integer>() {
|
||||
@Override
|
||||
public Integer answer(InvocationOnMock invocation) throws Throwable {
|
||||
ByteBuffer buffer = invocation.getArgumentAt(0, ByteBuffer.class);
|
||||
for (byte value : data) {
|
||||
buffer.put(value);
|
||||
}
|
||||
return data.length;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private void mockCompareToWithOrder(File first, File last) {
|
||||
when(first.compareTo(last)).thenReturn(-1);
|
||||
when(last.compareTo(first)).thenReturn(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class CopyFolders {
|
||||
|
||||
@Mock
|
||||
private Folder source;
|
||||
|
||||
@Mock
|
||||
private Folder destination;
|
||||
|
||||
@Test
|
||||
public void testCopyFolderDeletesAndCreatesDestinationBeforeIteratingOverTheFilesAndFoldersInSource() {
|
||||
when(source.files()).thenReturn(Stream.empty());
|
||||
when(source.folders()).thenReturn(Stream.empty());
|
||||
|
||||
Copier.copy(source, destination);
|
||||
|
||||
InOrder inOrder = inOrder(source, destination);
|
||||
inOrder.verify(destination).delete();
|
||||
inOrder.verify(destination).create();
|
||||
inOrder.verify(source).files();
|
||||
inOrder.verify(source).folders();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void testCopyFolderInvokesCopyToOnAllFilesInSourceWithFileWithSameNameFromDestination() {
|
||||
String filename1 = "nameOfFile1";
|
||||
String filename2 = "nameOfFile2";
|
||||
File file1 = mock(File.class);
|
||||
File file2 = mock(File.class);
|
||||
File destinationFile1 = mock(File.class);
|
||||
File destinationFile2 = mock(File.class);
|
||||
when(source.files()).thenReturn((Stream) asList(file1, file2).stream());
|
||||
when(source.folders()).thenReturn(Stream.empty());
|
||||
when(destination.file(filename1)).thenReturn(destinationFile1);
|
||||
when(destination.file(filename2)).thenReturn(destinationFile2);
|
||||
when(file1.name()).thenReturn(filename1);
|
||||
when(file2.name()).thenReturn(filename2);
|
||||
|
||||
Copier.copy(source, destination);
|
||||
|
||||
verify(file1).copyTo(destinationFile1);
|
||||
verify(file2).copyTo(destinationFile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void testCopyFolderInvokesCopyToOnAllFoldersInSourceWithFolderWithSameNameFromDestination() {
|
||||
String folderName1 = "nameOfFolder1";
|
||||
String folderName2 = "nameOfFolder2";
|
||||
Folder folder1 = mock(Folder.class);
|
||||
Folder folder2 = mock(Folder.class);
|
||||
Folder destinationfolder1 = mock(Folder.class);
|
||||
Folder destinationfolder2 = mock(Folder.class);
|
||||
when(source.folders()).thenReturn((Stream) asList(folder1, folder2).stream());
|
||||
when(source.files()).thenReturn(Stream.empty());
|
||||
when(destination.folder(folderName1)).thenReturn(destinationfolder1);
|
||||
when(destination.folder(folderName2)).thenReturn(destinationfolder2);
|
||||
when(folder1.name()).thenReturn(folderName1);
|
||||
when(folder2.name()).thenReturn(folderName2);
|
||||
|
||||
Copier.copy(source, destination);
|
||||
|
||||
verify(folder1).copyTo(destinationfolder1);
|
||||
verify(folder2).copyTo(destinationfolder2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyFolderFailsWithIllegalArgumentExceptionIfSourceIsNestedInDestination() {
|
||||
when(source.isAncestorOf(destination)).thenReturn(false);
|
||||
when(destination.isAncestorOf(source)).thenReturn(true);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
|
||||
Copier.copy(source, destination);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyFolderFailsWithIllegalArgumentExceptionIfDestinationIsNestedInSource() {
|
||||
when(source.isAncestorOf(destination)).thenReturn(true);
|
||||
when(destination.isAncestorOf(source)).thenReturn(false);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
|
||||
Copier.copy(source, destination);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class PathResolverTest {
|
||||
|
||||
private final Folder root = Mockito.mock(Folder.class);
|
||||
private final Folder foo = Mockito.mock(Folder.class);
|
||||
private final Folder bar = Mockito.mock(Folder.class);
|
||||
private final File baz = Mockito.mock(File.class);
|
||||
|
||||
@Before
|
||||
public void configureMocks() throws IOException {
|
||||
Mockito.doReturn(Optional.empty()).when(root).parent();
|
||||
Mockito.doReturn(Optional.of(root)).when(foo).parent();
|
||||
Mockito.doReturn(Optional.of(foo)).when(bar).parent();
|
||||
|
||||
Mockito.doReturn(foo).when(root).folder("foo");
|
||||
Mockito.doReturn(bar).when(foo).folder("bar");
|
||||
Mockito.doReturn(baz).when(bar).file("baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveSameFolder() {
|
||||
Assert.assertEquals(foo, PathResolver.resolveFolder(foo, ""));
|
||||
Assert.assertEquals(foo, PathResolver.resolveFolder(foo, "/"));
|
||||
Assert.assertEquals(foo, PathResolver.resolveFolder(foo, "///"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveChildFolder() {
|
||||
Assert.assertEquals(bar, PathResolver.resolveFolder(root, "foo/bar"));
|
||||
Assert.assertEquals(bar, PathResolver.resolveFolder(root, "foo/./bar"));
|
||||
Assert.assertEquals(bar, PathResolver.resolveFolder(root, "./foo/././bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveParentFolder() {
|
||||
Assert.assertEquals(foo, PathResolver.resolveFolder(bar, ".."));
|
||||
Assert.assertEquals(root, PathResolver.resolveFolder(bar, "../.."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveSiblingFolder() {
|
||||
Assert.assertEquals(foo, PathResolver.resolveFolder(bar, "../../foo"));
|
||||
}
|
||||
|
||||
@Test(expected = UncheckedIOException.class)
|
||||
public void testResolveUnresolvableFolder() {
|
||||
PathResolver.resolveFolder(root, "..");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testResolveFileWithEmptyPath() {
|
||||
PathResolver.resolveFile(root, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveFile() {
|
||||
Assert.assertEquals(baz, PathResolver.resolveFile(foo, "../foo/bar/./baz"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class DelegatingFileSystemTest {
|
||||
|
||||
@Test
|
||||
public void testQuotaAvailableBytes() {
|
||||
FileSystem mockFs = Mockito.mock(FileSystem.class);
|
||||
Mockito.when(mockFs.fileSystem()).thenReturn(mockFs);
|
||||
Mockito.when(mockFs.quotaAvailableBytes()).thenReturn(Optional.of(42l));
|
||||
|
||||
DelegatingFileSystem delegatingFs = TestDelegatingFileSystem.withRoot(mockFs);
|
||||
Assert.assertEquals(mockFs.quotaAvailableBytes(), delegatingFs.quotaAvailableBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuotaUsedBytes() {
|
||||
FileSystem mockFs = Mockito.mock(FileSystem.class);
|
||||
Mockito.when(mockFs.fileSystem()).thenReturn(mockFs);
|
||||
Mockito.when(mockFs.quotaUsedBytes()).thenReturn(Optional.of(23l));
|
||||
|
||||
DelegatingFileSystem delegatingFs = TestDelegatingFileSystem.withRoot(mockFs);
|
||||
Assert.assertEquals(mockFs.quotaUsedBytes(), delegatingFs.quotaUsedBytes());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class DelegatingFileTest {
|
||||
|
||||
@Test
|
||||
public void testName() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
|
||||
Mockito.when(mockFile.name()).thenReturn("Test");
|
||||
Assert.assertEquals(mockFile.name(), delegatingFile.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParent() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
|
||||
TestDelegatingFileSystem delegatingParent = TestDelegatingFileSystem.withRoot(mockFolder);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(delegatingParent, mockFile);
|
||||
Assert.assertEquals(delegatingParent, delegatingFile.parent().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExists() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
|
||||
Mockito.when(mockFile.exists()).thenReturn(true);
|
||||
Assert.assertTrue(delegatingFile.exists());
|
||||
|
||||
Mockito.when(mockFile.exists()).thenReturn(false);
|
||||
Assert.assertFalse(delegatingFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLastModified() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
|
||||
Instant now = Instant.now();
|
||||
Mockito.when(mockFile.lastModified()).thenReturn(now);
|
||||
Assert.assertEquals(now, delegatingFile.lastModified());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetLastModified() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
|
||||
Instant now = Instant.now();
|
||||
delegatingFile.setLastModified(now);
|
||||
Mockito.verify(mockFile).setLastModified(now);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTime() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
|
||||
Instant now = Instant.now();
|
||||
Mockito.when(mockFile.creationTime()).thenReturn(Optional.of(now));
|
||||
Assert.assertEquals(now, delegatingFile.creationTime().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCreationTime() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
|
||||
Instant now = Instant.now();
|
||||
delegatingFile.setCreationTime(now);
|
||||
Mockito.verify(mockFile).setCreationTime(now);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenReadable() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
|
||||
|
||||
Mockito.when(mockFile.openReadable()).thenReturn(mockReadableFile);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
Assert.assertNotNull(delegatingFile.openReadable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenWritable() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
|
||||
|
||||
Mockito.when(mockFile.openWritable()).thenReturn(mockWritableFile);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
Assert.assertNotNull(delegatingFile.openWritable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveTo() {
|
||||
File mockFile1 = Mockito.mock(File.class);
|
||||
File mockFile2 = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
|
||||
DelegatingFile<?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
|
||||
|
||||
delegatingFile1.moveTo(delegatingFile2);
|
||||
Mockito.verify(mockFile1).moveTo(mockFile2);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testMoveToDestinationFromDifferentLayer() {
|
||||
File mockFile1 = Mockito.mock(File.class);
|
||||
File mockFile2 = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
|
||||
|
||||
delegatingFile1.moveTo(mockFile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyTo() {
|
||||
File mockFile1 = Mockito.mock(File.class);
|
||||
File mockFile2 = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
|
||||
DelegatingFile<?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
|
||||
|
||||
delegatingFile1.copyTo(delegatingFile2);
|
||||
Mockito.verify(mockFile1).copyTo(mockFile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyToDestinationFromDifferentLayer() {
|
||||
File mockFile1 = Mockito.mock(File.class);
|
||||
File mockFile2 = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
|
||||
|
||||
delegatingFile1.copyTo(mockFile2);
|
||||
Mockito.verify(mockFile1).copyTo(mockFile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
|
||||
delegatingFile.delete();
|
||||
Mockito.verify(mockFile).delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareTo() {
|
||||
File mockFile1 = Mockito.mock(File.class);
|
||||
File mockFile2 = Mockito.mock(File.class);
|
||||
|
||||
Mockito.when(mockFile1.compareTo(mockFile2)).thenReturn(-1);
|
||||
DelegatingFile<?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
|
||||
DelegatingFile<?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
|
||||
Assert.assertEquals(-1, delegatingFile1.compareTo(delegatingFile2));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class DelegatingFolderTest {
|
||||
|
||||
@Test
|
||||
public void testName() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
|
||||
Mockito.when(mockFolder.name()).thenReturn("Test");
|
||||
Assert.assertEquals(mockFolder.name(), delegatingFolder.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParent() {
|
||||
Folder mockFolder1 = Mockito.mock(Folder.class);
|
||||
Folder mockFolder2 = Mockito.mock(Folder.class);
|
||||
|
||||
TestDelegatingFileSystem delegatingParent = TestDelegatingFileSystem.withRoot(mockFolder1);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(delegatingParent, mockFolder2);
|
||||
Assert.assertEquals(delegatingParent, delegatingFolder.parent().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExists() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
|
||||
Mockito.when(mockFolder.exists()).thenReturn(true);
|
||||
Assert.assertTrue(delegatingFolder.exists());
|
||||
|
||||
Mockito.when(mockFolder.exists()).thenReturn(false);
|
||||
Assert.assertFalse(delegatingFolder.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLastModified() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
|
||||
Instant now = Instant.now();
|
||||
Mockito.when(mockFolder.lastModified()).thenReturn(now);
|
||||
Assert.assertEquals(now, delegatingFolder.lastModified());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetLastModified() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
|
||||
Instant now = Instant.now();
|
||||
delegatingFolder.setLastModified(now);
|
||||
Mockito.verify(mockFolder).setLastModified(now);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTime() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
|
||||
Instant now = Instant.now();
|
||||
Mockito.when(mockFolder.creationTime()).thenReturn(Optional.of(now));
|
||||
Assert.assertEquals(now, delegatingFolder.creationTime().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCreationTime() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
|
||||
Instant now = Instant.now();
|
||||
delegatingFolder.setCreationTime(now);
|
||||
Mockito.verify(mockFolder).setCreationTime(now);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildren() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
TestDelegatingFileSystem delegatingFolder = TestDelegatingFileSystem.withRoot(mockFolder);
|
||||
|
||||
Folder subFolder1 = Mockito.mock(Folder.class);
|
||||
TestDelegatingFolder delegatingSubFolder1 = new TestDelegatingFolder(delegatingFolder, subFolder1);
|
||||
File subFile1 = Mockito.mock(File.class);
|
||||
TestDelegatingFile delegatingSubFile1 = new TestDelegatingFile(delegatingFolder, subFile1);
|
||||
|
||||
/* folders */
|
||||
Mockito.when(mockFolder.folder("subFolder1")).thenReturn(subFolder1);
|
||||
Assert.assertEquals(delegatingSubFolder1, delegatingFolder.folder("subFolder1"));
|
||||
|
||||
Mockito.<Stream<? extends Folder>>when(mockFolder.folders()).thenAnswer((invocation) -> {
|
||||
return Arrays.stream(new Folder[] {subFolder1});
|
||||
});
|
||||
List<TestDelegatingFolder> subFolders = delegatingFolder.folders().collect(Collectors.toList());
|
||||
Assert.assertThat(subFolders, Matchers.containsInAnyOrder(delegatingSubFolder1));
|
||||
|
||||
/* files */
|
||||
Mockito.when(mockFolder.file("subFile1")).thenReturn(subFile1);
|
||||
Assert.assertEquals(delegatingSubFile1, delegatingFolder.file("subFile1"));
|
||||
|
||||
Mockito.<Stream<? extends File>>when(mockFolder.files()).thenAnswer((invocation) -> {
|
||||
return Arrays.stream(new File[] {subFile1});
|
||||
});
|
||||
List<TestDelegatingFile> subFiles = delegatingFolder.files().collect(Collectors.toList());
|
||||
Assert.assertThat(subFiles, Matchers.containsInAnyOrder(delegatingSubFile1));
|
||||
|
||||
/* files and folders */
|
||||
List<Node> children = delegatingFolder.children().collect(Collectors.toList());
|
||||
DelegatingNode<?>[] expectedChildren = new DelegatingNode[] {delegatingSubFolder1, delegatingSubFile1};
|
||||
Assert.assertThat(children, Matchers.containsInAnyOrder(expectedChildren));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveTo() {
|
||||
Folder mockFolder1 = Mockito.mock(Folder.class);
|
||||
Folder mockFolder2 = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
|
||||
DelegatingFolder<?, ?> delegatingFolder2 = new TestDelegatingFolder(null, mockFolder2);
|
||||
|
||||
delegatingFolder1.moveTo(delegatingFolder2);
|
||||
Mockito.verify(mockFolder1).moveTo(mockFolder2);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testMoveToDestinationFromDifferentLayer() {
|
||||
Folder mockFolder1 = Mockito.mock(Folder.class);
|
||||
Folder mockFolder2 = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
|
||||
|
||||
delegatingFolder1.moveTo(mockFolder2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyTo() {
|
||||
Folder mockFolder1 = Mockito.mock(Folder.class);
|
||||
Folder mockFolder2 = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
|
||||
DelegatingFolder<?, ?> delegatingFolder2 = new TestDelegatingFolder(null, mockFolder2);
|
||||
|
||||
delegatingFolder1.copyTo(delegatingFolder2);
|
||||
Mockito.verify(mockFolder1).copyTo(mockFolder2);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCopyToDestinationFromDifferentLayer() {
|
||||
Folder mockFolder1 = Mockito.mock(Folder.class);
|
||||
Folder mockFolder2 = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
|
||||
|
||||
delegatingFolder1.copyTo(mockFolder2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
|
||||
delegatingFolder.create();
|
||||
Mockito.verify(mockFolder).create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
|
||||
delegatingFolder.delete();
|
||||
Mockito.verify(mockFolder).delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubresourcesAreSameInstance() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
Folder mockSubFolder = Mockito.mock(Folder.class);
|
||||
File mockSubFile = Mockito.mock(File.class);
|
||||
Mockito.when(mockFolder.folder("mockSubFolder")).thenReturn(mockSubFolder);
|
||||
Mockito.when(mockFolder.file("mockSubFile")).thenReturn(mockSubFile);
|
||||
|
||||
DelegatingFolder<?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
|
||||
Assert.assertSame(delegatingFolder.folder("mockSubFolder"), delegatingFolder.folder("mockSubFolder"));
|
||||
Assert.assertSame(delegatingFolder.file("mockSubFile"), delegatingFolder.file("mockSubFile"));
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class DelegatingReadableFileTest {
|
||||
|
||||
@Test
|
||||
public void testIsOpen() {
|
||||
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
|
||||
|
||||
Mockito.when(mockReadableFile.isOpen()).thenReturn(true);
|
||||
Assert.assertTrue(delegatingReadableFile.isOpen());
|
||||
|
||||
Mockito.when(mockReadableFile.isOpen()).thenReturn(false);
|
||||
Assert.assertFalse(delegatingReadableFile.isOpen());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() {
|
||||
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(4);
|
||||
Mockito.when(mockReadableFile.read(buf)).thenReturn(4);
|
||||
Assert.assertEquals(4, delegatingReadableFile.read(buf));
|
||||
Mockito.verify(mockReadableFile).read(buf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() {
|
||||
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
|
||||
|
||||
Mockito.when(mockReadableFile.size()).thenReturn(42l);
|
||||
Assert.assertEquals(42l, delegatingReadableFile.size());
|
||||
Mockito.verify(mockReadableFile).size();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPosition() {
|
||||
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
|
||||
|
||||
delegatingReadableFile.position(42);
|
||||
Mockito.verify(mockReadableFile).position(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() {
|
||||
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
|
||||
DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
|
||||
|
||||
delegatingReadableFile.close();
|
||||
Mockito.verify(mockReadableFile).close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class DelegatingWritableFileTest {
|
||||
|
||||
@Test
|
||||
public void testIsOpen() {
|
||||
WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
|
||||
|
||||
Mockito.when(mockWritableFile.isOpen()).thenReturn(true);
|
||||
Assert.assertTrue(delegatingWritableFile.isOpen());
|
||||
|
||||
Mockito.when(mockWritableFile.isOpen()).thenReturn(false);
|
||||
Assert.assertFalse(delegatingWritableFile.isOpen());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTruncate() {
|
||||
WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
|
||||
|
||||
delegatingWritableFile.truncate();
|
||||
Mockito.verify(mockWritableFile).truncate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrite() {
|
||||
WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(4);
|
||||
Mockito.when(mockWritableFile.write(buf)).thenReturn(4);
|
||||
Assert.assertEquals(4, delegatingWritableFile.write(buf));
|
||||
Mockito.verify(mockWritableFile).write(buf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPosition() {
|
||||
WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
|
||||
|
||||
delegatingWritableFile.position(42);
|
||||
Mockito.verify(mockWritableFile).position(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() {
|
||||
WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
|
||||
DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile);
|
||||
|
||||
delegatingWritableFile.close();
|
||||
Mockito.verify(mockWritableFile).close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
|
||||
class TestDelegatingFile extends DelegatingFile<TestDelegatingFolder> {
|
||||
|
||||
public TestDelegatingFile(TestDelegatingFolder parent, File delegate) {
|
||||
super(parent, delegate);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
|
||||
class TestDelegatingFileSystem extends TestDelegatingFolder implements DelegatingFileSystem {
|
||||
|
||||
private TestDelegatingFileSystem(Folder delegate) {
|
||||
super(null, delegate);
|
||||
}
|
||||
|
||||
public static TestDelegatingFileSystem withRoot(Folder delegate) {
|
||||
return new TestDelegatingFileSystem(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Folder getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
|
||||
class TestDelegatingFolder extends DelegatingFolder<TestDelegatingFolder, TestDelegatingFile> {
|
||||
|
||||
public TestDelegatingFolder(TestDelegatingFolder parent, Folder delegate) {
|
||||
super(parent, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestDelegatingFile newFile(File delegate) {
|
||||
return new TestDelegatingFile(this, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestDelegatingFolder newFolder(Folder delegate) {
|
||||
return new TestDelegatingFolder(this, delegate);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ByteBuffersTest {
|
||||
|
||||
@Test
|
||||
public void testCopyOfEmptySource() {
|
||||
final ByteBuffer src = ByteBuffer.allocate(0);
|
||||
final ByteBuffer dst = ByteBuffer.allocate(5);
|
||||
dst.put(new byte[3]);
|
||||
Assert.assertEquals(0, src.position());
|
||||
Assert.assertEquals(0, src.remaining());
|
||||
Assert.assertEquals(3, dst.position());
|
||||
Assert.assertEquals(2, dst.remaining());
|
||||
ByteBuffers.copy(src, dst);
|
||||
Assert.assertEquals(0, src.position());
|
||||
Assert.assertEquals(0, src.remaining());
|
||||
Assert.assertEquals(3, dst.position());
|
||||
Assert.assertEquals(2, dst.remaining());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyToEmptyDestination() {
|
||||
final ByteBuffer src = ByteBuffer.wrap(new byte[4]);
|
||||
final ByteBuffer dst = ByteBuffer.allocate(0);
|
||||
src.put(new byte[2]);
|
||||
Assert.assertEquals(2, src.position());
|
||||
Assert.assertEquals(2, src.remaining());
|
||||
Assert.assertEquals(0, dst.position());
|
||||
Assert.assertEquals(0, dst.remaining());
|
||||
ByteBuffers.copy(src, dst);
|
||||
Assert.assertEquals(2, src.position());
|
||||
Assert.assertEquals(2, src.remaining());
|
||||
Assert.assertEquals(0, dst.position());
|
||||
Assert.assertEquals(0, dst.remaining());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyToBiggerDestination() {
|
||||
final ByteBuffer src = ByteBuffer.wrap(new byte[2]);
|
||||
final ByteBuffer dst = ByteBuffer.allocate(10);
|
||||
dst.put(new byte[3]);
|
||||
Assert.assertEquals(0, src.position());
|
||||
Assert.assertEquals(2, src.remaining());
|
||||
Assert.assertEquals(3, dst.position());
|
||||
Assert.assertEquals(7, dst.remaining());
|
||||
ByteBuffers.copy(src, dst);
|
||||
Assert.assertEquals(2, src.position());
|
||||
Assert.assertEquals(0, src.remaining());
|
||||
Assert.assertEquals(5, dst.position());
|
||||
Assert.assertEquals(5, dst.remaining());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyToSmallerDestination() {
|
||||
final ByteBuffer src = ByteBuffer.wrap(new byte[5]);
|
||||
final ByteBuffer dst = ByteBuffer.allocate(2);
|
||||
Assert.assertEquals(0, src.position());
|
||||
Assert.assertEquals(5, src.remaining());
|
||||
Assert.assertEquals(0, dst.position());
|
||||
Assert.assertEquals(2, dst.remaining());
|
||||
ByteBuffers.copy(src, dst);
|
||||
Assert.assertEquals(2, src.position());
|
||||
Assert.assertEquals(3, src.remaining());
|
||||
Assert.assertEquals(2, dst.position());
|
||||
Assert.assertEquals(0, dst.remaining());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package org.cryptomator.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@RunWith(Theories.class)
|
||||
public class FileContentsTest {
|
||||
|
||||
@DataPoints
|
||||
public static final Iterable<Charset> CHARSETS = Arrays.asList(StandardCharsets.UTF_8, StandardCharsets.US_ASCII, StandardCharsets.UTF_16);
|
||||
|
||||
@DataPoints
|
||||
public static final Iterable<String> TEST_CONTENTS = Arrays.asList("hello world", "hellö wörld", "");
|
||||
|
||||
@Theory
|
||||
public void testReadAll(Charset charset, String testString) {
|
||||
Assume.assumeTrue(charset.newEncoder().canEncode(testString));
|
||||
|
||||
ByteBuffer testContent = ByteBuffer.wrap(testString.getBytes(charset));
|
||||
File file = Mockito.mock(File.class);
|
||||
ReadableFile readable = Mockito.mock(ReadableFile.class);
|
||||
Mockito.when(file.openReadable()).thenReturn(readable);
|
||||
Mockito.when(readable.read(Mockito.any(ByteBuffer.class))).then(invocation -> {
|
||||
ByteBuffer target = invocation.getArgumentAt(0, ByteBuffer.class);
|
||||
if (testContent.hasRemaining()) {
|
||||
return ByteBuffers.copy(testContent, target);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
|
||||
String contentsRead = FileContents.withCharset(charset).readContents(file);
|
||||
Assert.assertEquals(testString, contentsRead);
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testWriteAll(Charset charset, String testString) {
|
||||
Assume.assumeTrue(charset.newEncoder().canEncode(testString));
|
||||
|
||||
ByteBuffer testContent = ByteBuffer.allocate(100);
|
||||
File file = Mockito.mock(File.class);
|
||||
WritableFile writable = Mockito.mock(WritableFile.class);
|
||||
Mockito.when(file.openWritable()).thenReturn(writable);
|
||||
Mockito.doAnswer(invocation -> {
|
||||
testContent.clear();
|
||||
return null;
|
||||
}).when(writable).truncate();
|
||||
Mockito.when(writable.write(Mockito.any(ByteBuffer.class))).then(invocation -> {
|
||||
ByteBuffer source = invocation.getArgumentAt(0, ByteBuffer.class);
|
||||
if (testContent.hasRemaining()) {
|
||||
return ByteBuffers.copy(source, testContent);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
|
||||
FileContents.withCharset(charset).writeContents(file, testString);
|
||||
Assert.assertArrayEquals(testString.getBytes(charset), Arrays.copyOf(testContent.array(), testContent.position()));
|
||||
}
|
||||
|
||||
@Test(expected = UncheckedIOException.class)
|
||||
public void testIOExceptionDuringRead() {
|
||||
File file = Mockito.mock(File.class);
|
||||
Mockito.when(file.openReadable()).thenAnswer(invocation -> {
|
||||
throw new IOException("failed");
|
||||
});
|
||||
|
||||
FileContents.UTF_8.readContents(file);
|
||||
}
|
||||
|
||||
@Test(expected = UncheckedIOException.class)
|
||||
public void testUncheckedIOExceptionDuringRead() {
|
||||
File file = Mockito.mock(File.class);
|
||||
Mockito.when(file.openReadable()).thenThrow(new UncheckedIOException(new IOException("failed")));
|
||||
|
||||
FileContents.UTF_8.readContents(file);
|
||||
}
|
||||
|
||||
@Test(expected = UncheckedIOException.class)
|
||||
public void testUncheckedIOExceptionDuringWrite() {
|
||||
File file = Mockito.mock(File.class);
|
||||
Mockito.when(file.openWritable()).thenThrow(new UncheckedIOException(new IOException("failed")));
|
||||
|
||||
FileContents.UTF_8.writeContents(file, "hello world");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Configuration status="WARN">
|
||||
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
|
||||
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
|
||||
</Console>
|
||||
<Console name="StdErr" target="SYSTEM_ERR">
|
||||
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
|
||||
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="DEBUG">
|
||||
<AppenderRef ref="Console" />
|
||||
<AppenderRef ref="StdErr" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
||||
@@ -1,50 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2015 Markus Kreusch
|
||||
Copyright (c) 2015 Sebastian Stenzel
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - initial API and implementation
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.3</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
<name>Cryptomator filesystem: API</name>
|
||||
<artifactId>filesystem-charsets</artifactId>
|
||||
<name>Cryptomator filesystem: Charset compatibility layer</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Guava -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- apache commons -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
<!-- Tests -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-inmemory</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.3</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-crypto-integration-tests</artifactId>
|
||||
<name>Cryptomator filesystem: Encryption layer tests</name>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
|
||||
|
||||
/**
|
||||
* Used as drop-in-replacement for {@link CryptoEngineModule} during unit tests.
|
||||
*/
|
||||
public class CryptoEngineTestModule extends CryptoEngineModule {
|
||||
|
||||
@Override
|
||||
public SecureRandom provideSecureRandom() {
|
||||
return new SecureRandom() {
|
||||
|
||||
@Override
|
||||
public void nextBytes(byte[] bytes) {
|
||||
Arrays.fill(bytes, (byte) 0x00);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
|
||||
import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
/**
|
||||
* To be used in integration tests, where a {@link CryptoFileSystem} is needed in conjunction with {@link CryptoEngineTestModule} (which mocks the CSPRNG) as follows:
|
||||
* <code>
|
||||
* DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build()
|
||||
* </code>
|
||||
*/
|
||||
@Singleton
|
||||
@Component(modules = CryptoEngineModule.class)
|
||||
public interface CryptoFileSystemTestComponent {
|
||||
|
||||
CryptoFileSystemFactory cryptoFileSystemFactory();
|
||||
|
||||
ShorteningFileSystemFactory shorteningFileSystemFactory();
|
||||
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinTask;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CryptoFileSystemIntegrationTest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemIntegrationTest.class);
|
||||
|
||||
private final CryptoFileSystemTestComponent cryptoFsComp = DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build();
|
||||
|
||||
private CryptoFileSystemDelegate cryptoDelegate;
|
||||
private FileSystem ciphertextFs;
|
||||
private FileSystem cleartextFs;
|
||||
|
||||
@Before
|
||||
public void setupFileSystems() {
|
||||
cryptoDelegate = Mockito.mock(CryptoFileSystemDelegate.class);
|
||||
ciphertextFs = new InMemoryFileSystem();
|
||||
FileSystem shorteningFs = cryptoFsComp.shorteningFileSystemFactory().get(ciphertextFs);
|
||||
cryptoFsComp.cryptoFileSystemFactory().initializeNew(shorteningFs, "TopSecret");
|
||||
cleartextFs = cryptoFsComp.cryptoFileSystemFactory().unlockExisting(shorteningFs, "TopSecret", cryptoDelegate);
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testVaultStructureInitializationAndBackupBehaviour() throws UncheckedIOException, IOException {
|
||||
final FileSystem physicalFs = new InMemoryFileSystem();
|
||||
final File masterkeyFile = physicalFs.file("masterkey.cryptomator");
|
||||
final File masterkeyBkupFile = physicalFs.file("masterkey.cryptomator.bkup");
|
||||
final Folder physicalDataRoot = physicalFs.folder("d");
|
||||
Assert.assertFalse(masterkeyFile.exists());
|
||||
Assert.assertFalse(masterkeyBkupFile.exists());
|
||||
Assert.assertFalse(physicalDataRoot.exists());
|
||||
|
||||
cryptoFsComp.cryptoFileSystemFactory().initializeNew(physicalFs, "asd");
|
||||
Assert.assertTrue(masterkeyFile.exists());
|
||||
Assert.assertFalse(masterkeyBkupFile.exists());
|
||||
Assert.assertFalse(physicalDataRoot.exists());
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final FileSystem cryptoFs = cryptoFsComp.cryptoFileSystemFactory().unlockExisting(physicalFs, "asd", cryptoDelegate);
|
||||
Assert.assertTrue(masterkeyBkupFile.exists());
|
||||
Assert.assertTrue(physicalDataRoot.exists());
|
||||
Assert.assertEquals(3, physicalFs.children().count()); // d + masterkey.cryptomator + masterkey.cryptomator.bkup
|
||||
Assert.assertEquals(1, physicalDataRoot.folders().count()); // ROOT directory
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptionOfLongFolderNames() {
|
||||
final String shortName = "normal folder name";
|
||||
final String longName = "this will be a long filename after encryption, because its encrypted name is longer than onehundredandeighty characters";
|
||||
|
||||
final Folder shortFolder = cleartextFs.folder(shortName);
|
||||
final Folder longFolder = cleartextFs.folder(longName);
|
||||
|
||||
shortFolder.create();
|
||||
longFolder.create();
|
||||
|
||||
// because of the long file, a metadata folder should exist on the physical layer:
|
||||
Assert.assertEquals(1, ciphertextFs.folder("m").folders().count());
|
||||
Assert.assertTrue(ciphertextFs.folder("m").exists());
|
||||
|
||||
// but the shortened filenames must not be visible on the cleartext layer:
|
||||
Assert.assertArrayEquals(new String[] {shortName, longName}, cleartextFs.folders().map(Node::name).sorted().toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptionAndDecryptionOfFiles() {
|
||||
// write test content to encrypted file
|
||||
try (WritableFile writable = cleartextFs.file("test1.txt").openWritable()) {
|
||||
writable.write(ByteBuffer.wrap("Hello ".getBytes()));
|
||||
writable.write(ByteBuffer.wrap("World".getBytes()));
|
||||
}
|
||||
|
||||
File physicalFile = ciphertextFs.folder("d").folders().findAny().get().folders().findAny().get().files().findAny().get();
|
||||
Assert.assertTrue(physicalFile.exists());
|
||||
|
||||
// read test content from decrypted file
|
||||
try (ReadableFile readable = cleartextFs.file("test1.txt").openReadable()) {
|
||||
ByteBuffer buf1 = ByteBuffer.allocate(5);
|
||||
readable.read(buf1);
|
||||
buf1.flip();
|
||||
Assert.assertEquals("Hello", new String(buf1.array(), 0, buf1.remaining()));
|
||||
ByteBuffer buf2 = ByteBuffer.allocate(10);
|
||||
readable.read(buf2);
|
||||
buf2.flip();
|
||||
Assert.assertArrayEquals(" World".getBytes(), Arrays.copyOfRange(buf2.array(), 0, buf2.remaining()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForcedDecryptionOfManipulatedFile() {
|
||||
// write test content to encrypted file
|
||||
try (WritableFile writable = cleartextFs.file("test1.txt").openWritable()) {
|
||||
writable.write(ByteBuffer.wrap("Hello World".getBytes()));
|
||||
}
|
||||
|
||||
File physicalFile = ciphertextFs.folder("d").folders().findAny().get().folders().findAny().get().files().findAny().get();
|
||||
Assert.assertTrue(physicalFile.exists());
|
||||
|
||||
// toggle last bit
|
||||
try (WritableFile writable = physicalFile.openWritable(); ReadableFile readable = physicalFile.openReadable()) {
|
||||
ByteBuffer buf = ByteBuffer.allocate((int) readable.size());
|
||||
readable.read(buf);
|
||||
buf.array()[buf.limit() - 1] ^= 0x01;
|
||||
buf.flip();
|
||||
writable.write(buf);
|
||||
}
|
||||
|
||||
// whitelist
|
||||
Mockito.when(cryptoDelegate.shouldSkipAuthentication("/test1.txt")).thenReturn(true);
|
||||
|
||||
// read test content from decrypted file
|
||||
try (ReadableFile readable = cleartextFs.file("test1.txt").openReadable()) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(11);
|
||||
readable.read(buf);
|
||||
buf.flip();
|
||||
Assert.assertArrayEquals("Hello World".getBytes(), buf.array());
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 20000) // assuming a minimum speed of 10mb/s during encryption and decryption 20s should be enough
|
||||
public void testEncryptionAndDecryptionSpeed() throws InterruptedException, IOException {
|
||||
File file = cleartextFs.file("benchmark.test");
|
||||
|
||||
final long encStart = System.nanoTime();
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
final ByteBuffer cleartext = ByteBuffer.allocate(100000); // 100k
|
||||
for (int i = 0; i < 1000; i++) { // 100M total
|
||||
cleartext.rewind();
|
||||
writable.write(cleartext);
|
||||
}
|
||||
}
|
||||
final long encEnd = System.nanoTime();
|
||||
LOG.debug("Encryption of 100M took {}ms", (encEnd - encStart) / 1000 / 1000);
|
||||
|
||||
final long decStart = System.nanoTime();
|
||||
try (ReadableFile readable = file.openReadable()) {
|
||||
final ByteBuffer cleartext = ByteBuffer.allocate(100000); // 100k
|
||||
for (int i = 0; i < 1000; i++) { // 100M total
|
||||
cleartext.clear();
|
||||
readable.read(cleartext);
|
||||
cleartext.flip();
|
||||
Assert.assertEquals(cleartext.get(), 0x00);
|
||||
}
|
||||
}
|
||||
final long decEnd = System.nanoTime();
|
||||
LOG.debug("Decryption of 100M took {}ms", (decEnd - decStart) / 1000 / 1000);
|
||||
|
||||
file.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomAccessOnLastBlock() {
|
||||
// prepare test data:
|
||||
ByteBuffer testData = ByteBuffer.allocate(16000 * Integer.BYTES); // < 64kb
|
||||
for (int i = 0; i < 16000; i++) {
|
||||
testData.putInt(i);
|
||||
}
|
||||
|
||||
// write test data to file:
|
||||
File cleartextFile = cleartextFs.file("test");
|
||||
try (WritableFile writable = cleartextFile.openWritable()) {
|
||||
testData.flip();
|
||||
writable.write(testData);
|
||||
}
|
||||
|
||||
// read last block:
|
||||
try (ReadableFile readable = cleartextFile.openReadable()) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
buf.clear();
|
||||
readable.position(15999 * Integer.BYTES);
|
||||
readable.read(buf);
|
||||
buf.flip();
|
||||
Assert.assertEquals(15999, buf.getInt());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSequentialRandomAccess() {
|
||||
// prepare test data:
|
||||
ByteBuffer testData = ByteBuffer.allocate(1_000_000 * Integer.BYTES); // = 4MB
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
testData.putInt(i);
|
||||
}
|
||||
|
||||
// write test data to file:
|
||||
File cleartextFile = cleartextFs.file("test");
|
||||
try (WritableFile writable = cleartextFile.openWritable()) {
|
||||
testData.flip();
|
||||
writable.write(testData);
|
||||
}
|
||||
|
||||
// shuffle our test positions:
|
||||
List<Integer> nums = new ArrayList<>();
|
||||
for (int i = 0; i < 1_000_000; i++) {
|
||||
nums.add(i);
|
||||
}
|
||||
Collections.shuffle(nums);
|
||||
|
||||
// read parts from positions:
|
||||
try (ReadableFile readable = cleartextFile.openReadable()) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
int num = nums.get(i);
|
||||
buf.clear();
|
||||
readable.position(num * Integer.BYTES);
|
||||
readable.read(buf);
|
||||
buf.flip();
|
||||
Assert.assertEquals(num, buf.getInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParallelRandomAccess() {
|
||||
// prepare test data:
|
||||
ByteBuffer testData = ByteBuffer.allocate(1_000_000 * Integer.BYTES); // = 4MB
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
testData.putInt(i);
|
||||
}
|
||||
|
||||
// write test data to file:
|
||||
final File cleartextFile = cleartextFs.file("test");
|
||||
try (WritableFile writable = cleartextFile.openWritable()) {
|
||||
testData.flip();
|
||||
writable.write(testData);
|
||||
}
|
||||
|
||||
// shuffle our test positions:
|
||||
List<Integer> nums = new ArrayList<>();
|
||||
for (int i = 0; i < 1_000_000; i++) {
|
||||
nums.add(i);
|
||||
}
|
||||
Collections.shuffle(nums);
|
||||
|
||||
// read parts from positions in parallel:
|
||||
final ForkJoinPool pool = new ForkJoinPool(10);
|
||||
final List<Future<Boolean>> tasks = new ArrayList<>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
final int num = nums.get(i);
|
||||
final ForkJoinTask<Boolean> task = ForkJoinTask.adapt(() -> {
|
||||
try (ReadableFile readable = cleartextFile.openReadable()) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
buf.clear();
|
||||
readable.position(num * Integer.BYTES);
|
||||
readable.read(buf);
|
||||
buf.flip();
|
||||
int numRead = buf.getInt();
|
||||
return num == numRead;
|
||||
}
|
||||
});
|
||||
pool.execute(task);
|
||||
tasks.add(task);
|
||||
}
|
||||
|
||||
// Wait for tasks to finish and check results
|
||||
Assert.assertTrue(tasks.stream().allMatch(task -> {
|
||||
try {
|
||||
return task.get();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
1
main/filesystem-crypto/.gitignore
vendored
1
main/filesystem-crypto/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/target/
|
||||
@@ -12,14 +12,14 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.3</version>
|
||||
<version>1.2.4</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-crypto</artifactId>
|
||||
<name>Cryptomator filesystem: Encryption layer</name>
|
||||
|
||||
<properties>
|
||||
<bouncycastle.version>1.51</bouncycastle.version>
|
||||
<sivmode.version>1.0.2</sivmode.version>
|
||||
<sivmode.version>1.2.0</sivmode.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
public class AuthenticationFailedException extends CryptoException {
|
||||
|
||||
public AuthenticationFailedException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public AuthenticationFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AuthenticationFailedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public AuthenticationFailedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
abstract class CryptoException extends RuntimeException {
|
||||
|
||||
public CryptoException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public CryptoException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CryptoException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public CryptoException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
/**
|
||||
* A Cryptor instance, once initialized with a set of keys, provides access to threadsafe cryptographic routines.
|
||||
*/
|
||||
public interface Cryptor extends Destroyable {
|
||||
|
||||
FilenameCryptor getFilenameCryptor();
|
||||
|
||||
FileContentCryptor getFileContentCryptor();
|
||||
|
||||
void randomizeMasterkey();
|
||||
|
||||
void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException;
|
||||
|
||||
byte[] writeKeysToMasterkeyFile(CharSequence passphrase);
|
||||
|
||||
@Override
|
||||
void destroy();
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Factory for stateful {@link FileContentEncryptor Encryptor}/{@link FileContentDecryptor Decryptor} instances, that are capable of processing data exactly once.
|
||||
*/
|
||||
public interface FileContentCryptor {
|
||||
|
||||
public static final ByteBuffer EOF = ByteBuffer.allocate(0);
|
||||
|
||||
/**
|
||||
* @return The fixed number of bytes of the file header. The header length is implementation-specific.
|
||||
*/
|
||||
int getHeaderSize();
|
||||
|
||||
/**
|
||||
* @return The ciphertext position that correlates to the cleartext position.
|
||||
*/
|
||||
long toCiphertextPos(long cleartextPos);
|
||||
|
||||
/**
|
||||
* @param header The full fixed-length header of an encrypted file. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}.
|
||||
* @param firstCiphertextByte Position of the first ciphertext byte passed to the decryptor. If the decryptor can not fast-forward to the requested byte, an exception is thrown.
|
||||
* If firstCiphertextByte is an invalid starting point, i.e. doesn't align with the decryptors internal block size, an IllegalArgumentException will be thrown.
|
||||
* @param authenticate Skip authentication by setting this flag to <code>false</code>. Should be <code>true</code> by default.
|
||||
* @return A possibly new FileContentDecryptor instance which is capable of decrypting ciphertexts associated with the given file header.
|
||||
*/
|
||||
FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) throws IllegalArgumentException, AuthenticationFailedException;
|
||||
|
||||
/**
|
||||
* @param header The full fixed-length header of an encrypted file or {@link Optional#empty()}. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}.
|
||||
* If the header is empty, a new one will be created by the returned encryptor.
|
||||
* @param firstCleartextByte Position of the first cleartext byte passed to the encryptor. If the encryptor can not fast-forward to the requested byte, an exception is thrown.
|
||||
* If firstCiphertextByte is an invalid starting point, i.e. doesn't align with the encryptors internal block size, an IllegalArgumentException will be thrown.
|
||||
* @return A possibly new FileContentEncryptor instance which is capable of encrypting cleartext associated with the given file header.
|
||||
*/
|
||||
FileContentEncryptor createFileContentEncryptor(Optional<ByteBuffer> header, long firstCleartextByte) throws IllegalArgumentException;
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
/**
|
||||
* Stateful, thus not thread-safe.
|
||||
*/
|
||||
public interface FileContentDecryptor extends Destroyable, Closeable {
|
||||
|
||||
/**
|
||||
* @return Number of bytes of the decrypted file.
|
||||
*/
|
||||
long contentLength();
|
||||
|
||||
/**
|
||||
* Appends further ciphertext to this decryptor. This method might block until space becomes available. If so, it is interruptable.
|
||||
*
|
||||
* @param cleartext Cleartext data or {@link FileContentCryptor#EOF} to indicate the end of a ciphertext.
|
||||
* @see #skipToPosition(long)
|
||||
*/
|
||||
void append(ByteBuffer ciphertext) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Cancels decryption due to an exception in the thread responsible for appending ciphertext.
|
||||
* The exception will be the root cause of an {@link UncheckedIOException} thrown by {@link #cleartext()} when retrieving the decrypted result.
|
||||
*
|
||||
* @param cause The exception making it impossible to {@link #append(ByteBuffer)} further ciphertext.
|
||||
*/
|
||||
void cancelWithException(Exception cause) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Returns the next decrypted cleartext in byte-by-byte FIFO order, meaning in the order ciphertext has been appended to this encryptor.
|
||||
* However the number and size of the cleartext byte buffers doesn't need to resemble the ciphertext buffers.
|
||||
*
|
||||
* This method might block if no cleartext is available yet.
|
||||
*
|
||||
* @return Decrypted cleartext or {@link FileContentCryptor#EOF}.
|
||||
* @throws AuthenticationFailedException On MAC mismatches
|
||||
* @throws UncheckedIOException In case of I/O exceptions, e.g. caused by previous {@link #cancelWithException(Exception)}.
|
||||
*/
|
||||
ByteBuffer cleartext() throws InterruptedException, AuthenticationFailedException, UncheckedIOException;
|
||||
|
||||
/**
|
||||
* Clears file-specific sensitive information.
|
||||
*/
|
||||
@Override
|
||||
void destroy();
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
/**
|
||||
* Stateful, thus not thread-safe.
|
||||
*/
|
||||
public interface FileContentEncryptor extends Destroyable, Closeable {
|
||||
|
||||
/**
|
||||
* Creates the encrypted file header. This header might depend on the already encrypted data,
|
||||
* thus the caller should make sure all data is processed before requesting the header.
|
||||
*
|
||||
* @return Encrypted file header.
|
||||
*/
|
||||
ByteBuffer getHeader();
|
||||
|
||||
/**
|
||||
* @return the size of headers created by this {@code FileContentCryptor}. The length of headers returned by {@link #getHeader()} equals this value.
|
||||
*/
|
||||
int getHeaderSize();
|
||||
|
||||
/**
|
||||
* Appends further cleartext to this encryptor. This method might block until space becomes available.
|
||||
*
|
||||
* @param cleartext Cleartext data or {@link FileContentCryptor#EOF} to indicate the end of a cleartext.
|
||||
*/
|
||||
void append(ByteBuffer cleartext) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Cancels encryption due to an exception in the thread responsible for appending cleartext.
|
||||
* The exception will be the root cause of an {@link UncheckedIOException} thrown by {@link #ciphertext()} when retrieving the encrypted result.
|
||||
*
|
||||
* @param cause The exception making it impossible to {@link #append(ByteBuffer)} further cleartext.
|
||||
*/
|
||||
void cancelWithException(Exception cause) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Returns the next ciphertext in byte-by-byte FIFO order, meaning in the order cleartext has been appended to this encryptor.
|
||||
* However the number and size of the ciphertext byte buffers doesn't need to resemble the cleartext buffers.
|
||||
*
|
||||
* This method might block if no ciphertext is available yet.
|
||||
*
|
||||
* @return Encrypted ciphertext of {@link FileContentCryptor#EOF}.
|
||||
* @throws UncheckedIOException In case of I/O exceptions, e.g. caused by previous {@link #cancelWithException(Exception)}.
|
||||
*/
|
||||
ByteBuffer ciphertext() throws InterruptedException, UncheckedIOException;
|
||||
|
||||
/**
|
||||
* Clears file-specific sensitive information.
|
||||
*/
|
||||
@Override
|
||||
void destroy();
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
/**
|
||||
* Provides deterministic encryption capabilities as filenames must not change on subsequent encryption attempts,
|
||||
* otherwise each change results in major directory structure changes which would be a terrible idea for cloud storage encryption.
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Deterministic_encryption">Wikipedia on deterministic encryption</a>
|
||||
*/
|
||||
public interface FilenameCryptor {
|
||||
|
||||
/**
|
||||
* @return constant length string, that is unlikely to collide with any other name.
|
||||
*/
|
||||
String hashDirectoryId(String cleartextDirectoryId);
|
||||
|
||||
/**
|
||||
* Tests without an actual decryption attempt, if a name is a well-formed ciphertext.
|
||||
*
|
||||
* @param ciphertextName Filename in question
|
||||
* @return <code>true</code> if the given name is likely to be a valid ciphertext
|
||||
*/
|
||||
boolean isEncryptedFilename(String ciphertextName);
|
||||
|
||||
/**
|
||||
* @param cleartextName original filename including cleartext file extension
|
||||
* @param associatedData optional associated data, that will not get encrypted but needs to be provided during decryption
|
||||
* @return encrypted filename without any file extension
|
||||
*/
|
||||
String encryptFilename(String cleartextName, byte[]... associatedData);
|
||||
|
||||
/**
|
||||
* @param ciphertextName Ciphertext only, with any additional strings like file extensions stripped first.
|
||||
* @param associatedData the same associated data used during encryption, otherwise and {@link AuthenticationFailedException} will be thrown
|
||||
* @return cleartext filename, probably including its cleartext file extension.
|
||||
*/
|
||||
String decryptFilename(String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
public class InvalidPassphraseException extends CryptoException {
|
||||
|
||||
public InvalidPassphraseException() {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
public class UnsupportedVaultFormatException extends CryptoException {
|
||||
|
||||
private final Integer detectedVersion;
|
||||
private final Integer supportedVersion;
|
||||
|
||||
public UnsupportedVaultFormatException(Integer detectedVersion, Integer supportedVersion) {
|
||||
super("Tried to open vault of version " + detectedVersion + ", but can only handle version " + supportedVersion);
|
||||
this.detectedVersion = detectedVersion;
|
||||
this.supportedVersion = supportedVersion;
|
||||
}
|
||||
|
||||
public Integer getDetectedVersion() {
|
||||
return detectedVersion;
|
||||
}
|
||||
|
||||
public Integer getSupportedVersion() {
|
||||
return supportedVersion;
|
||||
}
|
||||
|
||||
public boolean isVaultOlderThanSoftware() {
|
||||
return detectedVersion == null || detectedVersion < supportedVersion;
|
||||
}
|
||||
|
||||
public boolean isSoftwareOlderThanVault() {
|
||||
return detectedVersion > supportedVersion;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine.impl;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
final class AesKeyWrap {
|
||||
|
||||
private static final String RFC3394_CIPHER = "AESWrap";
|
||||
|
||||
private AesKeyWrap() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param kek Key encrypting key
|
||||
* @param key Key to be wrapped
|
||||
* @return Wrapped key
|
||||
*/
|
||||
public static byte[] wrap(SecretKey kek, SecretKey key) {
|
||||
final Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(RFC3394_CIPHER);
|
||||
cipher.init(Cipher.WRAP_MODE, kek);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException("Invalid key.", e);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
throw new IllegalStateException("Algorithm/Padding should exist.", e);
|
||||
}
|
||||
|
||||
try {
|
||||
return cipher.wrap(key);
|
||||
} catch (InvalidKeyException | IllegalBlockSizeException e) {
|
||||
throw new IllegalStateException("Unable to wrap key.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param kek Key encrypting key
|
||||
* @param wrappedKey Key to be unwrapped
|
||||
* @param keyAlgorithm Key designation, i.e. algorithm name to be associated with the unwrapped key.
|
||||
* @return Unwrapped key
|
||||
* @throws NoSuchAlgorithmException If keyAlgorithm is unknown
|
||||
* @throws InvalidKeyException If unwrapping failed (i.e. wrong kek)
|
||||
*/
|
||||
public static SecretKey unwrap(SecretKey kek, byte[] wrappedKey, String keyAlgorithm) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||
final Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(RFC3394_CIPHER);
|
||||
cipher.init(Cipher.UNWRAP_MODE, kek);
|
||||
} catch (InvalidKeyException ex) {
|
||||
throw new IllegalArgumentException("Invalid key.", ex);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
|
||||
throw new IllegalStateException("Algorithm/Padding should exist.", ex);
|
||||
}
|
||||
|
||||
return (SecretKey) cipher.unwrap(wrappedKey, keyAlgorithm, Cipher.SECRET_KEY);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.cryptomator.crypto.engine.impl;
|
||||
|
||||
public final class Constants {
|
||||
|
||||
private Constants() {
|
||||
}
|
||||
|
||||
static final Integer CURRENT_VAULT_VERSION = 3;
|
||||
|
||||
public static final int PAYLOAD_SIZE = 32 * 1024;
|
||||
public static final int NONCE_SIZE = 16;
|
||||
public static final int MAC_SIZE = 32;
|
||||
public static final int CHUNK_SIZE = NONCE_SIZE + PAYLOAD_SIZE + MAC_SIZE;
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine.impl;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class CryptoEngineModule {
|
||||
|
||||
@Provides
|
||||
public Cryptor provideCryptor(SecureRandom secureRandom) {
|
||||
return new CryptorImpl(secureRandom);
|
||||
}
|
||||
|
||||
@Provides
|
||||
public SecureRandom provideSecureRandom() {
|
||||
try {
|
||||
// https://tersesystems.com/2015/12/17/the-right-way-to-use-securerandom/
|
||||
final SecureRandom nativeRandom = SecureRandom.getInstanceStrong();
|
||||
byte[] seed = nativeRandom.generateSeed(55); // NIST SP800-90A suggests 440 bits for SHA1 seed
|
||||
SecureRandom sha1Random = SecureRandom.getInstance("SHA1PRNG");
|
||||
sha1Random.setSeed(seed);
|
||||
return sha1Random;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("No strong PRNGs available.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine.impl;
|
||||
|
||||
import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.FileContentCryptor;
|
||||
import org.cryptomator.crypto.engine.FilenameCryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
|
||||
class CryptorImpl implements Cryptor {
|
||||
|
||||
private static final int SCRYPT_SALT_LENGTH = 8;
|
||||
private static final int SCRYPT_COST_PARAM = 1 << 14;
|
||||
private static final int SCRYPT_BLOCK_SIZE = 8;
|
||||
private static final int KEYLENGTH_IN_BYTES = 32;
|
||||
private static final String ENCRYPTION_ALG = "AES";
|
||||
private static final String MAC_ALG = "HmacSHA256";
|
||||
|
||||
private SecretKey encryptionKey;
|
||||
private SecretKey macKey;
|
||||
private final AtomicReference<FilenameCryptor> filenameCryptor = new AtomicReference<>();
|
||||
private final AtomicReference<FileContentCryptor> fileContentCryptor = new AtomicReference<>();
|
||||
private final SecureRandom randomSource;
|
||||
|
||||
public CryptorImpl(SecureRandom randomSource) {
|
||||
this.randomSource = randomSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilenameCryptor getFilenameCryptor() {
|
||||
assertKeysExist();
|
||||
return LazyInitializer.initializeLazily(filenameCryptor, () -> {
|
||||
return new FilenameCryptorImpl(encryptionKey, macKey);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileContentCryptor getFileContentCryptor() {
|
||||
assertKeysExist();
|
||||
return LazyInitializer.initializeLazily(fileContentCryptor, () -> {
|
||||
return new FileContentCryptorImpl(encryptionKey, macKey, randomSource);
|
||||
});
|
||||
}
|
||||
|
||||
private void assertKeysExist() {
|
||||
if (encryptionKey == null || encryptionKey.isDestroyed()) {
|
||||
throw new IllegalStateException("No or invalid encryptionKey.");
|
||||
}
|
||||
if (macKey == null || macKey.isDestroyed()) {
|
||||
throw new IllegalStateException("No or invalid MAC key.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomizeMasterkey() {
|
||||
final byte[] randomBytes = new byte[KEYLENGTH_IN_BYTES];
|
||||
try {
|
||||
randomSource.nextBytes(randomBytes);
|
||||
encryptionKey = new SecretKeySpec(randomBytes, ENCRYPTION_ALG);
|
||||
randomSource.nextBytes(randomBytes);
|
||||
macKey = new SecretKeySpec(randomBytes, ENCRYPTION_ALG);
|
||||
} finally {
|
||||
Arrays.fill(randomBytes, (byte) 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) {
|
||||
final KeyFile keyFile;
|
||||
try {
|
||||
final ObjectMapper om = new ObjectMapper();
|
||||
keyFile = om.readValue(masterkeyFileContents, KeyFile.class);
|
||||
if (keyFile == null) {
|
||||
throw new InvalidFormatException("Could not read masterkey file", null, KeyFile.class);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Unable to parse masterkeyFileContents", e);
|
||||
}
|
||||
assert keyFile != null;
|
||||
|
||||
// check version
|
||||
if (!CURRENT_VAULT_VERSION.equals(keyFile.getVersion())) {
|
||||
throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION);
|
||||
}
|
||||
|
||||
final byte[] kekBytes = Scrypt.scrypt(passphrase, keyFile.getScryptSalt(), keyFile.getScryptCostParam(), keyFile.getScryptBlockSize(), KEYLENGTH_IN_BYTES);
|
||||
try {
|
||||
final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG);
|
||||
this.macKey = AesKeyWrap.unwrap(kek, keyFile.getMacMasterKey(), MAC_ALG);
|
||||
// future use (as soon as we need to prevent downgrade attacks):
|
||||
// final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
|
||||
// final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
|
||||
// if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) {
|
||||
// destroyQuietly(macKey);
|
||||
// throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION);
|
||||
// }
|
||||
this.encryptionKey = AesKeyWrap.unwrap(kek, keyFile.getEncryptionMasterKey(), ENCRYPTION_ALG);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidPassphraseException();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e);
|
||||
} finally {
|
||||
Arrays.fill(kekBytes, (byte) 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] writeKeysToMasterkeyFile(CharSequence passphrase) {
|
||||
final byte[] scryptSalt = new byte[SCRYPT_SALT_LENGTH];
|
||||
randomSource.nextBytes(scryptSalt);
|
||||
|
||||
final byte[] kekBytes = Scrypt.scrypt(passphrase, scryptSalt, SCRYPT_COST_PARAM, SCRYPT_BLOCK_SIZE, KEYLENGTH_IN_BYTES);
|
||||
final byte[] wrappedEncryptionKey;
|
||||
final byte[] wrappedMacKey;
|
||||
try {
|
||||
final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG);
|
||||
wrappedEncryptionKey = AesKeyWrap.wrap(kek, encryptionKey);
|
||||
wrappedMacKey = AesKeyWrap.wrap(kek, macKey);
|
||||
} finally {
|
||||
Arrays.fill(kekBytes, (byte) 0x00);
|
||||
}
|
||||
|
||||
final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
|
||||
final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
|
||||
|
||||
final KeyFile keyfile = new KeyFile();
|
||||
keyfile.setVersion(CURRENT_VAULT_VERSION);
|
||||
keyfile.setScryptSalt(scryptSalt);
|
||||
keyfile.setScryptCostParam(SCRYPT_COST_PARAM);
|
||||
keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE);
|
||||
keyfile.setEncryptionMasterKey(wrappedEncryptionKey);
|
||||
keyfile.setMacMasterKey(wrappedMacKey);
|
||||
keyfile.setVersionMac(versionMac);
|
||||
|
||||
try {
|
||||
final ObjectMapper om = new ObjectMapper();
|
||||
return om.writeValueAsBytes(keyfile);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Unable to create JSON from " + keyfile, e);
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================= destruction ======================= */
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
destroyQuietly(encryptionKey);
|
||||
destroyQuietly(macKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return (encryptionKey == null || encryptionKey.isDestroyed()) && (macKey == null || macKey.isDestroyed());
|
||||
}
|
||||
|
||||
private void destroyQuietly(Destroyable d) {
|
||||
if (d == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
d.destroy();
|
||||
} catch (DestroyFailedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine.impl;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* Executes long-running computations and returns the result strictly in order of the job submissions, no matter how long each job takes.
|
||||
*
|
||||
* The internally used thread pool is shut down automatically as soon as this FifiParallelDataProcessor is no longer referenced (see Finalization behaviour of {@link ThreadPoolExecutor}).
|
||||
*/
|
||||
class FifoParallelDataProcessor<T> {
|
||||
|
||||
private final BlockingQueue<Future<T>> processedData;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
/**
|
||||
* @param numThreads How many jobs can run in parallel.
|
||||
* @param workAhead Maximum number of jobs accepted in {@link #submit(Callable)} without blocking until results are polled from {@link #processedData()}.
|
||||
*/
|
||||
public FifoParallelDataProcessor(int workAhead, ExecutorService executorService) {
|
||||
this.processedData = new ArrayBlockingQueue<>(workAhead);
|
||||
this.executorService = executorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a job for execution. The results of multiple submissions can be polled in FIFO order using {@link #processedData()}.
|
||||
*
|
||||
* @param processingJob A task, that will compute a result.
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
void submit(Callable<T> processingJob) throws InterruptedException {
|
||||
Future<T> future = executorService.submit(processingJob);
|
||||
processedData.put(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits already pre-processed data, that can be polled in FIFO order from {@link #processedData()}.
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
void submitPreprocessed(T preprocessedData) throws InterruptedException {
|
||||
this.submit(() -> {
|
||||
return preprocessedData;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of previously {@link #submit(Callable) submitted} jobs in the same order as they have been submitted. Blocks if the job didn't finish yet.
|
||||
*
|
||||
* @return Next job result
|
||||
* @throws InterruptedException If the calling thread was interrupted while waiting for the next result.
|
||||
*/
|
||||
T processedData() throws InterruptedException, ExecutionException {
|
||||
return processedData.take().get();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user