Compare commits

..
Author SHA1 Message Date
Armin Schrenk c0f3facab8 correct typos 2025-03-21 15:45:04 +01:00
Armin Schrenk b9512c9e93 cleanup 2025-03-21 15:37:04 +01:00
Armin Schrenk dd724220f8 enable clear() again 2025-03-21 15:31:58 +01:00
Armin Schrenk 94410f1839 more renaming 2025-03-21 15:24:40 +01:00
Armin Schrenk cc8aa00326 Renamed classes 2025-03-21 15:03:31 +01:00
Armin Schrenk e8e2fcb0b3 rename EventRegistry to Aggregator 2025-03-21 14:51:53 +01:00
Armin Schrenk 151b35355d no size restriction/lru needed 2025-03-21 14:49:19 +01:00
Armin Schrenk 37d5353f77 run the eventUpdate from app start 2025-03-21 13:46:20 +01:00
Armin Schrenk 7b6953445f remove comments 2025-03-21 13:45:59 +01:00
Armin Schrenk fcd3db63ce use a preemptive update status to not miss updates 2025-03-21 13:38:00 +01:00
Armin Schrenk d67085d57d doodles 2025-03-20 16:41:30 +01:00
Armin Schrenk 1f60d9f5e8 remove notification, if eventView is focused 2025-03-19 15:13:51 +01:00
Armin Schrenk 5378769467 adjust update-indicator 2025-03-19 15:12:40 +01:00
Armin Schrenk 913ed5e109 removed unused class 2025-03-19 14:53:24 +01:00
Armin Schrenk f4cfe19fdc more cleanup 2025-03-19 12:41:45 +01:00
Armin Schrenk a001dfd8a8 add equals to FileSystemEventBucket to ensure correct removal in collections 2025-03-19 12:30:23 +01:00
Armin Schrenk 893a4bcae9 the great rename 2025-03-19 12:25:26 +01:00
Armin Schrenk e61fb74367 removed unused classes 2025-03-19 12:06:02 +01:00
Armin Schrenk fbbbc1cb40 cleanup 2025-03-19 11:58:04 +01:00
Armin Schrenk f27f3c46c1 decouple eventMap insertion from event happening (to not spam FX thread) 2025-03-19 11:55:29 +01:00
Armin Schrenk 83b557b6be removed dead code 2025-03-18 18:02:55 +01:00
Armin Schrenk 32a0df06d0 move eventMap to event package 2025-03-18 18:02:46 +01:00
Armin Schrenk 5350e07f62 renamed VaultEventsMap.Key parameters for clarity 2025-03-18 17:57:15 +01:00
Armin Schrenk cc5c46743b Refactor EventMap:
* renamed to VaultEventsMap
* split between boilerplate and buissness logic (ObservableMapDecorator)
* add LRU cache
* fixed VaultEvent compareTo method
2025-03-18 17:55:32 +01:00
Armin Schrenk bc7f3fe7db more renaming 2025-03-18 12:17:39 +01:00
Armin Schrenk 750ca3f39c also key the eventMap for vault 2025-03-18 12:13:25 +01:00
Armin Schrenk 2bbffc3623 rename eventMap to make purpose clearer 2025-03-18 12:10:44 +01:00
36 changed files with 443 additions and 707 deletions
@@ -1,16 +1,12 @@
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg"> <svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
<defs> <style
<style id="current-color-scheme" type="text/css"> id="current-color-scheme" type="text/css">
.ColorScheme-Text { .ColorScheme-Text {
color:#222222; color:#232629;
} }
.ColorScheme-Highlight { </style>
color:#49B04A; <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"/>
</style> <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>
<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> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

+6 -8
View File
@@ -1,10 +1,8 @@
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg"> <svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
<defs> <style id="current-color-scheme" type="text/css">
<style id="current-color-scheme" type="text/css"> .ColorScheme-Text {
.ColorScheme-Text { color:#232629;
color:#222222; }
} </style>
</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>
<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> </svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

+14 -20
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId> <artifactId>cryptomator</artifactId>
<version>1.16.0</version> <version>1.16.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name> <name>Cryptomator Desktop App</name>
<organization> <organization>
@@ -34,24 +34,24 @@
<!-- cryptomator dependencies --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.9.0-beta2</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>2.9.0-beta2</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.5.1</cryptomator.integrations.version> <cryptomator.integrations.version>1.5.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version> <cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.3.0</cryptomator.integrations.mac.version> <cryptomator.integrations.mac.version>1.2.4</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.5.3</cryptomator.integrations.linux.version> <cryptomator.integrations.linux.version>1.5.2</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>5.0.3</cryptomator.fuse.version> <cryptomator.fuse.version>5.0.2</cryptomator.fuse.version>
<cryptomator.webdav.version>2.0.8</cryptomator.webdav.version> <cryptomator.webdav.version>2.0.7</cryptomator.webdav.version>
<!-- 3rd party dependencies --> <!-- 3rd party dependencies -->
<commons-lang3.version>3.17.0</commons-lang3.version> <commons-lang3.version>3.17.0</commons-lang3.version>
<dagger.version>2.55</dagger.version> <dagger.version>2.55</dagger.version>
<easybind.version>2.2</easybind.version> <easybind.version>2.2</easybind.version>
<jackson.version>2.18.3</jackson.version> <jackson.version>2.18.2</jackson.version>
<javafx.version>23.0.2</javafx.version> <javafx.version>23.0.1</javafx.version>
<jwt.version>4.5.0</jwt.version> <jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.37.3</nimbus-jose.version> <nimbus-jose.version>9.37.3</nimbus-jose.version>
<logback.version>1.5.17</logback.version> <logback.version>1.5.16</logback.version>
<slf4j.version>2.0.17</slf4j.version> <slf4j.version>2.0.16</slf4j.version>
<tinyoauth2.version>0.8.1</tinyoauth2.version> <tinyoauth2.version>0.8.0</tinyoauth2.version>
<zxcvbn.version>1.9.0</zxcvbn.version> <zxcvbn.version>1.9.0</zxcvbn.version>
<!-- test dependencies --> <!-- test dependencies -->
@@ -60,7 +60,7 @@
<hamcrest.version>3.0</hamcrest.version> <hamcrest.version>3.0</hamcrest.version>
<!-- build-time dependencies --> <!-- build-time dependencies -->
<jetbrains.annotations.version>26.0.2</jetbrains.annotations.version> <jetbrains.annotations.version>26.0.1</jetbrains.annotations.version>
<dependency-check.version>12.1.0</dependency-check.version> <dependency-check.version>12.1.0</dependency-check.version>
<jacoco.version>0.8.12</jacoco.version> <jacoco.version>0.8.12</jacoco.version>
<license-generator.version>2.5.0</license-generator.version> <license-generator.version>2.5.0</license-generator.version>
@@ -80,7 +80,7 @@
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>cryptolib</artifactId> <artifactId>cryptolib</artifactId>
<version>2.2.1</version> <version>2.2.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
@@ -159,12 +159,6 @@
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId> <artifactId>java-jwt</artifactId>
<version>${jwt.version}</version> <version>${jwt.version}</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.nimbusds</groupId> <groupId>com.nimbusds</groupId>
-1
View File
@@ -59,7 +59,6 @@ open module org.cryptomator.desktop {
uses org.cryptomator.common.locationpresets.LocationPresetsProvider; uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
uses SSLContextProvider; uses SSLContextProvider;
uses org.cryptomator.event.NotificationHandler;
provides TrayMenuController with AwtTrayMenuController; provides TrayMenuController with AwtTrayMenuController;
provides Configurator with LogbackConfiguratorFactory; provides Configurator with LogbackConfiguratorFactory;
@@ -1,22 +0,0 @@
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);
}
}
@@ -1,160 +0,0 @@
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());
}
}
@@ -2,7 +2,6 @@ package org.cryptomator.common.keychain;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
import org.cryptomator.common.Passphrase;
import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider; import org.cryptomator.integrations.keychain.KeychainAccessProvider;
@@ -14,24 +13,20 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Singleton @Singleton
public class KeychainManager implements KeychainAccessProvider { public class KeychainManager implements KeychainAccessProvider {
private final ObjectExpression<KeychainAccessProvider> keychain; private final ObjectExpression<KeychainAccessProvider> keychain;
private final LoadingCache<String, BooleanProperty> passphraseStoredProperties; private final LoadingCache<String, BooleanProperty> passphraseStoredProperties;
private final ReentrantReadWriteLock lock;
@Inject @Inject
KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) { KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
this.keychain = selectedKeychain; this.keychain = selectedKeychain;
this.passphraseStoredProperties = Caffeine.newBuilder() // this.passphraseStoredProperties = Caffeine.newBuilder() //
.softValues() // .weakValues() //
.build(this::createStoredPassphraseProperty); .build(this::createStoredPassphraseProperty);
keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll()); keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
this.lock = new ReentrantReadWriteLock(false);
} }
private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException { private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
@@ -47,59 +42,29 @@ public class KeychainManager implements KeychainAccessProvider {
return getClass().getName(); 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 @Override
public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean ignored) throws KeychainAccessException { public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean ignored) throws KeychainAccessException {
try { getKeychainOrFail().storePassphrase(key, displayName, passphrase);
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); setPassphraseStored(key, true);
} }
@Override @Override
public char[] loadPassphrase(String key) throws KeychainAccessException { public char[] loadPassphrase(String key) throws KeychainAccessException {
char[] passphrase = null; char[] passphrase = getKeychainOrFail().loadPassphrase(key);
try {
lock.readLock().lock();
passphrase = getKeychainOrFail().loadPassphrase(key);
} finally {
lock.readLock().unlock();
}
setPassphraseStored(key, passphrase != null); setPassphraseStored(key, passphrase != null);
return passphrase; return passphrase;
} }
@Override @Override
public void deletePassphrase(String key) throws KeychainAccessException { public void deletePassphrase(String key) throws KeychainAccessException {
try { getKeychainOrFail().deletePassphrase(key);
lock.writeLock().lock();
getKeychainOrFail().deletePassphrase(key);
} finally {
lock.writeLock().unlock();
}
setPassphraseStored(key, false); setPassphraseStored(key, false);
} }
@Override @Override
public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
if (isPassphraseStored(key)) { if (isPassphraseStored(key)) {
try { getKeychainOrFail().changePassphrase(key, displayName, passphrase);
lock.writeLock().lock();
getKeychainOrFail().changePassphrase(key, displayName, passphrase);
} finally {
lock.writeLock().unlock();
}
setPassphraseStored(key, true); setPassphraseStored(key, true);
} }
} }
@@ -136,11 +101,13 @@ public class KeychainManager implements KeychainAccessProvider {
} }
private void setPassphraseStored(String key, boolean value) { private void setPassphraseStored(String key, boolean value) {
BooleanProperty property = passphraseStoredProperties.get(key, _ -> new SimpleBooleanProperty(value)); BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
if (Platform.isFxApplicationThread()) { if (property != null) {
property.set(value); if (Platform.isFxApplicationThread()) {
} else { property.set(value);
Platform.runLater(() -> property.set(value)); } else {
Platform.runLater(() -> property.set(value));
}
} }
} }
@@ -167,22 +134,4 @@ 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();
}
}
}
} }
@@ -10,7 +10,7 @@ package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants; import org.cryptomator.common.Constants;
import org.cryptomator.common.EventMap; import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.common.mount.Mounter; import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VaultSettings;
@@ -23,7 +23,6 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.integrations.mount.MountFailedException; import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.Mountpoint; import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException; import org.cryptomator.integrations.mount.UnmountFailedException;
@@ -35,7 +34,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
@@ -78,7 +76,7 @@ public class Vault {
private final ObjectBinding<Mountpoint> mountPoint; private final ObjectBinding<Mountpoint> mountPoint;
private final Mounter mounter; private final Mounter mounter;
private final Settings settings; private final Settings settings;
private final EventMap eventMap; private final FileSystemEventAggregator fileSystemEventAggregator;
private final BooleanProperty showingStats; private final BooleanProperty showingStats;
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null); private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@@ -91,7 +89,7 @@ public class Vault {
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, // @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
VaultStats stats, // VaultStats stats, //
Mounter mounter, Settings settings, // Mounter mounter, Settings settings, //
EventMap eventMap) { FileSystemEventAggregator fileSystemEventAggregator) {
this.vaultSettings = vaultSettings; this.vaultSettings = vaultSettings;
this.configCache = configCache; this.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem; this.cryptoFileSystem = cryptoFileSystem;
@@ -108,7 +106,7 @@ public class Vault {
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state); this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
this.mounter = mounter; this.mounter = mounter;
this.settings = settings; this.settings = settings;
this.eventMap = eventMap; this.fileSystemEventAggregator = fileSystemEventAggregator;
this.showingStats = new SimpleBooleanProperty(false); this.showingStats = new SimpleBooleanProperty(false);
this.quickAccessEntry = new AtomicReference<>(null); this.quickAccessEntry = new AtomicReference<>(null);
} }
@@ -261,10 +259,7 @@ public class Vault {
private void consumeVaultEvent(FilesystemEvent e) { private void consumeVaultEvent(FilesystemEvent e) {
var wrapper = new VaultEvent(this, e); fileSystemEventAggregator.put(this, e);
Platform.runLater(() -> {
eventMap.put(wrapper);
});
} }
// ****************************************************************************** // ******************************************************************************
@@ -428,17 +423,6 @@ 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() { public VaultConfigCache getVaultConfigCache() {
return configCache; return configCache;
} }
@@ -1,14 +0,0 @@
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();
}
}
}
@@ -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) {}
@@ -0,0 +1,5 @@
package org.cryptomator.event;
import org.cryptomator.cryptofs.event.FilesystemEvent;
public record FSEventBucketContent(FilesystemEvent mostRecentEvent, int count) {}
@@ -0,0 +1,101 @@
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;
@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);
hasUpdates.set(true);
map.compute(key, (k, val) -> {
if (val == null) {
return new FSEventBucketContent(e, 1);
} else {
return new FSEventBucketContent(e, val.count() + 1);
}
});
}
/**
* Removes an event bucket from the map.
*/
public FSEventBucketContent remove(FSEventBucket key) {
hasUpdates.set(true);
return map.remove(key);
}
/**
* Clears the event map.
*/
public void clear() {
hasUpdates.set(true);
map.clear();
}
public boolean hasUpdates() {
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());
}
}
@@ -1,15 +0,0 @@
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);
}
}
@@ -1,27 +0,0 @@
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);
}
}
@@ -1,6 +1,8 @@
package org.cryptomator.ui.eventview; package org.cryptomator.ui.eventview;
import org.cryptomator.common.EventMap; import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.common.Nullable; import org.cryptomator.common.Nullable;
import org.cryptomator.common.ObservableUtil; import org.cryptomator.common.ObservableUtil;
import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.CryptoPath;
@@ -9,7 +11,6 @@ import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent; import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent; import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent; import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.integrations.revealpath.RevealFailedException; import org.cryptomator.integrations.revealpath.RevealFailedException;
import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.integrations.revealpath.RevealPathService;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
@@ -41,6 +42,7 @@ import java.nio.file.Path;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle; import java.time.format.FormatStyle;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.function.Function; import java.util.function.Function;
@@ -51,11 +53,11 @@ public class EventListCellController implements FxController {
private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault()); 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 static final DateTimeFormatter LOCAL_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
private final EventMap eventMap; private final FileSystemEventAggregator fileSystemEventAggregator;
@Nullable @Nullable
private final RevealPathService revealService; private final RevealPathService revealService;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final ObjectProperty<VaultEvent> event; private final ObjectProperty<Map.Entry<FSEventBucket, FSEventBucketContent>> eventEntry;
private final StringProperty eventMessage; private final StringProperty eventMessage;
private final StringProperty eventDescription; private final StringProperty eventDescription;
private final ObjectProperty<FontAwesome5Icon> eventIcon; private final ObjectProperty<FontAwesome5Icon> eventIcon;
@@ -77,18 +79,18 @@ public class EventListCellController implements FxController {
Button eventActionsButton; Button eventActionsButton;
@Inject @Inject
public EventListCellController(EventMap eventMap, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) { public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
this.eventMap = eventMap; this.fileSystemEventAggregator = fileSystemEventAggregator;
this.revealService = revealService.orElseGet(() -> null); this.revealService = revealService.orElseGet(() -> null);
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.event = new SimpleObjectProperty<>(null); this.eventEntry = new SimpleObjectProperty<>(null);
this.eventMessage = new SimpleStringProperty(); this.eventMessage = new SimpleStringProperty();
this.eventDescription = new SimpleStringProperty(); this.eventDescription = new SimpleStringProperty();
this.eventIcon = new SimpleObjectProperty<>(); this.eventIcon = new SimpleObjectProperty<>();
this.eventCount = ObservableUtil.mapWithDefault(event, e -> e.count() == 1? "" : "("+ e.count() +")", ""); this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1? "" : "("+ e.getValue().count() +")", "");
this.vaultUnlocked = ObservableUtil.mapWithDefault(event.flatMap(e -> e.v().unlockedProperty()), Function.identity(), false); this.vaultUnlocked = ObservableUtil.mapWithDefault(eventEntry.flatMap(e -> e.getKey().vault().unlockedProperty()), Function.identity(), false);
this.readableTime = ObservableUtil.mapWithDefault(event, e -> LOCAL_TIME_FORMATTER.format(e.actualEvent().getTimestamp()), ""); this.readableTime = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_TIME_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
this.readableDate = ObservableUtil.mapWithDefault(event, e -> LOCAL_DATE_FORMATTER.format(e.actualEvent().getTimestamp()), ""); this.readableDate = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_DATE_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage); this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage);
this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription); this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription);
this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon); this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon);
@@ -108,13 +110,15 @@ public class EventListCellController implements FxController {
return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover()); return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover());
} }
public void setEvent(@NotNull VaultEvent item) { public void setEventEntry(@NotNull Map.Entry<FSEventBucket, FSEventBucketContent> item) {
event.set(item); eventEntry.set(item);
eventActionsMenu.hide(); eventActionsMenu.hide();
eventActionsMenu.getItems().clear(); eventActionsMenu.getItems().clear();
eventTooltip.setText(item.v().getDisplayName()); eventTooltip.setText(item.getKey().vault().getDisplayName());
addAction("generic.action.dismiss", () -> eventMap.remove(item)); addAction("generic.action.dismiss", () -> {
switch (item.actualEvent()) { fileSystemEventAggregator.remove(item.getKey());
});
switch (item.getValue().mostRecentEvent()) {
case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse); case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse); case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse); case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
@@ -209,16 +213,18 @@ public class EventListCellController implements FxController {
private String selectDescription() { private String selectDescription() {
if (vaultUnlocked.getValue()) { if (vaultUnlocked.getValue()) {
return eventDescription.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 { } else {
var e = event.getValue(); return "";
return resourceBundle.getString("eventView.entry.vaultLocked.description").formatted(e != null ? e.v().getDisplayName() : "");
} }
} }
@FXML @FXML
public void toggleEventActionsMenu() { public void toggleEventActionsMenu() {
var e = event.get(); var e = eventEntry.get();
if (e != null) { if (e != null) {
if (eventActionsMenu.isShowing()) { if (eventActionsMenu.isShowing()) {
eventActionsMenu.hide(); eventActionsMenu.hide();
@@ -232,7 +238,7 @@ public class EventListCellController implements FxController {
if (!(p instanceof CryptoPath)) { if (!(p instanceof CryptoPath)) {
throw new IllegalArgumentException("Path " + p + " is not a vault path"); throw new IllegalArgumentException("Path " + p + " is not a vault path");
} }
var v = event.getValue().v(); var v = eventEntry.getValue().getKey().vault();
if (!v.isUnlocked()) { if (!v.isUnlocked()) {
return Path.of(System.getProperty("user.home")); return Path.of(System.getProperty("user.home"));
} }
@@ -1,6 +1,7 @@
package org.cryptomator.ui.eventview; package org.cryptomator.ui.eventview;
import org.cryptomator.event.VaultEvent; import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlLoaderFactory;
import javax.inject.Inject; import javax.inject.Inject;
@@ -12,9 +13,10 @@ import javafx.scene.control.ListView;
import javafx.util.Callback; import javafx.util.Callback;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.Map;
@EventViewScoped @EventViewScoped
public class EventListCellFactory implements Callback<ListView<VaultEvent>, ListCell<VaultEvent>> { 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 static final String FXML_PATH = "/fxml/eventview_cell.fxml";
@@ -27,7 +29,7 @@ public class EventListCellFactory implements Callback<ListView<VaultEvent>, List
@Override @Override
public ListCell<VaultEvent> call(ListView<VaultEvent> eventListView) { public ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> call(ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView) {
try { try {
FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH); FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH);
return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController()); return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
@@ -36,7 +38,7 @@ public class EventListCellFactory implements Callback<ListView<VaultEvent>, List
} }
} }
private static class Cell extends ListCell<VaultEvent> { private static class Cell extends ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> {
private final Parent root; private final Parent root;
private final EventListCellController controller; private final EventListCellController controller;
@@ -47,7 +49,7 @@ public class EventListCellFactory implements Callback<ListView<VaultEvent>, List
} }
@Override @Override
protected void updateItem(VaultEvent item, boolean empty) { protected void updateItem(Map.Entry<FSEventBucket, FSEventBucketContent> item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (empty || item == null) { if (empty || item == null) {
@@ -57,7 +59,7 @@ public class EventListCellFactory implements Callback<ListView<VaultEvent>, List
this.getStyleClass().addLast("list-cell"); this.getStyleClass().addLast("list-cell");
setContentDisplay(ContentDisplay.GRAPHIC_ONLY); setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(root); setGraphic(root);
controller.setEvent(item); controller.setEventEntry(item);
} }
} }
} }
@@ -1,15 +1,16 @@
package org.cryptomator.ui.eventview; package org.cryptomator.ui.eventview;
import org.cryptomator.common.EventMap;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.event.VaultEvent; 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.common.FxController;
import org.cryptomator.ui.fxapp.FxFSEventList;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList; import javafx.collections.transformation.SortedList;
@@ -17,17 +18,16 @@ import javafx.fxml.FXML;
import javafx.scene.control.ChoiceBox; import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import java.util.Comparator; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@EventViewScoped @EventViewScoped
public class EventViewController implements FxController { public class EventViewController implements FxController {
private final EventMap eventMap; private final FilteredList<Map.Entry<FSEventBucket, FSEventBucketContent>> filteredEventList;
private final ObservableList<VaultEvent> eventList;
private final FilteredList<VaultEvent> filteredEventList;
private final ObservableList<Vault> vaults; private final ObservableList<Vault> vaults;
private final SortedList<VaultEvent> reversedEventList; private final FileSystemEventAggregator aggregator;
private final SortedList<Map.Entry<FSEventBucket, FSEventBucketContent>> sortedEventList;
private final ObservableList<Vault> choiceBoxEntries; private final ObservableList<Vault> choiceBoxEntries;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final EventListCellFactory cellFactory; private final EventListCellFactory cellFactory;
@@ -35,20 +35,45 @@ public class EventViewController implements FxController {
@FXML @FXML
ChoiceBox<Vault> vaultFilterChoiceBox; ChoiceBox<Vault> vaultFilterChoiceBox;
@FXML @FXML
ListView<VaultEvent> eventListView; ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView;
@Inject @Inject
public EventViewController(EventMap eventMap, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory) { public EventViewController(FxFSEventList fxFSEventList, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory, FileSystemEventAggregator aggregator) {
this.eventMap = eventMap; this.filteredEventList = fxFSEventList.getObservableList().filtered(_ -> true);
this.eventList = FXCollections.observableArrayList();
this.filteredEventList = eventList.filtered(_ -> true);
this.vaults = vaults; this.vaults = vaults;
this.reversedEventList = new SortedList<>(filteredEventList, Comparator.reverseOrder()); this.aggregator = aggregator;
this.sortedEventList = new SortedList<>(filteredEventList, this::compareBuckets);
this.choiceBoxEntries = FXCollections.observableArrayList(); this.choiceBoxEntries = FXCollections.observableArrayList();
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.cellFactory = cellFactory; 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 @FXML
public void initialize() { public void initialize() {
choiceBoxEntries.add(null); choiceBoxEntries.add(null);
@@ -60,39 +85,25 @@ public class EventViewController implements FxController {
} }
}); });
eventList.addAll(eventMap.values());
eventMap.addListener((MapChangeListener<? super EventMap.EventKey, ? super VaultEvent>) this::updateList);
eventListView.setCellFactory(cellFactory); eventListView.setCellFactory(cellFactory);
eventListView.setItems(reversedEventList); eventListView.setItems(sortedEventList);
vaultFilterChoiceBox.setItems(choiceBoxEntries); vaultFilterChoiceBox.setItems(choiceBoxEntries);
vaultFilterChoiceBox.valueProperty().addListener(this::applyVaultFilter); vaultFilterChoiceBox.valueProperty().addListener(this::applyVaultFilter);
vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle)); vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle));
} }
private void updateList(MapChangeListener.Change<? extends EventMap.EventKey, ? extends VaultEvent> change) {
if (change.wasAdded() && change.wasRemoved()) {
//entry updated
eventList.remove(change.getValueRemoved());
eventList.addLast(change.getValueAdded());
} else if (change.wasAdded()) {
eventList.addLast(change.getValueAdded());
} else { //removed
eventList.remove(change.getValueRemoved());
}
}
private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) { private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) {
if (newV == null) { if (newV == null) {
filteredEventList.setPredicate(_ -> true); filteredEventList.setPredicate(_ -> true);
} else { } else {
filteredEventList.setPredicate(e -> e.v().equals(newV)); filteredEventList.setPredicate(e -> e.getKey().vault().equals(newV));
} }
} }
@FXML @FXML
void clearEvents() { void clearEvents() {
eventMap.clear(); aggregator.clear();
} }
private static class VaultConverter extends StringConverter<Vault> { private static class VaultConverter extends StringConverter<Vault> {
@@ -11,6 +11,7 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.fxapp.FxFSEventList;
import javax.inject.Provider; import javax.inject.Provider;
import javafx.scene.Scene; import javafx.scene.Scene;
@@ -25,12 +26,17 @@ abstract class EventViewModule {
@Provides @Provides
@EventViewScoped @EventViewScoped
@EventViewWindow @EventViewWindow
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) { static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle, FxFSEventList fxFSEventList) {
Stage stage = factory.create(); Stage stage = factory.create();
stage.setHeight(498); stage.setHeight(498);
stage.setTitle(resourceBundle.getString("eventView.title")); stage.setTitle(resourceBundle.getString("eventView.title"));
stage.setResizable(true); stage.setResizable(true);
stage.initModality(Modality.NONE); stage.initModality(Modality.NONE);
stage.focusedProperty().addListener((_,_,isFocused) -> {
if(isFocused) {
fxFSEventList.unreadEventsProperty().setValue(false);
}
});
return stage; return stage;
} }
@@ -1,14 +0,0 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
@EventViewScoped
public class UpdateEventViewController implements FxController {
@Inject
public UpdateEventViewController() {
}
}
@@ -29,9 +29,10 @@ public class FxApplication {
private final FxApplicationStyle applicationStyle; private final FxApplicationStyle applicationStyle;
private final FxApplicationTerminator applicationTerminator; private final FxApplicationTerminator applicationTerminator;
private final AutoUnlocker autoUnlocker; private final AutoUnlocker autoUnlocker;
private final FxFSEventList fxFSEventList; //not unused! By injecting it here, the object gets initiated the service starts
@Inject @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.startupTime = startupTime;
this.environment = environment; this.environment = environment;
this.settings = settings; this.settings = settings;
@@ -41,6 +42,7 @@ public class FxApplication {
this.applicationStyle = applicationStyle; this.applicationStyle = applicationStyle;
this.applicationTerminator = applicationTerminator; this.applicationTerminator = applicationTerminator;
this.autoUnlocker = autoUnlocker; this.autoUnlocker = autoUnlocker;
this.fxFSEventList = fxFSEventList;
} }
public void start() { public void start() {
@@ -20,6 +20,9 @@ import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent; import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javax.inject.Named;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -0,0 +1,65 @@
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.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@FxApplicationScoped
public class FxFSEventList {
private final ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> events;
private final FileSystemEventAggregator eventAggregator;
private final ScheduledFuture<?> scheduledTask;
private final BooleanProperty unreadEvents;
@Inject
public FxFSEventList(FileSystemEventAggregator fsEventAggregator, ScheduledExecutorService scheduler) {
this.events = FXCollections.observableArrayList();
this.eventAggregator = fsEventAggregator;
this.unreadEvents = new SimpleBooleanProperty(false);
this.scheduledTask = scheduler.scheduleWithFixedDelay(() -> {
if (fsEventAggregator.hasUpdates()) {
flush();
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
}
/**
* Starts the clone task on the FX thread and wait till it is completed
*/
private void flush() {
var latch = new CountDownLatch(1);
Platform.runLater(() -> {
eventAggregator.cloneTo(events);
unreadEvents.setValue(true);
latch.countDown();
});
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> getObservableList() {
return events;
}
public BooleanProperty unreadEventsProperty() {
return unreadEvents;
}
}
@@ -112,12 +112,12 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
} }
private void savePasswordToSystemkeychain(Passphrase passphrase) { private void savePasswordToSystemkeychain(Passphrase passphrase) {
try { if (keychain.isSupported()) {
if (keychain.isSupported() && !keychain.getPassphraseStoredProperty(vault.getId()).get()) { try {
keychain.storePassphrase(vault.getId(), vault.getDisplayName(), passphrase); 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);
} }
} }
@@ -95,6 +95,16 @@ public class MainWindowController implements FxController {
int width = settings.windowWidth.get(); int width = settings.windowWidth.get();
int height = settings.windowHeight.get(); int height = settings.windowHeight.get();
// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000)
// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453
// If the position is (-32000, -32000), restore to the last saved position
if (window.getX() == -32000 && window.getY() == -32000) {
window.setX(x);
window.setY(y);
window.setWidth(width);
window.setHeight(height);
}
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds(); Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
if (!isWithinDisplayBounds(x, y, width, height)) { //use stored window position if (!isWithinDisplayBounds(x, y, width, height)) { //use stored window position
LOG.debug("Resetting window position due to insufficient screen overlap"); LOG.debug("Resetting window position due to insufficient screen overlap");
@@ -166,7 +176,7 @@ public class MainWindowController implements FxController {
return updateAvailable.get(); return updateAvailable.get();
} }
public BooleanBinding licenseValidProperty() { public BooleanBinding licenseValidProperty(){
return licenseHolder.validLicenseProperty(); return licenseHolder.validLicenseProperty();
} }
@@ -8,9 +8,9 @@ import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.stage.Stage; import javafx.stage.Stage;
@@ -21,6 +21,7 @@ public class VaultDetailLockedController implements FxController {
private final ReadOnlyObjectProperty<Vault> vault; private final ReadOnlyObjectProperty<Vault> vault;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
private final VaultOptionsComponent.Factory vaultOptionsWindow; private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final KeychainManager keychain;
private final Stage mainWindow; private final Stage mainWindow;
private final ObservableValue<Boolean> passwordSaved; private final ObservableValue<Boolean> passwordSaved;
@@ -29,11 +30,13 @@ public class VaultDetailLockedController implements FxController {
this.vault = vault; this.vault = vault;
this.appWindows = appWindows; this.appWindows = appWindows;
this.vaultOptionsWindow = vaultOptionsWindow; this.vaultOptionsWindow = vaultOptionsWindow;
this.keychain = keychain;
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
this.passwordSaved = Bindings.createBooleanBinding(() -> { if (keychain.isSupported() && !keychain.isLocked()) {
var v = vault.get(); this.passwordSaved = vault.flatMap(v -> keychain.getPassphraseStoredProperty(v.getId())).orElse(false);
return v != null && keychain.getPassphraseStoredProperty(v.getId()).getValue(); } else {
}, vault, keychain.getKeychainImplementation()); this.passwordSaved = new SimpleBooleanProperty(false);
}
} }
@FXML @FXML
@@ -6,7 +6,6 @@ import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.EasyBind;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.mount.Mountpoint; import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.revealpath.RevealFailedException; import org.cryptomator.integrations.revealpath.RevealFailedException;
@@ -40,14 +39,10 @@ import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@MainWindowScoped @MainWindowScoped
public class VaultDetailUnlockedController implements FxController { public class VaultDetailUnlockedController implements FxController {
@@ -67,15 +62,11 @@ public class VaultDetailUnlockedController implements FxController {
private final ObservableValue<Boolean> accessibleViaPath; private final ObservableValue<Boolean> accessibleViaPath;
private final ObservableValue<Boolean> accessibleViaUri; private final ObservableValue<Boolean> accessibleViaUri;
private final ObservableValue<String> mountPoint; private final ObservableValue<String> mountPoint;
private final BooleanProperty draggingOverLocateEncrypted = new SimpleBooleanProperty(); private final BooleanProperty draggingOver = new SimpleBooleanProperty();
private final BooleanProperty draggingOverDecryptName = new SimpleBooleanProperty();
private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty(); private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty();
private final BooleanProperty cleartextNamesCopied = new SimpleBooleanProperty();
@FXML //FXML
public Button revealEncryptedDropZone; public Button dropZone;
@FXML
public Button decryptNameDropZone;
@Inject @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, ResourceBundle resourceBundle) {
@@ -101,90 +92,72 @@ public class VaultDetailUnlockedController implements FxController {
} }
public void initialize() { public void initialize() {
revealEncryptedDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverLocateEncrypted)); dropZone.setOnDragEntered(this::handleDragEvent);
revealEncryptedDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCiphertextPath, this::revealOrCopyPaths)); dropZone.setOnDragOver(this::handleDragEvent);
revealEncryptedDropZone.setOnDragExited(_ -> draggingOverLocateEncrypted.setValue(false)); dropZone.setOnDragDropped(this::handleDragEvent);
dropZone.setOnDragExited(this::handleDragEvent);
decryptNameDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverDecryptName)); EasyBind.includeWhen(dropZone.getStyleClass(), ACTIVE_CLASS, draggingOver);
decryptNameDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCleartextName, this::copyDecryptedNamesToClipboard));
decryptNameDropZone.setOnDragExited(_ -> draggingOverDecryptName.setValue(false));
EasyBind.includeWhen(revealEncryptedDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverLocateEncrypted);
EasyBind.includeWhen(decryptNameDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverDecryptName);
} }
private void handleDragOver(DragEvent event, BooleanProperty prop) { private void handleDragEvent(DragEvent event) {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) { if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
if (SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) { if(SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
event.acceptTransferModes(TransferMode.LINK); event.acceptTransferModes(TransferMode.LINK);
} else { } else {
event.acceptTransferModes(TransferMode.ANY); event.acceptTransferModes(TransferMode.ANY);
} }
prop.set(true); 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);
} }
} }
private <T> void handleDragDropped(DragEvent event, Function<Path, T> computation, Consumer<List<T>> positiveAction) { private VaultStatisticsComponent buildVaultStats(Vault vault) {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) { return vaultStatsBuilder.vault(vault).build();
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 @FXML
public void chooseDecryptedFileAndReveal() { 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() {
Preconditions.checkState(accessibleViaPath.getValue()); Preconditions.checkState(accessibleViaPath.getValue());
var fileChooser = new FileChooser(); var fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.locateEncrypted.filePickerTitle")); fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.filePickerTitle"));
fileChooser.setInitialDirectory(Path.of(mountPoint.getValue()).toFile()); fileChooser.setInitialDirectory(Path.of(mountPoint.getValue()).toFile());
var cleartextFile = fileChooser.showOpenDialog(mainWindow); var cleartextFile = fileChooser.showOpenDialog(mainWindow);
if (cleartextFile != null) { if (cleartextFile != null) {
var ciphertextPath = getCiphertextPath(cleartextFile.toPath()); var ciphertextPaths = getCiphertextPath(cleartextFile.toPath()).stream().toList();
if (ciphertextPath != null) { revealOrCopyPaths(ciphertextPaths);
revealOrCopyPaths(List.of(ciphertextPath));
}
}
}
@FXML
public void chooseEncryptedFileAndCopyNames() {
var fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.decryptName.filePickerTitle"));
fileChooser.setInitialDirectory(vault.getValue().getPath().toFile());
var ciphertextNode = fileChooser.showOpenDialog(mainWindow);
if (ciphertextNode != null) {
var nodeName = getCleartextName(ciphertextNode.toPath());
copyDecryptedNamesToClipboard(List.of(nodeName));
}
}
private void copyDecryptedNamesToClipboard(List<CipherToCleartext> mapping) {
if (mapping.size() == 1) {
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, mapping.getFirst().cleartext));
} else {
var content = mapping.stream().map(CipherToCleartext::toString).collect(Collectors.joining("\n"));
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, content));
}
cleartextNamesCopied.setValue(true);
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
cleartextNamesCopied.set(false);
});
}
@Nullable
private CipherToCleartext getCleartextName(Path ciphertextNode) {
try {
return new CipherToCleartext(ciphertextNode.getFileName().toString(), vault.get().getCleartextName(ciphertextNode));
} catch (IOException e) {
LOG.warn("Failed to decrypt filename for {}", ciphertextNode, e);
return null;
} }
} }
@@ -192,17 +165,16 @@ public class VaultDetailUnlockedController implements FxController {
return path.startsWith(Path.of(mountPoint.getValue())); return path.startsWith(Path.of(mountPoint.getValue()));
} }
@Nullable private Optional<Path> getCiphertextPath(Path path) {
private Path getCiphertextPath(Path path) {
if (!startsWithVaultAccessPoint(path)) { if (!startsWithVaultAccessPoint(path)) {
LOG.debug("Path does not start with mount point of selected vault: {}", path); LOG.debug("Path does not start with access point of selected vault: {}", path);
return null; return Optional.empty();
} }
try { try {
return vault.get().getCiphertextPath(path); return Optional.of(vault.get().getCiphertextPath(path));
} catch (IOException e) { } catch (IOException e) {
LOG.warn("Unable to get ciphertext path from path: {}", path, e); LOG.warn("Unable to get ciphertext path from path: {}", path, e);
return null; return Optional.empty();
} }
} }
@@ -234,40 +206,6 @@ 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();
}
record CipherToCleartext(String ciphertext, String cleartext) {
@Override
public String toString() {
return ciphertext + " > " + cleartext;
}
}
/* Getter/Setter */ /* Getter/Setter */
public ReadOnlyObjectProperty<Vault> vaultProperty() { public ReadOnlyObjectProperty<Vault> vaultProperty() {
@@ -309,12 +247,4 @@ public class VaultDetailUnlockedController implements FxController {
public boolean isCiphertextPathsCopied() { public boolean isCiphertextPathsCopied() {
return ciphertextPathsCopied.get(); return ciphertextPathsCopied.get();
} }
public BooleanProperty cleartextNamesCopiedProperty() {
return cleartextNamesCopied;
}
public boolean isCleartextNamesCopied() {
return cleartextNamesCopied.get();
}
} }
@@ -1,17 +1,16 @@
package org.cryptomator.ui.mainwindow; package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.EventMap;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.DirStructure; import org.cryptomator.cryptofs.DirStructure;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService; import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxFSEventList;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -25,7 +24,6 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Side; import javafx.geometry.Side;
@@ -69,8 +67,7 @@ public class VaultListController implements FxController {
private final VaultListCellFactory cellFactory; private final VaultListCellFactory cellFactory;
private final AddVaultWizardComponent.Builder addVaultWizard; private final AddVaultWizardComponent.Builder addVaultWizard;
private final BooleanBinding emptyVaultList; private final BooleanBinding emptyVaultList;
private final EventMap eventMap; private final BooleanProperty unreadEvents;
private final BooleanProperty newEventsPresent;
private final VaultListManager vaultListManager; private final VaultListManager vaultListManager;
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty(); private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
@@ -97,7 +94,7 @@ public class VaultListController implements FxController {
FxApplicationWindows appWindows, // FxApplicationWindows appWindows, //
Settings settings, // Settings settings, //
Dialogs dialogs, // Dialogs dialogs, //
EventMap eventMap) { FxFSEventList fxFSEventList) {
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
this.vaults = vaults; this.vaults = vaults;
this.selectedVault = selectedVault; this.selectedVault = selectedVault;
@@ -110,13 +107,7 @@ public class VaultListController implements FxController {
this.dialogs = dialogs; this.dialogs = dialogs;
this.emptyVaultList = Bindings.isEmpty(vaults); this.emptyVaultList = Bindings.isEmpty(vaults);
this.eventMap = eventMap; this.unreadEvents = fxFSEventList.unreadEventsProperty();
this.newEventsPresent = new SimpleBooleanProperty(false);
eventMap.addListener((MapChangeListener<? super EventMap.EventKey, ? super VaultEvent>) change -> {
if (change.wasAdded()) {
newEventsPresent.setValue(true);
}
});
selectedVault.addListener(this::selectedVaultDidChange); selectedVault.addListener(this::selectedVaultDidChange);
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0); cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
@@ -279,7 +270,7 @@ public class VaultListController implements FxController {
@FXML @FXML
public void showEventViewer() { public void showEventViewer() {
appWindows.showEventViewer(); appWindows.showEventViewer();
newEventsPresent.setValue(false); unreadEvents.setValue(false);
} }
// Getter and Setter // Getter and Setter
@@ -307,11 +298,11 @@ public class VaultListController implements FxController {
return cellSize.getValue(); return cellSize.getValue();
} }
public ObservableValue<Boolean> newEventsPresentProperty() { public ObservableValue<Boolean> unreadEventsPresentProperty() {
return newEventsPresent; return unreadEvents;
} }
public boolean getNewEventsPresent() { public boolean getUnreadEventsPresent() {
return newEventsPresent.getValue(); return unreadEvents.getValue();
} }
} }
@@ -1,13 +1,10 @@
package org.cryptomator.ui.preferences; package org.cryptomator.ui.preferences;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment; import org.cryptomator.common.Environment;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException; import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
import org.cryptomator.integrations.common.NamedServiceProvider; import org.cryptomator.integrations.common.NamedServiceProvider;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider; import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import org.cryptomator.integrations.quickaccess.QuickAccessService; import org.cryptomator.integrations.quickaccess.QuickAccessService;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
@@ -17,7 +14,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.application.Application; import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
@@ -27,10 +23,6 @@ import javafx.stage.Stage;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import java.util.List; import java.util.List;
import java.util.Optional; 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 @PreferencesScoped
public class GeneralPreferencesController implements FxController { public class GeneralPreferencesController implements FxController {
@@ -44,8 +36,6 @@ public class GeneralPreferencesController implements FxController {
private final Application application; private final Application application;
private final Environment environment; private final Environment environment;
private final List<KeychainAccessProvider> keychainAccessProviders; private final List<KeychainAccessProvider> keychainAccessProviders;
private final KeychainManager keychain;
private final ExecutorService backgroundExecutor;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
public CheckBox useKeychainCheckbox; public CheckBox useKeychainCheckbox;
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox; public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
@@ -57,18 +47,12 @@ public class GeneralPreferencesController implements FxController {
public CheckBox autoStartCheckbox; public CheckBox autoStartCheckbox;
public ToggleGroup nodeOrientation; public ToggleGroup nodeOrientation;
private CompletionStage<Void> keychainMigrations = CompletableFuture.completedFuture(null);
@Inject @Inject
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, // GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, List<KeychainAccessProvider> keychainAccessProviders, Application application, Environment environment, FxApplicationWindows appWindows) {
List<KeychainAccessProvider> keychainAccessProviders, KeychainManager keychain, Application application, //
Environment environment, FxApplicationWindows appWindows, ExecutorService backgroundExecutor) {
this.window = window; this.window = window;
this.settings = settings; this.settings = settings;
this.autoStartProvider = autoStartProvider; this.autoStartProvider = autoStartProvider;
this.keychainAccessProviders = keychainAccessProviders; this.keychainAccessProviders = keychainAccessProviders;
this.keychain = keychain;
this.backgroundExecutor = backgroundExecutor;
this.quickAccessServices = QuickAccessService.get().toList(); this.quickAccessServices = QuickAccessService.get().toList();
this.application = application; this.application = application;
this.environment = environment; this.environment = environment;
@@ -89,7 +73,6 @@ public class GeneralPreferencesController implements FxController {
Bindings.bindBidirectional(settings.keychainProvider, keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter); Bindings.bindBidirectional(settings.keychainProvider, keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain); useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain);
keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not()); keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not());
keychainBackendChoiceBox.valueProperty().addListener(this::migrateKeychainEntries);
useQuickAccessCheckbox.selectedProperty().bindBidirectional(settings.useQuickAccess); useQuickAccessCheckbox.selectedProperty().bindBidirectional(settings.useQuickAccess);
var quickAccessSettingsConverter = new ServiceToSettingsConverter<>(quickAccessServices); var quickAccessSettingsConverter = new ServiceToSettingsConverter<>(quickAccessServices);
@@ -100,25 +83,6 @@ public class GeneralPreferencesController implements FxController {
quickAccessServiceChoiceBox.disableProperty().bind(useQuickAccessCheckbox.selectedProperty().not()); 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() { public boolean isAutoStartSupported() {
return autoStartProvider.isPresent(); return autoStartProvider.isPresent();
} }
+1 -15
View File
@@ -16,10 +16,6 @@
src: url('opensans_bold.ttf'); src: url('opensans_bold.ttf');
} }
@font-face {
src: url('firacode_regular.ttf');
}
/******************************************************************************* /*******************************************************************************
* * * *
* Root Styling & Colors * * Root Styling & Colors *
@@ -129,13 +125,6 @@
-fx-fill: TEXT_FILL; -fx-fill: TEXT_FILL;
} }
.cryptic-text {
-fx-background-color: MAIN_BG;
-fx-text-fill: TEXT_FILL;
-fx-font-family: 'Fira Code';
-fx-font-size: 1.1em;
}
/******************************************************************************* /*******************************************************************************
* * * *
* Glyph Icons * * Glyph Icons *
@@ -653,10 +642,7 @@
******************************************************************************/ ******************************************************************************/
.update-indicator { .update-indicator {
-fx-background-color: white, RED_5; -fx-fill: RED_5;
-fx-background-insets: 1px, 2px;
-fx-background-radius: 6px, 5px;
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
} }
/******************************************************************************* /*******************************************************************************
Binary file not shown.
+2 -16
View File
@@ -16,10 +16,6 @@
src: url('opensans_bold.ttf'); src: url('opensans_bold.ttf');
} }
@font-face {
src: url('firacode_regular.ttf');
}
/******************************************************************************* /*******************************************************************************
* * * *
* Root Styling & Colors * * Root Styling & Colors *
@@ -128,13 +124,6 @@
-fx-fill: TEXT_FILL; -fx-fill: TEXT_FILL;
} }
.cryptic-text {
-fx-background-color: MAIN_BG;
-fx-text-fill: TEXT_FILL;
-fx-font-family: 'Fira Code';
-fx-font-size: 1.1em;
}
/******************************************************************************* /*******************************************************************************
* * * *
* Glyph Icons * * Glyph Icons *
@@ -655,11 +644,8 @@
* * * *
******************************************************************************/ ******************************************************************************/
.update-indicator { .icon-update-indicator {
-fx-background-color: white, RED_5; -fx-fill: RED_5;
-fx-background-insets: 1px, 2px;
-fx-background-radius: 6px, 5px;
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
} }
/******************************************************************************* /*******************************************************************************
@@ -1,14 +1,12 @@
<?import org.cryptomator.ui.controls.FontAwesome5IconView?> <?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.ThroughputLabel?> <?import org.cryptomator.ui.controls.ThroughputLabel?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?> <?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?> <?import javafx.scene.control.Tooltip?>
<?import javafx.geometry.Insets?>
<VBox xmlns:fx="http://javafx.com/fxml" <VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx" xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnlockedController" fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnlockedController"
@@ -46,47 +44,28 @@
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
<HBox alignment="BOTTOM_CENTER"> <HBox alignment="BOTTOM_CENTER">
<StackPane visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}"> <HBox visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
<padding> <padding>
<Insets topRightBottomLeft="0"/> <Insets topRightBottomLeft="0"/>
</padding> </padding>
<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}"> <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}">
<graphic> <graphic>
<Text styleClass="cryptic-text" text="abc → 101010"/> <FontAwesome5IconView glyph="FILE_DOWNLOAD" glyphSize="15"/>
</graphic> </graphic>
<tooltip> <tooltip>
<Tooltip text="%main.vaultDetail.locateEncryptedFileBtn.tooltip"/> <Tooltip text="%main.vaultDetail.locateEncryptedFileBtn.tooltip"/>
</tooltip> </tooltip>
</Button> </Button>
<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}"> <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}">
<graphic> <graphic>
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/> <FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
</graphic> </graphic>
</Button> </Button>
</StackPane> </HBox>
<!-- decrypt file name -->
<StackPane>
<padding>
<Insets topRightBottomLeft="0"/>
</padding>
<Button fx:id="decryptNameDropZone" styleClass="drag-n-drop" text="%main.vaultDetail.decryptName.buttonLabel" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseEncryptedFileAndCopyNames" contentDisplay="TOP" visible="${!controller.cleartextNamesCopied}" managed="${!controller.cleartextNamesCopied}">
<graphic>
<Text styleClass="cryptic-text" text="101010 → abc"/>
</graphic>
<tooltip>
<Tooltip text="%main.vaultDetail.decryptName.tooltip"/>
</tooltip>
</Button>
<Button styleClass="drag-n-drop" text="%main.vaultDetail.decryptName.copied" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseEncryptedFileAndCopyNames" contentDisplay="TOP" visible="${controller.cleartextNamesCopied}" managed="${controller.cleartextNamesCopied}">
<graphic>
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
</graphic>
</Button>
</StackPane>
<Region HBox.hgrow="ALWAYS"/> <Region HBox.hgrow="ALWAYS"/>
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM" prefHeight="72"> <Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM">
<graphic> <graphic>
<VBox spacing="6"> <VBox spacing="6">
<HBox alignment="CENTER_RIGHT" spacing="6"> <HBox alignment="CENTER_RIGHT" spacing="6">
+7 -1
View File
@@ -12,6 +12,10 @@
<?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Arc?> <?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" <StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx" xmlns="http://javafx.com/javafx"
fx:id="root" fx:id="root"
@@ -51,7 +55,9 @@
<Tooltip text="%main.vaultlist.showEventsButton.tooltip"/> <Tooltip text="%main.vaultlist.showEventsButton.tooltip"/>
</tooltip> </tooltip>
</Button> </Button>
<Region styleClass="update-indicator" visible="${controller.newEventsPresent}" mouseTransparent="true" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/> <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> </StackPane>
<Button onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY"> <Button onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
<graphic> <graphic>
+1 -5
View File
@@ -425,11 +425,7 @@ main.vaultDetail.stats=Vault Statistics
main.vaultDetail.locateEncryptedFileBtn=Locate Encrypted File main.vaultDetail.locateEncryptedFileBtn=Locate Encrypted File
main.vaultDetail.locateEncryptedFileBtn.tooltip=Choose a file from your vault to locate its encrypted counterpart main.vaultDetail.locateEncryptedFileBtn.tooltip=Choose a file from your vault to locate its encrypted counterpart
main.vaultDetail.encryptedPathsCopied=Paths Copied to Clipboard! main.vaultDetail.encryptedPathsCopied=Paths Copied to Clipboard!
main.vaultDetail.locateEncrypted.filePickerTitle=Select File Inside Vault main.vaultDetail.filePickerTitle=Select File Inside Vault
main.vaultDetail.decryptName.buttonLabel=Decrypt File Name
main.vaultDetail.decryptName.filePickerTitle=Select encrypted file
main.vaultDetail.decryptName.tooltip=Choose an encrypted vault file to decrypt its name
main.vaultDetail.decryptName.copied=Names copied to clipboard!
### Missing ### Missing
main.vaultDetail.missing.info=Cryptomator could not find a vault at this path. main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
main.vaultDetail.missing.recheck=Recheck main.vaultDetail.missing.recheck=Recheck
@@ -1,7 +1,6 @@
package org.cryptomator.common.keychain; package org.cryptomator.common.keychain;
import org.cryptomator.JavaFXUtil;
import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assumptions;
@@ -14,16 +13,11 @@ import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
class KeychainManagerTest { public class KeychainManagerTest {
@BeforeAll
public static void startup() throws InterruptedException {
var isRunning = JavaFXUtil.startPlatform();
Assumptions.assumeTrue(isRunning);
}
@Test @Test
public void testStoreAndLoad() throws KeychainAccessException { public void testStoreAndLoad() throws KeychainAccessException {
@@ -33,7 +27,15 @@ class KeychainManagerTest {
} }
@Nested @Nested
class WhenObservingProperties { public static class WhenObservingProperties {
@BeforeAll
public static void startup() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Platform.startup(latch::countDown);
var javafxStarted = latch.await(5, TimeUnit.SECONDS);
Assumptions.assumeTrue(javafxStarted);
}
@Test @Test
public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException { public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
@@ -41,7 +43,7 @@ class KeychainManagerTest {
ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test"); ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
Assertions.assertFalse(property.get()); Assertions.assertFalse(property.get());
keychainManager.storePassphrase("test", null, "bar"); keychainManager.storePassphrase("test", null,"bar");
AtomicBoolean result = new AtomicBoolean(false); AtomicBoolean result = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
@@ -1,6 +1,6 @@
package org.cryptomator.ui.controls; package org.cryptomator.ui.controls;
import org.cryptomator.JavaFXUtil; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@@ -8,14 +8,20 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import javafx.application.Platform;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class SecurePasswordFieldTest { public class SecurePasswordFieldTest {
private SecurePasswordField pwField = new SecurePasswordField(); private SecurePasswordField pwField = new SecurePasswordField();
@BeforeAll @BeforeAll
public static void initJavaFx() throws InterruptedException { public static void initJavaFx() throws InterruptedException {
var isRunning = JavaFXUtil.startPlatform(); CountDownLatch latch = new CountDownLatch(1);
Assumptions.assumeTrue(isRunning); Platform.startup(latch::countDown);
var javafxStarted = latch.await(5, TimeUnit.SECONDS);
Assumptions.assumeTrue(javafxStarted);
} }
@Nested @Nested