Merge branch 'release/1.5.14'
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github: [cryptomator]
|
||||||
|
custom: https://cryptomator.org/sponsors/
|
3
.gitmodules
vendored
@ -4,3 +4,6 @@
|
|||||||
[submodule "subsampling-scale-image-view"]
|
[submodule "subsampling-scale-image-view"]
|
||||||
path = subsampling-scale-image-view
|
path = subsampling-scale-image-view
|
||||||
url = https://github.com/SailReal/subsampling-scale-image-view.git
|
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
|
||||||
|
1
.idea/vcs.xml
generated
@ -3,6 +3,7 @@
|
|||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/msa-auth-for-android" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/msa-auth-for-android" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/pcloud-sdk-java" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/subsampling-scale-image-view" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/subsampling-scale-image-view" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
36
Gemfile.lock
@ -8,21 +8,21 @@ GEM
|
|||||||
rubyzip (~> 2.0)
|
rubyzip (~> 2.0)
|
||||||
artifactory (3.0.15)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.1)
|
||||||
aws-partitions (1.428.0)
|
aws-partitions (1.437.0)
|
||||||
aws-sdk-core (3.112.0)
|
aws-sdk-core (3.113.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.42.0)
|
aws-sdk-kms (1.43.0)
|
||||||
aws-sdk-core (~> 3, >= 3.112.0)
|
aws-sdk-core (~> 3, >= 3.112.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.88.1)
|
aws-sdk-s3 (1.93.0)
|
||||||
aws-sdk-core (~> 3, >= 3.112.0)
|
aws-sdk-core (~> 3, >= 3.112.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.2.2)
|
aws-sigv4 (1.2.3)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
bcrypt_pbkdf (1.0.1)
|
bcrypt_pbkdf (1.0.1)
|
||||||
@ -51,8 +51,8 @@ GEM
|
|||||||
faraday-net_http (1.0.1)
|
faraday-net_http (1.0.1)
|
||||||
faraday_middleware (1.0.0)
|
faraday_middleware (1.0.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.2)
|
fastimage (2.2.3)
|
||||||
fastlane (2.175.0)
|
fastlane (2.179.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
@ -104,7 +104,7 @@ GEM
|
|||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.0)
|
||||||
signet (~> 0.12)
|
signet (~> 0.12)
|
||||||
google-apis-core (0.2.1)
|
google-apis-core (0.3.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.14)
|
googleauth (~> 0.14)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.0)
|
||||||
@ -114,17 +114,17 @@ GEM
|
|||||||
rexml
|
rexml
|
||||||
signet (~> 0.14)
|
signet (~> 0.14)
|
||||||
webrick
|
webrick
|
||||||
google-apis-iamcredentials_v1 (0.1.0)
|
google-apis-iamcredentials_v1 (0.2.0)
|
||||||
google-apis-core (~> 0.1)
|
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-apis-core (~> 0.1)
|
||||||
google-cloud-core (1.5.0)
|
google-cloud-core (1.6.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 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)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.1)
|
google-cloud-errors (1.1.0)
|
||||||
google-cloud-storage (1.30.0)
|
google-cloud-storage (1.31.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
@ -132,7 +132,7 @@ GEM
|
|||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.2)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.15.1)
|
googleauth (0.16.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
@ -151,7 +151,7 @@ GEM
|
|||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2020.1104)
|
mime-types-data (3.2020.1104)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.3)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
@ -173,7 +173,7 @@ GEM
|
|||||||
ruby2_keywords (0.0.4)
|
ruby2_keywords (0.0.4)
|
||||||
rubyzip (2.3.0)
|
rubyzip (2.3.0)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.14.1)
|
signet (0.15.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
|
@ -3,19 +3,19 @@ apply from: 'buildsystem/dependencies.gradle'
|
|||||||
apply plugin: "com.vanniktech.android.junit.jacoco"
|
apply plugin: "com.vanniktech.android.junit.jacoco"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.4.30'
|
ext.kotlin_version = '1.4.32'
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
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 'org.greenrobot:greendao-gradle-plugin:3.3.0'
|
||||||
classpath 'com.fernandocejas.frodo:frodo-plugin:0.8.3'
|
classpath 'com.fernandocejas.frodo:frodo-plugin:0.8.3'
|
||||||
classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.16.0'
|
classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.16.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
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 {
|
ext {
|
||||||
androidApplicationId = 'org.cryptomator'
|
androidApplicationId = 'org.cryptomator'
|
||||||
androidVersionCode = getVersionCode()
|
androidVersionCode = getVersionCode()
|
||||||
androidVersionName = '1.5.13'
|
androidVersionName = '1.5.14'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -16,7 +16,7 @@ ext {
|
|||||||
javaxAnnotationVersion = '1.0'
|
javaxAnnotationVersion = '1.0'
|
||||||
|
|
||||||
// support lib
|
// support lib
|
||||||
androidSupportAnnotationsVersion = '1.1.0'
|
androidSupportAnnotationsVersion = '1.2.0'
|
||||||
androidSupportAppcompatVersion = '1.2.0'
|
androidSupportAppcompatVersion = '1.2.0'
|
||||||
androidSupportDesignVersion = '1.3.0'
|
androidSupportDesignVersion = '1.3.0'
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ ext {
|
|||||||
rxAndroidVersion = '2.1.1'
|
rxAndroidVersion = '2.1.1'
|
||||||
rxBindingVersion = '2.2.0'
|
rxBindingVersion = '2.2.0'
|
||||||
|
|
||||||
daggerVersion = '2.32'
|
daggerVersion = '2.34'
|
||||||
|
|
||||||
gsonVersion = '2.8.6'
|
gsonVersion = '2.8.6'
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ ext {
|
|||||||
|
|
||||||
timberVersion = '4.7.1'
|
timberVersion = '4.7.1'
|
||||||
|
|
||||||
zxcvbnVersion = '1.3.6'
|
zxcvbnVersion = '1.4.0'
|
||||||
|
|
||||||
scaleImageViewVersion = '3.10.0'
|
scaleImageViewVersion = '3.10.0'
|
||||||
|
|
||||||
@ -51,13 +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
|
// 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'
|
cryptolibVersion = '1.3.0'
|
||||||
|
|
||||||
dropboxVersion = '3.1.5'
|
dropboxVersion = '4.0.0'
|
||||||
|
|
||||||
googleApiServicesVersion = 'v3-rev197-1.25.0'
|
googleApiServicesVersion = 'v3-rev197-1.25.0'
|
||||||
googlePlayServicesVersion = '19.0.0'
|
googlePlayServicesVersion = '19.0.0'
|
||||||
googleClientVersion = '1.31.2'
|
googleClientVersion = '1.31.4'
|
||||||
|
|
||||||
msgraphVersion = '2.8.0'
|
msgraphVersion = '2.10.0'
|
||||||
msaAuthVersion = '0.10.0'
|
msaAuthVersion = '0.10.0'
|
||||||
|
|
||||||
commonsCodecVersion = '1.15'
|
commonsCodecVersion = '1.15'
|
||||||
@ -69,8 +69,8 @@ ext {
|
|||||||
jUnitVersion = '5.7.1'
|
jUnitVersion = '5.7.1'
|
||||||
jUnit4Version = '4.13.1'
|
jUnit4Version = '4.13.1'
|
||||||
assertJVersion = '1.7.1'
|
assertJVersion = '1.7.1'
|
||||||
mockitoVersion = '3.7.7'
|
mockitoVersion = '3.9.0'
|
||||||
mockitoInlineVersion = '3.7.7'
|
mockitoInlineVersion = '3.9.0'
|
||||||
hamcrestVersion = '1.3'
|
hamcrestVersion = '1.3'
|
||||||
dexmakerVersion = '1.0'
|
dexmakerVersion = '1.0'
|
||||||
espressoVersion = '3.3.0'
|
espressoVersion = '3.3.0'
|
||||||
@ -81,11 +81,11 @@ ext {
|
|||||||
uiautomatorVersion = '2.2.0'
|
uiautomatorVersion = '2.2.0'
|
||||||
|
|
||||||
androidxCoreVersion = '1.3.2'
|
androidxCoreVersion = '1.3.2'
|
||||||
androidxFragmentVersion = '1.3.0'
|
androidxFragmentVersion = '1.3.2'
|
||||||
androidxViewpagerVersion = '1.0.0'
|
androidxViewpagerVersion = '1.0.0'
|
||||||
androidxSwiperefreshVersion = '1.1.0'
|
androidxSwiperefreshVersion = '1.1.0'
|
||||||
androidxPreferenceVersion = '1.0.0' // 1.1.0 and 1.1.2 does have a bug with the text size
|
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'
|
androidxDocumentfileVersion = '1.0.1'
|
||||||
androidxBiometricVersion = '1.1.0'
|
androidxBiometricVersion = '1.1.0'
|
||||||
androidxTestCoreVersion = '1.3.0'
|
androidxTestCoreVersion = '1.3.0'
|
||||||
|
@ -74,7 +74,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
greendao {
|
greendao {
|
||||||
schemaVersion 4
|
schemaVersion 5
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
@ -88,6 +88,7 @@ dependencies {
|
|||||||
implementation project(':domain')
|
implementation project(':domain')
|
||||||
implementation project(':util')
|
implementation project(':util')
|
||||||
implementation project(':msa-auth-for-android')
|
implementation project(':msa-auth-for-android')
|
||||||
|
implementation project(':pcloud-sdk-java')
|
||||||
|
|
||||||
// cryptomator
|
// cryptomator
|
||||||
implementation dependencies.cryptolib
|
implementation dependencies.cryptolib
|
||||||
|
@ -4,5 +4,5 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
|
||||||
<application android:allowBackup="true" />
|
<application android:allowBackup="false" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public class PCloudApiError {
|
||||||
|
|
||||||
|
public static final HashSet<Integer> ignoreExistsSet = new HashSet<>( //
|
||||||
|
Arrays.asList( //
|
||||||
|
PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), //
|
||||||
|
PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), //
|
||||||
|
PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), //
|
||||||
|
PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(), //
|
||||||
|
PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() //
|
||||||
|
));
|
||||||
|
public static final HashSet<Integer> ignoreMoveSet = new HashSet<>( //
|
||||||
|
Arrays.asList( //
|
||||||
|
PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(), //
|
||||||
|
PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), //
|
||||||
|
PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), //
|
||||||
|
PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), //
|
||||||
|
PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() //
|
||||||
|
) //
|
||||||
|
);
|
||||||
|
|
||||||
|
public static boolean isCloudNodeAlreadyExistsException(int errorCode) {
|
||||||
|
return errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFatalBackendException(int errorCode) {
|
||||||
|
return errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.UPLOAD_NOT_FOUND.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.TRANSFER_NOT_FOUND.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isForbiddenException(int errorCode) {
|
||||||
|
return errorCode == PCloudApiErrorCodes.ACCESS_DENIED.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNetworkConnectionException(int errorCode) {
|
||||||
|
return errorCode == PCloudApiErrorCodes.CONNECTION_BROKE.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNoSuchCloudFileException(int errorCode) {
|
||||||
|
return errorCode == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isWrongCredentialsException(int errorCode) {
|
||||||
|
return errorCode == PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUnauthorizedException(int errorCode) {
|
||||||
|
return errorCode == PCloudApiErrorCodes.LOGIN_FAILED.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.LOGIN_REQUIRED.getValue() //
|
||||||
|
|| errorCode == PCloudApiErrorCodes.TOO_MANY_LOGIN_TRIES_FROM_IP.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PCloudApiErrorCodes {
|
||||||
|
LOGIN_REQUIRED(1000), //
|
||||||
|
NO_FULL_PATH_OR_NAME_FOLDER_ID_PROVIDED(1001), //
|
||||||
|
NO_FULL_PATH_OR_FOLDER_ID_PROVIDED(1002), //
|
||||||
|
NO_FILE_ID_OR_PATH_PROVIDED(1004), //
|
||||||
|
INVALID_DATE_TIME_FORMAT(1013), //
|
||||||
|
NO_DESTINATION_PROVIDED(1016), //
|
||||||
|
INVALID_FOLDER_ID(1017), //
|
||||||
|
INVALID_DESTINATION(1037), //
|
||||||
|
PROVIDE_URL(1040), //
|
||||||
|
UPLOAD_NOT_FOUND(1900), //
|
||||||
|
TRANSFER_NOT_FOUND(1902), //
|
||||||
|
LOGIN_FAILED(2000), //
|
||||||
|
INVALID_FILE_OR_FOLDER_NAME(2001), //
|
||||||
|
COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST(2002), //
|
||||||
|
ACCESS_DENIED(2003), //
|
||||||
|
FILE_OR_FOLDER_ALREADY_EXISTS(2004), //
|
||||||
|
DIRECTORY_DOES_NOT_EXIST(2005), //
|
||||||
|
FOLDER_NOT_EMPTY(2006), //
|
||||||
|
CANNOT_DELETE_ROOT_FOLDER(2007), //
|
||||||
|
USER_OVER_QUOTA(2008), //
|
||||||
|
FILE_NOT_FOUND(2009), //
|
||||||
|
INVALID_PATH(2010), //
|
||||||
|
SHARED_FOLDER_IN_SHARED_FOLDER(2023), //
|
||||||
|
ACTIVE_SHARES_OR_SHAREREQUESTS_PRESENT(2028), //
|
||||||
|
CONNECTION_BROKE(2041), //
|
||||||
|
CANNOT_RENAME_ROOT_FOLDER(2042), //
|
||||||
|
CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), //
|
||||||
|
FILE_OR_FOLDER_NOT_FOUND(2055), //
|
||||||
|
NO_FILE_UPLOAD_DETECTED(2088), //
|
||||||
|
INVALID_ACCESS_TOKEN(2094), //
|
||||||
|
ACCESS_TOKEN_REVOKED(2095), //
|
||||||
|
TRANSFER_OVER_QUOTA(2097), //
|
||||||
|
TARGET_FOLDER_DOES_NOT_EXIST(2208), //
|
||||||
|
TOO_MANY_LOGIN_TRIES_FROM_IP(4000), //
|
||||||
|
INTERNAL_ERROR(5000), //
|
||||||
|
INTERNAL_UPLOAD_ERROR(5001);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
PCloudApiErrorCodes(final int newValue) {
|
||||||
|
value = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.pcloud.sdk.ApiClient;
|
||||||
|
import com.pcloud.sdk.Authenticators;
|
||||||
|
import com.pcloud.sdk.PCloudSdk;
|
||||||
|
|
||||||
|
import org.cryptomator.data.cloud.okhttplogging.HttpLoggingInterceptor;
|
||||||
|
import org.cryptomator.util.crypto.CredentialCryptor;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static org.cryptomator.data.util.NetworkTimeout.CONNECTION;
|
||||||
|
import static org.cryptomator.data.util.NetworkTimeout.READ;
|
||||||
|
import static org.cryptomator.data.util.NetworkTimeout.WRITE;
|
||||||
|
|
||||||
|
class PCloudClientFactory {
|
||||||
|
|
||||||
|
private ApiClient apiClient;
|
||||||
|
|
||||||
|
private static Interceptor httpLoggingInterceptor(Context context) {
|
||||||
|
return new HttpLoggingInterceptor(message -> Timber.tag("OkHttp").d(message), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiClient getClient(String accessToken, String url, Context context) {
|
||||||
|
if (apiClient == null) {
|
||||||
|
apiClient = createApiClient(accessToken, url, context);
|
||||||
|
}
|
||||||
|
return apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiClient createApiClient(String accessToken, String url, Context context) {
|
||||||
|
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient() //
|
||||||
|
.newBuilder() //
|
||||||
|
.connectTimeout(CONNECTION.getTimeout(), CONNECTION.getUnit()) //
|
||||||
|
.readTimeout(READ.getTimeout(), READ.getUnit()) //
|
||||||
|
.writeTimeout(WRITE.getTimeout(), WRITE.getUnit()) //
|
||||||
|
.addInterceptor(httpLoggingInterceptor(context)); //;
|
||||||
|
|
||||||
|
OkHttpClient okHttpClient = okHttpClientBuilder.build();
|
||||||
|
|
||||||
|
return PCloudSdk.newClientBuilder().authenticator(Authenticators.newOAuthAuthenticator(decrypt(accessToken, context))).withClient(okHttpClient).apiHost(url).create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decrypt(String password, Context context) {
|
||||||
|
return CredentialCryptor //
|
||||||
|
.getInstance(context) //
|
||||||
|
.decrypt(password);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.pcloud.sdk.ApiError;
|
||||||
|
|
||||||
|
import org.cryptomator.data.cloud.InterceptingCloudContentRepository;
|
||||||
|
import org.cryptomator.domain.PCloud;
|
||||||
|
import org.cryptomator.domain.exception.BackendException;
|
||||||
|
import org.cryptomator.domain.exception.FatalBackendException;
|
||||||
|
import org.cryptomator.domain.exception.NetworkConnectionException;
|
||||||
|
import org.cryptomator.domain.exception.authentication.WrongCredentialsException;
|
||||||
|
import org.cryptomator.domain.repository.CloudContentRepository;
|
||||||
|
import org.cryptomator.domain.usecases.ProgressAware;
|
||||||
|
import org.cryptomator.domain.usecases.cloud.DataSource;
|
||||||
|
import org.cryptomator.domain.usecases.cloud.DownloadState;
|
||||||
|
import org.cryptomator.domain.usecases.cloud.UploadState;
|
||||||
|
import org.cryptomator.util.Optional;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.cryptomator.util.ExceptionUtil.contains;
|
||||||
|
|
||||||
|
class PCloudContentRepository extends InterceptingCloudContentRepository<PCloud, PCloudNode, PCloudFolder, PCloudFile> {
|
||||||
|
|
||||||
|
private final PCloud cloud;
|
||||||
|
|
||||||
|
public PCloudContentRepository(PCloud cloud, Context context) {
|
||||||
|
super(new Intercepted(cloud, context));
|
||||||
|
this.cloud = cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void throwWrappedIfRequired(Exception e) throws BackendException {
|
||||||
|
throwConnectionErrorIfRequired(e);
|
||||||
|
throwWrongCredentialsExceptionIfRequired(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwConnectionErrorIfRequired(Exception e) throws NetworkConnectionException {
|
||||||
|
if (contains(e, IOException.class)) {
|
||||||
|
throw new NetworkConnectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwWrongCredentialsExceptionIfRequired(Exception e) {
|
||||||
|
if (e instanceof ApiError) {
|
||||||
|
int errorCode = ((ApiError) e).errorCode();
|
||||||
|
if (errorCode == PCloudApiError.PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() //
|
||||||
|
|| errorCode == PCloudApiError.PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue()) {
|
||||||
|
throw new WrongCredentialsException(cloud);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Intercepted implements CloudContentRepository<PCloud, PCloudNode, PCloudFolder, PCloudFile> {
|
||||||
|
|
||||||
|
private final PCloudImpl cloud;
|
||||||
|
|
||||||
|
public Intercepted(PCloud cloud, Context context) {
|
||||||
|
this.cloud = new PCloudImpl(context, cloud);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudFolder root(PCloud cloud) {
|
||||||
|
return this.cloud.root();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFolder resolve(PCloud cloud, String path) throws BackendException {
|
||||||
|
try {
|
||||||
|
return this.cloud.resolve(path);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFile file(PCloudFolder parent, String name) throws BackendException {
|
||||||
|
try {
|
||||||
|
return cloud.file(parent, name);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFile file(PCloudFolder parent, String name, Optional<Long> size) throws BackendException {
|
||||||
|
try {
|
||||||
|
return cloud.file(parent, name, size);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFolder folder(PCloudFolder parent, String name) throws BackendException {
|
||||||
|
try {
|
||||||
|
return cloud.folder(parent, name);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(PCloudNode node) throws BackendException {
|
||||||
|
try {
|
||||||
|
return cloud.exists(node);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PCloudNode> list(PCloudFolder folder) throws BackendException {
|
||||||
|
try {
|
||||||
|
return cloud.list(folder);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFolder create(PCloudFolder folder) throws BackendException {
|
||||||
|
try {
|
||||||
|
return cloud.create(folder);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFolder move(PCloudFolder source, PCloudFolder target) throws BackendException {
|
||||||
|
try {
|
||||||
|
return (PCloudFolder) cloud.move(source, target);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFile move(PCloudFile source, PCloudFile target) throws BackendException {
|
||||||
|
try {
|
||||||
|
return (PCloudFile) cloud.move(source, target);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFile write(PCloudFile uploadFile, DataSource data, ProgressAware<UploadState> progressAware, boolean replace, long size) throws BackendException {
|
||||||
|
try {
|
||||||
|
return cloud.write(uploadFile, data, progressAware, replace, size);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(PCloudFile file, Optional<File> encryptedTmpFile, OutputStream data, ProgressAware<DownloadState> progressAware) throws BackendException {
|
||||||
|
try {
|
||||||
|
cloud.read(file, encryptedTmpFile, data, progressAware);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(PCloudNode node) throws BackendException {
|
||||||
|
try {
|
||||||
|
cloud.delete(node);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String checkAuthenticationAndRetrieveCurrentAccount(PCloud cloud) throws BackendException {
|
||||||
|
try {
|
||||||
|
return this.cloud.currentAccount();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalBackendException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logout(PCloud cloud) throws BackendException {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
||||||
|
import org.cryptomator.domain.Cloud;
|
||||||
|
import org.cryptomator.domain.PCloud;
|
||||||
|
import org.cryptomator.domain.repository.CloudContentRepository;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import static org.cryptomator.domain.CloudType.PCLOUD;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class PCloudContentRepositoryFactory implements CloudContentRepositoryFactory {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PCloudContentRepositoryFactory(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Cloud cloud) {
|
||||||
|
return cloud.type() == PCLOUD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) {
|
||||||
|
return new PCloudContentRepository((PCloud) cloud, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import org.cryptomator.domain.Cloud;
|
||||||
|
import org.cryptomator.domain.CloudFile;
|
||||||
|
import org.cryptomator.util.Optional;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
class PCloudFile implements CloudFile, PCloudNode {
|
||||||
|
|
||||||
|
private final PCloudFolder parent;
|
||||||
|
private final String name;
|
||||||
|
private final String path;
|
||||||
|
private final Optional<Long> size;
|
||||||
|
private final Optional<Date> modified;
|
||||||
|
|
||||||
|
public PCloudFile(PCloudFolder parent, String name, String path, Optional<Long> size, Optional<Date> modified) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.name = name;
|
||||||
|
this.path = path;
|
||||||
|
this.size = size;
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cloud getCloud() {
|
||||||
|
return parent.getCloud();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFolder getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Long> getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Date> getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import org.cryptomator.domain.Cloud;
|
||||||
|
import org.cryptomator.domain.CloudFolder;
|
||||||
|
|
||||||
|
class PCloudFolder implements CloudFolder, PCloudNode {
|
||||||
|
|
||||||
|
private final PCloudFolder parent;
|
||||||
|
private final String name;
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
public PCloudFolder(PCloudFolder parent, String name, String path) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.name = name;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cloud getCloud() {
|
||||||
|
return parent.getCloud();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFolder getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFolder withCloud(Cloud cloud) {
|
||||||
|
return new PCloudFolder(parent.withCloud(cloud), name, path);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,370 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.pcloud.sdk.ApiClient;
|
||||||
|
import com.pcloud.sdk.ApiError;
|
||||||
|
import com.pcloud.sdk.DataSink;
|
||||||
|
import com.pcloud.sdk.DownloadOptions;
|
||||||
|
import com.pcloud.sdk.FileLink;
|
||||||
|
import com.pcloud.sdk.ProgressListener;
|
||||||
|
import com.pcloud.sdk.RemoteEntry;
|
||||||
|
import com.pcloud.sdk.RemoteFile;
|
||||||
|
import com.pcloud.sdk.RemoteFolder;
|
||||||
|
import com.pcloud.sdk.UploadOptions;
|
||||||
|
import com.pcloud.sdk.UserInfo;
|
||||||
|
import com.tomclaw.cache.DiskLruCache;
|
||||||
|
|
||||||
|
import org.cryptomator.data.util.CopyStream;
|
||||||
|
import org.cryptomator.domain.PCloud;
|
||||||
|
import org.cryptomator.domain.exception.BackendException;
|
||||||
|
import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException;
|
||||||
|
import org.cryptomator.domain.exception.FatalBackendException;
|
||||||
|
import org.cryptomator.domain.exception.ForbiddenException;
|
||||||
|
import org.cryptomator.domain.exception.NetworkConnectionException;
|
||||||
|
import org.cryptomator.domain.exception.NoSuchCloudFileException;
|
||||||
|
import org.cryptomator.domain.exception.UnauthorizedException;
|
||||||
|
import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedException;
|
||||||
|
import org.cryptomator.domain.exception.authentication.WrongCredentialsException;
|
||||||
|
import org.cryptomator.domain.usecases.ProgressAware;
|
||||||
|
import org.cryptomator.domain.usecases.cloud.DataSource;
|
||||||
|
import org.cryptomator.domain.usecases.cloud.DownloadState;
|
||||||
|
import org.cryptomator.domain.usecases.cloud.Progress;
|
||||||
|
import org.cryptomator.domain.usecases.cloud.UploadState;
|
||||||
|
import org.cryptomator.util.Optional;
|
||||||
|
import org.cryptomator.util.SharedPreferencesHandler;
|
||||||
|
import org.cryptomator.util.file.LruFileCacheUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import okio.BufferedSink;
|
||||||
|
import okio.BufferedSource;
|
||||||
|
import okio.Okio;
|
||||||
|
import okio.Source;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static org.cryptomator.domain.usecases.cloud.Progress.progress;
|
||||||
|
import static org.cryptomator.util.file.LruFileCacheUtil.Cache.PCLOUD;
|
||||||
|
import static org.cryptomator.util.file.LruFileCacheUtil.retrieveFromLruCache;
|
||||||
|
import static org.cryptomator.util.file.LruFileCacheUtil.storeToLruCache;
|
||||||
|
|
||||||
|
class PCloudImpl {
|
||||||
|
|
||||||
|
private final PCloudClientFactory clientFactory = new PCloudClientFactory();
|
||||||
|
private final PCloud cloud;
|
||||||
|
private final RootPCloudFolder root;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
private final SharedPreferencesHandler sharedPreferencesHandler;
|
||||||
|
private DiskLruCache diskLruCache;
|
||||||
|
|
||||||
|
PCloudImpl(Context context, PCloud cloud) {
|
||||||
|
if (cloud.accessToken() == null) {
|
||||||
|
throw new NoAuthenticationProvidedException(cloud);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.context = context;
|
||||||
|
this.cloud = cloud;
|
||||||
|
this.root = new RootPCloudFolder(cloud);
|
||||||
|
this.sharedPreferencesHandler = new SharedPreferencesHandler(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiClient client() {
|
||||||
|
return clientFactory.getClient(cloud.accessToken(), cloud.url(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudFolder root() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudFolder resolve(String path) throws IOException, BackendException {
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
String[] names = path.split("/");
|
||||||
|
PCloudFolder folder = root;
|
||||||
|
for (String name : names) {
|
||||||
|
folder = folder(folder, name);
|
||||||
|
}
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudFile file(PCloudFolder parent, String name) throws BackendException, IOException {
|
||||||
|
return file(parent, name, Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudFile file(PCloudFolder parent, String name, Optional<Long> size) throws BackendException, IOException {
|
||||||
|
return PCloudNodeFactory.file(parent, name, size, parent.getPath() + "/" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudFolder folder(PCloudFolder parent, String name) throws IOException, BackendException {
|
||||||
|
return PCloudNodeFactory.folder(parent, name, parent.getPath() + "/" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exists(PCloudNode node) throws IOException, BackendException {
|
||||||
|
try {
|
||||||
|
if (node instanceof PCloudFolder) {
|
||||||
|
client().loadFolder(node.getPath()).execute();
|
||||||
|
} else {
|
||||||
|
client().loadFile(node.getPath()).execute();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
handleApiError(ex, PCloudApiError.ignoreExistsSet, node.getName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PCloudNode> list(PCloudFolder folder) throws IOException, BackendException {
|
||||||
|
List<PCloudNode> result = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
RemoteFolder listFolderResult = client().listFolder(folder.getPath()).execute();
|
||||||
|
List<RemoteEntry> entryMetadata = listFolderResult.children();
|
||||||
|
for (RemoteEntry metadata : entryMetadata) {
|
||||||
|
result.add(PCloudNodeFactory.from(folder, metadata));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
handleApiError(ex, folder.getName());
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudFolder create(PCloudFolder folder) throws IOException, BackendException {
|
||||||
|
if (!exists(folder.getParent())) {
|
||||||
|
folder = new PCloudFolder( //
|
||||||
|
create(folder.getParent()), //
|
||||||
|
folder.getName(), folder.getPath() //
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
RemoteFolder createdFolder = client() //
|
||||||
|
.createFolder(folder.getPath()) //
|
||||||
|
.execute();
|
||||||
|
return PCloudNodeFactory.folder(folder.getParent(), createdFolder);
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
handleApiError(ex, folder.getName());
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudNode move(PCloudNode source, PCloudNode target) throws IOException, BackendException {
|
||||||
|
if (exists(target)) {
|
||||||
|
throw new CloudNodeAlreadyExistsException(target.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (source instanceof PCloudFolder) {
|
||||||
|
return PCloudNodeFactory.from(target.getParent(), client().moveFolder(source.getPath(), target.getPath()).execute());
|
||||||
|
} else {
|
||||||
|
return PCloudNodeFactory.from(target.getParent(), client().moveFile(source.getPath(), target.getPath()).execute());
|
||||||
|
}
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
if (PCloudApiError.isCloudNodeAlreadyExistsException(ex.errorCode())) {
|
||||||
|
throw new CloudNodeAlreadyExistsException(target.getName());
|
||||||
|
} else if (PCloudApiError.isNoSuchCloudFileException(ex.errorCode())) {
|
||||||
|
throw new NoSuchCloudFileException(source.getName());
|
||||||
|
} else {
|
||||||
|
handleApiError(ex, PCloudApiError.ignoreMoveSet, null);
|
||||||
|
}
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware<UploadState> progressAware, boolean replace, long size) throws IOException, BackendException {
|
||||||
|
if (!replace && exists(file)) {
|
||||||
|
throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false");
|
||||||
|
}
|
||||||
|
|
||||||
|
progressAware.onProgress(Progress.started(UploadState.upload(file)));
|
||||||
|
UploadOptions uploadOptions = UploadOptions.DEFAULT;
|
||||||
|
if (replace) {
|
||||||
|
uploadOptions = UploadOptions.OVERRIDE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteFile uploadedFile = uploadFile(file, data, progressAware, uploadOptions, size);
|
||||||
|
|
||||||
|
progressAware.onProgress(Progress.completed(UploadState.upload(file)));
|
||||||
|
|
||||||
|
return PCloudNodeFactory.file(file.getParent(), uploadedFile);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware<UploadState> progressAware, UploadOptions uploadOptions, final long size) //
|
||||||
|
throws IOException, BackendException {
|
||||||
|
ProgressListener listener = (done, total) -> progressAware.onProgress( //
|
||||||
|
progress(UploadState.upload(file)) //
|
||||||
|
.between(0) //
|
||||||
|
.and(size) //
|
||||||
|
.withValue(done));
|
||||||
|
|
||||||
|
com.pcloud.sdk.DataSource pCloudDataSource = new com.pcloud.sdk.DataSource() {
|
||||||
|
@Override
|
||||||
|
public long contentLength() {
|
||||||
|
return data.size(context).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(BufferedSink sink) throws IOException {
|
||||||
|
try (Source source = Okio.source(data.open(context))) {
|
||||||
|
sink.writeAll(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return client() //
|
||||||
|
.createFile(file.getParent().getPath(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) //
|
||||||
|
.execute();
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
handleApiError(ex, file.getName());
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void read(PCloudFile file, Optional<File> encryptedTmpFile, OutputStream data, final ProgressAware<DownloadState> progressAware) throws IOException, BackendException {
|
||||||
|
progressAware.onProgress(Progress.started(DownloadState.download(file)));
|
||||||
|
|
||||||
|
Optional<String> cacheKey = Optional.empty();
|
||||||
|
Optional<File> cacheFile = Optional.empty();
|
||||||
|
|
||||||
|
RemoteFile remoteFile;
|
||||||
|
|
||||||
|
if (sharedPreferencesHandler.useLruCache() && createLruCache(sharedPreferencesHandler.lruCacheSize())) {
|
||||||
|
try {
|
||||||
|
remoteFile = client().loadFile(file.getPath()).execute().asFile();
|
||||||
|
cacheKey = Optional.of(remoteFile.fileId() + remoteFile.hash());
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
handleApiError(ex, file.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
File cachedFile = diskLruCache.get(cacheKey.get());
|
||||||
|
cacheFile = cachedFile != null ? Optional.of(cachedFile) : Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharedPreferencesHandler.useLruCache() && cacheFile.isPresent()) {
|
||||||
|
try {
|
||||||
|
retrieveFromLruCache(cacheFile.get(), data);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.tag("PCloudImpl").w(e, "Error while retrieving content from Cache, get from web request");
|
||||||
|
writeToData(file, data, encryptedTmpFile, cacheKey, progressAware);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
writeToData(file, data, encryptedTmpFile, cacheKey, progressAware);
|
||||||
|
}
|
||||||
|
|
||||||
|
progressAware.onProgress(Progress.completed(DownloadState.download(file)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeToData(final PCloudFile file, //
|
||||||
|
final OutputStream data, //
|
||||||
|
final Optional<File> encryptedTmpFile, //
|
||||||
|
final Optional<String> cacheKey, //
|
||||||
|
final ProgressAware<DownloadState> progressAware) throws IOException, BackendException {
|
||||||
|
try {
|
||||||
|
FileLink fileLink = client().createFileLink(file.getPath(), DownloadOptions.DEFAULT).execute();
|
||||||
|
|
||||||
|
ProgressListener listener = (done, total) -> progressAware.onProgress( //
|
||||||
|
progress(DownloadState.download(file)) //
|
||||||
|
.between(0) //
|
||||||
|
.and(file.getSize().orElse(Long.MAX_VALUE)) //
|
||||||
|
.withValue(done));
|
||||||
|
|
||||||
|
DataSink sink = new DataSink() {
|
||||||
|
@Override
|
||||||
|
public void readAll(BufferedSource source) {
|
||||||
|
CopyStream.copyStreamToStream(source.inputStream(), data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client().download(fileLink, sink, listener).execute();
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
handleApiError(ex, file.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) {
|
||||||
|
try {
|
||||||
|
storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.tag("PCloudImpl").e(e, "Failed to write downloaded file in LRU cache");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(PCloudNode node) throws IOException, BackendException {
|
||||||
|
try {
|
||||||
|
if (node instanceof PCloudFolder) {
|
||||||
|
client() //
|
||||||
|
.deleteFolder(node.getPath(), true).execute();
|
||||||
|
} else {
|
||||||
|
client() //
|
||||||
|
.deleteFile(node.getPath()).execute();
|
||||||
|
}
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
handleApiError(ex, node.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String currentAccount() throws IOException, BackendException {
|
||||||
|
try {
|
||||||
|
UserInfo currentAccount = client() //
|
||||||
|
.getUserInfo() //
|
||||||
|
.execute();
|
||||||
|
return currentAccount.email();
|
||||||
|
} catch (ApiError ex) {
|
||||||
|
handleApiError(ex);
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean createLruCache(int cacheSize) {
|
||||||
|
if (diskLruCache == null) {
|
||||||
|
try {
|
||||||
|
diskLruCache = DiskLruCache.create(new LruFileCacheUtil(context).resolve(PCLOUD), cacheSize);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.tag("PCloudImpl").e(e, "Failed to setup LRU cache");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleApiError(ApiError ex) throws BackendException {
|
||||||
|
handleApiError(ex, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleApiError(ApiError ex, String name) throws BackendException {
|
||||||
|
handleApiError(ex, null, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleApiError(ApiError ex, Set<Integer> errorCodes, String name) throws BackendException {
|
||||||
|
if (errorCodes == null || !errorCodes.contains(ex.errorCode())) {
|
||||||
|
int errorCode = ex.errorCode();
|
||||||
|
if (PCloudApiError.isCloudNodeAlreadyExistsException(errorCode)) {
|
||||||
|
throw new CloudNodeAlreadyExistsException(name);
|
||||||
|
} else if (PCloudApiError.isForbiddenException(errorCode)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
} else if (PCloudApiError.isNetworkConnectionException(errorCode)) {
|
||||||
|
throw new NetworkConnectionException(ex);
|
||||||
|
} else if (PCloudApiError.isNoSuchCloudFileException(errorCode)) {
|
||||||
|
throw new NoSuchCloudFileException(name);
|
||||||
|
} else if (PCloudApiError.isWrongCredentialsException(errorCode)) {
|
||||||
|
throw new WrongCredentialsException(cloud);
|
||||||
|
} else if (PCloudApiError.isUnauthorizedException(errorCode)) {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
} else {
|
||||||
|
throw new FatalBackendException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import org.cryptomator.domain.CloudNode;
|
||||||
|
|
||||||
|
interface PCloudNode extends CloudNode {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
PCloudFolder getParent();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import com.pcloud.sdk.RemoteEntry;
|
||||||
|
import com.pcloud.sdk.RemoteFile;
|
||||||
|
import com.pcloud.sdk.RemoteFolder;
|
||||||
|
|
||||||
|
import org.cryptomator.util.Optional;
|
||||||
|
|
||||||
|
class PCloudNodeFactory {
|
||||||
|
|
||||||
|
public static PCloudFile file(PCloudFolder parent, RemoteFile file) {
|
||||||
|
return new PCloudFile(parent, file.name(), getNodePath(parent, file.name()), Optional.ofNullable(file.size()), Optional.ofNullable(file.lastModified()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PCloudFile file(PCloudFolder parent, String name, Optional<Long> size) {
|
||||||
|
return new PCloudFile(parent, name, getNodePath(parent, name), size, Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PCloudFile file(PCloudFolder folder, String name, Optional<Long> size, String path) {
|
||||||
|
return new PCloudFile(folder, name, path, size, Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PCloudFolder folder(PCloudFolder parent, RemoteFolder folder) {
|
||||||
|
return new PCloudFolder(parent, folder.name(), getNodePath(parent, folder.name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PCloudFolder folder(PCloudFolder parent, String name) {
|
||||||
|
return new PCloudFolder(parent, name, getNodePath(parent, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PCloudFolder folder(PCloudFolder parent, String name, String path) {
|
||||||
|
return new PCloudFolder(parent, name, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getNodePath(PCloudFolder parent, String name) {
|
||||||
|
return parent.getPath() + "/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PCloudNode from(PCloudFolder parent, RemoteEntry remoteEntry) {
|
||||||
|
if (remoteEntry instanceof RemoteFile) {
|
||||||
|
return file(parent, remoteEntry.asFile());
|
||||||
|
} else {
|
||||||
|
return folder(parent, remoteEntry.asFolder());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package org.cryptomator.data.cloud.pcloud;
|
||||||
|
|
||||||
|
import org.cryptomator.domain.Cloud;
|
||||||
|
import org.cryptomator.domain.PCloud;
|
||||||
|
|
||||||
|
class RootPCloudFolder extends PCloudFolder {
|
||||||
|
|
||||||
|
private final PCloud cloud;
|
||||||
|
|
||||||
|
public RootPCloudFolder(PCloud cloud) {
|
||||||
|
super(null, "", "");
|
||||||
|
this.cloud = cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloud getCloud() {
|
||||||
|
return cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PCloudFolder withCloud(Cloud cloud) {
|
||||||
|
return new RootPCloudFolder((PCloud) cloud);
|
||||||
|
}
|
||||||
|
}
|
@ -22,13 +22,15 @@ class DatabaseUpgrades {
|
|||||||
Upgrade0To1 upgrade0To1, //
|
Upgrade0To1 upgrade0To1, //
|
||||||
Upgrade1To2 upgrade1To2, //
|
Upgrade1To2 upgrade1To2, //
|
||||||
Upgrade2To3 upgrade2To3, //
|
Upgrade2To3 upgrade2To3, //
|
||||||
Upgrade3To4 upgrade3To4) {
|
Upgrade3To4 upgrade3To4, //
|
||||||
|
Upgrade4To5 upgrade4To5) {
|
||||||
|
|
||||||
availableUpgrades = defineUpgrades( //
|
availableUpgrades = defineUpgrades( //
|
||||||
upgrade0To1, //
|
upgrade0To1, //
|
||||||
upgrade1To2, //
|
upgrade1To2, //
|
||||||
upgrade2To3, //
|
upgrade2To3, //
|
||||||
upgrade3To4);
|
upgrade3To4, //
|
||||||
|
upgrade4To5);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Comparator<DatabaseUpgrade> reverseOrder() {
|
private static Comparator<DatabaseUpgrade> reverseOrder() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.cryptomator.data.db;
|
package org.cryptomator.data.db;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
||||||
import org.greenrobot.greendao.database.Database;
|
import org.greenrobot.greendao.database.Database;
|
||||||
@ -49,6 +50,10 @@ class Sql {
|
|||||||
return new SqlUpdateBuilder(tableName);
|
return new SqlUpdateBuilder(tableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SqlQueryBuilder query(String table) {
|
||||||
|
return new SqlQueryBuilder(table);
|
||||||
|
}
|
||||||
|
|
||||||
public static Criterion eq(final String value) {
|
public static Criterion eq(final String value) {
|
||||||
return (column, whereClause, whereArgs) -> {
|
return (column, whereClause, whereArgs) -> {
|
||||||
whereClause.append('"').append(column).append("\" = ?");
|
whereClause.append('"').append(column).append("\" = ?");
|
||||||
@ -91,6 +96,56 @@ class Sql {
|
|||||||
void appendTo(String column, StringBuilder whereClause, List<String> whereArgs);
|
void appendTo(String column, StringBuilder whereClause, List<String> whereArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SqlQueryBuilder {
|
||||||
|
|
||||||
|
private final String tableName;
|
||||||
|
private final StringBuilder whereClause = new StringBuilder();
|
||||||
|
private final List<String> whereArgs = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<String> columns = new ArrayList<>();
|
||||||
|
private String groupBy;
|
||||||
|
private String having;
|
||||||
|
private String limit;
|
||||||
|
|
||||||
|
public SqlQueryBuilder(String tableName) {
|
||||||
|
this.tableName = tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqlQueryBuilder columns(List<String> 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 {
|
public static class SqlUpdateBuilder {
|
||||||
|
|
||||||
private final String tableName;
|
private final String tableName;
|
||||||
|
@ -2,10 +2,8 @@ package org.cryptomator.data.db
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import org.cryptomator.data.db.entities.CloudEntityDao
|
|
||||||
import org.cryptomator.util.crypto.CredentialCryptor
|
import org.cryptomator.util.crypto.CredentialCryptor
|
||||||
import org.greenrobot.greendao.database.Database
|
import org.greenrobot.greendao.database.Database
|
||||||
import org.greenrobot.greendao.internal.DaoConfig
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -13,16 +11,23 @@ import javax.inject.Singleton
|
|||||||
internal class Upgrade2To3 @Inject constructor(private val context: Context) : DatabaseUpgrade(2, 3) {
|
internal class Upgrade2To3 @Inject constructor(private val context: Context) : DatabaseUpgrade(2, 3) {
|
||||||
|
|
||||||
override fun internalApplyTo(db: Database, origin: Int) {
|
override fun internalApplyTo(db: Database, origin: Int) {
|
||||||
val clouds = CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll()
|
|
||||||
db.beginTransaction()
|
db.beginTransaction()
|
||||||
try {
|
try {
|
||||||
clouds.filter { cloud -> cloud.type == "DROPBOX" || cloud.type == "ONEDRIVE" } //
|
Sql.query("CLOUD_ENTITY")
|
||||||
.map {
|
.columns(listOf("ACCESS_TOKEN"))
|
||||||
Sql.update("CLOUD_ENTITY") //
|
.where("TYPE", Sql.eq("DROPBOX"))
|
||||||
.where("TYPE", Sql.eq(it.type)) //
|
.executeOn(db).use {
|
||||||
.set("ACCESS_TOKEN", Sql.toString(encrypt(if (it.type == "DROPBOX") it.accessToken else onedriveToken()))) //
|
if (it.moveToFirst()) {
|
||||||
.executeOn(db)
|
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()
|
db.setTransactionSuccessful()
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction()
|
db.endTransaction()
|
||||||
|
73
data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -16,18 +16,18 @@ public class CloudEntity extends DatabaseEntity {
|
|||||||
|
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
|
|
||||||
private String webdavUrl;
|
private String url;
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
private String webdavCertificate;
|
private String webdavCertificate;
|
||||||
|
|
||||||
@Generated(hash = 2078985174)
|
@Generated(hash = 361171073)
|
||||||
public CloudEntity(Long id, @NotNull String type, String accessToken, String webdavUrl, String username, String webdavCertificate) {
|
public CloudEntity(Long id, @NotNull String type, String accessToken, String url, String username, String webdavCertificate) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
this.webdavUrl = webdavUrl;
|
this.url = url;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.webdavCertificate = webdavCertificate;
|
this.webdavCertificate = webdavCertificate;
|
||||||
}
|
}
|
||||||
@ -60,12 +60,12 @@ public class CloudEntity extends DatabaseEntity {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getWebdavUrl() {
|
public String getUrl() {
|
||||||
return webdavUrl;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWebdavUrl(String webdavUrl) {
|
public void setUrl(String url) {
|
||||||
this.webdavUrl = webdavUrl;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
|
@ -182,7 +182,9 @@ public class VaultEntity extends DatabaseEntity {
|
|||||||
this.position = position;
|
this.position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** called by internal mechanisms, do not call yourself. */
|
/**
|
||||||
|
* called by internal mechanisms, do not call yourself.
|
||||||
|
*/
|
||||||
@Generated(hash = 674742652)
|
@Generated(hash = 674742652)
|
||||||
public void __setDaoSession(DaoSession daoSession) {
|
public void __setDaoSession(DaoSession daoSession) {
|
||||||
this.daoSession = daoSession;
|
this.daoSession = daoSession;
|
||||||
|
@ -7,6 +7,7 @@ import org.cryptomator.domain.DropboxCloud;
|
|||||||
import org.cryptomator.domain.GoogleDriveCloud;
|
import org.cryptomator.domain.GoogleDriveCloud;
|
||||||
import org.cryptomator.domain.LocalStorageCloud;
|
import org.cryptomator.domain.LocalStorageCloud;
|
||||||
import org.cryptomator.domain.OnedriveCloud;
|
import org.cryptomator.domain.OnedriveCloud;
|
||||||
|
import org.cryptomator.domain.PCloud;
|
||||||
import org.cryptomator.domain.WebDavCloud;
|
import org.cryptomator.domain.WebDavCloud;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
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.GoogleDriveCloud.aGoogleDriveCloud;
|
||||||
import static org.cryptomator.domain.LocalStorageCloud.aLocalStorage;
|
import static org.cryptomator.domain.LocalStorageCloud.aLocalStorage;
|
||||||
import static org.cryptomator.domain.OnedriveCloud.aOnedriveCloud;
|
import static org.cryptomator.domain.OnedriveCloud.aOnedriveCloud;
|
||||||
|
import static org.cryptomator.domain.PCloud.aPCloud;
|
||||||
import static org.cryptomator.domain.WebDavCloud.aWebDavCloudCloud;
|
import static org.cryptomator.domain.WebDavCloud.aWebDavCloudCloud;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@ -47,6 +49,13 @@ public class CloudEntityMapper extends EntityMapper<CloudEntity, Cloud> {
|
|||||||
.withAccessToken(entity.getAccessToken()) //
|
.withAccessToken(entity.getAccessToken()) //
|
||||||
.withUsername(entity.getUsername()) //
|
.withUsername(entity.getUsername()) //
|
||||||
.build();
|
.build();
|
||||||
|
case PCLOUD:
|
||||||
|
return aPCloud() //
|
||||||
|
.withId(entity.getId()) //
|
||||||
|
.withUrl(entity.getUrl()) //
|
||||||
|
.withAccessToken(entity.getAccessToken()) //
|
||||||
|
.withUsername(entity.getUsername()) //
|
||||||
|
.build();
|
||||||
case LOCAL:
|
case LOCAL:
|
||||||
return aLocalStorage() //
|
return aLocalStorage() //
|
||||||
.withId(entity.getId()) //
|
.withId(entity.getId()) //
|
||||||
@ -54,7 +63,7 @@ public class CloudEntityMapper extends EntityMapper<CloudEntity, Cloud> {
|
|||||||
case WEBDAV:
|
case WEBDAV:
|
||||||
return aWebDavCloudCloud() //
|
return aWebDavCloudCloud() //
|
||||||
.withId(entity.getId()) //
|
.withId(entity.getId()) //
|
||||||
.withUrl(entity.getWebdavUrl()) //
|
.withUrl(entity.getUrl()) //
|
||||||
.withUsername(entity.getUsername()) //
|
.withUsername(entity.getUsername()) //
|
||||||
.withPassword(entity.getAccessToken()) //
|
.withPassword(entity.getAccessToken()) //
|
||||||
.withCertificate(entity.getWebdavCertificate()) //
|
.withCertificate(entity.getWebdavCertificate()) //
|
||||||
@ -82,12 +91,17 @@ public class CloudEntityMapper extends EntityMapper<CloudEntity, Cloud> {
|
|||||||
result.setAccessToken(((OnedriveCloud) domainObject).accessToken());
|
result.setAccessToken(((OnedriveCloud) domainObject).accessToken());
|
||||||
result.setUsername(((OnedriveCloud) domainObject).username());
|
result.setUsername(((OnedriveCloud) domainObject).username());
|
||||||
break;
|
break;
|
||||||
|
case PCLOUD:
|
||||||
|
result.setAccessToken(((PCloud) domainObject).accessToken());
|
||||||
|
result.setUrl(((PCloud) domainObject).url());
|
||||||
|
result.setUsername(((PCloud) domainObject).username());
|
||||||
|
break;
|
||||||
case LOCAL:
|
case LOCAL:
|
||||||
result.setAccessToken(((LocalStorageCloud) domainObject).rootUri());
|
result.setAccessToken(((LocalStorageCloud) domainObject).rootUri());
|
||||||
break;
|
break;
|
||||||
case WEBDAV:
|
case WEBDAV:
|
||||||
result.setAccessToken(((WebDavCloud) domainObject).password());
|
result.setAccessToken(((WebDavCloud) domainObject).password());
|
||||||
result.setWebdavUrl(((WebDavCloud) domainObject).url());
|
result.setUrl(((WebDavCloud) domainObject).url());
|
||||||
result.setUsername(((WebDavCloud) domainObject).username());
|
result.setUsername(((WebDavCloud) domainObject).username());
|
||||||
result.setWebdavCertificate(((WebDavCloud) domainObject).certificate());
|
result.setWebdavCertificate(((WebDavCloud) domainObject).certificate());
|
||||||
break;
|
break;
|
||||||
|
@ -5,6 +5,7 @@ import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory;
|
|||||||
import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory;
|
import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory;
|
||||||
import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory;
|
import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory;
|
||||||
import org.cryptomator.data.cloud.onedrive.OnedriveCloudContentRepositoryFactory;
|
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.cloud.webdav.WebDavCloudContentRepositoryFactory;
|
||||||
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -25,6 +26,7 @@ public class CloudContentRepositoryFactories implements Iterable<CloudContentRep
|
|||||||
public CloudContentRepositoryFactories(DropboxCloudContentRepositoryFactory dropboxFactory, //
|
public CloudContentRepositoryFactories(DropboxCloudContentRepositoryFactory dropboxFactory, //
|
||||||
GoogleDriveCloudContentRepositoryFactory googleDriveFactory, //
|
GoogleDriveCloudContentRepositoryFactory googleDriveFactory, //
|
||||||
OnedriveCloudContentRepositoryFactory oneDriveFactory, //
|
OnedriveCloudContentRepositoryFactory oneDriveFactory, //
|
||||||
|
PCloudContentRepositoryFactory pCloudFactory, //
|
||||||
CryptoCloudContentRepositoryFactory cryptoFactory, //
|
CryptoCloudContentRepositoryFactory cryptoFactory, //
|
||||||
LocalStorageContentRepositoryFactory localStorageFactory, //
|
LocalStorageContentRepositoryFactory localStorageFactory, //
|
||||||
WebDavCloudContentRepositoryFactory webDavFactory) {
|
WebDavCloudContentRepositoryFactory webDavFactory) {
|
||||||
@ -32,6 +34,7 @@ public class CloudContentRepositoryFactories implements Iterable<CloudContentRep
|
|||||||
factories = asList(dropboxFactory, //
|
factories = asList(dropboxFactory, //
|
||||||
googleDriveFactory, //
|
googleDriveFactory, //
|
||||||
oneDriveFactory, //
|
oneDriveFactory, //
|
||||||
|
pCloudFactory, //
|
||||||
cryptoFactory, //
|
cryptoFactory, //
|
||||||
localStorageFactory, //
|
localStorageFactory, //
|
||||||
webDavFactory);
|
webDavFactory);
|
||||||
|
@ -3,7 +3,7 @@ package org.cryptomator.data.cloud.googledrive;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||||
import com.google.api.client.json.gson.GsonFactory;
|
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||||
import com.google.api.services.drive.Drive;
|
import com.google.api.services.drive.Drive;
|
||||||
import com.google.api.services.drive.DriveScopes;
|
import com.google.api.services.drive.DriveScopes;
|
||||||
|
|
||||||
@ -35,7 +35,9 @@ class GoogleDriveClientFactory {
|
|||||||
Logger.getLogger("com.google.api.client").addHandler(new Handler() {
|
Logger.getLogger("com.google.api.client").addHandler(new Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void publish(LogRecord record) {
|
public void publish(LogRecord record) {
|
||||||
if (record.getMessage().startsWith("-------------- RESPONSE --------------") || record.getMessage().startsWith("-------------- REQUEST --------------") || record.getMessage().startsWith("{\n \"files\": [\n")) {
|
if (record.getMessage().startsWith("-------------- RESPONSE --------------") //
|
||||||
|
|| record.getMessage().startsWith("-------------- REQUEST --------------") //
|
||||||
|
|| record.getMessage().startsWith("{\n \"files\": [\n")) {
|
||||||
Timber.tag("GoogleDriveClient").d(record.getMessage());
|
Timber.tag("GoogleDriveClient").d(record.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +55,7 @@ class GoogleDriveClientFactory {
|
|||||||
try {
|
try {
|
||||||
FixedGoogleAccountCredential credential = FixedGoogleAccountCredential.usingOAuth2(context, Collections.singleton(DriveScopes.DRIVE));
|
FixedGoogleAccountCredential credential = FixedGoogleAccountCredential.usingOAuth2(context, Collections.singleton(DriveScopes.DRIVE));
|
||||||
credential.setAccountName(accountName);
|
credential.setAccountName(accountName);
|
||||||
return new Drive.Builder(new NetHttpTransport(), GsonFactory.getDefaultInstance(), credential) //
|
return new Drive.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential) //
|
||||||
.setApplicationName("Cryptomator-Android/" + BuildConfig.VERSION_NAME) //
|
.setApplicationName("Cryptomator-Android/" + BuildConfig.VERSION_NAME) //
|
||||||
.build();
|
.build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.cryptomator.domain">
|
package="org.cryptomator.domain">
|
||||||
|
|
||||||
<application android:allowBackup="true" />
|
<application android:allowBackup="false" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -2,6 +2,6 @@ package org.cryptomator.domain;
|
|||||||
|
|
||||||
public enum CloudType {
|
public enum CloudType {
|
||||||
|
|
||||||
DROPBOX, GOOGLE_DRIVE, ONEDRIVE, WEBDAV, LOCAL, CRYPTO
|
DROPBOX, GOOGLE_DRIVE, ONEDRIVE, PCLOUD, WEBDAV, LOCAL, CRYPTO
|
||||||
|
|
||||||
}
|
}
|
||||||
|
140
domain/src/main/java/org/cryptomator/domain/PCloud.java
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package org.cryptomator.domain;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class PCloud implements Cloud {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final String accessToken;
|
||||||
|
private final String url;
|
||||||
|
private final String username;
|
||||||
|
|
||||||
|
private PCloud(Builder builder) {
|
||||||
|
this.id = builder.id;
|
||||||
|
this.accessToken = builder.accessToken;
|
||||||
|
this.url = builder.url;
|
||||||
|
this.username = builder.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder aPCloud() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder aCopyOf(PCloud pCloud) {
|
||||||
|
return new Builder() //
|
||||||
|
.withId(pCloud.id()) //
|
||||||
|
.withAccessToken(pCloud.accessToken()) //
|
||||||
|
.withUrl(pCloud.url()) //
|
||||||
|
.withUsername(pCloud.username());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String accessToken() {
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String url() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String username() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CloudType type() {
|
||||||
|
return CloudType.PCLOUD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configurationMatches(Cloud cloud) {
|
||||||
|
return cloud instanceof PCloud && configurationMatches((PCloud) cloud);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean configurationMatches(PCloud cloud) {
|
||||||
|
return username.equals(cloud.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean predefined() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean persistent() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresNetwork() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PCLOUD";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return internalEquals((PCloud) obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id == null ? 0 : id.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean internalEquals(PCloud obj) {
|
||||||
|
return id != null && id.equals(obj.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String accessToken;
|
||||||
|
private String url;
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
private Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withAccessToken(String accessToken) {
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PCloud build() {
|
||||||
|
return new PCloud(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package org.cryptomator.domain.usecases.cloud;
|
||||||
|
|
||||||
|
import org.cryptomator.domain.PCloud;
|
||||||
|
import org.cryptomator.domain.exception.BackendException;
|
||||||
|
import org.cryptomator.domain.repository.CloudContentRepository;
|
||||||
|
import org.cryptomator.generator.Parameter;
|
||||||
|
import org.cryptomator.generator.UseCase;
|
||||||
|
|
||||||
|
@UseCase
|
||||||
|
class ConnectToPCloud {
|
||||||
|
|
||||||
|
private final CloudContentRepository cloudContentRepository;
|
||||||
|
private final PCloud cloud;
|
||||||
|
|
||||||
|
public ConnectToPCloud(CloudContentRepository cloudContentRepository, @Parameter PCloud cloud) {
|
||||||
|
this.cloudContentRepository = cloudContentRepository;
|
||||||
|
this.cloud = cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws BackendException {
|
||||||
|
cloudContentRepository.checkAuthenticationAndRetrieveCurrentAccount(cloud);
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,7 @@ fastlane_require 'net/sftp'
|
|||||||
|
|
||||||
default_platform(:android)
|
default_platform(:android)
|
||||||
|
|
||||||
branch_name = `git rev-parse --abbrev-ref HEAD`
|
build = number_of_commits + 1958 # adding 1958 for legacy reasons. Must be in sync with getVersionCode() from build.gradle
|
||||||
build = `git rev-list --count #{branch_name} | tr -d " \t\n\r"`
|
|
||||||
build = build.to_i + 1958 # adding 1958 for legacy reasons. Must be in sync with getVersionCode() from build.gradle
|
|
||||||
version = get_version_name(
|
version = get_version_name(
|
||||||
gradle_file_path:"build.gradle",
|
gradle_file_path:"build.gradle",
|
||||||
ext_constant_name:"androidVersionName")
|
ext_constant_name:"androidVersionName")
|
||||||
@ -188,7 +186,7 @@ platform :android do |options|
|
|||||||
prerelease = false
|
prerelease = false
|
||||||
|
|
||||||
if options[:beta]
|
if options[:beta]
|
||||||
target_branch = "release/#{version}"
|
target_branch = git_branch
|
||||||
prerelease = true
|
prerelease = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
- Möglichkeit neu erstellte Videos über den automatischen Upload hochzuladen hinzugefügt
|
- Native pCloud-Unterstützung hinzugefügt (großen Dank an Manu für die Implementierung)
|
||||||
- Möglichkeit das Entsperren eines Tresors abzubrechen hinzugefügt
|
- 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
|
@ -1,2 +1,4 @@
|
|||||||
- Added possibility to upload newly created videos via automatic upload as well
|
- Added pCloud native support (thanks to Manu for this huge contribution)
|
||||||
- Added possibility to cancel unlocking a vault
|
- 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
|
@ -1,4 +1,6 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li>Added possibility to upload newly created videos via automatic upload as well</li>
|
<li>Added pCloud native support (thanks to Manu for this huge contribution)</li>
|
||||||
<li>Added possibility to cancel unlocking a vault</li>
|
<li>Fixed app crash when restoring Cryptomator from a backup</li>
|
||||||
|
<li>Enhanced display of long settings</li>
|
||||||
|
<li>Enhanced deletion of the last image via the preview. Now jumps back to the vault contents list</li>
|
||||||
</ul>
|
</ul>
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,5 @@
|
|||||||
#Thu Apr 18 12:59:33 CEST 2019
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
|
|
||||||
|
139
gradlew
vendored
@ -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
|
# Attempt to set APP_HOME
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
PRG="$0"
|
||||||
@ -60,8 +40,49 @@ cd "`dirname \"$PRG\"`/" >/dev/null
|
|||||||
APP_HOME="`pwd -P`"
|
APP_HOME="`pwd -P`"
|
||||||
cd "$SAVED" >/dev/null
|
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
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@ -85,7 +106,7 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# 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`
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
if [ $? -eq 0 ] ; then
|
if [ $? -eq 0 ] ; then
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; 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\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
@ -134,27 +156,30 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
# Escape application args
|
||||||
function splitJvmOpts() {
|
save () {
|
||||||
JVM_OPTS=("$@")
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
}
|
}
|
||||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
APP_ARGS=`save "$@"`
|
||||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
|
||||||
|
|
||||||
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" "$@"
|
||||||
|
53
gradlew.bat
vendored
@ -1,3 +1,19 @@
|
|||||||
|
@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
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@ -8,20 +24,23 @@
|
|||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables with windows NT shell
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
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
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
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
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@ -35,7 +54,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@ -45,34 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
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
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@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%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
1
pcloud-sdk-java
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit d12c6e6c4af8d0360812900663d5298ca093377b
|
@ -51,6 +51,7 @@ android {
|
|||||||
|
|
||||||
buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY') + "\""
|
buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY') + "\""
|
||||||
manifestPlaceholders = [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
|
resValue "string", "app_id", androidApplicationId
|
||||||
}
|
}
|
||||||
@ -65,6 +66,7 @@ android {
|
|||||||
|
|
||||||
buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY_DEBUG') + "\""
|
buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY_DEBUG') + "\""
|
||||||
manifestPlaceholders = [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"
|
applicationIdSuffix ".debug"
|
||||||
versionNameSuffix '-DEBUG'
|
versionNameSuffix '-DEBUG'
|
||||||
@ -118,6 +120,7 @@ dependencies {
|
|||||||
implementation project(':util')
|
implementation project(':util')
|
||||||
implementation project(':domain')
|
implementation project(':domain')
|
||||||
implementation project(':data')
|
implementation project(':data')
|
||||||
|
implementation project(':pcloud-sdk-android')
|
||||||
|
|
||||||
// dagger
|
// dagger
|
||||||
kapt dependencies.daggerCompiler
|
kapt dependencies.daggerCompiler
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".CryptomatorApp"
|
android:name=".CryptomatorApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
|
@ -7,42 +7,59 @@ enum class CloudTypeModel(builder: Builder) {
|
|||||||
|
|
||||||
CRYPTO(Builder("CRYPTO", R.string.cloud_names_crypto)), //
|
CRYPTO(Builder("CRYPTO", R.string.cloud_names_crypto)), //
|
||||||
DROPBOX(Builder("DROPBOX", R.string.cloud_names_dropbox) //
|
DROPBOX(Builder("DROPBOX", R.string.cloud_names_dropbox) //
|
||||||
.withCloudImageResource(R.drawable.cloud_type_dropbox) //
|
.withCloudImageResource(R.drawable.dropbox) //
|
||||||
.withCloudImageLargeResource(R.drawable.cloud_type_dropbox_large)), //
|
.withVaultImageResource(R.drawable.dropbox_vault) //
|
||||||
|
.withVaultSelectedImageResource(R.drawable.dropbox_vault_selected)), //
|
||||||
GOOGLE_DRIVE(Builder("GOOGLE_DRIVE", R.string.cloud_names_google_drive) //
|
GOOGLE_DRIVE(Builder("GOOGLE_DRIVE", R.string.cloud_names_google_drive) //
|
||||||
.withCloudImageResource(R.drawable.cloud_type_google_drive) //
|
.withCloudImageResource(R.drawable.google_drive) //
|
||||||
.withCloudImageLargeResource(R.drawable.cloud_type_google_drive_large)), //
|
.withVaultImageResource(R.drawable.google_drive_vault) //
|
||||||
|
.withVaultSelectedImageResource(R.drawable.google_drive_vault_selected)), //
|
||||||
ONEDRIVE(Builder("ONEDRIVE", R.string.cloud_names_onedrive) //
|
ONEDRIVE(Builder("ONEDRIVE", R.string.cloud_names_onedrive) //
|
||||||
.withCloudImageResource(R.drawable.cloud_type_onedrive) //
|
.withCloudImageResource(R.drawable.onedrive) //
|
||||||
.withCloudImageLargeResource(R.drawable.cloud_type_onedrive_large)), //
|
.withVaultImageResource(R.drawable.onedrive_vault) //
|
||||||
|
.withVaultSelectedImageResource(R.drawable.onedrive_vault_selected)), //
|
||||||
|
PCLOUD(Builder("PCLOUD", R.string.cloud_names_pcloud) //
|
||||||
|
.withCloudImageResource(R.drawable.pcloud) //
|
||||||
|
.withVaultImageResource(R.drawable.pcloud_vault) //
|
||||||
|
.withVaultSelectedImageResource(R.drawable.pcloud_vault_selected) //
|
||||||
|
.withMultiInstances()), //
|
||||||
WEBDAV(Builder("WEBDAV", R.string.cloud_names_webdav) //
|
WEBDAV(Builder("WEBDAV", R.string.cloud_names_webdav) //
|
||||||
.withCloudImageResource(R.drawable.cloud_type_webdav) //
|
.withCloudImageResource(R.drawable.webdav) //
|
||||||
.withCloudImageLargeResource(R.drawable.cloud_type_webdav_large) //
|
.withVaultImageResource(R.drawable.webdav_vault) //
|
||||||
|
.withVaultSelectedImageResource(R.drawable.webdav_vault_selected) //
|
||||||
.withMultiInstances()), //
|
.withMultiInstances()), //
|
||||||
LOCAL(Builder("LOCAL", R.string.cloud_names_local_storage) //
|
LOCAL(Builder("LOCAL", R.string.cloud_names_local_storage) //
|
||||||
.withCloudImageResource(R.drawable.storage_type_local) //
|
.withCloudImageResource(R.drawable.local_fs) //
|
||||||
.withCloudImageLargeResource(R.drawable.storage_type_local_large) //
|
.withVaultImageResource(R.drawable.local_fs_vault) //
|
||||||
|
.withVaultSelectedImageResource(R.drawable.local_fs_vault_selected) //
|
||||||
.withMultiInstances());
|
.withMultiInstances());
|
||||||
|
|
||||||
val cloudName: String = builder.cloudName
|
val cloudName: String = builder.cloudName
|
||||||
val displayNameResource: Int = builder.displayNameResource
|
val displayNameResource: Int = builder.displayNameResource
|
||||||
val cloudImageResource: Int = builder.cloudImageResource
|
val cloudImageResource: Int = builder.cloudImageResource
|
||||||
val cloudImageLargeResource: Int = builder.cloudImageLargeResource
|
val vaultImageResource: Int = builder.vaultImageResource
|
||||||
|
val vaultSelectedImageResource: Int = builder.vaultSelectedImageResource
|
||||||
val isMultiInstance: Boolean = builder.multiInstances
|
val isMultiInstance: Boolean = builder.multiInstances
|
||||||
|
|
||||||
private class Builder(val cloudName: String, val displayNameResource: Int) {
|
private class Builder(val cloudName: String, val displayNameResource: Int) {
|
||||||
|
|
||||||
var cloudImageResource = 0
|
var cloudImageResource = 0
|
||||||
var cloudImageLargeResource = 0
|
var vaultImageResource = 0
|
||||||
|
var vaultSelectedImageResource = 0
|
||||||
var multiInstances = false
|
var multiInstances = false
|
||||||
|
|
||||||
fun withCloudImageResource(cloudImageResource: Int): Builder {
|
fun withCloudImageResource(cloudImageLargeResource: Int): Builder {
|
||||||
this.cloudImageResource = cloudImageResource
|
this.cloudImageResource = cloudImageLargeResource
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun withCloudImageLargeResource(cloudImageLargeResource: Int): Builder {
|
fun withVaultImageResource(vaultImageResource: Int): Builder {
|
||||||
this.cloudImageLargeResource = cloudImageLargeResource
|
this.vaultImageResource = vaultImageResource
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withVaultSelectedImageResource(vaultSelectedImageResource: Int): Builder {
|
||||||
|
this.vaultSelectedImageResource = vaultSelectedImageResource
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package org.cryptomator.presentation.model
|
||||||
|
|
||||||
|
import org.cryptomator.domain.Cloud
|
||||||
|
import org.cryptomator.domain.PCloud
|
||||||
|
import org.cryptomator.presentation.R
|
||||||
|
|
||||||
|
class PCloudModel(cloud: Cloud) : CloudModel(cloud) {
|
||||||
|
|
||||||
|
override fun name(): Int {
|
||||||
|
return R.string.cloud_names_pcloud
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun username(): String? {
|
||||||
|
return cloud().username()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun url(): String {
|
||||||
|
return cloud().url()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun id(): Long {
|
||||||
|
return cloud().id()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cloud(): PCloud {
|
||||||
|
return toCloud() as PCloud
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cloudType(): CloudTypeModel {
|
||||||
|
return CloudTypeModel.PCLOUD
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import org.cryptomator.presentation.model.DropboxCloudModel
|
|||||||
import org.cryptomator.presentation.model.GoogleDriveCloudModel
|
import org.cryptomator.presentation.model.GoogleDriveCloudModel
|
||||||
import org.cryptomator.presentation.model.LocalStorageModel
|
import org.cryptomator.presentation.model.LocalStorageModel
|
||||||
import org.cryptomator.presentation.model.OnedriveCloudModel
|
import org.cryptomator.presentation.model.OnedriveCloudModel
|
||||||
|
import org.cryptomator.presentation.model.PCloudModel
|
||||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ class CloudModelMapper @Inject constructor() : ModelMapper<CloudModel, Cloud>()
|
|||||||
CloudTypeModel.DROPBOX -> DropboxCloudModel(domainObject)
|
CloudTypeModel.DROPBOX -> DropboxCloudModel(domainObject)
|
||||||
CloudTypeModel.GOOGLE_DRIVE -> GoogleDriveCloudModel(domainObject)
|
CloudTypeModel.GOOGLE_DRIVE -> GoogleDriveCloudModel(domainObject)
|
||||||
CloudTypeModel.ONEDRIVE -> OnedriveCloudModel(domainObject)
|
CloudTypeModel.ONEDRIVE -> OnedriveCloudModel(domainObject)
|
||||||
|
CloudTypeModel.PCLOUD -> PCloudModel(domainObject)
|
||||||
CloudTypeModel.CRYPTO -> CryptoCloudModel(domainObject)
|
CloudTypeModel.CRYPTO -> CryptoCloudModel(domainObject)
|
||||||
CloudTypeModel.LOCAL -> LocalStorageModel(domainObject)
|
CloudTypeModel.LOCAL -> LocalStorageModel(domainObject)
|
||||||
CloudTypeModel.WEBDAV -> WebDavCloudModel(domainObject)
|
CloudTypeModel.WEBDAV -> WebDavCloudModel(domainObject)
|
||||||
|
@ -6,16 +6,23 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
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.Cloud
|
||||||
import org.cryptomator.domain.LocalStorageCloud
|
import org.cryptomator.domain.LocalStorageCloud
|
||||||
|
import org.cryptomator.domain.PCloud
|
||||||
import org.cryptomator.domain.Vault
|
import org.cryptomator.domain.Vault
|
||||||
import org.cryptomator.domain.di.PerView
|
import org.cryptomator.domain.di.PerView
|
||||||
import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
|
import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
|
||||||
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
|
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.cloud.RemoveCloudUseCase
|
||||||
import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase
|
import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase
|
||||||
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
|
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
|
||||||
import org.cryptomator.generator.Callback
|
import org.cryptomator.generator.Callback
|
||||||
|
import org.cryptomator.presentation.BuildConfig
|
||||||
import org.cryptomator.presentation.R
|
import org.cryptomator.presentation.R
|
||||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||||
import org.cryptomator.presentation.intent.Intents
|
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.model.mappers.CloudModelMapper
|
||||||
import org.cryptomator.presentation.ui.activity.view.CloudConnectionListView
|
import org.cryptomator.presentation.ui.activity.view.CloudConnectionListView
|
||||||
import org.cryptomator.presentation.workflow.ActivityResult
|
import org.cryptomator.presentation.workflow.ActivityResult
|
||||||
|
import org.cryptomator.util.crypto.CredentialCryptor
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -34,6 +42,7 @@ import timber.log.Timber
|
|||||||
@PerView
|
@PerView
|
||||||
class CloudConnectionListPresenter @Inject constructor( //
|
class CloudConnectionListPresenter @Inject constructor( //
|
||||||
private val getCloudsUseCase: GetCloudsUseCase, //
|
private val getCloudsUseCase: GetCloudsUseCase, //
|
||||||
|
private val getUsernameUseCase: GetUsernameUseCase, //
|
||||||
private val removeCloudUseCase: RemoveCloudUseCase, //
|
private val removeCloudUseCase: RemoveCloudUseCase, //
|
||||||
private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, //
|
private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, //
|
||||||
private val getVaultListUseCase: GetVaultListUseCase, //
|
private val getVaultListUseCase: GetVaultListUseCase, //
|
||||||
@ -122,6 +131,18 @@ class CloudConnectionListPresenter @Inject constructor( //
|
|||||||
when (selectedCloudType.get()) {
|
when (selectedCloudType.get()) {
|
||||||
CloudTypeModel.WEBDAV -> requestActivityResult(ActivityResultCallbacks.addChangeWebDavCloud(), //
|
CloudTypeModel.WEBDAV -> requestActivityResult(ActivityResultCallbacks.addChangeWebDavCloud(), //
|
||||||
Intents.webDavAddOrChangeIntent())
|
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()
|
CloudTypeModel.LOCAL -> openDocumentTree()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,6 +183,71 @@ class CloudConnectionListPresenter @Inject constructor( //
|
|||||||
loadCloudList()
|
loadCloudList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Callback
|
||||||
|
fun pCloudAuthenticationFinished(activityResult: ActivityResult) {
|
||||||
|
val authData: AuthorizationData = AuthorizationActivity.getResult(activityResult.intent())
|
||||||
|
val result: AuthorizationResult = authData.result
|
||||||
|
|
||||||
|
when (result) {
|
||||||
|
AuthorizationResult.ACCESS_GRANTED -> {
|
||||||
|
val accessToken: String = CredentialCryptor //
|
||||||
|
.getInstance(this.context()) //
|
||||||
|
.encrypt(authData.token)
|
||||||
|
val pCloudSkeleton: PCloud = PCloud.aPCloud() //
|
||||||
|
.withAccessToken(accessToken)
|
||||||
|
.withUrl(authData.apiHost)
|
||||||
|
.build();
|
||||||
|
getUsernameUseCase //
|
||||||
|
.withCloud(pCloudSkeleton) //
|
||||||
|
.run(object : DefaultResultHandler<String>() {
|
||||||
|
override fun onSuccess(username: String?) {
|
||||||
|
prepareForSavingPCloud(PCloud.aCopyOf(pCloudSkeleton).withUsername(username).build())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
AuthorizationResult.ACCESS_DENIED -> {
|
||||||
|
Timber.tag("CloudConnListPresenter").e("Account access denied")
|
||||||
|
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||||
|
}
|
||||||
|
AuthorizationResult.AUTH_ERROR -> {
|
||||||
|
Timber.tag("CloudConnListPresenter").e("""Account access grant error: ${authData.errorMessage}""".trimIndent())
|
||||||
|
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||||
|
}
|
||||||
|
AuthorizationResult.CANCELLED -> {
|
||||||
|
Timber.tag("CloudConnListPresenter").i("Account access grant cancelled")
|
||||||
|
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun prepareForSavingPCloud(cloud: PCloud) {
|
||||||
|
getCloudsUseCase //
|
||||||
|
.withCloudType(CloudTypeModel.valueOf(selectedCloudType.get())) //
|
||||||
|
.run(object : DefaultResultHandler<List<Cloud>>() {
|
||||||
|
override fun onSuccess(clouds: List<Cloud>) {
|
||||||
|
clouds.firstOrNull {
|
||||||
|
(it as PCloud).username() == cloud.username()
|
||||||
|
}?.let {
|
||||||
|
it as PCloud
|
||||||
|
saveCloud(PCloud.aCopyOf(it) //
|
||||||
|
.withUrl(cloud.url())
|
||||||
|
.withAccessToken(cloud.accessToken())
|
||||||
|
.build())
|
||||||
|
} ?: saveCloud(cloud)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveCloud(cloud: PCloud) {
|
||||||
|
addOrChangeCloudConnectionUseCase //
|
||||||
|
.withCloud(cloud) //
|
||||||
|
.run(object : DefaultResultHandler<Void?>() {
|
||||||
|
override fun onSuccess(void: Void?) {
|
||||||
|
loadCloudList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@Callback
|
@Callback
|
||||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||||
fun pickedLocalStorageLocation(result: ActivityResult) {
|
fun pickedLocalStorageLocation(result: ActivityResult) {
|
||||||
|
@ -2,6 +2,7 @@ package org.cryptomator.presentation.presenter
|
|||||||
|
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
||||||
import org.cryptomator.domain.LocalStorageCloud
|
import org.cryptomator.domain.LocalStorageCloud
|
||||||
|
import org.cryptomator.domain.PCloud
|
||||||
import org.cryptomator.domain.WebDavCloud
|
import org.cryptomator.domain.WebDavCloud
|
||||||
import org.cryptomator.domain.di.PerView
|
import org.cryptomator.domain.di.PerView
|
||||||
import org.cryptomator.domain.exception.FatalBackendException
|
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.CloudModel
|
||||||
import org.cryptomator.presentation.model.CloudTypeModel
|
import org.cryptomator.presentation.model.CloudTypeModel
|
||||||
import org.cryptomator.presentation.model.LocalStorageModel
|
import org.cryptomator.presentation.model.LocalStorageModel
|
||||||
|
import org.cryptomator.presentation.model.PCloudModel
|
||||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||||
import org.cryptomator.presentation.model.mappers.CloudModelMapper
|
import org.cryptomator.presentation.model.mappers.CloudModelMapper
|
||||||
import org.cryptomator.presentation.ui.activity.view.CloudSettingsView
|
import org.cryptomator.presentation.ui.activity.view.CloudSettingsView
|
||||||
@ -34,6 +36,7 @@ class CloudSettingsPresenter @Inject constructor( //
|
|||||||
private val nonSingleLoginClouds: Set<CloudTypeModel> = EnumSet.of( //
|
private val nonSingleLoginClouds: Set<CloudTypeModel> = EnumSet.of( //
|
||||||
CloudTypeModel.CRYPTO, //
|
CloudTypeModel.CRYPTO, //
|
||||||
CloudTypeModel.LOCAL, //
|
CloudTypeModel.LOCAL, //
|
||||||
|
CloudTypeModel.PCLOUD, //
|
||||||
CloudTypeModel.WEBDAV)
|
CloudTypeModel.WEBDAV)
|
||||||
|
|
||||||
fun loadClouds() {
|
fun loadClouds() {
|
||||||
@ -41,7 +44,7 @@ class CloudSettingsPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onCloudClicked(cloudModel: CloudModel) {
|
fun onCloudClicked(cloudModel: CloudModel) {
|
||||||
if (isWebdavOrLocal(cloudModel)) {
|
if (isWebdavOrPCloudOrLocal(cloudModel)) {
|
||||||
startConnectionListActivity(cloudModel.cloudType())
|
startConnectionListActivity(cloudModel.cloudType())
|
||||||
} else {
|
} else {
|
||||||
if (isLoggedIn(cloudModel)) {
|
if (isLoggedIn(cloudModel)) {
|
||||||
@ -58,8 +61,8 @@ class CloudSettingsPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isWebdavOrLocal(cloudModel: CloudModel): Boolean {
|
private fun isWebdavOrPCloudOrLocal(cloudModel: CloudModel): Boolean {
|
||||||
return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel
|
return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel || cloudModel is PCloudModel
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loginCloud(cloudModel: CloudModel) {
|
private fun loginCloud(cloudModel: CloudModel) {
|
||||||
@ -91,6 +94,7 @@ class CloudSettingsPresenter @Inject constructor( //
|
|||||||
private fun effectiveTitle(cloudTypeModel: CloudTypeModel): String {
|
private fun effectiveTitle(cloudTypeModel: CloudTypeModel): String {
|
||||||
when (cloudTypeModel) {
|
when (cloudTypeModel) {
|
||||||
CloudTypeModel.WEBDAV -> return context().getString(R.string.screen_cloud_settings_webdav_connections)
|
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)
|
CloudTypeModel.LOCAL -> return context().getString(R.string.screen_cloud_settings_local_storage_locations)
|
||||||
}
|
}
|
||||||
return context().getString(R.string.screen_cloud_settings_title)
|
return context().getString(R.string.screen_cloud_settings_title)
|
||||||
@ -123,6 +127,7 @@ class CloudSettingsPresenter @Inject constructor( //
|
|||||||
.toMutableList() //
|
.toMutableList() //
|
||||||
.also {
|
.also {
|
||||||
it.add(aWebdavCloud())
|
it.add(aWebdavCloud())
|
||||||
|
it.add(aPCloud())
|
||||||
it.add(aLocalCloud())
|
it.add(aLocalCloud())
|
||||||
}
|
}
|
||||||
view?.render(cloudModel)
|
view?.render(cloudModel)
|
||||||
@ -132,6 +137,10 @@ class CloudSettingsPresenter @Inject constructor( //
|
|||||||
return WebDavCloudModel(WebDavCloud.aWebDavCloudCloud().build())
|
return WebDavCloudModel(WebDavCloud.aWebDavCloudCloud().build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun aPCloud(): PCloudModel {
|
||||||
|
return PCloudModel(PCloud.aPCloud().build())
|
||||||
|
}
|
||||||
|
|
||||||
private fun aLocalCloud(): CloudModel {
|
private fun aLocalCloud(): CloudModel {
|
||||||
return LocalStorageModel(LocalStorageCloud.aLocalStorage().build())
|
return LocalStorageModel(LocalStorageCloud.aLocalStorage().build())
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ class ChooseCloudServiceActivity : BaseActivity(), ChooseCloudServiceView {
|
|||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createFragment(): Fragment? = ChooseCloudServiceFragment()
|
override fun createFragment(): Fragment = ChooseCloudServiceFragment()
|
||||||
|
|
||||||
override fun getCustomMenuResource(): Int = R.menu.menu_cloud_services
|
override fun getCustomMenuResource(): Int = R.menu.menu_cloud_services
|
||||||
|
|
||||||
|
@ -203,7 +203,17 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou
|
|||||||
|
|
||||||
override fun onImageDeleted(index: Int) {
|
override fun onImageDeleted(index: Int) {
|
||||||
imagePreviewSliderAdapter.deletePage(index)
|
imagePreviewSliderAdapter.deletePage(index)
|
||||||
updateTitle(index)
|
|
||||||
|
presenter.pageIndexes.size.let {
|
||||||
|
when {
|
||||||
|
it == 0 -> {
|
||||||
|
showMessage(getString(R.string.dialog_no_more_images_to_display))
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
it > index -> updateTitle(index)
|
||||||
|
it <= index -> updateTitle(index - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setControlViewVisibility(visibility: Int) {
|
private fun setControlViewVisibility(visibility: Int) {
|
||||||
|
@ -48,7 +48,7 @@ constructor() : RecyclerViewBaseAdapter<VaultModel, BiometricAuthSettingsAdapter
|
|||||||
val vaultModel = getItem(position)
|
val vaultModel = getItem(position)
|
||||||
|
|
||||||
itemView.vaultName.text = vaultModel.name
|
itemView.vaultName.text = vaultModel.name
|
||||||
itemView.cloud.setImageResource(vaultModel.cloudType.cloudImageResource)
|
itemView.cloud.setImageResource(vaultModel.cloudType.vaultImageResource)
|
||||||
|
|
||||||
itemView.toggleBiometricAuth.isChecked = vaultModel.password != null
|
itemView.toggleBiometricAuth.isChecked = vaultModel.password != null
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import org.cryptomator.domain.exception.FatalBackendException
|
|||||||
import org.cryptomator.presentation.R
|
import org.cryptomator.presentation.R
|
||||||
import org.cryptomator.presentation.model.CloudModel
|
import org.cryptomator.presentation.model.CloudModel
|
||||||
import org.cryptomator.presentation.model.LocalStorageModel
|
import org.cryptomator.presentation.model.LocalStorageModel
|
||||||
|
import org.cryptomator.presentation.model.PCloudModel
|
||||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||||
import org.cryptomator.presentation.model.comparator.CloudModelComparator
|
import org.cryptomator.presentation.model.comparator.CloudModelComparator
|
||||||
import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter.CloudConnectionHolder
|
import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter.CloudConnectionHolder
|
||||||
@ -54,6 +55,8 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter<CloudModel, Clo
|
|||||||
|
|
||||||
if (cloudModel is WebDavCloudModel) {
|
if (cloudModel is WebDavCloudModel) {
|
||||||
bindWebDavCloudModel(cloudModel)
|
bindWebDavCloudModel(cloudModel)
|
||||||
|
} else if (cloudModel is PCloudModel) {
|
||||||
|
bindPCloudModel(cloudModel)
|
||||||
} else if (cloudModel is LocalStorageModel) {
|
} else if (cloudModel is LocalStorageModel) {
|
||||||
bindLocalStorageCloudModel(cloudModel)
|
bindLocalStorageCloudModel(cloudModel)
|
||||||
}
|
}
|
||||||
@ -70,6 +73,11 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter<CloudModel, Clo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun bindPCloudModel(cloudModel: PCloudModel) {
|
||||||
|
itemView.cloudText.text = cloudModel.username()
|
||||||
|
itemView.cloudSubText.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
private fun bindLocalStorageCloudModel(cloudModel: LocalStorageModel) {
|
private fun bindLocalStorageCloudModel(cloudModel: LocalStorageModel) {
|
||||||
if (cloudModel.location().isEmpty()) {
|
if (cloudModel.location().isEmpty()) {
|
||||||
itemView.cloudText.text = cloudModel.storage()
|
itemView.cloudText.text = cloudModel.storage()
|
||||||
|
@ -41,6 +41,8 @@ constructor(private val context: Context) : RecyclerViewBaseAdapter<CloudModel,
|
|||||||
|
|
||||||
if (webdav(cloudModel.cloudType())) {
|
if (webdav(cloudModel.cloudType())) {
|
||||||
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_webdav_connections)
|
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_webdav_connections)
|
||||||
|
} else if (pCloud(cloudModel.cloudType())) {
|
||||||
|
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_pcloud_connections)
|
||||||
} else if (local(cloudModel.cloudType())) {
|
} else if (local(cloudModel.cloudType())) {
|
||||||
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_local_storage_locations)
|
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_local_storage_locations)
|
||||||
} else {
|
} else {
|
||||||
@ -79,4 +81,8 @@ constructor(private val context: Context) : RecyclerViewBaseAdapter<CloudModel,
|
|||||||
private fun webdav(cloudType: CloudTypeModel): Boolean {
|
private fun webdav(cloudType: CloudTypeModel): Boolean {
|
||||||
return CloudTypeModel.WEBDAV == cloudType
|
return CloudTypeModel.WEBDAV == cloudType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun pCloud(cloudType: CloudTypeModel): Boolean {
|
||||||
|
return CloudTypeModel.PCLOUD == cloudType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import org.cryptomator.presentation.R
|
|||||||
import org.cryptomator.presentation.model.CloudTypeModel
|
import org.cryptomator.presentation.model.CloudTypeModel
|
||||||
import org.cryptomator.presentation.ui.adapter.CloudsAdapter.CloudViewHolder
|
import org.cryptomator.presentation.ui.adapter.CloudsAdapter.CloudViewHolder
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.android.synthetic.main.item_cloud.view.cloud
|
import kotlinx.android.synthetic.main.item_cloud.view.cloudImage
|
||||||
import kotlinx.android.synthetic.main.item_cloud.view.cloudName
|
import kotlinx.android.synthetic.main.item_cloud.view.cloudName
|
||||||
|
|
||||||
class CloudsAdapter @Inject
|
class CloudsAdapter @Inject
|
||||||
@ -28,10 +28,10 @@ constructor() : RecyclerViewBaseAdapter<CloudTypeModel, CloudsAdapter.OnItemClic
|
|||||||
|
|
||||||
override fun bind(position: Int) {
|
override fun bind(position: Int) {
|
||||||
val cloudTypeModel = getItem(position)
|
val cloudTypeModel = getItem(position)
|
||||||
itemView.cloud.setImageResource(cloudTypeModel.cloudImageLargeResource)
|
itemView.cloudImage.setImageResource(cloudTypeModel.cloudImageResource)
|
||||||
itemView.cloudName.setText(cloudTypeModel.displayNameResource)
|
itemView.cloudName.setText(cloudTypeModel.displayNameResource)
|
||||||
|
|
||||||
itemView.cloud.setOnClickListener { callback.onCloudClicked(cloudTypeModel) }
|
itemView.setOnClickListener { callback.onCloudClicked(cloudTypeModel) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,13 +67,13 @@ constructor() : RecyclerViewBaseAdapter<VaultModel, SharedLocationsAdapter.Callb
|
|||||||
boundVault = getItem(position)
|
boundVault = getItem(position)
|
||||||
|
|
||||||
boundVault?.let {
|
boundVault?.let {
|
||||||
itemView.cloudImage.setImageResource(it.cloudType.cloudImageResource)
|
|
||||||
itemView.vaultName.text = it.name
|
itemView.vaultName.text = it.name
|
||||||
|
|
||||||
val boundVaultSelected = it == selectedVault
|
val boundVaultSelected = it == selectedVault
|
||||||
itemView.selectedVault.isChecked = boundVaultSelected
|
itemView.selectedVault.isChecked = boundVaultSelected
|
||||||
itemView.selectedVault.isClickable = !boundVaultSelected
|
itemView.selectedVault.isClickable = !boundVaultSelected
|
||||||
if (boundVaultSelected) {
|
if (boundVaultSelected) {
|
||||||
|
itemView.cloudImage.setImageResource(it.cloudType.vaultSelectedImageResource)
|
||||||
if (selectedLocation != null) {
|
if (selectedLocation != null) {
|
||||||
itemView.chosenLocation.visibility = View.VISIBLE
|
itemView.chosenLocation.visibility = View.VISIBLE
|
||||||
itemView.chosenLocation.text = selectedLocation
|
itemView.chosenLocation.text = selectedLocation
|
||||||
@ -82,6 +82,7 @@ constructor() : RecyclerViewBaseAdapter<VaultModel, SharedLocationsAdapter.Callb
|
|||||||
}
|
}
|
||||||
itemView.chooseFolderLocation.visibility = View.VISIBLE
|
itemView.chooseFolderLocation.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
|
itemView.cloudImage.setImageResource(it.cloudType.vaultImageResource)
|
||||||
itemView.chosenLocation.visibility = View.GONE
|
itemView.chosenLocation.visibility = View.GONE
|
||||||
itemView.chooseFolderLocation.visibility = View.GONE
|
itemView.chooseFolderLocation.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ internal constructor() : RecyclerViewBaseAdapter<VaultModel, VaultsAdapter.OnIte
|
|||||||
itemView.vaultName.text = vaultModel.name
|
itemView.vaultName.text = vaultModel.name
|
||||||
itemView.vaultPath.text = vaultModel.path
|
itemView.vaultPath.text = vaultModel.path
|
||||||
|
|
||||||
itemView.cloudImage.setImageResource(vaultModel.cloudType.cloudImageResource)
|
itemView.cloudImage.setImageResource(vaultModel.cloudType.vaultImageResource)
|
||||||
|
|
||||||
if (vaultModel.isLocked) {
|
if (vaultModel.isLocked) {
|
||||||
itemView.unlockedImage.visibility = View.GONE
|
itemView.unlockedImage.visibility = View.GONE
|
||||||
@ -68,11 +68,17 @@ internal constructor() : RecyclerViewBaseAdapter<VaultModel, VaultsAdapter.OnIte
|
|||||||
itemView.unlockedImage.visibility = View.VISIBLE
|
itemView.unlockedImage.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.setOnClickListener { callback.onVaultClicked(vaultModel) }
|
itemView.setOnClickListener {
|
||||||
|
itemView.cloudImage.setImageResource(vaultModel.cloudType.vaultSelectedImageResource)
|
||||||
|
callback.onVaultClicked(vaultModel)
|
||||||
|
}
|
||||||
|
|
||||||
itemView.unlockedImage.setOnClickListener { callback.onVaultLockClicked(vaultModel) }
|
itemView.unlockedImage.setOnClickListener { callback.onVaultLockClicked(vaultModel) }
|
||||||
|
|
||||||
itemView.settings.setOnClickListener { callback.onVaultSettingsClicked(vaultModel) }
|
itemView.settings.setOnClickListener {
|
||||||
|
itemView.cloudImage.setImageResource(vaultModel.cloudType.vaultSelectedImageResource)
|
||||||
|
callback.onVaultSettingsClicked(vaultModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import org.cryptomator.presentation.R
|
|||||||
import org.cryptomator.presentation.model.CloudModel
|
import org.cryptomator.presentation.model.CloudModel
|
||||||
import org.cryptomator.presentation.model.CloudTypeModel
|
import org.cryptomator.presentation.model.CloudTypeModel
|
||||||
import org.cryptomator.presentation.model.LocalStorageModel
|
import org.cryptomator.presentation.model.LocalStorageModel
|
||||||
|
import org.cryptomator.presentation.model.PCloudModel
|
||||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||||
import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.change_cloud
|
import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.change_cloud
|
||||||
import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.delete_cloud
|
import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.delete_cloud
|
||||||
@ -28,6 +29,7 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet<CloudConnectionSettin
|
|||||||
|
|
||||||
when (cloudModel.cloudType()) {
|
when (cloudModel.cloudType()) {
|
||||||
CloudTypeModel.WEBDAV -> bindViewForWebDAV(cloudModel as WebDavCloudModel)
|
CloudTypeModel.WEBDAV -> bindViewForWebDAV(cloudModel as WebDavCloudModel)
|
||||||
|
CloudTypeModel.PCLOUD -> bindViewForPCloud(cloudModel as PCloudModel)
|
||||||
CloudTypeModel.LOCAL -> bindViewForLocal(cloudModel as LocalStorageModel)
|
CloudTypeModel.LOCAL -> bindViewForLocal(cloudModel as LocalStorageModel)
|
||||||
else -> throw IllegalStateException("Cloud model is not binded in the view")
|
else -> throw IllegalStateException("Cloud model is not binded in the view")
|
||||||
}
|
}
|
||||||
@ -59,6 +61,11 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet<CloudConnectionSettin
|
|||||||
tv_cloud_subtext.text = cloudModel.username()
|
tv_cloud_subtext.text = cloudModel.username()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun bindViewForPCloud(cloudModel: PCloudModel) {
|
||||||
|
change_cloud.visibility = View.GONE
|
||||||
|
tv_cloud_name.text = cloudModel.username()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val CLOUD_NODE_ARG = "cloudModel"
|
private const val CLOUD_NODE_ARG = "cloudModel"
|
||||||
|
@ -31,7 +31,7 @@ class SettingsVaultBottomSheet : BaseBottomSheet<SettingsVaultBottomSheet.Callba
|
|||||||
lock_vault.visibility = LinearLayout.GONE
|
lock_vault.visibility = LinearLayout.GONE
|
||||||
}
|
}
|
||||||
val cloudType = vaultModel.cloudType
|
val cloudType = vaultModel.cloudType
|
||||||
cloud_image.setImageResource(cloudType.cloudImageResource)
|
cloud_image.setImageResource(cloudType.vaultSelectedImageResource)
|
||||||
vault_name.text = vaultModel.name
|
vault_name.text = vaultModel.name
|
||||||
vault_path.text = vaultModel.path
|
vault_path.text = vaultModel.path
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.cryptomator.presentation.ui.fragment
|
package org.cryptomator.presentation.ui.fragment
|
||||||
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import org.cryptomator.generator.Fragment
|
import org.cryptomator.generator.Fragment
|
||||||
import org.cryptomator.presentation.R
|
import org.cryptomator.presentation.R
|
||||||
import org.cryptomator.presentation.model.CloudTypeModel
|
import org.cryptomator.presentation.model.CloudTypeModel
|
||||||
@ -36,7 +36,7 @@ class ChooseCloudServiceFragment : BaseFragment() {
|
|||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
cloudsAdapter.setCallback(onItemClickListener)
|
cloudsAdapter.setCallback(onItemClickListener)
|
||||||
recyclerView.layoutManager = GridLayoutManager(context(), 2)
|
recyclerView.layoutManager = LinearLayoutManager(context())
|
||||||
recyclerView.adapter = cloudsAdapter
|
recyclerView.adapter = cloudsAdapter
|
||||||
// smoother scrolling
|
// smoother scrolling
|
||||||
recyclerView.setHasFixedSize(true)
|
recyclerView.setHasFixedSize(true)
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 9.2 KiB |
BIN
presentation/src/main/res/drawable-mdpi/dropbox.png
Normal file
After Width: | Height: | Size: 516 B |
Before Width: | Height: | Size: 765 B After Width: | Height: | Size: 765 B |
After Width: | Height: | Size: 489 B |
BIN
presentation/src/main/res/drawable-mdpi/google_drive.png
Normal file
After Width: | Height: | Size: 962 B |
Before Width: | Height: | Size: 849 B After Width: | Height: | Size: 849 B |
After Width: | Height: | Size: 518 B |
BIN
presentation/src/main/res/drawable-mdpi/local_fs.png
Normal file
After Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 726 B After Width: | Height: | Size: 726 B |
After Width: | Height: | Size: 459 B |
BIN
presentation/src/main/res/drawable-mdpi/onedrive.png
Normal file
After Width: | Height: | Size: 510 B |
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 936 B |
After Width: | Height: | Size: 564 B |
BIN
presentation/src/main/res/drawable-mdpi/pcloud.png
Normal file
After Width: | Height: | Size: 525 B |
BIN
presentation/src/main/res/drawable-mdpi/pcloud_vault.png
Normal file
After Width: | Height: | Size: 912 B |
After Width: | Height: | Size: 551 B |
Before Width: | Height: | Size: 1.9 KiB |
BIN
presentation/src/main/res/drawable-mdpi/webdav.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 651 B |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 26 KiB |
BIN
presentation/src/main/res/drawable-xhdpi/dropbox.png
Normal file
After Width: | Height: | Size: 998 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 883 B |
BIN
presentation/src/main/res/drawable-xhdpi/google_drive.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1000 B |
BIN
presentation/src/main/res/drawable-xhdpi/local_fs.png
Normal file
After Width: | Height: | Size: 541 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 848 B |
BIN
presentation/src/main/res/drawable-xhdpi/onedrive.png
Normal file
After Width: | Height: | Size: 890 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
presentation/src/main/res/drawable-xhdpi/pcloud.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
presentation/src/main/res/drawable-xhdpi/pcloud_vault.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |