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 class S3CloudApiExceptions {
public enum S3CloudApiErrorCodes { public static boolean isAccessProblem(String errorCode) {
NO_SUCH_BUCKET("NoSuchBucket"); return errorCode.equals(S3CloudApiErrorCodes.ACCESS_DENIED.getValue())
|| errorCode.equals(S3CloudApiErrorCodes.ACCOUNT_PROBLEM.getValue())
private final String value; || errorCode.equals(S3CloudApiErrorCodes.INVALID_ACCESS_KEY_ID.getValue());
S3CloudApiErrorCodes(final String newValue) {
value = newValue;
}
public String getValue() {
return value;
}
} }
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 android.content.Context;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import org.cryptomator.data.cloud.InterceptingCloudContentRepository; import org.cryptomator.data.cloud.InterceptingCloudContentRepository;
import org.cryptomator.domain.S3Cloud; import org.cryptomator.domain.S3Cloud;
import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.BackendException;
import org.cryptomator.domain.exception.FatalBackendException; import org.cryptomator.domain.exception.FatalBackendException;
import org.cryptomator.domain.exception.NetworkConnectionException; import org.cryptomator.domain.exception.NetworkConnectionException;
import org.cryptomator.domain.exception.NoSuchBucketException;
import org.cryptomator.domain.exception.authentication.WrongCredentialsException; import org.cryptomator.domain.exception.authentication.WrongCredentialsException;
import org.cryptomator.domain.repository.CloudContentRepository; import org.cryptomator.domain.repository.CloudContentRepository;
import org.cryptomator.domain.usecases.ProgressAware; import org.cryptomator.domain.usecases.ProgressAware;
@ -31,29 +34,36 @@ class S3CloudContentRepository extends InterceptingCloudContentRepository<S3Clou
this.cloud = cloud; this.cloud = cloud;
} }
//TODO: add proper error handling
@Override @Override
protected void throwWrappedIfRequired(Exception e) throws BackendException { protected void throwWrappedIfRequired(Exception e) throws BackendException {
// throwConnectionErrorIfRequired(e); throwNoSuchBucketExceptionIfRequired(e);
// throwWrongCredentialsExceptionIfRequired(e); throwConnectionErrorIfRequired(e);
throwWrongCredentialsExceptionIfRequired(e);
} }
// private void throwConnectionErrorIfRequired(Exception e) throws NetworkConnectionException { private void throwNoSuchBucketExceptionIfRequired(Exception e) throws NoSuchBucketException {
// if (contains(e, IOException.class)) { if (e instanceof AmazonS3Exception) {
// throw new NetworkConnectionException(e); String errorCode = ((AmazonS3Exception)e).getErrorCode();
// } if(S3CloudApiExceptions.isNoSuchBucketException(errorCode)) {
// } throw new NoSuchBucketException(cloud.s3Bucket());
// }
// private void throwWrongCredentialsExceptionIfRequired(Exception e) { }
// if (e instanceof ApiError) { }
// int errorCode = ((ApiError) e).errorCode();
// if (errorCode == PCloudApiError.PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() // private void throwConnectionErrorIfRequired(Exception e) throws NetworkConnectionException {
// || errorCode == PCloudApiError.PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue()) { if (contains(e, IOException.class)) {
// throw new WrongCredentialsException(cloud); 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> { 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.TransferUtility;
import com.amazonaws.mobileconnectors.s3.transferutility.UploadOptions; import com.amazonaws.mobileconnectors.s3.transferutility.UploadOptions;
import com.amazonaws.services.s3.AmazonS3; 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.CopyObjectResult;
import com.amazonaws.services.s3.model.DeleteObjectsRequest; import com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion; 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.BackendException;
import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException;
import org.cryptomator.domain.exception.FatalBackendException; import org.cryptomator.domain.exception.FatalBackendException;
import org.cryptomator.domain.exception.ForbiddenException;
import org.cryptomator.domain.exception.NoSuchBucketException; import org.cryptomator.domain.exception.NoSuchBucketException;
import org.cryptomator.domain.exception.NoSuchCloudFileException; import org.cryptomator.domain.exception.NoSuchCloudFileException;
import org.cryptomator.domain.exception.UnauthorizedException;
import org.cryptomator.domain.exception.authentication.WrongCredentialsException; import org.cryptomator.domain.exception.authentication.WrongCredentialsException;
import org.cryptomator.domain.usecases.ProgressAware; import org.cryptomator.domain.usecases.ProgressAware;
import org.cryptomator.domain.usecases.cloud.DataSource; import org.cryptomator.domain.usecases.cloud.DataSource;
@ -46,6 +49,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -161,8 +165,12 @@ class S3Impl {
InputStream emptyContent = new ByteArrayInputStream(new byte[0]); InputStream emptyContent = new ByteArrayInputStream(new byte[0]);
PutObjectRequest putObjectRequest = new PutObjectRequest(cloud.s3Bucket(), folder.getKey(), emptyContent, metadata); try {
client().putObject(putObjectRequest); 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()); return S3CloudNodeFactory.folder(folder.getParent(), folder.getName());
} }
@ -206,10 +214,14 @@ class S3Impl {
final CompletableFuture<Optional<ObjectMetadata>> result = new CompletableFuture<>(); final CompletableFuture<Optional<ObjectMetadata>> result = new CompletableFuture<>();
if (size <= CHUNKED_UPLOAD_MAX_SIZE) { try {
uploadFile(file, data, progressAware, result, size); if (size <= CHUNKED_UPLOAD_MAX_SIZE) {
} else { uploadFile(file, data, progressAware, result, size);
uploadChunkedFile(file, data, progressAware, result, size); } else {
uploadChunkedFile(file, data, progressAware, result, size);
}
} catch(AmazonS3Exception ex) {
handleApiError(ex, file.getName());
} }
try { try {
@ -336,16 +348,20 @@ class S3Impl {
GetObjectRequest request = new GetObjectRequest(cloud.s3Bucket(), file.getPath()); GetObjectRequest request = new GetObjectRequest(cloud.s3Bucket(), file.getPath());
request.setGeneralProgressListener(listener); 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()) { 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("S3Impl").e(e, "Failed to write downloaded file in LRU cache"); 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; 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);
}
}
} }