Merge branch 'develop' into feature/150-office-apps-writable
This commit is contained in:
commit
a04b5f9e77
12
.github/stale.yml
vendored
12
.github/stale.yml
vendored
@ -1,10 +1,16 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
daysUntilStale: 365
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
daysUntilClose: 90
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- type:security-issue
|
||||
- type:security-issue # never close automatically
|
||||
- type:feature-request # never close automatically
|
||||
- type:enhancement # never close automatically
|
||||
- type:upstream-bug # never close automatically
|
||||
- state:awaiting-response # handled by different bot
|
||||
- state:blocked
|
||||
- state:confirmed
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
# Label to use when marking an issue as stale
|
||||
|
46
Gemfile.lock
46
Gemfile.lock
@ -10,20 +10,20 @@ GEM
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.554.0)
|
||||
aws-sdk-core (3.126.1)
|
||||
aws-partitions (1.587.0)
|
||||
aws-sdk-core (3.130.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.54.0)
|
||||
aws-sdk-core (~> 3, >= 3.126.0)
|
||||
aws-sdk-kms (1.56.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.112.0)
|
||||
aws-sdk-core (~> 3, >= 3.126.0)
|
||||
aws-sdk-s3 (1.114.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
aws-sigv4 (1.5.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
bcrypt_pbkdf (1.1.0)
|
||||
@ -40,8 +40,8 @@ GEM
|
||||
dotenv (2.7.6)
|
||||
ed25519 (1.3.0)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.91.0)
|
||||
faraday (1.9.3)
|
||||
excon (0.92.3)
|
||||
faraday (1.10.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@ -70,7 +70,7 @@ GEM
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.204.3)
|
||||
fastlane (2.205.2)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@ -109,13 +109,13 @@ GEM
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
fastlane-plugin-aws_s3 (2.0.3)
|
||||
fastlane-plugin-aws_s3 (2.1.0)
|
||||
apktools (~> 0.7)
|
||||
aws-sdk-s3 (~> 1)
|
||||
mime-types (~> 3.3)
|
||||
fastlane-plugin-get_version_name (0.2.2)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.16.0)
|
||||
google-apis-androidpublisher_v3 (0.20.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.4.2)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
@ -130,15 +130,15 @@ GEM
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.7.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.11.0)
|
||||
google-apis-storage_v1 (0.13.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.36.1)
|
||||
google-cloud-storage (1.36.2)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
@ -146,8 +146,8 @@ GEM
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.1.1)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
googleauth (1.1.3)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
@ -157,7 +157,7 @@ GEM
|
||||
http-cookie (1.0.4)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.0)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.1)
|
||||
jwt (2.3.0)
|
||||
memoist (0.16.2)
|
||||
@ -176,7 +176,7 @@ GEM
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.6)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.0.6)
|
||||
representable (3.1.1)
|
||||
declarative (< 0.1.0)
|
||||
@ -188,9 +188,9 @@ GEM
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.16.0)
|
||||
signet (0.16.1)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
faraday (>= 0.17.5, < 3.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
@ -207,7 +207,7 @@ GEM
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8)
|
||||
unf_ext (0.0.8.1)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
|
@ -29,11 +29,13 @@ git submodule init && git submodule update // (not necessary if cloned using --r
|
||||
./gradlew assembleApkstoreDebug
|
||||
```
|
||||
|
||||
Before connecting to OneDrive or Dropbox you have to provide valid API keys using environment variables:
|
||||
Before connecting to Dropbox, OneDrive or pCloud you have to provide valid API keys using environment variables:
|
||||
For build type
|
||||
|
||||
* **release**: `DROPBOX_API_KEY` or `ONEDRIVE_API_KEY` and `ONEDRIVE_API_REDIRCT_URI`
|
||||
* **debug**: `DROPBOX_API_KEY_DEBUG` or `ONEDRIVE_API_KEY_DEBUG` and `ONEDRIVE_API_REDIRCT_URI_DEBUG`
|
||||
* **release**: `DROPBOX_API_KEY`, `ONEDRIVE_API_KEY` and `ONEDRIVE_API_REDIRCT_URI` or `PCLOUD_CLIENT_ID`
|
||||
* **debug**: `DROPBOX_API_KEY_DEBUG`, `ONEDRIVE_API_KEY_DEBUG` and `ONEDRIVE_API_REDIRCT_URI_DEBUG` or `PCLOUD_CLIENT_ID_DEBUG`
|
||||
|
||||
Before connecting to Google Drive you have to create a new project in [Google Cloud Platform](https://console.cloud.google.com) with Google Drive API, credentials including Google Drive scopes (read, write, delete,..) and the fingerprint of the key you use to build the app.
|
||||
|
||||
## Contributing to Cryptomator for Android
|
||||
|
||||
|
@ -2,13 +2,13 @@ apply from: 'buildsystem/dependencies.gradle'
|
||||
apply plugin: "com.vanniktech.android.junit.jacoco"
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
ext.kotlin_version = '1.6.21'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.1'
|
||||
classpath 'com.android.tools.build:gradle:7.2.1'
|
||||
classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0'
|
||||
classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.16.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
@ -39,7 +39,7 @@ allprojects {
|
||||
ext {
|
||||
androidApplicationId = 'org.cryptomator'
|
||||
androidVersionCode = getVersionCode()
|
||||
androidVersionName = '1.7.0-SNAPSHOT'
|
||||
androidVersionName = '1.8.0-SNAPSHOT'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -21,8 +21,8 @@ allprojects {
|
||||
ext {
|
||||
androidBuildToolsVersion = "30.0.3"
|
||||
androidMinSdkVersion = 26
|
||||
androidTargetSdkVersion = 30
|
||||
androidCompileSdkVersion = 30
|
||||
androidTargetSdkVersion = 31
|
||||
androidCompileSdkVersion = 31
|
||||
|
||||
// android and java libs
|
||||
androidVersion = '4.1.1.4'
|
||||
@ -30,9 +30,9 @@ ext {
|
||||
javaxAnnotationVersion = '1.0'
|
||||
|
||||
// support lib
|
||||
androidSupportAnnotationsVersion = '1.2.0'
|
||||
androidSupportAppcompatVersion = '1.3.1'
|
||||
androidSupportDesignVersion = '1.4.0'
|
||||
androidSupportAnnotationsVersion = '1.3.0'
|
||||
androidSupportAppcompatVersion = '1.4.1'
|
||||
androidMaterialDesignVersion = '1.6.0'
|
||||
|
||||
coreDesugaringVersion = '1.1.5'
|
||||
|
||||
@ -42,18 +42,18 @@ ext {
|
||||
rxAndroidVersion = '2.1.1'
|
||||
rxBindingVersion = '2.2.0'
|
||||
|
||||
daggerVersion = '2.40.5'
|
||||
daggerVersion = '2.42'
|
||||
|
||||
gsonVersion = '2.9.0'
|
||||
|
||||
okHttpVersion = '4.9.3'
|
||||
okHttpDigestVersion = '2.6'
|
||||
okHttpDigestVersion = '2.7'
|
||||
|
||||
velocityVersion = '2.3'
|
||||
|
||||
timberVersion = '5.0.1'
|
||||
|
||||
zxcvbnVersion = '1.5.2'
|
||||
zxcvbnVersion = '1.7.0'
|
||||
|
||||
scaleImageViewVersion = '3.10.0'
|
||||
|
||||
@ -65,21 +65,21 @@ ext {
|
||||
// cloud provider libs
|
||||
cryptolibVersion = '2.0.2'
|
||||
|
||||
dropboxVersion = '5.1.1'
|
||||
dropboxVersion = '5.2.0'
|
||||
|
||||
googleApiServicesVersion = 'v3-rev20220110-1.32.1'
|
||||
googleApiServicesVersion = 'v3-rev20220508-1.32.1'
|
||||
googlePlayServicesVersion = '19.2.0'
|
||||
googleClientVersion = '1.33.2' // keep in sync with https://github.com/SailReal/google-http-java-client
|
||||
googleClientVersion = '1.35.0' // keep in sync with https://github.com/SailReal/google-http-java-client
|
||||
/*
|
||||
update using https://github.com/SailReal/google-http-java-client with `mvn clean install`,
|
||||
copying `google-http-client-*.jar` and `google-http-client-android-*.jar` into the lib folder of this project
|
||||
*/
|
||||
trackingFreeGoogleCLientVersion = '1.41.4'
|
||||
trackingFreeGoogleCLientVersion = '1.41.8'
|
||||
|
||||
msgraphVersion = '5.14.0'
|
||||
msgraphAuthVersion = '2.2.3'
|
||||
msgraphVersion = '5.25.0'
|
||||
msgraphAuthVersion = '3.0.2'
|
||||
|
||||
minIoVersion = '8.3.6'
|
||||
minIoVersion = '8.4.1'
|
||||
staxVersion = '1.2.0' // needed for minIO
|
||||
|
||||
commonsCodecVersion = '1.15'
|
||||
@ -90,7 +90,7 @@ ext {
|
||||
|
||||
jUnitVersion = '5.8.2'
|
||||
assertJVersion = '1.7.1'
|
||||
mockitoVersion = '4.3.1'
|
||||
mockitoVersion = '4.6.1'
|
||||
mockitoKotlinVersion = '4.0.0'
|
||||
hamcrestVersion = '1.3'
|
||||
dexmakerVersion = '1.0'
|
||||
@ -100,18 +100,20 @@ ext {
|
||||
rulesVersion = '1.4.0'
|
||||
contributionVersion = '3.4.0'
|
||||
uiautomatorVersion = '2.2.0'
|
||||
androidxTestJunitKtlnVersion = '1.1.3'
|
||||
|
||||
androidxCoreVersion = '1.6.0'
|
||||
androidxFragmentVersion = '1.3.6'
|
||||
androidxCoreVersion = '1.7.0'
|
||||
androidxFragmentVersion = '1.4.1'
|
||||
androidxViewpagerVersion = '1.0.0'
|
||||
androidxSwiperefreshVersion = '1.1.0'
|
||||
androidxPreferenceVersion = '1.1.1'
|
||||
androidxPreferenceVersion = '1.2.0'
|
||||
androidxRecyclerViewVersion = '1.2.1'
|
||||
androidxDocumentfileVersion = '1.0.1'
|
||||
androidxBiometricVersion = '1.1.0'
|
||||
androidxTestCoreVersion = '1.4.0'
|
||||
androidxSplashscreenVersion = '1.0.0-rc01'
|
||||
|
||||
jsonWebTokenApiVersion = '0.11.2'
|
||||
jsonWebTokenApiVersion = '0.11.5'
|
||||
|
||||
dependencies = [
|
||||
android : "com.google.android:android:${androidVersion}",
|
||||
@ -125,12 +127,14 @@ ext {
|
||||
androidxPreference : "androidx.preference:preference:${androidxPreferenceVersion}",
|
||||
documentFile : "androidx.documentfile:documentfile:${androidxDocumentfileVersion}",
|
||||
recyclerView : "androidx.recyclerview:recyclerview:${androidxRecyclerViewVersion}",
|
||||
androidxSplashscreen : "androidx.core:core-splashscreen:${androidxSplashscreenVersion}",
|
||||
androidxTestCore : "androidx.test:core:${androidxTestCoreVersion}",
|
||||
androidxTestJunitKtln : "androidx.test.ext:junit-ktx:${androidxTestJunitKtlnVersion}",
|
||||
commonsCodec : "commons-codec:commons-codec:${commonsCodecVersion}",
|
||||
cryptolib : "org.cryptomator:cryptolib:${cryptolibVersion}",
|
||||
dagger : "com.google.dagger:dagger:${daggerVersion}",
|
||||
daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}",
|
||||
design : "com.google.android.material:material:${androidSupportDesignVersion}",
|
||||
design : "com.google.android.material:material:${androidMaterialDesignVersion}",
|
||||
coreDesugaring : "com.android.tools:desugar_jdk_libs:${coreDesugaringVersion}",
|
||||
dropbox : "com.dropbox.core:dropbox-core-sdk:${dropboxVersion}",
|
||||
espresso : "androidx.test.espresso:espresso-core:${espressoVersion}",
|
||||
|
@ -53,19 +53,27 @@ android {
|
||||
fdroid {
|
||||
dimension "version"
|
||||
}
|
||||
|
||||
lite {
|
||||
dimension "version"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
playstore {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||
}
|
||||
|
||||
apkstore {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||
}
|
||||
|
||||
fdroid {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/foss/java', 'src/foss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/fdroid/java/']
|
||||
}
|
||||
|
||||
lite {
|
||||
java.srcDirs = ['src/main/java/', 'src/lite/java/']
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
@ -82,7 +90,7 @@ android {
|
||||
}
|
||||
|
||||
greendao {
|
||||
schemaVersion 11
|
||||
schemaVersion 12
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
@ -95,7 +103,9 @@ dependencies {
|
||||
|
||||
implementation project(':domain')
|
||||
implementation project(':util')
|
||||
implementation project(':pcloud-sdk-java')
|
||||
playstoreImplementation project(':pcloud-sdk-java')
|
||||
apkstoreImplementation project(':pcloud-sdk-java')
|
||||
fdroidImplementation project(':pcloud-sdk-java')
|
||||
|
||||
coreLibraryDesugaring dependencies.coreDesugaring
|
||||
|
||||
@ -113,9 +123,16 @@ dependencies {
|
||||
implementation dependencies.jsonWebTokenJson
|
||||
|
||||
// cloud
|
||||
implementation dependencies.dropbox
|
||||
implementation dependencies.msgraphAuth
|
||||
implementation dependencies.msgraph
|
||||
playstoreImplementation dependencies.dropbox
|
||||
apkstoreImplementation dependencies.dropbox
|
||||
fdroidImplementation dependencies.dropbox
|
||||
|
||||
playstoreImplementation dependencies.msgraphAuth
|
||||
apkstoreImplementation dependencies.msgraphAuth
|
||||
fdroidImplementation dependencies.msgraphAuth
|
||||
playstoreImplementation dependencies.msgraph
|
||||
apkstoreImplementation dependencies.msgraph
|
||||
fdroidImplementation dependencies.msgraph
|
||||
|
||||
implementation dependencies.stax
|
||||
api dependencies.minIo
|
||||
@ -191,6 +208,7 @@ dependencies {
|
||||
testRuntimeOnly dependencies.junitEngine
|
||||
testImplementation dependencies.junitParams
|
||||
testRuntimeOnly dependencies.junit4Engine
|
||||
implementation dependencies.androidxTestJunitKtln
|
||||
|
||||
testImplementation dependencies.mockito
|
||||
testImplementation dependencies.mockitoKotlin
|
||||
|
@ -2,9 +2,10 @@ package org.cryptomator.data.db
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.base.Optional
|
||||
import org.cryptomator.data.db.entities.CloudEntityDao
|
||||
import org.cryptomator.data.db.entities.UpdateCheckEntityDao
|
||||
import org.cryptomator.data.db.entities.VaultEntityDao
|
||||
@ -25,7 +26,7 @@ import org.junit.runner.RunWith
|
||||
@SmallTest
|
||||
class UpgradeDatabaseTest {
|
||||
|
||||
private val context = InstrumentationRegistry.getTargetContext()
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
private val sharedPreferencesHandler = SharedPreferencesHandler(context)
|
||||
private lateinit var db: Database
|
||||
|
||||
@ -580,4 +581,66 @@ class UpgradeDatabaseTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun upgrade11To12IfOldDefaultSet() {
|
||||
Upgrade0To1().applyTo(db, 0)
|
||||
Upgrade1To2().applyTo(db, 1)
|
||||
Upgrade2To3(context).applyTo(db, 2)
|
||||
Upgrade3To4().applyTo(db, 3)
|
||||
Upgrade4To5().applyTo(db, 4)
|
||||
Upgrade5To6().applyTo(db, 5)
|
||||
Upgrade6To7().applyTo(db, 6)
|
||||
Upgrade7To8().applyTo(db, 7)
|
||||
Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8)
|
||||
Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9)
|
||||
Upgrade10To11().applyTo(db, 10)
|
||||
|
||||
sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(7))
|
||||
|
||||
Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11)
|
||||
|
||||
Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.of(1)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun upgrade11To12MonthlySet() {
|
||||
Upgrade0To1().applyTo(db, 0)
|
||||
Upgrade1To2().applyTo(db, 1)
|
||||
Upgrade2To3(context).applyTo(db, 2)
|
||||
Upgrade3To4().applyTo(db, 3)
|
||||
Upgrade4To5().applyTo(db, 4)
|
||||
Upgrade5To6().applyTo(db, 5)
|
||||
Upgrade6To7().applyTo(db, 6)
|
||||
Upgrade7To8().applyTo(db, 7)
|
||||
Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8)
|
||||
Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9)
|
||||
Upgrade10To11().applyTo(db, 10)
|
||||
|
||||
sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(30))
|
||||
|
||||
Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11)
|
||||
|
||||
Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.of(1)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun upgrade11To12MonthlyNever() {
|
||||
Upgrade0To1().applyTo(db, 0)
|
||||
Upgrade1To2().applyTo(db, 1)
|
||||
Upgrade2To3(context).applyTo(db, 2)
|
||||
Upgrade3To4().applyTo(db, 3)
|
||||
Upgrade4To5().applyTo(db, 4)
|
||||
Upgrade5To6().applyTo(db, 5)
|
||||
Upgrade6To7().applyTo(db, 6)
|
||||
Upgrade7To8().applyTo(db, 7)
|
||||
Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8)
|
||||
Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9)
|
||||
Upgrade10To11().applyTo(db, 10)
|
||||
|
||||
sharedPreferencesHandler.setUpdateIntervalInDays(Optional.absent())
|
||||
|
||||
Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11)
|
||||
|
||||
Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.absent()))
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import com.microsoft.graph.httpcore.HttpClients
|
||||
import com.microsoft.graph.requests.GraphServiceClient
|
||||
import org.cryptomator.data.cloud.okhttplogging.HttpLoggingInterceptor
|
||||
import org.cryptomator.data.util.NetworkTimeout
|
||||
import org.cryptomator.util.SharedPreferencesHandler
|
||||
import org.cryptomator.util.crypto.CredentialCryptor
|
||||
import java.net.URL
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@ -19,7 +18,7 @@ class OnedriveClientFactory private constructor() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun createInstance(context: Context, encryptedToken: String, sharedPreferencesHandler: SharedPreferencesHandler): GraphServiceClient<Request> {
|
||||
fun createInstance(context: Context, encryptedToken: String): GraphServiceClient<Request> {
|
||||
val tokenAuthenticationProvider = object : BaseAuthenticationProvider() {
|
||||
val token = CompletableFuture.completedFuture(CredentialCryptor.getInstance(context).decrypt(encryptedToken))
|
||||
override fun getAuthorizationTokenAsync(requestUrl: URL): CompletableFuture<String> {
|
@ -4,7 +4,7 @@ import android.content.Context
|
||||
import com.microsoft.graph.core.GraphErrorCodes
|
||||
import com.microsoft.graph.http.GraphServiceException
|
||||
import com.microsoft.graph.requests.GraphServiceClient
|
||||
import com.microsoft.identity.common.exception.ClientException
|
||||
import com.microsoft.identity.common.java.exception.ClientException
|
||||
import org.cryptomator.data.cloud.InterceptingCloudContentRepository
|
||||
import org.cryptomator.domain.OnedriveCloud
|
||||
import org.cryptomator.domain.exception.BackendException
|
@ -33,6 +33,6 @@ public class OnedriveCloudContentRepositoryFactory implements CloudContentReposi
|
||||
@Override
|
||||
public CloudContentRepository<OnedriveCloud, OnedriveNode, OnedriveFolder, OnedriveFile> cloudContentRepositoryFor(Cloud cloud) {
|
||||
OnedriveCloud onedriveCloud = (OnedriveCloud) cloud;
|
||||
return new OnedriveCloudContentRepository(onedriveCloud, context, OnedriveClientFactory.Companion.createInstance(context, onedriveCloud.accessToken(), sharedPreferencesHandler));
|
||||
return new OnedriveCloudContentRepository(onedriveCloud, context, OnedriveClientFactory.Companion.createInstance(context, onedriveCloud.accessToken()));
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package org.cryptomator.data.cloud.onedrive
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.microsoft.graph.http.GraphServiceException
|
||||
import com.microsoft.graph.models.DriveItem
|
||||
import com.microsoft.graph.models.DriveItemCreateUploadSessionParameterSet
|
||||
@ -86,7 +85,7 @@ internal class OnedriveImpl(cloud: OnedriveCloud, context: Context, graphService
|
||||
|
||||
private fun childByName(parentId: String, parentDriveId: String, name: String): DriveItem? {
|
||||
return try {
|
||||
drive(parentDriveId).items(parentId).itemWithPath(Uri.encode(name)).buildRequest().get()
|
||||
drive(parentDriveId).items(parentId).itemWithPath(name).buildRequest().get()
|
||||
} catch (e: GraphServiceException) {
|
||||
if (isNotFoundError(e)) {
|
||||
null
|
@ -1,5 +1,7 @@
|
||||
package org.cryptomator.data.cloud;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory;
|
||||
@ -16,8 +18,6 @@ import java.util.Iterator;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@Singleton
|
||||
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {
|
||||
|
@ -56,7 +56,7 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun findFile(parentDriveId: String?, name: String): File? {
|
||||
val fileListQuery = client().files().list().setFields("files(id,mimeType,name,size,shortcutDetails)")
|
||||
val fileListQuery = client().files().list().setFields("files(id,mimeType,name,size,shortcutDetails)").setSupportsAllDrives(true).setIncludeItemsFromAllDrives(true)
|
||||
fileListQuery.q = "name contains '$name' and '$parentDriveId' in parents and trashed = false"
|
||||
return fileListQuery.execute().files.firstOrNull { it.name == name }
|
||||
}
|
||||
@ -131,6 +131,8 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl
|
||||
.setFields("nextPageToken,files(id,mimeType,modifiedTime,name,size,shortcutDetails)") //
|
||||
.setPageSize(1000) //
|
||||
.setPageToken(pageToken)
|
||||
.setSupportsAllDrives(true)
|
||||
.setIncludeItemsFromAllDrives(true)
|
||||
fileListQuery.q = "'" + folder.driveId + "' in parents and trashed = false"
|
||||
val fileList = fileListQuery.execute()
|
||||
for (file in fileList.files) {
|
||||
@ -160,6 +162,7 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl
|
||||
.files() //
|
||||
.create(metadata) //
|
||||
.setFields("id,name") //
|
||||
.setSupportsAllDrives(true) //
|
||||
.execute()
|
||||
return idCache.cache(GoogleDriveCloudNodeFactory.folder(parentFolder, createdFolder))
|
||||
} ?: throw ParentFolderIsNullException(folder.name)
|
||||
@ -181,6 +184,7 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl
|
||||
.setFields("id,mimeType,modifiedTime,name,size") //
|
||||
.setAddParents(targetsParent.driveId) //
|
||||
.setRemoveParents(sourcesParent.driveId) //
|
||||
.setSupportsAllDrives(true) //
|
||||
.execute()
|
||||
idCache.remove(source)
|
||||
return idCache.cache(GoogleDriveCloudNodeFactory.from(targetsParent, movedFile))
|
||||
@ -224,6 +228,7 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl
|
||||
.files() //
|
||||
.update(file.driveId, metadata, it) //
|
||||
.setFields("id,modifiedTime,name,size") //
|
||||
.setSupportsAllDrives(true) //
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
@ -246,6 +251,7 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl
|
||||
.files() //
|
||||
.create(metadata, it) //
|
||||
.setFields("id,modifiedTime,name,size") //
|
||||
.setSupportsAllDrives(true) //
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
@ -316,6 +322,7 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl
|
||||
client() //
|
||||
.files()[file.driveId] //
|
||||
.setAlt("media") //
|
||||
.setSupportsAllDrives(true) //
|
||||
.executeMediaAndDownloadTo(it)
|
||||
}
|
||||
} catch (e: HttpResponseException) {
|
||||
@ -373,7 +380,7 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun delete(node: GoogleDriveNode) {
|
||||
client().files().delete(node.driveId).execute()
|
||||
client().files().delete(node.driveId).setSupportsAllDrives(true).execute()
|
||||
idCache.remove(node)
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
package org.cryptomator.data.cloud;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.s3.S3CloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {
|
||||
|
||||
private final Iterable<CloudContentRepositoryFactory> factories;
|
||||
|
||||
@Inject
|
||||
public CloudContentRepositoryFactories(
|
||||
S3CloudContentRepositoryFactory s3Factory, //
|
||||
CryptoCloudContentRepositoryFactory cryptoFactory, //
|
||||
LocalStorageContentRepositoryFactory localStorageFactory, //
|
||||
WebDavCloudContentRepositoryFactory webDavFactory) {
|
||||
|
||||
factories = asList(s3Factory, //
|
||||
cryptoFactory, //
|
||||
localStorageFactory, //
|
||||
webDavFactory);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<CloudContentRepositoryFactory> iterator() {
|
||||
return factories.iterator();
|
||||
}
|
||||
}
|
@ -85,10 +85,13 @@ abstract class CryptoImplDecorator(
|
||||
abstract fun write(cryptoFile: CryptoFile, data: DataSource, progressAware: ProgressAware<UploadState>, replace: Boolean, length: Long): CryptoFile
|
||||
|
||||
@Throws(BackendException::class, EmptyDirFileException::class)
|
||||
abstract fun loadDirId(folder: CryptoFolder): String
|
||||
abstract fun loadDirId(folder: CryptoFolder): String?
|
||||
|
||||
@Throws(BackendException::class)
|
||||
abstract fun createDirIdInfo(folder: CryptoFolder): DirIdInfo
|
||||
abstract fun getOrCreateDirIdInfo(folder: CryptoFolder): DirIdInfo
|
||||
|
||||
@Throws(BackendException::class)
|
||||
abstract fun getDirIdInfo(folder: CryptoFolder): DirIdInfo?
|
||||
|
||||
private fun dirHash(directoryId: String): String {
|
||||
return cryptor().fileNameCryptor().hashDirectoryId(directoryId)
|
||||
@ -162,7 +165,7 @@ abstract class CryptoImplDecorator(
|
||||
@Throws(BackendException::class)
|
||||
private fun file(cryptoParent: CryptoFolder, cleartextName: String, ciphertextName: String, cleartextSize: Long?): CryptoFile {
|
||||
val ciphertextSize = cleartextSize?.let { cryptor().fileContentCryptor().ciphertextSize(it) + cryptor().fileHeaderCryptor().headerSize() }
|
||||
val cloudFile = cloudContentRepository.file(dirIdInfo(cryptoParent).cloudFolder, ciphertextName, ciphertextSize)
|
||||
val cloudFile = cloudContentRepository.file(getOrCreateCachingAwareDirIdInfo(cryptoParent).cloudFolder, ciphertextName, ciphertextSize)
|
||||
return file(cryptoParent, cleartextName, cloudFile, cleartextSize)
|
||||
}
|
||||
|
||||
@ -212,7 +215,9 @@ abstract class CryptoImplDecorator(
|
||||
@Throws(BackendException::class)
|
||||
private fun exists(folder: CryptoFolder): Boolean {
|
||||
requireNotNull(folder.dirFile)
|
||||
return cloudContentRepository.exists(folder.dirFile) && cloudContentRepository.exists(dirIdInfo(folder).cloudFolder)
|
||||
return cloudContentRepository.exists(folder.dirFile) && getCachingAwareDirIdInfo(folder)?.let {
|
||||
cloudContentRepository.exists(it.cloudFolder)
|
||||
} ?: false
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
@ -352,8 +357,13 @@ abstract class CryptoImplDecorator(
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
fun dirIdInfo(folder: CryptoFolder): DirIdInfo {
|
||||
return dirIdCache[folder] ?: return createDirIdInfo(folder)
|
||||
fun getOrCreateCachingAwareDirIdInfo(folder: CryptoFolder): DirIdInfo {
|
||||
return dirIdCache[folder] ?: return getOrCreateDirIdInfo(folder)
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
fun getCachingAwareDirIdInfo(folder: CryptoFolder): DirIdInfo? {
|
||||
return dirIdCache[folder] ?: return getDirIdInfo(folder)
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
|
@ -64,7 +64,7 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
@Throws(BackendException::class)
|
||||
override fun folder(cryptoParent: CryptoFolder, cleartextName: String): CryptoFolder {
|
||||
val dirFileName = encryptFolderName(cryptoParent, cleartextName)
|
||||
val dirFolder = cloudContentRepository.folder(dirIdInfo(cryptoParent).cloudFolder, dirFileName)
|
||||
val dirFolder = cloudContentRepository.folder(getOrCreateCachingAwareDirIdInfo(cryptoParent).cloudFolder, dirFileName)
|
||||
val dirFile = cloudContentRepository.file(dirFolder, CLOUD_FOLDER_DIR_FILE_PRE + CLOUD_NODE_EXT)
|
||||
return folder(cryptoParent, cleartextName, dirFile)
|
||||
}
|
||||
@ -73,7 +73,7 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
override fun encryptName(cryptoParent: CryptoFolder, name: String): String {
|
||||
var ciphertextName: String = cryptor() //
|
||||
.fileNameCryptor() //
|
||||
.encryptFilename(BaseEncoding.base64Url(), name, dirIdInfo(cryptoParent).id.toByteArray(StandardCharsets.UTF_8)) + CLOUD_NODE_EXT
|
||||
.encryptFilename(BaseEncoding.base64Url(), name, getOrCreateCachingAwareDirIdInfo(cryptoParent).id.toByteArray(StandardCharsets.UTF_8)) + CLOUD_NODE_EXT
|
||||
if (ciphertextName.length > shorteningThreshold) {
|
||||
ciphertextName = deflate(cryptoParent, ciphertextName)
|
||||
}
|
||||
@ -85,7 +85,7 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
val longFilenameBytes = longFileName.toByteArray(StandardCharsets.UTF_8)
|
||||
val hash = MessageDigestSupplier.SHA1.get().digest(longFilenameBytes)
|
||||
val shortFileName = BaseEncoding.base64Url().encode(hash) + LONG_NODE_FILE_EXT
|
||||
var dirFolder = cloudContentRepository.folder(dirIdInfo(cryptoParent).cloudFolder, shortFileName)
|
||||
var dirFolder = cloudContentRepository.folder(getOrCreateCachingAwareDirIdInfo(cryptoParent).cloudFolder, shortFileName)
|
||||
|
||||
// if folder already exists in case of renaming
|
||||
if (!cloudContentRepository.exists(dirFolder)) {
|
||||
@ -98,6 +98,14 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
return shortFileName
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
private fun inflate(cloudNode: CloudNode): String {
|
||||
val metadataFile = metadataFile(cloudNode)
|
||||
val out = ByteArrayOutputStream()
|
||||
cloudContentRepository.read(metadataFile, null, out, ProgressAware.NO_OP_PROGRESS_AWARE_DOWNLOAD)
|
||||
return String(out.toByteArray(), StandardCharsets.UTF_8)
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
private fun metadataFile(cloudNode: CloudNode): CloudFile {
|
||||
val cloudFolder = when (cloudNode) {
|
||||
@ -114,14 +122,6 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
return cloudContentRepository.file(cloudFolder, LONG_NODE_FILE_CONTENT_NAME + LONG_NODE_FILE_EXT)
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
private fun inflate(cloudNode: CloudNode): String {
|
||||
val metadataFile = metadataFile(cloudNode)
|
||||
val out = ByteArrayOutputStream()
|
||||
cloudContentRepository.read(metadataFile, null, out, ProgressAware.NO_OP_PROGRESS_AWARE_DOWNLOAD)
|
||||
return String(out.toByteArray(), StandardCharsets.UTF_8)
|
||||
}
|
||||
|
||||
override fun decryptName(dirId: String, encryptedName: String): String? {
|
||||
return extractEncryptedName(encryptedName)?.let {
|
||||
return cryptor().fileNameCryptor().decryptFilename(BaseEncoding.base64Url(), it, dirId.toByteArray(StandardCharsets.UTF_8))
|
||||
@ -132,34 +132,41 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
override fun list(cryptoFolder: CryptoFolder): List<CryptoNode> {
|
||||
dirIdCache.evictSubFoldersOf(cryptoFolder)
|
||||
|
||||
val dirIdInfo = dirIdInfo(cryptoFolder)
|
||||
val dirId = dirIdInfo(cryptoFolder).id
|
||||
val dirIdInfo = getCachingAwareDirIdInfo(cryptoFolder)
|
||||
?: when (cryptoFolder.dirFile) {
|
||||
null -> {
|
||||
Timber.tag("CryptoFs").e(String.format("Dir-file of folder is null %s", cryptoFolder.path))
|
||||
throw FatalBackendException(String.format("Dir-file of folder is null %s", cryptoFolder.path))
|
||||
}
|
||||
else -> {
|
||||
Timber.tag("CryptoFs").e("No dir file exists in %s", cryptoFolder.dirFile.path)
|
||||
throw NoDirFileException(cryptoFolder.name, cryptoFolder.dirFile.path)
|
||||
}
|
||||
}
|
||||
|
||||
val dirId = dirIdInfo.id
|
||||
val lvl2Dir = dirIdInfo.cloudFolder
|
||||
|
||||
val ciphertextNodes: List<CloudNode> = try {
|
||||
return try {
|
||||
cloudContentRepository.list(lvl2Dir)
|
||||
} catch (e: NoSuchCloudFileException) {
|
||||
if (cryptoFolder is RootCryptoFolder) {
|
||||
Timber.tag("CryptoFs").e("No lvl2Dir exists for root folder in %s", lvl2Dir.path)
|
||||
throw FatalBackendException(String.format("No lvl2Dir exists for root folder in %s", lvl2Dir.path), e)
|
||||
} else if (cryptoFolder.dirFile == null) {
|
||||
Timber.tag("CryptoFs").e(String.format("Dir-file of folder is null %s", lvl2Dir.path))
|
||||
throw FatalBackendException(String.format("Dir-file of folder is null %s", lvl2Dir.path))
|
||||
} else if (cloudContentRepository.exists(cloudContentRepository.file(cryptoFolder.dirFile.parent, CLOUD_NODE_SYMLINK_PRE + CLOUD_NODE_EXT))) {
|
||||
throw SymLinkException()
|
||||
} else if (!cloudContentRepository.exists(cryptoFolder.dirFile)) {
|
||||
Timber.tag("CryptoFs").e("No dir file exists in %s", cryptoFolder.dirFile.path)
|
||||
throw NoDirFileException(cryptoFolder.name, cryptoFolder.dirFile.path)
|
||||
when {
|
||||
cryptoFolder is RootCryptoFolder -> {
|
||||
Timber.tag("CryptoFs").e("No lvl2Dir exists for root folder in %s", lvl2Dir.path)
|
||||
throw FatalBackendException(String.format("No lvl2Dir exists for root folder in %s", lvl2Dir.path), e)
|
||||
}
|
||||
cryptoFolder.dirFile == null -> {
|
||||
Timber.tag("CryptoFs").e(String.format("Dir-file of folder is null %s", lvl2Dir.path))
|
||||
throw FatalBackendException(String.format("Dir-file of folder is null %s", lvl2Dir.path))
|
||||
}
|
||||
cloudContentRepository.exists(cloudContentRepository.file(cryptoFolder.dirFile.parent, CLOUD_NODE_SYMLINK_PRE + CLOUD_NODE_EXT)) -> {
|
||||
throw SymLinkException()
|
||||
}
|
||||
else -> return emptyList()
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return ciphertextNodes
|
||||
.map { node ->
|
||||
ciphertextToCleartextNode(cryptoFolder, dirId, node)
|
||||
}
|
||||
.toList()
|
||||
.filterNotNull()
|
||||
}.map { node ->
|
||||
ciphertextToCleartextNode(cryptoFolder, dirId, node)
|
||||
}.toList().filterNotNull()
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
@ -247,11 +254,17 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
override fun createDirIdInfo(folder: CryptoFolder): DirIdInfo {
|
||||
val dirId = loadDirId(folder)
|
||||
override fun getOrCreateDirIdInfo(folder: CryptoFolder): DirIdInfo {
|
||||
val dirId = loadDirId(folder) ?: newDirId()
|
||||
return dirIdCache.put(folder, createDirIdInfoFor(dirId))
|
||||
}
|
||||
|
||||
override fun getDirIdInfo(folder: CryptoFolder): DirIdInfo? {
|
||||
return loadDirId(folder)?.let {
|
||||
dirIdCache.put(folder, createDirIdInfoFor(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
override fun encryptFolderName(cryptoFolder: CryptoFolder, name: String): String {
|
||||
return encryptName(cryptoFolder, name)
|
||||
@ -263,17 +276,13 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
}
|
||||
|
||||
@Throws(BackendException::class, EmptyDirFileException::class)
|
||||
override fun loadDirId(folder: CryptoFolder): String {
|
||||
var dirFile: CloudFile? = null
|
||||
if (folder.dirFile != null) {
|
||||
dirFile = folder.dirFile
|
||||
}
|
||||
override fun loadDirId(folder: CryptoFolder): String? {
|
||||
return if (RootCryptoFolder.isRoot(folder)) {
|
||||
CryptoConstants.ROOT_DIR_ID
|
||||
} else if (dirFile != null && cloudContentRepository.exists(dirFile)) {
|
||||
String(loadContentsOfDirFile(dirFile), StandardCharsets.UTF_8)
|
||||
} else if (folder.dirFile != null && cloudContentRepository.exists(folder.dirFile)) {
|
||||
String(loadContentsOfDirFile(folder.dirFile), StandardCharsets.UTF_8)
|
||||
} else {
|
||||
newDirId()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,7 +311,7 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
assertCryptoFolderAlreadyExists(folder)
|
||||
shortName = true
|
||||
}
|
||||
val dirIdInfo = dirIdInfo(folder)
|
||||
val dirIdInfo = getOrCreateCachingAwareDirIdInfo(folder)
|
||||
val createdCloudFolder = cloudContentRepository.create(dirIdInfo.cloudFolder)
|
||||
var dirFolder = folder.dirFile.parent
|
||||
var dirFile = folder.dirFile
|
||||
@ -407,17 +416,13 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator {
|
||||
requireNotNull(node.dirFile)
|
||||
val cryptoSubfolders = deepCollectSubfolders(node)
|
||||
for (cryptoSubfolder in cryptoSubfolders) {
|
||||
try {
|
||||
cloudContentRepository.delete(dirIdInfo(cryptoSubfolder).cloudFolder)
|
||||
} catch (e: NoSuchCloudFileException) {
|
||||
// Ignoring because nothing can be done if the dir-file doesn't exists in the cloud
|
||||
}
|
||||
}
|
||||
try {
|
||||
cloudContentRepository.delete(dirIdInfo(node).cloudFolder)
|
||||
} catch (e: NoSuchCloudFileException) {
|
||||
// Ignoring because nothing can be done if the dir-file doesn't exists in the cloud
|
||||
getCachingAwareDirIdInfo(cryptoSubfolder)?.let {
|
||||
cloudContentRepository.delete(it.cloudFolder)
|
||||
} ?: Timber.tag("CryptoFs").w("Dir file doesn't exists of a sub folder while deleting the parent, continue anyway")
|
||||
}
|
||||
getCachingAwareDirIdInfo(node)?.let {
|
||||
cloudContentRepository.delete(it.cloudFolder)
|
||||
} ?: Timber.tag("CryptoFs").w("Dir file doesn't exists while deleting the folder, continue anyway")
|
||||
cloudContentRepository.delete(node.dirFile.parent)
|
||||
evictFromCache(node)
|
||||
} else if (node is CryptoFile) {
|
||||
|
@ -16,6 +16,7 @@ import org.cryptomator.domain.CloudNode
|
||||
import org.cryptomator.domain.exception.AlreadyExistException
|
||||
import org.cryptomator.domain.exception.BackendException
|
||||
import org.cryptomator.domain.exception.EmptyDirFileException
|
||||
import org.cryptomator.domain.exception.NoDirFileException
|
||||
import org.cryptomator.domain.exception.NoSuchCloudFileException
|
||||
import org.cryptomator.domain.exception.ParentFolderIsNullException
|
||||
import org.cryptomator.domain.repository.CloudContentRepository
|
||||
@ -27,7 +28,6 @@ import java.io.ByteArrayOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.function.Supplier
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.streams.toList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class CryptoImplVaultFormatPre7(
|
||||
@ -44,7 +44,7 @@ internal class CryptoImplVaultFormatPre7(
|
||||
@Throws(BackendException::class)
|
||||
override fun folder(cryptoParent: CryptoFolder, cleartextName: String): CryptoFolder {
|
||||
val dirFileName = encryptFolderName(cryptoParent, cleartextName)
|
||||
val dirFile = cloudContentRepository.file(dirIdInfo(cryptoParent).cloudFolder, dirFileName)
|
||||
val dirFile = cloudContentRepository.file(getOrCreateCachingAwareDirIdInfo(cryptoParent).cloudFolder, dirFileName)
|
||||
return folder(cryptoParent, cleartextName, dirFile)
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ internal class CryptoImplVaultFormatPre7(
|
||||
override fun create(folder: CryptoFolder): CryptoFolder {
|
||||
requireNotNull(folder.dirFile)
|
||||
assertCryptoFolderAlreadyExists(folder)
|
||||
val dirIdInfo = dirIdInfo(folder)
|
||||
val dirIdInfo = getOrCreateCachingAwareDirIdInfo(folder)
|
||||
val createdCloudFolder = cloudContentRepository.create(dirIdInfo.cloudFolder)
|
||||
val dirId = dirIdInfo.id.toByteArray(StandardCharsets.UTF_8)
|
||||
val createdDirFile = cloudContentRepository.write(folder.dirFile, from(dirId), ProgressAware.NO_OP_PROGRESS_AWARE_UPLOAD, false, dirId.size.toLong())
|
||||
@ -68,7 +68,7 @@ internal class CryptoImplVaultFormatPre7(
|
||||
|
||||
@Throws(BackendException::class)
|
||||
private fun encryptName(cryptoParent: CryptoFolder, name: String, prefix: String): String {
|
||||
var ciphertextName = prefix + cryptor().fileNameCryptor().encryptFilename(BaseEncoding.base32(), name, dirIdInfo(cryptoParent).id.toByteArray(StandardCharsets.UTF_8))
|
||||
var ciphertextName = prefix + cryptor().fileNameCryptor().encryptFilename(BaseEncoding.base32(), name, getOrCreateCachingAwareDirIdInfo(cryptoParent).id.toByteArray(StandardCharsets.UTF_8))
|
||||
if (ciphertextName.length > shorteningThreshold) {
|
||||
ciphertextName = deflate(ciphertextName)
|
||||
}
|
||||
@ -120,8 +120,8 @@ internal class CryptoImplVaultFormatPre7(
|
||||
|
||||
@Throws(BackendException::class)
|
||||
override fun list(cryptoFolder: CryptoFolder): List<CryptoNode> {
|
||||
val dirIdInfo = dirIdInfo(cryptoFolder)
|
||||
val dirId = dirIdInfo(cryptoFolder).id
|
||||
val dirIdInfo = getDirIdInfo(cryptoFolder) ?: throw NoDirFileException(cryptoFolder.name, cryptoFolder.dirFile?.path)
|
||||
val dirId = dirIdInfo.id
|
||||
val lvl2Dir = dirIdInfo.cloudFolder
|
||||
return cloudContentRepository
|
||||
.list(lvl2Dir)
|
||||
@ -198,7 +198,7 @@ internal class CryptoImplVaultFormatPre7(
|
||||
@Throws(BackendException::class)
|
||||
override fun symlink(cryptoParent: CryptoFolder, cleartextName: String, target: String): CryptoSymlink {
|
||||
val ciphertextName = encryptSymlinkName(cryptoParent, cleartextName)
|
||||
val cloudFile = cloudContentRepository.file(dirIdInfo(cryptoParent).cloudFolder, ciphertextName)
|
||||
val cloudFile = cloudContentRepository.file(getOrCreateCachingAwareDirIdInfo(cryptoParent).cloudFolder, ciphertextName)
|
||||
return CryptoSymlink(cryptoParent, cleartextName, path(cryptoParent, cleartextName), target, cloudFile)
|
||||
}
|
||||
|
||||
@ -237,9 +237,13 @@ internal class CryptoImplVaultFormatPre7(
|
||||
requireNotNull(node.dirFile)
|
||||
val cryptoSubfolders = deepCollectSubfolders(node)
|
||||
for (cryptoSubfolder in cryptoSubfolders) {
|
||||
cloudContentRepository.delete(dirIdInfo(cryptoSubfolder).cloudFolder)
|
||||
getCachingAwareDirIdInfo(cryptoSubfolder)?.let {
|
||||
cloudContentRepository.delete(it.cloudFolder)
|
||||
} ?: Timber.tag("CryptoFs").w("Dir file doesn't exists of a sub folder while deleting the parent, continue anyway")
|
||||
}
|
||||
cloudContentRepository.delete(dirIdInfo(node).cloudFolder)
|
||||
getCachingAwareDirIdInfo(node)?.let {
|
||||
cloudContentRepository.delete(it.cloudFolder)
|
||||
} ?: Timber.tag("CryptoFs").w("Dir file doesn't exists while deleting the folder, continue anyway")
|
||||
cloudContentRepository.delete(node.dirFile)
|
||||
evictFromCache(node)
|
||||
} else if (node is CryptoFile) {
|
||||
@ -248,22 +252,28 @@ internal class CryptoImplVaultFormatPre7(
|
||||
}
|
||||
|
||||
@Throws(BackendException::class, EmptyDirFileException::class)
|
||||
override fun loadDirId(folder: CryptoFolder): String {
|
||||
override fun loadDirId(folder: CryptoFolder): String? {
|
||||
return if (isRoot(folder)) {
|
||||
CryptoConstants.ROOT_DIR_ID
|
||||
} else if (folder.dirFile != null && cloudContentRepository.exists(folder.dirFile)) {
|
||||
String(loadContentsOfDirFile(folder), StandardCharsets.UTF_8)
|
||||
} else {
|
||||
newDirId()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
override fun createDirIdInfo(folder: CryptoFolder): DirIdInfo {
|
||||
val dirId = loadDirId(folder)
|
||||
override fun getOrCreateDirIdInfo(folder: CryptoFolder): DirIdInfo {
|
||||
val dirId = loadDirId(folder) ?: newDirId()
|
||||
return dirIdCache.put(folder, createDirIdInfoFor(dirId))
|
||||
}
|
||||
|
||||
override fun getDirIdInfo(folder: CryptoFolder): DirIdInfo? {
|
||||
return loadDirId(folder)?.let {
|
||||
dirIdCache.put(folder, createDirIdInfoFor(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(BackendException::class)
|
||||
override fun write(cryptoFile: CryptoFile, data: DataSource, progressAware: ProgressAware<UploadState>, replace: Boolean, length: Long): CryptoFile {
|
||||
return writeShortNameFile(cryptoFile, data, progressAware, replace, length)
|
||||
|
@ -19,6 +19,7 @@ import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.domain.exception.BackendException
|
||||
import org.cryptomator.domain.exception.CancellationException
|
||||
import org.cryptomator.domain.exception.FatalBackendException
|
||||
import org.cryptomator.domain.exception.vaultconfig.MissingVaultConfigFileException
|
||||
import org.cryptomator.domain.exception.vaultconfig.UnsupportedMasterkeyLocationException
|
||||
import org.cryptomator.domain.repository.CloudContentRepository
|
||||
import org.cryptomator.domain.usecases.ProgressAware
|
||||
@ -204,10 +205,16 @@ class MasterkeyCryptoCloudProvider(
|
||||
}
|
||||
|
||||
private fun assertLegacyVaultVersionIsSupported(version: Int) {
|
||||
if (version < CryptoConstants.MIN_VAULT_VERSION) {
|
||||
throw UnsupportedVaultFormatException(version, CryptoConstants.MIN_VAULT_VERSION)
|
||||
} else if (version > CryptoConstants.MAX_VAULT_VERSION_WITHOUT_VAULT_CONFIG) {
|
||||
throw UnsupportedVaultFormatException(version, CryptoConstants.MAX_VAULT_VERSION_WITHOUT_VAULT_CONFIG)
|
||||
when {
|
||||
version < CryptoConstants.MIN_VAULT_VERSION -> {
|
||||
throw UnsupportedVaultFormatException(version, CryptoConstants.MIN_VAULT_VERSION)
|
||||
}
|
||||
version == CryptoConstants.DEFAULT_MASTERKEY_FILE_VERSION -> {
|
||||
throw MissingVaultConfigFileException()
|
||||
}
|
||||
version > CryptoConstants.MAX_VAULT_VERSION_WITHOUT_VAULT_CONFIG -> {
|
||||
throw UnsupportedVaultFormatException(version, CryptoConstants.MAX_VAULT_VERSION_WITHOUT_VAULT_CONFIG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ internal object LocalStorageAccessFrameworkNodeFactory {
|
||||
getNodePath(parent, documentFile.name), //
|
||||
documentFile.length(), //
|
||||
Date(documentFile.lastModified()), //
|
||||
DocumentsContract.getTreeDocumentId(documentFile.uri), //
|
||||
DocumentsContract.getDocumentId(documentFile.uri), //
|
||||
documentFile.uri.toString()
|
||||
)
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ internal class WebDavCompatibleHttpClient(cloud: WebDavCloud, context: Context)
|
||||
private fun httpAuthenticator(context: Context, webDavCloud: WebDavCloud, authCache: Map<String, CachingAuthenticator>): Authenticator {
|
||||
val credentials = Credentials(webDavCloud.username(), decryptPassword(context, webDavCloud.password()))
|
||||
val basicAuthenticator = BasicAuthenticator(credentials, StandardCharsets.UTF_8)
|
||||
val digestAuthenticator = DigestAuthenticator(credentials)
|
||||
val digestAuthenticator = DigestAuthenticator(credentials, StandardCharsets.UTF_8)
|
||||
val authenticator = DispatchingAuthenticator.Builder() //
|
||||
.with("digest", digestAuthenticator) //
|
||||
.with("basic", basicAuthenticator) //
|
||||
|
@ -29,7 +29,8 @@ class DatabaseUpgrades {
|
||||
Upgrade7To8 upgrade7To8, //
|
||||
Upgrade8To9 upgrade8To9, //
|
||||
Upgrade9To10 upgrade9To10, //
|
||||
Upgrade10To11 upgrade10To11
|
||||
Upgrade10To11 upgrade10To11, //
|
||||
Upgrade11To12 upgrade11To12
|
||||
) {
|
||||
|
||||
availableUpgrades = defineUpgrades( //
|
||||
@ -43,7 +44,8 @@ class DatabaseUpgrades {
|
||||
upgrade7To8, //
|
||||
upgrade8To9, //
|
||||
upgrade9To10, //
|
||||
upgrade10To11);
|
||||
upgrade10To11, //
|
||||
upgrade11To12);
|
||||
}
|
||||
|
||||
private Map<Integer, List<DatabaseUpgrade>> defineUpgrades(DatabaseUpgrade... upgrades) {
|
||||
|
17
data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt
Normal file
17
data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package org.cryptomator.data.db
|
||||
|
||||
import com.google.common.base.Optional
|
||||
import org.cryptomator.util.SharedPreferencesHandler
|
||||
import org.greenrobot.greendao.database.Database
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class Upgrade11To12 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : DatabaseUpgrade(11, 12) {
|
||||
|
||||
override fun internalApplyTo(db: Database, origin: Int) {
|
||||
when (sharedPreferencesHandler.updateIntervalInDays()) {
|
||||
Optional.of(7), Optional.of(30) -> sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(1))
|
||||
}
|
||||
}
|
||||
}
|
@ -67,9 +67,9 @@ class CloudRepositoryImpl implements CloudRepository {
|
||||
}
|
||||
|
||||
Cloud storedCloud = mapper.fromEntity(database.store(mapper.toEntity(cloud)));
|
||||
database.clearCache();
|
||||
|
||||
dispatchingCloudContentRepository.updateCloudContentRepositoryFor(storedCloud);
|
||||
database.clearCache();
|
||||
|
||||
return storedCloud;
|
||||
}
|
||||
@ -80,6 +80,7 @@ class CloudRepositoryImpl implements CloudRepository {
|
||||
throw new IllegalArgumentException("Can not delete non persistent cloud");
|
||||
}
|
||||
database.delete(mapper.toEntity(cloud));
|
||||
dispatchingCloudContentRepository.removeCloudContentRepositoryFor(cloud);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -42,7 +42,6 @@ import java.io.InputStreamReader
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.ArrayList
|
||||
import kotlin.io.path.createTempDirectory
|
||||
import kotlin.io.path.deleteExisting
|
||||
|
||||
@ -730,6 +729,7 @@ class CryptoImplVaultFormat7Test {
|
||||
whenever(cloudContentRepository.folder(aaFolder, shortenedFileName)).thenReturn(testDir3)
|
||||
whenever(cloudContentRepository.exists(testDir3)).thenReturn(false)
|
||||
whenever(dirIdCache.put(eq(cryptoFolder3), any())).thenReturn(DirIdInfo("dir3-id", ddFolder))
|
||||
whenever(dirIdCache[cryptoFolder3]).thenReturn(DirIdInfo("dir3-id", ddFolder))
|
||||
whenever(cloudContentRepository.file(testDir3, "dir.c9r")).thenReturn(testDir3DirFile)
|
||||
whenever(cloudContentRepository.file(testDir3, "name.c9s", 257L)).thenReturn(testDir3NameFile)
|
||||
whenever<List<*>>(cloudContentRepository.list(ddFolder)).thenReturn(ArrayList<CloudNode>())
|
||||
|
@ -33,6 +33,7 @@ import org.mockito.AdditionalMatchers
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.invocation.InvocationOnMock
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.anyOrNull
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
@ -43,7 +44,6 @@ import java.io.InputStreamReader
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.ArrayList
|
||||
import java.util.function.Supplier
|
||||
import kotlin.io.path.createTempDirectory
|
||||
import kotlin.io.path.deleteExisting
|
||||
@ -138,11 +138,11 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
whenever(cloudContentRepository.folder(lvl2Dir, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).thenReturn(aaFolder)
|
||||
whenever(cloudContentRepository.file(aaFolder, "0dir1")).thenReturn(testDir1)
|
||||
whenever(cloudContentRepository.exists(testDir1)).thenReturn(true)
|
||||
Mockito.doAnswer { invocation: InvocationOnMock ->
|
||||
val out = invocation.getArgument<OutputStream>(2)
|
||||
whenever(cloudContentRepository.read(eq(cryptoFolder1.dirFile!!), any(), any(), any())).thenAnswer { invocationOnMock: InvocationOnMock ->
|
||||
val out = invocationOnMock.getArgument<OutputStream>(2)
|
||||
copyStreamToStream(ByteArrayInputStream(dirId1.toByteArray()), out)
|
||||
null
|
||||
}.`when`(cloudContentRepository).read(eq(cryptoFolder1.dirFile!!), any(), any(), any())
|
||||
}
|
||||
whenever<List<*>>(cloudContentRepository.list(aaFolder)).thenReturn(rootItems)
|
||||
whenever(dirIdCache.put(eq(root), any())).thenReturn(DirIdInfo("", aaFolder))
|
||||
}
|
||||
@ -193,11 +193,11 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
|
||||
val cryptoFolder3 = CryptoFolder(cryptoFolder1, dir3Name, "/Directory 1/$dir3Name", testDir3DirFile)
|
||||
|
||||
Mockito.doAnswer { invocation: InvocationOnMock ->
|
||||
val out = invocation.getArgument<OutputStream>(2)
|
||||
whenever(cloudContentRepository.read(eq(cryptoFolder3.dirFile!!), anyOrNull(), any(), any())).thenAnswer { invocationOnMock: InvocationOnMock ->
|
||||
val out = invocationOnMock.getArgument<OutputStream>(2)
|
||||
copyStreamToStream(ByteArrayInputStream("dir3-id".toByteArray()), out)
|
||||
null
|
||||
}.`when`(cloudContentRepository).read(eq(cryptoFolder3.dirFile!!), any(), any(), any())
|
||||
}
|
||||
|
||||
/*
|
||||
* │ ├─ Directory 3x250
|
||||
@ -217,11 +217,11 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
whenever(cloudContentRepository.file(directory4x250, "name.c9s")).thenReturn(testDir4NameFile)
|
||||
whenever(fileNameCryptor.encryptFilename(BaseEncoding.base32(), dir4Name, "dir3-id".toByteArray())).thenReturn(dir4Cipher)
|
||||
whenever(fileNameCryptor.decryptFilename(BaseEncoding.base32(), dir4Cipher, "dir3-id".toByteArray())).thenReturn(dir4Name)
|
||||
Mockito.doAnswer { invocation: InvocationOnMock ->
|
||||
val out = invocation.getArgument<OutputStream>(2)
|
||||
whenever(cloudContentRepository.read(eq(testDir4NameFile), any(), any(), any())).thenAnswer { invocationOnMock: InvocationOnMock ->
|
||||
val out = invocationOnMock.getArgument<OutputStream>(2)
|
||||
copyStreamToStream(ByteArrayInputStream(dir4Cipher.toByteArray(charset("UTF-8"))), out)
|
||||
null
|
||||
}.`when`(cloudContentRepository).read(eq(testDir4NameFile), any(), any(), any())
|
||||
}
|
||||
|
||||
val dir4Files: ArrayList<CloudNode> = object : ArrayList<CloudNode>() {
|
||||
init {
|
||||
@ -242,11 +242,12 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
whenever(cloudContentRepository.file(directory5x250, "name.c9s")).thenReturn(testFile5NameFile)
|
||||
whenever(fileNameCryptor.encryptFilename(BaseEncoding.base32(), file5Name, "dir3-id".toByteArray())).thenReturn(file5Cipher)
|
||||
whenever(fileNameCryptor.decryptFilename(BaseEncoding.base32(), file5Cipher, "dir3-id".toByteArray())).thenReturn(file5Name)
|
||||
Mockito.doAnswer { invocation: InvocationOnMock ->
|
||||
val out = invocation.getArgument<OutputStream>(2)
|
||||
whenever(cloudContentRepository.read(eq(testFile5NameFile), any(), any(), any())).thenAnswer { invocationOnMock: InvocationOnMock ->
|
||||
val out = invocationOnMock.getArgument<OutputStream>(2)
|
||||
copyStreamToStream(ByteArrayInputStream(file5Cipher.toByteArray(charset("UTF-8"))), out)
|
||||
null
|
||||
}.`when`(cloudContentRepository).read(eq(testFile5NameFile), any(), any(), any())
|
||||
}
|
||||
|
||||
val dir5Files: ArrayList<CloudNode> = object : ArrayList<CloudNode>() {
|
||||
init {
|
||||
add(testFile5ContentFile)
|
||||
@ -286,11 +287,11 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
whenever(fileHeaderCryptor.decryptHeader(StandardCharsets.UTF_8.encode("hhhhh"))).thenReturn(header)
|
||||
whenever(fileContentCryptor.decryptChunk(eq(StandardCharsets.UTF_8.encode("TOPSECRET!")), any(), eq(header), any()))
|
||||
.then { invocation: InvocationOnMock? -> StandardCharsets.UTF_8.encode("geheim!!") }
|
||||
Mockito.doAnswer { invocation: InvocationOnMock ->
|
||||
val out = invocation.getArgument<OutputStream>(2)
|
||||
whenever(cloudContentRepository.read(eq(cryptoFile1.cloudFile), any(), any(), any())).thenAnswer { invocationOnMock: InvocationOnMock ->
|
||||
val out = invocationOnMock.getArgument<OutputStream>(2)
|
||||
copyStreamToStream(ByteArrayInputStream(file1Content), out)
|
||||
null
|
||||
}.`when`(cloudContentRepository).read(eq(cryptoFile1.cloudFile), any(), any(), any())
|
||||
}
|
||||
|
||||
val outputStream = ByteArrayOutputStream(1000)
|
||||
inTest.read(cryptoFile1, outputStream, ProgressAware.NO_OP_PROGRESS_AWARE_DOWNLOAD)
|
||||
@ -318,11 +319,11 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
whenever(fileContentCryptor.decryptChunk(eq(StandardCharsets.UTF_8.encode("TOPSECRET!")), any(), eq(header), any()))
|
||||
.then { invocation: InvocationOnMock? -> StandardCharsets.UTF_8.encode("geheim!!") }
|
||||
val cryptoFile15 = CryptoFile(root, file3Name, "/$file3Name", null, testFile3ContentFile)
|
||||
Mockito.doAnswer { invocation: InvocationOnMock ->
|
||||
val out = invocation.getArgument<OutputStream>(2)
|
||||
whenever(cloudContentRepository.read(eq(cryptoFile15.cloudFile), any(), any(), any())).thenAnswer { invocationOnMock: InvocationOnMock ->
|
||||
val out = invocationOnMock.getArgument<OutputStream>(2)
|
||||
copyStreamToStream(ByteArrayInputStream(file1Content), out)
|
||||
null
|
||||
}.`when`(cloudContentRepository).read(eq(cryptoFile15.cloudFile), any(), any(), any())
|
||||
}
|
||||
|
||||
val outputStream = ByteArrayOutputStream(1000)
|
||||
inTest.read(cryptoFile15, outputStream, ProgressAware.NO_OP_PROGRESS_AWARE_DOWNLOAD)
|
||||
@ -593,11 +594,11 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
val testDir2DirFile = TestFile(bbFolder, "0dir2", "/d/11/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB/0dir2", null, null)
|
||||
val cryptoFolder2 = CryptoFolder(cryptoFolder1, "Directory 2", "/Directory 1/Directory 2", testDir2DirFile)
|
||||
|
||||
Mockito.doAnswer { invocation: InvocationOnMock ->
|
||||
val out = invocation.getArgument<OutputStream>(2)
|
||||
whenever(cloudContentRepository.read(eq(cryptoFolder2.dirFile!!), anyOrNull(), any(), any())).thenAnswer { invocationOnMock: InvocationOnMock ->
|
||||
val out = invocationOnMock.getArgument<OutputStream>(2)
|
||||
copyStreamToStream(ByteArrayInputStream(dirId2.toByteArray()), out)
|
||||
null
|
||||
}.`when`(cloudContentRepository).read(eq(cryptoFolder2.dirFile!!), any(), any(), any())
|
||||
}
|
||||
|
||||
val dir1Items: ArrayList<CloudNode> = object : ArrayList<CloudNode>() {
|
||||
init {
|
||||
@ -657,6 +658,7 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
whenever(cloudContentRepository.folder(d, "33")).thenReturn(ddLvl2Dir)
|
||||
whenever(cloudContentRepository.folder(lvl2Dir, "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDD")).thenReturn(ddFolder)
|
||||
whenever(dirIdCache.put(eq(cryptoFolder3), any())).thenReturn(DirIdInfo("dir3-id", ddFolder))
|
||||
whenever(dirIdCache[cryptoFolder3]).thenReturn(DirIdInfo("dir3-id", ddFolder))
|
||||
whenever(cloudContentRepository.file(aaFolder, shortenedFileName)).thenReturn(testDir3DirFile)
|
||||
whenever(cloudContentRepository.file(testDir3NameFile.parent!!, shortenedFileName, 257L)).thenReturn(testDir3NameFile)
|
||||
whenever<List<*>>(cloudContentRepository.list(ddFolder)).thenReturn(ArrayList<CloudNode>())
|
||||
@ -767,7 +769,7 @@ internal class CryptoImplVaultFormatPre7Test {
|
||||
val cryptoMovedFile4 = CryptoFile(cryptoFolder1, file4Name, "/Directory 1/$file4Name", null, testFile4ContentFile)
|
||||
|
||||
whenever(cloudContentRepository.move(testFile4ContentFileOld, testFile4ContentFile)).thenReturn(testFile4ContentFile)
|
||||
whenever(cloudContentRepository.create(testFile4NameFile.parent!!)).thenReturn(testFile4NameFile.parent)
|
||||
whenever(cloudContentRepository.create(testFile4NameFile.parent)).thenReturn(testFile4NameFile.parent)
|
||||
whenever(cloudContentRepository.write(eq(testFile4NameFile), any(), any(), eq(true), any())).thenAnswer { invocationOnMock: InvocationOnMock ->
|
||||
val inputStream = invocationOnMock.getArgument<DataSource>(1)
|
||||
val dirContent = BufferedReader(InputStreamReader(inputStream.open(context)!!, StandardCharsets.UTF_8)).readLine()
|
||||
|
@ -6,7 +6,6 @@ import org.cryptomator.cryptolib.api.Cryptor
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider
|
||||
import org.cryptomator.cryptolib.api.FileNameCryptor
|
||||
import org.cryptomator.cryptolib.api.Masterkey
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException
|
||||
import org.cryptomator.data.cloud.crypto.BackupFileIdSuffixGenerator.generate
|
||||
import org.cryptomator.data.cloud.crypto.MasterkeyCryptoCloudProvider.UnlockTokenImpl
|
||||
import org.cryptomator.data.cloud.crypto.VaultConfig.VaultConfigBuilder
|
||||
@ -19,6 +18,7 @@ import org.cryptomator.domain.CloudType
|
||||
import org.cryptomator.domain.UnverifiedVaultConfig
|
||||
import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.domain.exception.BackendException
|
||||
import org.cryptomator.domain.exception.vaultconfig.MissingVaultConfigFileException
|
||||
import org.cryptomator.domain.repository.CloudContentRepository
|
||||
import org.cryptomator.domain.usecases.ProgressAware
|
||||
import org.cryptomator.domain.usecases.cloud.DataSource
|
||||
@ -207,7 +207,7 @@ internal class MasterkeyCryptoCloudProviderTest {
|
||||
@DisplayName("unlockLegacyUsingNewVault(\"foo\")")
|
||||
fun testUnlockLegacyVaultUsingVaultFormat8() {
|
||||
val unlockToken: UnlockToken = UnlockTokenImpl(vault, masterkeyV8.toByteArray(StandardCharsets.UTF_8))
|
||||
Assertions.assertThrows(UnsupportedVaultFormatException::class.java) { inTest.unlock(unlockToken, Optional.absent(), "foo", { false }) }
|
||||
Assertions.assertThrows(MissingVaultConfigFileException::class.java) { inTest.unlock(unlockToken, Optional.absent(), "foo", { false }) }
|
||||
}
|
||||
|
||||
@DisplayName("changePassword(\"foo\")")
|
||||
|
@ -0,0 +1,6 @@
|
||||
package org.cryptomator.domain.exception.vaultconfig;
|
||||
|
||||
import org.cryptomator.domain.exception.BackendException;
|
||||
|
||||
public class MissingVaultConfigFileException extends BackendException {
|
||||
}
|
@ -29,6 +29,7 @@ platform :android do |options|
|
||||
deployToPlaystore(alpha:options[:alpha], beta:options[:beta])
|
||||
deployToServer(alpha:options[:alpha], beta:options[:beta])
|
||||
deployToFDroid(alpha:options[:alpha], beta:options[:beta])
|
||||
testLite(alpha:options[:alpha], beta:options[:beta])
|
||||
createGitHubDraftRelease(alpha:options[:alpha], beta:options[:beta])
|
||||
|
||||
slack(
|
||||
@ -220,6 +221,29 @@ platform :android do |options|
|
||||
FileUtils.cp(lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], "release/Cryptomator-#{version}_fdroid_signed.apk")
|
||||
end
|
||||
|
||||
desc "Deploy new lite version"
|
||||
lane :deployLite do |options|
|
||||
gradle(task: "clean")
|
||||
|
||||
gradle(
|
||||
task: "assemble",
|
||||
build_type: "Release",
|
||||
flavor: "lite",
|
||||
print_command: false,
|
||||
properties: {
|
||||
"android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"],
|
||||
"android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"],
|
||||
"android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"],
|
||||
"android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"],
|
||||
}
|
||||
)
|
||||
|
||||
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'lite')
|
||||
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'lite')
|
||||
|
||||
FileUtils.cp(lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], "release/Cryptomator-#{version}_lite_signed.apk")
|
||||
end
|
||||
|
||||
desc "Check if tracking added in some dependency using Izzy's script"
|
||||
lane :checkTrackingAddedInDependencyUsingIzzyScript do |options|
|
||||
flavor = options[:flavor]
|
||||
@ -289,8 +313,9 @@ platform :android do |options|
|
||||
|
||||
website_apk_sha256 = Digest::SHA256.hexdigest File.read "release/Cryptomator-#{version}_signed.apk"
|
||||
fdroid_apk_sha256 = Digest::SHA256.hexdigest File.read "release/Cryptomator-#{version}_fdroid_signed.apk"
|
||||
lite_sha256 = Digest::SHA256.hexdigest File.read "release/Cryptomator-#{version}_lite_signed.apk"
|
||||
|
||||
release_note = "## What's New\n\n" + File.read(release_note_path_en) + "\n\n---\n\nSHA256 Signature: `#{website_apk_sha256}`\nSHA256 Signature fdroid: `#{fdroid_apk_sha256}`\n"
|
||||
release_note = "## What's New\n\n" + File.read(release_note_path_en) + "\n\n---\n\nSHA256 Signature: `#{website_apk_sha256}`\nSHA256 Signature fdroid: `#{fdroid_apk_sha256}`\nSHA256 Signature lite: `#{lite_sha256}`\n"
|
||||
|
||||
puts release_note
|
||||
|
||||
@ -303,7 +328,82 @@ platform :android do |options|
|
||||
commitish: target_branch,
|
||||
is_draft: true,
|
||||
is_prerelease: prerelease,
|
||||
upload_assets: ["fastlane/release/Cryptomator-#{version}_fdroid_signed.apk", "fastlane/release/Cryptomator-#{version}_signed.apk"]
|
||||
upload_assets: ["fastlane/release/Cryptomator-#{version}_fdroid_signed.apk", "fastlane/release/Cryptomator-#{version}_lite_signed.apk", "fastlane/release/Cryptomator-#{version}_signed.apk"]
|
||||
)
|
||||
end
|
||||
|
||||
desc "Dry run - check tracking added for all flavors"
|
||||
lane :dryRun do |options|
|
||||
gradle(task: "clean")
|
||||
|
||||
gradle(
|
||||
task: "assemble",
|
||||
build_type: "Release",
|
||||
flavor: "playstore",
|
||||
print_command: false,
|
||||
properties: {
|
||||
"android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"],
|
||||
"android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"],
|
||||
"android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"],
|
||||
"android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"],
|
||||
}
|
||||
)
|
||||
|
||||
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'playstore')
|
||||
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'playstore')
|
||||
|
||||
gradle(task: "clean")
|
||||
|
||||
gradle(
|
||||
task: "assemble",
|
||||
build_type: "Release",
|
||||
flavor: "apkstore",
|
||||
print_command: false,
|
||||
properties: {
|
||||
"android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"],
|
||||
"android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"],
|
||||
"android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"],
|
||||
"android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"],
|
||||
}
|
||||
)
|
||||
|
||||
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'apkstore')
|
||||
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'apkstore')
|
||||
|
||||
gradle(task: "clean")
|
||||
|
||||
gradle(
|
||||
task: "assemble",
|
||||
build_type: "Release",
|
||||
flavor: "fdroid",
|
||||
print_command: false,
|
||||
properties: {
|
||||
"android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"],
|
||||
"android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"],
|
||||
"android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"],
|
||||
"android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"],
|
||||
}
|
||||
)
|
||||
|
||||
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'fdroid')
|
||||
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'fdroid')
|
||||
|
||||
gradle(task: "clean")
|
||||
|
||||
gradle(
|
||||
task: "assemble",
|
||||
build_type: "Release",
|
||||
flavor: "lite",
|
||||
print_command: false,
|
||||
properties: {
|
||||
"android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"],
|
||||
"android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"],
|
||||
"android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"],
|
||||
"android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"],
|
||||
}
|
||||
)
|
||||
|
||||
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'lite')
|
||||
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'lite')
|
||||
end
|
||||
end
|
||||
|
@ -1,64 +1,104 @@
|
||||
fastlane documentation
|
||||
================
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew install fastlane`
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## Android
|
||||
|
||||
### android test
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android test
|
||||
```
|
||||
fastlane android test
|
||||
```
|
||||
|
||||
Run all the tests
|
||||
|
||||
### android deploy
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android deploy
|
||||
```
|
||||
fastlane android deploy
|
||||
```
|
||||
|
||||
Deploy new version to Google Play and APK Store options: beta:false (default)
|
||||
|
||||
### android deployToPlaystore
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android deployToPlaystore
|
||||
```
|
||||
fastlane android deployToPlaystore
|
||||
```
|
||||
|
||||
Deploy new version to Play Store
|
||||
|
||||
### android deployToServer
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android deployToServer
|
||||
```
|
||||
fastlane android deployToServer
|
||||
```
|
||||
|
||||
Deploy new version to server
|
||||
|
||||
### android deployToFDroid
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android deployToFDroid
|
||||
```
|
||||
fastlane android deployToFDroid
|
||||
```
|
||||
|
||||
Deploy new version to F-Droid
|
||||
|
||||
### android deployLite
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android deployLite
|
||||
```
|
||||
|
||||
Deploy new lite version
|
||||
|
||||
### android checkTrackingAddedInDependencyUsingIzzyScript
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android checkTrackingAddedInDependencyUsingIzzyScript
|
||||
```
|
||||
fastlane android checkTrackingAddedInDependencyUsingIzzyScript
|
||||
```
|
||||
|
||||
Check if tracking added in some dependency using Izzy's script
|
||||
|
||||
### android checkTrackingAddedInDependencyUsingExodus
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android checkTrackingAddedInDependencyUsingExodus
|
||||
```
|
||||
fastlane android checkTrackingAddedInDependencyUsingExodus
|
||||
```
|
||||
|
||||
Check if tracking added in some dependency using exodus
|
||||
|
||||
### android createGitHubDraftRelease
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android createGitHubDraftRelease
|
||||
```
|
||||
fastlane android createGitHubDraftRelease
|
||||
```
|
||||
|
||||
Create GitHub draft release
|
||||
|
||||
### android dryRun
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android dryRun
|
||||
```
|
||||
|
||||
Dry run - check tracking added for all flavors
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
|
||||
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
fastlane/izzyscript/result_lite.json
Normal file
1
fastlane/izzyscript/result_lite.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,2 +1,3 @@
|
||||
- Zeige Dialog und Benachrichtigung an, wenn die Berechtigung "Dateien" widerrufen wird, erforderlich für den automatischen Upload
|
||||
- Das Abmelden von einer Cloud löscht nun auch die Anmeldeinformationen einer aktiven Verbindung zu ihr
|
||||
- Update auf die neueste Android-Zielplatform-Version
|
||||
- Löschen von Ordnern ohne Verzeichnisdatei im lokalen Cloud-Speicher behoben
|
||||
- Dateiexport wenn keine App den ausgewählten Dateityp verarbeiten kann behoben
|
@ -1,2 +1,3 @@
|
||||
- Show information when "Storage" permission revoked, required for auto upload
|
||||
- Logging out of a cloud now also clears the credentials of an active connection to it
|
||||
- Updated to the latest Android target version
|
||||
- Fixed deletion of folders without a dir-file in local storage cloud
|
||||
- Fixed file export when no app can process the selected file type
|
@ -1,4 +1,5 @@
|
||||
<ul>
|
||||
<li>Show information when "Storage" permission revoked, required for auto upload</li>
|
||||
<li>Logging out of a cloud now also clears the credentials of an active connection to it</li>
|
||||
<li>Updated to the latest Android target version</li>
|
||||
<li>Fixed deletion of folders without a dir-file in local storage cloud</li>
|
||||
<li>Fixed file export when no app can process the selected file type</li>
|
||||
</ul>
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
BIN
lib/google-http-client-1.41.8-sources.jar
Normal file
BIN
lib/google-http-client-1.41.8-sources.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
lib/google-http-client-android-1.41.8-sources.jar
Normal file
BIN
lib/google-http-client-android-1.41.8-sources.jar
Normal file
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
||||
Subproject commit dc4d0897f7917f026376d35f9a6eaf6edbc7115d
|
||||
Subproject commit 4834dde955127b1760be9bb527e9d45613b1f036
|
@ -50,8 +50,6 @@ android {
|
||||
buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID') + "\""
|
||||
|
||||
manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY'), ONEDRIVE_API_KEY_DECODED: getOnedriveApiKey()]
|
||||
|
||||
resValue "string", "app_id", androidApplicationId
|
||||
}
|
||||
|
||||
debug {
|
||||
@ -69,8 +67,6 @@ android {
|
||||
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix '-DEBUG'
|
||||
|
||||
resValue "string", "app_id", androidApplicationId + applicationIdSuffix
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,19 +84,30 @@ android {
|
||||
fdroid {
|
||||
dimension "version"
|
||||
}
|
||||
|
||||
lite {
|
||||
dimension "version"
|
||||
|
||||
applicationIdSuffix ".lite"
|
||||
resValue "string", "app_id", androidApplicationId + applicationIdSuffix
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
playstore {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
||||
java.srcDirs = ['src/main/java', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||
}
|
||||
|
||||
apkstore {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||
}
|
||||
|
||||
fdroid {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/foss/java', 'src/foss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/fdroid/java/', 'src/fdroidAndLite/java/']
|
||||
}
|
||||
|
||||
lite {
|
||||
java.srcDirs = ['src/main/java/', 'src/lite/java/', 'src/fdroidAndLite/java/']
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
@ -142,14 +149,22 @@ dependencies {
|
||||
implementation dependencies.androidxCore
|
||||
implementation dependencies.androidxFragment
|
||||
implementation dependencies.androidxViewpager
|
||||
implementation dependencies.androidxSplashscreen
|
||||
implementation dependencies.androidxSwiperefresh
|
||||
implementation dependencies.androidxPreference
|
||||
implementation dependencies.androidxBiometric
|
||||
|
||||
// cloud
|
||||
implementation dependencies.dropbox
|
||||
implementation dependencies.msgraph
|
||||
implementation dependencies.msgraphAuth
|
||||
playstoreImplementation dependencies.dropbox
|
||||
apkstoreImplementation dependencies.dropbox
|
||||
fdroidImplementation dependencies.dropbox
|
||||
|
||||
playstoreImplementation dependencies.msgraphAuth
|
||||
apkstoreImplementation dependencies.msgraphAuth
|
||||
fdroidImplementation dependencies.msgraphAuth
|
||||
playstoreImplementation dependencies.msgraph
|
||||
apkstoreImplementation dependencies.msgraph
|
||||
fdroidImplementation dependencies.msgraph
|
||||
|
||||
playstoreImplementation(dependencies.googleApiServicesDrive) {
|
||||
exclude module: 'guava-jdk5'
|
||||
|
@ -1,422 +0,0 @@
|
||||
package org.cryptomator.presentation;
|
||||
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
|
||||
import org.cryptomator.data.cloud.local.file.RootLocalFolder;
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.CloudFile;
|
||||
import org.cryptomator.domain.CloudFolder;
|
||||
import org.cryptomator.domain.CloudNode;
|
||||
import org.cryptomator.domain.CloudType;
|
||||
import org.cryptomator.domain.LocalStorageCloud;
|
||||
import org.cryptomator.domain.exception.BackendException;
|
||||
import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException;
|
||||
import org.cryptomator.domain.repository.CloudContentRepository;
|
||||
import org.cryptomator.domain.usecases.ProgressAware;
|
||||
import org.cryptomator.domain.usecases.cloud.ByteArrayDataSource;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
import org.cryptomator.presentation.testCloud.CryptoTestCloud;
|
||||
import org.cryptomator.presentation.testCloud.DropboxTestCloud;
|
||||
import org.cryptomator.presentation.testCloud.GoogledriveTestCloud;
|
||||
import org.cryptomator.presentation.testCloud.LocalStorageTestCloud;
|
||||
import org.cryptomator.presentation.testCloud.LocalTestCloud;
|
||||
import org.cryptomator.presentation.testCloud.OnedriveTestCloud;
|
||||
import org.cryptomator.presentation.testCloud.TestCloud;
|
||||
import org.cryptomator.presentation.testCloud.WebdavTestCloud;
|
||||
import org.cryptomator.presentation.ui.activity.SplashActivity;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static androidx.test.InstrumentationRegistry.getTargetContext;
|
||||
import static org.cryptomator.presentation.CloudNodeMatchers.aFile;
|
||||
import static org.cryptomator.presentation.CloudNodeMatchers.folder;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.collection.IsEmptyCollection.emptyCollectionOf;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class CloudContentRepositoryBlackboxTest {
|
||||
|
||||
private static final byte[] DIGITS_ONE_TO_TEN_AS_BYTES = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
private static final byte[] DIGITS_SEVEN_TO_ONE_AS_BYTES = new byte[] {7, 6, 5, 4, 3, 2, 1};
|
||||
|
||||
private static Cloud cloud;
|
||||
private static TestCloud inTestCloud;
|
||||
private static boolean setupCloudCompleted = false;
|
||||
@Rule
|
||||
public final ActivityTestRule<SplashActivity> activityTestRule = new ActivityTestRule<>(SplashActivity.class);
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
private CloudContentRepository inTest;
|
||||
private CloudFolder root;
|
||||
|
||||
public CloudContentRepositoryBlackboxTest(TestCloud testCloud) {
|
||||
if (inTestCloud != null && inTestCloud != testCloud) {
|
||||
setupCloudCompleted = false;
|
||||
}
|
||||
|
||||
inTestCloud = testCloud;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
public static TestCloud[] data() {
|
||||
return new TestCloud[] { //
|
||||
new LocalStorageTestCloud(), //
|
||||
new LocalTestCloud(), //
|
||||
new WebdavTestCloud(getTargetContext()), //
|
||||
new DropboxTestCloud(getTargetContext()), //
|
||||
new GoogledriveTestCloud(), //
|
||||
new OnedriveTestCloud(getTargetContext()), //
|
||||
new CryptoTestCloud()};
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws BackendException {
|
||||
|
||||
ApplicationComponent appComponent = ((CryptomatorApp) activityTestRule //
|
||||
.getActivity() //
|
||||
.getApplication()) //
|
||||
.getComponent();
|
||||
|
||||
if (!setupCloudCompleted) {
|
||||
if (inTestCloud instanceof CryptoTestCloud) {
|
||||
// FIXME 343 @julian just for testcase local cloud
|
||||
Cloud testCloud = appComponent.cloudRepository().clouds(CloudType.LOCAL).get(0);
|
||||
CloudFolder rootFolder = new RootLocalFolder((LocalStorageCloud) testCloud);
|
||||
cloud = ((CryptoTestCloud) inTestCloud).getInstance(appComponent, testCloud, rootFolder);
|
||||
} else {
|
||||
cloud = inTestCloud.getInstance(appComponent);
|
||||
}
|
||||
|
||||
setupCloudCompleted = true;
|
||||
}
|
||||
|
||||
inTest = appComponent.cloudContentRepository();
|
||||
root = inTest.create(inTest.resolve(cloud, UUID.randomUUID().toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListEmptyDirectory() throws BackendException {
|
||||
assertThat(listingOf(root), is(emptyCollectionOf(CloudNode.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListDirectory() throws BackendException {
|
||||
createParentsAndWrite("a", DIGITS_SEVEN_TO_ONE_AS_BYTES);
|
||||
createParentsAndWrite("b.dat", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
createParentsAndWrite("empty.txt", new byte[0]);
|
||||
inTest.create(inTest.folder(root, "b"));
|
||||
inTest.create(inTest.folder(root, "c"));
|
||||
|
||||
assertThat(listingOf(root), containsInAnyOrder( //
|
||||
aFile().withName("a").withSize(DIGITS_SEVEN_TO_ONE_AS_BYTES.length), //
|
||||
aFile().withName("b.dat").withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length), //
|
||||
aFile().withName("empty.txt").withSize(0L), //
|
||||
folder("b"), //
|
||||
folder("c")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDirectory() throws BackendException {
|
||||
CloudFolder created = inTest.folder(root, "created");
|
||||
created = inTest.create(created);
|
||||
|
||||
assertThat(listingOf(created), is(emptyCollectionOf(CloudNode.class)));
|
||||
assertThat(listingOf(root), containsInAnyOrder(folder("created")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteDirectory() throws BackendException {
|
||||
inTest.create(inTest.folder(root, "created"));
|
||||
inTest.delete(inTest.folder(root, "created"));
|
||||
|
||||
assertThat(inTest.exists(inTest.folder(root, "created")), is(false));
|
||||
assertThat(listingOf(root), is(emptyCollectionOf(CloudNode.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testUploadFile() throws BackendException {
|
||||
Date start = new Date();
|
||||
CloudFile file = createParentsAndWrite("file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
|
||||
assertThat(file, is(aFile() //
|
||||
.withName("file") //
|
||||
.withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length) //
|
||||
.withModifiedIn(start, new Date())));
|
||||
assertThat(listingOf(root), //
|
||||
containsInAnyOrder(aFile() //
|
||||
.withName("file") //
|
||||
.withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testReplaceFile() throws BackendException {
|
||||
createParentsAndWrite("file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
Date start = new Date();
|
||||
CloudFile file = createParentsAndWriteOrReplace("file", DIGITS_SEVEN_TO_ONE_AS_BYTES);
|
||||
|
||||
assertThat(file, is(aFile() //
|
||||
.withName("file") //
|
||||
.withSize(DIGITS_SEVEN_TO_ONE_AS_BYTES.length) //
|
||||
.withModifiedIn(start, new Date())));
|
||||
assertThat(listingOf(root), //
|
||||
containsInAnyOrder(aFile() //
|
||||
.withName("file") //
|
||||
.withSize(DIGITS_SEVEN_TO_ONE_AS_BYTES.length)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadExistingFileWithoutReplaceFlag() throws BackendException {
|
||||
createParentsAndWrite("file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
|
||||
thrown.expect(CloudNodeAlreadyExistsException.class);
|
||||
thrown.expectMessage("CloudNode already exists and replace is false");
|
||||
|
||||
createParentsAndWrite("file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadFile() throws BackendException {
|
||||
createParentsAndWrite("file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
|
||||
assertThat(read(inTest.file(root, "file")), is(DIGITS_ONE_TO_TEN_AS_BYTES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteFile() throws BackendException {
|
||||
createParentsAndWrite("file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
inTest.delete(inTest.file(root, "file"));
|
||||
|
||||
assertThat(inTest.exists(inTest.file(root, "file")), is(false));
|
||||
assertThat(listingOf(root), is(emptyCollectionOf(CloudNode.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testRenameDirectory() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder directory = file.getParent();
|
||||
CloudFolder target = inTest.folder(root, "newName");
|
||||
|
||||
target = inTest.move(directory, target);
|
||||
|
||||
assertThat(listingOf(target), containsInAnyOrder(aFile() //
|
||||
.withName("file") //
|
||||
.withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenameDirectoryToExistingDirectory() throws BackendException {
|
||||
CloudFolder directory = inTest.folder(root, "directory");
|
||||
directory = inTest.create(directory);
|
||||
CloudFolder target = inTest.folder(root, "newName");
|
||||
target = inTest.create(target);
|
||||
|
||||
thrown.expect(CloudNodeAlreadyExistsException.class);
|
||||
thrown.expectMessage("newName");
|
||||
|
||||
inTest.move(directory, target);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testRenameDirectoryToExistingFile() throws BackendException {
|
||||
CloudFolder directory = inTest.folder(root, "directory");
|
||||
directory = inTest.create(directory);
|
||||
CloudFolder target = inTest.folder(root, "newName");
|
||||
createParentsAndWrite("newName", new byte[0]);
|
||||
|
||||
thrown.expect(CloudNodeAlreadyExistsException.class);
|
||||
thrown.expectMessage("newName");
|
||||
|
||||
inTest.move(directory, target);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMoveDirectory() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder directory = file.getParent();
|
||||
CloudFolder newParent = inTest.create(inTest.folder(root, "newParent"));
|
||||
CloudFolder target = inTest.folder(newParent, "directory");
|
||||
|
||||
target = inTest.move(directory, target);
|
||||
|
||||
assertThat(listingOf(target), containsInAnyOrder(aFile() //
|
||||
.withName("file") //
|
||||
.withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMoveDirectoryToExistingDirectory() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder directory = file.getParent();
|
||||
CloudFolder newParent = inTest.create(inTest.folder(root, "newParent"));
|
||||
CloudFolder target = inTest.folder(newParent, "directory");
|
||||
target = inTest.create(target);
|
||||
|
||||
thrown.expect(CloudNodeAlreadyExistsException.class);
|
||||
thrown.expectMessage("directory");
|
||||
|
||||
inTest.move(directory, target);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMoveDirectoryToExistingFile() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder directory = file.getParent();
|
||||
CloudFolder newParent = inTest.create(inTest.folder(root, "newParent"));
|
||||
CloudFolder target = inTest.folder(newParent, "directory");
|
||||
createParentsAndWrite("newParent/directory", new byte[0]);
|
||||
|
||||
thrown.expect(CloudNodeAlreadyExistsException.class);
|
||||
thrown.expectMessage("directory");
|
||||
|
||||
inTest.move(directory, target);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMoveAndRenameDirectory() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder directory = file.getParent();
|
||||
CloudFolder newParent = inTest.create(inTest.folder(root, "newParent"));
|
||||
CloudFolder target = inTest.folder(newParent, "newName");
|
||||
|
||||
target = inTest.move(directory, target);
|
||||
|
||||
assertThat(listingOf(target), containsInAnyOrder(aFile() //
|
||||
.withName("file") //
|
||||
.withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMoveAndRenameDirectoryToExistingDirectory() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder directory = file.getParent();
|
||||
CloudFolder newParent = inTest.create(inTest.folder(root, "newParent"));
|
||||
CloudFolder target = inTest.folder(newParent, "newName");
|
||||
target = inTest.create(target);
|
||||
|
||||
thrown.expect(CloudNodeAlreadyExistsException.class);
|
||||
thrown.expectMessage("newName");
|
||||
|
||||
inTest.move(directory, target);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMoveAndRenameDirectoryToExistingFile() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder directory = file.getParent();
|
||||
CloudFolder newParent = inTest.create(inTest.folder(root, "newParent"));
|
||||
CloudFolder target = inTest.folder(newParent, "newName");
|
||||
createParentsAndWrite("newParent/newName", new byte[0]);
|
||||
|
||||
thrown.expect(CloudNodeAlreadyExistsException.class);
|
||||
thrown.expectMessage("newName");
|
||||
|
||||
inTest.move(directory, target);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testRenameFile() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFile target = inTest.file(file.getParent(), "newName");
|
||||
|
||||
target = inTest.move(file, target);
|
||||
|
||||
assertThat(listingOf(file.getParent()), containsInAnyOrder(aFile() //
|
||||
.withName("newName") //
|
||||
.withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length)));
|
||||
assertThat(read(target), is(DIGITS_ONE_TO_TEN_AS_BYTES));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMoveFile() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder oldParent = file.getParent();
|
||||
CloudFolder newParent = inTest.create(inTest.folder(root, "newParent"));
|
||||
CloudFile target = inTest.file(newParent, "file");
|
||||
|
||||
target = inTest.move(file, target);
|
||||
|
||||
assertThat(listingOf(oldParent), is(emptyCollectionOf(CloudNode.class)));
|
||||
assertThat(listingOf(newParent), containsInAnyOrder(aFile() //
|
||||
.withName("file") //
|
||||
.withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length)));
|
||||
assertThat(read(target), is(DIGITS_ONE_TO_TEN_AS_BYTES));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testMoveAndRenameFile() throws BackendException {
|
||||
CloudFile file = createParentsAndWrite("directory/file", DIGITS_ONE_TO_TEN_AS_BYTES);
|
||||
CloudFolder oldParent = file.getParent();
|
||||
CloudFolder newParent = inTest.create(inTest.folder(root, "newParent"));
|
||||
CloudFile target = inTest.file(newParent, "newName");
|
||||
|
||||
target = inTest.move(file, target);
|
||||
|
||||
assertThat(listingOf(oldParent), is(emptyCollectionOf(CloudNode.class)));
|
||||
assertThat(listingOf(newParent), containsInAnyOrder(aFile() //
|
||||
.withName("newName") //
|
||||
.withSize(DIGITS_ONE_TO_TEN_AS_BYTES.length)));
|
||||
assertThat(read(target), is(DIGITS_ONE_TO_TEN_AS_BYTES));
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() throws BackendException {
|
||||
if (inTest != null && root != null) {
|
||||
inTest.delete(root);
|
||||
}
|
||||
}
|
||||
|
||||
private List<CloudNode> listingOf(CloudFolder testRoot) throws BackendException {
|
||||
return inTest.list(testRoot);
|
||||
}
|
||||
|
||||
private byte[] read(CloudFile file) throws BackendException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
inTest.read(file, null, out, ProgressAware.NO_OP_PROGRESS_AWARE_DOWNLOAD);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private CloudFile createParentsAndWrite(String path, byte[] data) throws BackendException {
|
||||
return createParentsAndWriteOrReplaceImpl(path, data, false);
|
||||
}
|
||||
|
||||
private CloudFile createParentsAndWriteOrReplace(String path, byte[] data) throws BackendException {
|
||||
return createParentsAndWriteOrReplaceImpl(path, data, true);
|
||||
}
|
||||
|
||||
private CloudFile createParentsAndWriteOrReplaceImpl(String path, byte[] data, boolean repalce) throws BackendException {
|
||||
path = root.getName() + "/" + path;
|
||||
String pathToParent = path.substring(0, path.lastIndexOf('/') + 1);
|
||||
String name = path.substring(path.lastIndexOf('/') + 1);
|
||||
CloudFolder parent = inTest.resolve(cloud, pathToParent);
|
||||
if (!inTest.exists(parent)) {
|
||||
parent = inTest.create(parent);
|
||||
}
|
||||
CloudFile file = inTest.file(parent, name, new Long(data.length));
|
||||
return inTest.write(file, ByteArrayDataSource.from(data), ProgressAware.NO_OP_PROGRESS_AWARE_UPLOAD, repalce, data.length);
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
package org.cryptomator.presentation;
|
||||
|
||||
import org.cryptomator.domain.CloudFile;
|
||||
import org.cryptomator.domain.CloudFolder;
|
||||
import org.cryptomator.domain.CloudNode;
|
||||
import com.google.common.base.Optional;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
class CloudNodeMatchers {
|
||||
|
||||
public static Matcher<CloudNode> aFile(final String name) {
|
||||
return (Matcher) new TypeSafeDiagnosingMatcher<CloudFile>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(CloudFile file, Description description) {
|
||||
if (name.equals(file.getName())) {
|
||||
return true;
|
||||
} else {
|
||||
description.appendText("aFile with name '").appendText(file.getName()).appendText("'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("aFile with name '").appendText(name).appendText("'");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static FileMatcher aFile() {
|
||||
return new FileMatcher();
|
||||
}
|
||||
|
||||
public static Matcher<CloudNode> folder(final String name) {
|
||||
return (Matcher) new TypeSafeDiagnosingMatcher<CloudFolder>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(CloudFolder file, org.hamcrest.Description description) {
|
||||
if (name.equals(file.getName())) {
|
||||
return true;
|
||||
} else {
|
||||
description.appendText("folder with name '").appendText(file.getName()).appendText("'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(org.hamcrest.Description description) {
|
||||
description.appendText("folder with name '").appendText(name).appendText("'");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class FileMatcher extends TypeSafeDiagnosingMatcher<CloudNode> {
|
||||
|
||||
private String nameToCheck;
|
||||
private Optional<Long> sizeToCheck;
|
||||
|
||||
private Date minModifiedToCheck;
|
||||
private Date maxModifiedToCheck;
|
||||
|
||||
private FileMatcher() {
|
||||
super(CloudFile.class);
|
||||
}
|
||||
|
||||
public FileMatcher withName(String name) {
|
||||
this.nameToCheck = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileMatcher withSize(int size) {
|
||||
return withSize(Long.valueOf(size));
|
||||
}
|
||||
|
||||
public FileMatcher withSize(Long size) {
|
||||
this.sizeToCheck = Optional.ofNullable(size);
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileMatcher withModifiedIn(Date minModified, Date maxModified) {
|
||||
this.minModifiedToCheck = minModified;
|
||||
this.maxModifiedToCheck = maxModified;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("a file");
|
||||
if (nameToCheck != null) {
|
||||
description.appendText(" with name ").appendText(nameToCheck);
|
||||
}
|
||||
if (sizeToCheck != null) {
|
||||
description.appendText(" with size ").appendValue(sizeToCheck);
|
||||
}
|
||||
if (minModifiedToCheck != null) {
|
||||
description.appendText(" with modified in [").appendValue(minModifiedToCheck).appendText(",").appendValue(maxModifiedToCheck).appendText("]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(CloudNode cloudNode, Description description) {
|
||||
CloudFile cloudFile = (CloudFile) cloudNode;
|
||||
boolean match = true;
|
||||
description.appendText("a file");
|
||||
if (nameToCheck != null && !nameToCheck.equals(cloudFile.getName())) {
|
||||
description.appendText(" with name ").appendText(cloudFile.getName());
|
||||
match = false;
|
||||
}
|
||||
if (sizeToCheck != null && !sizeToCheck.equals(cloudFile.getSize())) {
|
||||
description.appendText(" with size ").appendValue(cloudFile.getSize());
|
||||
match = false;
|
||||
}
|
||||
if (minModifiedToCheck != null && dateInRange(minModifiedToCheck, maxModifiedToCheck, cloudFile.getModified())) {
|
||||
description.appendText(" with modified ").appendValue(cloudFile.getModified());
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
private boolean dateInRange(Date min, Date max, Optional<Date> modified) {
|
||||
return modified.isPresent() && !modified.get().before(min) && !modified.get().after(max);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package org.cryptomator.presentation.testCloud;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.CloudFolder;
|
||||
import org.cryptomator.domain.Vault;
|
||||
import org.cryptomator.domain.exception.BackendException;
|
||||
import org.cryptomator.domain.exception.FatalBackendException;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
|
||||
import static org.cryptomator.domain.Vault.aVault;
|
||||
|
||||
public class CryptoTestCloud extends TestCloud {
|
||||
|
||||
private final static String VAULT_PASSWORD = "password";
|
||||
private final static String VAULT_NAME = "testVault";
|
||||
|
||||
@Override
|
||||
public Cloud getInstance(ApplicationComponent appComponent) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public Cloud getInstance(ApplicationComponent appComponent, Cloud testCloud, CloudFolder rootFolder) {
|
||||
try {
|
||||
CloudFolder vaultFolder = appComponent //
|
||||
.cloudContentRepository() //
|
||||
.folder(rootFolder, VAULT_NAME);
|
||||
|
||||
Vault vault = aVault() //
|
||||
.thatIsNew() //
|
||||
.withCloud(testCloud) //
|
||||
.withNamePathAndCloudFrom(vaultFolder) //
|
||||
.build();
|
||||
|
||||
cleanup(appComponent, vault, vaultFolder);
|
||||
|
||||
vaultFolder = appComponent.cloudContentRepository().create(vaultFolder);
|
||||
appComponent.cloudRepository().create(vaultFolder, VAULT_PASSWORD);
|
||||
vault = appComponent.vaultRepository().store(vault);
|
||||
|
||||
return appComponent.cloudRepository().unlock(vault, VAULT_PASSWORD, () -> false);
|
||||
} catch (BackendException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanup(ApplicationComponent appComponent, Vault vault, CloudFolder vaultFolder) {
|
||||
try {
|
||||
appComponent.cloudContentRepository().delete(vaultFolder);
|
||||
} catch (BackendException | FatalBackendException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
appComponent.vaultRepository().vaults().forEach(vaultInRepo -> {
|
||||
if (vaultInRepo.getName().equals(vault.getName()) //
|
||||
&& vaultInRepo.getPath().equals(vault.getPath())) {
|
||||
try {
|
||||
appComponent.vaultRepository().delete(vaultInRepo);
|
||||
} catch (BackendException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (FatalBackendException | BackendException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CryptoTestCloud";
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package org.cryptomator.presentation.testCloud;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.WebDavCloud;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
import org.cryptomator.util.crypto.CredentialCryptor;
|
||||
|
||||
public class DropboxTestCloud extends TestCloud {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public DropboxTestCloud(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cloud getInstance(ApplicationComponent appComponent) {
|
||||
return WebDavCloud.aWebDavCloudCloud() //
|
||||
.withUrl("https://webdav.mc.gmx.net") //
|
||||
.withUsername("jraufelder@gmx.de") //
|
||||
.withPassword(CredentialCryptor.getInstance(context).encrypt("mG7!3B3Mx")) //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DropboxTestCloud";
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
package org.cryptomator.presentation.testCloud;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.UiObjectNotFoundException;
|
||||
import androidx.test.uiautomator.UiSelector;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.GoogleDriveCloud;
|
||||
import org.cryptomator.presentation.R;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.GOOGLE_DRIVE;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.withRecyclerView;
|
||||
import static org.cryptomator.presentation.ui.activity.CloudsOperationsTest.checkLoginResult;
|
||||
import static org.cryptomator.presentation.ui.activity.CloudsOperationsTest.openCloudServices;
|
||||
|
||||
public class GoogledriveTestCloud extends TestCloud {
|
||||
|
||||
@Override
|
||||
public Cloud getInstance(ApplicationComponent appComponent) {
|
||||
login();
|
||||
return GoogleDriveCloud.aGoogleDriveCloud() //
|
||||
.withUsername("geselthyn@googlemail.com") //
|
||||
.withAccessToken("geselthyn@googlemail.com") //
|
||||
.build();
|
||||
}
|
||||
|
||||
public void login() {
|
||||
UiDevice device = UiDevice.getInstance(getInstrumentation());
|
||||
|
||||
openCloudServices(device);
|
||||
|
||||
if (alreadyLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(GOOGLE_DRIVE, click()));
|
||||
|
||||
try {
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("android:id/text1")) //
|
||||
.click();
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("android:id/button1")) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("GoogleDrive login failed");
|
||||
}
|
||||
|
||||
device.waitForIdle();
|
||||
|
||||
checkLoginResult("Google Drive", GOOGLE_DRIVE);
|
||||
}
|
||||
|
||||
private boolean alreadyLoggedIn() {
|
||||
try {
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(GOOGLE_DRIVE, R.id.tv_cloud_name)) //
|
||||
.check(matches(withText(InstrumentationRegistry //
|
||||
.getTargetContext() //
|
||||
.getString(R.string.screen_cloud_settings_sign_out_from_cloud) + " Google Drive")));
|
||||
} catch (AssertionFailedError e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GoogledriveTestCloud";
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package org.cryptomator.presentation.testCloud;
|
||||
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.CloudType;
|
||||
import org.cryptomator.domain.LocalStorageCloud;
|
||||
import org.cryptomator.domain.exception.BackendException;
|
||||
import org.cryptomator.presentation.R;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static org.cryptomator.domain.executor.BackgroundTasks.awaitCompleted;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.LOCAL;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.chooseSdCard;
|
||||
import static org.cryptomator.presentation.ui.activity.CloudsOperationsTest.openCloudServices;
|
||||
import static org.cryptomator.presentation.ui.activity.LoginLocalClouds.chooseFolder;
|
||||
|
||||
public class LocalStorageTestCloud extends TestCloud {
|
||||
|
||||
@Override
|
||||
public Cloud getInstance(ApplicationComponent appComponent) {
|
||||
login();
|
||||
try {
|
||||
return appComponent.cloudRepository() //
|
||||
.clouds(CloudType.LOCAL).stream() //
|
||||
.map(LocalStorageCloud.class::cast) //
|
||||
.filter(cloud -> cloud.rootUri() != null) //
|
||||
.findFirst() //
|
||||
.get();
|
||||
} catch (BackendException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void login() {
|
||||
UiDevice device = UiDevice.getInstance(getInstrumentation());
|
||||
|
||||
openCloudServices(device);
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(LOCAL, click()));
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.floating_action_button)) //
|
||||
.perform(click());
|
||||
awaitCompleted();
|
||||
|
||||
chooseSdCard(device);
|
||||
awaitCompleted();
|
||||
|
||||
chooseFolder(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LocalStorageTestCloud";
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package org.cryptomator.presentation.testCloud;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.LocalStorageCloud;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
public class LocalTestCloud extends TestCloud {
|
||||
|
||||
@Override
|
||||
public Cloud getInstance(ApplicationComponent appComponent) {
|
||||
getInstrumentation() //
|
||||
.getUiAutomation() //
|
||||
.executeShellCommand("pm grant " + "org.cryptomator" + " android.permission.READ_EXTERNAL_STORAGE");
|
||||
|
||||
getInstrumentation() //
|
||||
.getUiAutomation() //
|
||||
.executeShellCommand("pm grant " + "org.cryptomator" + " android.permission.WRITE_EXTERNAL_STORAGE");
|
||||
|
||||
return LocalStorageCloud.aLocalStorage().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LocalTestCloud";
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package org.cryptomator.presentation.testCloud;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.OnedriveCloud;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
|
||||
public class OnedriveTestCloud extends TestCloud {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public OnedriveTestCloud(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cloud getInstance(ApplicationComponent appComponent) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("com.microsoft.live", 0);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("refresh_token", "OAQABAAAAAABHh4kmS_aKT5XrjzxRAtHzMkxzFfIzutF8Q04IwuU77Vmp5-ErO6muS0-watCiLjvPG" + //
|
||||
"-QICK6PwzSsJZApKZPIyTgDxJuElkhM9j9Caa-NGXKx3JPZx4Tk5X3zhmSWebZdQu2TM1N" + //
|
||||
"RrKWLl2k2m3Zj7xr1zEsjcr4tUjjrZl_W6EIglAIoi-DPOwznIEDWAzWCJeUY76CIpywax" + //
|
||||
"RTsbcZXD3u2321kIGaL-bnGLa_z5IzUbal0PcYfPNYPXXTuv8eMyl4L9Tls1tSEseTHgCu" + //
|
||||
"X3OZU2owiGQk6ycDAeyrbRaPoUOA78GYuaJGUXRmhAeH1WBccUzABdrZmAY1dAt4yu2eV7" + //
|
||||
"70RzrDQhbLCPV3u3x-7xEtvsM8w0W7096VBuu2-MXvaWuDccnCHo_PK8ketpow19_llBI9" + //
|
||||
"fx7yBnIU-HCkKuvOCKcvVq3Bv9r312bwAoWHdOrxsKrNK8aLoR317O9Cxjpr7q-YI3NSJJ" + //
|
||||
"veTK_vn2uE0e6gppGOYSmpJIvoW82ZVngpptW0jsp6rOsnkSg2yfKKpIPN-n0U1njVlYf8" + //
|
||||
"cwsS99tx8NDdCPS6MTkVmcdKRJ4dhMuuWdEm4E_hZRnnj2Pya3APxjL2eqyAR1-54TDLF-" + //
|
||||
"-L-jIJUS4bVpC_RBQn4fxcrX4s-ddo6I0ejfdC07mU_Np9p66VJ_3_Yokt64fFw-zzaGpn" + //
|
||||
"QEzRMxtJp5G40MAelYwxLhDIc-syw91JEoSSqfGJYHETKExPlnQOw-rqLGPFbIF6OKFNqO" + //
|
||||
"XtVLWKZFxIEf-EFQbhq5igZz8DZ2n-cRxC3HWi_x18tVSAA");
|
||||
editor.commit();
|
||||
|
||||
return OnedriveCloud.aOnedriveCloud() //
|
||||
.withUsername("info@cryptomator.org") //
|
||||
.withAccessToken("authenticated") //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OnedriveTestCloud";
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package org.cryptomator.presentation.testCloud;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
|
||||
public abstract class TestCloud {
|
||||
|
||||
public abstract Cloud getInstance(ApplicationComponent appComponent);
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package org.cryptomator.presentation.testCloud;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.WebDavCloud;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
import org.cryptomator.util.crypto.CredentialCryptor;
|
||||
|
||||
public class WebdavTestCloud extends TestCloud {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public WebdavTestCloud(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cloud getInstance(ApplicationComponent appComponent) {
|
||||
return WebDavCloud.aWebDavCloudCloud() //
|
||||
.withUrl("https://webdav.mc.gmx.net") //
|
||||
.withUsername("jraufelder@gmx.de") //
|
||||
.withPassword(CredentialCryptor.getInstance(context).encrypt("mG7!3B3Mx")) //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WebdavTestCloud";
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package org.cryptomator.presentation.ui;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
/**
|
||||
* Created by dannyroa on 5/10/15.
|
||||
*/
|
||||
public class RecyclerViewMatcher {
|
||||
|
||||
private final int recyclerViewId;
|
||||
|
||||
public RecyclerViewMatcher(int recyclerViewId) {
|
||||
this.recyclerViewId = recyclerViewId;
|
||||
}
|
||||
|
||||
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
|
||||
|
||||
return new TypeSafeMatcher<View>() {
|
||||
Resources resources = null;
|
||||
View childView;
|
||||
|
||||
public void describeTo(Description description) {
|
||||
String idDescription = Integer.toString(recyclerViewId);
|
||||
if (this.resources != null) {
|
||||
try {
|
||||
idDescription = this.resources.getResourceName(recyclerViewId);
|
||||
} catch (Resources.NotFoundException var4) {
|
||||
idDescription = String.format("%s (resource name not found)", recyclerViewId);
|
||||
}
|
||||
}
|
||||
|
||||
description.appendText("with id: " + idDescription);
|
||||
}
|
||||
|
||||
public boolean matchesSafely(View view) {
|
||||
|
||||
this.resources = view.getResources();
|
||||
|
||||
if (childView == null) {
|
||||
RecyclerView recyclerView = view.getRootView().findViewById(recyclerViewId);
|
||||
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
|
||||
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetViewId == -1) {
|
||||
return view == childView;
|
||||
} else {
|
||||
View targetView = childView.findViewById(targetViewId);
|
||||
return view == targetView;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
package org.cryptomator.presentation.ui;
|
||||
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.UiObjectNotFoundException;
|
||||
import androidx.test.uiautomator.UiSelector;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.CloudFolder;
|
||||
import org.cryptomator.domain.CloudNode;
|
||||
import org.cryptomator.domain.CloudType;
|
||||
import org.cryptomator.domain.Vault;
|
||||
import org.cryptomator.domain.exception.BackendException;
|
||||
import org.cryptomator.presentation.R;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static org.cryptomator.domain.executor.BackgroundTasks.awaitCompleted;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
public class TestUtil {
|
||||
|
||||
public static final int DROPBOX = 0;
|
||||
public static final int GOOGLE_DRIVE = 1;
|
||||
public static final int ONEDRIVE = 2;
|
||||
public static final int WEBDAV = 3;
|
||||
public static final int LOCAL = 4;
|
||||
|
||||
private static final String SD_CARD_REGEX = "(?i)SD[- ]*(CARD|KARTE)";
|
||||
|
||||
public static void isToastDisplayed(String message, ActivityTestRule activityTestRule) {
|
||||
onView(withText(message)) //
|
||||
.inRoot(withDecorView(not(is(activityTestRule.getActivity().getWindow().getDecorView())))) //
|
||||
.check(matches(isDisplayed()));
|
||||
}
|
||||
|
||||
public static void openMenu(UiDevice device) {
|
||||
try {
|
||||
final UiSelector toolbar = new UiSelector() //
|
||||
.resourceId("com.android.documentsui:id/toolbar");
|
||||
|
||||
device //
|
||||
.findObject(toolbar.childSelector(new UiSelector().index(0))) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Menu not found");
|
||||
}
|
||||
}
|
||||
|
||||
public static void chooseSdCard(UiDevice device) {
|
||||
try {
|
||||
if (!sdCardAlreadySelected()) {
|
||||
openMenu(device);
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().textMatches(SD_CARD_REGEX)) //
|
||||
.click();
|
||||
}
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Menu not found");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean sdCardAlreadySelected() {
|
||||
ViewInteraction textView = onView(allOf(withText("SDCARD"), withParent(withId(R.id.toolbar)), isDisplayed()));
|
||||
return textView.check(matches(withText("SDCARD"))) != null;
|
||||
}
|
||||
|
||||
public static void openSettings(UiDevice device) {
|
||||
awaitCompleted();
|
||||
|
||||
waitForIdle(device);
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
|
||||
|
||||
waitForIdle(device);
|
||||
|
||||
onView(allOf( //
|
||||
withId(R.id.title), //
|
||||
withText(R.string.snack_bar_action_title_settings))) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
}
|
||||
|
||||
public static void waitForIdle(UiDevice uiDevice) {
|
||||
uiDevice.waitForIdle();
|
||||
}
|
||||
|
||||
public static void addFolderInCloud(ApplicationComponent appComponent, String path, CloudType cloudType) {
|
||||
try {
|
||||
CloudFolder vaultFolder = (CloudFolder) getNode(appComponent, getEncryptedCloud(appComponent, cloudType), path);
|
||||
|
||||
if (!appComponent.cloudContentRepository().exists(vaultFolder)) {
|
||||
assertThat(appComponent.cloudContentRepository().create(vaultFolder), Matchers.is(notNullValue()));
|
||||
}
|
||||
} catch (BackendException e) {
|
||||
throw new AssertionError("Error while adding testVault");
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeFolderInCloud(ApplicationComponent appComponent, String path, CloudType cloudType) {
|
||||
try {
|
||||
CloudFolder vaultFolder = (CloudFolder) getNode(appComponent, getEncryptedCloud(appComponent, cloudType), path);
|
||||
|
||||
if (appComponent.cloudContentRepository().exists(vaultFolder)) {
|
||||
appComponent.cloudContentRepository().delete(vaultFolder);
|
||||
}
|
||||
} catch (BackendException e) {
|
||||
throw new AssertionError("Error while removing testVault");
|
||||
}
|
||||
}
|
||||
|
||||
private static Cloud getEncryptedCloud(ApplicationComponent appComponent, CloudType cloudType) throws BackendException {
|
||||
Cloud cloud;
|
||||
if (cloudType.equals(CloudType.LOCAL)) {
|
||||
cloud = appComponent.cloudRepository().clouds(cloudType).get(1);
|
||||
} else {
|
||||
cloud = appComponent.cloudRepository().clouds(cloudType).get(0);
|
||||
}
|
||||
|
||||
return cloud;
|
||||
}
|
||||
|
||||
private static CloudNode getNode(ApplicationComponent appComponent, Cloud cloud, String path) throws BackendException {
|
||||
return appComponent.cloudContentRepository().resolve(cloud, path);
|
||||
}
|
||||
|
||||
public static void removeFolderInVault(ApplicationComponent appComponent, String name, CloudType cloudType) {
|
||||
try {
|
||||
Cloud decryptedCloud = getDecryptedCloud(appComponent, cloudType);
|
||||
CloudFolder root = appComponent.cloudContentRepository().root(decryptedCloud);
|
||||
CloudFolder folder = appComponent.cloudContentRepository().folder(root, name);
|
||||
appComponent.cloudContentRepository().delete(folder);
|
||||
} catch (BackendException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addFolderInVaultsRoot(ApplicationComponent appComponent, String name, CloudType cloudType) {
|
||||
try {
|
||||
Cloud decryptedCloud = getDecryptedCloud(appComponent, cloudType);
|
||||
CloudFolder root = appComponent.cloudContentRepository().root(decryptedCloud);
|
||||
CloudFolder folder = appComponent.cloudContentRepository().folder(root, name);
|
||||
assertThat(appComponent.cloudContentRepository().create(folder), is(notNullValue()));
|
||||
} catch (BackendException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Cloud getDecryptedCloud(ApplicationComponent appComponent, CloudType cloudType) throws BackendException {
|
||||
List<Vault> vaults = appComponent.vaultRepository().vaults();
|
||||
for (Vault vault : vaults) {
|
||||
if (vault.getCloudType().equals(cloudType)) {
|
||||
return appComponent.cloudRepository().decryptedViewOf(vault);
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError("Cloud for vault not found");
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package org.cryptomator.presentation.ui.activity;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
|
||||
import org.cryptomator.presentation.R;
|
||||
import org.cryptomator.presentation.ui.RecyclerViewMatcher;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static java.lang.String.format;
|
||||
import static org.cryptomator.domain.executor.BackgroundTasks.awaitCompleted;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
|
||||
public class BasicNodeOperationsUtil {
|
||||
|
||||
static void openSettings(UiDevice device, int nodePosition) {
|
||||
awaitCompleted();
|
||||
|
||||
waitForIdle(device);
|
||||
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(nodePosition, R.id.settings)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
static void waitForIdle(UiDevice uiDevice) {
|
||||
uiDevice.waitForIdle();
|
||||
}
|
||||
|
||||
static void checkEmptyFolderHint() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(allOf( //
|
||||
withId(R.id.tv_empty_folder_hint), //
|
||||
withText(R.string.screen_file_browser_msg_empty_folder))) //
|
||||
.check(matches(withText(R.string.screen_file_browser_msg_empty_folder)));
|
||||
}
|
||||
|
||||
static void checkFileOrFolderAlreadyExistsErrorMessage(String nodeName) {
|
||||
onView(withId(R.id.tv_error)) //
|
||||
.check(matches(withText(format( //
|
||||
InstrumentationRegistry //
|
||||
.getTargetContext() //
|
||||
.getString(R.string.error_file_or_folder_exists), //
|
||||
nodeName))));
|
||||
}
|
||||
|
||||
public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
|
||||
return new RecyclerViewMatcher(recyclerViewId);
|
||||
}
|
||||
}
|
@ -1,248 +0,0 @@
|
||||
package org.cryptomator.presentation.ui.activity;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.UiObject;
|
||||
import androidx.test.uiautomator.UiObjectNotFoundException;
|
||||
import androidx.test.uiautomator.UiScrollable;
|
||||
import androidx.test.uiautomator.UiSelector;
|
||||
|
||||
import org.cryptomator.presentation.R;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withChild;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static java.lang.Thread.sleep;
|
||||
import static org.cryptomator.domain.executor.BackgroundTasks.awaitCompleted;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.DROPBOX;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.GOOGLE_DRIVE;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.LOCAL;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.ONEDRIVE;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.WEBDAV;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.openSettings;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.withRecyclerView;
|
||||
import static org.hamcrest.core.AllOf.allOf;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class CloudsOperationsTest {
|
||||
|
||||
private final UiDevice device = UiDevice.getInstance(getInstrumentation());
|
||||
@Rule
|
||||
public ActivityTestRule<SplashActivity> activityTestRule //
|
||||
= new ActivityTestRule<>(SplashActivity.class);
|
||||
|
||||
public static void openCloudServices(UiDevice device) {
|
||||
openSettings(device);
|
||||
|
||||
ViewInteraction recyclerView = onView(allOf(withId(R.id.recycler_view), childAtPosition(withId(android.R.id.list_container), 0)));
|
||||
recyclerView.perform(RecyclerViewActions.actionOnItemAtPosition(3, click()));
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
public static void checkLoginResult(String cloudName, int cloudPosition) {
|
||||
String displayText = InstrumentationRegistry //
|
||||
.getTargetContext() //
|
||||
.getString(R.string.screen_cloud_settings_sign_out_from_cloud) + " " + cloudName;
|
||||
|
||||
UiObject signOutText = UiDevice.getInstance(getInstrumentation()).findObject(new UiSelector().text(displayText));
|
||||
signOutText.waitForExists(15000);
|
||||
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(cloudPosition, R.id.cloudName)) //
|
||||
.check(matches(withText(displayText)));
|
||||
}
|
||||
|
||||
private static Matcher<View> childAtPosition(final Matcher<View> parentMatcher, final int position) {
|
||||
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Child at position " + position + " in parent ");
|
||||
parentMatcher.describeTo(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
ViewParent parent = view.getParent();
|
||||
return parent instanceof ViewGroup && parentMatcher.matches(parent) //
|
||||
&& view.equals(((ViewGroup) parent).getChildAt(position));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test01EnableDebugModeLeadsToDebugMode() {
|
||||
openSettings(device);
|
||||
|
||||
try {
|
||||
new UiScrollable(new UiSelector().scrollable(true)).scrollToEnd(10);
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withChild(withText(R.string.screen_settings_debug_mode_label))) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(android.R.id.button1)) //
|
||||
.perform(click());
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Scrolling down failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02LoginDropboxCloudLeadsToLoggedInDropboxCloud() {
|
||||
openCloudServices(device);
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(DROPBOX, click()));
|
||||
|
||||
try {
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("android:id/button_once")) //
|
||||
.click();
|
||||
|
||||
device.waitForIdle();
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().text("Email")) //
|
||||
.setText("");
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().text("Password")) //
|
||||
.setText("");
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().description("Sign in")) //
|
||||
.click();
|
||||
|
||||
device.waitForIdle();
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().description("Allow")) //
|
||||
.click();
|
||||
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Dropbox login failed");
|
||||
}
|
||||
|
||||
device.waitForIdle();
|
||||
|
||||
checkLoginResult("Dropbox", DROPBOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test03LoginGoogleDriveCloudLeadsToLoggedInGoogleDriveCloud() {
|
||||
openCloudServices(device);
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(GOOGLE_DRIVE, click()));
|
||||
|
||||
try {
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("android:id/text1")) //
|
||||
.click();
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("android:id/button1")) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("GoogleDrive login failed");
|
||||
}
|
||||
|
||||
device.waitForIdle();
|
||||
|
||||
checkLoginResult("Google Drive", GOOGLE_DRIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test04LoginOneDriveLeadsToLoggedInOneDriveCloud() {
|
||||
openCloudServices(device);
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(ONEDRIVE, click()));
|
||||
|
||||
try {
|
||||
try {
|
||||
sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("i0116")) //
|
||||
.setText("");
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("idSIButton9")) //
|
||||
.click();
|
||||
|
||||
device.waitForWindowUpdate(null, 500);
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("i0118")) //
|
||||
.setText("");
|
||||
|
||||
device.waitForWindowUpdate(null, 500);
|
||||
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("idSIButton9")) //
|
||||
.click();
|
||||
|
||||
try {
|
||||
device //
|
||||
.findObject(new UiSelector().resourceId("idSIButton9")) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
// Do nothing because second click is normaly not necessary
|
||||
}
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("OneDrive login failed");
|
||||
}
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
checkLoginResult("OneDrive", ONEDRIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test05LoginWebdavCloudLeadsToLoggedInWebdavCloud() {
|
||||
openCloudServices(device);
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(WEBDAV, click()));
|
||||
|
||||
LoginWebdavClouds.loginWebdavClouds(activityTestRule.getActivity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test06LoginLocalCloudLeadsToLoggedInLocalCloud() {
|
||||
openCloudServices(device);
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(LOCAL, click()));
|
||||
|
||||
LoginLocalClouds.loginLocalClouds(device);
|
||||
}
|
||||
}
|
@ -1,668 +0,0 @@
|
||||
package org.cryptomator.presentation.ui.activity;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.UiObjectNotFoundException;
|
||||
import androidx.test.uiautomator.UiSelector;
|
||||
|
||||
import org.cryptomator.domain.CloudType;
|
||||
import org.cryptomator.presentation.CryptomatorApp;
|
||||
import org.cryptomator.presentation.R;
|
||||
import org.cryptomator.presentation.di.component.ApplicationComponent;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static android.R.id.button1;
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.Espresso.pressBack;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
import static androidx.test.espresso.action.ViewActions.swipeDown;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static java.lang.String.format;
|
||||
import static org.cryptomator.domain.executor.BackgroundTasks.awaitCompleted;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.DROPBOX;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.GOOGLE_DRIVE;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.LOCAL;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.ONEDRIVE;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.WEBDAV;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.addFolderInVaultsRoot;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.chooseSdCard;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.isToastDisplayed;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.removeFolderInVault;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.checkEmptyFolderHint;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.checkFileOrFolderAlreadyExistsErrorMessage;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.openSettings;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.withRecyclerView;
|
||||
import static org.cryptomator.presentation.ui.activity.FolderOperationsTest.openFolder;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assume.assumeThat;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@RunWith(Parameterized.class)
|
||||
public class FileOperationsTest {
|
||||
|
||||
private final UiDevice device = UiDevice.getInstance(getInstrumentation());
|
||||
private final Context context = InstrumentationRegistry.getTargetContext();
|
||||
private final Integer cloudId;
|
||||
@Rule
|
||||
public ActivityTestRule<SplashActivity> activityTestRule //
|
||||
= new ActivityTestRule<>(SplashActivity.class);
|
||||
private String packageName;
|
||||
|
||||
public FileOperationsTest(Integer cloudId, String cloudName) {
|
||||
this.cloudId = cloudId;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][] {{DROPBOX, "DROPBOX"}, {GOOGLE_DRIVE, "GOOGLE_DRIVE"}, {ONEDRIVE, "ONEDRIVE"}, {WEBDAV, "WEBDAV"}, {LOCAL, "LOCAL"}});
|
||||
}
|
||||
|
||||
static void isPermissionShown(UiDevice device) {
|
||||
if (!device //
|
||||
.findObject(new UiSelector().text("ALLOW")) //
|
||||
.waitForExists(1000L)) {
|
||||
throw new AssertionError("View with text <???> not found!");
|
||||
}
|
||||
}
|
||||
|
||||
static void grantPermission(UiDevice device) {
|
||||
try {
|
||||
device //
|
||||
.findObject(new UiSelector().text("ALLOW")) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Permission not found");
|
||||
}
|
||||
}
|
||||
|
||||
static void openFile(int nodePosition) {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(nodePosition, R.id.cloudFileText)) //
|
||||
.perform(click());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test00UploadFileWithCancelPressedWhileUploadingLeadsToNoNewFileInVault() {
|
||||
assumeThat(cloudId, is(not(LOCAL)));
|
||||
|
||||
packageName = activityTestRule.getActivity().getPackageName();
|
||||
|
||||
String nodeName = "foo.pdf";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
uploadFile(nodeName);
|
||||
|
||||
onView(withId(android.R.id.button3)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(swipeDown());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
try {
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(1, R.id.cloudFileText)) //
|
||||
.check(matches(withText(nodeName)));
|
||||
|
||||
throw new AssertionError("Canceling the upload should not lead to new cloud node");
|
||||
} catch (NullPointerException e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test01UploadFileLeadsToNewFileInVault() {
|
||||
packageName = activityTestRule.getActivity().getPackageName();
|
||||
String nodeName = "lala.png";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
uploadFile(nodeName);
|
||||
|
||||
device.waitForWindowUpdate(packageName, 15000);
|
||||
|
||||
checkFileDisplayText(nodeName, 1);
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02UploadAlreadyExistingFileAndCancelLeadsToNoNewFileInVault() {
|
||||
String nodeName = "lala.png";
|
||||
String subString;
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
// subString = subtextFromFile();
|
||||
|
||||
uploadFile(nodeName);
|
||||
|
||||
device.waitForWindowUpdate(packageName, 500);
|
||||
|
||||
onView(withText(R.string.dialog_button_cancel)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
// assertThat(subtextFromFile(), equalTo(subString));
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test03UploadAlreadyExistingFileAndReplaceLeadsToUpdatedFileInVault() {
|
||||
String nodeName = "lala.png";
|
||||
String subString;
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
subString = subtextFromFile();
|
||||
|
||||
uploadFile(nodeName);
|
||||
|
||||
device.waitForWindowUpdate(packageName, 500);
|
||||
|
||||
onView(withText(R.string.dialog_existing_file_positive_button)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
// assertThat(subtextFromFile(), not(equalTo(subString)));
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test04OpenFileLeadsToOpenFile() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openFile(1);
|
||||
|
||||
device.waitForWindowUpdate(null, 25000);
|
||||
|
||||
device.pressBack();
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test05RenameFileLeadsToFileWithNewName() {
|
||||
String fileName = "foo.png";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
renameFileTo(fileName, 1);
|
||||
|
||||
checkFileDisplayText(fileName, 1);
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test06RenameFileToExistingFolderLeadsToNothingChanged() {
|
||||
String fileName = "testFolder";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
renameFileTo(fileName, 1);
|
||||
|
||||
checkFileOrFolderAlreadyExistsErrorMessage(fileName);
|
||||
|
||||
onView(withId(android.R.id.button2)) //
|
||||
.perform(click());
|
||||
|
||||
checkFileDisplayText("foo.png", 1);
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test07MoveFileLeadsToFileWithNewLocation() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openSettings(device, 1);
|
||||
|
||||
openMoveFile();
|
||||
|
||||
openFolder(0);
|
||||
|
||||
onView(withId(R.id.chooseLocationButton)) //
|
||||
.perform(click());
|
||||
|
||||
openFolder(0);
|
||||
|
||||
checkFileDisplayText("foo.png", 0);
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test08MoveWithExistingFileLeadsToNoNewFile() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openFolder(0);
|
||||
|
||||
openSettings(device, 0);
|
||||
|
||||
openMoveFile();
|
||||
|
||||
onView(withId(R.id.chooseLocationButton)) //
|
||||
.perform(click());
|
||||
|
||||
isToastDisplayed( //
|
||||
format( //
|
||||
InstrumentationRegistry //
|
||||
.getTargetContext() //
|
||||
.getString(R.string.error_file_or_folder_exists), //
|
||||
"foo.png"), //
|
||||
activityTestRule);
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test09MoveWithExistingFolderLeadsToNoNewFile() {
|
||||
ApplicationComponent appComponent = ((CryptomatorApp) activityTestRule.getActivity().getApplication()).getComponent();
|
||||
|
||||
String moveFileNameAndTmpFolder = "foo.png";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
addFolderInVaultsRoot(appComponent, moveFileNameAndTmpFolder, CloudType.values()[cloudId]);
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
refreshCloudNodes();
|
||||
|
||||
openFolder(1);
|
||||
|
||||
openSettings(device, 0);
|
||||
|
||||
openMoveFile();
|
||||
|
||||
pressBack();
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.chooseLocationButton)) //
|
||||
.perform(click());
|
||||
|
||||
isToastDisplayed( //
|
||||
InstrumentationRegistry //
|
||||
.getTargetContext() //
|
||||
.getString(R.string.error_file_or_folder_exists), //
|
||||
activityTestRule);
|
||||
|
||||
removeFolderInVault(appComponent, moveFileNameAndTmpFolder, CloudType.values()[cloudId]);
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test10ShareFileWithCryptomatorLeadsToNewFileInVault() {
|
||||
String newFileName = "fooBar.png";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openFolder(0);
|
||||
|
||||
shareFile(newFileName, cloudId, 0);
|
||||
|
||||
isToastDisplayed(context.getString(R.string.screen_share_files_msg_success), activityTestRule);
|
||||
|
||||
refreshCloudNodes();
|
||||
|
||||
checkFileDisplayText(newFileName, 1);
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test11ShareExistingFileAndReplaceWithCryptomatorLeadsToUpdatedFileInVault() {
|
||||
String newFileName = "fooBar.png";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openFolder(0);
|
||||
|
||||
shareFile(newFileName, cloudId, 0);
|
||||
|
||||
onView(withId(android.R.id.button1)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
isToastDisplayed(context.getString(R.string.screen_share_files_msg_success), activityTestRule);
|
||||
|
||||
refreshCloudNodes();
|
||||
|
||||
checkFileDisplayText(newFileName, 1); // check before and compare with after upload
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test12ShareExistingFileWithoutReplaceWithCryptomatorLeadsToNotUpdatedFileInVault() {
|
||||
String newFileName = "fooBar.png";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openFolder(0);
|
||||
|
||||
shareFile(newFileName, cloudId, 0);
|
||||
|
||||
onView(withId(android.R.id.button3)) //
|
||||
.perform(click());
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test13RenameFileToExistingFileLeadsToNothingChanged() {
|
||||
String fileName = "fooBar.png";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openFolder(0);
|
||||
|
||||
renameFileTo(fileName, 0);
|
||||
|
||||
checkFileOrFolderAlreadyExistsErrorMessage(fileName);
|
||||
|
||||
onView(withId(android.R.id.button2)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
checkFileDisplayText("foo.png", 0);
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test14DeleteFileLeadsToRemovedFile() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
openFolder(0);
|
||||
|
||||
deleteNodeOnPosition(0);
|
||||
awaitCompleted();
|
||||
deleteNodeOnPosition(0);
|
||||
|
||||
checkEmptyFolderHint();
|
||||
|
||||
pressBack();
|
||||
|
||||
deleteTestFolder();
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
private void deleteNodeOnPosition(int position) {
|
||||
openSettings(device, position);
|
||||
|
||||
onView(withId(R.id.delete_file)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(android.R.id.button1)) //
|
||||
.perform(click());
|
||||
}
|
||||
|
||||
private void refreshCloudNodes() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(swipeDown());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void shareFile(String newFilename, int vaultPosition, int filePosition) {
|
||||
awaitCompleted();
|
||||
|
||||
openSettings(device, filePosition);
|
||||
|
||||
openEncryptWithCryptomator();
|
||||
|
||||
chooseShareLocation(vaultPosition);
|
||||
|
||||
checkPathInSharingScreen("/testFolder", vaultPosition);
|
||||
|
||||
onView(withId(R.id.fileName)) //
|
||||
.perform(replaceText(newFilename), closeSoftKeyboard());
|
||||
|
||||
onView(withId(R.id.saveFiles)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void chooseShareLocation(int nodePosition) {
|
||||
onView(withRecyclerView(R.id.locationsRecyclerView) //
|
||||
.atPositionOnView(nodePosition, R.id.vaultName)) //
|
||||
.perform(click());
|
||||
|
||||
device.waitForWindowUpdate(packageName, 300);
|
||||
|
||||
onView(withRecyclerView(R.id.locationsRecyclerView) //
|
||||
.atPositionOnView(nodePosition, R.id.chooseFolderLocation)) //
|
||||
.perform(click());
|
||||
|
||||
openFolder(0);
|
||||
|
||||
onView(withId(R.id.chooseLocationButton)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void checkPathInSharingScreen(String path, int nodePosition) {
|
||||
onView(withRecyclerView(R.id.locationsRecyclerView) //
|
||||
.atPositionOnView(nodePosition, R.id.chosenLocation)) //
|
||||
.check(matches(withText(path)));
|
||||
}
|
||||
|
||||
private void deleteTestFolder() {
|
||||
awaitCompleted();
|
||||
|
||||
openSettings(device, 0);
|
||||
|
||||
onView(withId(R.id.delete_folder)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(android.R.id.button1)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
checkEmptyFolderHint();
|
||||
}
|
||||
|
||||
private void uploadFile(String nodeName) {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.floatingActionButton)) //
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.upload_files)) //
|
||||
.perform(click());
|
||||
|
||||
chooseSdCard(device);
|
||||
|
||||
try {
|
||||
device //
|
||||
.findObject(new UiSelector().text(nodeName)) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Image " + nodeName + " not available");
|
||||
}
|
||||
}
|
||||
|
||||
private void renameFileTo(String name, int nodePosition) {
|
||||
awaitCompleted();
|
||||
|
||||
openSettings(device, nodePosition);
|
||||
|
||||
onView(withId(R.id.rename_file)) //
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.et_rename)) //
|
||||
.perform(replaceText(name), closeSoftKeyboard());
|
||||
|
||||
onView(allOf( //
|
||||
withId(button1), //
|
||||
withText(R.string.dialog_rename_node_positive_button))) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void checkFileDisplayText(String assertNodeText, int nodePosition) {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(nodePosition, R.id.cloudFileText)) //
|
||||
.check(matches(withText(assertNodeText)));
|
||||
}
|
||||
|
||||
private void openMoveFile() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.move_file)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void openEncryptWithCryptomator() {
|
||||
openShareFile();
|
||||
try {
|
||||
device //
|
||||
.findObject(new UiSelector().text(context.getString(R.string.share_with_label))) //
|
||||
.waitForExists(30000L);
|
||||
device //
|
||||
.findObject(new UiSelector().text(context.getString(R.string.share_with_label))) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Share with Cryptomator not available");
|
||||
}
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void openShareFile() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.share_file)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private String subtextFromFile() {
|
||||
try {
|
||||
final UiSelector docList = new UiSelector() //
|
||||
.resourceId("org.cryptomator:id/cloudFileContent");
|
||||
|
||||
return device //
|
||||
.findObject(docList.childSelector(new UiSelector(). //
|
||||
resourceId("org.cryptomator:id/cloudFileSubText") //
|
||||
.index(0))) //
|
||||
.getText();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Folder 0 not found");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,313 +0,0 @@
|
||||
package org.cryptomator.presentation.ui.activity;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
|
||||
import org.cryptomator.presentation.R;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.Espresso.pressBack;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static org.cryptomator.domain.executor.BackgroundTasks.awaitCompleted;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.DROPBOX;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.GOOGLE_DRIVE;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.LOCAL;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.ONEDRIVE;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.WEBDAV;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.isToastDisplayed;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.checkEmptyFolderHint;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.checkFileOrFolderAlreadyExistsErrorMessage;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.openSettings;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.withRecyclerView;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@RunWith(Parameterized.class)
|
||||
public class FolderOperationsTest {
|
||||
|
||||
private final UiDevice device = UiDevice.getInstance(getInstrumentation());
|
||||
private final Context context = InstrumentationRegistry.getTargetContext();
|
||||
private final Integer cloudId;
|
||||
@Rule
|
||||
public ActivityTestRule<SplashActivity> activityTestRule //
|
||||
= new ActivityTestRule<>(SplashActivity.class);
|
||||
|
||||
public FolderOperationsTest(Integer cloudId, String cloudName) {
|
||||
this.cloudId = cloudId;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters(name = "{1}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][] {{DROPBOX, "DROPBOX"}, {GOOGLE_DRIVE, "GOOGLE_DRIVE"}, {ONEDRIVE, "ONEDRIVE"}, {WEBDAV, "WEBDAV"}, {LOCAL, "LOCAL"}});
|
||||
}
|
||||
|
||||
static void openFolder(int nodePosition) {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(nodePosition, R.id.cloudFolderText)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test00CreateFolderLeadsToFolderInVault() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
String folderName = "testFolder";
|
||||
createFolder(folderName);
|
||||
|
||||
checkFolderCreationResult(folderName, "/0/testVault", 0);
|
||||
pressBack();
|
||||
|
||||
folderName = "testFolder1";
|
||||
createFolder(folderName);
|
||||
|
||||
checkFolderCreationResult(folderName, "/0/testVault", 1);
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test01CreateExistingFolderLeadsToNoNewFolderInVault() {
|
||||
String folderName = "testFolder";
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
createFolder(folderName);
|
||||
|
||||
checkFileOrFolderAlreadyExistsErrorMessage(folderName);
|
||||
|
||||
onView(withId(android.R.id.button2)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02OpenFolderLeadsToOpenFolder() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openFolder(0);
|
||||
|
||||
checkEmptyFolderHint();
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test03RenameFolderLeadsToFolderWithNewName() {
|
||||
String newFolderName = "testFolder2";
|
||||
int nodePosition = 1;
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
renameFolderTo(newFolderName, nodePosition);
|
||||
|
||||
checkFolderDisplayText(newFolderName, nodePosition);
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test04RenameFolderToAlreadyExistFolderLeadsToSameFolderName() {
|
||||
String newFolderName = "testFolder";
|
||||
int nodePosition = 1;
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
renameFolderTo(newFolderName, nodePosition);
|
||||
|
||||
checkFileOrFolderAlreadyExistsErrorMessage(newFolderName);
|
||||
|
||||
onView(withId(android.R.id.button2)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test05MoveFolderLeadsToFolderWithNewLocation() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openSettings(device, 1);
|
||||
|
||||
openMoveFolder();
|
||||
|
||||
openFolder(0);
|
||||
|
||||
onView(withId(R.id.chooseLocationButton)) //
|
||||
.perform(click());
|
||||
|
||||
openFolder(0);
|
||||
|
||||
checkFolderDisplayText("testFolder2", 0);
|
||||
|
||||
openFolder(0);
|
||||
|
||||
checkEmptyFolderHint();
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test06MoveFolderToAlreadyExistingFolderLeadsToErrorMessage() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openSettings(device, 0);
|
||||
|
||||
openMoveFolder();
|
||||
|
||||
onView(withId(R.id.chooseLocationButton)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
isToastDisplayed( //
|
||||
context.getString(R.string.error_file_or_folder_exists), //
|
||||
activityTestRule);
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test07DeleteFolderLeadsToRemovedFolder() {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.recyclerView)) //
|
||||
.perform(actionOnItemAtPosition(cloudId, click()));
|
||||
|
||||
openFolder(0);
|
||||
|
||||
openSettings(device, 0);
|
||||
|
||||
onView(withId(R.id.delete_folder)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(android.R.id.button1)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
checkEmptyFolderHint();
|
||||
|
||||
pressBack();
|
||||
pressBack();
|
||||
}
|
||||
|
||||
private void openMoveFolder() {
|
||||
onView(withId(R.id.move_folder)) //
|
||||
.perform(click());
|
||||
}
|
||||
|
||||
private void createFolder(String name) {
|
||||
awaitCompleted();
|
||||
|
||||
onView(withId(R.id.floatingActionButton)) //
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.create_new_folder)) //
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.et_folder_name)) //
|
||||
.perform(replaceText(name), closeSoftKeyboard());
|
||||
|
||||
onView(allOf( //
|
||||
withId(android.R.id.button1), //
|
||||
withText(R.string.screen_enter_vault_name_button_text))) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void checkFolderCreationResult(String folderName, String path, int position) {
|
||||
checkFolderDisplayText(folderName, position);
|
||||
|
||||
openSettings(device, position);
|
||||
|
||||
onView(allOf( //
|
||||
withId(R.id.tv_folder_path), //
|
||||
withText(path))) //
|
||||
.check(matches(withText(path)));
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void renameFolderTo(String name, int nodePosition) {
|
||||
awaitCompleted();
|
||||
|
||||
openSettings(device, nodePosition);
|
||||
|
||||
onView(withId(R.id.change_cloud)) //
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.et_rename)) //
|
||||
.perform(replaceText(name), closeSoftKeyboard());
|
||||
|
||||
onView(allOf( //
|
||||
withId(android.R.id.button1), //
|
||||
withText(R.string.dialog_rename_node_positive_button))) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
}
|
||||
|
||||
private void checkFolderDisplayText(String assertNodeText, int nodePosition) {
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(nodePosition, R.id.cloudFolderText)) //
|
||||
.check(matches(withText(assertNodeText)));
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package org.cryptomator.presentation.ui.activity;
|
||||
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.UiObjectNotFoundException;
|
||||
import androidx.test.uiautomator.UiSelector;
|
||||
|
||||
import org.cryptomator.presentation.R;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static org.cryptomator.domain.executor.BackgroundTasks.awaitCompleted;
|
||||
import static org.cryptomator.presentation.ui.TestUtil.chooseSdCard;
|
||||
import static org.cryptomator.presentation.ui.activity.BasicNodeOperationsUtil.withRecyclerView;
|
||||
|
||||
public class LoginLocalClouds {
|
||||
|
||||
private static UiDevice device;
|
||||
|
||||
public static void loginLocalClouds(UiDevice uiDevice) {
|
||||
device = uiDevice;
|
||||
String folderName = "0";
|
||||
createNewStorageAccessCloudCloud(folderName);
|
||||
awaitCompleted();
|
||||
checkResult(folderName);
|
||||
}
|
||||
|
||||
private static void createNewStorageAccessCloudCloud(String folderName) {
|
||||
onView(withId(R.id.floating_action_button)) //
|
||||
.perform(click());
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
chooseSdCard(device);
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
openFolder0();
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
chooseFolder(device);
|
||||
}
|
||||
|
||||
private static void openFolder0() {
|
||||
try {
|
||||
final UiSelector docList = new UiSelector() //
|
||||
.resourceId("com.android.documentsui:id/container_directory") //
|
||||
.childSelector( //
|
||||
new UiSelector() //
|
||||
.resourceId("com.android.documentsui:id/dir_list"));
|
||||
|
||||
device //
|
||||
.findObject(docList.childSelector(new UiSelector().text("0"))) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Folder 0 not found");
|
||||
}
|
||||
}
|
||||
|
||||
public static void chooseFolder(UiDevice device) {
|
||||
try {
|
||||
|
||||
final UiSelector docList = new UiSelector() //
|
||||
.resourceId("com.android.documentsui:id/container_save");
|
||||
|
||||
device //
|
||||
.findObject(docList.childSelector(new UiSelector().resourceId("android:id/button1"))) //
|
||||
.click();
|
||||
} catch (UiObjectNotFoundException e) {
|
||||
throw new AssertionError("Folder 0 not found");
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkResult(String folderName) {
|
||||
onView(withRecyclerView(R.id.recyclerView) //
|
||||
.atPositionOnView(0, R.id.cloudText)) //
|
||||
.perform(click());
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
package org.cryptomator.presentation.ui.activity;
|
||||
|
||||
import org.cryptomator.presentation.R;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.pressBack;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.cryptomator.domain.executor.BackgroundTasks.awaitCompleted;
|
||||
import static org.cryptomator.presentation.ui.activity.LoginWebdavClouds.WebdavCloudCredentials.notSpecialWebdavClouds;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
|
||||
class LoginWebdavClouds {
|
||||
|
||||
private static final String localUrl = "192.168.0.108";
|
||||
|
||||
static void loginWebdavClouds(SplashActivity activity) {
|
||||
loginStandardWebdavClouds();
|
||||
|
||||
/*
|
||||
* loginAuthenticationFailWebdavCloud(activity);
|
||||
*
|
||||
* loginSelfSignedWebdavCloud();
|
||||
*
|
||||
* loginRedirectToHttpsWebdavCloud();
|
||||
*
|
||||
* loginRedirectToUrlWebdavCloud();
|
||||
*/
|
||||
}
|
||||
|
||||
private static void loginStandardWebdavClouds() {
|
||||
for (WebdavCloudCredentials webdavCloudCredential : notSpecialWebdavClouds()) {
|
||||
createNewCloud();
|
||||
|
||||
enterCredentials(webdavCloudCredential);
|
||||
|
||||
startLoginProcess();
|
||||
|
||||
awaitCompleted();
|
||||
|
||||
checkResult(webdavCloudCredential);
|
||||
}
|
||||
|
||||
pressBack();
|
||||
}
|
||||
|
||||
private static void createNewCloud() {
|
||||
onView(withId(R.id.floating_action_button)) //
|
||||
.perform(click());
|
||||
}
|
||||
|
||||
private static void startLoginProcess() {
|
||||
onView(withId(R.id.createCloudButton)) //
|
||||
.perform(click());
|
||||
}
|
||||
|
||||
/*
|
||||
* private static void loginAuthenticationFailWebdavCloud(SplashActivity activity) {
|
||||
* createNewCloud();
|
||||
* enterCredentials(AUTHENTICATION_FAIL);
|
||||
* startLoginProcess();
|
||||
*
|
||||
* onView(withText(R.string.error_authentication_failed)) //
|
||||
* .inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))) //
|
||||
* .check(matches(isDisplayed()));
|
||||
*
|
||||
* onView(allOf( //
|
||||
* withId(R.id.et_url_port), //
|
||||
* withText(AUTHENTICATION_FAIL.url)));
|
||||
*
|
||||
* onView(allOf( //
|
||||
* withId(R.id.et_user), //
|
||||
* withText(AUTHENTICATION_FAIL.username)));
|
||||
*
|
||||
* onView(allOf( //
|
||||
* withId(R.id.et_password), //
|
||||
* withText(AUTHENTICATION_FAIL.password)));
|
||||
*
|
||||
* pressBack();
|
||||
* }
|
||||
*
|
||||
* private static void loginSelfSignedWebdavCloud() {
|
||||
* createNewCloud();
|
||||
* enterCredentials(SELF_SIGNED_HTTPS);
|
||||
* startLoginProcess();
|
||||
* clickOk();
|
||||
* startLoginProcess();
|
||||
* checkResult(SELF_SIGNED_HTTPS);
|
||||
* }
|
||||
*
|
||||
* private static void loginRedirectToHttpsWebdavCloud() {
|
||||
* createNewCloud();
|
||||
* enterCredentials(REDIRECT_TO_HTTPS);
|
||||
* startLoginProcess();
|
||||
* clickOk();
|
||||
* clickOk();
|
||||
* startLoginProcess();
|
||||
* checkResult(REDIRECT_TO_HTTPS);
|
||||
* }
|
||||
*
|
||||
* private static void loginRedirectToUrlWebdavCloud() {
|
||||
* createNewCloud();
|
||||
* enterCredentials(REDIRECT_TO_URL);
|
||||
* startLoginProcess();
|
||||
* clickOk();
|
||||
* startLoginProcess();
|
||||
* }
|
||||
*/
|
||||
|
||||
private static void enterCredentials(WebdavCloudCredentials webdavCloudCredential) {
|
||||
onView(withId(R.id.urlPortEditText)) //
|
||||
.perform(replaceText(webdavCloudCredential.url));
|
||||
|
||||
onView(withId(R.id.userNameEditText)) //
|
||||
.perform(replaceText(webdavCloudCredential.username));
|
||||
|
||||
onView(withId(R.id.passwordEditText)) //
|
||||
.perform( //
|
||||
replaceText(webdavCloudCredential.password), //
|
||||
closeSoftKeyboard());
|
||||
}
|
||||
|
||||
private static void checkResult(WebdavCloudCredentials webdavCloudCredential) {
|
||||
onView(allOf( //
|
||||
withId(R.id.cloudText), //
|
||||
withText(webdavCloudCredential.displayUrl)));
|
||||
|
||||
onView(allOf( //
|
||||
withId(R.id.cloudSubText), //
|
||||
withText(webdavCloudCredential.username + " • ")));
|
||||
}
|
||||
|
||||
enum WebdavCloudCredentials {
|
||||
GMX("https://webdav.mc.gmx.net/", "webdav.mc.gmx.net", "jraufelder@gmx.de", "mG7!3B3Mx"), //
|
||||
/*
|
||||
* FREENET("https://webmail.freenet.de/webdav", "webmail.freenet.de", "milestone@freenet.de", "rF7!3B3Et")
|
||||
*
|
||||
* , //
|
||||
* /*
|
||||
* AUTHENTICATION_FAIL("https://webdav.mc.gmx.net/", "webdav.mc.gmx.net", "bla@bla.de", "bla"), //
|
||||
* SELF_SIGNED_HTTPS("https://" + localUrl + "/webdav", localUrl + "/webdav", "bla@bla.de", "bla"), //
|
||||
* REDIRECT_TO_HTTPS("http://" + localUrl + "/webdav", localUrl + "/webdav", "bla@bla.de", "bla"), //
|
||||
* REDIRECT_TO_URL("https://" + localUrl + "/bar/baz", localUrl + "/bar/baz", "bla@bla.de", "bla")
|
||||
*/;
|
||||
|
||||
private final String url;
|
||||
private final String displayUrl;
|
||||
private final String username;
|
||||
private final String password;
|
||||
|
||||
WebdavCloudCredentials(String url, String displayUrl, String username, String password) {
|
||||
this.url = url;
|
||||
this.displayUrl = displayUrl;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
static List<WebdavCloudCredentials> notSpecialWebdavClouds() {
|
||||
return Collections.singletonList(GMX/* , FREENET */);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user