diff --git a/.gitmodules b/.gitmodules
index 32f48167..393a652c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "subsampling-scale-image-view"]
path = subsampling-scale-image-view
url = https://github.com/SailReal/subsampling-scale-image-view.git
+[submodule "pcloud-sdk-java"]
+ path = pcloud-sdk-java
+ url = https://github.com/SailReal/pcloud-sdk-java
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 286a07d2..16b33dfd 100755
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -3,6 +3,7 @@
+
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index ad45b909..6566bd76 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -8,21 +8,21 @@ GEM
rubyzip (~> 2.0)
artifactory (3.0.15)
atomos (0.1.3)
- aws-eventstream (1.1.0)
- aws-partitions (1.429.0)
- aws-sdk-core (3.112.0)
+ aws-eventstream (1.1.1)
+ aws-partitions (1.437.0)
+ aws-sdk-core (3.113.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.42.0)
+ aws-sdk-kms (1.43.0)
aws-sdk-core (~> 3, >= 3.112.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.89.0)
+ aws-sdk-s3 (1.93.0)
aws-sdk-core (~> 3, >= 3.112.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
- aws-sigv4 (1.2.2)
+ aws-sigv4 (1.2.3)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
bcrypt_pbkdf (1.0.1)
@@ -51,8 +51,8 @@ GEM
faraday-net_http (1.0.1)
faraday_middleware (1.0.0)
faraday (~> 1.0)
- fastimage (2.2.2)
- fastlane (2.176.0)
+ fastimage (2.2.3)
+ fastlane (2.179.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0)
@@ -104,7 +104,7 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
- google-apis-core (0.2.1)
+ google-apis-core (0.3.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.14)
httpclient (>= 2.8.1, < 3.0)
@@ -114,17 +114,17 @@ GEM
rexml
signet (~> 0.14)
webrick
- google-apis-iamcredentials_v1 (0.1.0)
+ google-apis-iamcredentials_v1 (0.2.0)
google-apis-core (~> 0.1)
- google-apis-storage_v1 (0.2.0)
+ google-apis-storage_v1 (0.3.0)
google-apis-core (~> 0.1)
- google-cloud-core (1.5.0)
+ google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.4.0)
+ google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
- google-cloud-errors (1.0.1)
- google-cloud-storage (1.30.0)
+ google-cloud-errors (1.1.0)
+ google-cloud-storage (1.31.0)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
@@ -132,7 +132,7 @@ GEM
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
- googleauth (0.15.1)
+ googleauth (0.16.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -151,7 +151,7 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2020.1104)
mini_magick (4.11.0)
- mini_mime (1.0.2)
+ mini_mime (1.0.3)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
@@ -173,7 +173,7 @@ GEM
ruby2_keywords (0.0.4)
rubyzip (2.3.0)
security (0.1.3)
- signet (0.14.1)
+ signet (0.15.0)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
diff --git a/build.gradle b/build.gradle
index f29f533e..93627bf2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,19 +3,19 @@ apply from: 'buildsystem/dependencies.gradle'
apply plugin: "com.vanniktech.android.junit.jacoco"
buildscript {
- ext.kotlin_version = '1.4.30'
+ ext.kotlin_version = '1.4.32'
repositories {
jcenter()
mavenCentral()
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.2'
+ classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0'
classpath 'com.fernandocejas.frodo:frodo-plugin:0.8.3'
classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.16.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath "de.mannodermaus.gradle.plugins:android-junit5:1.7.0.0"
+ classpath "de.mannodermaus.gradle.plugins:android-junit5:1.7.1.1"
}
}
@@ -42,7 +42,7 @@ allprojects {
ext {
androidApplicationId = 'org.cryptomator'
androidVersionCode = getVersionCode()
- androidVersionName = '1.5.13-SNAPSHOT'
+ androidVersionName = '1.6.0-SNAPSHOT'
}
repositories {
mavenCentral()
diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle
index 6e690241..3c0d40c6 100644
--- a/buildsystem/dependencies.gradle
+++ b/buildsystem/dependencies.gradle
@@ -16,7 +16,7 @@ ext {
javaxAnnotationVersion = '1.0'
// support lib
- androidSupportAnnotationsVersion = '1.1.0'
+ androidSupportAnnotationsVersion = '1.2.0'
androidSupportAppcompatVersion = '1.2.0'
androidSupportDesignVersion = '1.3.0'
@@ -26,7 +26,7 @@ ext {
rxAndroidVersion = '2.1.1'
rxBindingVersion = '2.2.0'
- daggerVersion = '2.32'
+ daggerVersion = '2.34.1'
gsonVersion = '2.8.6'
@@ -37,7 +37,7 @@ ext {
timberVersion = '4.7.1'
- zxcvbnVersion = '1.4.0'
+ zxcvbnVersion = '1.4.1'
scaleImageViewVersion = '3.10.0'
@@ -51,14 +51,13 @@ ext {
// do not update to 1.4.0 until minsdk is 7.x (or desugaring works better) otherwise it will crash on 6.x
cryptolibVersion = '1.3.0'
- dropboxVersion = '3.1.5'
+ dropboxVersion = '4.0.0'
googleApiServicesVersion = 'v3-rev197-1.25.0'
googlePlayServicesVersion = '19.0.0'
- googleClientVersion = '1.31.2'
+ googleClientVersion = '1.31.4'
- msgraphVersion = '2.8.1'
- msaAuthVersion = '0.10.0'
+ msgraphVersion = '2.10.0'
commonsCodecVersion = '1.15'
@@ -69,8 +68,8 @@ ext {
jUnitVersion = '5.7.1'
jUnit4Version = '4.13.1'
assertJVersion = '1.7.1'
- mockitoVersion = '3.8.0'
- mockitoInlineVersion = '3.8.0'
+ mockitoVersion = '3.9.0'
+ mockitoInlineVersion = '3.9.0'
hamcrestVersion = '1.3'
dexmakerVersion = '1.0'
espressoVersion = '3.3.0'
@@ -81,11 +80,11 @@ ext {
uiautomatorVersion = '2.2.0'
androidxCoreVersion = '1.3.2'
- androidxFragmentVersion = '1.3.0'
+ androidxFragmentVersion = '1.3.2'
androidxViewpagerVersion = '1.0.0'
androidxSwiperefreshVersion = '1.1.0'
androidxPreferenceVersion = '1.0.0' // 1.1.0 and 1.1.2 does have a bug with the text size
- androidxRecyclerViewVersion = '1.1.0'
+ androidxRecyclerViewVersion = '1.2.0'
androidxDocumentfileVersion = '1.0.1'
androidxBiometricVersion = '1.1.0'
androidxTestCoreVersion = '1.3.0'
@@ -126,7 +125,6 @@ ext {
junit4 : "org.junit.jupiter:junit-jupiter:${jUnit4Version}",
junit4Engine : "org.junit.vintage:junit-vintage-engine:${jUnitVersion}",
msgraph : "com.microsoft.graph:microsoft-graph:${msgraphVersion}",
- msaAuth : "com.microsoft.graph:msa-auth-for-android-adapter:${msaAuthVersion}",
mockito : "org.mockito:mockito-core:${mockitoVersion}",
mockitoInline : "org.mockito:mockito-inline:${mockitoInlineVersion}",
multidex : "androidx.multidex:multidex:${multidexVersion}",
diff --git a/data/build.gradle b/data/build.gradle
index 7740b19c..2187a71f 100644
--- a/data/build.gradle
+++ b/data/build.gradle
@@ -74,7 +74,7 @@ android {
}
greendao {
- schemaVersion 4
+ schemaVersion 5
}
configurations.all {
@@ -88,6 +88,7 @@ dependencies {
implementation project(':domain')
implementation project(':util')
implementation project(':msa-auth-for-android')
+ implementation project(':pcloud-sdk-java')
// cryptomator
implementation dependencies.cryptolib
diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml
index 406049e3..bcd14ac8 100644
--- a/data/src/main/AndroidManifest.xml
+++ b/data/src/main/AndroidManifest.xml
@@ -4,5 +4,5 @@
-
+
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java
new file mode 100644
index 00000000..d502576d
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java
@@ -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 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 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;
+ }
+ }
+
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java
new file mode 100644
index 00000000..f0a0b535
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java
@@ -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);
+ }
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java
new file mode 100644
index 00000000..20d1d62a
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java
@@ -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 {
+
+ 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 {
+
+ 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 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 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 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 encryptedTmpFile, OutputStream data, ProgressAware 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
+ }
+ }
+
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepositoryFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepositoryFactory.java
new file mode 100644
index 00000000..d3d1515d
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepositoryFactory.java
@@ -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);
+ }
+
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java
new file mode 100644
index 00000000..b245a4e7
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java
@@ -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 size;
+ private final Optional modified;
+
+ public PCloudFile(PCloudFolder parent, String name, String path, Optional size, Optional 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 getSize() {
+ return size;
+ }
+
+ @Override
+ public Optional getModified() {
+ return modified;
+ }
+
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java
new file mode 100644
index 00000000..2674ffd6
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java
@@ -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);
+ }
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java
new file mode 100644
index 00000000..22e077a2
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java
@@ -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 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 list(PCloudFolder folder) throws IOException, BackendException {
+ List result = new ArrayList<>();
+
+ try {
+ RemoteFolder listFolderResult = client().listFolder(folder.getPath()).execute();
+ List 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 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 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 encryptedTmpFile, OutputStream data, final ProgressAware progressAware) throws IOException, BackendException {
+ progressAware.onProgress(Progress.started(DownloadState.download(file)));
+
+ Optional cacheKey = Optional.empty();
+ Optional 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 encryptedTmpFile, //
+ final Optional cacheKey, //
+ final ProgressAware 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 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);
+ }
+ }
+ }
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java
new file mode 100644
index 00000000..e460ae2c
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java
@@ -0,0 +1,10 @@
+package org.cryptomator.data.cloud.pcloud;
+
+import org.cryptomator.domain.CloudNode;
+
+interface PCloudNode extends CloudNode {
+
+ @Override
+ PCloudFolder getParent();
+
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java
new file mode 100644
index 00000000..55e72da9
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java
@@ -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 size) {
+ return new PCloudFile(parent, name, getNodePath(parent, name), size, Optional.empty());
+ }
+
+ public static PCloudFile file(PCloudFolder folder, String name, Optional 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());
+ }
+ }
+
+}
diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java
new file mode 100644
index 00000000..fd819a92
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java
@@ -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);
+ }
+}
diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java
index 1b3725ee..b116cb04 100644
--- a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java
+++ b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java
@@ -22,13 +22,15 @@ class DatabaseUpgrades {
Upgrade0To1 upgrade0To1, //
Upgrade1To2 upgrade1To2, //
Upgrade2To3 upgrade2To3, //
- Upgrade3To4 upgrade3To4) {
+ Upgrade3To4 upgrade3To4, //
+ Upgrade4To5 upgrade4To5) {
availableUpgrades = defineUpgrades( //
upgrade0To1, //
upgrade1To2, //
upgrade2To3, //
- upgrade3To4);
+ upgrade3To4, //
+ upgrade4To5);
}
private static Comparator reverseOrder() {
diff --git a/data/src/main/java/org/cryptomator/data/db/Sql.java b/data/src/main/java/org/cryptomator/data/db/Sql.java
index 5f703a0a..1fc488a4 100644
--- a/data/src/main/java/org/cryptomator/data/db/Sql.java
+++ b/data/src/main/java/org/cryptomator/data/db/Sql.java
@@ -1,6 +1,7 @@
package org.cryptomator.data.db;
import android.content.ContentValues;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import org.greenrobot.greendao.database.Database;
@@ -49,6 +50,10 @@ class Sql {
return new SqlUpdateBuilder(tableName);
}
+ public static SqlQueryBuilder query(String table) {
+ return new SqlQueryBuilder(table);
+ }
+
public static Criterion eq(final String value) {
return (column, whereClause, whereArgs) -> {
whereClause.append('"').append(column).append("\" = ?");
@@ -91,6 +96,56 @@ class Sql {
void appendTo(String column, StringBuilder whereClause, List whereArgs);
}
+ public static class SqlQueryBuilder {
+
+ private final String tableName;
+ private final StringBuilder whereClause = new StringBuilder();
+ private final List whereArgs = new ArrayList<>();
+
+ private List columns = new ArrayList<>();
+ private String groupBy;
+ private String having;
+ private String limit;
+
+ public SqlQueryBuilder(String tableName) {
+ this.tableName = tableName;
+ }
+
+ public SqlQueryBuilder columns(List columns) {
+ this.columns = columns;
+ return this;
+ }
+
+ public SqlQueryBuilder where(String column, Criterion criterion) {
+ if (whereClause.length() > 0) {
+ whereClause.append(" AND ");
+ }
+ criterion.appendTo(column, whereClause, whereArgs);
+ return this;
+ }
+
+ public SqlQueryBuilder groupBy(String groupBy) {
+ this.groupBy = groupBy;
+ return this;
+ }
+
+ public SqlQueryBuilder having(String having) {
+ this.having = having;
+ return this;
+ }
+
+ public SqlQueryBuilder limit(String limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public Cursor executeOn(Database wrapped) {
+ SQLiteDatabase db = unwrap(wrapped);
+ return db.query(tableName, columns.toArray(new String[columns.size()]), whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()]), groupBy, having, limit);
+ }
+
+ }
+
public static class SqlUpdateBuilder {
private final String tableName;
diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt
index 465b5cd6..e87528eb 100644
--- a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt
+++ b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt
@@ -2,10 +2,8 @@ package org.cryptomator.data.db
import android.content.Context
import android.content.SharedPreferences
-import org.cryptomator.data.db.entities.CloudEntityDao
import org.cryptomator.util.crypto.CredentialCryptor
import org.greenrobot.greendao.database.Database
-import org.greenrobot.greendao.internal.DaoConfig
import javax.inject.Inject
import javax.inject.Singleton
@@ -13,16 +11,23 @@ import javax.inject.Singleton
internal class Upgrade2To3 @Inject constructor(private val context: Context) : DatabaseUpgrade(2, 3) {
override fun internalApplyTo(db: Database, origin: Int) {
- val clouds = CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll()
db.beginTransaction()
try {
- clouds.filter { cloud -> cloud.type == "DROPBOX" || cloud.type == "ONEDRIVE" } //
- .map {
- Sql.update("CLOUD_ENTITY") //
- .where("TYPE", Sql.eq(it.type)) //
- .set("ACCESS_TOKEN", Sql.toString(encrypt(if (it.type == "DROPBOX") it.accessToken else onedriveToken()))) //
- .executeOn(db)
+ Sql.query("CLOUD_ENTITY")
+ .columns(listOf("ACCESS_TOKEN"))
+ .where("TYPE", Sql.eq("DROPBOX"))
+ .executeOn(db).use {
+ if (it.moveToFirst()) {
+ Sql.update("CLOUD_ENTITY")
+ .set("ACCESS_TOKEN", Sql.toString(encrypt(it.getString(it.getColumnIndex("ACCESS_TOKEN")))))
+ .where("TYPE", Sql.eq("DROPBOX"));
+ }
}
+
+ Sql.update("CLOUD_ENTITY")
+ .set("ACCESS_TOKEN", Sql.toString(encrypt(onedriveToken())))
+ .where("TYPE", Sql.eq("ONEDRIVE"));
+
db.setTransactionSuccessful()
} finally {
db.endTransaction()
diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt
new file mode 100644
index 00000000..9f8b72e7
--- /dev/null
+++ b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt
@@ -0,0 +1,73 @@
+package org.cryptomator.data.db
+
+import org.greenrobot.greendao.database.Database
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class Upgrade4To5 @Inject constructor() : DatabaseUpgrade(4, 5) {
+
+ override fun internalApplyTo(db: Database, origin: Int) {
+ db.beginTransaction()
+ try {
+ changeWebdavUrlInCloudEntityToUrl(db)
+ db.setTransactionSuccessful()
+ } finally {
+ db.endTransaction()
+ }
+ }
+
+ private fun changeWebdavUrlInCloudEntityToUrl(db: Database) {
+ Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db)
+
+ Sql.createTable("CLOUD_ENTITY") //
+ .id() //
+ .requiredText("TYPE") //
+ .optionalText("ACCESS_TOKEN") //
+ .optionalText("URL") //
+ .optionalText("USERNAME") //
+ .optionalText("WEBDAV_CERTIFICATE") //
+ .executeOn(db);
+
+ Sql.insertInto("CLOUD_ENTITY") //
+ .select("_id", "TYPE", "ACCESS_TOKEN", "WEBDAV_URL", "USERNAME", "WEBDAV_CERTIFICATE") //
+ .columns("_id", "TYPE", "ACCESS_TOKEN", "URL", "USERNAME", "WEBDAV_CERTIFICATE") //
+ .from("CLOUD_ENTITY_OLD") //
+ .executeOn(db)
+
+ recreateVaultEntity(db)
+
+ Sql.dropTable("CLOUD_ENTITY_OLD").executeOn(db)
+ }
+
+ private fun recreateVaultEntity(db: Database) {
+ Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db)
+ Sql.createTable("VAULT_ENTITY") //
+ .id() //
+ .optionalInt("FOLDER_CLOUD_ID") //
+ .optionalText("FOLDER_PATH") //
+ .optionalText("FOLDER_NAME") //
+ .requiredText("CLOUD_TYPE") //
+ .optionalText("PASSWORD") //
+ .optionalInt("POSITION") //
+ .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) //
+ .executeOn(db)
+
+ Sql.insertInto("VAULT_ENTITY") //
+ .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_ENTITY.TYPE") //
+ .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_TYPE") //
+ .from("VAULT_ENTITY_OLD") //
+ .join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") //
+ .executeOn(db)
+
+ Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db)
+
+ Sql.createUniqueIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") //
+ .on("VAULT_ENTITY") //
+ .asc("FOLDER_PATH") //
+ .asc("FOLDER_CLOUD_ID") //
+ .executeOn(db)
+
+ Sql.dropTable("VAULT_ENTITY_OLD").executeOn(db)
+ }
+}
diff --git a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java
index 21551729..0ce2c8a1 100644
--- a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java
+++ b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java
@@ -16,18 +16,18 @@ public class CloudEntity extends DatabaseEntity {
private String accessToken;
- private String webdavUrl;
+ private String url;
private String username;
private String webdavCertificate;
- @Generated(hash = 2078985174)
- public CloudEntity(Long id, @NotNull String type, String accessToken, String webdavUrl, String username, String webdavCertificate) {
+ @Generated(hash = 361171073)
+ public CloudEntity(Long id, @NotNull String type, String accessToken, String url, String username, String webdavCertificate) {
this.id = id;
this.type = type;
this.accessToken = accessToken;
- this.webdavUrl = webdavUrl;
+ this.url = url;
this.username = username;
this.webdavCertificate = webdavCertificate;
}
@@ -60,12 +60,12 @@ public class CloudEntity extends DatabaseEntity {
this.id = id;
}
- public String getWebdavUrl() {
- return webdavUrl;
+ public String getUrl() {
+ return url;
}
- public void setWebdavUrl(String webdavUrl) {
- this.webdavUrl = webdavUrl;
+ public void setUrl(String url) {
+ this.url = url;
}
public String getUsername() {
diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java
index b8d683fc..af12e1ef 100644
--- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java
+++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java
@@ -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;
diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java
index 39118d2b..4b637b25 100644
--- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java
+++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java
@@ -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 {
.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()) //
@@ -54,7 +63,7 @@ public class CloudEntityMapper extends EntityMapper {
case WEBDAV:
return aWebDavCloudCloud() //
.withId(entity.getId()) //
- .withUrl(entity.getWebdavUrl()) //
+ .withUrl(entity.getUrl()) //
.withUsername(entity.getUsername()) //
.withPassword(entity.getAccessToken()) //
.withCertificate(entity.getWebdavCertificate()) //
@@ -82,12 +91,17 @@ public class CloudEntityMapper extends EntityMapper {
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;
case WEBDAV:
result.setAccessToken(((WebDavCloud) domainObject).password());
- result.setWebdavUrl(((WebDavCloud) domainObject).url());
+ result.setUrl(((WebDavCloud) domainObject).url());
result.setUsername(((WebDavCloud) domainObject).username());
result.setWebdavCertificate(((WebDavCloud) domainObject).certificate());
break;
diff --git a/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java b/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java
index 955822ab..ce4f9b41 100644
--- a/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java
+++ b/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java
@@ -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
-
+
diff --git a/domain/src/main/java/org/cryptomator/domain/CloudType.java b/domain/src/main/java/org/cryptomator/domain/CloudType.java
index 5161e418..b2df0cf8 100644
--- a/domain/src/main/java/org/cryptomator/domain/CloudType.java
+++ b/domain/src/main/java/org/cryptomator/domain/CloudType.java
@@ -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
}
diff --git a/domain/src/main/java/org/cryptomator/domain/PCloud.java b/domain/src/main/java/org/cryptomator/domain/PCloud.java
new file mode 100644
index 00000000..95164c65
--- /dev/null
+++ b/domain/src/main/java/org/cryptomator/domain/PCloud.java
@@ -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);
+ }
+
+ }
+
+}
diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToPCloud.java b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToPCloud.java
new file mode 100644
index 00000000..428480da
--- /dev/null
+++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToPCloud.java
@@ -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);
+ }
+}
diff --git a/fastlane/.default.env b/fastlane/.default.env
index 1e701b96..28589204 100644
--- a/fastlane/.default.env
+++ b/fastlane/.default.env
@@ -20,4 +20,4 @@ S3_SECRET_ACCESS_KEY=
SLACK_URL=
-GITHUB_TOKEN=
+GITHUB_PERSONAL_ACCESS_TOKEN=
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index c1e05118..eb99add4 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -32,7 +32,7 @@ platform :android do |options|
slack(
default_payloads: [], # reduce the notification to the minimum
- message: ":rocket: Successfully deployed #{version} with code #{build} to the Play Store :cryptomator:",
+ message: ":rocket: Successfully deployed #{version} with code #{build} :cryptomator:",
payload: {
"Changes" => File.read(release_note_path_en)
}
diff --git a/fastlane/metadata/android/de-DE/changelogs/default.txt b/fastlane/metadata/android/de-DE/changelogs/default.txt
index d723022d..5cdd56c1 100644
--- a/fastlane/metadata/android/de-DE/changelogs/default.txt
+++ b/fastlane/metadata/android/de-DE/changelogs/default.txt
@@ -1,4 +1,4 @@
-- Möglichkeit zum Sortieren der Tresorliste hinzugefügt
-- Logging in Google Drive Cloud hinzugefügt
-- Verhalten bei Änderung des OneDrive-Passworts verbessert
-- CryptoBot-Symbole aufpoliert
\ No newline at end of file
+- Native pCloud-Unterstützung hinzugefügt (großen Dank an Manu für die Implementierung)
+- App-Absturz beim Wiederherstellen von Cryptomator aus einem Backup behoben
+- Verbesserte Anzeige von langen Einstellungen
+- Verbessertes Löschen des letzten Bildes über die Vorschau. Springt jetzt zurück in die Tresor-Inhaltsliste
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/default.txt b/fastlane/metadata/android/en-US/changelogs/default.txt
index 22075f45..30049bab 100644
--- a/fastlane/metadata/android/en-US/changelogs/default.txt
+++ b/fastlane/metadata/android/en-US/changelogs/default.txt
@@ -1,4 +1,4 @@
-- Added possibility to sort vault list
-- Added logging to Google drive cloud
-- Enhanced behavior when OneDrive password changed
-- Polished CryptoBot icons
\ No newline at end of file
+- Added pCloud native support (thanks to Manu for this huge contribution)
+- Fixed app crash when restoring Cryptomator from a backup
+- Enhanced display of long settings
+- Enhanced deletion of the last image via the preview. Now jumps back to the vault contents list
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/.gitignore b/fastlane/metadata/android/fr-FR/changelogs/.gitignore
new file mode 100644
index 00000000..5e7d2734
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
diff --git a/fastlane/release-notes.html b/fastlane/release-notes.html
index 839af27d..f32f2534 100644
--- a/fastlane/release-notes.html
+++ b/fastlane/release-notes.html
@@ -1,6 +1,6 @@
- - Added possibility to sort vault list
- - Added logging to Google drive cloud
- - Enhanced behavior when OneDrive password changed
- - Polished CryptoBot icons
+ - Added pCloud native support (thanks to Manu for this huge contribution)
+ - Fixed app crash when restoring Cryptomator from a backup
+ - Enhanced display of long settings
+ - Enhanced deletion of the last image via the preview. Now jumps back to the vault contents list
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 13372aef..e708b1c0 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e9c0019c..442d9132 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Thu Apr 18 12:59:33 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
diff --git a/gradlew b/gradlew
index 9d82f789..4f906e0c 100755
--- a/gradlew
+++ b/gradlew
@@ -1,4 +1,20 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
##############################################################################
##
@@ -6,42 +22,6 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -60,8 +40,49 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -85,7 +106,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -105,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -134,27 +156,30 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=`save "$@"`
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 8a0b282a..ac1b06f9 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,90 +1,89 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/pcloud-sdk-java b/pcloud-sdk-java
new file mode 160000
index 00000000..d12c6e6c
--- /dev/null
+++ b/pcloud-sdk-java
@@ -0,0 +1 @@
+Subproject commit d12c6e6c4af8d0360812900663d5298ca093377b
diff --git a/presentation/build.gradle b/presentation/build.gradle
index 81a1ea40..8a1c5f99 100644
--- a/presentation/build.gradle
+++ b/presentation/build.gradle
@@ -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'
@@ -118,6 +120,7 @@ dependencies {
implementation project(':util')
implementation project(':domain')
implementation project(':data')
+ implementation project(':pcloud-sdk-android')
// dagger
kapt dependencies.daggerCompiler
diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml
index 882a5e27..fa7a7563 100644
--- a/presentation/src/main/AndroidManifest.xml
+++ b/presentation/src/main/AndroidManifest.xml
@@ -25,7 +25,7 @@
()
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)
diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt
index e9151719..2ca5df31 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt
@@ -850,8 +850,7 @@ class BrowseFilesPresenter @Inject constructor( //
private fun createNewDocumentUri(parentUri: Uri, fileName: String): Uri {
val mimeType = mimeTypes.fromFilename(fileName) //
.orElse(MimeType.APPLICATION_OCTET_STREAM)
- val newDocumentUri: Uri?
- newDocumentUri = try {
+ return try {
DocumentsContract.createDocument( //
context().contentResolver, //
parentUri, //
@@ -859,11 +858,7 @@ class BrowseFilesPresenter @Inject constructor( //
fileName)
} catch (e: FileNotFoundException) {
throw NoSuchCloudFileException(fileName)
- }
- if (newDocumentUri == null) {
- throw IllegalFileNameException()
- }
- return newDocumentUri
+ } ?: throw IllegalFileNameException()
}
@Callback
@@ -1093,7 +1088,7 @@ class BrowseFilesPresenter @Inject constructor( //
fun openFileFinished() {
try {
- // necessary see https://gitlab.skymatic.de/cryptomator/android/-/issues/569
+ // necessary see https://community.cryptomator.org/t/android-tabelle-nach-upload-unlesbar/6550
Thread.sleep(500)
} catch (e: InterruptedException) {
Timber.tag("BrowseFilesPresenter").e(e, "Failed to sleep after resuming editing, necessary for google office apps")
@@ -1107,12 +1102,17 @@ class BrowseFilesPresenter @Inject constructor( //
context().revokeUriPermission(uriToOpenedFile, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
uriToOpenedFile?.let {
- val hashAfterEdit = calculateDigestFromUri(it)
- if (hashAfterEdit.isPresent && openedCloudFileMd5.isPresent //
- && Arrays.equals(hashAfterEdit.get(), openedCloudFileMd5.get())) {
- Timber.tag("BrowseFilesPresenter").i("Opened app finished, file not changed")
- } else {
- uploadChangedFile()
+ try {
+ val hashAfterEdit = calculateDigestFromUri(it)
+ if (hashAfterEdit.isPresent && openedCloudFileMd5.isPresent //
+ && Arrays.equals(hashAfterEdit.get(), openedCloudFileMd5.get())) {
+ Timber.tag("BrowseFilesPresenter").i("Opened app finished, file not changed")
+ } else {
+ uploadChangedFile()
+ }
+ } catch (e: FileNotFoundException) {
+ Timber.tag("BrowseFilesPresenter").e(e, "Failed to read back changes, file isn't present anymore")
+ Toast.makeText(context(), R.string.error_file_not_found_after_opening_using_3party, Toast.LENGTH_LONG).show()
}
}
}
@@ -1159,6 +1159,7 @@ class BrowseFilesPresenter @Inject constructor( //
openWritableFileNotification.ifPresent { obj: OpenWritableFileNotification -> obj.hide() }
}
+ @Throws(FileNotFoundException::class)
private fun calculateDigestFromUri(uri: Uri): Optional {
val digest = MessageDigest.getInstance("MD5")
DigestInputStream(context().contentResolver.openInputStream(uri), digest).use { dis ->
diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt
index fae5629b..e5cab69e 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt
@@ -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() {
+ 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>() {
+ override fun onSuccess(clouds: List) {
+ 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() {
+ override fun onSuccess(void: Void?) {
+ loadCloudList()
+ }
+ })
+ }
+
@Callback
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
fun pickedLocalStorageLocation(result: ActivityResult) {
diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt
index b4f1af61..4c00f108 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt
@@ -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 = 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())
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt
index e3555a74..fd0ec4f7 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt
@@ -7,6 +7,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Handler
+import android.widget.Toast
import androidx.biometric.BiometricManager
import org.cryptomator.data.cloud.crypto.CryptoCloud
import org.cryptomator.data.util.NetworkConnectionCheck
@@ -154,7 +155,13 @@ class VaultListPresenter @Inject constructor( //
val intent = Intent(context(), LicenseCheckActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
intent.data = Uri.parse(String.format("app://cryptomator/%s", license))
- context().startActivity(intent)
+
+ try {
+ context().startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ Toast.makeText(context(), "Please contact the support.", Toast.LENGTH_LONG).show()
+ finish()
+ }
}
})
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt
index f9d0caf9..c5e1bc3c 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt
@@ -29,7 +29,7 @@ class ChooseCloudServiceActivity : BaseActivity(), ChooseCloudServiceView {
setSupportActionBar(toolbar)
}
- override fun createFragment(): Fragment? = ChooseCloudServiceFragment()
+ override fun createFragment(): Fragment = ChooseCloudServiceFragment()
override fun getCustomMenuResource(): Int = R.menu.menu_cloud_services
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt
index f44a24f8..35d65634 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt
@@ -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)
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt
index ee154fa0..85bd708b 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt
@@ -48,7 +48,7 @@ constructor() : RecyclerViewBaseAdapter 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
+ et_new_retype_password.nextFocusForwardId = button.id
+ }
}
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/CloudNodeRenameDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/CloudNodeRenameDialog.kt
index 2d340aef..a0780e22 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/CloudNodeRenameDialog.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/CloudNodeRenameDialog.kt
@@ -41,6 +41,9 @@ class CloudNodeRenameDialog : BaseProgressErrorDialog
+ et_rename.nextFocusForwardId = button.id
+ }
}
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/CreateFolderDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/CreateFolderDialog.kt
index 9d3794d2..1a166978 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/CreateFolderDialog.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/CreateFolderDialog.kt
@@ -36,6 +36,9 @@ class CreateFolderDialog : BaseProgressErrorDialog(
}
dialog.setCanceledOnTouchOutside(false)
et_folder_name.requestFocus()
+ createFolderButton?.let { button ->
+ et_folder_name.nextFocusForwardId = button.id
+ }
}
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/EnterPasswordDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/EnterPasswordDialog.kt
index da3f1846..913871d7 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/EnterPasswordDialog.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/EnterPasswordDialog.kt
@@ -41,7 +41,9 @@ class EnterPasswordDialog : BaseProgressErrorDialog
+ et_password.nextFocusForwardId = button.id
+ }
it.setCanceledOnTouchOutside(false)
et_password.requestFocus()
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/FileNameDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/FileNameDialog.kt
index 0bb9d731..b17f1336 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/FileNameDialog.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/FileNameDialog.kt
@@ -32,6 +32,9 @@ class FileNameDialog : BaseProgressErrorDialog() {
}
dialog.setCanceledOnTouchOutside(false)
file_name.requestFocus()
+ createFileButton?.let { button ->
+ file_name.nextFocusForwardId = button.id
+ }
}
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/UpdateLicenseDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/UpdateLicenseDialog.kt
index 52b5f5d5..6ffe92ee 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/UpdateLicenseDialog.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/UpdateLicenseDialog.kt
@@ -23,14 +23,17 @@ class UpdateLicenseDialog : BaseProgressErrorDialog
+ et_license.nextFocusForwardId = button.id
+ }
}
}
@@ -39,7 +42,6 @@ class UpdateLicenseDialog : BaseProgressErrorDialog } //
.setNegativeButton(getText(R.string.dialog_enter_license_decline_button)) { _: DialogInterface, _: Int -> callback?.onCheckLicenseCanceled() } //
- .setCancelable(false) //
.create()
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultRenameDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultRenameDialog.kt
index 7e11da2b..b4c47913 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultRenameDialog.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultRenameDialog.kt
@@ -37,6 +37,9 @@ class VaultRenameDialog : BaseProgressErrorDialog()
}
dialog.setCanceledOnTouchOutside(false)
et_rename.requestFocus()
+ renameConfirmButton?.let { button ->
+ et_rename.nextFocusForwardId = button.id
+ }
}
}
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/ChooseCloudServiceFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/ChooseCloudServiceFragment.kt
index 3c63a3e2..4902d353 100644
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/ChooseCloudServiceFragment.kt
+++ b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/ChooseCloudServiceFragment.kt
@@ -1,6 +1,6 @@
package org.cryptomator.presentation.ui.fragment
-import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
import org.cryptomator.generator.Fragment
import org.cryptomator.presentation.R
import org.cryptomator.presentation.model.CloudTypeModel
@@ -36,7 +36,7 @@ class ChooseCloudServiceFragment : BaseFragment() {
private fun setupRecyclerView() {
cloudsAdapter.setCallback(onItemClickListener)
- recyclerView.layoutManager = GridLayoutManager(context(), 2)
+ recyclerView.layoutManager = LinearLayoutManager(context())
recyclerView.adapter = cloudsAdapter
// smoother scrolling
recyclerView.setHasFixedSize(true)
diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/LicensesFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/LicensesFragment.kt
deleted file mode 100644
index 7df1e1eb..00000000
--- a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/LicensesFragment.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.cryptomator.presentation.ui.fragment
-
-import android.os.Bundle
-import androidx.preference.PreferenceFragmentCompat
-import org.cryptomator.presentation.R
-
-class LicensesFragment : PreferenceFragmentCompat() {
-
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- addPreferencesFromResource(R.xml.licenses)
- }
-}
diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_dropbox_large.png b/presentation/src/main/res/drawable-mdpi/cloud_type_dropbox_large.png
deleted file mode 100644
index 2e77eb43..00000000
Binary files a/presentation/src/main/res/drawable-mdpi/cloud_type_dropbox_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_google_drive_large.png b/presentation/src/main/res/drawable-mdpi/cloud_type_google_drive_large.png
deleted file mode 100644
index 9279d5b9..00000000
Binary files a/presentation/src/main/res/drawable-mdpi/cloud_type_google_drive_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_onedrive_large.png b/presentation/src/main/res/drawable-mdpi/cloud_type_onedrive_large.png
deleted file mode 100644
index b5504da6..00000000
Binary files a/presentation/src/main/res/drawable-mdpi/cloud_type_onedrive_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_webdav_large.png b/presentation/src/main/res/drawable-mdpi/cloud_type_webdav_large.png
deleted file mode 100644
index d55d555e..00000000
Binary files a/presentation/src/main/res/drawable-mdpi/cloud_type_webdav_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-mdpi/dropbox.png b/presentation/src/main/res/drawable-mdpi/dropbox.png
new file mode 100644
index 00000000..fde513b1
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/dropbox.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_dropbox.png b/presentation/src/main/res/drawable-mdpi/dropbox_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-mdpi/cloud_type_dropbox.png
rename to presentation/src/main/res/drawable-mdpi/dropbox_vault.png
diff --git a/presentation/src/main/res/drawable-mdpi/dropbox_vault_selected.png b/presentation/src/main/res/drawable-mdpi/dropbox_vault_selected.png
new file mode 100644
index 00000000..9c636e86
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/dropbox_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/google_drive.png b/presentation/src/main/res/drawable-mdpi/google_drive.png
new file mode 100644
index 00000000..4a2f04d7
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/google_drive.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_google_drive.png b/presentation/src/main/res/drawable-mdpi/google_drive_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-mdpi/cloud_type_google_drive.png
rename to presentation/src/main/res/drawable-mdpi/google_drive_vault.png
diff --git a/presentation/src/main/res/drawable-mdpi/google_drive_vault_selected.png b/presentation/src/main/res/drawable-mdpi/google_drive_vault_selected.png
new file mode 100644
index 00000000..315d60ed
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/google_drive_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/local_fs.png b/presentation/src/main/res/drawable-mdpi/local_fs.png
new file mode 100644
index 00000000..6025d2e1
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/local_fs.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/storage_type_local.png b/presentation/src/main/res/drawable-mdpi/local_fs_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-mdpi/storage_type_local.png
rename to presentation/src/main/res/drawable-mdpi/local_fs_vault.png
diff --git a/presentation/src/main/res/drawable-mdpi/local_fs_vault_selected.png b/presentation/src/main/res/drawable-mdpi/local_fs_vault_selected.png
new file mode 100644
index 00000000..ba53b5b9
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/local_fs_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/onedrive.png b/presentation/src/main/res/drawable-mdpi/onedrive.png
new file mode 100644
index 00000000..33a55eba
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/onedrive.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_onedrive.png b/presentation/src/main/res/drawable-mdpi/onedrive_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-mdpi/cloud_type_onedrive.png
rename to presentation/src/main/res/drawable-mdpi/onedrive_vault.png
diff --git a/presentation/src/main/res/drawable-mdpi/onedrive_vault_selected.png b/presentation/src/main/res/drawable-mdpi/onedrive_vault_selected.png
new file mode 100644
index 00000000..1e4bd83c
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/onedrive_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/pcloud.png b/presentation/src/main/res/drawable-mdpi/pcloud.png
new file mode 100644
index 00000000..bab8c5c5
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/pcloud.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/pcloud_vault.png b/presentation/src/main/res/drawable-mdpi/pcloud_vault.png
new file mode 100644
index 00000000..f3ccd64b
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/pcloud_vault.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/pcloud_vault_selected.png b/presentation/src/main/res/drawable-mdpi/pcloud_vault_selected.png
new file mode 100644
index 00000000..648a8f8f
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/pcloud_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/storage_type_local_large.png b/presentation/src/main/res/drawable-mdpi/storage_type_local_large.png
deleted file mode 100644
index fdcba645..00000000
Binary files a/presentation/src/main/res/drawable-mdpi/storage_type_local_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-mdpi/webdav.png b/presentation/src/main/res/drawable-mdpi/webdav.png
new file mode 100644
index 00000000..0280e06f
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/webdav.png differ
diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_webdav.png b/presentation/src/main/res/drawable-mdpi/webdav_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-mdpi/cloud_type_webdav.png
rename to presentation/src/main/res/drawable-mdpi/webdav_vault.png
diff --git a/presentation/src/main/res/drawable-mdpi/webdav_vault_selected.png b/presentation/src/main/res/drawable-mdpi/webdav_vault_selected.png
new file mode 100644
index 00000000..be58746c
Binary files /dev/null and b/presentation/src/main/res/drawable-mdpi/webdav_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_dropbox_large.png b/presentation/src/main/res/drawable-xhdpi/cloud_type_dropbox_large.png
deleted file mode 100644
index 60b2e26d..00000000
Binary files a/presentation/src/main/res/drawable-xhdpi/cloud_type_dropbox_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive_large.png b/presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive_large.png
deleted file mode 100644
index 204d6571..00000000
Binary files a/presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_onedrive_large.png b/presentation/src/main/res/drawable-xhdpi/cloud_type_onedrive_large.png
deleted file mode 100644
index 7c09f390..00000000
Binary files a/presentation/src/main/res/drawable-xhdpi/cloud_type_onedrive_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_webdav_large.png b/presentation/src/main/res/drawable-xhdpi/cloud_type_webdav_large.png
deleted file mode 100644
index 6f877927..00000000
Binary files a/presentation/src/main/res/drawable-xhdpi/cloud_type_webdav_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xhdpi/dropbox.png b/presentation/src/main/res/drawable-xhdpi/dropbox.png
new file mode 100644
index 00000000..6cb9cd48
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/dropbox.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_dropbox.png b/presentation/src/main/res/drawable-xhdpi/dropbox_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xhdpi/cloud_type_dropbox.png
rename to presentation/src/main/res/drawable-xhdpi/dropbox_vault.png
diff --git a/presentation/src/main/res/drawable-xhdpi/dropbox_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/dropbox_vault_selected.png
new file mode 100644
index 00000000..92ae71d3
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/dropbox_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/google_drive.png b/presentation/src/main/res/drawable-xhdpi/google_drive.png
new file mode 100644
index 00000000..c18ae762
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/google_drive.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive.png b/presentation/src/main/res/drawable-xhdpi/google_drive_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive.png
rename to presentation/src/main/res/drawable-xhdpi/google_drive_vault.png
diff --git a/presentation/src/main/res/drawable-xhdpi/google_drive_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/google_drive_vault_selected.png
new file mode 100644
index 00000000..2166ab4f
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/google_drive_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/local_fs.png b/presentation/src/main/res/drawable-xhdpi/local_fs.png
new file mode 100644
index 00000000..8761e85a
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/local_fs.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/storage_type_local.png b/presentation/src/main/res/drawable-xhdpi/local_fs_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xhdpi/storage_type_local.png
rename to presentation/src/main/res/drawable-xhdpi/local_fs_vault.png
diff --git a/presentation/src/main/res/drawable-xhdpi/local_fs_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/local_fs_vault_selected.png
new file mode 100644
index 00000000..f6461dd2
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/local_fs_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/onedrive.png b/presentation/src/main/res/drawable-xhdpi/onedrive.png
new file mode 100644
index 00000000..ef34a734
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/onedrive.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_onedrive.png b/presentation/src/main/res/drawable-xhdpi/onedrive_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xhdpi/cloud_type_onedrive.png
rename to presentation/src/main/res/drawable-xhdpi/onedrive_vault.png
diff --git a/presentation/src/main/res/drawable-xhdpi/onedrive_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/onedrive_vault_selected.png
new file mode 100644
index 00000000..9225a553
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/onedrive_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/pcloud.png b/presentation/src/main/res/drawable-xhdpi/pcloud.png
new file mode 100644
index 00000000..76446e04
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/pcloud.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/pcloud_vault.png b/presentation/src/main/res/drawable-xhdpi/pcloud_vault.png
new file mode 100644
index 00000000..13d91ac5
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/pcloud_vault.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/pcloud_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/pcloud_vault_selected.png
new file mode 100644
index 00000000..593e456c
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/pcloud_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/storage_type_local_large.png b/presentation/src/main/res/drawable-xhdpi/storage_type_local_large.png
deleted file mode 100644
index c0885405..00000000
Binary files a/presentation/src/main/res/drawable-xhdpi/storage_type_local_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xhdpi/webdav.png b/presentation/src/main/res/drawable-xhdpi/webdav.png
new file mode 100644
index 00000000..9bada6f7
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/webdav.png differ
diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_webdav.png b/presentation/src/main/res/drawable-xhdpi/webdav_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xhdpi/cloud_type_webdav.png
rename to presentation/src/main/res/drawable-xhdpi/webdav_vault.png
diff --git a/presentation/src/main/res/drawable-xhdpi/webdav_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/webdav_vault_selected.png
new file mode 100644
index 00000000..85abf330
Binary files /dev/null and b/presentation/src/main/res/drawable-xhdpi/webdav_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox_large.png b/presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox_large.png
deleted file mode 100644
index a7f144f1..00000000
Binary files a/presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive_large.png b/presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive_large.png
deleted file mode 100644
index db50a85f..00000000
Binary files a/presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_onedrive_large.png b/presentation/src/main/res/drawable-xxhdpi/cloud_type_onedrive_large.png
deleted file mode 100644
index 43d8b8aa..00000000
Binary files a/presentation/src/main/res/drawable-xxhdpi/cloud_type_onedrive_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav_large.png b/presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav_large.png
deleted file mode 100644
index 5ec3ef44..00000000
Binary files a/presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/dropbox.png b/presentation/src/main/res/drawable-xxhdpi/dropbox.png
new file mode 100644
index 00000000..34f3a496
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/dropbox.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox.png b/presentation/src/main/res/drawable-xxhdpi/dropbox_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox.png
rename to presentation/src/main/res/drawable-xxhdpi/dropbox_vault.png
diff --git a/presentation/src/main/res/drawable-xxhdpi/dropbox_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/dropbox_vault_selected.png
new file mode 100644
index 00000000..92939cce
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/dropbox_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/google_drive.png b/presentation/src/main/res/drawable-xxhdpi/google_drive.png
new file mode 100644
index 00000000..289e2fa6
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/google_drive.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive.png b/presentation/src/main/res/drawable-xxhdpi/google_drive_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive.png
rename to presentation/src/main/res/drawable-xxhdpi/google_drive_vault.png
diff --git a/presentation/src/main/res/drawable-xxhdpi/google_drive_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/google_drive_vault_selected.png
new file mode 100644
index 00000000..395f4948
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/google_drive_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/local_fs.png b/presentation/src/main/res/drawable-xxhdpi/local_fs.png
new file mode 100644
index 00000000..59475f68
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/local_fs.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/storage_type_local.png b/presentation/src/main/res/drawable-xxhdpi/local_fs_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xxhdpi/storage_type_local.png
rename to presentation/src/main/res/drawable-xxhdpi/local_fs_vault.png
diff --git a/presentation/src/main/res/drawable-xxhdpi/local_fs_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/local_fs_vault_selected.png
new file mode 100644
index 00000000..7ea7d563
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/local_fs_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/onedrive.png b/presentation/src/main/res/drawable-xxhdpi/onedrive.png
new file mode 100644
index 00000000..52c04c95
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/onedrive.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_onedrive.png b/presentation/src/main/res/drawable-xxhdpi/onedrive_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xxhdpi/cloud_type_onedrive.png
rename to presentation/src/main/res/drawable-xxhdpi/onedrive_vault.png
diff --git a/presentation/src/main/res/drawable-xxhdpi/onedrive_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/onedrive_vault_selected.png
new file mode 100644
index 00000000..364e6516
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/onedrive_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/pcloud.png b/presentation/src/main/res/drawable-xxhdpi/pcloud.png
new file mode 100644
index 00000000..007cc327
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/pcloud.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/pcloud_vault.png b/presentation/src/main/res/drawable-xxhdpi/pcloud_vault.png
new file mode 100644
index 00000000..3cf1b337
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/pcloud_vault.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/pcloud_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/pcloud_vault_selected.png
new file mode 100644
index 00000000..a6ca25e9
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/pcloud_vault_selected.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/storage_type_local_large.png b/presentation/src/main/res/drawable-xxhdpi/storage_type_local_large.png
deleted file mode 100644
index 4bc9f07a..00000000
Binary files a/presentation/src/main/res/drawable-xxhdpi/storage_type_local_large.png and /dev/null differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/webdav.png b/presentation/src/main/res/drawable-xxhdpi/webdav.png
new file mode 100644
index 00000000..c913817a
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/webdav.png differ
diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav.png b/presentation/src/main/res/drawable-xxhdpi/webdav_vault.png
similarity index 100%
rename from presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav.png
rename to presentation/src/main/res/drawable-xxhdpi/webdav_vault.png
diff --git a/presentation/src/main/res/drawable-xxhdpi/webdav_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/webdav_vault_selected.png
new file mode 100644
index 00000000..4661a93d
Binary files /dev/null and b/presentation/src/main/res/drawable-xxhdpi/webdav_vault_selected.png differ
diff --git a/presentation/src/main/res/layout/item_cloud.xml b/presentation/src/main/res/layout/item_cloud.xml
index 29e0d2e9..af4afd5d 100644
--- a/presentation/src/main/res/layout/item_cloud.xml
+++ b/presentation/src/main/res/layout/item_cloud.xml
@@ -1,23 +1,42 @@
-
+ android:layout_height="72dp"
+ android:background="?android:attr/selectableItemBackground">
-
+
+
+
+
+
+
+
+
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_alignParentBottom="true"
+ android:layout_marginStart="16dp"
+ android:layout_toEndOf="@+id/cloudImage"
+ android:background="@color/list_divider" />
-
+
diff --git a/presentation/src/main/res/values-de/strings.xml b/presentation/src/main/res/values-de/strings.xml
index 92b776b1..4e81b793 100644
--- a/presentation/src/main/res/values-de/strings.xml
+++ b/presentation/src/main/res/values-de/strings.xml
@@ -5,6 +5,7 @@
Ein Fehler ist aufgetreten
Authentifizierung fehlgeschlagen
+ Authentifizierungsfehler, bitte mit %1$s anmelden
Keine Internetverbindung
Falsches Passwort
Die Datei oder der Ordner existiert bereits.
@@ -25,6 +26,7 @@
Beim entschlüsseln des WebDAV-Passworts trat ein Fehler auf. Bitte in den Einstellungen erneut festlegen.
Die Play Services sind nicht installiert
Biometrischer Login abgebrochen
+ Lokale Datei ist nach dem Zurückwechseln zu Cryptomator nicht mehr vorhanden. Mögliche Änderungen können nicht in die Cloud übertragen werden.
Lokaler Speicher
@@ -54,7 +56,6 @@
Passwort wurde erfolgreich geändert
Tresor
- Neuen Tresor anlegen
Masterkey-Datei auswählen
Hier ablegen
Tresorname: %1$s
@@ -101,10 +102,8 @@
Speicherort
Speichern
Dateien verschlüsselt
- text.txt
Cloud-Dienst
- Neuen Tresor anlegen
Ort auswählen
Hier einen neuen Ort hinzufügen
@@ -126,7 +125,6 @@
Erstellen
Passwort setzen
- Passwort muss ausgefüllt werden.
Passwort stimmt nicht mit dem wiederholten Passwort überein.
Fertig
WICHTIG: Wenn Sie Ihr Passwort vergessen, gibt es keine Möglichkeit die Daten zu entschlüsseln.
@@ -143,19 +141,24 @@
Biometrischer Login aktivieren
Nach Entsperrung mittels dem Gesichts, bestätigen (falls verfügbar)
App blockieren, wenn verdeckt
- Bildschirm-Sicherheit
+ Blockiere das Abfangen der Eingabe und das Anzeigen einer falschen Benutzeroberfläche
+ Blockiere Screenshots
+ Blockiere Screenshots im Anwendungsverlauf und innerhalb der App
Suche
Live-Suche
+ Suchergebnisse während der Eingabe der Abfrage aktualisieren
Suche mit Glob-Muster
+ Verwende Glob-Muster wie z.B. alice.*.jpg
Automatisch sperren
Sperren nach
Bei deaktiviertem Bildschirm
Automatisches Photo-Hochladen
Tresor auswählen für das Hochladen
Aktivieren
+ Bilder im Hintergrund aufnehmen und sobald der ausgewählte Tresor entsperrt ist, Upload automatisch starten
Nur mit WLAN hochladen
+ Videos hochladen
Bilder abspeichern in…
- Uns folgen
Cryptomator-Website
Folgen Sie uns auf Twitter
Liken Sie uns auf Facebook
@@ -167,11 +170,14 @@
Log-Datei senden
Senden fehlgeschlagen
Sicherheitshinweise
- Halte Tresore geöffnet während dem Editieren einer Datei
Erweiterte Eigenschaften
- Vorbereitungen zum Entsperren im Hintergrund
+ Entsperren beschleunigen
+ Während der Passwort-Eingabe oder biometrische Authentifizierung, Tresor-Konfiguration im Hintergrund herunterladen
+ Entsperrt bleiben
+ Halte Tresore geöffnet während dem Editieren einer Datei
WebDAV-Verbindungen
+ pCloud-Verbindungen
Lokale Speicherorte
Einloggen in
Abmelden von
@@ -206,11 +212,12 @@
Ersetzen
Eine Datei names \'%1$s\' existiert bereits. Soll diese ersetzt werden?
Alle Dateien existieren bereits. Sollen diese ersetzt werden?
- %d Dateien existieren bereits. Sollen diese ersetzt werden?
+ %1$d Dateien existieren bereits. Sollen diese ersetzt werden?
Datei ersetzen?
Dateien ersetzen?
Teilen nicht möglich
Sie haben keinen Tresor eingerichtet. Bitte legen Sie zuerst einen Tresor mit der Cryptomator-App an.
+ OK
Tresor erstellen
%1$s kann nicht geöffnet werden
Bitte installieren Sie eine App, die diese Datei öffnen kann. Möchten Sie die Datei stattdessen auf dem Gerät speichern?
@@ -220,8 +227,7 @@
Sie haben ungespeicherte Änderungen
Möchten Sie wirklich beenden, ohne zu speichern?
Verwerfen
- @string/dialog_button_cancel
- @string/screen_share_files_new_text_file
+ text.txt
Sind Sie sicher, dass sie den Tresor entfernen wollen?
Dieser Vorgang wird den Tresor nur aus dieser Liste entfernen und nicht tatsächlich löschen.
Lade hoch…
@@ -244,6 +250,7 @@
Sperren
Ungültiges SSL-Zertifikat
Das SSL-Zertifikat ist ungültig. Wollen Sie diesem trotzdem vertrauen?
+ Details
Dies könnte ein Sicherheitsrisiko sein. Ich weiß was ich tue.
Die Verwendung von HTTP ist unsicher. Wir empfehlen stattdessen die Nutzung von HTTPS. Wenn Sie sich der Risiken bewusst sind, können Sie mit HTTP fortfahren.
Zu HTTPS ändern
@@ -292,6 +299,7 @@
Der Ordner \'%1$s\' in der Cloud hat keine Verzeichniss-Datei. Es könnte sein, dass der Ordner auf einem anderen Gerät erstellt wurde und noch nicht vollständig mit der Cloud synchronisiert ist. Bitte überprüfen, ob die folgende Datei in der Cloud existiert:\n%2$s
Beta-Version
Das ist eine Beta-Version, die das Tresor-Format 7 unterstützt. Bitte nicht mit einem produktiv eingesetzten Tresor verwenden oder dafür sorgen, dass gute Sicherungen vorhanden sind.
+ Keine weiteren Bilder anzuzeigen…
Cryptomator benötigt Zugriff auf den Speicher um lokale Tresore zu nutzen
Cryptomator benötigt Zugriff auf den Speicher um den automatischen Foto-Upload zu nutzen
@@ -336,6 +344,7 @@
Tresor bleibt entsperrt bis die Datei nicht mehr editiert wird
Neueste Version installiert
Zwischenspeicher
+ Cache kürzlich geöffnete Dateien lokal und verschlüsseltauf dem Gerät für eine spätere Wiederverwendung beim erneuten öffnen
Zwischenspeichergröße insgesamt
Zwischenspeicher leeren
Änderungen werden nach einem Neustart der App aktiv
@@ -350,6 +359,7 @@
2 Minuten
5 Minuten
10 Minuten
+ Nie
Design
diff --git a/presentation/src/main/res/values-es/strings.xml b/presentation/src/main/res/values-es/strings.xml
index 7e55f17c..ce79edd2 100644
--- a/presentation/src/main/res/values-es/strings.xml
+++ b/presentation/src/main/res/values-es/strings.xml
@@ -110,6 +110,7 @@
Versión
Conexiones de WebDAV
+ Conexiones de pCloud
Ubicaciones de almacenamiento local
Iniciar sesión en
Cerrar sesión de
@@ -141,7 +142,7 @@
Reemplazar
Ya existe un archivo llamado %1$s. ¿Quieres reemplazarlo?
Todos los archivos existen ya. ¿Quieres reemplazarlos?
- \"%d archivos existen ya. ¿Quieres reemplazarlos?
+ %1$d archivos existen ya. ¿Quieres reemplazarlos?
\"¿Reemplazar archivo?
\"¿Reemplazar archivos?
No se puede compartir archivos
diff --git a/presentation/src/main/res/values-fr/strings.xml b/presentation/src/main/res/values-fr/strings.xml
index a9fe6d4a..998efadd 100644
--- a/presentation/src/main/res/values-fr/strings.xml
+++ b/presentation/src/main/res/values-fr/strings.xml
@@ -5,6 +5,7 @@
Une erreur est survenue
Échec de l\'authentification
+ Échec de l\'authentification, veuillez vous connecter en utilisant %1$s
Pas de connexion au réseau
Mot de passe erroné
Un fichier ou un dossier existe déjà.
@@ -142,19 +143,24 @@
Activer l\'authentification biométrique
Confirmer le déverrouillage par reconnaissance faciale (si disponible)
Bloquer l\'application lorsqu\'elle est masquée
- Sécurité de l\'écran
+ Bloquer l\'interception de l\'entrée et l\'affichage d\'une fausse interface utilisateur
+ Bloquer les captures d\'écran
+ Empêcher la prise de capture d\'écran dans la liste des éléments récents et dans l\'application
Recherche
Recherche en direct
+ Mettre à jour les résultats de la recherche pendant la saisie de la requête
Recherche avec le modèle glob
+ Utilisez le modèle de correspondance glob comme alice.*.jpg
Verrouillage automatique
Verrouillage après
Lorsque l\'écran est éteint
Téléversement automatique de photo
Choisir un coffre-fort pour le téléversement
Activer
+ Capturez les images en arrière-plan et une fois que le coffre-fort sélectionné est déverrouillé, lancez le téléversement
Téléverser sur réseau WIFI uniquement
Téléversement des vidéos
- Enregistrer les fichiers du téléversement automatique dans…
+ Enregistrer les fichiers téléverser automatiquement dans…
Site web de Cryptomator
Suivez-nous sur Twitter
Aimez notre page Facebook
@@ -168,11 +174,14 @@
L\'envoi a échoué
Conseils de sécurité
Version
- Gardez les coffres déverrouillés lors de la modification des fichiers
Paramètres Avancés
- Préparations du déverrouillage en arrière-plan
+ Accélérer le déverrouillage
+ Téléchargez la configuration du coffre-fort en arrière-plan lorsque vous êtes invité à entrer le mot de passe ou l\'authentification biométrique
+ Maintenir deverouillé
+ Gardez les coffres forts déverrouillées pendant l\'édition des fichiers
Connexions WebDAV
+ Connexions pCloud
Emplacements du stockage local
Se connecter à
Se déconnecter de
@@ -207,7 +216,7 @@
Remplacer
Un fichier nommé \'%1$s\' existe déjà. Voulez-vous le remplacer?
Tous les fichiers existent déjà. Voulez-vous les remplacer?
- %d fichiers existent déjà. Voulez-vous les remplacer?
+ %1$d fichiers existent déjà. Voulez-vous les remplacer?
Remplacer le fichier?
Remplacer les fichiers?
Impossible de partager des fichiers
@@ -294,6 +303,7 @@
Le dossier cloud \'%1$s\' n\'a pas de fichier de répertoire. Il se peut que le dossier ait été créé sur un autre appareil et qu\'il n\'ait pas encore été entièrement synchronisé avec le cloud. Veuillez vérifier dans votre cloud si le fichier suivant existe: \n%2$s
Version bêta
Il s\'agit d\'une version bêta qui introduit la prise en charge du format de coffre-fort vault 7. Veuillez vous assurer que vous n\'utilisez pas votre coffre-fort principal pour les tests ou que vous disposez d\'une bonne stratégie de sauvegarde.
+ Plus d\'images à afficher…
Cryptomator a besoin de l\'accès au stockage pour utiliser les coffres locaux
Cryptomator a besoin de l\'accès au stockage pour effectuer le téléversement automatique de photos
@@ -342,6 +352,7 @@
Le coffre-fort reste déverrouillé jusqu\'à la fin des modifications
Dernière version installée
Cache
+ Mettre en cache les fichiers récemment consultés chiffrés localement sur l\'appareil pour une réutilisation lors d\'une réouverture ultérieure
Taille totale du cache
Vider le cache
Les changements seront appliqués lors du prochain démarrage de l\'application
diff --git a/presentation/src/main/res/values-pl/strings.xml b/presentation/src/main/res/values-pl/strings.xml
new file mode 100644
index 00000000..c1f0cdd8
--- /dev/null
+++ b/presentation/src/main/res/values-pl/strings.xml
@@ -0,0 +1,382 @@
+
+
+
+ Szyfruj
+
+ Błąd programu
+ Uwierzytelnianie nie powiodło się
+ Uwierzytelnianie nie powiodło się, zaloguj się za pomocą %1$s
+ Brak połączenia z Internetem
+ Błędne hasło
+ Plik lub folder już istnieje.
+ Nieobsługiwany sejf. Ten sejf został utworzony z inną wersją Cryptomator.
+ Sejf już istnieje.
+ Plik nie istnieje.
+ Sejf został zablokowany.
+ Usługa już istnieje.
+ Pobierz aplikację, która może otworzyć ten plik.
+ Nie znaleziono serwera.
+ Otwórz ustawienia urządzenia i ustaw ręcznie blokadę ekranu
+ Eksport nie powiódł się. Spróbuj usunąć znaki specjalne z nazw plików i spróbuj ponownie.
+ Nie może zawierać znaków specjalnych.
+ Nazwa pliku nie może zawierać znaków specjalnych.
+ Nazwa skarbca nie może zawierać znaków specjalnych.
+ Błąd sprawdzania aktualizacji. Wystąpił błąd ogólny.
+ Błąd sprawdzania aktualizacji. Brak połączenia z Internetem.
+ Nie udało się odszyfrować hasła WebDAV, proszę dodać je w ustawieniach
+ Usługi Google Play nie są zainstalowane
+ Przerwano biometryczną autoryzację
+
+
+ Pamięć wewnętrzna
+
+
+ Cryptomator potrzebuje dostępu do pamięci masowej do eksportu plików
+ Cryptomator potrzebuje dostępu do pamięci masowej, aby przesłać pliki
+ Cryptomator potrzebuje dostępu do pamięci masowej, aby udostępniać pliki
+ Ustawienia
+ Szukaj
+ Poprzedni
+ Następny
+ Sortuj
+ A - Z
+ Z - A
+ Od najnowszych
+ Od najstarszych
+ Od największych
+ Od najmniejszych
+
+
+ Dodaj do Cryptomator
+ Utwórz nowy sejf
+ Dodaj istniejący sejf
+ Usuń
+ Kliknij tutaj, aby utworzyć nowy sejf
+ Hasło zostało pomyślnie zmienione
+
+ Sejf
+ Wybierz plik klucza głównego
+ Umieść tutaj
+ Nazwa skarbca: %1$s
+ Przenieś
+ Pusty folder
+ zmodyfikowano %1$s temu
+ Udostępnij z
+ Miejsce docelowe
+ Wybierz
+ Nie ma nic do udostępnienia
+ Dodaj do %1$s
+ Utwórz folder
+ Utwórz plik tekstowy
+ Prześlij pliki
+ Pliki
+ Plik został wyeksportowany
+ Pliki zostały wyeksportowane
+ Nie ma nic do wyeksportowania
+ Tworzenie katalogu pobierania nie powiodło się
+ Udostępnij
+ Zmień nazwę
+ Edytuj
+ Eksportuj
+ Usuń
+ Otwórz z…
+ Wybierz elementy
+ %1$d wybranych
+ Wybierz…
+ Zaznacz wszystko
+ Odśwież
+ Brak połączenia
+ Ponów
+
+ Zapisano pomyślnie
+
+ Zapisz %1$s do…
+ tekst
+ plik
+ pliki
+ Nazwy plików muszą być unikalne, zmień nazwy duplikatów.
+ Lokalizacja zapisu
+ Zapisz
+ Szyfrowanie zakończone
+
+ Usługi chmury
+
+ Wybierz lokalizację
+ Kliknij tutaj, aby dodać nową lokalizację
+ Serwer wydaje się być niekompatybilny z WebDAV
+ Niestandardowe lokalizacje
+ Pamięć domyślna
+ Brak dodatkowych lokalizacji.
+
+ URL
+ Login
+ Hasło
+ Połącz
+ Adres URL nie może być pusty.
+ Adres URL jest nieprawidłowy.
+ Login nie może być pusty.
+ Hasło nie może być puste.
+
+ Nazwa skarbca nie może być pusta.
+ Nazwa skarbca
+ Utwórz
+
+ Ustaw hasło
+ Hasła nie zgadzają się.
+ Gotowe
+ WAŻNE: Jeśli zapomnisz hasła, nie ma możliwości odzyskania danych.
+ Wpisz hasło ponownie
+ Bardzo słabe
+ Słabe
+ Średnie
+ Mocne
+ Bardzo mocne
+
+ Ogólne
+ Usługi chmury
+ Uwierzytelnianie biometryczne
+ Aktywuj uwierzytelnianie biometryczne
+ Potwierdź odblokowanie twarzą (jeśli dostępne)
+ Zablokuj aplikację po ukryciu
+ Blokuj przechwytywanie danych wejściowych i wyświetlanie fałszywego interfejsu użytkownika
+ Blokuj zrzuty ekranu
+ Blokuj zrzuty ekranu na liście ostatnich aplikacji i wewnątrz aplikacji
+ Wyszukiwanie
+ Wyszukiwanie
+ Aktualizuj wyniki wyszukiwania podczas wprowadzania zapytania
+ Szukaj używając wzorca glob
+ Użyj wzoru glob pasującego do alice.*.jpg
+ Automatyczne blokowanie
+ Zablokuj po
+ Kiedy ekran jest wyłączony
+ Automatyczne przesyłanie zdjęć
+ Wybierz sejf do przesłania
+ Aktywuj
+ Przechwytuj zdjęcia w tle i po odblokowaniu wybranego sejfu rozpocznij przesyłanie
+ Prześlij tylko przy użyciu WIFI
+ Prześlij filmy
+ Zapisz automatycznie przesyłane pliki w…
+ Cryptomator w Internecie
+ Śledź nas na Twitterze
+ Polub nas na Facebooku
+ Informacje prawne
+ Licencje
+ Warunki licencji
+ Wsparcie
+ Poproś o pomoc
+ Tryb debugowania
+ Wyślij plik dziennika
+ Próba wysłania raportu nie powiodła się
+ Wskazówki bezpieczeństwa
+ Wersja
+ Ustawienia zaawansowane
+ Przyspiesz odblokowanie
+ Pobierz konfigurację sejfu w tle, gdy zostaniesz poproszony o wprowadzenie hasła lub autoryzację biometryczną
+ Zachowaj odblokowany
+ Zachowaj odblokowane skarbce podczas edycji plików
+
+ Połączenia WebDAV
+ Połączenia pCloud
+ Lokalizacja pamięci wewnętrznej
+ Zaloguj się do
+ Wyloguj się z
+
+
+ Nie można uwierzytelnić %1$s.
+
+ \'%1$s\' jest nieosiągalne
+ Cryptomator wykrył, że ten folder jest nieosiągalny.
+ Być może został usunięty przez inną aplikację lub wystąpiła nieprawidłowa synchronizacja z usługą chmury. \n\nSpróbuj przywrócić plik katalogu za pośrednictwem dostawcy chmury do poprzedniej wersji, która nie jest pusta. Omawiany plik to:\n%1$s\n\nJeśli to nie zadziała, możesz użyć Sanitizera do sprawdzenia swojego skarbca pod kątem problemów i ewentualnie przywrócić dane.
+ Więcej szczegółów na temat Sanitizera
+
+
+ Anuluj
+ Odblokuj
+ Stare hasło
+ Nowe hasło
+ Zmień hasło
+ Stare hasło nie może być puste.
+ Nowe hasło nie może być puste.
+ Hasła nie zgadzają się.
+
+ Skarbca %1$s nie odnaleziono
+ Sejf został przeniesiony, usunięty albo zmieniono jego nazwę. Należy usunąć ten sejf z listy i dodać go ponownie. Chcesz to zrobić teraz?
+ Usuń
+ Plik już istnieje
+ Zastąp
+ Plik \'%1$s\' już istnieje.
+ Pomiń istniejące
+ Zastąp wszystko
+ Zastąp istniejące
+ Zastąp
+ Plik \'%1$s\' już istnieje. Czy chcesz go zastąpić?
+ Pliki już istnieją. Czy chcesz go zastąpić?
+ Zastąp plik?
+ Zastąp pliki?
+ Nie można udostępnić plików
+ Nie wczytałeś żadnych skarbców. Proszę najpierw utworzyć nowy sejf z aplikacją Cryptomator.
+ OK
+ Utwórz sejf
+ Nie można otworzyć %1$s
+ Pobierz aplikację, która może otworzyć ten plik, a może chcesz zapisać ten plik na swoim urządzeniu?
+ Zmień nazwę skarbca
+ Zmień nazwę folderu
+ Zmień nazwę pliku
+ Istnieją niezapisane zmiany
+ Czy na pewno chcesz wyjść bez zapisywania?
+ Zaniechaj
+ tekst.txt
+ Czy na pewno chcesz usunąć ten sejf?
+ Ta akcja usunie tylko sejf z tej listy i nie usunie go fizycznie.
+ Przesyłanie…
+ Plik %1$d z %2$d
+ Eksportowanie (%1$d/%2$d)
+ Proszę czekać…
+ Tworzenie folderu…
+ Tworzenie pliku tekstowego…
+ Uwierzytelnianie…
+ Zmiana nazwy…
+ Usuwanie…
+ Odblokowanie skarbca…
+ Zmiana hasła…
+ Tworzenie skarbca…
+ Przesyłanie…
+ Pobieranie…
+ Szyfrowanie…
+ Odszyfrowanie…
+ Przenoszenie…
+ Zablokuj
+ Nieprawidłowy certyfikat SSL
+ Certyfikat SSL jest nieprawidłowy. Czy mimo to chcesz mu zaufać?
+ Szczegóły
+ Mogłoby to stanowić zagrożenie dla bezpieczeństwa. Wiem, co robię.
+ Używanie HTTP jest niezabezpieczone. Zamiast tego zalecamy używanie HTTPS. Jeśli znasz ryzyko, możesz kontynuować używając HTTP.
+ Zmień na HTTPS
+ Używać HTTPS?
+ Nie ustawiono blokady ekranu. Aby przechowywać dane logowania w bezpieczny sposób, ustaw wzór lub hasło.
+ Ustawić blokadę ekranu?
+ Ustaw blokadę ekranu
+ Brak konfiguracji podstawowego uwierzytelnienia w systemie
+ Zapisz co najmniej jeden odcisk palca/wizerunek twarzy, aby korzystać z tej usługi.
+ W tym trybie poufne dane mogą być zapisywane do pliku dziennika na urządzeniu (np. nazwy plików i ścieżki). Hasła, ciasteczka itp. są wykluczone.\n\nPamiętaj, aby wyłączyć tryb debugowania tak szybko, jak to możliwe.
+ Uwaga
+ Włącz
+ To ustawienie jest funkcją bezpieczeństwa i uniemożliwia innym aplikacjom oszukiwanie użytkowników do robienia rzeczy, których nie chcą robić.\n\nWyłączając je potwierdzasz, że jesteś świadomy ryzyka.
+ Uwaga
+ Wyłącz
+ Aplikacja jest zasłonięta inną
+ Inna aplikacja jest wyświetlana nad Cryptomatorem (na przykład filtr niebieskiego światła, tryb nocny). Dla twojego bezpieczeństwa Cryptomator jest wyłączony.\n\nJak ponownie włączyć aplikację.
+ Zamknij
+ To ustawienie jest funkcją bezpieczeństwa i uniemożliwia innym aplikacjom oszukiwanie użytkowników do robienia rzeczy, których nie chcą robić.\n\nWyłączając je potwierdzasz, że jesteś świadomy ryzyka.
+ Czy na pewno chcesz usunąć to połączenie z serwerem chmury?
+ Ta akcja usunie połączenie z usługą chmury i wszystkie skarbce w tej chmurze.
+ Usunąć %1$d elementów?
+ Na pewno chcesz usunąć wybrane elementy?
+ Na pewno chcesz usunąć ten plik?
+ Spowoduje to usunięcie całej zawartości folderu. Czy na pewno chcesz usunąć ten folder?
+ Funkcja uwierzytelniania biometrycznego jest wyłączona
+ Ponieważ klucz został unieważniony, funkcja uwierzytelniania biometrycznego została dezaktywowana. Aby ponownie włączyć, otwórz ustawienia Cryptomator.
+ Podaj poprawną licencję
+ Wykryliśmy, że zainstalowałeś Cryptomator bez korzystania ze sklepu Google Play. Podaj poprawną licencję, którą można kupić na https://cryptomator.org/android/
+ Podana licencja jest nieprawidłowa. Upewnij się, że została wprowadzona poprawnie.
+ Nie udzielono licencji. Wprowadź prawidłową licencję.
+ Wyjdź
+ Informacje o licencji
+ Dziękujemy %1$s za korzystanie z ważnej licencji.
+ Aktualizacja jest dostępna
+ Zaktualizuj Cryptomator do najnowszej wersji. Aby pobrać aktualizację potwierdź przyciskiem OK. Następnie zostaniesz poproszony o instalację pobranej aktualizacji.
+ Aktualizuj teraz
+ Otwórz stronę pobierania
+ Później
+ Trwa pobieranie
+ Pobieranie najnowszej wersji Cryptomator
+ Folder jest linkiem symbolicznym
+ Nie możesz przejść do tego linku symbolicznego
+ Powrót
+ Nie można załadować zawartości katalogu
+ Folder \'%1$s\' w chmurze nie ma pliku katalogowego. Być może folder został utworzony na innym urządzeniu i nie został jeszcze w pełni zsynchronizowany z chmurą. Sprawdź w chmurze, czy następujący plik istnieje:\n%2$s
+ Wersja Beta
+ To jest wydanie beta wprowadzająca obsługę formatu 7 skarbca. Przed kontynuowaniem upewnij się, że masz kopię zapasową skarbca oraz nie używasz wersji jego wersji produkcyjnej.
+ Brak obrazów do wyświetlenia…
+ Cryptomator potrzebuje dostępu do pamięci lokalnej, aby uzyskać dostęp do skarbca
+ Cryptomator potrzebuje dostępu do pamięci lokalnej, aby automatycznie przesyłać zdjęcia
+
+
+
+ 0 kB
+ bajtów
+ kB
+ MB
+ GB
+ TB
+
+ sekunda
+ sekund
+ minuta
+ minut
+ godzina
+ godzin
+ dzień
+ dni
+ tydzień
+ tygodni
+ miesiąc
+ miesięcy
+ rok
+ lata
+
+ Logowanie biometryczne
+ Zaloguj się przy użyciu danych biometrycznych
+ Użyj hasła skarbca
+ Nie można automatycznie przesłać plików
+
+ Odblokowane skarbce: %1$d
+ Automatyczna blokada w %1$s
+ Zablokuj wszystko
+ Anuluj przesyłanie
+ Automatyczne przesyłanie zdjęć w toku
+ Przesyłanie %1d/%2d
+ Automatyczne przesyłanie zdjęć zostało zakończone
+ %1$d zdjęć zostało przesłanych do skarbca
+ Automatyczne przesyłanie zdjęć nie powiodło się
+ Wystąpił błąd podczas przesyłania.
+ Wybrany folder do przesłania nie jest już dostępny. Przejdź do ustawień i wybierz nowy
+ Sejf został zablokowany podczas przesyłania, otwórz go ponownie aby kontynuować
+ Otwórz plik zapisywalny
+ Sejf pozostaje odblokowany do czasu zakończenia edycji
+ Zainstalowano najnowszą wersję
+ Pamięć podręczna
+ Pamięć podręczna ostatnio uruchomionych plików zaszyfrowanych lokalnie na urządzeniu w celu ponownego użycia po ponownym otwarciu
+ Rozmiar całkowity pamięci podręcznej
+ Wyczyść pamięć podręczną
+ Zmiany zostaną zastosowane po ponownym uruchomieniu
+ Zarejestrowano dla
+ %1$s
+ Częstotliwość sprawdzania aktualizacji
+ Sprawdź dostępne aktualizacje
+ Ostatnio uruchomiono %1$s
+ Rozmiar pamięci podręcznej na usługę
+
+ Natychmiast
+ 1 minuta
+ 2 minuty
+ 5 minut
+ 10 minut
+ Nigdy
+
+ 50 MB
+ 100 MB
+ 250 MB
+ 500 MB
+ 1 GB
+ 5 GB
+
+ Wygląd
+ Automatyczny (motyw systemu)
+ Jasny
+ Ciemny
+
+ Raz dziennie
+ Raz w tygodniu
+ Raz w miesiącu
+
diff --git a/presentation/src/main/res/values-tr/strings.xml b/presentation/src/main/res/values-tr/strings.xml
index 4b24f543..9f6ae216 100644
--- a/presentation/src/main/res/values-tr/strings.xml
+++ b/presentation/src/main/res/values-tr/strings.xml
@@ -138,7 +138,6 @@
Biyometrik kimlik doğrulamayı etkinleştir
Yüz tanıma kilidini (varsa) onaylayın
Gizlendiğinde uygulamayı engelle
- Ekran güvenliği
Arama
Canlı arama
Glob kalıbı kullanarak ara
@@ -163,11 +162,10 @@
Gönderim başarısız oldu
Güvenlik ipuçları
Sürüm
- Düzenlerken kasa kilidi açık
Gelişmiş Ayarlar
- Arka planda kilit açma
WebDAV bağlantıları
+ pCloud bağlantıları
Yerel depolama konumları
Giriş
Oturumunu kapat
@@ -202,7 +200,7 @@
Değiştir
\'%1$s\' adlı bir dosya zaten var. Değiştirmek istiyor musunuz?
Tüm dosyalar zaten mevcut. Onları değiştirmek ister misin?
- %d dosya zaten var. Onları değiştirmek ister misin?
+ %1$d dosya zaten var. Onları değiştirmek ister misin?
Dosya değiştirilsin mi?
Dosyalar değiştirilsin mi?
Dosyalar paylaşılamıyor
diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml
index c7f0b4d3..1d8d0ab2 100644
--- a/presentation/src/main/res/values/strings.xml
+++ b/presentation/src/main/res/values/strings.xml
@@ -11,6 +11,7 @@
An error occurred
Authentication failed
+ Authentication failed, please login using %1$s
No network connection
Wrong password
A file or folder already exists.
@@ -31,6 +32,7 @@
Failed to decrypt WebDAV password, please re add in settings
Play Services not installed
Biometric authentication aborted
+ Local file isn\'t present anymore after switching back to Cryptomator. Possible changes cannot be propagated back to the cloud.
@@ -39,6 +41,7 @@
Dropbox
Google Drive
OneDrive
+ pCloud
WebDAV
Local storage
@@ -68,7 +71,7 @@
Add to Cryptomator
Create new vault
Add existing vault
- @string/screen_file_browser_node_action_rename
+ @string/screen_file_browser_node_action_rename
Remove
Click here to create a new vault
Password successfully changed
@@ -76,9 +79,9 @@
Vault
- @string/screen_vault_list_action_add_existing_vault
+ @string/screen_vault_list_action_add_existing_vault
Select masterkey file
- @string/screen_vault_list_action_create_new_vault
+ @string/screen_vault_list_action_create_new_vault
Place here
Vault name: %1$s
@@ -113,7 +116,7 @@
Share
Rename
Edit
- @string/screen_file_browser_move_button_text
+ @string/screen_file_browser_move_button_text
Export
Delete
Open with…
@@ -137,21 +140,21 @@
files
Filenames have to be unique, please rename the duplicates.
- @string/screen_share_files_content_files
+ @string/screen_share_files_content_files
Save location
Save
Encryption completed
- @string/dialog_file_name_placeholder
+ @string/dialog_file_name_placeholder
Cloud service
- @string/screen_vault_list_action_add_existing_vault
- @string/screen_vault_list_action_create_new_vault
+ @string/screen_vault_list_action_add_existing_vault
+ @string/screen_vault_list_action_create_new_vault
Choose a location
- @string/screen_file_browser_node_action_edit_text
- @string/screen_vault_list_vault_action_delete
+ @string/screen_file_browser_node_action_edit_text
+ @string/screen_vault_list_vault_action_delete
Click here to add locations
Server doesn\'t seem to be WebDAV compatible
Custom locations
@@ -159,7 +162,7 @@
No additional locations available.
- @string/cloud_names_webdav
+ @string/cloud_names_webdav
URL
Username
Password
@@ -170,18 +173,18 @@
Password can\'t be empty.
- @string/screen_vault_list_action_create_new_vault
+ @string/screen_vault_list_action_create_new_vault
Vault name can\'t be empty.
Vault name
Create
Set password
- @string/screen_webdav_settings_msg_password_must_not_be_empty
+ @string/screen_webdav_settings_msg_password_must_not_be_empty
Password doesn\'t match retyped password.
Done
IMPORTANT: If you forget your password, there is no way to recover your data.
- @string/screen_webdav_settings_password_label
+ @string/screen_webdav_settings_password_label
Retype password
Very weak
@@ -191,7 +194,7 @@
Very Strong
- @string/snack_bar_action_title_settings
+ @string/snack_bar_action_title_settings
General
Cloud services
@@ -221,7 +224,7 @@
Save auto upload files to…
- @string/screen_webdav_settings_done_button_text
+ @string/screen_webdav_settings_done_button_text
Cryptomator website
Follow us on Twitter
Like us on Facebook
@@ -246,19 +249,20 @@
Advanced Settings
Accelerate unlock
- Download key material in the background while prompted to enter the password or biometric auth
+ Download vault config in the background while prompted to enter the password or biometric auth
Keep unlocked
Keep vaults unlocked while editing files
- @string/screen_settings_cloud_settings_label
+ @string/screen_settings_cloud_settings_label
WebDAV connections
+ pCloud connections
Local storage locations
Log in to
Sign out from
- @string/screen_settings_licenses_label
+ @string/screen_settings_licenses_label
%1$s could not be authenticated.
@@ -274,15 +278,15 @@
Cancel
- @string/screen_file_browser_action_create_folder
- @string/screen_enter_vault_name_button_text
+ @string/screen_file_browser_action_create_folder
+ @string/screen_enter_vault_name_button_text
- @string/screen_webdav_settings_password_label
+ @string/screen_webdav_settings_password_label
Unlock
Old Password
New Password
- @string/screen_set_password_retype_password_label
+ @string/screen_set_password_retype_password_label
Change password
Old password can\'t be empty.
New password can\'t be empty.
@@ -303,7 +307,7 @@
Replace
A file named \'%1$s\' already exists. Do you want to replace it?
All files already exist. Do you want to replace them?
- %d files already exist. Do you want to replace them?
+ %1$d files already exist. Do you want to replace them?
Replace file?
Replace files?
@@ -313,36 +317,36 @@
Create vault
Can\'t open %1$s
- @string/screen_file_browser_node_action_export
+ @string/screen_file_browser_node_action_export
Please download an app that can open this file or do you like to save to your device?
Rename vault
- @string/screen_file_browser_node_action_rename
+ @string/screen_file_browser_node_action_rename
Rename folder
Rename file
- @string/screen_file_browser_node_action_rename
+ @string/screen_file_browser_node_action_rename
You have unsaved changes
Do you really want to quit without saving?
Discard
- @string/screen_share_files_save_button_text
+ @string/screen_share_files_save_button_text
- @string/screen_file_browser_action_create_new_text_file
- @string/screen_enter_vault_name_button_text
- @string/dialog_button_cancel
+ @string/screen_file_browser_action_create_new_text_file
+ @string/screen_enter_vault_name_button_text
+ @string/dialog_button_cancel
text.txt
- @string/screen_file_browser_node_action_delete
+ @string/screen_file_browser_node_action_delete
Are you sure you want to remove this vault?
This action will only remove the vault from this list and not delete it physically.
Uploading…
- @string/dialog_button_cancel
+ @string/dialog_button_cancel
File %1$d of %2$d
Exporting (%1$d/%2$d)
- @string/dialog_button_cancel
+ @string/dialog_button_cancel
Please wait…
Creating folder…
@@ -379,12 +383,12 @@
In this mode, sensitive data may be written to a log file on your device (e.g., filenames and paths). Passwords, cookies, etc. are explicitly excluded.\n\nRemember to disable debug mode as soon as possible.
Attention
Enable
- @string/dialog_button_cancel
+ @string/dialog_button_cancel
This setting is a security feature and prevents other apps from tricking users into doing things they do not wan\'t to do.\n\nBy disabling, you confirm that you are aware of the risks.
Attention
Disable
- @string/dialog_button_cancel
+ @string/dialog_button_cancel
App is obscured
Another app is displaying something on top of Cryptomator (e.g., a blue light filter or night mode app). For security reasons, Cryptomator is disabled.\n\nHow to enable Cryptomator
@@ -394,11 +398,11 @@
Are you sure you want to remove this cloud connection?
This action will remove the cloud connection and all vaults of this cloud.
- @string/screen_file_browser_node_action_delete
- @string/dialog_button_cancel
+ @string/screen_file_browser_node_action_delete
+ @string/dialog_button_cancel
- @string/screen_file_browser_node_action_delete
- @string/dialog_button_cancel
+ @string/screen_file_browser_node_action_delete
+ @string/dialog_button_cancel
Delete %1$d items?
Are you sure you want to delete these items?
Are you sure you want to delete this file?
@@ -406,18 +410,18 @@
Biometric authentication feature deactivated
Because the key has been invalidated, the biometric authentication feature has been deactivated. To re-enable, open the Cryptomator settings.
- @string/dialog_unable_to_share_positive_button
+ @string/dialog_unable_to_share_positive_button
Provide a valid license
We detected that you installed Cryptomator without using Google Play Store. Provide a valid license, which can be purchased on https://cryptomator.org/android/
The provided license isn\'t valid. Make sure you entered it correctly.
No license provided. Please enter a valid license.
- @string/dialog_unable_to_share_positive_button
+ @string/dialog_unable_to_share_positive_button
Exit
License confirmation
Thanks %1$s for providing your valid license.
- @string/dialog_unable_to_share_positive_button
+ @string/dialog_unable_to_share_positive_button
Update available
Update Cryptomator to the latest version. By pressing OK we will download the app in the background and will ask you to install it.
@@ -434,7 +438,7 @@
Unable to load contents of directory
Cloud folder \'%1$s\' doesn\'t have a directory file. It could be that the folder was created on another device and has not yet been fully synchronized to the cloud. Please check in your cloud if the following file exists:\n%2$s
- @string/dialog_sym_link_back_button
+ @string/dialog_sym_link_back_button
Beta release
This is a beta release which introduces the support of vault format 7. Please make sure that you don\'t use your production vault for testing or have a good backup strategy.
@@ -500,14 +504,14 @@
Selected folder for upload isn\'t available anymore. Go to settings and choose a new one
Vault locked during upload, please reopen vault to continue
- @string/dialog_button_cancel
+ @string/dialog_button_cancel
Open writable file
Vault stays unlocked until finished editing
Latest version installed
Cache
- @string/screen_settings_section_auto_photo_upload_toggle
+ @string/screen_settings_section_auto_photo_upload_toggle
Cache recently accessed files encrypted locally on the device for later reuse when reopened
Total cache size
Clear Cache
@@ -519,7 +523,7 @@
Update check interval
Check for updates
Last run %1$s
- @string/lock_timeout_never
+ @string/lock_timeout_never
Cache size per Cloud
@@ -549,6 +553,6 @@
Once a day
Once a week
Once a month
- @string/lock_timeout_never
+ @string/lock_timeout_never
diff --git a/presentation/src/main/res/xml/licenses.xml b/presentation/src/main/res/xml/licenses.xml
index ab3f1415..78bd3a9c 100644
--- a/presentation/src/main/res/xml/licenses.xml
+++ b/presentation/src/main/res/xml/licenses.xml
@@ -113,6 +113,13 @@
android:action="android.intent.action.VIEW"
android:data="https://github.com/rburgst/okhttp-digest/" />
+
+
+
diff --git a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt
index be9e4bee..8b228d10 100644
--- a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt
+++ b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt
@@ -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(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() {
+ 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>() {
+ override fun onSuccess(clouds: List) {
+ 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)
}
}
diff --git a/settings.gradle b/settings.gradle
index d0e47c23..66b721f7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,5 @@
-include ':generator', ':presentation', ':generator-api', ':domain', ':data', ':util', ':subsampling-image-view', ':msa-auth-for-android'
+include ':generator', ':presentation', ':generator-api', ':domain', ':data', ':util', ':subsampling-image-view', ':msa-auth-for-android', ':pcloud-sdk-java-root', ':pcloud-sdk-java', ':pcloud-sdk-android'
project(':subsampling-image-view').projectDir = file(new File(rootDir, 'subsampling-scale-image-view/library'))
+project(':pcloud-sdk-java-root').projectDir = file(new File(rootDir, 'pcloud-sdk-java'))
+project(':pcloud-sdk-java').projectDir = file(new File(rootDir, 'pcloud-sdk-java/java-core'))
+project(':pcloud-sdk-android').projectDir = file(new File(rootDir, 'pcloud-sdk-java/android'))
diff --git a/subsampling-scale-image-view b/subsampling-scale-image-view
index 960962bd..acf19284 160000
--- a/subsampling-scale-image-view
+++ b/subsampling-scale-image-view
@@ -1 +1 @@
-Subproject commit 960962bdfd07cba2a68d5c109358ec92cf765a44
+Subproject commit acf192842fefda106395b88b8c898873cd95c550
diff --git a/util/src/main/AndroidManifest.xml b/util/src/main/AndroidManifest.xml
index a4f8e57e..ea1dd7e9 100644
--- a/util/src/main/AndroidManifest.xml
+++ b/util/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/util/src/main/java/org/cryptomator/util/file/LruFileCacheUtil.kt b/util/src/main/java/org/cryptomator/util/file/LruFileCacheUtil.kt
index f2ea8bf7..c4082926 100644
--- a/util/src/main/java/org/cryptomator/util/file/LruFileCacheUtil.kt
+++ b/util/src/main/java/org/cryptomator/util/file/LruFileCacheUtil.kt
@@ -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()