Compare commits

..

138 Commits

Author SHA1 Message Date
Armin Schrenk
4e39eaa1f1 Merge branch 'release/1.16.0' 2025-04-29 11:27:56 +02:00
Armin Schrenk
eaca95e8dc finalize 1.16.0 2025-04-29 11:27:44 +02:00
Armin Schrenk
cf32e794c8 Merge branch 'develop' into release/1.16.0 2025-04-29 11:26:48 +02:00
Armin Schrenk
b5b221a5c7 remove manual wix installation from ci 2025-04-29 11:15:13 +02:00
Cryptobot
d7cb99a12d New Crowdin updates (#3819)
New translations strings.properties

Chinese Traditional; Dutch; French; Hebrew; Italian; Latvian; Persian; Portuguese; Portuguese, Brazilian; Russian; Slovak; Spanish; Swedish; 

[ci skip]
2025-04-29 10:55:33 +02:00
Armin Schrenk
b1893c63d4 Bump cryptofs from version 2.9.0-beta2 to 2.9.0 2025-04-28 13:00:21 +02:00
Armin Schrenk
b50cc703b6 Merge branch 'develop' into release/1.16.0 2025-04-15 13:46:30 +02:00
Armin Schrenk
9dc4a1dc5a [skip ci] update distribution build files to jfx 23.0.2 2025-04-15 13:45:14 +02:00
Armin Schrenk
f1a63a1679 update debian workflow to use JDK 23.0.2 2025-04-15 13:42:49 +02:00
Armin Schrenk
d53d4e3452 Merge branch 'develop' into release/1.16.0 2025-04-15 11:42:17 +02:00
Armin Schrenk
b7fa1ae6d8 Merge branch 'develop' into release/1.16.0
# Conflicts:
#	src/main/java/org/cryptomator/common/vaults/Vault.java
#	src/main/java/org/cryptomator/ui/eventview/EventListCellController.java
#	src/main/java/org/cryptomator/ui/eventview/EventListCellFactory.java
#	src/main/java/org/cryptomator/ui/eventview/EventViewController.java
#	src/main/java/org/cryptomator/ui/eventview/EventViewModule.java
#	src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
#	src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
#	src/main/resources/css/dark_theme.css
#	src/main/resources/css/light_theme.css
#	src/main/resources/fxml/eventview.fxml
#	src/main/resources/fxml/vault_detail_unlocked.fxml
#	src/main/resources/fxml/vault_list.fxml
#	src/main/resources/i18n/strings.properties
2025-04-15 11:41:39 +02:00
Cryptobot
3f192a587c New Crowdin updates (#3725)
New translations strings.properties

Afrikaans; Arabic; Bashkir; Belarusian; Bengali; Bosnian; Bulgarian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Croatian; Czech; Danish; Dutch; Filipino; Finnish; French; Galician; German; Greek; Hebrew; Hindi; Hungarian; Indonesian; Italian; Japanese; Korean; Latvian; Macedonian; Marathi; Norwegian; Norwegian Bokmal; Norwegian Nynorsk; Persian; Polish; Portuguese; Portuguese, Brazilian; Punjabi; Romanian; Russian; Serbian (Cyrillic); Serbian (Latin); Sinhala; Slovak; Slovenian; Spanish; Swahili, Tanzania; Swedish; Tamil; Telugu; Thai; Turkish; Ukrainian; Urdu (Pakistan); Uyghur; Vietnamese; 

[ci skip]
2025-04-15 11:31:16 +02:00
Armin Schrenk
3a1b0b17d6 fix translations 2025-04-15 11:23:46 +02:00
dependabot[bot]
5d2a243c55 Bump the java-test-dependencies group across 1 directory with 2 updates (#3776) 2025-04-15 08:59:50 +00:00
dependabot[bot]
008a2a3301 Bump the java-production-dependencies group with 4 updates (#3796) 2025-04-15 08:59:01 +00:00
Armin Schrenk
0680437004 Set a mininum width for the vault choice box in the event view 2025-04-14 17:38:21 +02:00
Armin Schrenk
db6cf5459e CI: Update and split up anti virus whitelisting flow (#3817) 2025-04-14 16:09:22 +02:00
Armin Schrenk
1d8466a9e3 Feature: Decrypt encrypted file name (#3788)
Closes #2713
2025-04-10 17:44:55 +02:00
Armin Schrenk
dcbcd692e7 [skip ci] Merge branch 'main' into develop 2025-04-09 14:02:14 +02:00
dependabot[bot]
34ccd3ff73 Bump the maven-build-plugins group across 1 directory with 4 updates (#3813) 2025-04-08 15:49:18 +00:00
Tobias Hagemann
989c349331 Merge tag '1.15.2' into develop
# -----BEGIN SSH SIGNATURE-----
# U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAKgu9N6lXOixsGNEmzxZhA
# Rc8ABi3MlOHGT30s3zCQEtvbE/j1QXRycgru5n/TZPKiEqZz5OGLM7FG3lNqHTjrQG1jfZ
# rH9JfmTRX9kLWyDmsiKJkB0L19NcCJQKwhkAdcJkbhQHP4bk1o3uMS+M89n6Y0nmUocGrk
# j7yX/79iQn7PQXdDRk7I+p0p1hCMC7fRlhxPwBX8iPOoty9L75ZoteRoWMGoq4T2fhDdUd
# 5ASRKfdVDjUnFrYvI+o8mhXdbzxNLjp5oN9VWQsb+YIkzApcI1+42BT5WABk+3cLlheqPR
# SocNgYdoZZPA1x6Zlzy2gggm33m/uU7gww2iATphdoyXoj1Nv9loMDOaCQUe6s7Fnnn+xH
# hC1fJoCRlrWJ2QjQmZj18KkrXp2/ZNfvLYUIoQVONF40iizb5GJewH00m1ZV+/oAqbJ/W1
# nfLmXJ/ShwACe3hyZFVOaB2582UyTDFCFJL2Bgbiw8y1ayS15+gs9TkFJlAtbpwofPqVB2
# 16iiE8ViFf//J5ommM1S2sHXaPvu1D038OScIGnz0grhnCjzBr3BduW7EYQ6RhVOqMaEUy
# mQiRKIAtfj+6eHgk3IxB3ynReUFQqE3wreE9buA0vrCO4aWEet+qJsg+395K+/l7E6LQOg
# 5rkgcVj9rtFeo4gFBT2AUUSLmHaqpfABAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAACFAAAAA
# xyc2Etc2hhMi0yNTYAAAIAMAXINugL3MrR1dUYQILEttmSOilND0hlz90eGBXuKRcijebq
# TbIwOrTJBPBlHM5aJC1lzBPcfB5Irz0DWc8eg0u2W/vHxtDXP5VL6me6u92Rz6WV73VWMD
# P8otZlMci0n3C5mM8DhrI9Nvg3qthRS2JW5vt3TFA7rL6IiAqsbA/nXWNGZuQwP+tJGCXg
# lWMD9NHaE1mmCcH+im2Vvnhz0y2Q0IEJCN8MDTrOA3ydnmF+YJ1b2QyQFBGrX3TNitZajX
# Uft3Jw5glpSlcaNEZOPR0cMCPBYy0skMUs4ykq4bVTy3I2qASdY3vb4l795OJPrK5FYyed
# ML5Zad7V8yrif0Cy/Ywy1C4TN6ftoEVH6HFEmJTohEY5BjT5qNK5tpmEs/o7z8W2oJa9Su
# G+KMAGe6AS/4ryQ+OQRJXzzFnAQRnR2Ou3iAA2ypsGWmP09Sr+1bb2qRSvy+lybaayfSLQ
# FouTQxecNq5TVfo1PWaGH9lLxd4i3J0PLk6uM8HfkIFimEN8h+4XT/4YXwO1czwun+IWaS
# nD92KIrdoHth+v05f8PpuqCz6odzojO33GfkMU/6Lkp2RgYeHD4eDb2/vyIJUtZHTPwPPJ
# tDybf2GbcDD4F43+fZdz7Q5A4a9DSDHeB6qZoqou6pUttbTneKQn/tq2yVa0TpyUjbGoCA
# QBvT4=
# -----END SSH SIGNATURE-----
# gpg verification failed.

# Conflicts:
#	pom.xml
2025-04-04 18:26:20 +02:00
Armin Schrenk
6a26d95c15 Feature: Event view (#3780) 2025-04-03 18:10:22 +02:00
Tobias Hagemann
439d3d7529 Update CONTRIBUTING.md 2025-03-29 18:00:22 +01:00
Armin Schrenk
7476e192a3 [skip ci] fix wrong JDK version in appimage build 2025-03-24 17:16:46 +01:00
Armin Schrenk
c6193bc259 update JDK for release builds to 23.0.2 (except ppa-builds) 2025-03-24 16:55:45 +01:00
Armin Schrenk
7446c69cd8 Update org.cryptomator:fuse-nio-adapter from 5.0.3 to 5.0.4
fixes #3797
2025-03-24 14:38:12 +01:00
Armin Schrenk
43c352e0d2 prepare 1.16.0 2025-03-18 11:39:46 +01:00
Armin Schrenk
61ec3bc465 Merge branch 'feature/event-view' into release/1.16.0
# Conflicts:
#	pom.xml
2025-03-18 11:39:22 +01:00
Armin Schrenk
36dd98127d update to cryptofs 2.9.0-beta2 2025-03-18 11:38:52 +01:00
Armin Schrenk
30862146b0 Merge branch 'feature/decrypt-name' into release/1.16.0
# Conflicts:
#	pom.xml
2025-03-18 11:21:54 +01:00
Armin Schrenk
c19734da6f decrypt name is always possible 2025-03-18 11:17:49 +01:00
Armin Schrenk
ba8fb273c7 cleanup 2025-03-18 11:14:38 +01:00
Armin Schrenk
5204a8e356 remove unused icons 2025-03-18 11:10:38 +01:00
Armin Schrenk
43b341860b extend styling to dark theme 2025-03-18 11:09:43 +01:00
dependabot[bot]
ad02e43daf Merge pull request #3787 from cryptomator/dependabot/maven/java-production-dependencies-af5d03dc73 2025-03-18 10:07:14 +00:00
Armin Schrenk
4942bcb52b exclude fasterxml:jackson-core dependency from auth0:java-jwt 2025-03-17 18:15:43 +01:00
dependabot[bot]
6b2e718e04 Bump the java-production-dependencies group across 1 directory with 13 updates
Bumps the java-production-dependencies group with 13 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [org.cryptomator:cryptolib](https://github.com/cryptomator/cryptolib) | `2.2.0` | `2.2.1` |
| [org.cryptomator:fuse-nio-adapter](https://github.com/cryptomator/fuse-nio-adapter) | `5.0.2` | `5.0.3` |
| [org.cryptomator:webdav-nio-adapter](https://github.com/cryptomator/webdav-nio-adapter) | `2.0.7` | `2.0.8` |
| [org.cryptomator:integrations-api](https://github.com/cryptomator/integrations-api) | `1.5.0` | `1.5.1` |
| org.slf4j:slf4j-api | `2.0.16` | `2.0.17` |
| [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) | `1.5.16` | `1.5.17` |
| [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) | `1.5.16` | `1.5.17` |
| [io.github.coffeelibs:tiny-oauth2-client](https://github.com/coffeelibs/tiny-oauth2-client) | `0.8.0` | `0.8.1` |
| [com.auth0:java-jwt](https://github.com/auth0/java-jwt) | `4.4.0` | `4.5.0` |
| [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) | `2.18.2` | `2.18.3` |
| com.fasterxml.jackson.datatype:jackson-datatype-jsr310 | `2.18.2` | `2.18.3` |
| [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) | `26.0.1` | `26.0.2` |
| [org.cryptomator:integrations-linux](https://github.com/cryptomator/integrations-linux) | `1.5.2` | `1.5.3` |



Updates `org.cryptomator:cryptolib` from 2.2.0 to 2.2.1
- [Release notes](https://github.com/cryptomator/cryptolib/releases)
- [Commits](https://github.com/cryptomator/cryptolib/compare/2.2.0...2.2.1)

Updates `org.cryptomator:fuse-nio-adapter` from 5.0.2 to 5.0.3
- [Release notes](https://github.com/cryptomator/fuse-nio-adapter/releases)
- [Commits](https://github.com/cryptomator/fuse-nio-adapter/compare/5.0.2...5.0.3)

Updates `org.cryptomator:webdav-nio-adapter` from 2.0.7 to 2.0.8
- [Release notes](https://github.com/cryptomator/webdav-nio-adapter/releases)
- [Commits](https://github.com/cryptomator/webdav-nio-adapter/compare/2.0.7...2.0.8)

Updates `org.cryptomator:integrations-api` from 1.5.0 to 1.5.1
- [Release notes](https://github.com/cryptomator/integrations-api/releases)
- [Commits](https://github.com/cryptomator/integrations-api/compare/1.5.0...1.5.1)

Updates `org.slf4j:slf4j-api` from 2.0.16 to 2.0.17

Updates `ch.qos.logback:logback-core` from 1.5.16 to 1.5.17
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.16...v_1.5.17)

Updates `ch.qos.logback:logback-classic` from 1.5.16 to 1.5.17
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.16...v_1.5.17)

Updates `ch.qos.logback:logback-classic` from 1.5.16 to 1.5.17
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.16...v_1.5.17)

Updates `io.github.coffeelibs:tiny-oauth2-client` from 0.8.0 to 0.8.1
- [Release notes](https://github.com/coffeelibs/tiny-oauth2-client/releases)
- [Commits](https://github.com/coffeelibs/tiny-oauth2-client/compare/0.8.0...0.8.1)

Updates `com.auth0:java-jwt` from 4.4.0 to 4.5.0
- [Release notes](https://github.com/auth0/java-jwt/releases)
- [Changelog](https://github.com/auth0/java-jwt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/auth0/java-jwt/compare/4.4.0...4.5.0)

Updates `com.fasterxml.jackson.core:jackson-databind` from 2.18.2 to 2.18.3
- [Commits](https://github.com/FasterXML/jackson/commits)

Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.18.2 to 2.18.3

Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.18.2 to 2.18.3

Updates `org.jetbrains:annotations` from 26.0.1 to 26.0.2
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/26.0.1...26.0.2)

Updates `org.cryptomator:integrations-linux` from 1.5.2 to 1.5.3
- [Release notes](https://github.com/cryptomator/integrations-linux/releases)
- [Commits](https://github.com/cryptomator/integrations-linux/compare/1.5.2...1.5.3)

---
updated-dependencies:
- dependency-name: org.cryptomator:cryptolib
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: org.cryptomator:fuse-nio-adapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: org.cryptomator:webdav-nio-adapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: org.cryptomator:integrations-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: io.github.coffeelibs:tiny-oauth2-client
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: com.auth0:java-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
- dependency-name: org.cryptomator:integrations-linux
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: java-production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 16:47:33 +00:00
Ralph Plawetzki
a1323b5e81 Merge pull request #3785 from purejava/fix/improve-symbolic-icons 2025-03-17 17:46:23 +01:00
dependabot[bot]
ab4f487de1 Bump the javafx group with 5 updates (#3703) 2025-03-17 16:40:48 +00:00
Armin Schrenk
d506e7472d Bump cryptofs from 2.8.0 to 2.9.0-beta2
Closes #3707
2025-03-17 17:32:48 +01:00
Armin Schrenk
2d33d02b46 Feature: TouchID [macOS]
Closes #2180
2025-03-17 17:29:39 +01:00
Armin Schrenk
06ddec4b70 more UI refinment:
* only copy decrypted names to clipboard
* indicate in UI
2025-03-17 17:14:52 +01:00
Armin Schrenk
24e31553fd refine UI:
* align locate/decrypt buttons horizontally
* change to text icons
* dedup code
2025-03-17 12:02:03 +01:00
Armin Schrenk
eaa6b31de8 bump cryptofs to 2.9.0-beta2 2025-03-14 16:15:17 +01:00
Ralph Plawetzki
060ae588a2 Improve symbolic icons (#3782)
* Remove the style attributes from the path tags
Fixes #3775
* Improve SVG code using a <g> tag and tighten current-color-scheme definition
* Add missing attribute from original SVG
2025-03-14 13:22:14 +01:00
Armin Schrenk
262f84e8db new design 2025-03-13 16:47:23 +01:00
Armin Schrenk
6d55331ef2 add missing translation key 2025-03-13 10:36:40 +01:00
Armin Schrenk
42269a6057 just a draft 2025-03-12 18:01:06 +01:00
Armin Schrenk
181e45a596 renamed event list item localization keys 2025-03-12 15:27:24 +01:00
Armin Schrenk
3c8e506805 add tooltip for clear all button 2025-03-12 15:21:32 +01:00
Armin Schrenk
401b0afe9f remove unnecessary code 2025-03-12 12:37:06 +01:00
Armin Schrenk
42ec41b991 clean up code 2025-03-12 12:19:27 +01:00
Armin Schrenk
7ec3c5d04f first delete, then write 2025-03-12 11:41:12 +01:00
Armin Schrenk
f0a7379575 hide count if vault is locked 2025-03-12 10:50:36 +01:00
Armin Schrenk
0d12b11c48 cleanup 2025-03-12 10:01:09 +01:00
Armin Schrenk
14a025f769 size restrict the eventMap 2025-03-12 10:00:56 +01:00
Armin Schrenk
598fc9bbc8 complete themes 2025-03-12 10:00:34 +01:00
Armin Schrenk
fbab2df00f add notification marker for new events 2025-03-12 09:38:56 +01:00
Armin Schrenk
fb56b61a75 add exceptionally clause to keychain migration task 2025-03-11 17:14:42 +01:00
Armin Schrenk
c5c3d55658 Merge branch 'develop' into feature/touch-id 2025-03-11 17:08:13 +01:00
Armin Schrenk
48c2d49c86 revert default version string 2025-03-11 17:07:46 +01:00
Armin Schrenk
00d68393f6 use beta version of cryptoFs 2025-03-11 17:07:17 +01:00
Armin Schrenk
7a50ba0b43 Merge branch 'develop' into feature/event-view 2025-03-11 17:03:58 +01:00
Armin Schrenk
b2bee99286 Refactor: Out-of-bounds-check (#3778)
fixes #3723, fixes #3722
2025-03-11 16:03:49 +01:00
Armin Schrenk
88d384afdf reduce tooltip show duration 2025-03-07 11:50:53 +01:00
Armin Schrenk
7af82b831f add event count to viewer 2025-03-07 11:50:40 +01:00
Armin Schrenk
a86f42fa44 replace eventList with a eventMap in model to group events 2025-03-07 11:21:23 +01:00
Sebastian Stenzel
70b0413777 replace labels with issue type
see https://github.blog/changelog/2025-01-13-evolving-github-issues-public-preview/#organize-your-work-with-issue-types
[ci skip]
2025-03-07 09:32:39 +01:00
Sebastian Stenzel
577a77af38 moved summary to the top for better link previews 2025-03-07 09:31:35 +01:00
Armin Schrenk
3ba7e9ba00 cleanup 2025-03-06 12:55:12 +01:00
Armin Schrenk
b2250e8ce0 add Tooltip 2025-03-06 12:54:58 +01:00
Armin Schrenk
f2a480c7a0 reworked event cell texts 2025-03-06 12:40:02 +01:00
Armin Schrenk
bda38096e1 fix minor inconsitencies 2025-03-04 12:34:45 +01:00
Armin Schrenk
7e66a61294 add choicebox to filter for single vaults 2025-03-04 12:34:17 +01:00
clkue
3aa627a467 fix unicode escaping for rtf file (#3609)
Signed-off-by: C1emency <>
Signed-off-by: clkue <clemens_kuennemann@protonmail.com>
2025-03-03 14:55:09 +01:00
Armin Schrenk
ae1b5fc925 Switch to ubuntu-22.04 for building the deb package 2025-03-03 12:05:25 +01:00
mindmonk
b1076f9c86 Merge pull request #3764 from cryptomator/feature/dialogBuilder-one-Button
Feature: Simple Dialog with one Button
2025-02-28 14:53:22 +01:00
Jan-Peter Klein
ede1c72595 cancelButtonVisible BooleanProperty to boolean 2025-02-28 13:29:04 +01:00
Armin Schrenk
d2fcd5b64f adjust stylings 2025-02-28 10:36:13 +01:00
Armin Schrenk
6d9704ffa2 adjust event description when locked 2025-02-28 10:36:03 +01:00
Armin Schrenk
40df3d015a remove optional wrapper of revealService 2025-02-28 10:01:52 +01:00
Armin Schrenk
4e22706d92 add alternative if revealService is not present 2025-02-28 09:51:58 +01:00
Armin Schrenk
341710f724 add time field to the eventcell 2025-02-27 18:56:41 +01:00
Armin Schrenk
8386183f7a * Hide button when not hovering or vault is locked
* localize
2025-02-27 17:25:31 +01:00
Armin Schrenk
b76a7d6895 implement event views for all filesystemevents 2025-02-26 18:36:52 +01:00
Jan-Peter Klein
4a397a8151 code cleanup 2025-02-26 12:36:03 +01:00
Armin Schrenk
2239a3e6a3 reduce API to only consider vault events 2025-02-26 11:56:17 +01:00
tturturiello
5318368879 Add underline effect to clickable notification labels (#3757) 2025-02-26 11:53:46 +01:00
Jan-Peter Klein
41a279da9d renamed property 2025-02-25 19:04:25 +01:00
Jan-Peter Klein
478c69f82c simple dialog without cancel button 2025-02-25 18:23:17 +01:00
Armin Schrenk
eb0e630a44 move migration to KeychainManager 2025-02-25 17:33:14 +01:00
Armin Schrenk
0bcbf9a13a add migration code and synchronize keychain edits 2025-02-25 16:32:38 +01:00
Armin Schrenk
e3073a3613 adjust wording for autoLock feature [skip ci] 2025-02-25 15:38:50 +01:00
Armin Schrenk
3333dc7f22 Fix not appearing removeCert dialog 2025-02-25 11:52:00 +01:00
Julian Raufelder
12ec6fbec8 Change /access-token deviceId header name to Hub-Device-ID 2025-02-19 14:25:34 +01:00
Armin Schrenk
43fc976ad7 reduce #3311 to touchID 2025-02-19 11:49:59 +01:00
Armin Schrenk
034278c0a0 changes:
* implement reveal conflictResolved
* removed showVault context menu entry
* cleanup
2025-02-19 11:27:39 +01:00
Julian Raufelder
92b3a9e1bd Merge pull request #3752 from cryptomator/feature/add-device-id-during-hub-unlock
Send DeviceId to Hub during vault key retrieval
2025-02-19 09:51:39 +01:00
Julian Raufelder
fc7169f2a0 Send DeviceId to Hub during vault key retrieval
See https://github.com/cryptomator/hub/pull/320
2025-02-19 09:05:13 +01:00
Armin Schrenk
c39710ede0 show event specific context menu 2025-02-18 18:32:06 +01:00
Armin Schrenk
cc0b6aed15 Update org.cryptomator.Cryptomator.metainfo.xml
* adjust screenshot captions
* reorder screenshots
* add keywords
2025-02-18 16:52:31 +01:00
Armin Schrenk
f8a83c9cc8 scope eventListcell factory 2025-02-18 15:32:34 +01:00
Armin Schrenk
43ce64f01e adjust styling 2025-02-18 11:44:48 +01:00
mindmonk
d585a03f76 Merge pull request #3699 from cryptomator/feature/vault-scheme-tag
Add lastKnownKeyLoader entry to VaultSettings
2025-02-17 17:35:47 +01:00
Armin Schrenk
f953be6237 Merge branch 'develop' into feature/event-view 2025-02-17 17:12:35 +01:00
Armin Schrenk
e81bbe197b if mainWindow is closed without tray menu, terminate app 2025-02-17 16:46:25 +01:00
Armin Schrenk
738fa4da12 cleanup 2025-02-17 16:08:09 +01:00
dependabot[bot]
9a08dcc46d Bump org.owasp:dependency-check-maven (#3748) 2025-02-17 10:04:30 +00:00
Armin Schrenk
74ef8d915d make eventView independent, but singleton window 2025-02-14 17:26:25 +01:00
Armin Schrenk
025a7a5582 add contextMenu to eventListCell 2025-02-14 17:11:43 +01:00
Armin Schrenk
3b2ddcf98d style the eventlist cell more 2025-02-14 16:47:15 +01:00
Armin Schrenk
c10fc0ee5b Update README.md
For build badge use develop branch.
2025-02-14 11:56:55 +01:00
Armin Schrenk
aaa37e2c7a Feature: Add UTM parameters to download uri (#3735)
Co-authored-by: Tobias Hagemann <tobias.hagemann@skymatic.de>
2025-02-14 11:55:22 +01:00
Armin Schrenk
7af7c920e3 use same listCell factory pattern as in vault list 2025-02-14 11:54:25 +01:00
Armin Schrenk
a4f79fb98a unify naming 2025-02-14 11:41:35 +01:00
Armin Schrenk
f429f2d3e7 rename method 2025-02-14 10:51:48 +01:00
Armin Schrenk
4f6e091c13 first proper design draft 2025-02-13 16:58:05 +01:00
Jan-Peter Klein
2df779f7ab introduce isMasterkeyFileVault() 2025-02-13 15:44:10 +01:00
Jan-Peter Klein
5b9d9150c5 remove extra try catch block 2025-02-13 15:39:06 +01:00
Jan-Peter Klein
f0aaec2058 comparison reversed 2025-02-13 15:15:29 +01:00
Jan-Peter Klein
4adc4a9175 removed unused StringBinding 2025-02-13 15:03:14 +01:00
Armin Schrenk
5a97060fac make the list synchronized 2025-02-13 11:06:48 +01:00
Armin Schrenk
db96074119 replace AppEvent by UpdateEvent and emit it on available update 2025-02-12 11:12:40 +01:00
Jan-Peter Klein
7ecc10bc79 removed setVaultScheme method; lastKnownKeyLoader is now set within the create method 2025-02-12 10:35:21 +01:00
Armin Schrenk
44fe4a6f8a displayable event view window 2025-02-11 17:37:04 +01:00
Jan-Peter Klein
76a4ef50cb improve exception handling 2025-02-11 16:21:53 +01:00
Armin Schrenk
ca2f80024a impl draft 2025-02-11 15:38:18 +01:00
Armin Schrenk
dcd7077b08 Feature: readd out of bounds check (#3729)
references #3091
2025-02-11 15:24:36 +01:00
Armin Schrenk
31482d7d18 [skip ci] update ide files 2025-02-11 12:37:45 +01:00
Armin Schrenk
5ef666154e use correct height in css [skip ci] 2025-02-11 10:16:34 +01:00
Armin Schrenk
a810eff797 make menu buttons in vault list appear clickable 2025-02-10 18:29:57 +01:00
Jan-Peter Klein
664158eb84 add isHubVault method to KeyLoadingStrategy 2025-02-10 18:09:03 +01:00
Armin Schrenk
6b43881909 [skip ci] update screenshot url in metainfo file 2025-02-10 17:08:03 +01:00
Jan-Peter Klein
126004b1f8 improve error handling 2025-02-10 12:58:54 +01:00
Jan-Peter Klein
ba34cfa9d5 renamed to lastKnownKeyLoader 2025-02-10 12:07:25 +01:00
Armin Schrenk
03b4ad85ef [skip ci] Merge branch 'main' into develop 2025-02-05 16:22:09 +01:00
Jan-Peter Klein
b25b5bd5a3 add keyLoader to observables 2025-01-22 16:59:52 +01:00
Jan-Peter Klein
6365c22297 add keyLoader entry to VaultSettings 2025-01-22 16:46:18 +01:00
142 changed files with 4325 additions and 597 deletions

View File

@@ -16,6 +16,10 @@
- Suggest your change by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new/choose) and start writing code.
## Do you intend to add a new translation or change an existing one?
Translations are not managed directly in this repository. Instead, we use [Crowdin](https://translate.cryptomator.org/), which automatically synchronizes translations with this repository. If you want to help us with translations, please visit our translation project on Crowdin.
## Code of Conduct
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).

View File

@@ -1,7 +1,14 @@
name: Bug Report
description: Create a report to help us improve
labels: ["type:bug"]
type: "Bug"
body:
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your problem.
validations:
required: true
- type: checkboxes
id: terms
attributes:
@@ -11,13 +18,6 @@ body:
required: true
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
required: true
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your problem.
validations:
required: true
- type: textarea
id: software-versions
attributes:
@@ -97,4 +97,4 @@ body:
id: further-info
attributes:
label: Anything else?
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?

View File

@@ -1,7 +1,14 @@
name: Feature Request
description: Suggest an idea for this project
labels: ["type:feature-request"]
type: "Feature"
body:
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your feature request.
validations:
required: true
- type: checkboxes
id: terms
attributes:
@@ -11,13 +18,6 @@ body:
required: true
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
required: true
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your feature request.
validations:
required: true
- type: textarea
id: motivation
attributes:

View File

@@ -11,7 +11,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '23.0.1+11'
JAVA_VERSION: '23.0.2'
jobs:
get-version:
@@ -29,12 +29,12 @@ jobs:
include:
- os: ubuntu-latest
appimage-suffix: x86_64
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip'
openjfx-sha: '2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6'
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-x64_bin-jmods.zip'
openjfx-sha: '063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
- os: ubuntu-24.04-arm
appimage-suffix: aarch64
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-aarch64_bin-jmods.zip'
openjfx-sha: '09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b'
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-aarch64_bin-jmods.zip'
openjfx-sha: '9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
steps:
- uses: actions/checkout@v4
- name: Setup Java

View File

@@ -13,15 +13,48 @@ on:
description: "Url to the file to upload"
required: true
type: string
avast:
description: "Upload to Avast"
required: false
type: boolean
default: false
kaspersky:
description: "Upload to Kaspersky"
required: false
type: boolean
default: false
jobs:
allowlist:
name: Anti Virus Allowlisting
download-file:
name: Downloads the file into the VM
runs-on: ubuntu-latest
outputs:
fileName: ${{ steps.extractName.outputs.fileName}}
steps:
- name: Download file
- name: Extract file name
id: extractName
run: |
curl --remote-name ${{ inputs.url }} -L
url="${{ inputs.url }}"
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
- name: Download file
run: curl --remote-name ${{ inputs.url }} -L -o ${{steps.extractName.outputs.fileName}}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.extractName.outputs.fileName }}
path: ${{ steps.extractName.outputs.fileName }}
if-no-files-found: error
allowlist-kaspersky:
name: Anti Virus Allowlisting Kaspersky
runs-on: ubuntu-latest
needs: download-file
if: github.event_name == 'workflow_call' || inputs.kaspersky
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.download-file.outputs.fileName }}
path: upload
- name: Upload to Kaspersky
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
@@ -30,11 +63,26 @@ jobs:
port: 990
username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
- name: Upload to Avast
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
local-dir: ./upload/
allowlist-avast:
name: Anti Virus Allowlisting Avast
runs-on: ubuntu-latest
needs: download-file
if: github.event_name == 'workflow_call' || inputs.avast
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.download-file.outputs.fileName }}
path: upload
- name: Upload to Avast
uses: wlixcc/SFTP-Deploy-Action@v1.2.5
with:
protocol: ftp
server: whitelisting.avast.com
port: 21
port: 22
username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
ssh_private_key: ''
sftp_only: true
local_path: './upload/*'
remote_path: '/data'

View File

@@ -17,18 +17,18 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '23.0.1+11'
JAVA_VERSION: '23.0.2+7'
COFFEELIBS_JDK: 23
COFFEELIBS_JDK_VERSION: '23.0.1+11-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: '09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b'
COFFEELIBS_JDK_VERSION: '23.0.2+7-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: '9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
jobs:
build:
name: Build Debian Package
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- id: versions

View File

@@ -15,7 +15,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '23.0.1+11'
JAVA_VERSION: '23.0.2+7'
jobs:
get-version:
@@ -35,8 +35,8 @@ jobs:
architecture: x64
output-suffix: x64
fuse-lib: macFUSE
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_osx-x64_bin-jmods.zip'
openjfx-sha: '8857965975c464a0e5d57709292ce357d0ebb39f6168c41d5ca38301e42c3c8e'
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_osx-x64_bin-jmods.zip'
openjfx-sha: '5e6c65c065eea22430c0eab36f37a5985eb8ad99e19e8772262021740d338f68'
steps:
- uses: actions/checkout@v4
- name: Setup Java

View File

@@ -16,7 +16,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '23.0.1+11'
JAVA_VERSION: '23.0.2+7'
jobs:
get-version:
@@ -36,8 +36,8 @@ jobs:
architecture: aarch64
output-suffix: arm64
fuse-lib: FUSE-T
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_osx-aarch64_bin-jmods.zip'
openjfx-sha: 'a800724a1f3e6757ecfa0bd5bf7ed64d2e6a7a3f5b3522650a70b8cfc7782fb6'
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_osx-aarch64_bin-jmods.zip'
openjfx-sha: 'c690cc642a3924cf56622951f478ba57aec9ce09063761f800c3319331bed3fc'
steps:
- uses: actions/checkout@v4
- name: Setup Java

View File

@@ -16,7 +16,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '23.0.1+11'
JAVA_VERSION: '23.0.2+7'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
@@ -40,9 +40,6 @@ jobs:
LOOPBACK_ALIAS: 'cryptomator-vault'
WIN_CONSOLE_FLAG: ''
steps:
- name: Upgrade WIX to latest version
run: choco install wixtoolset --version 3.14.1
shell: pwsh
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
@@ -394,7 +391,7 @@ jobs:
allowlist-exe:
uses: ./.github/workflows/av-whitelist.yml
needs: [publish]
needs: [publish, allowlist-msi]
with:
url: ${{ needs.publish.outputs.download-url-exe }}
secrets: inherit

25
.idea/compiler.xml generated
View File

@@ -39,31 +39,6 @@
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.55/dagger-compiler-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.55/dagger-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.55/dagger-spi-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.0.21-1.0.28/symbol-processing-api-2.0.21-1.0.28.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/3.41.0/checker-qual-3.41.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.23.0/error_prone_annotations-2.23.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
</processorPath>
<module name="cryptomator" />
</profile>

View File

@@ -1,6 +1,6 @@
[![cryptomator](cryptomator.png)](https://cryptomator.org/)
[![Build](https://github.com/cryptomator/cryptomator/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
[![Build](https://github.com/cryptomator/cryptomator/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptomator/actions/workflows/build.yml?query=branch%3Adevelop)
[![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptomator/badge.svg)](https://snyk.io/test/github/cryptomator/cryptomator)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cryptomator_cryptomator&metric=alert_status)](https://sonarcloud.io/dashboard?id=cryptomator_cryptomator)
[![Mastodon](https://img.shields.io/mastodon/follow/176112?domain=mastodon.online&style=flat)](https://mastodon.online/@cryptomator)

View File

@@ -23,12 +23,12 @@ mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests -Djavafx.platform=l
cp ../../../LICENSE.txt ../../../target
cp ../../../target/cryptomator-*.jar ../../../target/mods
JAVAFX_VERSION=22.0.2
JAVAFX_VERSION=23.0.2
JAVAFX_ARCH="x64"
JAVAFX_JMODS_SHA256='2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6'
JAVAFX_JMODS_SHA256='063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
if [ "${CPU_ARCH}" = "aarch64" ]; then
JAVAFX_ARCH="aarch64"
JAVAFX_JMODS_SHA256='09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b'
JAVAFX_JMODS_SHA256='9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
fi
# download javaFX jmods

View File

@@ -6,6 +6,12 @@
<name>Cryptomator</name>
<summary>Encryption for your cloud made easy</summary>
<keywords>
<keyword>encryption</keyword>
<keyword>security</keyword>
<keyword>privacy</keyword>
</keywords>
<description>
<p>
Cryptomator provides easy-to-use, transparent, client-side encryption for your cloud.
@@ -43,16 +49,16 @@
<screenshots>
<screenshot type="default">
<caption>Encrypt your data, protect your privacy</caption>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png</image>
<caption>Encrypts your data, protects your privacy</caption>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png</image>
</screenshot>
<screenshot>
<caption>Dark theme available</caption>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlock_dark.png</image>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_dark.png</image>
</screenshot>
<screenshot>
<caption>Uses AES-GCM 256 - an industry standardized, quantum resistant encryption</caption>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png</image>
<caption>Easy to use - work on encrypted files as if they were not</caption>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png</image>
</screenshot>
</screenshots>
@@ -77,6 +83,9 @@
</content_rating>
<releases>
<release date="2025-04-29" version="1.16.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.0</url>
</release>
<release date="2025-04-09" version="1.15.3">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.3</url>
</release>

View File

@@ -1,12 +1,16 @@
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
<style
id="current-color-scheme" type="text/css">
.ColorScheme-Text {
color:#232629;
}
</style>
<g fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1" class="ColorScheme-Text" fill="currentColor">
<path d="m15.591 35.824c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.043c.051 1.03-.161 2.644-.509 2.834zm6.409-6.824h-2l.5-5a2 2 0 1 1 1 0zm-14.544-3.241.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm13.544-10.759a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm14.473 6a8.067 8.067 0 0 0 -8.08 8v2.141a3.891 3.891 0 0 0 -2.893 3.734v5.125a23.166 23.166 0 0 1 -4.174-1.623 7.857 7.857 0 0 1 -.027.878 3.263 3.263 0 0 1 -.729 2.074l-1.794 1.483a.379.379 0 0 1 -.276.188h-4c-1.324 0-2.346-1.336-2.653-3.343a7.058 7.058 0 0 1 .234-3.18 3.477 3.477 0 0 1 1.636-2.157 1.868 1.868 0 0 1 .783-.32h1.5a8.035 8.035 0 0 1 -1.5-5 11.1 11.1 0 0 1 .5-3 2.519 2.519 0 0 0 0-1.5 13.272 13.272 0 0 1 -.5-3.5c6.687-1.936 11 0 11 0s4.319-1.955 11 0"/>
<path d="m39 28h-10v-4a3.13 3.13 0 0 1 3-3 3.087 3.087 0 0 1 3 3v1a1.034 1.034 0 0 0 1 1h1a1.034 1.034 0 0 0 1-1v-1a6 6 0 0 0 -12 0v4h-1a2.073 2.073 0 0 0 -2 2v6a2.073 2.073 0 0 0 2 2h14a2.073 2.073 0 0 0 2-2v-6a2.073 2.073 0 0 0 -2-2zm-5.391 5.94a1.609 1.609 0 0 1 -3.217 0v-1.876a1.609 1.609 0 0 1 3.217 0z"/>
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text {
color:#222222;
}
.ColorScheme-Highlight {
color:#49B04A;
}
</style>
</defs>
<g fill-rule="evenodd">
<path d="m15.591 35.824c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.043c.051 1.03-.161 2.644-.509 2.834zm6.409-6.824h-2l.5-5a2 2 0 1 1 1 0zm-14.544-3.241.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm13.544-10.759a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm14.473 6a8.067 8.067 0 0 0 -8.08 8v2.141a3.891 3.891 0 0 0 -2.893 3.734v5.125a23.166 23.166 0 0 1 -4.174-1.623 7.857 7.857 0 0 1 -.027.878 3.263 3.263 0 0 1 -.729 2.074l-1.794 1.483a.379.379 0 0 1 -.276.188h-4c-1.324 0-2.346-1.336-2.653-3.343a7.058 7.058 0 0 1 .234-3.18 3.477 3.477 0 0 1 1.636-2.157 1.868 1.868 0 0 1 .783-.32h1.5a8.035 8.035 0 0 1 -1.5-5 11.1 11.1 0 0 1 .5-3 2.519 2.519 0 0 0 0-1.5 13.272 13.272 0 0 1 -.5-3.5c6.687-1.936 11 0 11 0s4.319-1.955 11 0" class="ColorScheme-Text" fill="currentColor"/>
<path d="m39 28h-10v-4a3.13 3.13 0 0 1 3-3 3.087 3.087 0 0 1 3 3v1a1.034 1.034 0 0 0 1 1h1a1.034 1.034 0 0 0 1-1v-1a6 6 0 0 0 -12 0v4h-1a2.073 2.073 0 0 0 -2 2v6a2.073 2.073 0 0 0 2 2h14a2.073 2.073 0 0 0 2-2v-6a2.073 2.073 0 0 0 -2-2zm-5.391 5.94a1.609 1.609 0 0 1 -3.217 0v-1.876a1.609 1.609 0 0 1 3.217 0z" class="ColorScheme-Highlight" fill="currentColor"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,8 +1,10 @@
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text {
color:#232629;
}
</style>
<path d="m32.66 29.319a1.432 1.432 0 0 0 -.66-.319h-1.5a8.125 8.125 0 0 0 1.5-5 11.027 11.027 0 0 0 -.5-3 2.519 2.519 0 0 1 0-1.5 12.987 12.987 0 0 0 .5-3.5c-6.681-1.955-11 0-11 0s-4.313-1.936-11 0a13.272 13.272 0 0 0 .5 3.5 2.519 2.519 0 0 1 0 1.5 11.1 11.1 0 0 0 -.5 3 8.035 8.035 0 0 0 1.5 5h-1.5a1.868 1.868 0 0 0 -.783.319 3.477 3.477 0 0 0 -1.636 2.157 7.058 7.058 0 0 0 -.234 3.18c.307 2.008 1.329 3.344 2.653 3.344h4a.379.379 0 0 0 .277-.187l1.793-1.483a3.263 3.263 0 0 0 .729-2.074 7.857 7.857 0 0 0 .027-.878 23.166 23.166 0 0 0 4.174 1.622 24.4 24.4 0 0 0 4.051-1.614 7.848 7.848 0 0 0 .027.869 3.263 3.263 0 0 0 .729 2.074l1.793 1.484a.61.61 0 0 0 .4.187h4c1.324 0 2.223-1.336 2.529-3.343a7.057 7.057 0 0 0 -.234-3.18 3.477 3.477 0 0 0 -1.635-2.158zm-17.069 6.5c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.048c.051 1.03-.161 2.644-.509 2.834zm6.409-6.819h-2l.5-5a2 2 0 1 1 1 0zm6.38 7.921a.418.418 0 0 1 -.627.111c-.522-.433-1.439-1.2-1.458-1.208-.348-.189-.56-1.8-.505-2.828a15.84 15.84 0 0 0 2.9-2.037 9.322 9.322 0 0 1 -.31 5.962zm-20.924-11.162.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm33.217 1.2a3.021 3.021 0 0 0 -2.658-1.525 1.574 1.574 0 0 0 -.107-1.283l-.745-1.367a1.779 1.779 0 0 0 -1.317-.891l-.7-1.278a2.409 2.409 0 0 0 .229-2.661 2.283 2.283 0 0 0 -2.375-1.454 7.039 7.039 0 0 1 0 3 3.272 3.272 0 0 0 0 1.5c.047.152-.047.3 0 .5.227.04-.069.156.165.14l.653 1.2a1.579 1.579 0 0 0 -.019 1.557l.745 1.367a1.753 1.753 0 0 0 1.045.832 2.66 2.66 0 0 0 -.238 2.916 2.989 2.989 0 0 0 2.326 1.509.79.79 0 0 0 .082 0 .707.707 0 0 0 .717-.6.688.688 0 0 0 -.636-.749 1.546 1.546 0 0 1 -1.2-.78 1.408 1.408 0 0 1 .672-1.95 1.628 1.628 0 0 1 1.179-.089 1.512 1.512 0 0 1 .9.719 1.37 1.37 0 0 1 -.023 1.361.656.656 0 0 0 .284.92.748.748 0 0 0 .981-.266 2.648 2.648 0 0 0 .04-2.633zm-19.673-11.959a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2z" fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1" class="ColorScheme-Text" fill="currentColor"/>
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text {
color:#222222;
}
</style>
</defs>
<path d="m32.66 29.319a1.432 1.432 0 0 0 -.66-.319h-1.5a8.125 8.125 0 0 0 1.5-5 11.027 11.027 0 0 0 -.5-3 2.519 2.519 0 0 1 0-1.5 12.987 12.987 0 0 0 .5-3.5c-6.681-1.955-11 0-11 0s-4.313-1.936-11 0a13.272 13.272 0 0 0 .5 3.5 2.519 2.519 0 0 1 0 1.5 11.1 11.1 0 0 0 -.5 3 8.035 8.035 0 0 0 1.5 5h-1.5a1.868 1.868 0 0 0 -.783.319 3.477 3.477 0 0 0 -1.636 2.157 7.058 7.058 0 0 0 -.234 3.18c.307 2.008 1.329 3.344 2.653 3.344h4a.379.379 0 0 0 .277-.187l1.793-1.483a3.263 3.263 0 0 0 .729-2.074 7.857 7.857 0 0 0 .027-.878 23.166 23.166 0 0 0 4.174 1.622 24.4 24.4 0 0 0 4.051-1.614 7.848 7.848 0 0 0 .027.869 3.263 3.263 0 0 0 .729 2.074l1.793 1.484a.61.61 0 0 0 .4.187h4c1.324 0 2.223-1.336 2.529-3.343a7.057 7.057 0 0 0 -.234-3.18 3.477 3.477 0 0 0 -1.635-2.158zm-17.069 6.5c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.048c.051 1.03-.161 2.644-.509 2.834zm6.409-6.819h-2l.5-5a2 2 0 1 1 1 0zm6.38 7.921a.418.418 0 0 1 -.627.111c-.522-.433-1.439-1.2-1.458-1.208-.348-.189-.56-1.8-.505-2.828a15.84 15.84 0 0 0 2.9-2.037 9.322 9.322 0 0 1 -.31 5.962zm-20.924-11.162.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm33.217 1.2a3.021 3.021 0 0 0 -2.658-1.525 1.574 1.574 0 0 0 -.107-1.283l-.745-1.367a1.779 1.779 0 0 0 -1.317-.891l-.7-1.278a2.409 2.409 0 0 0 .229-2.661 2.283 2.283 0 0 0 -2.375-1.454 7.039 7.039 0 0 1 0 3 3.272 3.272 0 0 0 0 1.5c.047.152-.047.3 0 .5.227.04-.069.156.165.14l.653 1.2a1.579 1.579 0 0 0 -.019 1.557l.745 1.367a1.753 1.753 0 0 0 1.045.832 2.66 2.66 0 0 0 -.238 2.916 2.989 2.989 0 0 0 2.326 1.509.79.79 0 0 0 .082 0 .707.707 0 0 0 .717-.6.688.688 0 0 0 -.636-.749 1.546 1.546 0 0 1 -1.2-.78 1.408 1.408 0 0 1 .672-1.95 1.628 1.628 0 0 1 1.179-.089 1.512 1.512 0 0 1 .9.719 1.37 1.37 0 0 1 -.023 1.361.656.656 0 0 0 .284.92.748.748 0 0 0 .981-.266 2.648 2.648 0 0 0 .04-2.633zm-19.673-11.959a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2z" fill-rule="evenodd" class="ColorScheme-Text" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils
Priority: optional
Build-Depends: debhelper (>=10), coffeelibs-jdk-23 (>= 23.0.1+11-0ppa1), libgtk-3-0, libxxf86vm1, libgl1
Build-Depends: debhelper (>=10), coffeelibs-jdk-23 (>= 23.0.2+7-0ppa1), libgtk-3-0, libxxf86vm1, libgl1
Standards-Version: 4.5.0
Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git

View File

@@ -32,15 +32,15 @@ REVISION_NO=`git rev-list --count HEAD`
VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'`
FUSE_LIB="FUSE-T"
JAVAFX_VERSION=23.0.1
JAVAFX_VERSION=23.0.2
JAVAFX_ARCH="undefined"
JAVAFX_JMODS_SHA256="undefined"
if [ "$(machine)" = "arm64e" ]; then
JAVAFX_ARCH="aarch64"
JAVAFX_JMODS_SHA256="a800724a1f3e6757ecfa0bd5bf7ed64d2e6a7a3f5b3522650a70b8cfc7782fb6"
JAVAFX_JMODS_SHA256="c690cc642a3924cf56622951f478ba57aec9ce09063761f800c3319331bed3fc"
else
JAVAFX_ARCH="x64"
JAVAFX_JMODS_SHA256="8857965975c464a0e5d57709292ce357d0ebb39f6168c41d5ca38301e42c3c8e"
JAVAFX_JMODS_SHA256="5e6c65c065eea22430c0eab36f37a5985eb8ad99e19e8772262021740d338f68"
fi
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip"

2
dist/win/build.ps1 vendored
View File

@@ -51,7 +51,7 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
}
## download jfx jmods
$javaFxVersion='23.0.1'
$javaFxVersion='23.0.2'
$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip"
$javaFxJmodsSHA256 = 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3'
$javaFxJmods = '.\resources\jfxJmods.zip'

46
pom.xml
View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.15.3</version>
<version>1.16.0</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -33,42 +33,42 @@
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.8.0</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.5.0</cryptomator.integrations.version>
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.5.1</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.4</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.5.2</cryptomator.integrations.linux.version>
<cryptomator.integrations.mac.version>1.3.0</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.5.3</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>5.0.5</cryptomator.fuse.version>
<cryptomator.webdav.version>2.0.10</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<commons-lang3.version>3.17.0</commons-lang3.version>
<dagger.version>2.55</dagger.version>
<dagger.version>2.56.1</dagger.version>
<easybind.version>2.2</easybind.version>
<jackson.version>2.18.2</jackson.version>
<javafx.version>23.0.1</javafx.version>
<jwt.version>4.4.0</jwt.version>
<jackson.version>2.18.3</jackson.version>
<javafx.version>23.0.2</javafx.version>
<jwt.version>4.5.0</jwt.version>
<nimbus-jose.version>9.37.3</nimbus-jose.version>
<logback.version>1.5.16</logback.version>
<slf4j.version>2.0.16</slf4j.version>
<tinyoauth2.version>0.8.0</tinyoauth2.version>
<logback.version>1.5.18</logback.version>
<slf4j.version>2.0.17</slf4j.version>
<tinyoauth2.version>0.8.1</tinyoauth2.version>
<zxcvbn.version>1.9.0</zxcvbn.version>
<!-- test dependencies -->
<junit.jupiter.version>5.11.4</junit.jupiter.version>
<mockito.version>5.15.2</mockito.version>
<junit.jupiter.version>5.12.2</junit.jupiter.version>
<mockito.version>5.17.0</mockito.version>
<hamcrest.version>3.0</hamcrest.version>
<!-- build-time dependencies -->
<jetbrains.annotations.version>26.0.1</jetbrains.annotations.version>
<dependency-check.version>12.0.1</dependency-check.version>
<jacoco.version>0.8.12</jacoco.version>
<jetbrains.annotations.version>26.0.2</jetbrains.annotations.version>
<dependency-check.version>12.1.1</dependency-check.version>
<jacoco.version>0.8.13</jacoco.version>
<license-generator.version>2.5.0</license-generator.version>
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
<mvn-compiler.version>3.13.0</mvn-compiler.version>
<mvn-compiler.version>3.14.0</mvn-compiler.version>
<mvn-resources.version>3.3.1</mvn-resources.version>
<mvn-dependency.version>3.8.1</mvn-dependency.version>
<mvn-surefire.version>3.5.2</mvn-surefire.version>
<mvn-surefire.version>3.5.3</mvn-surefire.version>
<mvn-jar.version>3.4.2</mvn-jar.version>
<!-- Property used by surefire to determine jacoco engine -->
@@ -80,7 +80,7 @@
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptolib</artifactId>
<version>2.2.0</version>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
@@ -159,6 +159,12 @@
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>

View File

@@ -59,6 +59,7 @@ open module org.cryptomator.desktop {
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
uses SSLContextProvider;
uses org.cryptomator.event.NotificationHandler;
provides TrayMenuController with AwtTrayMenuController;
provides Configurator with LogbackConfiguratorFactory;

View File

@@ -0,0 +1,22 @@
package org.cryptomator;
import javafx.application.Platform;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class JavaFXUtil {
private JavaFXUtil() {}
public static boolean startPlatform() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
try {
Platform.startup(latch::countDown);
} catch (IllegalStateException e) {
//already initialized
latch.countDown();
}
return latch.await(5, TimeUnit.SECONDS);
}
}

View File

@@ -0,0 +1,160 @@
package org.cryptomator.common;
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.event.VaultEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.beans.InvalidationListener;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
/**
* Map containing {@link VaultEvent}s.
* The map is keyed by the ciphertext path of the affected resource _and_ the {@link FilesystemEvent}s class in order to group same events
* <p>
* Use {@link EventMap#put(VaultEvent)} to add an element and {@link EventMap#remove(VaultEvent)} to remove it.
* <p>
* The map is size restricted to {@value MAX_SIZE} elements. If a _new_ element (i.e. not already present) is added, the least recently added is removed.
*/
@Singleton
public class EventMap implements ObservableMap<EventMap.EventKey, VaultEvent> {
private static final int MAX_SIZE = 300;
public record EventKey(Path ciphertextPath, Class<? extends FilesystemEvent> c) {}
private final ObservableMap<EventMap.EventKey, VaultEvent> delegate;
@Inject
public EventMap() {
delegate = FXCollections.observableHashMap();
}
@Override
public void addListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
delegate.addListener(mapChangeListener);
}
@Override
public void removeListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
delegate.removeListener(mapChangeListener);
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public VaultEvent get(Object key) {
return delegate.get(key);
}
@Override
public @Nullable VaultEvent put(EventKey key, VaultEvent value) {
return delegate.put(key, value);
}
@Override
public VaultEvent remove(Object key) {
return delegate.remove(key);
}
@Override
public void putAll(@NotNull Map<? extends EventKey, ? extends VaultEvent> m) {
delegate.putAll(m);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public @NotNull Set<EventKey> keySet() {
return delegate.keySet();
}
@Override
public @NotNull Collection<VaultEvent> values() {
return delegate.values();
}
@Override
public @NotNull Set<Entry<EventKey, VaultEvent>> entrySet() {
return delegate.entrySet();
}
@Override
public void addListener(InvalidationListener invalidationListener) {
delegate.addListener(invalidationListener);
}
@Override
public void removeListener(InvalidationListener invalidationListener) {
delegate.removeListener(invalidationListener);
}
public synchronized void put(VaultEvent e) {
//compute key
var key = computeKey(e.actualEvent());
//if-else
var nullOrEntry = delegate.get(key);
if (nullOrEntry == null) {
if (size() == MAX_SIZE) {
delegate.entrySet().stream() //
.min(Comparator.comparing(entry -> entry.getValue().actualEvent().getTimestamp())) //
.ifPresent(oldestEntry -> delegate.remove(oldestEntry.getKey()));
}
delegate.put(key, e);
} else {
delegate.put(key, nullOrEntry.incrementCount(e.actualEvent()));
}
}
public synchronized VaultEvent remove(VaultEvent similar) {
//compute key
var key = computeKey(similar.actualEvent());
return this.remove(key);
}
private EventKey computeKey(FilesystemEvent e) {
var p = switch (e) {
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
};
return new EventKey(p, e.getClass());
}
}

View File

@@ -2,6 +2,7 @@ package org.cryptomator.common.keychain;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.cryptomator.common.Passphrase;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
@@ -13,20 +14,24 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Singleton
public class KeychainManager implements KeychainAccessProvider {
private final ObjectExpression<KeychainAccessProvider> keychain;
private final LoadingCache<String, BooleanProperty> passphraseStoredProperties;
private final ReentrantReadWriteLock lock;
@Inject
KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
this.keychain = selectedKeychain;
this.passphraseStoredProperties = Caffeine.newBuilder() //
.weakValues() //
.softValues() //
.build(this::createStoredPassphraseProperty);
keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
this.lock = new ReentrantReadWriteLock(false);
}
private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
@@ -42,29 +47,59 @@ public class KeychainManager implements KeychainAccessProvider {
return getClass().getName();
}
@Override
public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
storePassphrase(key, displayName, passphrase, true);
}
//TODO: remove ignored parameter once the API is fixed
@Override
public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean ignored) throws KeychainAccessException {
getKeychainOrFail().storePassphrase(key, displayName, passphrase);
try {
lock.writeLock().lock();
var kc = getKeychainOrFail();
//this is the only keychain actually using the parameter
var usesOSAuth = (kc.getClass().getName().equals("org.cryptomator.macos.keychain.TouchIdKeychainAccess"));
kc.storePassphrase(key, displayName, passphrase, usesOSAuth);
} finally {
lock.writeLock().unlock();
}
setPassphraseStored(key, true);
}
@Override
public char[] loadPassphrase(String key) throws KeychainAccessException {
char[] passphrase = getKeychainOrFail().loadPassphrase(key);
char[] passphrase = null;
try {
lock.readLock().lock();
passphrase = getKeychainOrFail().loadPassphrase(key);
} finally {
lock.readLock().unlock();
}
setPassphraseStored(key, passphrase != null);
return passphrase;
}
@Override
public void deletePassphrase(String key) throws KeychainAccessException {
getKeychainOrFail().deletePassphrase(key);
try {
lock.writeLock().lock();
getKeychainOrFail().deletePassphrase(key);
} finally {
lock.writeLock().unlock();
}
setPassphraseStored(key, false);
}
@Override
public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
if (isPassphraseStored(key)) {
getKeychainOrFail().changePassphrase(key, displayName, passphrase);
try {
lock.writeLock().lock();
getKeychainOrFail().changePassphrase(key, displayName, passphrase);
} finally {
lock.writeLock().unlock();
}
setPassphraseStored(key, true);
}
}
@@ -101,13 +136,11 @@ public class KeychainManager implements KeychainAccessProvider {
}
private void setPassphraseStored(String key, boolean value) {
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
if (property != null) {
if (Platform.isFxApplicationThread()) {
property.set(value);
} else {
Platform.runLater(() -> property.set(value));
}
BooleanProperty property = passphraseStoredProperties.get(key, _ -> new SimpleBooleanProperty(value));
if (Platform.isFxApplicationThread()) {
property.set(value);
} else {
Platform.runLater(() -> property.set(value));
}
}
@@ -134,4 +167,22 @@ public class KeychainManager implements KeychainAccessProvider {
}
}
public ObjectExpression<KeychainAccessProvider> getKeychainImplementation() {
return this.keychain;
}
public static void migrate(KeychainAccessProvider oldProvider, KeychainAccessProvider newProvider, Map<String, String> idsAndNames) throws KeychainAccessException {
if (oldProvider instanceof KeychainManager || newProvider instanceof KeychainManager) {
throw new IllegalArgumentException("KeychainManger must not be the source or target of migration");
}
for (var entry : idsAndNames.entrySet()) {
var passphrase = oldProvider.loadPassphrase(entry.getKey());
if (passphrase != null) {
var wrapper = new Passphrase(passphrase);
oldProvider.deletePassphrase(entry.getKey()); //we cannot apply "first-write-then-delete" pattern here, since we can potentially write to the same passphrase store (e.g., touchID and regular keychain)
newProvider.storePassphrase(entry.getKey(), entry.getValue(), wrapper);
wrapper.destroy();
}
}
}
}

View File

@@ -58,6 +58,7 @@ public class VaultSettings {
public final StringExpression mountName;
public final StringProperty mountService;
public final IntegerProperty port;
public final StringProperty lastKnownKeyLoader;
VaultSettings(VaultSettingsJson json) {
this.id = json.id;
@@ -74,6 +75,7 @@ public class VaultSettings {
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
this.port = new SimpleIntegerProperty(this, "port", json.port);
this.lastKnownKeyLoader = new SimpleStringProperty(this, "lastKnownKeyLoader", json.lastKnownKeyLoader);
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
final String name;
@@ -99,7 +101,7 @@ public class VaultSettings {
}
Observable[] observables() {
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService};
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService, lastKnownKeyLoader};
}
public static VaultSettings withRandomId() {
@@ -130,6 +132,7 @@ public class VaultSettings {
json.mountPoint = mountPoint.map(Path::toString).getValue();
json.mountService = mountService.get();
json.port = port.get();
json.lastKnownKeyLoader = lastKnownKeyLoader.get();
return json;
}

View File

@@ -48,6 +48,9 @@ class VaultSettingsJson {
@JsonProperty("mountService")
String mountService;
@JsonProperty("lastKnownKeyLoader")
String lastKnownKeyLoader;
@JsonProperty("port")
int port = VaultSettings.DEFAULT_PORT;

View File

@@ -10,6 +10,7 @@ package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
@@ -18,9 +19,11 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException;
@@ -32,6 +35,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
@@ -74,6 +78,7 @@ public class Vault {
private final ObjectBinding<Mountpoint> mountPoint;
private final Mounter mounter;
private final Settings settings;
private final FileSystemEventAggregator fileSystemEventAggregator;
private final BooleanProperty showingStats;
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@@ -85,7 +90,8 @@ public class Vault {
VaultState state, //
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
VaultStats stats, //
Mounter mounter, Settings settings) {
Mounter mounter, Settings settings, //
FileSystemEventAggregator fileSystemEventAggregator) {
this.vaultSettings = vaultSettings;
this.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem;
@@ -102,6 +108,7 @@ public class Vault {
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
this.mounter = mounter;
this.settings = settings;
this.fileSystemEventAggregator = fileSystemEventAggregator;
this.showingStats = new SimpleBooleanProperty(false);
this.quickAccessEntry = new AtomicReference<>(null);
}
@@ -143,6 +150,7 @@ public class Vault {
.withFlags(flags) //
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
.withFilesystemEventConsumer(this::consumeVaultEvent) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
@@ -251,6 +259,11 @@ public class Vault {
}
}
private void consumeVaultEvent(FilesystemEvent e) {
fileSystemEventAggregator.put(this, e);
}
// ******************************************************************************
// Observable Properties
// *******************************************************************************
@@ -412,6 +425,17 @@ public class Vault {
}
}
/**
* Gets the cleartext name from a given path to an encrypted vault file
*/
public String getCleartextName(Path ciphertextPath) throws IOException {
if (!state.getValue().equals(VaultState.Value.UNLOCKED)) {
throw new IllegalStateException("Vault is not unlocked");
}
var fs = cryptoFileSystem.get();
return fs.getCleartextName(ciphertextPath);
}
public VaultConfigCache getVaultConfigCache() {
return configCache;
}

View File

@@ -27,6 +27,7 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
@@ -49,9 +50,9 @@ public class VaultListManager {
@Inject
public VaultListManager(ObservableList<Vault> vaultList, //
AutoLocker autoLocker, //
List<MountService> mountServices,
VaultComponent.Factory vaultComponentFactory,
ResourceBundle resourceBundle,
List<MountService> mountServices, //
VaultComponent.Factory vaultComponentFactory, //
ResourceBundle resourceBundle, //
Settings settings) {
this.vaultList = vaultList;
this.autoLocker = autoLocker;
@@ -114,6 +115,10 @@ public class VaultListManager {
private Vault create(VaultSettings vaultSettings) {
var wrapper = new VaultConfigCache(vaultSettings);
try {
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
var keyIdScheme = wrapper.get().getKeyId().getScheme();
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
}
var vaultState = determineVaultState(vaultSettings.path.get());
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
wrapper.reloadConfig();

View File

@@ -0,0 +1,14 @@
package org.cryptomator.event;
public sealed interface Answer permits Answer.DoNothing, Answer.DoSomething {
record DoNothing() implements Answer {}
record DoSomething(Runnable action) implements Answer {
void run() {
action.run();
}
}
}

View File

@@ -0,0 +1,8 @@
package org.cryptomator.event;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import java.nio.file.Path;
public record FSEventBucket(Vault vault, Path idPath, Class<? extends FilesystemEvent> c) {}

View File

@@ -0,0 +1,5 @@
package org.cryptomator.event;
import org.cryptomator.cryptofs.event.FilesystemEvent;
public record FSEventBucketContent(FilesystemEvent mostRecentEvent, int count) {}

View File

@@ -0,0 +1,107 @@
package org.cryptomator.event;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Aggregator for {@link FilesystemEvent}s.
* <p>
* The aggregator groups filesystem events by the vault where the event occurred, an identifying path (clear- or ciphertext) and the event class (aka type).
* A group is called an {@link FSEventBucket}, its {@link FSEventBucketContent} is the most recent event object and a count of how often the event already occurred.
*/
@Singleton
public class FileSystemEventAggregator {
private final ConcurrentHashMap<FSEventBucket, FSEventBucketContent> map;
private final AtomicBoolean hasUpdates;
@Inject
public FileSystemEventAggregator() {
this.map = new ConcurrentHashMap<>();
this.hasUpdates = new AtomicBoolean(false);
}
/**
* Adds the given event to the map. If a bucket for this event already exists, only the count is updated and the event set as the most recent one.
*
* @param v Vault where the event occurred
* @param e Actual {@link FilesystemEvent}
*/
public void put(Vault v, FilesystemEvent e) {
var key = computeKey(v, e);
map.compute(key, (k, val) -> {
if (val == null) {
return new FSEventBucketContent(e, 1);
} else {
return new FSEventBucketContent(e, val.count() + 1);
}
});
hasUpdates.set(true);
}
/**
* Removes an event bucket from the map.
*/
public FSEventBucketContent remove(FSEventBucket key) {
var content = map.remove(key);
hasUpdates.set(true);
return content;
}
/**
* Clears the event map.
*/
public void clear() {
map.clear();
hasUpdates.set(true);
}
public boolean hasMaybeUpdates() {
return hasUpdates.get();
}
/**
* Clones the map entries into a collection.
* <p>
* The collection is first cleared, then all map entries are added in one bulk operation. Cleans the hasUpdates status.
*
* @param target collection which is first cleared and then the EntrySet copied to.
*/
public void cloneTo(Collection<Map.Entry<FSEventBucket, FSEventBucketContent>> target) {
hasUpdates.set(false);
target.clear();
target.addAll(map.entrySet());
}
/**
* Method to compute the identifying key for a given filesystem event
*
* @param v Vault where the event occurred
* @param event Actual {@link FilesystemEvent}
* @return a {@link FSEventBucket} used in the map and lru cache
*/
private static FSEventBucket computeKey(Vault v, FilesystemEvent event) {
var p = switch (event) {
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
};
return new FSEventBucket(v, p, event.getClass());
}
}

View File

@@ -0,0 +1,15 @@
package org.cryptomator.event;
import org.cryptomator.integrations.common.IntegrationsLoader;
import java.util.ServiceLoader;
import java.util.stream.Stream;
public interface NotificationHandler {
Answer handle(VaultEvent e);
static Stream<NotificationHandler> loadAll() {
return IntegrationsLoader.loadAll(ServiceLoader.load(NotificationHandler.class), NotificationHandler.class);
}
}

View File

@@ -0,0 +1,27 @@
package org.cryptomator.event;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import java.time.Instant;
public record VaultEvent(Vault v, FilesystemEvent actualEvent, int count) implements Comparable<VaultEvent> {
public VaultEvent(Vault v, FilesystemEvent actualEvent) {
this(v, actualEvent, 1);
}
@Override
public int compareTo(VaultEvent other) {
var timeResult = actualEvent.getTimestamp().compareTo(other.actualEvent().getTimestamp());
if(timeResult != 0) {
return timeResult;
} else {
return this.equals(other) ? 0 : this.actualEvent.getClass().getName().compareTo(other.actualEvent.getClass().getName());
}
}
public VaultEvent incrementCount(FilesystemEvent update) {
return new VaultEvent(v, update, count+1);
}
}

View File

@@ -76,8 +76,10 @@ public class ReadmeGenerator {
input.chars().forEachOrdered(c -> {
if (c < 128) {
sb.append((char) c);
} else if (c <= 0xFF) {
sb.append("\\'").append(String.format("%02X", c));
} else if (c < 0xFFFF) {
sb.append("\\u").append(c);
sb.append("\\uc1\\u").append(c);
}
});
}

View File

@@ -12,7 +12,9 @@ public enum FxmlFile {
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), //
DECRYPTNAMES("/fxml/decryptnames.fxml"), //
ERROR("/fxml/error.fxml"), //
EVENT_VIEW("/fxml/eventview.fxml"), //
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
HEALTH_START("/fxml/health_start.fxml"), //
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //

View File

@@ -7,6 +7,7 @@ public enum FontAwesome5Icon {
ANCHOR("\uF13D"), //
ARROW_UP("\uF062"), //
BAN("\uF05E"), //
BELL("\uF0F3"), //
BUG("\uF188"), //
CARET_DOWN("\uF0D7"), //
CARET_RIGHT("\uF0Da"), //
@@ -15,10 +16,12 @@ public enum FontAwesome5Icon {
CLIPBOARD("\uF328"), //
COG("\uF013"), //
COGS("\uF085"), //
COMPRESS_ALT("\uF422"), //
COPY("\uF0C5"), //
CROWN("\uF521"), //
DONATE("\uF4B9"), //
EDIT("\uF044"), //
ELLIPSIS_V("\uF142"), //
EXCHANGE_ALT("\uF362"), //
EXCLAMATION("\uF12A"), //
EXCLAMATION_CIRCLE("\uF06A"), //

View File

@@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger;
@@ -108,6 +109,7 @@ public class HubToPasswordConvertController implements FxController {
.thenRunAsync(this::convertInternal, backgroundExecutorService) //
.whenCompleteAsync((result, exception) -> {
if (exception == null) {
vault.getVaultSettings().lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
LOG.info("Conversion of vault {} succeeded.", vault.getPath());
window.setScene(successScene.get());
} else {

View File

@@ -0,0 +1,25 @@
package org.cryptomator.ui.decryptname;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import java.nio.file.Path;
public record CipherAndCleartext(Path ciphertext, String cleartextName) {
public String getCiphertextFilename() {
return ciphertext.getFileName().toString();
}
public ObservableValue<String> ciphertextFilenameProperty() {
return new ReadOnlyStringWrapper(getCiphertextFilename());
}
public String getCleartextName() {
return cleartextName;
}
public ObservableValue<String> cleartextNameProperty() {
return new ReadOnlyStringWrapper(getCleartextName());
}
}

View File

@@ -0,0 +1,233 @@
package org.cryptomator.ui.decryptname;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.TransferMode;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@DecryptNameScoped
public class DecryptFileNamesViewController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(DecryptFileNamesViewController.class);
private static final KeyCodeCombination COPY_TO_CLIPBOARD_SHORTCUT = new KeyCodeCombination(KeyCode.C, KeyCodeCombination.SHORTCUT_DOWN);
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_WIN = "CTRL+C";
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_MAC = "⌘C";
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_LINUX = "CTRL+C";
private final ListProperty<CipherAndCleartext> mapping;
private final StringProperty dropZoneText = new SimpleStringProperty();
private final ObjectProperty<FontAwesome5Icon> dropZoneIcon = new SimpleObjectProperty<>();
private final BooleanProperty wrongFilesSelected = new SimpleBooleanProperty(false);
private final Stage window;
private final Vault vault;
private final ResourceBundle resourceBundle;
private final List<Path> initialList;
@FXML
public TableColumn<CipherAndCleartext, String> ciphertextColumn;
@FXML
public TableColumn<CipherAndCleartext, String> cleartextColumn;
@FXML
public TableView<CipherAndCleartext> cipherToCleartextTable;
@Inject
public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, @DecryptNameWindow List<Path> pathsToDecrypt, ResourceBundle resourceBundle) {
this.window = window;
this.vault = vault;
this.resourceBundle = resourceBundle;
this.mapping = new SimpleListProperty<>(FXCollections.observableArrayList());
this.initialList = pathsToDecrypt;
}
@FXML
public void initialize() {
cipherToCleartextTable.setItems(mapping);
cipherToCleartextTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
//DragNDrop
cipherToCleartextTable.setOnDragEntered(event -> {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
cipherToCleartextTable.setItems(FXCollections.emptyObservableList());
}
});
cipherToCleartextTable.setOnDragOver(event -> {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
if (SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
event.acceptTransferModes(TransferMode.LINK);
} else {
event.acceptTransferModes(TransferMode.ANY);
}
}
});
cipherToCleartextTable.setOnDragDropped(event -> {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
checkAndDecrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList());
cipherToCleartextTable.setItems(mapping);
}
});
cipherToCleartextTable.setOnDragExited(_ -> cipherToCleartextTable.setItems(mapping));
//selectionModel and copy-to-clipboard action
cipherToCleartextTable.getSelectionModel().setCellSelectionEnabled(true);
cipherToCleartextTable.setOnKeyPressed(keyEvent -> {
if (COPY_TO_CLIPBOARD_SHORTCUT.match(keyEvent)) {
copySingleCelltoClipboard();
}
});
ciphertextColumn.setCellValueFactory(new PropertyValueFactory<>("ciphertextFilename"));
cleartextColumn.setCellValueFactory(new PropertyValueFactory<>("cleartextName"));
dropZoneText.setValue(resourceBundle.getString("decryptNames.dropZone.message"));
dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
wrongFilesSelected.addListener((_, _, areWrongFiles) -> {
if (areWrongFiles) {
CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
dropZoneText.setValue(resourceBundle.getString("decryptNames.dropZone.message"));
dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
wrongFilesSelected.setValue(false);
});
}
});
if (!initialList.isEmpty()) {
checkAndDecrypt(initialList);
}
}
private void copySingleCelltoClipboard() {
cipherToCleartextTable.getSelectionModel().getSelectedCells().stream().findFirst().ifPresent(tablePosition -> {
var selectedItem = cipherToCleartextTable.getSelectionModel().getSelectedItem();
//TODO: give user feedback, if content is copied -> must be done via a custom cell factory to access the actual table cell!
if (tablePosition.getTableColumn().equals(ciphertextColumn)) {
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, selectedItem.ciphertext().toString()));
} else {
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, selectedItem.cleartextName()));
}
});
}
@FXML
public void selectFiles() {
var fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("decryptNames.filePicker.title"));
fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter(resourceBundle.getString("decryptNames.filePicker.extensionDescription"), List.of("*.c9r")));
fileChooser.setInitialDirectory(vault.getPath().toFile());
var ciphertextNodes = fileChooser.showOpenMultipleDialog(window);
if (ciphertextNodes != null) {
checkAndDecrypt(ciphertextNodes.stream().map(File::toPath).toList());
}
}
private void checkAndDecrypt(List<Path> pathsToDecrypt) {
mapping.clear();
//Assumption: All files are in the same directory
var testPath = pathsToDecrypt.getFirst();
if (!testPath.startsWith(vault.getPath())) {
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.foreignFiles").formatted(vault.getDisplayName()));
return;
}
if (pathsToDecrypt.size() == 1 && testPath.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)) {
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.vaultInternalFiles"));
return;
}
try {
var newMapping = pathsToDecrypt.stream().filter(p -> !p.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)).map(this::getCleartextName).toList();
mapping.addAll(newMapping);
} catch (UncheckedIOException e) {
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.generic"));
LOG.info("Failed to decrypt filenames for directory {}", testPath.getParent(), e);
} catch (IllegalArgumentException e) {
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.vaultInternalFiles"));
} catch (UnsupportedOperationException e) {
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.noDirIdBackup"));
}
}
private void setDropZoneError(String text) {
dropZoneIcon.setValue(FontAwesome5Icon.TIMES);
dropZoneText.setValue(text);
wrongFilesSelected.setValue(true);
}
private CipherAndCleartext getCleartextName(Path ciphertextNode) {
try {
var cleartextName = vault.getCleartextName(ciphertextNode);
return new CipherAndCleartext(ciphertextNode, cleartextName);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
//obvservable getter
public ObservableValue<String> dropZoneTextProperty() {
return dropZoneText;
}
public String getDropZoneText() {
return dropZoneText.get();
}
public ObservableValue<FontAwesome5Icon> dropZoneIconProperty() {
return dropZoneIcon;
}
public FontAwesome5Icon getDropZoneIcon() {
return dropZoneIcon.get();
}
public void clearTable() {
mapping.clear();
}
public void copyTableToClipboard() {
var csv = mapping.stream().map(cipherAndClear -> "\"" + cipherAndClear.ciphertext() + "\", \"" + cipherAndClear.cleartextName() + "\"").collect(Collectors.joining("\n"));
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, csv));
}
public String getCopyToClipboardShortcutString() {
if (SystemUtils.IS_OS_WINDOWS) {
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_WIN;
} else if (SystemUtils.IS_OS_MAC) {
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_MAC;
} else {
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_LINUX;
}
}
}

View File

@@ -0,0 +1,50 @@
package org.cryptomator.ui.decryptname;
import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.nio.file.Path;
import java.util.List;
@DecryptNameScoped
@Subcomponent(modules = DecryptNameModule.class)
public interface DecryptNameComponent {
Logger LOG = LoggerFactory.getLogger(DecryptNameComponent.class);
@DecryptNameWindow
Stage window();
@FxmlScene(FxmlFile.DECRYPTNAMES)
Lazy<Scene> decryptNamesView();
@DecryptNameWindow
Vault vault();
default void showDecryptFileNameWindow() {
Stage s = window();
s.setScene(decryptNamesView().get());
s.sizeToScene();
if (vault().isUnlocked()) {
s.show();
} else {
LOG.error("Aborted showing DecryptFileName window: vault state is not {}, but {}.", VaultState.Value.UNLOCKED, vault().getState());
}
}
@Subcomponent.Factory
interface Factory {
DecryptNameComponent create(@BindsInstance @DecryptNameWindow Vault vault, @BindsInstance @Named("windowOwner") Stage owner, @BindsInstance @DecryptNameWindow List<Path> pathsToDecrypt);
}
}

View File

@@ -0,0 +1,59 @@
package org.cryptomator.ui.decryptname;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import javax.inject.Named;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module
public abstract class DecryptNameModule {
@Provides
@DecryptNameScoped
@DecryptNameWindow
static Stage provideStage(StageFactory factory, @Named("windowOwner") Stage owner, @DecryptNameWindow Vault vault, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setResizable(true);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
stage.setTitle(resourceBundle.getString("decryptNames.title"));
vault.stateProperty().addListener(((_, _, _) -> stage.close())); //as soon as the state changes from unlocked, close the window
return stage;
}
@Provides
@DecryptNameScoped
@DecryptNameWindow
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@FxmlScene(FxmlFile.DECRYPTNAMES)
@DecryptNameScoped
static Scene provideDecryptNamesViewScene(@DecryptNameWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.DECRYPTNAMES);
}
@Binds
@IntoMap
@FxControllerKey(DecryptFileNamesViewController.class)
abstract FxController bindDecryptNamesViewController(DecryptFileNamesViewController controller);
}

View File

@@ -0,0 +1,11 @@
package org.cryptomator.ui.decryptname;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface DecryptNameScoped {}

View File

@@ -0,0 +1,12 @@
package org.cryptomator.ui.decryptname;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface DecryptNameWindow {}

View File

@@ -38,7 +38,7 @@ public class Dialogs {
.setMessageKey("removeVault.message") //
.setDescriptionKey("removeVault.description") //
.setIcon(FontAwesome5Icon.QUESTION) //
.setOkButtonKey("removeVault.confirmBtn") //
.setOkButtonKey("generic.button.remove") //
.setCancelButtonKey("generic.button.cancel") //
.setOkAction(stage -> {
LOG.debug("Removing vault {}.", vault.getDisplayName());
@@ -54,7 +54,7 @@ public class Dialogs {
.setMessageKey("removeCert.message") //
.setDescriptionKey("removeCert.description") //
.setIcon(FontAwesome5Icon.QUESTION) //
.setOkButtonKey("removeCert.confirmBtn") //
.setOkButtonKey("generic.button.remove") //
.setCancelButtonKey("generic.button.cancel") //
.setOkAction(stage -> {
settings.licenseKey.set(null);

View File

@@ -31,8 +31,9 @@ public class SimpleDialog {
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
new SimpleDialogController(resolveText(builder.messageKey, null), //
resolveText(builder.descriptionKey, null), //
builder.icon, resolveText(builder.okButtonKey, null), //
resolveText(builder.cancelButtonKey, null), //
builder.icon, //
resolveText(builder.okButtonKey, null), //
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
() -> builder.okAction.accept(dialogStage), //
() -> builder.cancelAction.accept(dialogStage)), //
Scene::new, builder.resourceBundle);
@@ -67,7 +68,6 @@ public class SimpleDialog {
private String descriptionKey;
private String okButtonKey;
private String cancelButtonKey;
private FontAwesome5Icon icon;
private Consumer<Stage> okAction = Stage::close;
private Consumer<Stage> cancelAction = Stage::close;
@@ -128,7 +128,6 @@ public class SimpleDialog {
Objects.requireNonNull(messageKey, "SimpleDialog messageKey must be set.");
Objects.requireNonNull(descriptionKey, "SimpleDialog descriptionKey must be set.");
Objects.requireNonNull(okButtonKey, "SimpleDialog okButtonKey must be set.");
Objects.requireNonNull(cancelButtonKey, "SimpleDialog cancelButtonKey must be set.");
try {
return new SimpleDialog(this);

View File

@@ -14,6 +14,7 @@ public class SimpleDialogController implements FxController {
private final String cancelButtonText;
private final Runnable okAction;
private final Runnable cancelAction;
private final boolean cancelButtonVisible;
public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) {
this.message = message;
@@ -23,6 +24,11 @@ public class SimpleDialogController implements FxController {
this.cancelButtonText = cancelButtonText;
this.okAction = okAction;
this.cancelAction = cancelAction;
this.cancelButtonVisible = cancelButtonText != null && !cancelButtonText.isEmpty();
}
public boolean isCancelButtonVisible() {
return cancelButtonVisible;
}
public String getMessage() {

View File

@@ -0,0 +1,329 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.integrations.revealpath.RevealFailedException;
import org.cryptomator.integrations.revealpath.RevealPathService;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
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;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tooltip;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.HBox;
import javafx.util.Duration;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Function;
public class EventListCellController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(EventListCellController.class);
private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
private static final DateTimeFormatter LOCAL_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
private final FileSystemEventAggregator fileSystemEventAggregator;
@Nullable
private final RevealPathService revealService;
private final ResourceBundle resourceBundle;
private final ObjectProperty<Map.Entry<FSEventBucket, FSEventBucketContent>> eventEntry;
private final StringProperty eventMessage;
private final StringProperty eventDescription;
private final ObjectProperty<FontAwesome5Icon> eventIcon;
private final ObservableValue<String> eventCount;
private final ObservableValue<Boolean> vaultUnlocked;
private final ObservableValue<String> readableTime;
private final ObservableValue<String> readableDate;
private final ObservableValue<String> message;
private final ObservableValue<String> description;
private final ObservableValue<FontAwesome5Icon> icon;
private final BooleanProperty actionsButtonVisible;
private final Tooltip eventTooltip;
@FXML
HBox root;
@FXML
ContextMenu eventActionsMenu;
@FXML
Button eventActionsButton;
@Inject
public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
this.fileSystemEventAggregator = fileSystemEventAggregator;
this.revealService = revealService.orElseGet(() -> null);
this.resourceBundle = resourceBundle;
this.eventEntry = new SimpleObjectProperty<>(null);
this.eventMessage = new SimpleStringProperty();
this.eventDescription = new SimpleStringProperty();
this.eventIcon = new SimpleObjectProperty<>();
this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1? "" : "("+ e.getValue().count() +")", "");
this.vaultUnlocked = ObservableUtil.mapWithDefault(eventEntry.flatMap(e -> e.getKey().vault().unlockedProperty()), Function.identity(), false);
this.readableTime = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_TIME_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
this.readableDate = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_DATE_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage);
this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription);
this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon);
this.actionsButtonVisible = new SimpleBooleanProperty();
this.eventTooltip = new Tooltip();
eventTooltip.setShowDelay(Duration.millis(500.0));
}
@FXML
public void initialize() {
actionsButtonVisible.bind(Bindings.createBooleanBinding(this::determineActionsButtonVisibility, root.hoverProperty(), eventActionsMenu.showingProperty(), vaultUnlocked));
vaultUnlocked.addListener((_, _, newValue) -> eventActionsMenu.hide());
Tooltip.install(root, eventTooltip);
}
private boolean determineActionsButtonVisibility() {
return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover());
}
public void setEventEntry(@NotNull Map.Entry<FSEventBucket, FSEventBucketContent> item) {
eventEntry.set(item);
eventActionsMenu.hide();
eventActionsMenu.getItems().clear();
eventTooltip.setText(item.getKey().vault().getDisplayName());
addAction("generic.action.dismiss", () -> {
fileSystemEventAggregator.remove(item.getKey());
});
switch (item.getValue().mostRecentEvent()) {
case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse);
case BrokenFileNodeEvent fse -> this.adjustToBrokenFileNodeEvent(fse);
}
}
private void adjustToBrokenFileNodeEvent(BrokenFileNodeEvent bfe) {
eventIcon.setValue(FontAwesome5Icon.TIMES);
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message"));
eventDescription.setValue(bfe.ciphertextPath().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, convertVaultPathToSystemPath(bfe.ciphertextPath())));
} else {
addAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.ciphertextPath()).toString()));
}
addAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
}
private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
eventIcon.setValue(FontAwesome5Icon.CHECK);
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message"));
eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
} else {
addAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
}
}
private void adjustToConflictEvent(ConflictResolutionFailedEvent cfe) {
eventIcon.setValue(FontAwesome5Icon.COMPRESS_ALT);
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflict.message"));
eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
addAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath()));
} else {
addAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
addAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString()));
}
}
private void adjustToDecryptionFailedEvent(DecryptionFailedEvent dfe) {
eventIcon.setValue(FontAwesome5Icon.BAN);
eventMessage.setValue(resourceBundle.getString("eventView.entry.decryptionFailed.message"));
eventDescription.setValue(dfe.ciphertextPath().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
} else {
addAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
}
}
private void adjustToBrokenDirFileEvent(BrokenDirFileEvent bde) {
eventIcon.setValue(FontAwesome5Icon.TIMES);
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenDirFile.message"));
eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
} else {
addAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
}
}
private void addAction(String localizationKey, Runnable action) {
var entry = new MenuItem(resourceBundle.getString(localizationKey));
entry.getStyleClass().addLast("dropdown-button-context-menu-item");
entry.setOnAction(_ -> action.run());
eventActionsMenu.getItems().addLast(entry);
}
private FontAwesome5Icon selectIcon() {
if (vaultUnlocked.getValue()) {
return eventIcon.getValue();
} else {
return FontAwesome5Icon.LOCK;
}
}
private String selectMessage() {
if (vaultUnlocked.getValue()) {
return eventMessage.getValue();
} else {
return "***********";
}
}
private String selectDescription() {
if (vaultUnlocked.getValue()) {
return eventDescription.getValue();
} else if (eventEntry.getValue() != null) {
var e = eventEntry.getValue().getKey();
return resourceBundle.getString("eventView.entry.vaultLocked.description").formatted(e != null ? e.vault().getDisplayName() : "");
} else {
return "";
}
}
@FXML
public void toggleEventActionsMenu() {
var e = eventEntry.get();
if (e != null) {
if (eventActionsMenu.isShowing()) {
eventActionsMenu.hide();
} else {
eventActionsMenu.show(eventActionsButton, Side.BOTTOM, 0.0, 0.0);
}
}
}
private Path convertVaultPathToSystemPath(Path p) {
if (!(p instanceof CryptoPath)) {
throw new IllegalArgumentException("Path " + p + " is not a vault path");
}
var v = eventEntry.getValue().getKey().vault();
if (!v.isUnlocked()) {
return Path.of(System.getProperty("user.home"));
}
var mountUri = v.getMountPoint().uri();
var internalPath = p.toString().substring(1);
return Path.of(mountUri.getPath().concat(internalPath).substring(1));
}
private void reveal(RevealPathService s, Path p) {
try {
s.reveal(p);
} catch (RevealFailedException e) {
LOG.warn("Failed to show path {}", p, e);
}
}
private void copyToClipboard(String s) {
var content = new ClipboardContent();
content.putString(s);
Clipboard.getSystemClipboard().setContent(content);
}
//-- property accessors --
public ObservableValue<String> messageProperty() {
return message;
}
public String getMessage() {
return message.getValue();
}
public ObservableValue<String> countProperty() {
return eventCount;
}
public String getCount() {
return eventCount.getValue();
}
public ObservableValue<String> descriptionProperty() {
return description;
}
public String getDescription() {
return description.getValue();
}
public ObservableValue<FontAwesome5Icon> iconProperty() {
return icon;
}
public FontAwesome5Icon getIcon() {
return icon.getValue();
}
public ObservableValue<Boolean> actionsButtonVisibleProperty() {
return actionsButtonVisible;
}
public boolean isActionsButtonVisible() {
return actionsButtonVisible.getValue();
}
public ObservableValue<String> eventLocalTimeProperty() {
return readableTime;
}
public String getEventLocalTime() {
return readableTime.getValue();
}
public ObservableValue<String> eventLocalDateProperty() {
return readableDate;
}
public String getEventLocalDate() {
return readableDate.getValue();
}
public ObservableValue<Boolean> vaultUnlockedProperty() {
return vaultUnlocked;
}
public boolean isVaultUnlocked() {
return vaultUnlocked.getValue();
}
}

View File

@@ -0,0 +1,66 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import javax.inject.Inject;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Map;
@EventViewScoped
public class EventListCellFactory implements Callback<ListView<Map.Entry<FSEventBucket, FSEventBucketContent>>, ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>>> {
private static final String FXML_PATH = "/fxml/eventview_cell.fxml";
private final FxmlLoaderFactory fxmlLoaders;
@Inject
EventListCellFactory(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
this.fxmlLoaders = fxmlLoaders;
}
@Override
public ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> call(ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView) {
try {
FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH);
return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
} catch (IOException e) {
throw new UncheckedIOException("Failed to load %s.".formatted(FXML_PATH), e);
}
}
private static class Cell extends ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> {
private final Parent root;
private final EventListCellController controller;
public Cell(Parent root, EventListCellController controller) {
this.root = root;
this.controller = controller;
}
@Override
protected void updateItem(Map.Entry<FSEventBucket, FSEventBucketContent> item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
this.getStyleClass().remove("list-cell");
} else {
this.getStyleClass().addLast("list-cell");
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(root);
controller.setEventEntry(item);
}
}
}
}

View File

@@ -0,0 +1,35 @@
package org.cryptomator.ui.eventview;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene;
import javafx.stage.Stage;
@EventViewScoped
@Subcomponent(modules = {EventViewModule.class})
public interface EventViewComponent {
@EventViewWindow
Stage window();
@FxmlScene(FxmlFile.EVENT_VIEW)
Lazy<Scene> scene();
default Stage showEventViewerWindow() {
Stage stage = window();
stage.setScene(scene().get());
stage.sizeToScene();
stage.show();
stage.requestFocus();
return stage;
}
@Subcomponent.Factory
interface Factory {
EventViewComponent create();
}
}

View File

@@ -0,0 +1,132 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxFSEventList;
import javax.inject.Inject;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListView;
import javafx.util.StringConverter;
import java.util.Map;
import java.util.ResourceBundle;
@EventViewScoped
public class EventViewController implements FxController {
private final FilteredList<Map.Entry<FSEventBucket, FSEventBucketContent>> filteredEventList;
private final ObservableList<Vault> vaults;
private final FileSystemEventAggregator aggregator;
private final SortedList<Map.Entry<FSEventBucket, FSEventBucketContent>> sortedEventList;
private final ObservableList<Vault> choiceBoxEntries;
private final ResourceBundle resourceBundle;
private final EventListCellFactory cellFactory;
@FXML
ChoiceBox<Vault> vaultFilterChoiceBox;
@FXML
ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView;
@Inject
public EventViewController(FxFSEventList fxFSEventList, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory, FileSystemEventAggregator aggregator) {
this.filteredEventList = fxFSEventList.getObservableList().filtered(_ -> true);
this.vaults = vaults;
this.aggregator = aggregator;
this.sortedEventList = new SortedList<>(filteredEventList, this::compareBuckets);
this.choiceBoxEntries = FXCollections.observableArrayList();
this.resourceBundle = resourceBundle;
this.cellFactory = cellFactory;
}
/**
* Comparison method for the lru cache. During comparsion the map is accessed.
* First the entries are compared by the event timestamp, then vaultId, then identifying path and lastly by class name.
*
* @param left an entry of a {@link FSEventBucket} and its content
* @param right another entry of a {@link FSEventBucket} plus content, compared to {@code left}
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
*/
private int compareBuckets(Map.Entry<FSEventBucket, FSEventBucketContent> left, Map.Entry<FSEventBucket, FSEventBucketContent> right) {
var t1 = left.getValue().mostRecentEvent().getTimestamp();
var t2 = right.getValue().mostRecentEvent().getTimestamp();
var timeComparison = t1.compareTo(t2);
if (timeComparison != 0) {
return -timeComparison; //we need the reverse timesorting
}
var vaultIdComparison = left.getKey().vault().getId().compareTo(right.getKey().vault().getId());
if (vaultIdComparison != 0) {
return vaultIdComparison;
}
var pathComparison = left.getKey().idPath().compareTo(right.getKey().idPath());
if (pathComparison != 0) {
return pathComparison;
}
return left.getKey().c().getName().compareTo(right.getKey().c().getName());
}
@FXML
public void initialize() {
choiceBoxEntries.add(null);
choiceBoxEntries.addAll(vaults);
vaults.addListener((ListChangeListener<? super Vault>) c -> {
while (c.next()) {
choiceBoxEntries.removeAll(c.getRemoved());
choiceBoxEntries.addAll(c.getAddedSubList());
}
});
eventListView.setCellFactory(cellFactory);
eventListView.setItems(sortedEventList);
vaultFilterChoiceBox.setItems(choiceBoxEntries);
vaultFilterChoiceBox.valueProperty().addListener(this::applyVaultFilter);
vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle));
}
private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) {
if (newV == null) {
filteredEventList.setPredicate(_ -> true);
} else {
filteredEventList.setPredicate(e -> e.getKey().vault().equals(newV));
}
}
@FXML
void clearEvents() {
aggregator.clear();
}
private static class VaultConverter extends StringConverter<Vault> {
private final ResourceBundle resourceBundle;
VaultConverter(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
@Override
public String toString(Vault v) {
if (v == null) {
return resourceBundle.getString("eventView.filter.allVaults");
} else {
return v.getDisplayName();
}
}
@Override
public Vault fromString(String displayLanguage) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -0,0 +1,67 @@
package org.cryptomator.ui.eventview;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.fxapp.FxFSEventList;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class EventViewModule {
@Provides
@EventViewScoped
@EventViewWindow
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle, FxFSEventList fxFSEventList) {
Stage stage = factory.create();
stage.setHeight(498);
stage.setTitle(resourceBundle.getString("eventView.title"));
stage.setResizable(true);
stage.initModality(Modality.NONE);
stage.focusedProperty().addListener((_,_,isFocused) -> {
if(isFocused) {
fxFSEventList.unreadEventsProperty().setValue(false);
}
});
return stage;
}
@Provides
@EventViewScoped
@EventViewWindow
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@FxmlScene(FxmlFile.EVENT_VIEW)
@EventViewScoped
static Scene provideEventViewerScene(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.EVENT_VIEW);
}
@Binds
@IntoMap
@FxControllerKey(EventViewController.class)
abstract FxController bindEventViewController(EventViewController controller);
@Binds
@IntoMap
@FxControllerKey(EventListCellController.class)
abstract FxController bindEventListCellController(EventListCellController controller);
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.eventview;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface EventViewScoped {
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.eventview;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface EventViewWindow {
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
@EventViewScoped
public class UpdateEventViewController implements FxController {
@Inject
public UpdateEventViewController() {
}
}

View File

@@ -43,8 +43,8 @@ public class AutoUnlocker {
private CompletionStage<Void> unlockSequentially(Stream<Vault> vaultStream) {
// this is an attempt to run all the unlock workflows sequentially, i.e. start the next workflow only after completing/failing the previous workflow.
return vaultStream.filter(Vault::isLocked).reduce(CompletableFuture.completedFuture(null),
(prevUnlock, nextVault) -> prevUnlock.thenCompose(unused -> appWindows.startUnlockWorkflow(nextVault, null)),
(prevUnlock, nextUnlock) -> nextUnlock.exceptionally(e -> null) // we don't care here about the exception, logged elsewhere
(prevUnlock, nextVault) -> prevUnlock.thenCompose(_ -> appWindows.startUnlockWorkflow(nextVault, null)),
(_, nextUnlock) -> nextUnlock.exceptionally(_ -> null) // we don't care here about the exception, logged elsewhere
);
}

View File

@@ -29,9 +29,10 @@ public class FxApplication {
private final FxApplicationStyle applicationStyle;
private final FxApplicationTerminator applicationTerminator;
private final AutoUnlocker autoUnlocker;
private final FxFSEventList fxFSEventList;
@Inject
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker, FxFSEventList fxFSEventList) {
this.startupTime = startupTime;
this.environment = environment;
this.settings = settings;
@@ -41,6 +42,7 @@ public class FxApplication {
this.applicationStyle = applicationStyle;
this.applicationTerminator = applicationTerminator;
this.autoUnlocker = autoUnlocker;
this.fxFSEventList = fxFSEventList;
}
public void start() {
@@ -85,6 +87,7 @@ public class FxApplication {
migrateAndInformDokanyRemoval();
launchEventHandler.startHandlingLaunchEvents();
fxFSEventList.schedulePollForUpdates();
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
}

View File

@@ -7,7 +7,9 @@ package org.cryptomator.ui.fxapp;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.ui.decryptname.DecryptNameComponent;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.eventview.EventViewComponent;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
@@ -19,11 +21,15 @@ import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javax.inject.Named;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.image.Image;
import java.io.IOException;
import java.io.InputStream;
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, //
DecryptNameComponent.class, //
MainWindowComponent.class, //
PreferencesComponent.class, //
VaultOptionsComponent.class, //
@@ -33,7 +39,8 @@ import java.io.InputStream;
ErrorComponent.class, //
HealthCheckComponent.class, //
UpdateReminderComponent.class, //
ShareVaultComponent.class})
ShareVaultComponent.class, //
EventViewComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {
@@ -66,4 +73,10 @@ abstract class FxApplicationModule {
return builder.build();
}
@Provides
@FxApplicationScoped
static EventViewComponent provideEventViewComponent(EventViewComponent.Factory factory) {
return factory.create();
}
}

View File

@@ -6,7 +6,9 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.dialogs.SimpleDialog;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.eventview.EventViewComponent;
import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
@@ -51,6 +53,7 @@ public class FxApplicationWindows {
private final UpdateReminderComponent.Factory updateReminderWindowFactory;
private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory;
private final Lazy<EventViewComponent> eventViewWindow;
private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow;
@@ -69,6 +72,7 @@ public class FxApplicationWindows {
ErrorComponent.Factory errorWindowFactory, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, //
Lazy<EventViewComponent> eventViewWindow, //
ExecutorService executor, //
Dialogs dialogs) {
this.primaryStage = primaryStage;
@@ -80,6 +84,7 @@ public class FxApplicationWindows {
this.updateReminderWindowFactory = updateReminderWindowFactory;
this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory;
this.eventViewWindow = eventViewWindow;
this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow;
@@ -93,17 +98,17 @@ public class FxApplicationWindows {
// register preferences shortcut
if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) {
desktop.setPreferencesHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ANY));
desktop.setPreferencesHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ANY));
}
// register preferences shortcut
if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
desktop.setAboutHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
desktop.setAboutHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
}
// register app reopen listener
if (desktop.isSupported(Desktop.Action.APP_EVENT_REOPENED)) {
desktop.addAppEventListener((AppReopenedListener) e -> showMainWindow());
desktop.addAppEventListener((AppReopenedListener) _ -> showMainWindow());
}
// observe visible windows
@@ -135,11 +140,12 @@ public class FxApplicationWindows {
}
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
return showMainWindow().thenApplyAsync(_ -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater) //
.whenComplete(this::reportErrors);
}
public void showQuitWindow(QuitResponse response, boolean forced) {
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response, forced), Platform::runLater);
}
public void showUpdateReminderWindow() {
@@ -147,13 +153,14 @@ public class FxApplicationWindows {
}
public void showDokanySupportEndWindow() {
CompletableFuture.runAsync(() -> dialogs.prepareDokanySupportEndDialog(
mainWindow.get().window(),
stage -> {
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
stage.close();
}
).build().showAndWait(), Platform::runLater);
CompletableFuture.runAsync(() -> createDokanySupportEndDialog().showAndWait(), Platform::runLater);
}
private SimpleDialog createDokanySupportEndDialog() {
return dialogs.prepareDokanySupportEndDialog(mainWindow.get().window(), stage -> {
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
stage.close();
}).build();
}
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
@@ -162,8 +169,7 @@ public class FxApplicationWindows {
LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
return unlockWorkflowFactory.create(vault, owner).unlockWorkflow();
}, Platform::runLater) //
.thenAcceptAsync(UnlockWorkflow::run, executor)
.exceptionally(e -> {
.thenAcceptAsync(UnlockWorkflow::run, executor).exceptionally(e -> {
showErrorWindow(e, owner == null ? primaryStage : owner, null);
return null;
});
@@ -182,6 +188,11 @@ public class FxApplicationWindows {
});
}
public CompletionStage<Stage> showEventViewer() {
return CompletableFuture.supplyAsync(() -> eventViewWindow.get().showEventViewerWindow(), Platform::runLater).whenComplete(this::reportErrors);
}
/**
* Displays the generic error scene in the given window.
*
@@ -199,5 +210,4 @@ public class FxApplicationWindows {
LOG.error("Failed to display stage", error);
}
}
}

View File

@@ -0,0 +1,67 @@
package org.cryptomator.ui.fxapp;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* List of all occurred filesystem events.
* <p>
* The list exposes an observable list and a property to listen for updates. Internally it polls the {@link FileSystemEventAggregator} in a regular interval for updates.
* If an update is available, the list from the {@link FileSystemEventAggregator } is cloned to this list on the FX application thread.
*/
@FxApplicationScoped
public class FxFSEventList {
private final ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> events;
private final FileSystemEventAggregator eventAggregator;
private final ScheduledExecutorService scheduler;
private final BooleanProperty unreadEvents;
@Inject
public FxFSEventList(FileSystemEventAggregator fsEventAggregator, ScheduledExecutorService scheduler) {
this.events = FXCollections.observableArrayList();
this.eventAggregator = fsEventAggregator;
this.scheduler = scheduler;
this.unreadEvents = new SimpleBooleanProperty(false);
}
public void schedulePollForUpdates() {
scheduler.schedule(this::checkForEventUpdates, 1000, TimeUnit.MILLISECONDS);
}
/**
* Checks for event updates and reschedules.
* If updates are available, the aggregated events are copied from back- to the frontend.
* Reschedules itself on successful execution
*/
private void checkForEventUpdates() {
if (eventAggregator.hasMaybeUpdates()) {
Platform.runLater(() -> {
eventAggregator.cloneTo(events);
unreadEvents.setValue(true);
schedulePollForUpdates();
});
} else {
schedulePollForUpdates();
}
}
public ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> getObservableList() {
return events;
}
public BooleanProperty unreadEventsProperty() {
return unreadEvents;
}
}

View File

@@ -3,6 +3,8 @@ package org.cryptomator.ui.keyloading;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,6 +30,33 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
@Override
Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException;
/**
* Determines whether the provided key loader scheme corresponds to a Hub Vault.
* <p>
* This method compares the {@code keyLoader} parameter with the known Hub Vault schemes
* {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTP} and {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTPS}.
*
* @param keyLoader A string representing the key loader scheme to be checked.
* @return {@code true} if the given key loader scheme represents a Hub Vault; {@code false} otherwise.
*/
static boolean isHubVault(String keyLoader) {
return HubKeyLoadingStrategy.SCHEME_HUB_HTTP.equals(keyLoader) || HubKeyLoadingStrategy.SCHEME_HUB_HTTPS.equals(keyLoader);
}
/**
* Determines whether the provided key loader scheme corresponds to a Masterkey File Vault.
* <p>
* This method checks if the {@code keyLoader} parameter matches the known Masterkey File Vault scheme
* {@link MasterkeyFileLoadingStrategy#SCHEME}.
* </p>
*
* @param keyLoader A string representing the key loader scheme to be checked.
* @return {@code true} if the given key loader scheme represents a Masterkey File Vault; {@code false} otherwise.
*/
static boolean isMasterkeyFileVault(String keyLoader) {
return MasterkeyFileLoadingStrategy.SCHEME.equals(keyLoader);
}
/**
* Allows the loader to try and recover from an exception thrown during the last attempt.
*

View File

@@ -165,6 +165,7 @@ public class ReceiveKeyController implements FxController {
var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token");
var request = HttpRequest.newBuilder(vaultKeyUri) //
.header("Authorization", "Bearer " + bearerToken) //
.header("Hub-Device-ID", deviceId) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();

View File

@@ -112,12 +112,12 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
}
private void savePasswordToSystemkeychain(Passphrase passphrase) {
if (keychain.isSupported()) {
try {
try {
if (keychain.isSupported() && !keychain.getPassphraseStoredProperty(vault.getId()).get()) {
keychain.storePassphrase(vault.getId(), vault.getDisplayName(), passphrase);
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
}

View File

@@ -1,13 +1,5 @@
package org.cryptomator.ui.mainwindow;
import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
@@ -21,6 +13,17 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.layout.StackPane;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
@MainWindowScoped
public class MainWindowController implements FxController {
@@ -63,27 +66,66 @@ public class MainWindowController implements FxController {
}
window.focusedProperty().addListener(this::mainWindowFocusChanged);
if (!neverTouched()) {
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
int x = settings.windowXPosition.get();
int y = settings.windowYPosition.get();
int width = settings.windowWidth.get();
int height = settings.windowHeight.get();
if (windowPositionSaved(x, y, width, height)) {
window.setX(x);
window.setY(y);
window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
}
window.widthProperty().addListener((_, _, _) -> savePositionalSettings());
window.heightProperty().addListener((_, _, _) -> savePositionalSettings());
window.xProperty().addListener((_, _, _) -> savePositionalSettings());
window.yProperty().addListener((_, _, _) -> savePositionalSettings());
window.setOnShowing(this::checkDisplayBounds);
settings.windowXPosition.bind(window.xProperty());
settings.windowYPosition.bind(window.yProperty());
settings.windowWidth.bind(window.widthProperty());
settings.windowHeight.bind(window.heightProperty());
}
private boolean neverTouched() {
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
private boolean windowPositionSaved(int x, int y, int width, int height) {
return x != 0 || y != 0 || width != 0 || height != 0;
}
public void savePositionalSettings() {
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
private void checkDisplayBounds(WindowEvent windowEvent) {
int x = settings.windowXPosition.get();
int y = settings.windowYPosition.get();
int width = settings.windowWidth.get();
int height = settings.windowHeight.get();
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
if (!isWithinDisplayBounds(x, y, width, height)) { //use stored window position
LOG.debug("Resetting window position due to insufficient screen overlap");
var centeredX = (primaryScreenBounds.getWidth() - window.getMinWidth()) / 2;
var centeredY = (primaryScreenBounds.getHeight() - window.getMinHeight()) / 2;
//check if we can keep width and height
if (isWithinDisplayBounds((int) centeredX, (int) centeredY, width, height)) {
//if so, keep window size
window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
}
//reset position of upper left corner
window.setX(centeredX);
window.setY(centeredY);
}
}
private boolean isWithinDisplayBounds(int x, int y, int width, int height) {
// define a rect which is inset on all sides from the window's rect:
final int shrinkedX = x + 20; // 20px left
final int shrinkedY = y + 5; // 5px top
final int shrinkedWidth = width - 40; // 20px left + 20px right
final int shrinkedHeigth = height - 25; // 5px top + 20px bottom
return isRectangleWithinBounds(shrinkedX, shrinkedY, 0, shrinkedHeigth) // Left pixel column
&& isRectangleWithinBounds(shrinkedX + shrinkedWidth, shrinkedY, 0, shrinkedHeigth) // Right pixel column
&& isRectangleWithinBounds(shrinkedX, shrinkedY, shrinkedWidth, 0) // Top pixel row
&& isRectangleWithinBounds(shrinkedX, shrinkedY + shrinkedHeigth, shrinkedWidth, 0); // Bottom pixel row
}
private boolean isRectangleWithinBounds(int x, int y, int width, int height) {
return !Screen.getScreensForRectangle(x, y, width, height).isEmpty();
}
private void mainWindowFocusChanged(Observable observable) {
@@ -124,7 +166,7 @@ public class MainWindowController implements FxController {
return updateAvailable.get();
}
public BooleanBinding licenseValidProperty(){
public BooleanBinding licenseValidProperty() {
return licenseHolder.validLicenseProperty();
}

View File

@@ -1,6 +1,7 @@
package org.cryptomator.ui.mainwindow;
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
@@ -14,9 +15,11 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.common.StageInitializer;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.migration.MigrationComponent;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import javax.inject.Named;
@@ -35,11 +38,19 @@ abstract class MainWindowModule {
@Provides
@MainWindow
@MainWindowScoped
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) {
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer, FxApplicationTerminator terminator, Lazy<TrayMenuComponent> trayMenu) {
initializer.accept(stage);
stage.setTitle("Cryptomator");
stage.setMinWidth(650);
stage.setMinHeight(498);
stage.setOnCloseRequest(e -> {
if (!trayMenu.get().isInitialized()) {
terminator.terminate();
e.consume();
} else {
stage.close();
}
});
return stage;
}

View File

@@ -8,9 +8,9 @@ import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -21,7 +21,6 @@ public class VaultDetailLockedController implements FxController {
private final ReadOnlyObjectProperty<Vault> vault;
private final FxApplicationWindows appWindows;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final KeychainManager keychain;
private final Stage mainWindow;
private final ObservableValue<Boolean> passwordSaved;
@@ -30,13 +29,11 @@ public class VaultDetailLockedController implements FxController {
this.vault = vault;
this.appWindows = appWindows;
this.vaultOptionsWindow = vaultOptionsWindow;
this.keychain = keychain;
this.mainWindow = mainWindow;
if (keychain.isSupported() && !keychain.isLocked()) {
this.passwordSaved = vault.flatMap(v -> keychain.getPassphraseStoredProperty(v.getId())).orElse(false);
} else {
this.passwordSaved = new SimpleBooleanProperty(false);
}
this.passwordSaved = Bindings.createBooleanBinding(() -> {
var v = vault.get();
return v != null && keychain.getPassphraseStoredProperty(v.getId()).getValue();
}, vault, keychain.getKeychainImplementation());
}
@FXML

View File

@@ -6,12 +6,14 @@ import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.tobiasdiez.easybind.EasyBind;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.revealpath.RevealFailedException;
import org.cryptomator.integrations.revealpath.RevealPathService;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.decryptname.DecryptNameComponent;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
@@ -39,10 +41,13 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
@MainWindowScoped
public class VaultDetailUnlockedController implements FxController {
@@ -56,26 +61,39 @@ public class VaultDetailUnlockedController implements FxController {
private final WrongFileAlertComponent.Builder wrongFileAlert;
private final Stage mainWindow;
private final Optional<RevealPathService> revealPathService;
private final DecryptNameComponent.Factory decryptNameWindowFactory;
private final ResourceBundle resourceBundle;
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
private final ObservableValue<Boolean> accessibleViaPath;
private final ObservableValue<Boolean> accessibleViaUri;
private final ObservableValue<String> mountPoint;
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
private final BooleanProperty draggingOverLocateEncrypted = new SimpleBooleanProperty();
private final BooleanProperty draggingOverDecryptName = new SimpleBooleanProperty();
private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty();
//FXML
public Button dropZone;
@FXML
public Button revealEncryptedDropZone;
@FXML
public Button decryptNameDropZone;
@Inject
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, WrongFileAlertComponent.Builder wrongFileAlert, @MainWindow Stage mainWindow, Optional<RevealPathService> revealPathService, ResourceBundle resourceBundle) {
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, //
FxApplicationWindows appWindows, //
VaultService vaultService, //
VaultStatisticsComponent.Builder vaultStatsBuilder, //
WrongFileAlertComponent.Builder wrongFileAlert, //
@MainWindow Stage mainWindow, //
Optional<RevealPathService> revealPathService, //
DecryptNameComponent.Factory decryptNameWindowFactory, //
ResourceBundle resourceBundle) {
this.vault = vault;
this.appWindows = appWindows;
this.vaultService = vaultService;
this.wrongFileAlert = wrongFileAlert;
this.mainWindow = mainWindow;
this.revealPathService = revealPathService;
this.decryptNameWindowFactory = decryptNameWindowFactory;
this.resourceBundle = resourceBundle;
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
this.vaultStatsBuilder = vaultStatsBuilder;
@@ -92,89 +110,81 @@ public class VaultDetailUnlockedController implements FxController {
}
public void initialize() {
dropZone.setOnDragEntered(this::handleDragEvent);
dropZone.setOnDragOver(this::handleDragEvent);
dropZone.setOnDragDropped(this::handleDragEvent);
dropZone.setOnDragExited(this::handleDragEvent);
revealEncryptedDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverLocateEncrypted));
revealEncryptedDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCiphertextPath, this::revealOrCopyPaths));
revealEncryptedDropZone.setOnDragExited(_ -> draggingOverLocateEncrypted.setValue(false));
EasyBind.includeWhen(dropZone.getStyleClass(), ACTIVE_CLASS, draggingOver);
decryptNameDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverDecryptName));
decryptNameDropZone.setOnDragDropped(e -> showDecryptNameWindow(e.getDragboard().getFiles().stream().map(File::toPath).toList()));
decryptNameDropZone.setOnDragExited(_ -> draggingOverDecryptName.setValue(false));
EasyBind.includeWhen(revealEncryptedDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverLocateEncrypted);
EasyBind.includeWhen(decryptNameDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverDecryptName);
}
private void handleDragEvent(DragEvent event) {
if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
if(SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
private void handleDragOver(DragEvent event, BooleanProperty prop) {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
if (SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
event.acceptTransferModes(TransferMode.LINK);
} else {
event.acceptTransferModes(TransferMode.ANY);
}
draggingOver.set(true);
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
List<Path> ciphertextPaths = event.getDragboard().getFiles().stream().map(File::toPath).map(this::getCiphertextPath).flatMap(Optional::stream).toList();
if (ciphertextPaths.isEmpty()) {
wrongFileAlert.build().showWrongFileAlertWindow();
} else {
revealOrCopyPaths(ciphertextPaths);
}
event.setDropCompleted(!ciphertextPaths.isEmpty());
event.consume();
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
draggingOver.set(false);
prop.set(true);
}
}
private VaultStatisticsComponent buildVaultStats(Vault vault) {
return vaultStatsBuilder.vault(vault).build();
private <T> void handleDragDropped(DragEvent event, Function<Path, T> computation, Consumer<List<T>> positiveAction) {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
List<T> objects = event.getDragboard().getFiles().stream().map(File::toPath).map(computation).filter(Objects::nonNull).toList();
if (objects.isEmpty()) {
wrongFileAlert.build().showWrongFileAlertWindow();
} else {
positiveAction.accept(objects);
}
event.setDropCompleted(!objects.isEmpty());
event.consume();
}
}
@FXML
public void revealAccessLocation() {
vaultService.reveal(vault.get());
}
@FXML
public void copyMountUri() {
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(mountPoint.getValue());
Clipboard.getSystemClipboard().setContent(clipboardContent);
}
@FXML
public void lock() {
appWindows.startLockWorkflow(vault.get(), mainWindow);
}
@FXML
public void showVaultStatistics() {
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
}
@FXML
public void chooseFileAndReveal() {
public void chooseDecryptedFileAndReveal() {
Preconditions.checkState(accessibleViaPath.getValue());
var fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.filePickerTitle"));
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.locateEncrypted.filePickerTitle"));
fileChooser.setInitialDirectory(Path.of(mountPoint.getValue()).toFile());
var cleartextFile = fileChooser.showOpenDialog(mainWindow);
if (cleartextFile != null) {
var ciphertextPaths = getCiphertextPath(cleartextFile.toPath()).stream().toList();
revealOrCopyPaths(ciphertextPaths);
var ciphertextPath = getCiphertextPath(cleartextFile.toPath());
if (ciphertextPath != null) {
revealOrCopyPaths(List.of(ciphertextPath));
}
}
}
@FXML
public void showDecryptNameWindow() {
showDecryptNameWindow(List.of());
}
private void showDecryptNameWindow(List<Path> pathsToDecrypt) {
decryptNameWindowFactory.create(vault.get(), mainWindow, pathsToDecrypt).showDecryptFileNameWindow();
}
private boolean startsWithVaultAccessPoint(Path path) {
return path.startsWith(Path.of(mountPoint.getValue()));
}
private Optional<Path> getCiphertextPath(Path path) {
@Nullable
private Path getCiphertextPath(Path path) {
if (!startsWithVaultAccessPoint(path)) {
LOG.debug("Path does not start with access point of selected vault: {}", path);
return Optional.empty();
LOG.debug("Path does not start with mount point of selected vault: {}", path);
return null;
}
try {
return Optional.of(vault.get().getCiphertextPath(path));
return vault.get().getCiphertextPath(path);
} catch (IOException e) {
LOG.warn("Unable to get ciphertext path from path: {}", path, e);
return Optional.empty();
return null;
}
}
@@ -206,6 +216,32 @@ public class VaultDetailUnlockedController implements FxController {
});
}
private VaultStatisticsComponent buildVaultStats(Vault vault) {
return vaultStatsBuilder.vault(vault).build();
}
@FXML
public void revealAccessLocation() {
vaultService.reveal(vault.get());
}
@FXML
public void copyMountUri() {
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(mountPoint.getValue());
Clipboard.getSystemClipboard().setContent(clipboardContent);
}
@FXML
public void lock() {
appWindows.startLockWorkflow(vault.get(), mainWindow);
}
@FXML
public void showVaultStatistics() {
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
}
/* Getter/Setter */
public ReadOnlyObjectProperty<Vault> vaultProperty() {
@@ -247,4 +283,6 @@ public class VaultDetailUnlockedController implements FxController {
public boolean isCiphertextPathsCopied() {
return ciphertextPathsCopied.get();
}
}

View File

@@ -10,6 +10,7 @@ import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxFSEventList;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
@@ -26,6 +27,7 @@ import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListView;
import javafx.scene.input.ContextMenuEvent;
@@ -34,7 +36,6 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.File;
@@ -66,6 +67,7 @@ public class VaultListController implements FxController {
private final VaultListCellFactory cellFactory;
private final AddVaultWizardComponent.Builder addVaultWizard;
private final BooleanBinding emptyVaultList;
private final BooleanProperty unreadEvents;
private final VaultListManager vaultListManager;
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
private final ResourceBundle resourceBundle;
@@ -76,7 +78,7 @@ public class VaultListController implements FxController {
public ListView<Vault> vaultList;
public StackPane root;
@FXML
private HBox addVaultButton;
private Button addVaultButton;
@FXML
private ContextMenu addVaultContextMenu;
@@ -91,7 +93,8 @@ public class VaultListController implements FxController {
ResourceBundle resourceBundle, //
FxApplicationWindows appWindows, //
Settings settings, //
Dialogs dialogs) {
Dialogs dialogs, //
FxFSEventList fxFSEventList) {
this.mainWindow = mainWindow;
this.vaults = vaults;
this.selectedVault = selectedVault;
@@ -104,6 +107,7 @@ public class VaultListController implements FxController {
this.dialogs = dialogs;
this.emptyVaultList = Bindings.isEmpty(vaults);
this.unreadEvents = fxFSEventList.unreadEventsProperty();
selectedVault.addListener(this::selectedVaultDidChange);
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
@@ -263,6 +267,10 @@ public class VaultListController implements FxController {
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
@FXML
public void showEventViewer() {
appWindows.showEventViewer();
}
// Getter and Setter
public BooleanBinding emptyVaultListProperty() {
@@ -289,4 +297,11 @@ public class VaultListController implements FxController {
return cellSize.getValue();
}
public ObservableValue<Boolean> unreadEventsPresentProperty() {
return unreadEvents;
}
public boolean getUnreadEventsPresent() {
return unreadEvents.getValue();
}
}

View File

@@ -1,10 +1,13 @@
package org.cryptomator.ui.preferences;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
import org.cryptomator.integrations.common.NamedServiceProvider;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import org.cryptomator.integrations.quickaccess.QuickAccessService;
import org.cryptomator.ui.common.FxController;
@@ -14,6 +17,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
@@ -23,6 +27,10 @@ import javafx.stage.Stage;
import javafx.util.StringConverter;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
@PreferencesScoped
public class GeneralPreferencesController implements FxController {
@@ -36,6 +44,8 @@ public class GeneralPreferencesController implements FxController {
private final Application application;
private final Environment environment;
private final List<KeychainAccessProvider> keychainAccessProviders;
private final KeychainManager keychain;
private final ExecutorService backgroundExecutor;
private final FxApplicationWindows appWindows;
public CheckBox useKeychainCheckbox;
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
@@ -47,12 +57,18 @@ public class GeneralPreferencesController implements FxController {
public CheckBox autoStartCheckbox;
public ToggleGroup nodeOrientation;
private CompletionStage<Void> keychainMigrations = CompletableFuture.completedFuture(null);
@Inject
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, List<KeychainAccessProvider> keychainAccessProviders, Application application, Environment environment, FxApplicationWindows appWindows) {
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, //
List<KeychainAccessProvider> keychainAccessProviders, KeychainManager keychain, Application application, //
Environment environment, FxApplicationWindows appWindows, ExecutorService backgroundExecutor) {
this.window = window;
this.settings = settings;
this.autoStartProvider = autoStartProvider;
this.keychainAccessProviders = keychainAccessProviders;
this.keychain = keychain;
this.backgroundExecutor = backgroundExecutor;
this.quickAccessServices = QuickAccessService.get().toList();
this.application = application;
this.environment = environment;
@@ -73,6 +89,7 @@ public class GeneralPreferencesController implements FxController {
Bindings.bindBidirectional(settings.keychainProvider, keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain);
keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not());
keychainBackendChoiceBox.valueProperty().addListener(this::migrateKeychainEntries);
useQuickAccessCheckbox.selectedProperty().bindBidirectional(settings.useQuickAccess);
var quickAccessSettingsConverter = new ServiceToSettingsConverter<>(quickAccessServices);
@@ -83,6 +100,25 @@ public class GeneralPreferencesController implements FxController {
quickAccessServiceChoiceBox.disableProperty().bind(useQuickAccessCheckbox.selectedProperty().not());
}
private void migrateKeychainEntries(Observable observable, KeychainAccessProvider oldProvider, KeychainAccessProvider newProvider) {
//currently, we only migrate on macOS (touchID vs regular keychain)
if (SystemUtils.IS_OS_MAC) {
var idsAndNames = settings.directories.stream().collect(Collectors.toMap(vs -> vs.id, vs -> vs.displayName.getValue()));
if (!idsAndNames.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Migrating keychain entries {} from {} to {}", idsAndNames.keySet(), oldProvider.displayName(), newProvider.displayName());
}
keychainMigrations = keychainMigrations.thenRunAsync(() -> {
try {
KeychainManager.migrate(oldProvider, newProvider, idsAndNames);
} catch (KeychainAccessException e) {
LOG.warn("Failed to migrate all entries from {} to {}", oldProvider.displayName(), newProvider.displayName(), e);
}
}, backgroundExecutor);
}
}
}
public boolean isAutoStartSupported() {
return autoStartProvider.isPresent();
}

View File

@@ -19,6 +19,8 @@ import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
@@ -32,7 +34,10 @@ import java.util.ResourceBundle;
@PreferencesScoped
public class UpdatesPreferencesController implements FxController {
private static final String DOWNLOADS_URI = "https://cryptomator.org/downloads";
private static final String DOWNLOADS_URI_TEMPLATE = "https://cryptomator.org/downloads/" //
+ "?utm_source=cryptomator-desktop" //
+ "&utm_medium=update-notification&" //
+ "utm_campaign=app-update-%s";
private final Application application;
private final Environment environment;
@@ -50,6 +55,7 @@ public class UpdatesPreferencesController implements FxController {
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
private final DateTimeFormatter formatter;
private final BooleanBinding upToDate;
private final String downloadsUri;
/* FXML */
public CheckBox checkForUpdatesCheckbox;
@@ -65,12 +71,13 @@ public class UpdatesPreferencesController implements FxController {
this.latestVersion = updateChecker.latestVersionProperty();
this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty();
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck);
this.currentVersion = updateChecker.getCurrentVersion();
this.currentVersion = environment.getAppVersion();
this.updateAvailable = updateChecker.updateAvailableProperty();
this.formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
this.upToDate = updateChecker.updateCheckStateProperty().isEqualTo(UpdateChecker.UpdateCheckState.CHECK_SUCCESSFUL).and(latestVersion.isEqualTo(currentVersion));
this.checkFailed = updateChecker.checkFailedProperty();
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, lastSuccessfulUpdateCheck);
this.downloadsUri = DOWNLOADS_URI_TEMPLATE.formatted(URLEncoder.encode(currentVersion, StandardCharsets.US_ASCII));
}
public void initialize() {
@@ -93,7 +100,7 @@ public class UpdatesPreferencesController implements FxController {
@FXML
public void visitDownloadsPage() {
application.getHostServices().showDocument(DOWNLOADS_URI);
application.getHostServices().showDocument(downloadsUri);
}
@FXML

View File

@@ -3,7 +3,7 @@ package org.cryptomator.ui.sharevault;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import javax.inject.Inject;
import javafx.application.Application;
@@ -33,8 +33,7 @@ public class ShareVaultController implements FxController {
this.window = window;
this.application = application;
this.vault = vault;
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
this.hubVault = KeyLoadingStrategy.isHubVault(vault.getVaultSettings().lastKnownKeyLoader.get());
}
@FXML

View File

@@ -3,6 +3,7 @@ package org.cryptomator.ui.vaultoptions;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.slf4j.Logger;
@@ -42,11 +43,11 @@ public class VaultOptionsController implements FxController {
window.setOnShowing(this::windowWillAppear);
selectedTabProperty.addListener(observable -> this.selectChosenTab());
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
if(!vaultScheme.equals(MasterkeyFileLoadingStrategy.SCHEME)){
var vaultKeyLoader = vault.getVaultSettings().lastKnownKeyLoader.get();
if(!KeyLoadingStrategy.isMasterkeyFileVault(vaultKeyLoader)){
tabPane.getTabs().remove(keyTab);
}
if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
if(!KeyLoadingStrategy.isHubVault(vaultKeyLoader)){
tabPane.getTabs().remove(hubTab);
}

View File

@@ -16,6 +16,10 @@
src: url('opensans_bold.ttf');
}
@font-face {
src: url('firacode_regular.ttf');
}
/*******************************************************************************
* *
* Root Styling & Colors *
@@ -125,6 +129,12 @@
-fx-fill: TEXT_FILL;
}
.cryptic-text {
-fx-fill: TEXT_FILL;
-fx-font-family: 'Fira Code';
-fx-font-size: 1.1em;
}
/*******************************************************************************
* *
* Glyph Icons *
@@ -183,19 +193,37 @@
}
.main-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
}
.main-window .button-left {
.main-window .button-bar .button-left {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 1px 0 0;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.main-window .button-right {
.main-window .button-bar .button-right {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.main-window .button-bar .button-left:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.main-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
/*******************************************************************************
@@ -284,6 +312,10 @@
-fx-font-size: 1.0em;
}
.list-cell .header-misc {
-fx-font-size: 1.0em;
}
.list-cell .detail-label {
-fx-text-fill: TEXT_FILL_MUTED;
-fx-font-size: 0.8em;
@@ -312,6 +344,42 @@
-fx-fill: transparent;
}
/*******************************************************************************
* *
* Event List *
* *
******************************************************************************/
.event-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
-fx-border-width: 0 0 1px 0;
}
.event-window .button-bar .button-right {
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.event-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.event-window .list-view .list-cell:hover {
-fx-background-color: CONTROL_BG_SELECTED;
}
.event-window .list-view .list-cell:selected {
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
-fx-background-insets: 0, 0 0 0 3px;
}
/*******************************************************************************
* *
* NotificationBar *
@@ -341,6 +409,12 @@
-fx-background-color: PRIMARY;
}
.notification-debug:hover .notification-label,
.notification-update:hover .notification-label,
.notification-support:hover .notification-label {
-fx-underline:true;
}
/*******************************************************************************
* *
* ScrollBar *
@@ -575,6 +649,16 @@
-fx-graphic-text-gap: 9px;
}
/*******************************************************************************
* *
* Update indicator
* *
******************************************************************************/
.icon-update-indicator {
-fx-fill: RED_5;
}
/*******************************************************************************
* *
* Hyperlinks *
@@ -938,4 +1022,167 @@
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px;
-fx-background-radius: 4px;
}
}
/*******************************************************************************
* *
* Decrypt Name Window
* *
******************************************************************************/
.decrypt-name-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
-fx-border-width: 0 0 1px 0;
}
.decrypt-name-window .button-bar .button-right {
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.decrypt-name-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.decrypt-name-window .table-view {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0,1;
/* There is some oddness if padding is in em values rather than pixels,
in particular, the left border of the control doesn't show. */
-fx-padding: 1; /* 0.083333em; */
}
.table-view > .placeholder {
-fx-background-color: transparent;
-fx-background-radius: 0px;
}
.table-view > .placeholder > .button {
-fx-border-width: 0;
-fx-border-color: transparent;
-fx-background-radius: 0px;
}
.table-view:focused {
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1;
-fx-background-radius: 0, 0;
/* There is some oddness if padding is in em values rather than pixels,
in particular, the left border of the control doesn't show. */
-fx-padding: 1; /* 0.083333em; */
}
.table-view > .virtual-flow > .scroll-bar:vertical {
-fx-background-insets: 0, 0 0 0 1;
-fx-padding: -1 -1 -1 0;
}
.table-view > .virtual-flow > .corner {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL ;
-fx-background-insets: 0, 1 0 0 1;
}
/* Each row in the table is a table-row-cell. Inside a table-row-cell is any
number of table-cell. */
.table-row-cell {
-fx-background-color: GRAY_3, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em; /* 0 */
-fx-text-fill: TEXT_FILL;
}
.table-row-cell:odd {
-fx-background-color: GRAY_3, GRAY_1;
-fx-background-insets: 0, 0 0 1 0;
}
.table-cell {
-fx-padding: 3px 6px 3px 6px;
-fx-background-color: transparent;
-fx-border-color: transparent CONTROL_BORDER_NORMAL transparent transparent;
-fx-border-width: 1px;
-fx-cell-size: 30px;
-fx-text-fill: TEXT_FILL;
-fx-text-overrun: center-ellipsis;
}
.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected > .table-cell {
-fx-text-fill: TEXT_FILL;
}
/* selected, hover - not specified */
/* selected, focused, hover */
/* selected, focused */
/* selected */
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:selected,
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected,
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected:hover {
-fx-background-color: CONTROL_PRIMARY_BG_NORMAL, PRIMARY_D1;
-fx-background-insets: 0 0 0 0, 1 1 1 3;
-fx-text-fill: TEXT_FILL;
}
/* focused */
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused {
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , CONTROL_BG_NORMAL;
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
-fx-text-fill: TEXT_FILL;
}
/* focused, hover */
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:hover {
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , PRIMARY_D2;
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
-fx-text-fill: TEXT_FILL;
}
/* hover */
.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:hover {
-fx-background-color: PRIMARY_D2;
-fx-text-fill: TEXT_FILL;
-fx-background-insets: 0 0 1 0;
}
/* The column-resize-line is shown when the user is attempting to resize a column. */
.table-view .column-resize-line {
-fx-background-color: CONTROL_BG_ARMED;
-fx-padding: 0.0em 0.0416667em 0.0em 0.0416667em; /* 0 0.571429 0 0.571429 */
}
/* This is the area behind the column headers. An ideal place to specify background
and border colors for the whole area (not individual column-header's). */
.table-view .column-header-background {
-fx-background-color: GRAY_2;
-fx-padding: 0;
}
/* The column header row is made up of a number of column-header, one for each
TableColumn, and a 'filler' area that extends from the right-most column
to the edge of the tableview, or up to the 'column control' button. */
.table-view .column-header {
-fx-text-fill: TEXT_FILL;
-fx-font-size: 1.083333em; /* 13pt ; 1 more than the default font */
-fx-size: 24;
-fx-border-style: solid;
-fx-border-color:
transparent
GRAY_3
GRAY_3
transparent;
-fx-border-insets: 0 0 0 0;
-fx-border-width: 0.083333em;
}
.table-view .column-header .label {
-fx-alignment: center;
}
.table-view .empty-table {
-fx-background-color: MAIN_BG;
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
}

Binary file not shown.

View File

@@ -16,6 +16,10 @@
src: url('opensans_bold.ttf');
}
@font-face {
src: url('firacode_regular.ttf');
}
/*******************************************************************************
* *
* Root Styling & Colors *
@@ -79,6 +83,7 @@
PROGRESS_INDICATOR_END: GRAY_4;
PROGRESS_BAR_BG: GRAY_8;
-fx-background-color: MAIN_BG;
-fx-text-fill: TEXT_FILL;
-fx-font-family: 'Open Sans';
@@ -124,6 +129,12 @@
-fx-fill: TEXT_FILL;
}
.cryptic-text {
-fx-fill: TEXT_FILL;
-fx-font-family: 'Fira Code';
-fx-font-size: 1.1em;
}
/*******************************************************************************
* *
* Glyph Icons *
@@ -182,6 +193,8 @@
}
.main-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
@@ -190,11 +203,27 @@
.main-window .button-bar .button-left {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 1px 0 0;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.main-window .button-bar .button-right {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.main-window .button-bar .button-left:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.main-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
/*******************************************************************************
@@ -283,6 +312,10 @@
-fx-font-size: 1.0em;
}
.list-cell .header-misc {
-fx-font-size: 1.0em;
}
.list-cell .detail-label {
-fx-text-fill: TEXT_FILL_MUTED;
-fx-font-size: 0.8em;
@@ -311,6 +344,42 @@
-fx-fill: transparent;
}
/*******************************************************************************
* *
* Event List *
* *
******************************************************************************/
.event-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
-fx-border-width: 0 0 1px 0;
}
.event-window .button-bar .button-right {
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.event-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.event-window .list-view .list-cell:hover {
-fx-background-color: CONTROL_BG_SELECTED;
}
.event-window .list-view .list-cell:selected {
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
-fx-background-insets: 0, 0 0 0 3px;
}
/*******************************************************************************
* *
* NotificationBar *
@@ -340,6 +409,12 @@
-fx-background-color: PRIMARY;
}
.notification-debug:hover .notification-label,
.notification-update:hover .notification-label,
.notification-support:hover .notification-label {
-fx-underline:true;
}
/*******************************************************************************
* *
* ScrollBar *
@@ -574,6 +649,16 @@
-fx-graphic-text-gap: 9px;
}
/*******************************************************************************
* *
* Update indicator
* *
******************************************************************************/
.icon-update-indicator {
-fx-fill: RED_5;
}
/*******************************************************************************
* *
* Hyperlinks *
@@ -786,11 +871,11 @@
/*******************************************************************************
* *
* Add Vault - MenuItem *
* Dropdown button context menu
* *
******************************************************************************/
.add-vault-menu-item {
.dropdown-button-context-menu-item {
-fx-padding: 4px 8px;
}
@@ -937,4 +1022,167 @@
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px;
-fx-background-radius: 4px;
}
}
/*******************************************************************************
* *
* Decrypt Name Window
* *
******************************************************************************/
.decrypt-name-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
-fx-border-width: 0 0 1px 0;
}
.decrypt-name-window .button-bar .button-right {
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.decrypt-name-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.decrypt-name-window .table-view {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0,1;
/* There is some oddness if padding is in em values rather than pixels,
in particular, the left border of the control doesn't show. */
-fx-padding: 1; /* 0.083333em; */
}
.table-view > .placeholder {
-fx-background-color: transparent;
-fx-background-radius: 0px;
}
.table-view > .placeholder > .button {
-fx-border-width: 0;
-fx-border-color: transparent;
-fx-background-radius: 0px;
}
.table-view:focused {
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1;
-fx-background-radius: 0, 0;
/* There is some oddness if padding is in em values rather than pixels,
in particular, the left border of the control doesn't show. */
-fx-padding: 1; /* 0.083333em; */
}
.table-view > .virtual-flow > .scroll-bar:vertical {
-fx-background-insets: 0, 0 0 0 1;
-fx-padding: -1 -1 -1 0;
}
.table-view > .virtual-flow > .corner {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL ;
-fx-background-insets: 0, 1 0 0 1;
}
/* Each row in the table is a table-row-cell. Inside a table-row-cell is any
number of table-cell. */
.table-row-cell {
-fx-background-color: GRAY_6, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em; /* 0 */
-fx-text-fill: TEXT_FILL;
}
.table-row-cell:odd {
-fx-background-color: GRAY_6, GRAY_9;
-fx-background-insets: 0, 0 0 1 0;
}
.table-cell {
-fx-padding: 3px 6px 3px 6px;
-fx-background-color: transparent;
-fx-border-color: transparent CONTROL_BORDER_NORMAL transparent transparent;
-fx-border-width: 1px;
-fx-cell-size: 30px;
-fx-text-fill: TEXT_FILL;
-fx-text-overrun: center-ellipsis;
}
.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected > .table-cell {
-fx-text-fill: TEXT_FILL;
}
/* selected, hover - not specified */
/* selected, focused, hover */
/* selected, focused */
/* selected */
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:selected,
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected,
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected:hover {
-fx-background-color: CONTROL_PRIMARY_BG_NORMAL, CONTROL_BG_SELECTED;
-fx-background-insets: 0 0 0 0, 1 1 1 3;
-fx-text-fill: TEXT_FILL;
}
/* focused */
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused {
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , CONTROL_BG_NORMAL;
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
-fx-text-fill: TEXT_FILL;
}
/* focused, hover */
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:hover {
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , PRIMARY_L2;
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
-fx-text-fill: TEXT_FILL;
}
/* hover */
.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:hover {
-fx-background-color: PRIMARY_L2;
-fx-text-fill: TEXT_FILL;
-fx-background-insets: 0 0 1 0;
}
/* The column-resize-line is shown when the user is attempting to resize a column. */
.table-view .column-resize-line {
-fx-background-color: CONTROL_BG_ARMED;
-fx-padding: 0.0em 0.0416667em 0.0em 0.0416667em; /* 0 0.571429 0 0.571429 */
}
/* This is the area behind the column headers. An ideal place to specify background
and border colors for the whole area (not individual column-header's). */
.table-view .column-header-background {
-fx-background-color: GRAY_7;
-fx-padding: 0;
}
/* The column header row is made up of a number of column-header, one for each
TableColumn, and a 'filler' area that extends from the right-most column
to the edge of the tableview, or up to the 'column control' button. */
.table-view .column-header {
-fx-text-fill: TEXT_FILL;
-fx-font-size: 1.083333em; /* 13pt ; 1 more than the default font */
-fx-size: 24;
-fx-border-style: solid;
-fx-border-color:
CONTROL_BORDER_NORMAL
GRAY_5
GRAY_5
transparent;
-fx-border-insets: 0 0 0 0;
-fx-border-width: 0.083333em;
}
.table-view .column-header .label {
-fx-alignment: center;
}
.table-view .empty-table {
-fx-background-color: MAIN_BG;
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
}

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.decryptname.DecryptFileNamesViewController"
styleClass="decrypt-name-window"
minWidth="400"
maxWidth="400"
minHeight="145">
<HBox styleClass="button-bar" alignment="CENTER">
<padding>
<Insets left="6"/>
</padding>
<Region HBox.hgrow="ALWAYS"/>
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#copyTableToClipboard">
<graphic>
<FontAwesome5IconView glyph="CLIPBOARD" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%decryptNames.copyTable.tooltip"/>
</tooltip>
</Button>
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#clearTable">
<graphic>
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%decryptNames.clearTable.tooltip"/>
</tooltip>
</Button>
</HBox>
<TableView fx:id="cipherToCleartextTable" VBox.vgrow="ALWAYS">
<placeholder>
<Button alignment="CENTER" onAction="#selectFiles" text="${controller.dropZoneText}" contentDisplay="TOP" maxWidth="Infinity" maxHeight="Infinity">
<graphic>
<FontAwesome5IconView glyph="${controller.dropZoneIcon}" glyphSize="16"/>
</graphic>
</Button>
</placeholder>
<columns>
<TableColumn fx:id="ciphertextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
<graphic>
<FontAwesome5IconView glyph="LOCK"/>
</graphic>
</TableColumn>
<TableColumn fx:id="cleartextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
<graphic>
<FontAwesome5IconView glyph="LOCK_OPEN"/>
</graphic>
</TableColumn>
</columns>
</TableView>
<HBox>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Region HBox.hgrow="ALWAYS"/>
<FormattedLabel styleClass="label-small" format="%decryptNames.copyHint" arg1="${controller.copyToClipboardShortcutString}"/>
</HBox>
</VBox>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Tooltip?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.eventview.EventViewController"
minWidth="300"
prefWidth="300"
styleClass="event-window"
>
<HBox styleClass="button-bar" alignment="CENTER">
<padding>
<Insets left="6" />
</padding>
<ChoiceBox fx:id="vaultFilterChoiceBox" minWidth="42"/>
<Region HBox.hgrow="ALWAYS"/>
<Button styleClass="button-right" onAction="#clearEvents" contentDisplay="GRAPHIC_ONLY">
<graphic>
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%eventView.clearListButton.tooltip"/>
</tooltip>
</Button>
</HBox>
<ListView fx:id="eventListView" fixedCellSize="60" VBox.vgrow="ALWAYS"/>
</VBox>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.eventview.EventListCellController"
prefHeight="60"
prefWidth="200"
spacing="12"
alignment="CENTER_LEFT"
fx:id="root">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
<VBox alignment="CENTER" minWidth="20">
<FontAwesome5IconView glyph="${controller.icon}" HBox.hgrow="NEVER" glyphSize="16"/>
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<HBox spacing="4">
<Label styleClass="header-label" text="${controller.message}"/>
<Label styleClass="header-misc" text="${controller.count}" visible="${controller.vaultUnlocked}"/>
</HBox>
<Label text="${controller.description}"/>
</VBox>
<Button fx:id="eventActionsButton" contentDisplay="GRAPHIC_ONLY" onAction="#toggleEventActionsMenu" managed="${controller.actionsButtonVisible}" visible="${controller.actionsButtonVisible}">
<graphic>
<FontAwesome5IconView glyph="ELLIPSIS_V" glyphSize="16"/>
</graphic>
</Button>
<VBox alignment="CENTER" maxWidth="64" minWidth="64" visible="${!controller.actionsButtonVisible}" managed="${!controller.actionsButtonVisible}">
<Label text="${controller.eventLocalTime}" />
<Label text="${controller.eventLocalDate}" />
</VBox>
<fx:define>
<ContextMenu fx:id="eventActionsMenu"/>
</fx:define>
</HBox>

View File

@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.dialogs.SimpleDialogController"
@@ -41,7 +41,7 @@
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel"/>
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel" visible="${controller.cancelButtonVisible}" managed="${controller.cancelButtonVisible}"/>
<Button text="${controller.okButtonText}" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#handleOk"/>
</buttons>
</ButtonBar>

View File

@@ -1,12 +1,14 @@
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.ThroughputLabel?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.text.Text?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnlockedController"
@@ -44,28 +46,37 @@
<Region VBox.vgrow="ALWAYS"/>
<HBox alignment="BOTTOM_CENTER">
<HBox visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
<StackPane visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
<padding>
<Insets topRightBottomLeft="0"/>
</padding>
<Button fx:id="dropZone" styleClass="drag-n-drop" text="%main.vaultDetail.locateEncryptedFileBtn" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${!controller.ciphertextPathsCopied}" managed="${!controller.ciphertextPathsCopied}">
<Button fx:id="revealEncryptedDropZone" styleClass="drag-n-drop" text="%main.vaultDetail.locateEncryptedFileBtn" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseDecryptedFileAndReveal" contentDisplay="TOP" visible="${!controller.ciphertextPathsCopied}" managed="${!controller.ciphertextPathsCopied}">
<graphic>
<FontAwesome5IconView glyph="FILE_DOWNLOAD" glyphSize="15"/>
<Text styleClass="cryptic-text" text="abc → 101010"/>
</graphic>
<tooltip>
<Tooltip text="%main.vaultDetail.locateEncryptedFileBtn.tooltip"/>
</tooltip>
</Button>
<Button styleClass="drag-n-drop" text="%main.vaultDetail.encryptedPathsCopied" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${controller.ciphertextPathsCopied}" managed="${controller.ciphertextPathsCopied}">
<!-- TODO: instead of showing a button, show on error a small dialog and if copied, show a tooltip -->
<Button styleClass="drag-n-drop" text="%main.vaultDetail.encryptedPathsCopied" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseDecryptedFileAndReveal" contentDisplay="TOP" visible="${controller.ciphertextPathsCopied}" managed="${controller.ciphertextPathsCopied}">
<graphic>
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
</graphic>
</Button>
</HBox>
</StackPane>
<!-- decrypt file name -->
<Button fx:id="decryptNameDropZone" styleClass="drag-n-drop" text="%main.vaultDetail.decryptName.buttonLabel" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#showDecryptNameWindow" contentDisplay="TOP">
<graphic>
<Text styleClass="cryptic-text" text="101010 → abc"/>
</graphic>
<tooltip>
<Tooltip text="%main.vaultDetail.decryptName.tooltip"/>
</tooltip>
</Button>
<Region HBox.hgrow="ALWAYS"/>
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM">
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM" prefHeight="72">
<graphic>
<VBox spacing="6">
<HBox alignment="CENTER_RIGHT" spacing="6">

View File

@@ -1,16 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.shape.Arc?>
<?import javafx.scene.shape.Circle?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.layout.AnchorPane?>
<StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="root"
@@ -35,33 +40,44 @@
</VBox>
</StackPane>
<HBox styleClass="button-bar">
<HBox fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<FontAwesome5IconView glyph="PLUS" HBox.hgrow="NEVER" glyphSize="16"/>
</HBox>
<Button fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
<graphic>
<FontAwesome5IconView glyph="PLUS" glyphSize="16"/>
</graphic>
</Button>
<Region HBox.hgrow="ALWAYS"/>
<HBox onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<FontAwesome5IconView glyph="COG" HBox.hgrow="NEVER" glyphSize="16"/>
</HBox>
<StackPane>
<Button onMouseClicked="#showEventViewer" styleClass="button-right" minWidth="20" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
<graphic>
<FontAwesome5IconView glyph="BELL" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%main.vaultlist.showEventsButton.tooltip"/>
</tooltip>
</Button>
<AnchorPane mouseTransparent="true" minWidth="12" maxWidth="12" minHeight="12" maxHeight="12" StackPane.alignment="CENTER">
<Circle radius="4" styleClass="icon-update-indicator" AnchorPane.topAnchor="-8" AnchorPane.rightAnchor="-6" visible="${controller.unreadEventsPresent}" />
</AnchorPane>
</StackPane>
<Button onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
<graphic>
<FontAwesome5IconView glyph="COG" glyphSize="16"/>
</graphic>
</Button>
</HBox>
</VBox>
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
<fx:define>
<ContextMenu fx:id="addVaultContextMenu">
<items>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault">
<graphic>
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14"/>
</graphic>
</MenuItem>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14"/>
</graphic>
</MenuItem>
</items>

View File

@@ -14,17 +14,15 @@
spacing="12"
alignment="CENTER_LEFT">
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
<children>
<VBox alignment="CENTER" minWidth="20">
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
<tooltip>
<Tooltip text="${controller.vault.displayablePath}"/>
</tooltip>
</Label>
</VBox>
</children>
<VBox alignment="CENTER" minWidth="20">
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
<tooltip>
<Tooltip text="${controller.vault.displayablePath}"/>
</tooltip>
</Label>
</VBox>
</HBox>

View File

@@ -2,6 +2,7 @@
additionalStyleSheets=
# Generics
generic.action.dismiss=Dismiss
## Button
generic.button.apply=Apply
generic.button.back=Back
@@ -107,7 +108,6 @@ addvaultwizard.success.unlockNow=Unlock Now
removeVault.title=Remove "%s"
removeVault.message=Remove vault?
removeVault.description=This will only make Cryptomator forget about this vault. You can add it again. No encrypted files will be deleted from your hard drive.
removeVault.confirmBtn=Remove Vault
# Change Password
changepassword.title=Change Password
@@ -284,7 +284,7 @@ preferences.title=Preferences
## General
preferences.general=General
preferences.general.startHidden=Hide window when starting Cryptomator
preferences.general.autoCloseVaults=Lock open vaults automatically when quitting application
preferences.general.autoCloseVaults=Lock vaults without asking when quitting application
preferences.general.debugLogging=Enable debug logging
preferences.general.debugDirectory=Reveal log files
preferences.general.autoStart=Launch Cryptomator on system start
@@ -396,6 +396,7 @@ main.vaultlist.contextMenu.vaultoptions=Show Vault Options
main.vaultlist.contextMenu.reveal=Reveal Drive
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault...
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault...
main.vaultlist.showEventsButton.tooltip=Open event view
##Notificaition
main.notification.updateAvailable=Update is available.
main.notification.support=Support Cryptomator.
@@ -424,7 +425,9 @@ main.vaultDetail.stats=Vault Statistics
main.vaultDetail.locateEncryptedFileBtn=Locate Encrypted File
main.vaultDetail.locateEncryptedFileBtn.tooltip=Choose a file from your vault to locate its encrypted counterpart
main.vaultDetail.encryptedPathsCopied=Paths Copied to Clipboard!
main.vaultDetail.filePickerTitle=Select File Inside Vault
main.vaultDetail.locateEncrypted.filePickerTitle=Select File Inside Vault
main.vaultDetail.decryptName.buttonLabel=Decrypt File Name
main.vaultDetail.decryptName.tooltip=Choose an encrypted vault file to decrypt its name
### Missing
main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
main.vaultDetail.missing.recheck=Recheck
@@ -578,4 +581,43 @@ shareVault.hub.message=How to share a Hub vault
shareVault.hub.description=In order to share the vault content with another team member, you have to perform two steps:
shareVault.hub.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
shareVault.hub.instruction.2=2. Grant access to team member in Cryptomator Hub.
shareVault.hub.openHub=Open Cryptomator Hub
shareVault.hub.openHub=Open Cryptomator Hub
# Decrypt File Names
decryptNames.title=Decrypt File Names
decryptNames.filePicker.title=Select encrypted file
decryptNames.filePicker.extensionDescription=Cryptomator encrypted file
decryptNames.copyTable.tooltip=Copy table
decryptNames.clearTable.tooltip=Clear table
decryptNames.copyHint=Copy cell content with %s
decryptNames.dropZone.message=Drop files or click to select
decryptNames.dropZone.error.vaultInternalFiles=Vault internal files with no decrypt-able name selected
decryptNames.dropZone.error.foreignFiles=Files do not belong to vault "%s"
decryptNames.dropZone.error.noDirIdBackup=Directory of selected files does not contain dirId.c9r file
decryptNames.dropZone.error.generic=Failed to decrypt file names
# Event View
eventView.title=Events
eventView.filter.allVaults=All
eventView.clearListButton.tooltip=Clear list
## event list entries
eventView.entry.vaultLocked.description=Unlock "%s" for details
eventView.entry.conflictResolved.message=Resolved conflict
eventView.entry.conflictResolved.showDecrypted=Show decrypted file
eventView.entry.conflictResolved.copyDecrypted=Copy decrypted path
eventView.entry.conflict.message=Conflict resolution failed
eventView.entry.conflict.showDecrypted=Show decrypted, original file
eventView.entry.conflict.copyDecrypted=Copy decrypted, original path
eventView.entry.conflict.showEncrypted=Show conflicting, encrypted file
eventView.entry.conflict.copyEncrypted=Copy conflicting, encrypted path
eventView.entry.decryptionFailed.message=Decryption failed
eventView.entry.decryptionFailed.showEncrypted=Show encrypted file
eventView.entry.decryptionFailed.copyEncrypted=Copy encrypted path
eventView.entry.brokenDirFile.message=Broken directory link
eventView.entry.brokenDirFile.showEncrypted=Show broken, encrypted link
eventView.entry.brokenDirFile.copyEncrypted=Copy path of broken link
eventView.entry.brokenFileNode.message=Broken filesystem node
eventView.entry.brokenFileNode.showEncrypted=Show broken, encrypted node
eventView.entry.brokenFileNode.copyEncrypted=Copy path of broken, encrypted node
eventView.entry.brokenFileNode.copyDecrypted=Copy decrypted path

View File

@@ -121,3 +121,10 @@
#Retry If Readonly
# Share Vault
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=تجاهل
## Button
generic.button.apply=تطبيق
generic.button.back=رجوع
@@ -176,6 +177,7 @@ hub.registerFailed.description.generic=حدث خطأ في عملية تسجيل
hub.registerFailed.description.deviceAlreadyExists=هذا الجهاز مسجل لمستخدم مختلف بالفعل. حاول تغيير حساب المستخدم أو استخدام جهاز مختلف.
### Unauthorized
hub.unauthorized.message=تم رفض الوصول
hub.unauthorized.description=غير مسموح لك بفتح هذا المستودع. اتصل بمالك المستودع لطلب الوصول.
### Requires Account Initialization
hub.requireAccountInit.message=مطلوب اتخاذ إجراء
hub.requireAccountInit.description.0=للمتابعة، يرجى إكمال الخطوات المطلوبة في
@@ -281,7 +283,7 @@ preferences.title=تفضيلات
## General
preferences.general=عام
preferences.general.startHidden=إخفاء النافذة عند بدء تشغيل Cryptomator
preferences.general.autoCloseVaults=اقفل الخزانات المفتوحة تلقائياً عند الإقلاع عن التطبيق
preferences.general.autoCloseVaults=اقفل المخازن دون السؤال عند الإقلاع عن التطبيق
preferences.general.debugLogging=تمكين سجلات التصحيح
preferences.general.debugDirectory=عرض ملفات السجل
preferences.general.autoStart=تشغيل Cryptomator عند بدء تشغيل النظام
@@ -421,7 +423,6 @@ main.vaultDetail.stats=إحصائيات الخزنة
main.vaultDetail.locateEncryptedFileBtn=تحديد موقع الملف المشفر
main.vaultDetail.locateEncryptedFileBtn.tooltip=اختر ملف من خزانتك لتحديد مكان نظيره المشفر
main.vaultDetail.encryptedPathsCopied=تم نسخ مسارات الملفات إلى الحافظة!
main.vaultDetail.filePickerTitle=إختر الملف من الخزنة
### Missing
main.vaultDetail.missing.info=لم يتمكن Cryptomator من العثور على خزنة في هذا المسار.
main.vaultDetail.missing.recheck=إعادة الفحص
@@ -552,6 +553,10 @@ dokanySupportEnd.description=نوع وحدة التخزين Dokany لم يعد
dokanySupportEnd.preferencesBtn=فتح التفضيلات
#Retry If Readonly
retryIfReadonly.title=الوصول إلى المخزن المقيّد
retryIfReadonly.message=لا يوجد وصول للكتابة إلى مجلد المخزن
retryIfReadonly.description=Cryptomator لا يمكنه الكتابة إلى دليل المخزن. يمكنك تغيير المخزن ليكون للقراءة فقط وحاول مرة أخرى. يمكن تعطيل هذا الخيار في خيارات المخزن.
retryIfReadonly.retry=غيّر وأعد المحاولة
# Share Vault
shareVault.title=مشاركة الخزانة
@@ -571,4 +576,10 @@ shareVault.hub.message=كيفية مشاركة خزانة Hub
shareVault.hub.description=لمشاركة محتوى الخزانة مع عضو آخر في الفريق، عليك القيام بخطوتين:
shareVault.hub.instruction.1=1. شارك الوصول إلى مجلد الخزانة المشفر عبر التخزين السحابي.
shareVault.hub.instruction.2=2. امنح الوصول لعضو الفريق في Cryptomator Hub.
shareVault.hub.openHub=زيارة Cryptomator Hub
shareVault.hub.openHub=زيارة Cryptomator Hub
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Кире ҡаҡ
## Button
generic.button.apply=Ҡуллан
generic.button.back=Артҡа
@@ -269,7 +270,6 @@ preferences.title=Көйләүҙәр
## General
preferences.general=Дөйөм
preferences.general.startHidden=Cryptomator башланған саҡта тәҙрәне йәшерергә
preferences.general.autoCloseVaults=Ҡушымтанан сыҡҡан ваҡытта һаҡлағыстарҙы автоматик рәүештә бикләргә
preferences.general.debugLogging=Төҙөкләндереү журналын асырға
preferences.general.debugDirectory=Журнал файлдарын күрһәт
preferences.general.autoStart=Система стартында Cryptomator-ҙы эшләтеп ебәрергә
@@ -389,7 +389,6 @@ main.vaultDetail.stats=Һаҡлағыс статистикаһы
main.vaultDetail.locateEncryptedFileBtn=Шифрланған файлды тап
main.vaultDetail.locateEncryptedFileBtn.tooltip=Шифрланған аналогын табыр өсөн һаҡлағыстан файл һайлағыҙ
main.vaultDetail.encryptedPathsCopied=Юлдарҙын Clipboard-ҡа күсермәһе алынды!
main.vaultDetail.filePickerTitle=Һаҡлағыс эсендә файл һайлау
### Missing
main.vaultDetail.missing.info=Cryptomator был юлдан һаҡлағыс таба алманы.
main.vaultDetail.missing.recheck=Яңынан тикшер
@@ -518,3 +517,10 @@ dokanySupportEnd.preferencesBtn=Көйләүҙәрҙе ас
#Retry If Readonly
# Share Vault
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Адхіліць
## Button
generic.button.apply=Ужыць
generic.button.back=Назад
@@ -258,7 +259,6 @@ preferences.title=Налады
## General
preferences.general=Агульныя
preferences.general.startHidden=Хаваць акно пры запуску Cryptomator
preferences.general.autoCloseVaults=Замыкаць адчыненыя скарбніцы аўтаматычна пры выхадзе з праграмы
preferences.general.debugLogging=Уключыць пратакаляванне адладкі
preferences.general.debugDirectory=Паказаць файлы пратаколу
preferences.general.autoStart=Запускаць Cryptomator падчас запуску сістэмы
@@ -376,7 +376,6 @@ main.vaultDetail.stats=Статыстыка скарбніцы
main.vaultDetail.locateEncryptedFileBtn=Знайсці зашыфраваны файл
main.vaultDetail.locateEncryptedFileBtn.tooltip=Абяры файл у тваёй скрабніцы, каб знайсці ягоны зашыфраваны адпаведнік
main.vaultDetail.encryptedPathsCopied=Шлях скапіяваны ў буфер абмену!
main.vaultDetail.filePickerTitle=Абраць файл унутры скарбніцы
### Missing
main.vaultDetail.missing.info=Cryptomator ня змог знайсці скарбніцу па гэтай сцежцы.
main.vaultDetail.missing.recheck=Пераправерыць
@@ -498,3 +497,10 @@ dokanySupportEnd.preferencesBtn=Адчыніць налады
#Retry If Readonly
# Share Vault
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Отхвърляне
## Button
generic.button.apply=Прилагане
generic.button.back=Назад
@@ -269,11 +270,11 @@ preferences.title=Настройки
## General
preferences.general=Общи
preferences.general.startHidden=Скриване на прозореца при отваряне на Криптоматор
preferences.general.autoCloseVaults=Заключване на хранилищата при затваряне на приложението
preferences.general.debugLogging=Дневник за отстраняване на дефекти
preferences.general.debugDirectory=Файлове на дневниците
preferences.general.autoStart=Отваряне на Криптоматор при старт на системата
preferences.general.keychainBackend=Съхраняванеа паролите в
preferences.general.quickAccessService=Добавяне на отключените хранилища в зоната за бърз достъп
## Interface
preferences.interface=Външен вид
preferences.interface.theme=Тема
@@ -389,7 +390,6 @@ main.vaultDetail.stats=Статистики на хранилището
main.vaultDetail.locateEncryptedFileBtn=Намиране на шифровани файлове
main.vaultDetail.locateEncryptedFileBtn.tooltip=Изберете файл от хранилището, за да бъде намерено шифрованото му копие
main.vaultDetail.encryptedPathsCopied=Пътищата са копирани!
main.vaultDetail.filePickerTitle=Изберете файл от хранилището
### Missing
main.vaultDetail.missing.info=Криптоматор не намира хранилище на това място.
main.vaultDetail.missing.recheck=Повторен опит
@@ -518,3 +518,10 @@ dokanySupportEnd.preferencesBtn=Към настройките
#Retry If Readonly
# Share Vault
# Decrypt File Names
# Event View
## event list entries

View File

@@ -185,3 +185,10 @@ vaultOptions.mount.mountPoint.directoryPickerButton=নির্বাচন ক
#Retry If Readonly
# Share Vault
# Decrypt File Names
# Event View
## event list entries

View File

@@ -321,3 +321,10 @@ quit.lockAndQuitBtn=Zaključaj i zatvori
#Retry If Readonly
# Share Vault
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Descartar
## Button
generic.button.apply=Aplica
generic.button.back=Enrere
@@ -176,6 +177,7 @@ hub.registerFailed.description.generic=S'ha produït un error en el procés de r
hub.registerFailed.description.deviceAlreadyExists=El dispositiu ja ha estat registrat per un altre usuari. Mireu de canviar el compte d'usuari o feu servir un dispositiu diferent.
### Unauthorized
hub.unauthorized.message=Accés denegat
hub.unauthorized.description=No estàs autoritzat a obrir aquesta caixa forta. Contacta amb el seu propietari per obtenir accés.
### Requires Account Initialization
hub.requireAccountInit.message=Acció necessària
hub.requireAccountInit.description.0=Per a continuar, si us plau, seguiu els passos necessaris en el vostre
@@ -281,7 +283,7 @@ preferences.title=Preferències
## General
preferences.general=General
preferences.general.startHidden=Amaga la finestra quan s'inicia Cryptomator
preferences.general.autoCloseVaults=Bloquejar les caixes fortes automàticament quan surti de l'aplicació
preferences.general.autoCloseVaults=Bloca les caixes fortes sense preguntar-ho en sortir de l'aplicació
preferences.general.debugLogging=Habilita el registre de depuració
preferences.general.debugDirectory=Mostra els fitxers de registres
preferences.general.autoStart=Executa Cryptomator en engegar el sistema
@@ -340,6 +342,7 @@ preferences.contribute.sponsor=Patrocinador
### Remove License Key Dialog
removeCert.title=Suprimeix certificat
removeCert.message=Eliminar certificat de col·laborador?
removeCert.description=Les característiques principals de Cryptomator no es veuen afectades per això. Ni l'accés a les vostres caixes fortes s'ha restringit ni el nivell de seguretat ha estat rebaixat.
#<-- Add entries for donations and code/translation/documentation contribution -->
@@ -420,7 +423,6 @@ main.vaultDetail.stats=Estadístiques de la caixa forta
main.vaultDetail.locateEncryptedFileBtn=Trobar fitxer xifrat
main.vaultDetail.locateEncryptedFileBtn.tooltip=Esculli un fitxer de la caixa forta per trobar el seu homòleg xifrat
main.vaultDetail.encryptedPathsCopied=Rutes copiades al porta-retalls!
main.vaultDetail.filePickerTitle=Seleccioni un fitxer dins la caixa forta
### Missing
main.vaultDetail.missing.info=Cryptomator no ha trobat una caixa forta en aquesta ruta.
main.vaultDetail.missing.recheck=Torna a comprovar
@@ -545,6 +547,9 @@ updateReminder.yesOnce=Sí, una vegada
updateReminder.yesAutomatically=Sí, automàticament
#Dokany Support End
dokanySupportEnd.title=Avís d'obsolescència
dokanySupportEnd.message=Fi de la compatibilitat amb Dokany
dokanySupportEnd.description=El tipus de volum Dokany ja no és compatible amb Cryptomator. La teva configuració canviarà al tipus de volum predeterminat. Pots veure el tipus de volum predeterminat en la configuració.
dokanySupportEnd.preferencesBtn=Obrir les Preferències
#Retry If Readonly
@@ -567,4 +572,10 @@ shareVault.hub.message=Com compartir una caixa forta al Hub
shareVault.hub.description=Per tal de compartir el contingut de la caixa forta amb un altre membre de l'equip, heu de seguir dos passos:
shareVault.hub.instruction.1=1. Compartiu l'accés a la carpeta via emmagatzematge en el núvol.
shareVault.hub.instruction.2=2. Doneu accés al membre de l'equip a Cryptomator Hub.
shareVault.hub.openHub=Obre Cryptomator Hub
shareVault.hub.openHub=Obre Cryptomator Hub
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Zrušit
## Button
generic.button.apply=Použít
generic.button.back=Zpět
@@ -272,7 +273,6 @@ preferences.title=Nastavení
## General
preferences.general=Obecné
preferences.general.startHidden=Skrýt okno Cryptomatoru při spuštění
preferences.general.autoCloseVaults=Zamknout trezory automaticky při ukončení aplikace
preferences.general.debugLogging=Ladicí režim
preferences.general.debugDirectory=Ukázat soubory se záznamy událostí (log)
preferences.general.autoStart=Spustit Cryptomator při spuštění systému
@@ -390,7 +390,6 @@ main.vaultDetail.stats=Statistiky trezoru
main.vaultDetail.locateEncryptedFileBtn=Najít šifrovaný soubor
main.vaultDetail.locateEncryptedFileBtn.tooltip=Vyberte soubor z vašeho trezoru, abyste našli jeho šifrovaný protějšek
main.vaultDetail.encryptedPathsCopied=Cesta souboru byla zkopírována do schránky!
main.vaultDetail.filePickerTitle=Vyberte soubor uvnitř trezoru
### Missing
main.vaultDetail.missing.info=Cryptomator nemohl najít trezor na této cestě.
main.vaultDetail.missing.recheck=Znovu zkontrolovat
@@ -534,4 +533,10 @@ shareVault.hub.message=Jak sdílet Hub trezor
shareVault.hub.description=Chcete-li sdílet obsah trezoru s jiným členem týmu, musíte provést dva kroky:
shareVault.hub.instruction.1=1. Sdílejte přístup ke šifrované složce trezoru prostřednictvím cloudového úložiště.
shareVault.hub.instruction.2=2. Udělte přístup členovi týmu v Cryptomator Hubu.
shareVault.hub.openHub=Otevřít Cryptomator Hub
shareVault.hub.openHub=Otevřít Cryptomator Hub
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Luk
## Button
generic.button.apply=Anvend
generic.button.back=Tilbage
@@ -282,7 +283,6 @@ preferences.title=Præferencer
## General
preferences.general=Generelt
preferences.general.startHidden=Skjul vinduet når Cryptomator starter
preferences.general.autoCloseVaults=Lås åbne bokse automatisk, når programmet afsluttes
preferences.general.debugLogging=Aktivér fejllogning
preferences.general.debugDirectory=Vis logfiler
preferences.general.autoStart=Start Cryptomator automatisk ved opstart
@@ -407,7 +407,6 @@ main.vaultDetail.stats=Boks statistik
main.vaultDetail.locateEncryptedFileBtn=Find Krypteret Fil
main.vaultDetail.locateEncryptedFileBtn.tooltip=Vælg en fil fra din boks for at finde dens krypterede modpart
main.vaultDetail.encryptedPathsCopied=Stier kopieret!
main.vaultDetail.filePickerTitle=Vælg fil inde i boks
### Missing
main.vaultDetail.missing.info=Cryptomator kunne ikke finde en boks på denne sti.
main.vaultDetail.missing.recheck=Kontrollér igen
@@ -554,4 +553,10 @@ shareVault.hub.message=Sådan deler du en Hub boks
shareVault.hub.description=For at dele indholdet i boksen med et andet holdmedlem skal du udføre to trin:
shareVault.hub.instruction.1=1. Del adgang til den krypterede boks-mappe vha. opbevaring i skyen.
shareVault.hub.instruction.2=2. Giv adgang til holdmedlem i Cryptomator Hub.
shareVault.hub.openHub=Åben Cryptomator Hub
shareVault.hub.openHub=Åben Cryptomator Hub
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Verwerfen
## Button
generic.button.apply=Übernehmen
generic.button.back=Zurück
@@ -176,6 +177,7 @@ hub.registerFailed.description.generic=Im Registrierungsprozess ist ein Fehler a
hub.registerFailed.description.deviceAlreadyExists=Dieses Gerät ist bereits für einen anderen Benutzer registriert. Ändere das Benutzerkonto oder verwende ein anderes Gerät.
### Unauthorized
hub.unauthorized.message=Zugriff verweigert
hub.unauthorized.description=Du bist nicht berechtigt, diesen Tresor zu öffnen. Wende dich an den Tresoreigentümer, um Zugriff zu erhalten.
### Requires Account Initialization
hub.requireAccountInit.message=Aktion erforderlich
hub.requireAccountInit.description.0=Um fortzufahren, führe bitte die erforderlichen Schritte in deinem
@@ -393,6 +395,7 @@ main.vaultlist.contextMenu.vaultoptions=Tresoroptionen anzeigen
main.vaultlist.contextMenu.reveal=Laufwerk anzeigen
main.vaultlist.addVaultBtn.menuItemNew=Neuen Tresor erstellen...
main.vaultlist.addVaultBtn.menuItemExisting=Bestehenden Tresor öffnen...
main.vaultlist.showEventsButton.tooltip=Ereignis-Ansicht öffnen
##Notificaition
main.notification.updateAvailable=Eine neue Version ist verfügbar.
main.notification.support=Unterstütze Cryptomator.
@@ -421,7 +424,9 @@ main.vaultDetail.stats=Tresorstatistik
main.vaultDetail.locateEncryptedFileBtn=Verschlüsselte Datei finden
main.vaultDetail.locateEncryptedFileBtn.tooltip=Wähle eine Datei aus deinem Tresor aus, um das verschlüsselte Gegenstück zu finden
main.vaultDetail.encryptedPathsCopied=Pfade in Zwischenablage kopiert!
main.vaultDetail.filePickerTitle=Datei im Tresor auswählen
main.vaultDetail.locateEncrypted.filePickerTitle=Datei im Tresor auswählen
main.vaultDetail.decryptName.buttonLabel=Dateiname entschlüsseln
main.vaultDetail.decryptName.tooltip=Wählen eine verschlüsselte Tresordatei, um ihren Namen zu entschlüsseln
### Missing
main.vaultDetail.missing.info=Cryptomator konnte keinen Tresor mit diesem Pfad finden.
main.vaultDetail.missing.recheck=Erneut prüfen
@@ -552,6 +557,10 @@ dokanySupportEnd.description=Der Laufwerkstyp Dokany wird von Cryptomator nicht
dokanySupportEnd.preferencesBtn=Einstellungen öffnen
#Retry If Readonly
retryIfReadonly.title=Eingeschränkter Tresorzugriff
retryIfReadonly.message=Kein Schreibzugriff auf Tresor
retryIfReadonly.description=Cryptomator kann nicht in das Verzeichnis des Tresors schreiben. Du kannst den Tresor auf schreibgeschützt umstellen und es erneut versuchen. Diese Option kann in den Tresoroptionen deaktiviert werden.
retryIfReadonly.retry=Ändern und wiederholen
# Share Vault
shareVault.title=Tresor teilen
@@ -571,4 +580,43 @@ shareVault.hub.message=Teilen eines Hub-Tresors
shareVault.hub.description=Zur Freigabe des Tresorinhalts für ein anderes Teammitglied sind zwei Schritte erforderlich:
shareVault.hub.instruction.1=1. Teile den Zugriff auf den verschlüsselten Tresorordner über den Cloud-Speicher.
shareVault.hub.instruction.2=2. Füge das Teammitglied in Cryptomator Hub als Tresormitglied hinzu.
shareVault.hub.openHub=Cryptomator Hub öffnen
shareVault.hub.openHub=Cryptomator Hub öffnen
# Decrypt File Names
decryptNames.title=Dateinamen entschlüsseln
decryptNames.filePicker.title=Verschlüsselte Datei auswählen
decryptNames.filePicker.extensionDescription=Cryptomator-verschlüsselte Datei
decryptNames.copyTable.tooltip=Tabelle kopieren
decryptNames.clearTable.tooltip=Tabelle leeren
decryptNames.copyHint=Inhalt der Zelle mit %s kopieren
decryptNames.dropZone.message=Dateien ablegen oder zum Auswählen klicken
decryptNames.dropZone.error.vaultInternalFiles=Tresor interne Dateien mit nicht-entschlüsselbaren Namen ausgewählt
decryptNames.dropZone.error.foreignFiles=Dateien gehören nicht zum Tresor "%s"
decryptNames.dropZone.error.noDirIdBackup=Verzeichnis der ausgewählten Dateien enthält keine dirId.c9r Datei
decryptNames.dropZone.error.generic=Fehler beim Entschlüsseln der Dateinamen
# Event View
eventView.title=Ereignisse
eventView.filter.allVaults=Alle
eventView.clearListButton.tooltip=Liste leeren
## event list entries
eventView.entry.vaultLocked.description=Entsperre "%s" für Details
eventView.entry.conflictResolved.message=Konflikt gelöst
eventView.entry.conflictResolved.showDecrypted=Entschlüsselte Datei anzeigen
eventView.entry.conflictResolved.copyDecrypted=Pfad der entschlüsselten Datei kopieren
eventView.entry.conflict.message=Konfliktlösung fehlgeschlagen
eventView.entry.conflict.showDecrypted=Entschlüsselte, ursprüngliche Datei anzeigen
eventView.entry.conflict.copyDecrypted=Entschlüsselten, ursprünglicher Pfad kopieren
eventView.entry.conflict.showEncrypted=Zeige verschlüsselte Konfliktdatei
eventView.entry.conflict.copyEncrypted=Verschlüsselten Konfliktpfad kopieren
eventView.entry.decryptionFailed.message=Entschlüsselung fehlgeschlagen
eventView.entry.decryptionFailed.showEncrypted=Verschlüsselte Datei anzeigen
eventView.entry.decryptionFailed.copyEncrypted=Pfad der verschlüsselten Datei kopieren
eventView.entry.brokenDirFile.message=Ungültiger Verzeichnislink
eventView.entry.brokenDirFile.showEncrypted=Defekten, verschlüsselten Link anzeigen
eventView.entry.brokenDirFile.copyEncrypted=Pfad des ungültigen Links kopieren
eventView.entry.brokenFileNode.message=Fehlender Dateisystemknoten
eventView.entry.brokenFileNode.showEncrypted=Beschädigten, verschlüsselten Knoten anzeigen
eventView.entry.brokenFileNode.copyEncrypted=Pfad des kaputten, verschlüsselten Knotens kopieren
eventView.entry.brokenFileNode.copyDecrypted=Pfad der entschlüsselten Datei kopieren

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Παράβλεψη
## Button
generic.button.apply=Εφαρμογή
generic.button.back=Πίσω
@@ -282,7 +283,7 @@ preferences.title=Προτιμήσεις
## General
preferences.general=Γενικά
preferences.general.startHidden=Απόκρυψη παραθύρου όταν ξεκινά το Cryptomator
preferences.general.autoCloseVaults=Αυτόματο κλείδωμα ανοιγμένης κρύπτης κατά την έξοδο της εφαρμογής
preferences.general.autoCloseVaults=Κλείδωμα κρυπτών χωρίς ερώτηση κατά την έξοδο από την εφαρμογή
preferences.general.debugLogging=Ενεργοποίηση καταγραφής σφαλμάτων
preferences.general.debugDirectory=Αποκάλυψη αρχείων καταγραφής
preferences.general.autoStart=Εκκίνηση Cryptomator στην εκκίνηση του συστήματος
@@ -422,7 +423,6 @@ main.vaultDetail.stats=Στατιστικά Vault
main.vaultDetail.locateEncryptedFileBtn=Εντοπισμός Κρυπτογραφημένου Αρχείου
main.vaultDetail.locateEncryptedFileBtn.tooltip=Επιλέξτε ένα αρχείο από την κρύπτη σας για να εντοπίσετε το κρυπτογραφημένο αντίστοιχο
main.vaultDetail.encryptedPathsCopied=Οι Διαδρομές Αντιγράφηκαν στο Πρόχειρο!
main.vaultDetail.filePickerTitle=Επιλογή Αρχείου Μέσα Στην Κρύπτη
### Missing
main.vaultDetail.missing.info=Cryptomator δεν βρήκε vault σε αυτόν τον κατάλογο.
main.vaultDetail.missing.recheck=Επανέλεγχος
@@ -576,4 +576,11 @@ shareVault.hub.message=Πώς να μοιραστείτε μια κρύπτη Hu
shareVault.hub.description=Για να μοιραστείτε το περιεχόμενο της κρύπτης με άλλο μέλος της ομάδας, πρέπει να εκτελέσετε δύο βήματα:
shareVault.hub.instruction.1=1. Μοιραστείτε την πρόσβαση στον κρυπτογραφημένο φάκελο κρύπτης μέσω του χώρου αποθήκευσης στο cloud.
shareVault.hub.instruction.2=2. Παραχωρήστε πρόσβαση σε μέλος της ομάδας στο Cryptomator Hub.
shareVault.hub.openHub=Ανοίξτε το Cryptomator Hub
shareVault.hub.openHub=Ανοίξτε το Cryptomator Hub
# Decrypt File Names
# Event View
eventView.filter.allVaults=Όλες
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Descartar
## Button
generic.button.apply=Aplicar
generic.button.back=Volver
@@ -282,7 +283,7 @@ preferences.title=Preferencias
## General
preferences.general=General
preferences.general.startHidden=Ocultar ventana al iniciar Cryptomator
preferences.general.autoCloseVaults=Bloquear automáticamente las bóvedas abiertas al salir de la aplicación
preferences.general.autoCloseVaults=Bloquear bóvedas sin preguntar al salir de la aplicación
preferences.general.debugLogging=Habilitar registro de depuración
preferences.general.debugDirectory=Revelar archivos de registro
preferences.general.autoStart=Cargar Cryptomator al iniciar el sistema
@@ -394,6 +395,7 @@ main.vaultlist.contextMenu.vaultoptions=Mostrar opciones de la bóveda
main.vaultlist.contextMenu.reveal=Revelar unidad
main.vaultlist.addVaultBtn.menuItemNew=Crear Bóveda Nueva...
main.vaultlist.addVaultBtn.menuItemExisting=Abrir Bóveda Existente...
main.vaultlist.showEventsButton.tooltip=Abrir vista de evento
##Notificaition
main.notification.updateAvailable=Existen actualizaciones disponibles.
main.notification.support=Soporte de Cryptomator.
@@ -422,7 +424,9 @@ main.vaultDetail.stats=Estadísticas de la bóveda
main.vaultDetail.locateEncryptedFileBtn=Ubicar archivo cifrado
main.vaultDetail.locateEncryptedFileBtn.tooltip=Elija un archivo de su bóveda para ubicar su contraparte cifrada
main.vaultDetail.encryptedPathsCopied=¡Rutas copiadas al portapapeles!
main.vaultDetail.filePickerTitle=Seleccionar archivo dentro de la bóveda
main.vaultDetail.locateEncrypted.filePickerTitle=Seleccionar archivo dentro de la bóveda
main.vaultDetail.decryptName.buttonLabel=Descifrar nombre de archivo
main.vaultDetail.decryptName.tooltip=Elija un archivo de bóveda cifrado para descifrar su nombre
### Missing
main.vaultDetail.missing.info=Cryptomator no pudo encontrar una bóveda en esta ruta.
main.vaultDetail.missing.recheck=Volver a comprobar
@@ -576,4 +580,43 @@ shareVault.hub.message=Cómo compartir una bóveda de Hub
shareVault.hub.description=Para compartir el contenido de la bóveda con otro miembro del equipo, tiene que realizar dos pasos:
shareVault.hub.instruction.1=1. Comparta el acceso a la carpeta de la bóveda cifrada a través del almacenamiento en la nube.
shareVault.hub.instruction.2=2. Conceda el acceso al miembro del equipo en Cryptomator Hub.
shareVault.hub.openHub=Abrir Cryptomator Hub
shareVault.hub.openHub=Abrir Cryptomator Hub
# Decrypt File Names
decryptNames.title=Descifrar nombre de archivos
decryptNames.filePicker.title=Seleccione archivo cifrado
decryptNames.filePicker.extensionDescription=Archivo cifrado Cryptomator
decryptNames.copyTable.tooltip=Copiar tabla
decryptNames.clearTable.tooltip=Limpiar tabla
decryptNames.copyHint=Copiar contenido de celda con %s
decryptNames.dropZone.message=Soltar archivos o hacer clic para seleccionar
decryptNames.dropZone.error.vaultInternalFiles=Archivos internos de la bóveda sin nombre descifrable seleccionado
decryptNames.dropZone.error.foreignFiles=Los archivos no pertenecen a la bóveda "%s"
decryptNames.dropZone.error.noDirIdBackup=El directorio de los archivos seleccionados no contiene el archivo dirId.c9r
decryptNames.dropZone.error.generic=Error al descifrar nombre de archivos
# Event View
eventView.title=Eventos
eventView.filter.allVaults=Todos
eventView.clearListButton.tooltip=Borrar lista
## event list entries
eventView.entry.vaultLocked.description=Desbloquear "%s" para más detalles
eventView.entry.conflictResolved.message=Conflicto resuelto
eventView.entry.conflictResolved.showDecrypted=Mostrar archivo descifrado
eventView.entry.conflictResolved.copyDecrypted=Copiar ruta descifrada
eventView.entry.conflict.message=Resolución de conflictos fallida
eventView.entry.conflict.showDecrypted=Mostrar archivo descifrado, original
eventView.entry.conflict.copyDecrypted=Copiar ruta descifrada, original
eventView.entry.conflict.showEncrypted=Mostrar archivo en conflicto, cifrado
eventView.entry.conflict.copyEncrypted=Copiar ruta en conflicto, cifrada
eventView.entry.decryptionFailed.message=Desencriptación fallida
eventView.entry.decryptionFailed.showEncrypted=Mostrar archivo cifrado
eventView.entry.decryptionFailed.copyEncrypted=Copiar ruta cifrada
eventView.entry.brokenDirFile.message=Enlace de directorio roto
eventView.entry.brokenDirFile.showEncrypted=Mostrar enlace roto, cifrado
eventView.entry.brokenDirFile.copyEncrypted=Copiar ruta del enlace roto
eventView.entry.brokenFileNode.message=Nodo de sistema de archivos roto
eventView.entry.brokenFileNode.showEncrypted=Mostrar nodo roto, cifrado
eventView.entry.brokenFileNode.copyEncrypted=Copiar ruta del enlace roto, encriptado
eventView.entry.brokenFileNode.copyDecrypted=Copiar ruta descifrada

View File

@@ -1,8 +1,9 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=لغو
## Button
generic.button.apply=درخواست
generic.button.apply=اعمال
generic.button.back=بازگشت
generic.button.cancel=انصراف
generic.button.change=تغییر
@@ -106,11 +107,14 @@ addvaultwizard.existing.chooseBtn=انتخاب کنید…
unlock.unlockBtn=بازکردن قفل
## Select
## Success
unlock.success.revealBtn=نمایش درایو
## Failure
## Hub
hub.noKeychain.openBtn=باز کردن تنظیمات
### Waiting
### Receive Key
### Register Device
hub.register.message=دستگاه جدید
### Register Device Legacy
### Registration Success
hub.registerSuccess.unlockBtn=بازکردن قفل
@@ -170,11 +174,13 @@ preferences.updates.upToDate=Cryptomator به روز می باشد.
# Main Window
## Vault List
main.vaultlist.contextMenu.lock=قفل
main.vaultlist.contextMenu.reveal=نمایش درایو
##Notificaition
## Vault Detail
### Welcome
### Locked
### Unlocked
main.vaultDetail.revealBtn=نمایش درایو
main.vaultDetail.lockBtn=قفل
### Missing
### Needs Migration
@@ -185,6 +191,7 @@ main.vaultDetail.lockBtn=قفل
# Vault Options
## General
vaultOptions.general.vaultName=نام گاوصندوق
vaultOptions.general.actionAfterUnlock.reveal=نمایش درایو
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=انتخاب کنید…
@@ -209,7 +216,15 @@ vaultOptions.mount.mountPoint.directoryPickerButton=انتخاب کنید…
# Update Reminder
#Dokany Support End
dokanySupportEnd.preferencesBtn=باز کردن تنظیمات
#Retry If Readonly
# Share Vault
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Ohita
## Button
generic.button.apply=Käytä
generic.button.back=Takaisin
@@ -281,7 +282,6 @@ preferences.title=Asetukset
## General
preferences.general=Yleiset
preferences.general.startHidden=Piilota ikkuna kun Cryptomator käynnistyy
preferences.general.autoCloseVaults=Lukitse avoimet holvit automaattisesti kun ohjelma sammutetaan
preferences.general.debugLogging=Ota virheloki käyttöön
preferences.general.debugDirectory=Näytä lokitiedostot
preferences.general.autoStart=Käynnistä Cryptomator järjestelmän käynnistyessä
@@ -419,7 +419,6 @@ main.vaultDetail.stats=Holvin tilastot
main.vaultDetail.locateEncryptedFileBtn=Etsi salattu tiedosto
main.vaultDetail.locateEncryptedFileBtn.tooltip=Valitse tiedosto holvistasi löytääksesi sen salatun vastineen
main.vaultDetail.encryptedPathsCopied=Polut kopioitu leikepöydälle!
main.vaultDetail.filePickerTitle=Valitse tiedosto holvin sisältä
### Missing
main.vaultDetail.missing.info=Cryptomator ei löytänyt täältä holvia.
main.vaultDetail.missing.recheck=Tarkista uudelleen
@@ -559,4 +558,10 @@ shareVault.visitHub=Käy Cryptomator Hubissa
shareVault.hub.message=Kuinka jakaa Hub -holvi
shareVault.hub.instruction.1=1. Jaa käyttöoikeus salattuun holvikansioon pilvipalvelun kautta.
shareVault.hub.openHub=Avaa Cryptomator Hub
shareVault.hub.openHub=Avaa Cryptomator Hub
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=I-dismiss
## Button
generic.button.apply=I-apply
generic.button.back=Bumalik
@@ -281,7 +282,6 @@ preferences.title=Mga Kagustuhan
## General
preferences.general=Heneral
preferences.general.startHidden=Itago ang window kapag sinimulan ang Cryptomator
preferences.general.autoCloseVaults=Awtomatikong i-lock ang mga bukas na vault kapag huminto sa aplikasyon
preferences.general.debugLogging=Paganahin ang pag-log ng debug
preferences.general.debugDirectory=Magbunyag ng mga log file
preferences.general.autoStart=Ilunsad ang Cryptomator sa pagsisimula ng system
@@ -410,7 +410,6 @@ main.vaultDetail.stats=Mga Istatistika ng Vault
main.vaultDetail.locateEncryptedFileBtn=Hanapin ang Naka-encrypt na File
main.vaultDetail.locateEncryptedFileBtn.tooltip=Pumili ng file mula sa iyong vault upang mahanap ang naka-encrypt na katapat nito
main.vaultDetail.encryptedPathsCopied=Mga Path na Nakopya sa Clipboard!
main.vaultDetail.filePickerTitle=Piliin ang File Inside Vault
### Missing
main.vaultDetail.missing.info=Hindi makahanap ng vault ang Cryptomator sa landas na ito.
main.vaultDetail.missing.recheck=Suriin muli
@@ -560,4 +559,10 @@ shareVault.hub.message=Paano ibahagi ang Hub vault
shareVault.hub.description=Upang maibahagi ang nilalaman ng vault sa isa pang miyembro ng koponan, kailangan mong magsagawa ng dalawang hakbang:
shareVault.hub.instruction.1=1. Ibahagi ang access ng naka-encrypt na folder ng vault sa pamamagitan ng cloud storage.
shareVault.hub.instruction.2=2. Magbigay ng access sa miyembro ng team sa Cryptomator Hub.
shareVault.hub.openHub=Buksan ang Cryptomator Hub
shareVault.hub.openHub=Buksan ang Cryptomator Hub
# Decrypt File Names
# Event View
## event list entries

View File

@@ -1,6 +1,7 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
generic.action.dismiss=Ignorer
## Button
generic.button.apply=Appliquer
generic.button.back=Précédent
@@ -282,7 +283,7 @@ preferences.title=Préférences
## General
preferences.general=Général
preferences.general.startHidden=Démarrer Cryptomator en mode caché
preferences.general.autoCloseVaults=Verrouiller automatiquement les coffres ouverts en quittant l'application
preferences.general.autoCloseVaults=Verrouiller les coffres sans confirmation lors de la fermeture de l'application
preferences.general.debugLogging=Activer les logs debug
preferences.general.debugDirectory=Afficher le journal
preferences.general.autoStart=Lancer Cryptomator au démarrage du système
@@ -342,7 +343,7 @@ preferences.contribute.sponsor=Parrain
### Remove License Key Dialog
removeCert.title=Supprimer le certificat
removeCert.message=Supprimer le certificat de soutien ?
removeCert.description=
removeCert.description=Les fonctionnalités principales de Cryptomator ne sont pas affectées par cela. L'accès à vos coffres n'est pas restreint et le niveau de sécurité n'est pas diminué.
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -394,6 +395,7 @@ main.vaultlist.contextMenu.vaultoptions=Afficher les options du volume chiffré
main.vaultlist.contextMenu.reveal=Afficher le lecteur
main.vaultlist.addVaultBtn.menuItemNew=Créer un nouveau coffre...
main.vaultlist.addVaultBtn.menuItemExisting=Ouvrir un coffre existant...
main.vaultlist.showEventsButton.tooltip=Ouvrir la vue Événements
##Notificaition
main.notification.updateAvailable=Mise à jour disponible.
main.notification.support=Soutenir Cryptomator.
@@ -422,7 +424,9 @@ main.vaultDetail.stats=Statistiques du volume chiffré
main.vaultDetail.locateEncryptedFileBtn=Localiser le fichier chiffré
main.vaultDetail.locateEncryptedFileBtn.tooltip=Choisissez un fichier dans votre coffre pour localiser sa version chiffrée
main.vaultDetail.encryptedPathsCopied=Chemins d'accès copiés dans le presse-papier !
main.vaultDetail.filePickerTitle=Sélectionner le fichier dans le coffre
main.vaultDetail.locateEncrypted.filePickerTitle=Sélectionner le fichier dans le coffre
main.vaultDetail.decryptName.buttonLabel=Déchiffrer le nom d'un fichier
main.vaultDetail.decryptName.tooltip=Choisir un fichier de coffre chiffré pour déchiffrer son nom
### Missing
main.vaultDetail.missing.info=Cryptomator n'a pas pu trouver de volume chiffré dans ce chemin d'accès.
main.vaultDetail.missing.recheck=Revérifier
@@ -576,4 +580,42 @@ shareVault.hub.message=Comment partager un coffre central
shareVault.hub.description=Afin de partager le contenu du coffre avec un autre membre de l'équipe, vous devez effectuer deux étapes :
shareVault.hub.instruction.1=1. Partagez l'accès du dossier de coffre chiffré via le stockage cloud.
shareVault.hub.instruction.2=2. Accorder l'accès au membre de l'équipe dans Cryptomator Hub.
shareVault.hub.openHub=Ouvrir le Hub Cryptomator
shareVault.hub.openHub=Ouvrir le Hub Cryptomator
# Decrypt File Names
decryptNames.title=Déchiffrer les noms de fichiers
decryptNames.filePicker.title=Sélectionner le fichier chiffré
decryptNames.filePicker.extensionDescription=Fichier chiffré Cryptomator
decryptNames.copyTable.tooltip=Copier le tableau
decryptNames.clearTable.tooltip=Effacer le tableau
decryptNames.copyHint=Copier le contenu de la cellule avec %s
decryptNames.dropZone.message=Déposer des fichiers ou cliquer pour en sélectionner
decryptNames.dropZone.error.foreignFiles=Les fichiers n'appartiennent pas au coffre « %s »
decryptNames.dropZone.error.noDirIdBackup=Le répertoire des fichiers sélectionnés ne contient pas de fichier dirId.c9r
decryptNames.dropZone.error.generic=Impossible de déchiffrer les noms de fichiers
# Event View
eventView.title=Événements
eventView.filter.allVaults=Tous
eventView.clearListButton.tooltip=Effacer la liste
## event list entries
eventView.entry.vaultLocked.description=Déverrouillez "%s" pour plus de détails
eventView.entry.conflictResolved.message=Conflit résolu
eventView.entry.conflictResolved.showDecrypted=Afficher le fichier déchiffré
eventView.entry.conflictResolved.copyDecrypted=Copier le chemin déchiffré
eventView.entry.conflict.message=La résolution des conflits a échoué
eventView.entry.conflict.showDecrypted=Afficher le fichier original déchiffré
eventView.entry.conflict.copyDecrypted=Copier le chemin original déchiffré
eventView.entry.conflict.showEncrypted=Afficher le fichier chiffré en conflit
eventView.entry.conflict.copyEncrypted=Copier le chemin chiffré en conflit
eventView.entry.decryptionFailed.message=Le déchiffrement a échoué
eventView.entry.decryptionFailed.showEncrypted=Afficher le fichier chiffré
eventView.entry.decryptionFailed.copyEncrypted=Copier le chemin chiffré
eventView.entry.brokenDirFile.message=Lien de répertoire cassé
eventView.entry.brokenDirFile.showEncrypted=Afficher le lien chiffré cassé
eventView.entry.brokenDirFile.copyEncrypted=Copier le chemin du lien cassé
eventView.entry.brokenFileNode.message=Nœud de système de fichiers cassé
eventView.entry.brokenFileNode.showEncrypted=Afficher le nœud chiffré cassé
eventView.entry.brokenFileNode.copyEncrypted=Copier le chemin du nœud chiffré cassé
eventView.entry.brokenFileNode.copyDecrypted=Copier le chemin déchiffré

View File

@@ -135,3 +135,10 @@ lock.forced.retryBtn=Tentar de novo
#Retry If Readonly
# Share Vault
# Decrypt File Names
# Event View
## event list entries

Some files were not shown because too many files have changed in this diff Show More