feat(S3): initial structure (non functional S3Impl)

This commit is contained in:
Manuel Jenny 2021-04-16 14:09:05 +02:00
parent 0224532c45
commit 00a63228c1
No known key found for this signature in database
GPG Key ID: 1C80FE62B2BEAA18
10 changed files with 982 additions and 0 deletions

View File

@ -0,0 +1,24 @@
package org.cryptomator.data.cloud.s3;
import org.cryptomator.domain.Cloud;
import org.cryptomator.domain.S3Cloud;
class RootS3Folder extends S3Folder {
private final S3Cloud cloud;
public RootS3Folder(S3Cloud cloud) {
super(null, "", "");
this.cloud = cloud;
}
@Override
public S3Cloud getCloud() {
return cloud;
}
@Override
public S3Folder withCloud(Cloud cloud) {
return new RootS3Folder((S3Cloud) cloud);
}
}

View File

@ -0,0 +1,39 @@
package org.cryptomator.data.cloud.s3;
import android.content.Context;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.cryptomator.domain.S3Cloud;
import org.cryptomator.util.crypto.CredentialCryptor;
class S3ClientFactory {
private AmazonS3 apiClient;
public AmazonS3 getClient(S3Cloud cloud, Context context) {
if (apiClient == null) {
apiClient = createApiClient(cloud, context);
}
return apiClient;
}
private AmazonS3 createApiClient(S3Cloud cloud, Context context) {
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(cloud.s3Endpoint(), cloud.s3Region());
AWSCredentials credentials = new BasicAWSCredentials(cloud.accessKey(), decrypt(cloud.secretKey(), context));
return AmazonS3ClientBuilder.standard().withEndpointConfiguration(endpointConfiguration).withCredentials(new AWSStaticCredentialsProvider(credentials)).build();
}
private String decrypt(String password, Context context) {
return CredentialCryptor //
.getInstance(context) //
.decrypt(password);
}
}

View File

@ -0,0 +1,193 @@
package org.cryptomator.data.cloud.s3;
import android.content.Context;
import com.pcloud.sdk.ApiError;
import org.cryptomator.data.cloud.InterceptingCloudContentRepository;
import org.cryptomator.domain.PCloud;
import org.cryptomator.domain.exception.BackendException;
import org.cryptomator.domain.exception.FatalBackendException;
import org.cryptomator.domain.exception.NetworkConnectionException;
import org.cryptomator.domain.exception.authentication.WrongCredentialsException;
import org.cryptomator.domain.repository.CloudContentRepository;
import org.cryptomator.domain.usecases.ProgressAware;
import org.cryptomator.domain.usecases.cloud.DataSource;
import org.cryptomator.domain.usecases.cloud.DownloadState;
import org.cryptomator.domain.usecases.cloud.UploadState;
import org.cryptomator.util.Optional;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import static org.cryptomator.util.ExceptionUtil.contains;
class S3CloudContentRepository extends InterceptingCloudContentRepository<PCloud, S3Node, S3Folder, S3File> {
private final PCloud cloud;
public S3CloudContentRepository(PCloud cloud, Context context) {
super(new Intercepted(cloud, context));
this.cloud = cloud;
}
@Override
protected void throwWrappedIfRequired(Exception e) throws BackendException {
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 static class Intercepted implements CloudContentRepository<PCloud, S3Node, S3Folder, S3File> {
private final S3Impl cloud;
public Intercepted(PCloud cloud, Context context) {
this.cloud = new S3Impl(context, cloud);
}
public S3Folder root(PCloud cloud) {
return this.cloud.root();
}
@Override
public S3Folder resolve(PCloud cloud, String path) throws BackendException {
try {
return this.cloud.resolve(path);
} catch (IOException ex) {
throw new FatalBackendException(ex);
}
}
@Override
public S3File file(S3Folder parent, String name) throws BackendException {
try {
return cloud.file(parent, name);
} catch (IOException ex) {
throw new FatalBackendException(ex);
}
}
@Override
public S3File file(S3Folder parent, String name, Optional<Long> size) throws BackendException {
try {
return cloud.file(parent, name, size);
} catch (IOException ex) {
throw new FatalBackendException(ex);
}
}
@Override
public S3Folder folder(S3Folder parent, String name) throws BackendException {
try {
return cloud.folder(parent, name);
} catch (IOException ex) {
throw new FatalBackendException(ex);
}
}
@Override
public boolean exists(S3Node node) throws BackendException {
try {
return cloud.exists(node);
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public List<S3Node> list(S3Folder folder) throws BackendException {
try {
return cloud.list(folder);
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public S3Folder create(S3Folder folder) throws BackendException {
try {
return cloud.create(folder);
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public S3Folder move(S3Folder source, S3Folder target) throws BackendException {
try {
return (S3Folder) cloud.move(source, target);
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public S3File move(S3File source, S3File target) throws BackendException {
try {
return (S3File) cloud.move(source, target);
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public S3File write(S3File uploadFile, DataSource data, ProgressAware<UploadState> progressAware, boolean replace, long size) throws BackendException {
try {
return cloud.write(uploadFile, data, progressAware, replace, size);
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public void read(S3File file, Optional<File> encryptedTmpFile, OutputStream data, ProgressAware<DownloadState> progressAware) throws BackendException {
try {
cloud.read(file, encryptedTmpFile, data, progressAware);
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public void delete(S3Node node) throws BackendException {
try {
cloud.delete(node);
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public String checkAuthenticationAndRetrieveCurrentAccount(PCloud cloud) throws BackendException {
try {
return this.cloud.currentAccount();
} catch (IOException e) {
throw new FatalBackendException(e);
}
}
@Override
public void logout(PCloud cloud) throws BackendException {
// empty
}
}
}

View File

@ -0,0 +1,35 @@
package org.cryptomator.data.cloud.s3;
import android.content.Context;
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
import org.cryptomator.domain.Cloud;
import org.cryptomator.domain.PCloud;
import org.cryptomator.domain.repository.CloudContentRepository;
import javax.inject.Inject;
import javax.inject.Singleton;
import static org.cryptomator.domain.CloudType.PCLOUD;
@Singleton
public class S3CloudContentRepositoryFactory implements CloudContentRepositoryFactory {
private final Context context;
@Inject
public S3CloudContentRepositoryFactory(Context context) {
this.context = context;
}
@Override
public boolean supports(Cloud cloud) {
return cloud.type() == PCLOUD;
}
@Override
public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) {
return new S3CloudContentRepository((PCloud) cloud, context);
}
}

View File

@ -0,0 +1,47 @@
package org.cryptomator.data.cloud.s3;
import com.pcloud.sdk.RemoteEntry;
import com.pcloud.sdk.RemoteFile;
import com.pcloud.sdk.RemoteFolder;
import org.cryptomator.util.Optional;
class S3CloudNodeFactory {
public static S3File file(S3Folder parent, RemoteFile file) {
return new S3File(parent, file.name(), getNodePath(parent, file.name()), Optional.ofNullable(file.size()), Optional.ofNullable(file.lastModified()));
}
public static S3File file(S3Folder parent, String name, Optional<Long> size) {
return new S3File(parent, name, getNodePath(parent, name), size, Optional.empty());
}
public static S3File file(S3Folder folder, String name, Optional<Long> size, String path) {
return new S3File(folder, name, path, size, Optional.empty());
}
public static S3Folder folder(S3Folder parent, RemoteFolder folder) {
return new S3Folder(parent, folder.name(), getNodePath(parent, folder.name()));
}
public static S3Folder folder(S3Folder parent, String name) {
return new S3Folder(parent, name, getNodePath(parent, name));
}
public static S3Folder folder(S3Folder parent, String name, String path) {
return new S3Folder(parent, name, path);
}
public static String getNodePath(S3Folder parent, String name) {
return parent.getPath() + "/" + name;
}
public static S3Node from(S3Folder parent, RemoteEntry remoteEntry) {
if (remoteEntry instanceof RemoteFile) {
return file(parent, remoteEntry.asFile());
} else {
return folder(parent, remoteEntry.asFolder());
}
}
}

View File

@ -0,0 +1,55 @@
package org.cryptomator.data.cloud.s3;
import org.cryptomator.domain.Cloud;
import org.cryptomator.domain.CloudFile;
import org.cryptomator.util.Optional;
import java.util.Date;
class S3File implements CloudFile, S3Node {
private final S3Folder parent;
private final String name;
private final String path;
private final Optional<Long> size;
private final Optional<Date> modified;
public S3File(S3Folder parent, String name, String path, Optional<Long> size, Optional<Date> modified) {
this.parent = parent;
this.name = name;
this.path = path;
this.size = size;
this.modified = modified;
}
@Override
public Cloud getCloud() {
return parent.getCloud();
}
@Override
public String getName() {
return name;
}
@Override
public String getPath() {
return path;
}
@Override
public S3Folder getParent() {
return parent;
}
@Override
public Optional<Long> getSize() {
return size;
}
@Override
public Optional<Date> getModified() {
return modified;
}
}

View File

@ -0,0 +1,42 @@
package org.cryptomator.data.cloud.s3;
import org.cryptomator.domain.Cloud;
import org.cryptomator.domain.CloudFolder;
class S3Folder implements CloudFolder, S3Node {
private final S3Folder parent;
private final String name;
private final String path;
public S3Folder(S3Folder parent, String name, String path) {
this.parent = parent;
this.name = name;
this.path = path;
}
@Override
public Cloud getCloud() {
return parent.getCloud();
}
@Override
public String getName() {
return name;
}
@Override
public String getPath() {
return path;
}
@Override
public S3Folder getParent() {
return parent;
}
@Override
public S3Folder withCloud(Cloud cloud) {
return new S3Folder(parent.withCloud(cloud), name, path);
}
}

View File

@ -0,0 +1,370 @@
package org.cryptomator.data.cloud.s3;
import android.content.Context;
import com.amazonaws.services.s3.AmazonS3;
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 org.cryptomator.data.util.CopyStream;
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.NetworkConnectionException;
import org.cryptomator.domain.exception.NoSuchCloudFileException;
import org.cryptomator.domain.exception.UnauthorizedException;
import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedException;
import org.cryptomator.domain.exception.authentication.WrongCredentialsException;
import org.cryptomator.domain.usecases.ProgressAware;
import org.cryptomator.domain.usecases.cloud.DataSource;
import org.cryptomator.domain.usecases.cloud.DownloadState;
import org.cryptomator.domain.usecases.cloud.Progress;
import org.cryptomator.domain.usecases.cloud.UploadState;
import org.cryptomator.util.Optional;
import org.cryptomator.util.SharedPreferencesHandler;
import org.cryptomator.util.file.LruFileCacheUtil;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;
import okio.Source;
import timber.log.Timber;
import static org.cryptomator.domain.usecases.cloud.Progress.progress;
import static org.cryptomator.util.file.LruFileCacheUtil.Cache.PCLOUD;
import static org.cryptomator.util.file.LruFileCacheUtil.retrieveFromLruCache;
import static org.cryptomator.util.file.LruFileCacheUtil.storeToLruCache;
class S3Impl {
private final S3ClientFactory clientFactory = new S3ClientFactory();
private final S3Cloud cloud;
private final RootS3Folder root;
private final Context context;
private final SharedPreferencesHandler sharedPreferencesHandler;
private DiskLruCache diskLruCache;
S3Impl(Context context, S3Cloud cloud) {
if (cloud.accessKey() == null || cloud.secretKey() == null) {
throw new NoAuthenticationProvidedException(cloud);
}
this.context = context;
this.cloud = cloud;
this.root = new RootS3Folder(cloud);
this.sharedPreferencesHandler = new SharedPreferencesHandler(context);
}
private AmazonS3 client() {
return clientFactory.getClient(cloud, context);
}
public S3Folder root() {
return root;
}
public S3Folder resolve(String path) throws IOException, BackendException {
if (path.startsWith("/")) {
path = path.substring(1);
}
String[] names = path.split("/");
S3Folder folder = root;
for (String name : names) {
folder = folder(folder, name);
}
return folder;
}
public S3File file(S3Folder parent, String name) throws BackendException, IOException {
return file(parent, name, Optional.empty());
}
public S3File file(S3Folder parent, String name, Optional<Long> size) throws BackendException, IOException {
return S3CloudNodeFactory.file(parent, name, size, parent.getPath() + "/" + name);
}
public S3Folder folder(S3Folder parent, String name) throws IOException, BackendException {
return S3CloudNodeFactory.folder(parent, name, parent.getPath() + "/" + name);
}
public boolean exists(S3Node node) throws IOException, BackendException {
try {
if (node instanceof S3Folder) {
client().loadFolder(node.getPath()).execute();
} else {
client().loadFile(node.getPath()).execute();
}
return true;
} catch (ApiError ex) {
handleApiError(ex, PCloudApiError.ignoreExistsSet, node.getName());
return false;
}
}
public List<S3Node> list(S3Folder folder) throws IOException, BackendException {
List<S3Node> result = new ArrayList<>();
try {
RemoteFolder listFolderResult = client().listFolder(folder.getPath()).execute();
List<RemoteEntry> entryMetadata = listFolderResult.children();
for (RemoteEntry metadata : entryMetadata) {
result.add(S3CloudNodeFactory.from(folder, metadata));
}
return result;
} catch (ApiError ex) {
handleApiError(ex, folder.getName());
throw new FatalBackendException(ex);
}
}
public S3Folder create(S3Folder folder) throws IOException, BackendException {
if (!exists(folder.getParent())) {
folder = new S3Folder( //
create(folder.getParent()), //
folder.getName(), folder.getPath() //
);
}
try {
RemoteFolder createdFolder = client() //
.createFolder(folder.getPath()) //
.execute();
return S3CloudNodeFactory.folder(folder.getParent(), createdFolder);
} catch (ApiError ex) {
handleApiError(ex, folder.getName());
throw new FatalBackendException(ex);
}
}
public S3Node move(S3Node source, S3Node target) throws IOException, BackendException {
if (exists(target)) {
throw new CloudNodeAlreadyExistsException(target.getName());
}
try {
if (source instanceof S3Folder) {
return S3CloudNodeFactory.from(target.getParent(), client().moveFolder(source.getPath(), target.getPath()).execute());
} else {
return S3CloudNodeFactory.from(target.getParent(), client().moveFile(source.getPath(), target.getPath()).execute());
}
} catch (ApiError ex) {
if (PCloudApiError.isCloudNodeAlreadyExistsException(ex.errorCode())) {
throw new CloudNodeAlreadyExistsException(target.getName());
} else if (PCloudApiError.isNoSuchCloudFileException(ex.errorCode())) {
throw new NoSuchCloudFileException(source.getName());
} else {
handleApiError(ex, PCloudApiError.ignoreMoveSet, null);
}
throw new FatalBackendException(ex);
}
}
public S3File write(S3File file, DataSource data, final ProgressAware<UploadState> progressAware, boolean replace, long size) throws IOException, BackendException {
if (!replace && exists(file)) {
throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false");
}
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);
progressAware.onProgress(Progress.completed(UploadState.upload(file)));
return S3CloudNodeFactory.file(file.getParent(), uploadedFile);
}
private RemoteFile uploadFile(final S3File file, DataSource data, final ProgressAware<UploadState> progressAware, UploadOptions uploadOptions, final long size) //
throws IOException, BackendException {
ProgressListener listener = (done, total) -> progressAware.onProgress( //
progress(UploadState.upload(file)) //
.between(0) //
.and(size) //
.withValue(done));
com.pcloud.sdk.DataSource pCloudDataSource = new com.pcloud.sdk.DataSource() {
@Override
public long contentLength() {
return data.size(context).get();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
try (Source source = Okio.source(data.open(context))) {
sink.writeAll(source);
}
}
};
try {
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 {
progressAware.onProgress(Progress.started(DownloadState.download(file)));
Optional<String> cacheKey = Optional.empty();
Optional<File> cacheFile = Optional.empty();
RemoteFile remoteFile;
if (sharedPreferencesHandler.useLruCache() && createLruCache(sharedPreferencesHandler.lruCacheSize())) {
try {
remoteFile = client().loadFile(file.getPath()).execute().asFile();
cacheKey = Optional.of(remoteFile.fileId() + remoteFile.hash());
} catch (ApiError ex) {
handleApiError(ex, file.getName());
}
File cachedFile = diskLruCache.get(cacheKey.get());
cacheFile = cachedFile != null ? Optional.of(cachedFile) : Optional.empty();
}
if (sharedPreferencesHandler.useLruCache() && cacheFile.isPresent()) {
try {
retrieveFromLruCache(cacheFile.get(), data);
} catch (IOException e) {
Timber.tag("PCloudImpl").w(e, "Error while retrieving content from Cache, get from web request");
writeToData(file, data, encryptedTmpFile, cacheKey, progressAware);
}
} else {
writeToData(file, data, encryptedTmpFile, cacheKey, progressAware);
}
progressAware.onProgress(Progress.completed(DownloadState.download(file)));
}
private void writeToData(final S3File file, //
final OutputStream data, //
final Optional<File> encryptedTmpFile, //
final Optional<String> cacheKey, //
final ProgressAware<DownloadState> progressAware) throws IOException, BackendException {
try {
FileLink fileLink = client().createFileLink(file.getPath(), DownloadOptions.DEFAULT).execute();
ProgressListener listener = (done, total) -> progressAware.onProgress( //
progress(DownloadState.download(file)) //
.between(0) //
.and(file.getSize().orElse(Long.MAX_VALUE)) //
.withValue(done));
DataSink sink = new DataSink() {
@Override
public void readAll(BufferedSource source) {
CopyStream.copyStreamToStream(source.inputStream(), data);
}
};
client().download(fileLink, sink, listener).execute();
} catch (ApiError ex) {
handleApiError(ex, file.getName());
}
if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) {
try {
storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get());
} catch (IOException e) {
Timber.tag("PCloudImpl").e(e, "Failed to write downloaded file in LRU cache");
}
}
}
public void delete(S3Node node) throws IOException, BackendException {
try {
if (node instanceof S3Folder) {
client() //
.deleteFolder(node.getPath(), true).execute();
} else {
client() //
.deleteFile(node.getPath()).execute();
}
} catch (ApiError ex) {
handleApiError(ex, node.getName());
}
}
public String currentAccount() throws IOException, BackendException {
try {
UserInfo currentAccount = client() //
.getUserInfo() //
.execute();
return currentAccount.email();
} catch (ApiError ex) {
handleApiError(ex);
throw new FatalBackendException(ex);
}
}
private boolean createLruCache(int cacheSize) {
if (diskLruCache == null) {
try {
diskLruCache = DiskLruCache.create(new LruFileCacheUtil(context).resolve(PCLOUD), cacheSize);
} catch (IOException e) {
Timber.tag("PCloudImpl").e(e, "Failed to setup LRU cache");
return false;
}
}
return true;
}
private void handleApiError(ApiError ex) throws BackendException {
handleApiError(ex, null, null);
}
private void handleApiError(ApiError ex, String name) throws BackendException {
handleApiError(ex, null, name);
}
private void handleApiError(ApiError ex, Set<Integer> errorCodes, String name) throws BackendException {
if (errorCodes == null || !errorCodes.contains(ex.errorCode())) {
int errorCode = ex.errorCode();
if (PCloudApiError.isCloudNodeAlreadyExistsException(errorCode)) {
throw new CloudNodeAlreadyExistsException(name);
} else if (PCloudApiError.isForbiddenException(errorCode)) {
throw new ForbiddenException();
} else if (PCloudApiError.isNetworkConnectionException(errorCode)) {
throw new NetworkConnectionException(ex);
} else if (PCloudApiError.isNoSuchCloudFileException(errorCode)) {
throw new NoSuchCloudFileException(name);
} else if (PCloudApiError.isWrongCredentialsException(errorCode)) {
throw new WrongCredentialsException(cloud);
} else if (PCloudApiError.isUnauthorizedException(errorCode)) {
throw new UnauthorizedException();
} else {
throw new FatalBackendException(ex);
}
}
}
}

View File

@ -0,0 +1,10 @@
package org.cryptomator.data.cloud.s3;
import org.cryptomator.domain.CloudNode;
interface S3Node extends CloudNode {
@Override
S3Folder getParent();
}

View File

@ -0,0 +1,167 @@
package org.cryptomator.domain;
import org.jetbrains.annotations.NotNull;
public class S3Cloud implements Cloud {
private final Long id;
private final String accessKey;
private final String secretKey;
private final String s3Bucket;
private final String s3Endpoint;
private final String s3Region;
private S3Cloud(Builder builder) {
this.id = builder.id;
this.accessKey = builder.accessKey;
this.secretKey = builder.secretKey;
this.s3Bucket = builder.s3Bucket;
this.s3Endpoint = builder.s3Endpoint;
this.s3Region = builder.s3Region;
}
public static Builder aPCloud() {
return new Builder();
}
public static Builder aCopyOf(S3Cloud s3Cloud) {
return new Builder() //
.withId(s3Cloud.id()) //
.withAccessKey(s3Cloud.accessKey()) //
.withSecretKey(s3Cloud.secretKey()) //
.withS3Bucket(s3Cloud.s3Bucket()) //
.withS3Endpoint(s3Cloud.s3Endpoint()) //
.withS3Region(s3Cloud.s3Region());
}
@Override
public Long id() {
return id;
}
public String accessKey() {
return accessKey;
}
public String secretKey() {
return secretKey;
}
public String s3Bucket() {
return s3Bucket;
}
public String s3Endpoint() {
return s3Endpoint;
}
public String s3Region() {
return s3Region;
}
@Override
public CloudType type() {
return CloudType.PCLOUD;
}
@Override
public boolean configurationMatches(Cloud cloud) {
return cloud instanceof S3Cloud && configurationMatches((S3Cloud) cloud);
}
private boolean configurationMatches(S3Cloud cloud) {
//FIXME: figure out when it is necessary to create a new cloud
return s3Bucket.equals(cloud.s3Bucket) && s3Endpoint.equals(cloud.s3Endpoint) && s3Region.equals(cloud.s3Region);
}
@Override
public boolean predefined() {
return false;
}
@Override
public boolean persistent() {
return true;
}
@Override
public boolean requiresNetwork() {
return true;
}
@NotNull
@Override
public String toString() {
return "S3";
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
if (obj == this) {
return true;
}
return internalEquals((S3Cloud) obj);
}
@Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
private boolean internalEquals(S3Cloud obj) {
return id != null && id.equals(obj.id);
}
public static class Builder {
private Long id;
private String accessKey;
private String secretKey;
private String s3Bucket;
private String s3Endpoint;
private String s3Region;
private Builder() {
}
public Builder withId(Long id) {
this.id = id;
return this;
}
public Builder withAccessKey(String accessKey) {
this.accessKey = accessKey;
return this;
}
public Builder withSecretKey(String secretKey) {
this.secretKey = secretKey;
return this;
}
public Builder withS3Bucket(String s3Bucket) {
this.s3Bucket = s3Bucket;
return this;
}
public Builder withS3Endpoint(String s3Endpoint) {
this.s3Endpoint = s3Endpoint;
return this;
}
public Builder withS3Region(String s3Region) {
this.s3Region = s3Region;
return this;
}
public S3Cloud build() {
return new S3Cloud(this);
}
}
}