feat(S3): add error handling

This commit is contained in:
Manuel Jenny 2021-04-30 09:18:07 +02:00
parent 68203fb88a
commit 13b9fbedf5
No known key found for this signature in database
GPG Key ID: 1C80FE62B2BEAA18
4 changed files with 100 additions and 44 deletions

View File

@ -0,0 +1,22 @@
package org.cryptomator.data.cloud.s3;
public enum S3CloudApiErrorCodes {
ACCESS_DENIED("AccessDenied"),
ACCOUNT_PROBLEM("AccountProblem"),
INTERNAL_ERROR("InternalError"),
INVALID_ACCESS_KEY_ID("InvalidAccessKeyId"),
INVALID_BUCKET_NAME("InvalidBucketName"),
INVALID_OBJECT_STATE("InvalidObjectState"),
NO_SUCH_BUCKET("NoSuchBucket"),
NO_SUCH_KEY("NoSuchKey");
private final String value;
S3CloudApiErrorCodes(final String newValue) {
value = newValue;
}
public String getValue() {
return value;
}
}

View File

@ -2,18 +2,13 @@ package org.cryptomator.data.cloud.s3;
public class S3CloudApiExceptions {
public enum S3CloudApiErrorCodes {
NO_SUCH_BUCKET("NoSuchBucket");
private final String value;
S3CloudApiErrorCodes(final String newValue) {
value = newValue;
}
public String getValue() {
return value;
}
public static boolean isAccessProblem(String errorCode) {
return errorCode.equals(S3CloudApiErrorCodes.ACCESS_DENIED.getValue())
|| errorCode.equals(S3CloudApiErrorCodes.ACCOUNT_PROBLEM.getValue())
|| errorCode.equals(S3CloudApiErrorCodes.INVALID_ACCESS_KEY_ID.getValue());
}
public static boolean isNoSuchBucketException(String errorCode) {
return errorCode.equals(S3CloudApiErrorCodes.NO_SUCH_BUCKET.getValue());
}
}

View File

@ -2,11 +2,14 @@ package org.cryptomator.data.cloud.s3;
import android.content.Context;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import org.cryptomator.data.cloud.InterceptingCloudContentRepository;
import org.cryptomator.domain.S3Cloud;
import org.cryptomator.domain.exception.BackendException;
import org.cryptomator.domain.exception.FatalBackendException;
import org.cryptomator.domain.exception.NetworkConnectionException;
import org.cryptomator.domain.exception.NoSuchBucketException;
import org.cryptomator.domain.exception.authentication.WrongCredentialsException;
import org.cryptomator.domain.repository.CloudContentRepository;
import org.cryptomator.domain.usecases.ProgressAware;
@ -31,29 +34,36 @@ class S3CloudContentRepository extends InterceptingCloudContentRepository<S3Clou
this.cloud = cloud;
}
//TODO: add proper error handling
@Override
protected void throwWrappedIfRequired(Exception e) throws BackendException {
// throwConnectionErrorIfRequired(e);
// throwWrongCredentialsExceptionIfRequired(e);
throwNoSuchBucketExceptionIfRequired(e);
throwConnectionErrorIfRequired(e);
throwWrongCredentialsExceptionIfRequired(e);
}
// private void throwConnectionErrorIfRequired(Exception e) throws NetworkConnectionException {
// if (contains(e, IOException.class)) {
// throw new NetworkConnectionException(e);
// }
// }
//
// private void throwWrongCredentialsExceptionIfRequired(Exception e) {
// if (e instanceof ApiError) {
// int errorCode = ((ApiError) e).errorCode();
// if (errorCode == PCloudApiError.PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() //
// || errorCode == PCloudApiError.PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue()) {
// throw new WrongCredentialsException(cloud);
// }
// }
// }
private void throwNoSuchBucketExceptionIfRequired(Exception e) throws NoSuchBucketException {
if (e instanceof AmazonS3Exception) {
String errorCode = ((AmazonS3Exception)e).getErrorCode();
if(S3CloudApiExceptions.isNoSuchBucketException(errorCode)) {
throw new NoSuchBucketException(cloud.s3Bucket());
}
}
}
private void throwConnectionErrorIfRequired(Exception e) throws NetworkConnectionException {
if (contains(e, IOException.class)) {
throw new NetworkConnectionException(e);
}
}
private void throwWrongCredentialsExceptionIfRequired(Exception e) {
if (e instanceof AmazonS3Exception) {
String errorCode = ((AmazonS3Exception) e).getErrorCode();
if (S3CloudApiExceptions.isAccessProblem(errorCode)) {
throw new WrongCredentialsException(cloud);
}
}
}
private static class Intercepted implements CloudContentRepository<S3Cloud, S3Node, S3Folder, S3File> {

View File

@ -8,6 +8,7 @@ import com.amazonaws.mobileconnectors.s3.transferutility.TransferState;
import com.amazonaws.mobileconnectors.s3.transferutility.TransferUtility;
import com.amazonaws.mobileconnectors.s3.transferutility.UploadOptions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.CopyObjectResult;
import com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion;
@ -26,8 +27,10 @@ import org.cryptomator.domain.S3Cloud;
import org.cryptomator.domain.exception.BackendException;
import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException;
import org.cryptomator.domain.exception.FatalBackendException;
import org.cryptomator.domain.exception.ForbiddenException;
import org.cryptomator.domain.exception.NoSuchBucketException;
import org.cryptomator.domain.exception.NoSuchCloudFileException;
import org.cryptomator.domain.exception.UnauthorizedException;
import org.cryptomator.domain.exception.authentication.WrongCredentialsException;
import org.cryptomator.domain.usecases.ProgressAware;
import org.cryptomator.domain.usecases.cloud.DataSource;
@ -46,6 +49,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
@ -161,8 +165,12 @@ class S3Impl {
InputStream emptyContent = new ByteArrayInputStream(new byte[0]);
PutObjectRequest putObjectRequest = new PutObjectRequest(cloud.s3Bucket(), folder.getKey(), emptyContent, metadata);
client().putObject(putObjectRequest);
try {
PutObjectRequest putObjectRequest = new PutObjectRequest(cloud.s3Bucket(), folder.getKey(), emptyContent, metadata);
client().putObject(putObjectRequest);
} catch(AmazonS3Exception ex) {
handleApiError(ex, folder.getName());
}
return S3CloudNodeFactory.folder(folder.getParent(), folder.getName());
}
@ -206,10 +214,14 @@ class S3Impl {
final CompletableFuture<Optional<ObjectMetadata>> result = new CompletableFuture<>();
if (size <= CHUNKED_UPLOAD_MAX_SIZE) {
uploadFile(file, data, progressAware, result, size);
} else {
uploadChunkedFile(file, data, progressAware, result, size);
try {
if (size <= CHUNKED_UPLOAD_MAX_SIZE) {
uploadFile(file, data, progressAware, result, size);
} else {
uploadChunkedFile(file, data, progressAware, result, size);
}
} catch(AmazonS3Exception ex) {
handleApiError(ex, file.getName());
}
try {
@ -336,16 +348,20 @@ class S3Impl {
GetObjectRequest request = new GetObjectRequest(cloud.s3Bucket(), file.getPath());
request.setGeneralProgressListener(listener);
S3Object s3Object = client().getObject(request);
try {
S3Object s3Object = client().getObject(request);
CopyStream.copyStreamToStream(s3Object.getObjectContent(), data);
CopyStream.copyStreamToStream(s3Object.getObjectContent(), data);
if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) {
try {
storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get());
} catch (IOException e) {
Timber.tag("S3Impl").e(e, "Failed to write downloaded file in LRU cache");
if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) {
try {
storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get());
} catch (IOException e) {
Timber.tag("S3Impl").e(e, "Failed to write downloaded file in LRU cache");
}
}
} catch(AmazonS3Exception ex) {
handleApiError(ex, file.getName());
}
}
@ -386,4 +402,17 @@ class S3Impl {
return true;
}
private void handleApiError(AmazonS3Exception ex, String name) throws BackendException {
String errorCode = ex.getErrorCode();
if (S3CloudApiExceptions.isAccessProblem(errorCode)) {
throw new ForbiddenException();
} else if (S3CloudApiErrorCodes.NO_SUCH_BUCKET.getValue().equals(errorCode)) {
throw new NoSuchBucketException(name);
} else if (S3CloudApiErrorCodes.NO_SUCH_KEY.getValue().equals(errorCode)) {
throw new NoSuchCloudFileException(name);
} else {
throw new FatalBackendException(ex);
}
}
}