feat(S3): initial structure (non functional S3Impl)
This commit is contained in:
parent
0224532c45
commit
00a63228c1
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
55
data/src/main/java/org/cryptomator/data/cloud/s3/S3File.java
Normal file
55
data/src/main/java/org/cryptomator/data/cloud/s3/S3File.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
370
data/src/main/java/org/cryptomator/data/cloud/s3/S3Impl.java
Normal file
370
data/src/main/java/org/cryptomator/data/cloud/s3/S3Impl.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
data/src/main/java/org/cryptomator/data/cloud/s3/S3Node.java
Normal file
10
data/src/main/java/org/cryptomator/data/cloud/s3/S3Node.java
Normal file
@ -0,0 +1,10 @@
|
||||
package org.cryptomator.data.cloud.s3;
|
||||
|
||||
import org.cryptomator.domain.CloudNode;
|
||||
|
||||
interface S3Node extends CloudNode {
|
||||
|
||||
@Override
|
||||
S3Folder getParent();
|
||||
|
||||
}
|
167
domain/src/main/java/org/cryptomator/domain/S3Cloud.java
Normal file
167
domain/src/main/java/org/cryptomator/domain/S3Cloud.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user