Skip to content
This repository was archived by the owner on Mar 14, 2025. It is now read-only.

Commit 2f8058c

Browse files
committed
CloudProvider:write now returns CompletionStage<Void>
Some implementations of CloudProvider doesn't get the metadata after write is executed and we don't always need it directly.
1 parent 215d36b commit 2f8058c

File tree

12 files changed

+53
-117
lines changed

12 files changed

+53
-117
lines changed

src/main/java/org/cryptomator/cloudaccess/MetadataCachingProviderDecorator.java

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
public class MetadataCachingProviderDecorator implements CloudProvider {
2121

22-
private final CloudProvider delegate;
2322
final Cache<CloudPath, Optional<CloudItemMetadata>> metadataCache;
23+
private final CloudProvider delegate;
2424

2525
public MetadataCachingProviderDecorator(CloudProvider delegate) {
2626
this(delegate, Duration.ofSeconds(10));
@@ -100,19 +100,16 @@ public CompletionStage<InputStream> read(CloudPath file, long offset, long count
100100
}
101101

102102
@Override
103-
public CompletionStage<CloudItemMetadata> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener) {
104-
return delegate.write(file, replace, data, size, lastModified, progressListener).thenApply(metadata -> {
105-
metadataCache.put(file, Optional.of(metadata));
106-
return metadata;
107-
}).handle((metadata, exception) -> {
108-
if (exception == null) {
109-
assert metadata != null;
110-
return CompletableFuture.completedFuture(metadata);
111-
} else {
112-
metadataCache.invalidate(file);
113-
return CompletableFuture.<CloudItemMetadata>failedFuture(exception);
114-
}
115-
}).thenCompose(Function.identity());
103+
public CompletionStage<Void> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener) {
104+
return delegate.write(file, replace, data, size, lastModified, progressListener)
105+
.handle((nullReturn, exception) -> {
106+
if (exception == null) {
107+
return CompletableFuture.completedFuture(nullReturn);
108+
} else {
109+
metadataCache.invalidate(file);
110+
return CompletableFuture.<Void>failedFuture(exception);
111+
}
112+
}).thenCompose(Function.identity());
116113
}
117114

118115
@Override
@@ -157,8 +154,8 @@ public CompletionStage<CloudPath> move(CloudPath source, CloudPath target, boole
157154
}
158155

159156
private void evictIncludingDescendants(CloudPath cleartextPath) {
160-
for(var path : metadataCache.asMap().keySet()) {
161-
if(path.startsWith(cleartextPath)) {
157+
for (var path : metadataCache.asMap().keySet()) {
158+
if (path.startsWith(cleartextPath)) {
162159
metadataCache.invalidate(path);
163160
}
164161
}

src/main/java/org/cryptomator/cloudaccess/api/CloudProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ default CompletionStage<InputStream> read(CloudPath file, ProgressListener progr
124124
* @param size The size of data
125125
* @param lastModified The lastModified which should be provided to the server
126126
* @param progressListener TODO Future use
127-
* @return CompletionStage that will be completed after writing all <code>data</code> and holds the new metadata of the item referenced by <code>file</code>.
127+
* @return CompletionStage that will be completed after writing all <code>data</code>.
128128
*/
129-
CompletionStage<CloudItemMetadata> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener);
129+
CompletionStage<Void> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener);
130130

131131
/**
132132
* Create a folder. Does not create any potentially missing parent directories.

src/main/java/org/cryptomator/cloudaccess/localfs/LocalFsCloudProvider.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public CompletionStage<InputStream> read(CloudPath file, long offset, long count
144144
}
145145

146146
@Override
147-
public CompletionStage<CloudItemMetadata> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener) {
147+
public CompletionStage<Void> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener) {
148148
Path filePath = resolve(file);
149149
var options = replace
150150
? EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)
@@ -153,15 +153,12 @@ public CompletionStage<CloudItemMetadata> write(CloudPath file, boolean replace,
153153
Lock l = lock.writeLock();
154154
l.lock();
155155
try (var ch = FileChannel.open(filePath, options)) {
156-
var tmpSize = ch.transferFrom(Channels.newChannel(data), 0, Long.MAX_VALUE);
157-
var modifiedDate = Files.getLastModifiedTime(filePath).toInstant();
158-
156+
var written = ch.transferFrom(Channels.newChannel(data), 0, Long.MAX_VALUE);
157+
assert size == written : "Written bytes should be equal to provided size";
159158
if(lastModified.isPresent()) {
160159
Files.setLastModifiedTime(filePath, FileTime.from(lastModified.get()));
161160
}
162-
163-
var metadata = new CloudItemMetadata(file.getFileName().toString(), file, CloudItemType.FILE, Optional.of(modifiedDate), Optional.of(tmpSize));
164-
return CompletableFuture.completedFuture(metadata);
161+
return CompletableFuture.completedFuture(null);
165162
} catch (NoSuchFileException e) {
166163
return CompletableFuture.failedFuture(new NotFoundException(e));
167164
} catch (FileAlreadyExistsException e) {

src/main/java/org/cryptomator/cloudaccess/vaultformat8/VaultFormat8ProviderDecorator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,15 @@ private long checkedAdd(long a, long b, long onOverflow) {
137137
}
138138

139139
@Override
140-
public CompletionStage<CloudItemMetadata> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener) {
140+
public CompletionStage<Void> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener) {
141141
return getC9rPath(file).thenCompose(ciphertextPath -> {
142142
fileHeaderCache.evict(ciphertextPath);
143143
var src = Channels.newChannel(data);
144144
var encryptingChannel = new EncryptingReadableByteChannel(src, cryptor);
145145
var encryptedIn = Channels.newInputStream(encryptingChannel);
146146
long numBytes = Cryptors.ciphertextSize(size, cryptor) + cryptor.fileHeaderCryptor().headerSize();
147147
return delegate.write(ciphertextPath, replace, encryptedIn, numBytes, lastModified, progressListener);
148-
}).thenApply(ciphertextMetadata -> toCleartextMetadata(ciphertextMetadata, file.getParent(), file.getFileName().toString()));
148+
});
149149
}
150150

151151
@Override

src/main/java/org/cryptomator/cloudaccess/webdav/WebDavClient.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ private InputStream read(final Request.Builder getRequest, final ProgressListene
183183
}
184184
}
185185

186-
CloudItemMetadata write(final CloudPath file, final boolean replace, final InputStream data, final long size, final Optional<Instant> lastModified, final ProgressListener progressListener) throws CloudProviderException {
186+
void write(final CloudPath file, final boolean replace, final InputStream data, final long size, final Optional<Instant> lastModified, final ProgressListener progressListener) throws CloudProviderException {
187187
if (!replace && exists(file)) {
188188
throw new AlreadyExistsException("CloudNode already exists and replace is false");
189189
}
@@ -197,7 +197,6 @@ CloudItemMetadata write(final CloudPath file, final boolean replace, final Input
197197

198198
try (final var response = httpClient.execute(requestBuilder)) {
199199
checkExecutionSucceeded(response.code());
200-
return itemMetadata(file);
201200
} catch (IOException e) {
202201
throw new CloudProviderException(e);
203202
}

src/main/java/org/cryptomator/cloudaccess/webdav/WebDavCloudProvider.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
import java.util.concurrent.CompletableFuture;
1313
import java.util.concurrent.CompletionStage;
1414

15-
import static java.util.concurrent.CompletableFuture.supplyAsync;
16-
1715
public class WebDavCloudProvider implements CloudProvider {
1816

1917
private final WebDavClient webDavClient;
@@ -28,32 +26,32 @@ public static WebDavCloudProvider from(final WebDavCredential webDavCredential)
2826

2927
@Override
3028
public CompletionStage<CloudItemMetadata> itemMetadata(CloudPath node) {
31-
return supplyAsync(() -> webDavClient.itemMetadata(node));
29+
return CompletableFuture.supplyAsync(() -> webDavClient.itemMetadata(node));
3230
}
3331

3432
@Override
3533
public CompletionStage<CloudItemList> list(CloudPath folder, Optional<String> pageToken) {
36-
return supplyAsync(() -> webDavClient.list(folder));
34+
return CompletableFuture.supplyAsync(() -> webDavClient.list(folder));
3735
}
3836

3937
@Override
4038
public CompletionStage<InputStream> read(CloudPath file, ProgressListener progressListener) {
41-
return supplyAsync(() -> webDavClient.read(file, progressListener));
39+
return CompletableFuture.supplyAsync(() -> webDavClient.read(file, progressListener));
4240
}
4341

4442
@Override
4543
public CompletionStage<InputStream> read(CloudPath file, long offset, long count, ProgressListener progressListener) {
46-
return supplyAsync(() -> webDavClient.read(file, offset, count, progressListener));
44+
return CompletableFuture.supplyAsync(() -> webDavClient.read(file, offset, count, progressListener));
4745
}
4846

4947
@Override
50-
public CompletionStage<CloudItemMetadata> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener) {
51-
return supplyAsync(() -> webDavClient.write(file, replace, data, size, lastModified, progressListener));
48+
public CompletionStage<Void> write(CloudPath file, boolean replace, InputStream data, long size, Optional<Instant> lastModified, ProgressListener progressListener) {
49+
return CompletableFuture.runAsync(() -> webDavClient.write(file, replace, data, size, lastModified, progressListener));
5250
}
5351

5452
@Override
5553
public CompletionStage<CloudPath> createFolder(CloudPath folder) {
56-
return supplyAsync(() -> webDavClient.createFolder(folder));
54+
return CompletableFuture.supplyAsync(() -> webDavClient.createFolder(folder));
5755
}
5856

5957
@Override
@@ -63,7 +61,7 @@ public CompletionStage<Void> delete(CloudPath node) {
6361

6462
@Override
6563
public CompletionStage<CloudPath> move(CloudPath source, CloudPath target, boolean replace) {
66-
return supplyAsync(() -> webDavClient.move(source, target, replace));
64+
return CompletableFuture.supplyAsync(() -> webDavClient.move(source, target, replace));
6765
}
6866

6967
}

src/test/java/org/cryptomator/cloudaccess/MetadataCachingProviderDecoratorTest.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -264,18 +264,12 @@ public void testWriteToFile() {
264264
var updatedFile1Metadata = new CloudItemMetadata(file1Metadata.getName(), file1Metadata.getPath(), CloudItemType.FILE, Optional.of(Instant.EPOCH), Optional.of(15l));
265265

266266
Mockito.when(cloudProvider.write(Mockito.eq(file1Metadata.getPath()), Mockito.eq(false), Mockito.any(InputStream.class), Mockito.eq(15l), Mockito.eq(Optional.empty()), Mockito.eq(ProgressListener.NO_PROGRESS_AWARE)))
267-
.thenReturn(CompletableFuture.completedFuture(updatedFile1Metadata));
267+
.thenReturn(CompletableFuture.completedFuture(null));
268268

269269
var futureResult = decorator.write(file1Metadata.getPath(), false, new ByteArrayInputStream("TOPSECRET!".getBytes(UTF_8)),15l, Optional.empty(), ProgressListener.NO_PROGRESS_AWARE);
270-
var result = Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), () -> futureResult.toCompletableFuture().get());
271-
272-
Assertions.assertEquals(file1Metadata.getPath(), result.getPath());
273-
Assertions.assertEquals(file1Metadata.getName(), result.getName());
274-
Assertions.assertEquals(CloudItemType.FILE, result.getItemType());
275-
Assertions.assertEquals(Optional.of(15l), result.getSize());
276-
Assertions.assertEquals(Optional.of(Instant.EPOCH), result.getLastModifiedDate());
270+
Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), () -> futureResult.toCompletableFuture().get());
277271

278-
Assertions.assertEquals(decorator.metadataCache.getIfPresent(file1Metadata.getPath()).get(), updatedFile1Metadata);
272+
Assertions.assertEquals(decorator.metadataCache.size(), 0l);
279273
}
280274

281275
@Test

src/test/java/org/cryptomator/cloudaccess/localfs/LocalFsCloudProviderTest.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ public void testWriteToNewFile() throws IOException {
9595
var in = new ByteArrayInputStream("hallo welt".getBytes());
9696

9797
var result = provider.write(CloudPath.of("/file"), false, in, 10, Optional.empty(), ProgressListener.NO_PROGRESS_AWARE);
98-
var metaData = Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> result.toCompletableFuture().get());
98+
Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> result.toCompletableFuture().get());
99+
100+
var metaData = provider.itemMetadata(CloudPath.of("/file")).toCompletableFuture().join();
99101

100-
Assertions.assertEquals("file", metaData.getName());
101102
Assertions.assertEquals(CloudPath.of("/file"), metaData.getPath());
102103
Assertions.assertEquals(CloudItemType.FILE, metaData.getItemType());
103104
Assertions.assertTrue(metaData.getSize().isPresent());
@@ -126,7 +127,9 @@ public void testWriteToAndReplaceExistingFile() throws IOException {
126127
var in = new ByteArrayInputStream("hallo welt".getBytes());
127128

128129
var result = provider.write(CloudPath.of("/file"), true, in, 10, Optional.empty(), ProgressListener.NO_PROGRESS_AWARE);
129-
var metaData = Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> result.toCompletableFuture().get());
130+
Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> result.toCompletableFuture().get());
131+
132+
var metaData = provider.itemMetadata(CloudPath.of("/file")).toCompletableFuture().join();
130133

131134
Assertions.assertEquals("file", metaData.getName());
132135
Assertions.assertEquals(CloudPath.of("/file"), metaData.getPath());
@@ -146,7 +149,9 @@ public void testWriteToNewFileUpdateModificationDate() throws IOException {
146149
var modDate = Instant.now().minus(Duration.ofDays(365));
147150

148151
var result = provider.write(CloudPath.of("/file"), false, in, 10, Optional.of(modDate), ProgressListener.NO_PROGRESS_AWARE);
149-
var metaData = Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> result.toCompletableFuture().get());
152+
Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> result.toCompletableFuture().get());
153+
154+
var metaData = provider.itemMetadata(CloudPath.of("/file")).toCompletableFuture().join();
150155

151156
Assertions.assertEquals("file", metaData.getName());
152157
Assertions.assertEquals(CloudPath.of("/file"), metaData.getPath());

src/test/java/org/cryptomator/cloudaccess/vaultformat8/VaultFormat8IntegrationTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ public void testWriteThenReadFile() throws IOException {
3838

3939
// write 100k
4040
var futureMetadata = encryptedProvider.write(path, true, new ByteArrayInputStream(content), content.length, Optional.empty(), ProgressListener.NO_PROGRESS_AWARE);
41-
var metadata = Assertions.assertTimeoutPreemptively(TIMEOUT, () -> futureMetadata.toCompletableFuture().get());
42-
Assertions.assertEquals(content.length, metadata.getSize().get());
41+
Assertions.assertTimeoutPreemptively(TIMEOUT, () -> futureMetadata.toCompletableFuture().get());
4342

4443
// read all bytes
4544
var futureInputStream1 = encryptedProvider.read(path, ProgressListener.NO_PROGRESS_AWARE);

src/test/java/org/cryptomator/cloudaccess/vaultformat8/VaultFormat8ProviderDecoratorTest.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ public void testCreateFolder() {
326326
Mockito.when(fileNameCryptor.hashDirectoryId(Mockito.eq(""))).thenReturn("00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
327327
Mockito.when(fileNameCryptor.hashDirectoryId(AdditionalMatchers.not(Mockito.eq("")))).thenReturn(hashFolder3Id);
328328
Mockito.when(cloudProvider.createFolder(dir3Metadata.getPath())).thenReturn(CompletableFuture.completedFuture(dir3Metadata.getPath()));
329-
Mockito.when(cloudProvider.write(Mockito.eq(dir3Metadata.getPath().resolve("dir.c9r")), Mockito.eq(false), Mockito.any(), Mockito.anyLong(), Mockito.eq(Optional.empty()), Mockito.eq(ProgressListener.NO_PROGRESS_AWARE))).thenReturn(CompletableFuture.completedFuture(dir3Metadata));
329+
Mockito.when(cloudProvider.write(Mockito.eq(dir3Metadata.getPath().resolve("dir.c9r")), Mockito.eq(false), Mockito.any(), Mockito.anyLong(), Mockito.eq(Optional.empty()), Mockito.eq(ProgressListener.NO_PROGRESS_AWARE))).thenReturn(CompletableFuture.completedFuture(null));
330330
Mockito.when(cloudProvider.createFolderIfNonExisting(dataDirFolder3.getParent())).thenReturn(CompletableFuture.completedFuture(dataDirFolder3.getParent()));
331331
Mockito.when(cloudProvider.createFolder(dataDirFolder3)).thenReturn(CompletableFuture.completedFuture(dataDirFolder3));
332332

@@ -359,17 +359,11 @@ public void testWriteToFile() {
359359
InputStream in = invocationOnMock.getArgument(2);
360360
var encrypted = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).readLine();
361361
Assertions.assertEquals("hhhhhtopsecret!", encrypted);
362-
return CompletableFuture.completedFuture(new CloudItemMetadata(file1Metadata.getName(), file1Metadata.getPath(), CloudItemType.FILE, Optional.of(Instant.EPOCH), Optional.of(15l)));
362+
return CompletableFuture.completedFuture(null);
363363
});
364364

365365
var futureResult = decorator.write(CloudPath.of("/File 1"), false, new ByteArrayInputStream("TOPSECRET!".getBytes(UTF_8)),10l, Optional.empty(), ProgressListener.NO_PROGRESS_AWARE);
366-
var result = Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), () -> futureResult.toCompletableFuture().get());
367-
368-
Assertions.assertEquals(CloudPath.of("/File 1"), result.getPath());
369-
Assertions.assertEquals("File 1", result.getName());
370-
Assertions.assertEquals(CloudItemType.FILE, result.getItemType());
371-
Assertions.assertEquals(Optional.of(10l), result.getSize());
372-
Assertions.assertEquals(Optional.of(Instant.EPOCH), result.getLastModifiedDate());
366+
Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), () -> futureResult.toCompletableFuture().get());
373367
}
374368

375369
}

0 commit comments

Comments
 (0)