Merge pull request #283 from mjenny/feature/native-pcloud-support
feat: introduce native pCloud support
This commit is contained in:
commit
10ce9dee79
@ -0,0 +1,112 @@
|
||||
package org.cryptomator.data.cloud.pcloud;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class PCloudApiError {
|
||||
|
||||
public static final HashSet<Integer> ignoreExistsSet = new HashSet<>( //
|
||||
Arrays.asList( //
|
||||
PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), //
|
||||
PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), //
|
||||
PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), //
|
||||
PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(), //
|
||||
PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() //
|
||||
));
|
||||
public static final HashSet<Integer> ignoreMoveSet = new HashSet<>( //
|
||||
Arrays.asList( //
|
||||
PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(), //
|
||||
PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), //
|
||||
PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), //
|
||||
PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), //
|
||||
PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() //
|
||||
) //
|
||||
);
|
||||
|
||||
public static boolean isCloudNodeAlreadyExistsException(int errorCode) {
|
||||
return errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue();
|
||||
}
|
||||
|
||||
public static boolean isFatalBackendException(int errorCode) {
|
||||
return errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.UPLOAD_NOT_FOUND.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.TRANSFER_NOT_FOUND.getValue();
|
||||
}
|
||||
|
||||
public static boolean isForbiddenException(int errorCode) {
|
||||
return errorCode == PCloudApiErrorCodes.ACCESS_DENIED.getValue();
|
||||
}
|
||||
|
||||
public static boolean isNetworkConnectionException(int errorCode) {
|
||||
return errorCode == PCloudApiErrorCodes.CONNECTION_BROKE.getValue();
|
||||
}
|
||||
|
||||
public static boolean isNoSuchCloudFileException(int errorCode) {
|
||||
return errorCode == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue();
|
||||
}
|
||||
|
||||
public static boolean isWrongCredentialsException(int errorCode) {
|
||||
return errorCode == PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue();
|
||||
}
|
||||
|
||||
public static boolean isUnauthorizedException(int errorCode) {
|
||||
return errorCode == PCloudApiErrorCodes.LOGIN_FAILED.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.LOGIN_REQUIRED.getValue() //
|
||||
|| errorCode == PCloudApiErrorCodes.TOO_MANY_LOGIN_TRIES_FROM_IP.getValue();
|
||||
}
|
||||
|
||||
public enum PCloudApiErrorCodes {
|
||||
LOGIN_REQUIRED(1000), //
|
||||
NO_FULL_PATH_OR_NAME_FOLDER_ID_PROVIDED(1001), //
|
||||
NO_FULL_PATH_OR_FOLDER_ID_PROVIDED(1002), //
|
||||
NO_FILE_ID_OR_PATH_PROVIDED(1004), //
|
||||
INVALID_DATE_TIME_FORMAT(1013), //
|
||||
NO_DESTINATION_PROVIDED(1016), //
|
||||
INVALID_FOLDER_ID(1017), //
|
||||
INVALID_DESTINATION(1037), //
|
||||
PROVIDE_URL(1040), //
|
||||
UPLOAD_NOT_FOUND(1900), //
|
||||
TRANSFER_NOT_FOUND(1902), //
|
||||
LOGIN_FAILED(2000), //
|
||||
INVALID_FILE_OR_FOLDER_NAME(2001), //
|
||||
COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST(2002), //
|
||||
ACCESS_DENIED(2003), //
|
||||
FILE_OR_FOLDER_ALREADY_EXISTS(2004), //
|
||||
DIRECTORY_DOES_NOT_EXIST(2005), //
|
||||
FOLDER_NOT_EMPTY(2006), //
|
||||
CANNOT_DELETE_ROOT_FOLDER(2007), //
|
||||
USER_OVER_QUOTA(2008), //
|
||||
FILE_NOT_FOUND(2009), //
|
||||
INVALID_PATH(2010), //
|
||||
SHARED_FOLDER_IN_SHARED_FOLDER(2023), //
|
||||
ACTIVE_SHARES_OR_SHAREREQUESTS_PRESENT(2028), //
|
||||
CONNECTION_BROKE(2041), //
|
||||
CANNOT_RENAME_ROOT_FOLDER(2042), //
|
||||
CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), //
|
||||
FILE_OR_FOLDER_NOT_FOUND(2055), //
|
||||
NO_FILE_UPLOAD_DETECTED(2088), //
|
||||
INVALID_ACCESS_TOKEN(2094), //
|
||||
ACCESS_TOKEN_REVOKED(2095), //
|
||||
TRANSFER_OVER_QUOTA(2097), //
|
||||
TARGET_FOLDER_DOES_NOT_EXIST(2208), //
|
||||
TOO_MANY_LOGIN_TRIES_FROM_IP(4000), //
|
||||
INTERNAL_ERROR(5000), //
|
||||
INTERNAL_UPLOAD_ERROR(5001);
|
||||
|
||||
private final int value;
|
||||
|
||||
PCloudApiErrorCodes(final int newValue) {
|
||||
value = newValue;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package org.cryptomator.data.cloud.pcloud;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.pcloud.sdk.ApiClient;
|
||||
import com.pcloud.sdk.Authenticators;
|
||||
import com.pcloud.sdk.PCloudSdk;
|
||||
|
||||
import org.cryptomator.data.cloud.okhttplogging.HttpLoggingInterceptor;
|
||||
import org.cryptomator.util.crypto.CredentialCryptor;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static org.cryptomator.data.util.NetworkTimeout.CONNECTION;
|
||||
import static org.cryptomator.data.util.NetworkTimeout.READ;
|
||||
import static org.cryptomator.data.util.NetworkTimeout.WRITE;
|
||||
|
||||
class PCloudClientFactory {
|
||||
|
||||
private ApiClient apiClient;
|
||||
|
||||
private static Interceptor httpLoggingInterceptor(Context context) {
|
||||
return new HttpLoggingInterceptor(message -> Timber.tag("OkHttp").d(message), context);
|
||||
}
|
||||
|
||||
public ApiClient getClient(String accessToken, String url, Context context) {
|
||||
if (apiClient == null) {
|
||||
apiClient = createApiClient(accessToken, url, context);
|
||||
}
|
||||
return apiClient;
|
||||
}
|
||||
|
||||
private ApiClient createApiClient(String accessToken, String url, Context context) {
|
||||
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient() //
|
||||
.newBuilder() //
|
||||
.connectTimeout(CONNECTION.getTimeout(), CONNECTION.getUnit()) //
|
||||
.readTimeout(READ.getTimeout(), READ.getUnit()) //
|
||||
.writeTimeout(WRITE.getTimeout(), WRITE.getUnit()) //
|
||||
.addInterceptor(httpLoggingInterceptor(context)); //;
|
||||
|
||||
OkHttpClient okHttpClient = okHttpClientBuilder.build();
|
||||
|
||||
return PCloudSdk.newClientBuilder().authenticator(Authenticators.newOAuthAuthenticator(decrypt(accessToken, context))).withClient(okHttpClient).apiHost(url).create();
|
||||
}
|
||||
|
||||
private String decrypt(String password, Context context) {
|
||||
return CredentialCryptor //
|
||||
.getInstance(context) //
|
||||
.decrypt(password);
|
||||
}
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
package org.cryptomator.data.cloud.pcloud;
|
||||
|
||||
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 PCloudContentRepository extends InterceptingCloudContentRepository<PCloud, PCloudNode, PCloudFolder, PCloudFile> {
|
||||
|
||||
private final PCloud cloud;
|
||||
|
||||
public PCloudContentRepository(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, PCloudNode, PCloudFolder, PCloudFile> {
|
||||
|
||||
private final PCloudImpl cloud;
|
||||
|
||||
public Intercepted(PCloud cloud, Context context) {
|
||||
this.cloud = new PCloudImpl(context, cloud);
|
||||
}
|
||||
|
||||
public PCloudFolder root(PCloud cloud) {
|
||||
return this.cloud.root();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFolder resolve(PCloud cloud, String path) throws BackendException {
|
||||
try {
|
||||
return this.cloud.resolve(path);
|
||||
} catch (IOException ex) {
|
||||
throw new FatalBackendException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFile file(PCloudFolder parent, String name) throws BackendException {
|
||||
try {
|
||||
return cloud.file(parent, name);
|
||||
} catch (IOException ex) {
|
||||
throw new FatalBackendException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFile file(PCloudFolder parent, String name, Optional<Long> size) throws BackendException {
|
||||
try {
|
||||
return cloud.file(parent, name, size);
|
||||
} catch (IOException ex) {
|
||||
throw new FatalBackendException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFolder folder(PCloudFolder parent, String name) throws BackendException {
|
||||
try {
|
||||
return cloud.folder(parent, name);
|
||||
} catch (IOException ex) {
|
||||
throw new FatalBackendException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(PCloudNode node) throws BackendException {
|
||||
try {
|
||||
return cloud.exists(node);
|
||||
} catch (IOException e) {
|
||||
throw new FatalBackendException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PCloudNode> list(PCloudFolder folder) throws BackendException {
|
||||
try {
|
||||
return cloud.list(folder);
|
||||
} catch (IOException e) {
|
||||
throw new FatalBackendException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFolder create(PCloudFolder folder) throws BackendException {
|
||||
try {
|
||||
return cloud.create(folder);
|
||||
} catch (IOException e) {
|
||||
throw new FatalBackendException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFolder move(PCloudFolder source, PCloudFolder target) throws BackendException {
|
||||
try {
|
||||
return (PCloudFolder) cloud.move(source, target);
|
||||
} catch (IOException e) {
|
||||
throw new FatalBackendException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFile move(PCloudFile source, PCloudFile target) throws BackendException {
|
||||
try {
|
||||
return (PCloudFile) cloud.move(source, target);
|
||||
} catch (IOException e) {
|
||||
throw new FatalBackendException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFile write(PCloudFile 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(PCloudFile 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(PCloudNode 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.pcloud;
|
||||
|
||||
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 PCloudContentRepositoryFactory implements CloudContentRepositoryFactory {
|
||||
|
||||
private final Context context;
|
||||
|
||||
@Inject
|
||||
public PCloudContentRepositoryFactory(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Cloud cloud) {
|
||||
return cloud.type() == PCLOUD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) {
|
||||
return new PCloudContentRepository((PCloud) cloud, context);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package org.cryptomator.data.cloud.pcloud;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.CloudFile;
|
||||
import org.cryptomator.util.Optional;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
class PCloudFile implements CloudFile, PCloudNode {
|
||||
|
||||
private final PCloudFolder parent;
|
||||
private final String name;
|
||||
private final String path;
|
||||
private final Optional<Long> size;
|
||||
private final Optional<Date> modified;
|
||||
|
||||
public PCloudFile(PCloudFolder 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 PCloudFolder 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.pcloud;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.CloudFolder;
|
||||
|
||||
class PCloudFolder implements CloudFolder, PCloudNode {
|
||||
|
||||
private final PCloudFolder parent;
|
||||
private final String name;
|
||||
private final String path;
|
||||
|
||||
public PCloudFolder(PCloudFolder 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 PCloudFolder getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFolder withCloud(Cloud cloud) {
|
||||
return new PCloudFolder(parent.withCloud(cloud), name, path);
|
||||
}
|
||||
}
|
@ -0,0 +1,370 @@
|
||||
package org.cryptomator.data.cloud.pcloud;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.pcloud.sdk.ApiClient;
|
||||
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.PCloud;
|
||||
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 PCloudImpl {
|
||||
|
||||
private final PCloudClientFactory clientFactory = new PCloudClientFactory();
|
||||
private final PCloud cloud;
|
||||
private final RootPCloudFolder root;
|
||||
private final Context context;
|
||||
|
||||
private final SharedPreferencesHandler sharedPreferencesHandler;
|
||||
private DiskLruCache diskLruCache;
|
||||
|
||||
PCloudImpl(Context context, PCloud cloud) {
|
||||
if (cloud.accessToken() == null) {
|
||||
throw new NoAuthenticationProvidedException(cloud);
|
||||
}
|
||||
|
||||
this.context = context;
|
||||
this.cloud = cloud;
|
||||
this.root = new RootPCloudFolder(cloud);
|
||||
this.sharedPreferencesHandler = new SharedPreferencesHandler(context);
|
||||
}
|
||||
|
||||
private ApiClient client() {
|
||||
return clientFactory.getClient(cloud.accessToken(), cloud.url(), context);
|
||||
}
|
||||
|
||||
public PCloudFolder root() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public PCloudFolder resolve(String path) throws IOException, BackendException {
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
String[] names = path.split("/");
|
||||
PCloudFolder folder = root;
|
||||
for (String name : names) {
|
||||
folder = folder(folder, name);
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
public PCloudFile file(PCloudFolder parent, String name) throws BackendException, IOException {
|
||||
return file(parent, name, Optional.empty());
|
||||
}
|
||||
|
||||
public PCloudFile file(PCloudFolder parent, String name, Optional<Long> size) throws BackendException, IOException {
|
||||
return PCloudNodeFactory.file(parent, name, size, parent.getPath() + "/" + name);
|
||||
}
|
||||
|
||||
public PCloudFolder folder(PCloudFolder parent, String name) throws IOException, BackendException {
|
||||
return PCloudNodeFactory.folder(parent, name, parent.getPath() + "/" + name);
|
||||
}
|
||||
|
||||
public boolean exists(PCloudNode node) throws IOException, BackendException {
|
||||
try {
|
||||
if (node instanceof PCloudFolder) {
|
||||
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<PCloudNode> list(PCloudFolder folder) throws IOException, BackendException {
|
||||
List<PCloudNode> result = new ArrayList<>();
|
||||
|
||||
try {
|
||||
RemoteFolder listFolderResult = client().listFolder(folder.getPath()).execute();
|
||||
List<RemoteEntry> entryMetadata = listFolderResult.children();
|
||||
for (RemoteEntry metadata : entryMetadata) {
|
||||
result.add(PCloudNodeFactory.from(folder, metadata));
|
||||
}
|
||||
return result;
|
||||
} catch (ApiError ex) {
|
||||
handleApiError(ex, folder.getName());
|
||||
throw new FatalBackendException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public PCloudFolder create(PCloudFolder folder) throws IOException, BackendException {
|
||||
if (!exists(folder.getParent())) {
|
||||
folder = new PCloudFolder( //
|
||||
create(folder.getParent()), //
|
||||
folder.getName(), folder.getPath() //
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
RemoteFolder createdFolder = client() //
|
||||
.createFolder(folder.getPath()) //
|
||||
.execute();
|
||||
return PCloudNodeFactory.folder(folder.getParent(), createdFolder);
|
||||
} catch (ApiError ex) {
|
||||
handleApiError(ex, folder.getName());
|
||||
throw new FatalBackendException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public PCloudNode move(PCloudNode source, PCloudNode target) throws IOException, BackendException {
|
||||
if (exists(target)) {
|
||||
throw new CloudNodeAlreadyExistsException(target.getName());
|
||||
}
|
||||
|
||||
try {
|
||||
if (source instanceof PCloudFolder) {
|
||||
return PCloudNodeFactory.from(target.getParent(), client().moveFolder(source.getPath(), target.getPath()).execute());
|
||||
} else {
|
||||
return PCloudNodeFactory.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 PCloudFile write(PCloudFile 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 PCloudNodeFactory.file(file.getParent(), uploadedFile);
|
||||
|
||||
}
|
||||
|
||||
private RemoteFile uploadFile(final PCloudFile 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(PCloudFile 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 PCloudFile 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(PCloudNode node) throws IOException, BackendException {
|
||||
try {
|
||||
if (node instanceof PCloudFolder) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.cryptomator.data.cloud.pcloud;
|
||||
|
||||
import org.cryptomator.domain.CloudNode;
|
||||
|
||||
interface PCloudNode extends CloudNode {
|
||||
|
||||
@Override
|
||||
PCloudFolder getParent();
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package org.cryptomator.data.cloud.pcloud;
|
||||
|
||||
import com.pcloud.sdk.RemoteEntry;
|
||||
import com.pcloud.sdk.RemoteFile;
|
||||
import com.pcloud.sdk.RemoteFolder;
|
||||
|
||||
import org.cryptomator.util.Optional;
|
||||
|
||||
class PCloudNodeFactory {
|
||||
|
||||
public static PCloudFile file(PCloudFolder parent, RemoteFile file) {
|
||||
return new PCloudFile(parent, file.name(), getNodePath(parent, file.name()), Optional.ofNullable(file.size()), Optional.ofNullable(file.lastModified()));
|
||||
}
|
||||
|
||||
public static PCloudFile file(PCloudFolder parent, String name, Optional<Long> size) {
|
||||
return new PCloudFile(parent, name, getNodePath(parent, name), size, Optional.empty());
|
||||
}
|
||||
|
||||
public static PCloudFile file(PCloudFolder folder, String name, Optional<Long> size, String path) {
|
||||
return new PCloudFile(folder, name, path, size, Optional.empty());
|
||||
}
|
||||
|
||||
public static PCloudFolder folder(PCloudFolder parent, RemoteFolder folder) {
|
||||
return new PCloudFolder(parent, folder.name(), getNodePath(parent, folder.name()));
|
||||
}
|
||||
|
||||
public static PCloudFolder folder(PCloudFolder parent, String name) {
|
||||
return new PCloudFolder(parent, name, getNodePath(parent, name));
|
||||
}
|
||||
|
||||
public static PCloudFolder folder(PCloudFolder parent, String name, String path) {
|
||||
return new PCloudFolder(parent, name, path);
|
||||
}
|
||||
|
||||
public static String getNodePath(PCloudFolder parent, String name) {
|
||||
return parent.getPath() + "/" + name;
|
||||
}
|
||||
|
||||
public static PCloudNode from(PCloudFolder parent, RemoteEntry remoteEntry) {
|
||||
if (remoteEntry instanceof RemoteFile) {
|
||||
return file(parent, remoteEntry.asFile());
|
||||
} else {
|
||||
return folder(parent, remoteEntry.asFolder());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.cryptomator.data.cloud.pcloud;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.PCloud;
|
||||
|
||||
class RootPCloudFolder extends PCloudFolder {
|
||||
|
||||
private final PCloud cloud;
|
||||
|
||||
public RootPCloudFolder(PCloud cloud) {
|
||||
super(null, "", "");
|
||||
this.cloud = cloud;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloud getCloud() {
|
||||
return cloud;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PCloudFolder withCloud(Cloud cloud) {
|
||||
return new RootPCloudFolder((PCloud) cloud);
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ internal class Upgrade2To3 @Inject constructor(private val context: Context) : D
|
||||
.columns(listOf("ACCESS_TOKEN"))
|
||||
.where("TYPE", Sql.eq("DROPBOX"))
|
||||
.executeOn(db).use {
|
||||
if(it.moveToFirst()) {
|
||||
if (it.moveToFirst()) {
|
||||
Sql.update("CLOUD_ENTITY")
|
||||
.set("ACCESS_TOKEN", Sql.toString(encrypt(it.getString(it.getColumnIndex("ACCESS_TOKEN")))))
|
||||
.where("TYPE", Sql.eq("DROPBOX"));
|
||||
|
@ -182,7 +182,9 @@ public class VaultEntity extends DatabaseEntity {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/** called by internal mechanisms, do not call yourself. */
|
||||
/**
|
||||
* called by internal mechanisms, do not call yourself.
|
||||
*/
|
||||
@Generated(hash = 674742652)
|
||||
public void __setDaoSession(DaoSession daoSession) {
|
||||
this.daoSession = daoSession;
|
||||
|
@ -7,6 +7,7 @@ import org.cryptomator.domain.DropboxCloud;
|
||||
import org.cryptomator.domain.GoogleDriveCloud;
|
||||
import org.cryptomator.domain.LocalStorageCloud;
|
||||
import org.cryptomator.domain.OnedriveCloud;
|
||||
import org.cryptomator.domain.PCloud;
|
||||
import org.cryptomator.domain.WebDavCloud;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -16,6 +17,7 @@ import static org.cryptomator.domain.DropboxCloud.aDropboxCloud;
|
||||
import static org.cryptomator.domain.GoogleDriveCloud.aGoogleDriveCloud;
|
||||
import static org.cryptomator.domain.LocalStorageCloud.aLocalStorage;
|
||||
import static org.cryptomator.domain.OnedriveCloud.aOnedriveCloud;
|
||||
import static org.cryptomator.domain.PCloud.aPCloud;
|
||||
import static org.cryptomator.domain.WebDavCloud.aWebDavCloudCloud;
|
||||
|
||||
@Singleton
|
||||
@ -47,6 +49,13 @@ public class CloudEntityMapper extends EntityMapper<CloudEntity, Cloud> {
|
||||
.withAccessToken(entity.getAccessToken()) //
|
||||
.withUsername(entity.getUsername()) //
|
||||
.build();
|
||||
case PCLOUD:
|
||||
return aPCloud() //
|
||||
.withId(entity.getId()) //
|
||||
.withUrl(entity.getUrl()) //
|
||||
.withAccessToken(entity.getAccessToken()) //
|
||||
.withUsername(entity.getUsername()) //
|
||||
.build();
|
||||
case LOCAL:
|
||||
return aLocalStorage() //
|
||||
.withId(entity.getId()) //
|
||||
@ -82,6 +91,11 @@ public class CloudEntityMapper extends EntityMapper<CloudEntity, Cloud> {
|
||||
result.setAccessToken(((OnedriveCloud) domainObject).accessToken());
|
||||
result.setUsername(((OnedriveCloud) domainObject).username());
|
||||
break;
|
||||
case PCLOUD:
|
||||
result.setAccessToken(((PCloud) domainObject).accessToken());
|
||||
result.setUrl(((PCloud) domainObject).url());
|
||||
result.setUsername(((PCloud) domainObject).username());
|
||||
break;
|
||||
case LOCAL:
|
||||
result.setAccessToken(((LocalStorageCloud) domainObject).rootUri());
|
||||
break;
|
||||
|
@ -5,6 +5,7 @@ import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.onedrive.OnedriveCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.pcloud.PCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -25,6 +26,7 @@ public class CloudContentRepositoryFactories implements Iterable<CloudContentRep
|
||||
public CloudContentRepositoryFactories(DropboxCloudContentRepositoryFactory dropboxFactory, //
|
||||
GoogleDriveCloudContentRepositoryFactory googleDriveFactory, //
|
||||
OnedriveCloudContentRepositoryFactory oneDriveFactory, //
|
||||
PCloudContentRepositoryFactory pCloudFactory, //
|
||||
CryptoCloudContentRepositoryFactory cryptoFactory, //
|
||||
LocalStorageContentRepositoryFactory localStorageFactory, //
|
||||
WebDavCloudContentRepositoryFactory webDavFactory) {
|
||||
@ -32,6 +34,7 @@ public class CloudContentRepositoryFactories implements Iterable<CloudContentRep
|
||||
factories = asList(dropboxFactory, //
|
||||
googleDriveFactory, //
|
||||
oneDriveFactory, //
|
||||
pCloudFactory, //
|
||||
cryptoFactory, //
|
||||
localStorageFactory, //
|
||||
webDavFactory);
|
||||
|
@ -2,6 +2,6 @@ package org.cryptomator.domain;
|
||||
|
||||
public enum CloudType {
|
||||
|
||||
DROPBOX, GOOGLE_DRIVE, ONEDRIVE, WEBDAV, LOCAL, CRYPTO
|
||||
DROPBOX, GOOGLE_DRIVE, ONEDRIVE, PCLOUD, WEBDAV, LOCAL, CRYPTO
|
||||
|
||||
}
|
||||
|
140
domain/src/main/java/org/cryptomator/domain/PCloud.java
Normal file
140
domain/src/main/java/org/cryptomator/domain/PCloud.java
Normal file
@ -0,0 +1,140 @@
|
||||
package org.cryptomator.domain;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PCloud implements Cloud {
|
||||
|
||||
private final Long id;
|
||||
private final String accessToken;
|
||||
private final String url;
|
||||
private final String username;
|
||||
|
||||
private PCloud(Builder builder) {
|
||||
this.id = builder.id;
|
||||
this.accessToken = builder.accessToken;
|
||||
this.url = builder.url;
|
||||
this.username = builder.username;
|
||||
}
|
||||
|
||||
public static Builder aPCloud() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static Builder aCopyOf(PCloud pCloud) {
|
||||
return new Builder() //
|
||||
.withId(pCloud.id()) //
|
||||
.withAccessToken(pCloud.accessToken()) //
|
||||
.withUrl(pCloud.url()) //
|
||||
.withUsername(pCloud.username());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String accessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String url() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String username() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudType type() {
|
||||
return CloudType.PCLOUD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configurationMatches(Cloud cloud) {
|
||||
return cloud instanceof PCloud && configurationMatches((PCloud) cloud);
|
||||
}
|
||||
|
||||
private boolean configurationMatches(PCloud cloud) {
|
||||
return username.equals(cloud.username);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean predefined() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persistent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresNetwork() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PCLOUD";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
return internalEquals((PCloud) obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id == null ? 0 : id.hashCode();
|
||||
}
|
||||
|
||||
private boolean internalEquals(PCloud obj) {
|
||||
return id != null && id.equals(obj.id);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private Long id;
|
||||
private String accessToken;
|
||||
private String url;
|
||||
private String username;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder withId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUsername(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PCloud build() {
|
||||
return new PCloud(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.cryptomator.domain.usecases.cloud;
|
||||
|
||||
import org.cryptomator.domain.PCloud;
|
||||
import org.cryptomator.domain.exception.BackendException;
|
||||
import org.cryptomator.domain.repository.CloudContentRepository;
|
||||
import org.cryptomator.generator.Parameter;
|
||||
import org.cryptomator.generator.UseCase;
|
||||
|
||||
@UseCase
|
||||
class ConnectToPCloud {
|
||||
|
||||
private final CloudContentRepository cloudContentRepository;
|
||||
private final PCloud cloud;
|
||||
|
||||
public ConnectToPCloud(CloudContentRepository cloudContentRepository, @Parameter PCloud cloud) {
|
||||
this.cloudContentRepository = cloudContentRepository;
|
||||
this.cloud = cloud;
|
||||
}
|
||||
|
||||
public void execute() throws BackendException {
|
||||
cloudContentRepository.checkAuthenticationAndRetrieveCurrentAccount(cloud);
|
||||
}
|
||||
}
|
@ -51,6 +51,7 @@ android {
|
||||
|
||||
buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY') + "\""
|
||||
manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY')]
|
||||
buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID') + "\""
|
||||
|
||||
resValue "string", "app_id", androidApplicationId
|
||||
}
|
||||
@ -65,6 +66,7 @@ android {
|
||||
|
||||
buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY_DEBUG') + "\""
|
||||
manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY_DEBUG')]
|
||||
buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID_DEBUG') + "\""
|
||||
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix '-DEBUG'
|
||||
|
@ -18,6 +18,11 @@ enum class CloudTypeModel(builder: Builder) {
|
||||
.withCloudImageResource(R.drawable.onedrive) //
|
||||
.withVaultImageResource(R.drawable.onedrive_vault) //
|
||||
.withVaultSelectedImageResource(R.drawable.onedrive_vault_selected)), //
|
||||
PCLOUD(Builder("PCLOUD", R.string.cloud_names_pcloud) //
|
||||
.withCloudImageResource(R.drawable.pcloud) //
|
||||
.withVaultImageResource(R.drawable.pcloud_vault) //
|
||||
.withVaultSelectedImageResource(R.drawable.pcloud_vault_selected) //
|
||||
.withMultiInstances()), //
|
||||
WEBDAV(Builder("WEBDAV", R.string.cloud_names_webdav) //
|
||||
.withCloudImageResource(R.drawable.webdav) //
|
||||
.withVaultImageResource(R.drawable.webdav_vault) //
|
||||
|
@ -0,0 +1,32 @@
|
||||
package org.cryptomator.presentation.model
|
||||
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.PCloud
|
||||
import org.cryptomator.presentation.R
|
||||
|
||||
class PCloudModel(cloud: Cloud) : CloudModel(cloud) {
|
||||
|
||||
override fun name(): Int {
|
||||
return R.string.cloud_names_pcloud
|
||||
}
|
||||
|
||||
override fun username(): String? {
|
||||
return cloud().username()
|
||||
}
|
||||
|
||||
fun url(): String {
|
||||
return cloud().url()
|
||||
}
|
||||
|
||||
fun id(): Long {
|
||||
return cloud().id()
|
||||
}
|
||||
|
||||
private fun cloud(): PCloud {
|
||||
return toCloud() as PCloud
|
||||
}
|
||||
|
||||
override fun cloudType(): CloudTypeModel {
|
||||
return CloudTypeModel.PCLOUD
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import org.cryptomator.presentation.model.DropboxCloudModel
|
||||
import org.cryptomator.presentation.model.GoogleDriveCloudModel
|
||||
import org.cryptomator.presentation.model.LocalStorageModel
|
||||
import org.cryptomator.presentation.model.OnedriveCloudModel
|
||||
import org.cryptomator.presentation.model.PCloudModel
|
||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -24,6 +25,7 @@ class CloudModelMapper @Inject constructor() : ModelMapper<CloudModel, Cloud>()
|
||||
CloudTypeModel.DROPBOX -> DropboxCloudModel(domainObject)
|
||||
CloudTypeModel.GOOGLE_DRIVE -> GoogleDriveCloudModel(domainObject)
|
||||
CloudTypeModel.ONEDRIVE -> OnedriveCloudModel(domainObject)
|
||||
CloudTypeModel.PCLOUD -> PCloudModel(domainObject)
|
||||
CloudTypeModel.CRYPTO -> CryptoCloudModel(domainObject)
|
||||
CloudTypeModel.LOCAL -> LocalStorageModel(domainObject)
|
||||
CloudTypeModel.WEBDAV -> WebDavCloudModel(domainObject)
|
||||
|
@ -6,16 +6,23 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.pcloud.sdk.AuthorizationActivity
|
||||
import com.pcloud.sdk.AuthorizationData
|
||||
import com.pcloud.sdk.AuthorizationRequest
|
||||
import com.pcloud.sdk.AuthorizationResult
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.LocalStorageCloud
|
||||
import org.cryptomator.domain.PCloud
|
||||
import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.RemoveCloudUseCase
|
||||
import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.presentation.BuildConfig
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||
import org.cryptomator.presentation.intent.Intents
|
||||
@ -26,6 +33,7 @@ import org.cryptomator.presentation.model.WebDavCloudModel
|
||||
import org.cryptomator.presentation.model.mappers.CloudModelMapper
|
||||
import org.cryptomator.presentation.ui.activity.view.CloudConnectionListView
|
||||
import org.cryptomator.presentation.workflow.ActivityResult
|
||||
import org.cryptomator.util.crypto.CredentialCryptor
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
@ -34,6 +42,7 @@ import timber.log.Timber
|
||||
@PerView
|
||||
class CloudConnectionListPresenter @Inject constructor( //
|
||||
private val getCloudsUseCase: GetCloudsUseCase, //
|
||||
private val getUsernameUseCase: GetUsernameUseCase, //
|
||||
private val removeCloudUseCase: RemoveCloudUseCase, //
|
||||
private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, //
|
||||
private val getVaultListUseCase: GetVaultListUseCase, //
|
||||
@ -122,6 +131,18 @@ class CloudConnectionListPresenter @Inject constructor( //
|
||||
when (selectedCloudType.get()) {
|
||||
CloudTypeModel.WEBDAV -> requestActivityResult(ActivityResultCallbacks.addChangeWebDavCloud(), //
|
||||
Intents.webDavAddOrChangeIntent())
|
||||
CloudTypeModel.PCLOUD -> {
|
||||
val authIntent: Intent = AuthorizationActivity.createIntent(
|
||||
this.context(),
|
||||
AuthorizationRequest.create()
|
||||
.setType(AuthorizationRequest.Type.TOKEN)
|
||||
.setClientId(BuildConfig.PCLOUD_CLIENT_ID)
|
||||
.setForceAccessApproval(true)
|
||||
.addPermission("manageshares")
|
||||
.build())
|
||||
requestActivityResult(ActivityResultCallbacks.pCloudAuthenticationFinished(), //
|
||||
authIntent)
|
||||
}
|
||||
CloudTypeModel.LOCAL -> openDocumentTree()
|
||||
}
|
||||
}
|
||||
@ -162,6 +183,71 @@ class CloudConnectionListPresenter @Inject constructor( //
|
||||
loadCloudList()
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun pCloudAuthenticationFinished(activityResult: ActivityResult) {
|
||||
val authData: AuthorizationData = AuthorizationActivity.getResult(activityResult.intent())
|
||||
val result: AuthorizationResult = authData.result
|
||||
|
||||
when (result) {
|
||||
AuthorizationResult.ACCESS_GRANTED -> {
|
||||
val accessToken: String = CredentialCryptor //
|
||||
.getInstance(this.context()) //
|
||||
.encrypt(authData.token)
|
||||
val pCloudSkeleton: PCloud = PCloud.aPCloud() //
|
||||
.withAccessToken(accessToken)
|
||||
.withUrl(authData.apiHost)
|
||||
.build();
|
||||
getUsernameUseCase //
|
||||
.withCloud(pCloudSkeleton) //
|
||||
.run(object : DefaultResultHandler<String>() {
|
||||
override fun onSuccess(username: String?) {
|
||||
prepareForSavingPCloud(PCloud.aCopyOf(pCloudSkeleton).withUsername(username).build())
|
||||
}
|
||||
})
|
||||
}
|
||||
AuthorizationResult.ACCESS_DENIED -> {
|
||||
Timber.tag("CloudConnListPresenter").e("Account access denied")
|
||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||
}
|
||||
AuthorizationResult.AUTH_ERROR -> {
|
||||
Timber.tag("CloudConnListPresenter").e("""Account access grant error: ${authData.errorMessage}""".trimIndent())
|
||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||
}
|
||||
AuthorizationResult.CANCELLED -> {
|
||||
Timber.tag("CloudConnListPresenter").i("Account access grant cancelled")
|
||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareForSavingPCloud(cloud: PCloud) {
|
||||
getCloudsUseCase //
|
||||
.withCloudType(CloudTypeModel.valueOf(selectedCloudType.get())) //
|
||||
.run(object : DefaultResultHandler<List<Cloud>>() {
|
||||
override fun onSuccess(clouds: List<Cloud>) {
|
||||
clouds.firstOrNull {
|
||||
(it as PCloud).username() == cloud.username()
|
||||
}?.let {
|
||||
it as PCloud
|
||||
saveCloud(PCloud.aCopyOf(it) //
|
||||
.withUrl(cloud.url())
|
||||
.withAccessToken(cloud.accessToken())
|
||||
.build())
|
||||
} ?: saveCloud(cloud)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun saveCloud(cloud: PCloud) {
|
||||
addOrChangeCloudConnectionUseCase //
|
||||
.withCloud(cloud) //
|
||||
.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
loadCloudList()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Callback
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
fun pickedLocalStorageLocation(result: ActivityResult) {
|
||||
|
@ -2,6 +2,7 @@ package org.cryptomator.presentation.presenter
|
||||
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.LocalStorageCloud
|
||||
import org.cryptomator.domain.PCloud
|
||||
import org.cryptomator.domain.WebDavCloud
|
||||
import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.exception.FatalBackendException
|
||||
@ -16,6 +17,7 @@ import org.cryptomator.presentation.intent.Intents
|
||||
import org.cryptomator.presentation.model.CloudModel
|
||||
import org.cryptomator.presentation.model.CloudTypeModel
|
||||
import org.cryptomator.presentation.model.LocalStorageModel
|
||||
import org.cryptomator.presentation.model.PCloudModel
|
||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||
import org.cryptomator.presentation.model.mappers.CloudModelMapper
|
||||
import org.cryptomator.presentation.ui.activity.view.CloudSettingsView
|
||||
@ -34,6 +36,7 @@ class CloudSettingsPresenter @Inject constructor( //
|
||||
private val nonSingleLoginClouds: Set<CloudTypeModel> = EnumSet.of( //
|
||||
CloudTypeModel.CRYPTO, //
|
||||
CloudTypeModel.LOCAL, //
|
||||
CloudTypeModel.PCLOUD, //
|
||||
CloudTypeModel.WEBDAV)
|
||||
|
||||
fun loadClouds() {
|
||||
@ -41,7 +44,7 @@ class CloudSettingsPresenter @Inject constructor( //
|
||||
}
|
||||
|
||||
fun onCloudClicked(cloudModel: CloudModel) {
|
||||
if (isWebdavOrLocal(cloudModel)) {
|
||||
if (isWebdavOrPCloudOrLocal(cloudModel)) {
|
||||
startConnectionListActivity(cloudModel.cloudType())
|
||||
} else {
|
||||
if (isLoggedIn(cloudModel)) {
|
||||
@ -58,8 +61,8 @@ class CloudSettingsPresenter @Inject constructor( //
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWebdavOrLocal(cloudModel: CloudModel): Boolean {
|
||||
return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel
|
||||
private fun isWebdavOrPCloudOrLocal(cloudModel: CloudModel): Boolean {
|
||||
return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel || cloudModel is PCloudModel
|
||||
}
|
||||
|
||||
private fun loginCloud(cloudModel: CloudModel) {
|
||||
@ -91,6 +94,7 @@ class CloudSettingsPresenter @Inject constructor( //
|
||||
private fun effectiveTitle(cloudTypeModel: CloudTypeModel): String {
|
||||
when (cloudTypeModel) {
|
||||
CloudTypeModel.WEBDAV -> return context().getString(R.string.screen_cloud_settings_webdav_connections)
|
||||
CloudTypeModel.PCLOUD -> return context().getString(R.string.screen_cloud_settings_pcloud_connections)
|
||||
CloudTypeModel.LOCAL -> return context().getString(R.string.screen_cloud_settings_local_storage_locations)
|
||||
}
|
||||
return context().getString(R.string.screen_cloud_settings_title)
|
||||
@ -123,6 +127,7 @@ class CloudSettingsPresenter @Inject constructor( //
|
||||
.toMutableList() //
|
||||
.also {
|
||||
it.add(aWebdavCloud())
|
||||
it.add(aPCloud())
|
||||
it.add(aLocalCloud())
|
||||
}
|
||||
view?.render(cloudModel)
|
||||
@ -132,6 +137,10 @@ class CloudSettingsPresenter @Inject constructor( //
|
||||
return WebDavCloudModel(WebDavCloud.aWebDavCloudCloud().build())
|
||||
}
|
||||
|
||||
private fun aPCloud(): PCloudModel {
|
||||
return PCloudModel(PCloud.aPCloud().build())
|
||||
}
|
||||
|
||||
private fun aLocalCloud(): CloudModel {
|
||||
return LocalStorageModel(LocalStorageCloud.aLocalStorage().build())
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou
|
||||
presenter.pageIndexes.size.let {
|
||||
when {
|
||||
it == 0 -> {
|
||||
showMessage(getString(R.string.dialog_no_more_images_to_display ))
|
||||
showMessage(getString(R.string.dialog_no_more_images_to_display))
|
||||
finish()
|
||||
}
|
||||
it > index -> updateTitle(index)
|
||||
|
@ -7,6 +7,7 @@ import org.cryptomator.domain.exception.FatalBackendException
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.model.CloudModel
|
||||
import org.cryptomator.presentation.model.LocalStorageModel
|
||||
import org.cryptomator.presentation.model.PCloudModel
|
||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||
import org.cryptomator.presentation.model.comparator.CloudModelComparator
|
||||
import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter.CloudConnectionHolder
|
||||
@ -54,6 +55,8 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter<CloudModel, Clo
|
||||
|
||||
if (cloudModel is WebDavCloudModel) {
|
||||
bindWebDavCloudModel(cloudModel)
|
||||
} else if (cloudModel is PCloudModel) {
|
||||
bindPCloudModel(cloudModel)
|
||||
} else if (cloudModel is LocalStorageModel) {
|
||||
bindLocalStorageCloudModel(cloudModel)
|
||||
}
|
||||
@ -70,6 +73,11 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter<CloudModel, Clo
|
||||
|
||||
}
|
||||
|
||||
private fun bindPCloudModel(cloudModel: PCloudModel) {
|
||||
itemView.cloudText.text = cloudModel.username()
|
||||
itemView.cloudSubText.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun bindLocalStorageCloudModel(cloudModel: LocalStorageModel) {
|
||||
if (cloudModel.location().isEmpty()) {
|
||||
itemView.cloudText.text = cloudModel.storage()
|
||||
|
@ -41,6 +41,8 @@ constructor(private val context: Context) : RecyclerViewBaseAdapter<CloudModel,
|
||||
|
||||
if (webdav(cloudModel.cloudType())) {
|
||||
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_webdav_connections)
|
||||
} else if (pCloud(cloudModel.cloudType())) {
|
||||
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_pcloud_connections)
|
||||
} else if (local(cloudModel.cloudType())) {
|
||||
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_local_storage_locations)
|
||||
} else {
|
||||
@ -79,4 +81,8 @@ constructor(private val context: Context) : RecyclerViewBaseAdapter<CloudModel,
|
||||
private fun webdav(cloudType: CloudTypeModel): Boolean {
|
||||
return CloudTypeModel.WEBDAV == cloudType
|
||||
}
|
||||
|
||||
private fun pCloud(cloudType: CloudTypeModel): Boolean {
|
||||
return CloudTypeModel.PCLOUD == cloudType
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.model.CloudModel
|
||||
import org.cryptomator.presentation.model.CloudTypeModel
|
||||
import org.cryptomator.presentation.model.LocalStorageModel
|
||||
import org.cryptomator.presentation.model.PCloudModel
|
||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||
import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.change_cloud
|
||||
import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.delete_cloud
|
||||
@ -28,6 +29,7 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet<CloudConnectionSettin
|
||||
|
||||
when (cloudModel.cloudType()) {
|
||||
CloudTypeModel.WEBDAV -> bindViewForWebDAV(cloudModel as WebDavCloudModel)
|
||||
CloudTypeModel.PCLOUD -> bindViewForPCloud(cloudModel as PCloudModel)
|
||||
CloudTypeModel.LOCAL -> bindViewForLocal(cloudModel as LocalStorageModel)
|
||||
else -> throw IllegalStateException("Cloud model is not binded in the view")
|
||||
}
|
||||
@ -59,6 +61,11 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet<CloudConnectionSettin
|
||||
tv_cloud_subtext.text = cloudModel.username()
|
||||
}
|
||||
|
||||
private fun bindViewForPCloud(cloudModel: PCloudModel) {
|
||||
change_cloud.visibility = View.GONE
|
||||
tv_cloud_name.text = cloudModel.username()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val CLOUD_NODE_ARG = "cloudModel"
|
||||
|
@ -172,6 +172,7 @@
|
||||
<string name="screen_settings_background_unlock_preparation_label">Vorbereitungen zum Entsperren im Hintergrund</string>
|
||||
<!-- ## screen: cloud settings -->
|
||||
<string name="screen_cloud_settings_webdav_connections">WebDAV-Verbindungen</string>
|
||||
<string name="screen_cloud_settings_pcloud_connections">pCloud-Verbindungen</string>
|
||||
<string name="screen_cloud_settings_local_storage_locations">Lokale Speicherorte</string>
|
||||
<string name="screen_cloud_settings_log_in_to">Einloggen in</string>
|
||||
<string name="screen_cloud_settings_sign_out_from_cloud">Abmelden von</string>
|
||||
|
@ -110,6 +110,7 @@
|
||||
<string name="screen_settings_section_version">Versión</string>
|
||||
<!-- ## screen: cloud settings -->
|
||||
<string name="screen_cloud_settings_webdav_connections">Conexiones de WebDAV</string>
|
||||
<string name="screen_cloud_settings_pcloud_connections">Conexiones de pCloud</string>
|
||||
<string name="screen_cloud_settings_local_storage_locations">Ubicaciones de almacenamiento local</string>
|
||||
<string name="screen_cloud_settings_log_in_to">Iniciar sesión en</string>
|
||||
<string name="screen_cloud_settings_sign_out_from_cloud">Cerrar sesión de</string>
|
||||
|
@ -173,6 +173,7 @@
|
||||
<string name="screen_settings_background_unlock_preparation_label">Préparations du déverrouillage en arrière-plan</string>
|
||||
<!-- ## screen: cloud settings -->
|
||||
<string name="screen_cloud_settings_webdav_connections">Connexions WebDAV</string>
|
||||
<string name="screen_cloud_settings_pcloud_connections">Connexions pCloud</string>
|
||||
<string name="screen_cloud_settings_local_storage_locations">Emplacements du stockage local</string>
|
||||
<string name="screen_cloud_settings_log_in_to">Se connecter à</string>
|
||||
<string name="screen_cloud_settings_sign_out_from_cloud">Se déconnecter de</string>
|
||||
|
@ -168,6 +168,7 @@
|
||||
<string name="screen_settings_background_unlock_preparation_label">Arka planda kilit açma</string>
|
||||
<!-- ## screen: cloud settings -->
|
||||
<string name="screen_cloud_settings_webdav_connections">WebDAV bağlantıları</string>
|
||||
<string name="screen_cloud_settings_pcloud_connections">pCloud bağlantıları</string>
|
||||
<string name="screen_cloud_settings_local_storage_locations">Yerel depolama konumları</string>
|
||||
<string name="screen_cloud_settings_log_in_to">Giriş</string>
|
||||
<string name="screen_cloud_settings_sign_out_from_cloud">Oturumunu kapat</string>
|
||||
|
@ -11,6 +11,7 @@
|
||||
<!-- # error messages -->
|
||||
<string name="error_generic">An error occurred</string>
|
||||
<string name="error_authentication_failed">Authentication failed</string>
|
||||
<string name="error_authentication_failed_re_authenticate">Authentication failed, please login using %1$s</string>
|
||||
<string name="error_no_network_connection">No network connection</string>
|
||||
<string name="error_invalid_passphrase">Wrong password</string>
|
||||
<string name="error_file_or_folder_exists">A file or folder already exists.</string>
|
||||
@ -39,6 +40,7 @@
|
||||
<string name="cloud_names_dropbox" translatable="false">Dropbox</string>
|
||||
<string name="cloud_names_google_drive" translatable="false">Google Drive</string>
|
||||
<string name="cloud_names_onedrive" translatable="false">OneDrive</string>
|
||||
<string name="cloud_names_pcloud" translatable="false">pCloud</string>
|
||||
<string name="cloud_names_webdav" translatable="false">WebDAV</string>
|
||||
<string name="cloud_names_local_storage">Local storage</string>
|
||||
|
||||
@ -253,6 +255,7 @@
|
||||
<!-- ## screen: cloud settings -->
|
||||
<string name="screen_cloud_settings_title">@string/screen_settings_cloud_settings_label</string>
|
||||
<string name="screen_cloud_settings_webdav_connections">WebDAV connections</string>
|
||||
<string name="screen_cloud_settings_pcloud_connections">pCloud connections</string>
|
||||
<string name="screen_cloud_settings_local_storage_locations">Local storage locations</string>
|
||||
<string name="screen_cloud_settings_log_in_to">Log in to</string>
|
||||
<string name="screen_cloud_settings_sign_out_from_cloud">Sign out from</string>
|
||||
|
@ -3,9 +3,15 @@ package org.cryptomator.presentation.presenter
|
||||
import android.Manifest
|
||||
import android.accounts.AccountManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import com.dropbox.core.android.Auth
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
import com.google.api.services.drive.DriveScopes
|
||||
import com.pcloud.sdk.AuthorizationActivity
|
||||
import com.pcloud.sdk.AuthorizationData
|
||||
import com.pcloud.sdk.AuthorizationRequest
|
||||
import com.pcloud.sdk.AuthorizationResult
|
||||
import org.cryptomator.data.cloud.onedrive.OnedriveClientFactory
|
||||
import org.cryptomator.data.cloud.onedrive.graph.ClientException
|
||||
import org.cryptomator.data.cloud.onedrive.graph.ICallback
|
||||
@ -15,6 +21,7 @@ import org.cryptomator.domain.CloudType
|
||||
import org.cryptomator.domain.DropboxCloud
|
||||
import org.cryptomator.domain.GoogleDriveCloud
|
||||
import org.cryptomator.domain.OnedriveCloud
|
||||
import org.cryptomator.domain.PCloud
|
||||
import org.cryptomator.domain.WebDavCloud
|
||||
import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.exception.FatalBackendException
|
||||
@ -25,6 +32,7 @@ import org.cryptomator.domain.exception.authentication.WebDavNotSupportedExcepti
|
||||
import org.cryptomator.domain.exception.authentication.WebDavServerNotFoundException
|
||||
import org.cryptomator.domain.exception.authentication.WrongCredentialsException
|
||||
import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.presentation.BuildConfig
|
||||
@ -57,6 +65,7 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
||||
exceptionHandlers: ExceptionHandlers, //
|
||||
private val cloudModelMapper: CloudModelMapper, //
|
||||
private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, //
|
||||
private val getCloudsUseCase: GetCloudsUseCase, //
|
||||
private val getUsernameUseCase: GetUsernameUseCase, //
|
||||
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
|
||||
private val createNewVaultWorkflow: CreateNewVaultWorkflow) : Presenter<AuthenticateCloudView>(exceptionHandlers) {
|
||||
@ -65,6 +74,7 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
||||
DropboxAuthStrategy(), //
|
||||
GoogleDriveAuthStrategy(), //
|
||||
OnedriveAuthStrategy(), //
|
||||
PCloudAuthStrategy(), //
|
||||
WebDAVAuthStrategy(), //
|
||||
LocalStorageAuthStrategy() //
|
||||
)
|
||||
@ -282,6 +292,102 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PCloudAuthStrategy : AuthStrategy {
|
||||
|
||||
private var authenticationStarted = false
|
||||
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return cloud.cloudType() == CloudTypeModel.PCLOUD
|
||||
}
|
||||
|
||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
||||
when {
|
||||
ExceptionUtil.contains(intent.error(), WrongCredentialsException::class.java) -> {
|
||||
if (!authenticationStarted) {
|
||||
startAuthentication()
|
||||
Toast.makeText(
|
||||
context(),
|
||||
String.format(getString(R.string.error_authentication_failed_re_authenticate), intent.cloud().username()),
|
||||
Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.tag("AuthicateCloudPrester").e(intent.error())
|
||||
failAuthentication(intent.cloud().name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAuthentication() {
|
||||
authenticationStarted = true
|
||||
val authIntent: Intent = AuthorizationActivity.createIntent(
|
||||
context(),
|
||||
AuthorizationRequest.create()
|
||||
.setType(AuthorizationRequest.Type.TOKEN)
|
||||
.setClientId(BuildConfig.PCLOUD_CLIENT_ID)
|
||||
.setForceAccessApproval(true)
|
||||
.addPermission("manageshares")
|
||||
.build())
|
||||
requestActivityResult(ActivityResultCallbacks.pCloudReAuthenticationFinished(), //
|
||||
authIntent)
|
||||
}
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun pCloudReAuthenticationFinished(activityResult: ActivityResult) {
|
||||
val authData: AuthorizationData = AuthorizationActivity.getResult(activityResult.intent())
|
||||
val result: AuthorizationResult = authData.result
|
||||
|
||||
when (result) {
|
||||
AuthorizationResult.ACCESS_GRANTED -> {
|
||||
val accessToken: String = CredentialCryptor //
|
||||
.getInstance(context()) //
|
||||
.encrypt(authData.token)
|
||||
val pCloudSkeleton: PCloud = PCloud.aPCloud() //
|
||||
.withAccessToken(accessToken)
|
||||
.withUrl(authData.apiHost)
|
||||
.build();
|
||||
getUsernameUseCase //
|
||||
.withCloud(pCloudSkeleton) //
|
||||
.run(object : DefaultResultHandler<String>() {
|
||||
override fun onSuccess(username: String?) {
|
||||
prepareForSavingPCloud(PCloud.aCopyOf(pCloudSkeleton).withUsername(username).build())
|
||||
}
|
||||
})
|
||||
}
|
||||
AuthorizationResult.ACCESS_DENIED -> {
|
||||
Timber.tag("CloudConnListPresenter").e("Account access denied")
|
||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||
}
|
||||
AuthorizationResult.AUTH_ERROR -> {
|
||||
Timber.tag("CloudConnListPresenter").e("""Account access grant error: ${authData.errorMessage}""".trimIndent())
|
||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||
}
|
||||
AuthorizationResult.CANCELLED -> {
|
||||
Timber.tag("CloudConnListPresenter").i("Account access grant cancelled")
|
||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareForSavingPCloud(cloud: PCloud) {
|
||||
getCloudsUseCase //
|
||||
.withCloudType(cloud.type()) //
|
||||
.run(object : DefaultResultHandler<List<Cloud>>() {
|
||||
override fun onSuccess(clouds: List<Cloud>) {
|
||||
clouds.firstOrNull {
|
||||
(it as PCloud).username() == cloud.username()
|
||||
}?.let {
|
||||
it as PCloud
|
||||
succeedAuthenticationWith(PCloud.aCopyOf(it) //
|
||||
.withUrl(cloud.url())
|
||||
.withAccessToken(cloud.accessToken())
|
||||
.build())
|
||||
} ?: succeedAuthenticationWith(cloud)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private inner class WebDAVAuthStrategy : AuthStrategy {
|
||||
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
@ -403,6 +509,6 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
||||
}
|
||||
|
||||
init {
|
||||
unsubscribeOnDestroy(addOrChangeCloudConnectionUseCase, getUsernameUseCase)
|
||||
unsubscribeOnDestroy(addOrChangeCloudConnectionUseCase, getCloudsUseCase, getUsernameUseCase)
|
||||
}
|
||||
}
|
||||
|
@ -21,13 +21,14 @@ class LruFileCacheUtil(context: Context) {
|
||||
private val parent: File = context.cacheDir
|
||||
|
||||
enum class Cache {
|
||||
DROPBOX, WEBDAV, ONEDRIVE, GOOGLE_DRIVE
|
||||
DROPBOX, WEBDAV, PCLOUD, ONEDRIVE, GOOGLE_DRIVE
|
||||
}
|
||||
|
||||
fun resolve(cache: Cache?): File {
|
||||
return when (cache) {
|
||||
Cache.DROPBOX -> File(parent, "LruCacheDropbox")
|
||||
Cache.WEBDAV -> File(parent, "LruCacheWebdav")
|
||||
Cache.PCLOUD -> File(parent, "LruCachePCloud")
|
||||
Cache.ONEDRIVE -> File(parent, "LruCacheOneDrive")
|
||||
Cache.GOOGLE_DRIVE -> File(parent, "LruCacheGoogleDrive")
|
||||
else -> throw IllegalStateException()
|
||||
|
Loading…
x
Reference in New Issue
Block a user