feat(S3): implement exists(), write(), read() and delete()

- add implementation for `exists()`
- add implementation for `write()`
- add implementation for `read()`
- add implementation for `delete()`
- add support for `currentAccount()`
This commit is contained in:
Manuel Jenny 2021-04-20 13:43:08 +02:00
parent c63db47e56
commit 4afd6bc703
No known key found for this signature in database
GPG Key ID: 1C80FE62B2BEAA18

View File

@ -2,24 +2,20 @@ package org.cryptomator.data.cloud.s3;
import android.content.Context; import android.content.Context;
import com.amazonaws.event.ProgressListener;
import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.AbstractPutObjectRequest; import com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ListObjectsV2Result; import com.amazonaws.services.s3.model.ListObjectsV2Result;
import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.Owner;
import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult; import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary; import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.pcloud.sdk.ApiError; import com.pcloud.sdk.ApiError;
import com.pcloud.sdk.DataSink;
import com.pcloud.sdk.DownloadOptions;
import com.pcloud.sdk.FileLink;
import com.pcloud.sdk.ProgressListener;
import com.pcloud.sdk.RemoteEntry;
import com.pcloud.sdk.RemoteFile;
import com.pcloud.sdk.RemoteFolder;
import com.pcloud.sdk.UploadOptions;
import com.pcloud.sdk.UserInfo;
import com.tomclaw.cache.DiskLruCache; import com.tomclaw.cache.DiskLruCache;
import org.cryptomator.data.util.CopyStream; import org.cryptomator.data.util.CopyStream;
@ -48,14 +44,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;
import okio.Source;
import timber.log.Timber; import timber.log.Timber;
import static org.cryptomator.domain.usecases.cloud.Progress.progress; import static org.cryptomator.domain.usecases.cloud.Progress.progress;
@ -111,23 +102,24 @@ class S3Impl {
} }
public S3File file(S3Folder parent, String name, Optional<Long> size) throws BackendException, IOException { public S3File file(S3Folder parent, String name, Optional<Long> size) throws BackendException, IOException {
return S3CloudNodeFactory.file(parent, name, size, parent.getPath() + "/" + name); return S3CloudNodeFactory.file(parent, name, size, parent.getPath() + SUFFIX + name);
} }
public S3Folder folder(S3Folder parent, String name) throws IOException, BackendException { public S3Folder folder(S3Folder parent, String name) throws IOException, BackendException {
return S3CloudNodeFactory.folder(parent, name, parent.getPath() + "/" + name); return S3CloudNodeFactory.folder(parent, name, parent.getPath() + SUFFIX + name + SUFFIX);
} }
public boolean exists(S3Node node) throws IOException, BackendException { public boolean exists(S3Node node) {
try { String path = node.getPath();
if (node instanceof S3Folder) { if (node instanceof S3Folder) {
client().loadFolder(node.getPath()).execute(); path += SUFFIX;
} else { }
client().loadFile(node.getPath()).execute();
} ObjectListing result = client().listObjects(cloud.s3Bucket(), path);
if (result.getObjectSummaries().size() > 0) {
return true; return true;
} catch (ApiError ex) { } else {
handleApiError(ex, PCloudApiError.ignoreExistsSet, node.getName());
return false; return false;
} }
} }
@ -190,49 +182,30 @@ class S3Impl {
} }
progressAware.onProgress(Progress.started(UploadState.upload(file))); progressAware.onProgress(Progress.started(UploadState.upload(file)));
UploadOptions uploadOptions = UploadOptions.DEFAULT;
if (replace) {
uploadOptions = UploadOptions.OVERRIDE_FILE;
}
RemoteFile uploadedFile = uploadFile(file, data, progressAware, uploadOptions, size); PutObjectResult result = uploadFile(file, data, progressAware, size);
progressAware.onProgress(Progress.completed(UploadState.upload(file))); progressAware.onProgress(Progress.completed(UploadState.upload(file)));
return S3CloudNodeFactory.file(file.getParent(), uploadedFile); return S3CloudNodeFactory.file(file.getParent(), file.getName(), result);
} }
private RemoteFile uploadFile(final S3File file, DataSource data, final ProgressAware<UploadState> progressAware, UploadOptions uploadOptions, final long size) // private PutObjectResult uploadFile(final S3File file, DataSource data, final ProgressAware<UploadState> progressAware, final long size) //
throws IOException, BackendException { throws IOException, BackendException {
ProgressListener listener = (done, total) -> progressAware.onProgress( // ProgressListener listener = progressEvent -> progressAware.onProgress( //
progress(UploadState.upload(file)) // progress(UploadState.upload(file)) //
.between(0) // .between(0) //
.and(size) // .and(size) //
.withValue(done)); .withValue(progressEvent.getBytesTransferred()));
com.pcloud.sdk.DataSource pCloudDataSource = new com.pcloud.sdk.DataSource() { ObjectMetadata metadata = new ObjectMetadata();
@Override metadata.setContentLength(data.size(context).get());
public long contentLength() {
return data.size(context).get();
}
@Override PutObjectRequest request = new PutObjectRequest(cloud.s3Bucket(), file.getPath(), data.open(context), metadata);
public void writeTo(BufferedSink sink) throws IOException { request.setGeneralProgressListener(listener);
try (Source source = Okio.source(data.open(context))) {
sink.writeAll(source);
}
}
};
try { return client().putObject(request);
return client() //
.createFile(file.getParent().getPath(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) //
.execute();
} catch (ApiError ex) {
handleApiError(ex, file.getName());
throw new FatalBackendException(ex);
}
} }
public void read(S3File file, Optional<File> encryptedTmpFile, OutputStream data, final ProgressAware<DownloadState> progressAware) throws IOException, BackendException { public void read(S3File file, Optional<File> encryptedTmpFile, OutputStream data, final ProgressAware<DownloadState> progressAware) throws IOException, BackendException {
@ -241,15 +214,15 @@ class S3Impl {
Optional<String> cacheKey = Optional.empty(); Optional<String> cacheKey = Optional.empty();
Optional<File> cacheFile = Optional.empty(); Optional<File> cacheFile = Optional.empty();
RemoteFile remoteFile; ObjectListing objectListing;
if (sharedPreferencesHandler.useLruCache() && createLruCache(sharedPreferencesHandler.lruCacheSize())) { if (sharedPreferencesHandler.useLruCache() && createLruCache(sharedPreferencesHandler.lruCacheSize())) {
try { objectListing = client().listObjects(cloud.s3Bucket(), file.getPath());
remoteFile = client().loadFile(file.getPath()).execute().asFile(); if (objectListing.getObjectSummaries().size() != 1) {
cacheKey = Optional.of(remoteFile.fileId() + remoteFile.hash()); throw new NoSuchCloudFileException(file.getPath());
} catch (ApiError ex) {
handleApiError(ex, file.getName());
} }
S3ObjectSummary summary = objectListing.getObjectSummaries().get(0);
cacheKey = Optional.of(summary.getKey() + summary.getETag());
File cachedFile = diskLruCache.get(cacheKey.get()); File cachedFile = diskLruCache.get(cacheKey.get());
cacheFile = cachedFile != null ? Optional.of(cachedFile) : Optional.empty(); cacheFile = cachedFile != null ? Optional.of(cachedFile) : Optional.empty();
@ -259,7 +232,7 @@ class S3Impl {
try { try {
retrieveFromLruCache(cacheFile.get(), data); retrieveFromLruCache(cacheFile.get(), data);
} catch (IOException e) { } catch (IOException e) {
Timber.tag("PCloudImpl").w(e, "Error while retrieving content from Cache, get from web request"); Timber.tag("S3Impl").w(e, "Error while retrieving content from Cache, get from web request");
writeToData(file, data, encryptedTmpFile, cacheKey, progressAware); writeToData(file, data, encryptedTmpFile, cacheKey, progressAware);
} }
} else { } else {
@ -274,61 +247,51 @@ class S3Impl {
final Optional<File> encryptedTmpFile, // final Optional<File> encryptedTmpFile, //
final Optional<String> cacheKey, // final Optional<String> cacheKey, //
final ProgressAware<DownloadState> progressAware) throws IOException, BackendException { final ProgressAware<DownloadState> progressAware) throws IOException, BackendException {
try {
FileLink fileLink = client().createFileLink(file.getPath(), DownloadOptions.DEFAULT).execute();
ProgressListener listener = (done, total) -> progressAware.onProgress( // ProgressListener listener = progressEvent -> progressAware.onProgress( //
progress(DownloadState.download(file)) // progress(DownloadState.download(file)) //
.between(0) // .between(0) //
.and(file.getSize().orElse(Long.MAX_VALUE)) // .and(file.getSize().orElse(Long.MAX_VALUE)) //
.withValue(done)); .withValue(progressEvent.getBytesTransferred()));
DataSink sink = new DataSink() { GetObjectRequest request = new GetObjectRequest(cloud.s3Bucket(), file.getPath());
@Override request.setGeneralProgressListener(listener);
public void readAll(BufferedSource source) {
CopyStream.copyStreamToStream(source.inputStream(), data);
}
};
client().download(fileLink, sink, listener).execute(); S3Object s3Object = client().getObject(request);
} catch (ApiError ex) {
handleApiError(ex, file.getName()); CopyStream.copyStreamToStream(s3Object.getObjectContent(), data);
}
if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) { if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) {
try { try {
storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get()); storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get());
} catch (IOException e) { } catch (IOException e) {
Timber.tag("PCloudImpl").e(e, "Failed to write downloaded file in LRU cache"); Timber.tag("S3Impl").e(e, "Failed to write downloaded file in LRU cache");
} }
} }
} }
public void delete(S3Node node) throws IOException, BackendException { public void delete(S3Node node) throws IOException, BackendException {
try { if (node instanceof S3Folder) {
if (node instanceof S3Folder) { ObjectListing listing = client().listObjects(cloud.s3Bucket(), node.getPath() + SUFFIX);
client() // List<KeyVersion> keys = new ArrayList<>();
.deleteFolder(node.getPath(), true).execute(); for (S3ObjectSummary summary : listing.getObjectSummaries()) {
} else { keys.add(new KeyVersion(summary.getKey()));
client() //
.deleteFile(node.getPath()).execute();
} }
} catch (ApiError ex) {
handleApiError(ex, node.getName()); DeleteObjectsRequest request = new DeleteObjectsRequest(cloud.s3Bucket());
request.withKeys(keys);
client().deleteObjects(request);
} else {
client().deleteObject(cloud.s3Bucket(), node.getPath());
} }
} }
public String currentAccount() throws IOException, BackendException { public String currentAccount() {
try { Owner currentAccount = client() //
UserInfo currentAccount = client() // .getS3AccountOwner();
.getUserInfo() // return currentAccount.getDisplayName();
.execute();
return currentAccount.email();
} catch (ApiError ex) {
handleApiError(ex);
throw new FatalBackendException(ex);
}
} }
private boolean createLruCache(int cacheSize) { private boolean createLruCache(int cacheSize) {