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 ;
1011import org .cryptomator .cloudaccess .api .Quota ;
1112import org .cryptomator .cloudaccess .api .exceptions .NotFoundException ;
13+ import org .cryptomator .cloudaccess .api .exceptions .QuotaNotAvailableException ;
1214
1315import java .io .InputStream ;
1416import java .time .Duration ;
1517import java .time .Instant ;
1618import java .util .Optional ;
1719import java .util .concurrent .CompletableFuture ;
1820import java .util .concurrent .CompletionStage ;
21+ import java .util .concurrent .ExecutionException ;
1922
2023public class MetadataCachingProviderDecorator implements CloudProvider {
2124
2225 private final static int DEFAULT_CACHE_TIMEOUT_SECONDS = 10 ;
2326
24- final Cache <CloudPath , Optional <CloudItemMetadata >> itemMetadataCache ;
25- final Cache <CloudPath , Optional <Quota >> quotaCache ;
27+ final Cache <CloudPath , CompletionStage <CloudItemMetadata >> itemMetadataCache ;
28+ final Cache <CloudPath , CompletionStage <Quota >> quotaCache ;
2629 private final CloudProvider delegate ;
2730
2831 public MetadataCachingProviderDecorator (CloudProvider delegate ) {
2932 this (delegate , Duration .ofSeconds ( //
30- Integer .getInteger ("org.cryptomator.cloudaccess.metadatacachingprovider.timeoutSeconds" , DEFAULT_CACHE_TIMEOUT_SECONDS )
33+ Integer .getInteger ("org.cryptomator.cloudaccess.metadatacachingprovider.timeoutSeconds" , DEFAULT_CACHE_TIMEOUT_SECONDS ) //
3134 ));
3235 }
3336
@@ -39,45 +42,29 @@ public MetadataCachingProviderDecorator(CloudProvider delegate, Duration cacheEn
3942
4043 @ Override
4144 public CompletionStage <CloudItemMetadata > itemMetadata (CloudPath node ) {
42- var cachedMetadata = itemMetadataCache .getIfPresent (node );
43- if (cachedMetadata != null ) {
44- return cachedMetadata //
45- .map (CompletableFuture ::completedFuture ) //
46- .orElseGet (() -> CompletableFuture .failedFuture (new NotFoundException ()));
47- } else {
48- return delegate .itemMetadata (node ) //
49- .whenComplete ((metadata , exception ) -> {
50- if (exception == null ) {
51- assert metadata != null ;
52- itemMetadataCache .put (node , Optional .of (metadata ));
53- } else if (exception instanceof NotFoundException ) {
54- itemMetadataCache .put (node , Optional .empty ());
55- } else {
56- itemMetadataCache .invalidate (node );
57- }
58- });
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 );
5954 }
6055 }
6156
6257 @ Override
6358 public CompletionStage <Quota > quota (CloudPath folder ) {
64- var cachedMetadata = quotaCache .getIfPresent (folder );
65- if (cachedMetadata != null ) {
66- return cachedMetadata //
67- .map (CompletableFuture ::completedFuture ) //
68- .orElseGet (() -> CompletableFuture .failedFuture (new NotFoundException ()));
69- } else {
70- return delegate .quota (folder ) //
71- .whenComplete ((quota , exception ) -> {
72- if (exception == null ) {
73- assert quota != null ;
74- quotaCache .put (folder , Optional .of (quota ));
75- } else if (exception instanceof NotFoundException ) {
76- quotaCache .put (folder , Optional .empty ());
77- } else {
78- quotaCache .invalidate (folder );
79- }
80- });
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 );
8168 }
8269 }
8370
@@ -88,7 +75,7 @@ public CompletionStage<CloudItemList> list(CloudPath folder, Optional<String> pa
8875 evictIncludingDescendants (folder );
8976 if (exception == null ) {
9077 assert cloudItemList != null ;
91- cloudItemList .getItems ().forEach (metadata -> itemMetadataCache .put (metadata .getPath (), Optional . of (metadata )));
78+ cloudItemList .getItems ().forEach (metadata -> itemMetadataCache .put (metadata .getPath (), CompletableFuture . completedFuture (metadata )));
9279 }
9380 });
9481 }
@@ -117,9 +104,11 @@ public CompletionStage<InputStream> read(CloudPath file, long offset, long count
117104 public CompletionStage <Void > write (CloudPath file , boolean replace , InputStream data , long size , Optional <Instant > lastModified , ProgressListener progressListener ) {
118105 return delegate .write (file , replace , data , size , lastModified , progressListener ) //
119106 .whenComplete ((nullReturn , exception ) -> {
120- if (exception ! = null ) {
121- 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 ))) );
122109 quotaCache .invalidateAll ();
110+ } else {
111+ itemMetadataCache .invalidate (file );
123112 }
124113 });
125114 }
@@ -129,14 +118,27 @@ public CompletionStage<CloudPath> createFolder(CloudPath folder) {
129118 return delegate .createFolder (folder ) //
130119 .whenComplete ((metadata , exception ) -> {
131120 itemMetadataCache .invalidate (folder );
121+ quotaCache .invalidateAll ();
122+ });
123+ }
124+
125+ @ Override
126+ public CompletionStage <Void > deleteFile (CloudPath file ) {
127+ return delegate .deleteFile (file ) //
128+ .whenComplete ((nullReturn , exception ) -> {
129+ CompletionStage <CloudItemMetadata > future = CompletableFuture .failedFuture (new NotFoundException ());
130+ itemMetadataCache .put (file , future );
131+ quotaCache .invalidateAll ();
132132 });
133133 }
134134
135135 @ Override
136- public CompletionStage <Void > delete (CloudPath node ) {
137- return delegate .delete ( node ) //
136+ public CompletionStage <Void > deleteFolder (CloudPath folder ) {
137+ return delegate .deleteFolder ( folder ) //
138138 .whenComplete ((nullReturn , exception ) -> {
139- evictIncludingDescendants (node );
139+ evictIncludingDescendants (folder );
140+ CompletionStage <CloudItemMetadata > future = CompletableFuture .failedFuture (new NotFoundException ());
141+ itemMetadataCache .put (folder , future );
140142 quotaCache .invalidateAll ();
141143 });
142144 }
@@ -145,8 +147,8 @@ public CompletionStage<Void> delete(CloudPath node) {
145147 public CompletionStage <CloudPath > move (CloudPath source , CloudPath target , boolean replace ) {
146148 return delegate .move (source , target , replace ) //
147149 .whenComplete ((path , exception ) -> {
148- itemMetadataCache . invalidate (source );
149- itemMetadataCache . invalidate (target );
150+ evictIncludingDescendants (source );
151+ evictIncludingDescendants (target );
150152 quotaCache .invalidateAll ();
151153 });
152154 }
0 commit comments