44import com .google .common .cache .CacheBuilder ;
55import org .cryptomator .cloudaccess .api .CloudItemList ;
66import org .cryptomator .cloudaccess .api .CloudItemMetadata ;
7+ import org .cryptomator .cloudaccess .api .CloudItemType ;
78import org .cryptomator .cloudaccess .api .CloudPath ;
89import org .cryptomator .cloudaccess .api .CloudProvider ;
910import org .cryptomator .cloudaccess .api .ProgressListener ;
1718import java .util .Optional ;
1819import java .util .concurrent .CompletableFuture ;
1920import java .util .concurrent .CompletionStage ;
21+ import java .util .concurrent .ExecutionException ;
2022
2123public class MetadataCachingProviderDecorator implements CloudProvider {
2224
2325 private final static int DEFAULT_CACHE_TIMEOUT_SECONDS = 10 ;
2426
25- final Cache <CloudPath , Optional <CloudItemMetadata >> itemMetadataCache ;
26- final Cache <CloudPath , Optional <Quota >> quotaCache ;
27+ final Cache <CloudPath , CompletionStage <CloudItemMetadata >> itemMetadataCache ;
28+ final Cache <CloudPath , CompletionStage <Quota >> quotaCache ;
2729 private final CloudProvider delegate ;
2830
2931 public MetadataCachingProviderDecorator (CloudProvider delegate ) {
3032 this (delegate , Duration .ofSeconds ( //
31- Integer .getInteger ("org.cryptomator.cloudaccess.metadatacachingprovider.timeoutSeconds" , DEFAULT_CACHE_TIMEOUT_SECONDS )
33+ Integer .getInteger ("org.cryptomator.cloudaccess.metadatacachingprovider.timeoutSeconds" , DEFAULT_CACHE_TIMEOUT_SECONDS ) //
3234 ));
3335 }
3436
@@ -40,45 +42,29 @@ public MetadataCachingProviderDecorator(CloudProvider delegate, Duration cacheEn
4042
4143 @ Override
4244 public CompletionStage <CloudItemMetadata > itemMetadata (CloudPath node ) {
43- var cachedMetadata = itemMetadataCache .getIfPresent (node );
44- if (cachedMetadata != null ) {
45- return cachedMetadata //
46- .map (CompletableFuture ::completedFuture ) //
47- .orElseGet (() -> CompletableFuture .failedFuture (new NotFoundException ()));
48- } else {
49- return delegate .itemMetadata (node ) //
50- .whenComplete ((metadata , exception ) -> {
51- if (exception == null ) {
52- assert metadata != null ;
53- itemMetadataCache .put (node , Optional .of (metadata ));
54- } else if (exception instanceof NotFoundException ) {
55- itemMetadataCache .put (node , Optional .empty ());
56- } else {
57- itemMetadataCache .invalidate (node );
58- }
59- });
45+ try {
46+ return itemMetadataCache .get (node , () -> delegate .itemMetadata (node ).whenComplete ((metadata , throwable ) -> {
47+ // immediately invalidate cache in case of exceptions (except for NOT FOUND):
48+ if (throwable != null && !(throwable instanceof NotFoundException )) {
49+ itemMetadataCache .invalidate (node );
50+ }
51+ }));
52+ } catch (ExecutionException e ) {
53+ return CompletableFuture .failedFuture (e );
6054 }
6155 }
6256
6357 @ Override
6458 public CompletionStage <Quota > quota (CloudPath folder ) {
65- var cachedMetadata = quotaCache .getIfPresent (folder );
66- if (cachedMetadata != null ) {
67- return cachedMetadata //
68- .map (CompletableFuture ::completedFuture ) //
69- .orElseGet (() -> CompletableFuture .failedFuture (new QuotaNotAvailableException ()));
70- } else {
71- return delegate .quota (folder ) //
72- .whenComplete ((quota , exception ) -> {
73- if (exception == null ) {
74- assert quota != null ;
75- quotaCache .put (folder , Optional .of (quota ));
76- } else if (exception instanceof NotFoundException || exception instanceof QuotaNotAvailableException ) {
77- quotaCache .put (folder , Optional .empty ());
78- } else {
79- quotaCache .invalidate (folder );
80- }
81- });
59+ try {
60+ return quotaCache .get (folder , () -> delegate .quota (folder ).whenComplete ((metadata , throwable ) -> {
61+ // immediately invalidate cache in case of exceptions (except for NOT FOUND and QUOTA NOT AVAILABLE):
62+ if (throwable != null && !(throwable instanceof NotFoundException ) && !(throwable instanceof QuotaNotAvailableException )) {
63+ quotaCache .invalidate (folder );
64+ }
65+ }));
66+ } catch (ExecutionException e ) {
67+ return CompletableFuture .failedFuture (e );
8268 }
8369 }
8470
@@ -89,7 +75,7 @@ public CompletionStage<CloudItemList> list(CloudPath folder, Optional<String> pa
8975 evictIncludingDescendants (folder );
9076 if (exception == null ) {
9177 assert cloudItemList != null ;
92- cloudItemList .getItems ().forEach (metadata -> itemMetadataCache .put (metadata .getPath (), Optional . of (metadata )));
78+ cloudItemList .getItems ().forEach (metadata -> itemMetadataCache .put (metadata .getPath (), CompletableFuture . completedFuture (metadata )));
9379 }
9480 });
9581 }
@@ -118,9 +104,11 @@ public CompletionStage<InputStream> read(CloudPath file, long offset, long count
118104 public CompletionStage <Void > write (CloudPath file , boolean replace , InputStream data , long size , Optional <Instant > lastModified , ProgressListener progressListener ) {
119105 return delegate .write (file , replace , data , size , lastModified , progressListener ) //
120106 .whenComplete ((nullReturn , exception ) -> {
121- if (exception ! = null ) {
122- itemMetadataCache .invalidate (file );
107+ if (exception = = null ) {
108+ itemMetadataCache .put (file , CompletableFuture . completedFuture ( new CloudItemMetadata ( file . getFileName (). toString (), file , CloudItemType . FILE , lastModified , Optional . of ( size ))) );
123109 quotaCache .invalidateAll ();
110+ } else {
111+ itemMetadataCache .invalidate (file );
124112 }
125113 });
126114 }
@@ -130,14 +118,16 @@ public CompletionStage<CloudPath> createFolder(CloudPath folder) {
130118 return delegate .createFolder (folder ) //
131119 .whenComplete ((metadata , exception ) -> {
132120 itemMetadataCache .invalidate (folder );
121+ quotaCache .invalidateAll ();
133122 });
134123 }
135124
136125 @ Override
137126 public CompletionStage <Void > deleteFile (CloudPath file ) {
138127 return delegate .deleteFile (file ) //
139128 .whenComplete ((nullReturn , exception ) -> {
140- itemMetadataCache .put (file , Optional .empty ());
129+ CompletionStage <CloudItemMetadata > future = CompletableFuture .failedFuture (new NotFoundException ());
130+ itemMetadataCache .put (file , future );
141131 quotaCache .invalidateAll ();
142132 });
143133 }
@@ -147,7 +137,8 @@ public CompletionStage<Void> deleteFolder(CloudPath folder) {
147137 return delegate .deleteFolder (folder ) //
148138 .whenComplete ((nullReturn , exception ) -> {
149139 evictIncludingDescendants (folder );
150- itemMetadataCache .put (folder , Optional .empty ());
140+ CompletionStage <CloudItemMetadata > future = CompletableFuture .failedFuture (new NotFoundException ());
141+ itemMetadataCache .put (folder , future );
151142 quotaCache .invalidateAll ();
152143 });
153144 }
@@ -156,8 +147,8 @@ public CompletionStage<Void> deleteFolder(CloudPath folder) {
156147 public CompletionStage <CloudPath > move (CloudPath source , CloudPath target , boolean replace ) {
157148 return delegate .move (source , target , replace ) //
158149 .whenComplete ((path , exception ) -> {
159- itemMetadataCache . invalidate (source );
160- itemMetadataCache . invalidate (target );
150+ evictIncludingDescendants (source );
151+ evictIncludingDescendants (target );
161152 quotaCache .invalidateAll ();
162153 });
163154 }
0 commit comments